Dissect Spam Karma
Spam Karma是一个非常棒的WP插件,用来过滤WP的留言中的Spam。用过WP的朋友或多或少的都受到过Spam疯狂的攻击,SK可以很大程度上的帮助你从这种困境中解脱出来。
Karma [’ka:ma] - 卡马(镍铬丝精密级),是一种度量单位。SK中,用Karma来表示一个留言的"Spam等级",最后根据一个留言的Karma值来判定这个留言是不是Spam。
Update:Ben提醒Karma的另一个意思是佛教中的”因果报应”,也许,这个意思更符合作者的本意。
SK本身是一个WP的插件,而为了有更好的扩展性,SK2也是采用插件的形式来工作的。当然,它的插件工作比起WP的机制来说,简单很多。
Plugin Framework大概有几种模式:
一种如Eclipse那样,采用"微内核+插件"。除了一个微小的核(包括Plugin Framework)之外,其它任何东西都是插件。这就需要尽可能的多留出扩展点,每个插件在做的时候要时刻考虑别人可能从哪个地方来扩展。
一种如Firefox、WordPress这样,主程序相对独立,在独立Standalone程序的基础上,暴露出一些接口,这种扩展性也非常强,当然,这是建立在StandAlone程序暴露出了足够丰富的扩展点的基础上。
这两种都有非常丰富的扩展性,比较适合做大型的Framework,比如Eclipse RCP等等。另外,这两种模式中往往都有"Extension Point"的概念。
另一种方式就简单多了,系统规定好几个Interface,插件只需要实现这几个Interface即可。这样的插件Framework,实现起来比较简单,但是扩展性也非常有限,比较适合做比较专用的小型程序。这种插件的核心概念就是"Interface"。
SK2就是一个这样的例子。
先来看看SK扩展WP的部分:
SK Extend了以下几个Extension:
- <?php
- add_action('comment_form', 'sk2_form_insert');
- add_action('admin_menu', 'sk2_add_options');
- add_action('admin_head', 'sk2_output_admin_css');
- add_filter('pre_comment_approved', 'sk2_fix_approved');
- add_action('comment_post', 'sk2_filter_comment');
- add_action('wp_footer', 'sk2_insert_footer', 3);
- ?>
其中,"admin_menu"是用来在菜单中添加一个Option选项,"admin_head"是在Head中添加SK自己的CSS信息,"wp_footer"是在Blog的尾部添加一个SK的Copyright信息。
其余的三个就比较重要了
Extend "comment_form" Extension的是函数"sk2_form_insert",顾名思义,是在Comment Form Load的时候添加代码。
"pre_comment_approved"是一个Trick,防止WP自动发送Comment Notification。
"comment_post"是最重要的一个扩展点,它是在一条Comment被提交之后来调用的,SK的处理也正在此处。
再来看看SK2插件,前面说过它是属于比较简单的第三种形式,看看它的Interface是怎么定义的:
这个类中有三个核心的接口函数分别是
- <?php
- function filter_this(&$cmt_object)
- { // override this to do your own filtering
- log_msg (__("Default filter (no action) called for plugin: ", 'sk2') . $name, 3, $cmt_object->ID);
- }
- function treat_this(&$cmt_object)
- { // override this to do your own treatment
- log_msg (__("Default treatment (no action) called for plugin: ", 'sk2') . $name, 3, $cmt_object->ID);
- }
- function form_insert($post_ID)
- {
- // do nothing: only there as a placeholder for plugins
- return true;
- }
- ?>
每个插件在filter_this中对留言进行打分,在treat_this中进行其它一些操作,如果需要在Comment Form中添加一些元素(比如增加一些JS代码,或者增加一个图像识别的小模块儿),就需要重载form_insert函数了。
插件定义中另外两个比较重要的函数是两个更改Karma的函数:
- <?php
- function hit_karma(&$cmt_object, $karma_diff, $reason = "")
- {
- $cmt_object->modify_karma(- $this->get_option_value('weight') * $karma_diff, $this->name, $reason);
- }
- function raise_karma(&$cmt_object, $karma_diff, $reason = "")
- {
- $cmt_object->modify_karma($this->get_option_value('weight') * $karma_diff, $this->name, $reason);
- }
- ?>
再看看看SK是怎样处理一条留言的。
在收到一条留言后,SK会首先从数据库中取出将要处理的那条留言,然后加载所有的插件(这些插件都在"plugin"目录下)。
核心函数是sk2_core类中的函数process_comment
- <?php
- function process_comment($comment_ID = 0)
- {
- if ($comment_ID && ($comment_ID != @$this->cur_comment->ID))
- $this->cur_comment = new sk2_comment($comment_ID, $this->post_proc); // ? does PHP garbage collect? sure hope so...
- global $sk2_settings;
- $this->filter_comment();
- if ($bias = $sk2_settings->get_core_settings("general_bias"))
- $this->cur_comment->modify_karma($bias, "core", "Severity settings adjustment.");
- $this->treat_comment();
- $this->set_comment_sk_info();
- }
- ?>
其中,"$this->filter_comment()"和"$this->treat_comment()"都是依次调用每个插件中的函数:
- <?php
- foreach ($this->plugins as $plugin)
- {
- if ($plugin[1]->is_filter()
- && ($plugin[1]->is_enabled())
- && ($this->post_proc
- || (((int) $this->cur_comment->karma > $plugin[1]->skip_under)
- && ((int) $this->cur_comment->karma < $plugin[1]->skip_above))))
- $plugin[1]->filter_this($this->cur_comment);
- }
- ?>
这样,每一条留言都会流经所有的SK插件,所有的插件串起来,正是一个Chain of Responsibility Pattern。这个串起来也有学问,就是根据每个插件给自己定义的优先级,插到插件的列表中。
另外,每个插件还都可以设置自己的Weight,最后的Karma值乘以这个Weight,这样,用户就可以通过调整Weight和Enable/Disable Plugin来自动调节本地的Anti Spam Policy了。
我们来举几个例子:
sk2_referrer_check_plugin - Checks the TrackBack source page to ensure it contains a link to the site.
这个插件就是来检查TrackBack的Source Page中是不是有这个Blog的链接,来看看是不是Trackback Spam。
- <?php
- function filter_this(&$cmt_object)
- {
- if ($cmt_object->is_pingback())
- {
- $this->raise_karma($cmt_object, 4, "Pingback bonus"); // not impossible to spoof, only give a minor bonus
- return;
- }
- if (! $cmt_object->is_trackback())
- return;
- //if( ! ini_get('allow_url_fopen'))
- //{
- // $this->log_msg("<p>::CoolCode_BLOCK_1::</p> is disabled on this PHP install: sk2_referrer_check_plugin cannot run." , 5);
- // return;
- //}
- //print_r($cmt_object->author_url);
- if (empty($cmt_object->author_url['href']))
- return;
- // first check that the domain even replies
- if (!empty($cmt_object->author_url['full_domain']) && (gethostbyname($cmt_object->author_url['full_domain'] . ".") != $cmt_object->author_url['full_domain'] . "."))
- {
- $source_content = sk2_get_url_content($cmt_object->author_url['href'], 0, true);
- $this_server = str_replace(array("www.", "http://"), "", $_SERVER["HTTP_HOST"]);
- }
- else
- $source_content = "";
- if(empty($source_content))
- {
- $log = sprintf(__("Trackback Source Site (%s) unreachable.", 'sk2'), "<em>". $cmt_object->author_url['href'] ."</em>");
- $this->hit_karma($cmt_object, 5, $log);
- $this->log_msg($log , 2);
- }
- elseif (strpos(strtolower($source_content), strtolower($this_server)) !== FALSE)
- {
- $log = sprintf(__("Trackback Source Site (%s) <strong>does</strong> contain Blog URL domain (%s).", 'sk2'), "<em>". $cmt_object->author_url['href'] ."</em>", "<em>$this_server</em>");
- $this->raise_karma($cmt_object, 2, $log); // not impossible to spoof, only give a minor bonus
- $this->log_msg($log , 1);
- }
- else
- {
- $log = sprintf(__("Trackback Source Site (%s) does <strong>not</strong> contain Blog URL domain (%s).", 'sk2'), "<em>". $cmt_object->author_url['href'] ."</em>", "<em>$this_server</em>");
- $this->hit_karma($cmt_object, 7, $log);
- $this->log_msg($log , 1);
- }
- }
- ?>
可见,如果无法访问Source Page,就把Karma值减5,如果能够找到本页链接,就加2(not impossible to spoof, only give a minor bonus),如果不能找到本页链接,就减7。
sk2_basic_plugins.php中包括一些比较简单的插件,比如:
sk2_old_post_plugin
Stricter on old posts showing no recent activity.
如果留言日期和Post发表的日期超过了作者设定的期限(比如3个月),那么就会适当的减少Karma值。这也跟我们的常识是对应的,历史越久的文章,被评论的概率也应该越低。
看一个实际例子:
-3.08: Entry posted 8 months ago. 1 comments in the past 15 days. Current Karma: -13
sk2_link_count_plugin用来计算Comment中的链接数。如果完全没有链接,Karma += 2,如果Content中没有链接,其它信息有, Karma += 0.5,如果包含Link或者URL_No_Link,则给予一定的扣分,Link越多,扣分越多。这样,很多发很多Link的Spam都能被捕获。
插件队列中的最后一个插件一定是sk2_anubis_plugin,这是决定留言命运的最终场所。这个插件根据留言的Karma值和我们预设的Threshold来判定这个留言最终的结果。这里的类比很有意思:
Spam -> Hell
Moderated -> Purgatory
Approved -> Paradise
最后,看看SK真实处理的效果。
-18.17.
-13.09: Comment contains: 2 linked URLs and 4 unlinked URLs: total link coef: 5 >= threshold (2). Non-URL text size: 208 chars.-2: Browser doesn’t support Javascript-3.08: Entry posted 7 months, 3 weeks ago. 1 comments in the past 15 days. Current Karma: -15.
这是我的Blog上SK最近捕获的一条留言,它的最终Karma值为 -18.17,各项扣分情况如上,最终被扔到了Hell里面。
SK采用这种插件方式非常好,一方面,它并不是一个Framework,并不需要完全扩充自己的功能,因此,没有必要利用"Extension Point"那样的机制,
另一方面,采用插件机制可以很好的提供扩展性,可以非常简单的做新的插件来增加系统的功能。
另外,WEB应用中的插件系统,和Desktop的还是有很大不同。Desktop中的Plugin Framework一般在系统启动的时候加载,在退出的时候来卸载。而对于Web方式,每一个用户访问的Session其实就伴随着系统的启动和退出,因此,插件的初始化、加载也非常频繁。因此,在插件系统的设计上,也应该考虑到这些情况。
Popularity: 36%
Related entries:
- No Related Posts

February 20th, 2006 at 1:02 am
Karma 是佛教的词,“因果报应”。
February 20th, 2006 at 9:22 am
谢谢Ben,这样想来,这个解释也许才是作者的意思。