root/branches/release-39/plugins/TypePadAntiSpam/TypePadAntiSpam.pl @ 2461

Revision 2461, 12.1 kB (checked in by bchoate, 18 months ago)

Checkin of TypePad AntiSpam plugin; MT version bump.

Line 
1# TypePad AntiSpam Plugin for Movable Type
2# Copyright (C) 2008, Six Apart, Ltd.
3# Derived from Tim Appnel's MT Akismet plugin for Movable Type.
4# This program is distributed under the terms of the
5# GNU General Public License, version 2.
6#
7# $Id: TypePadAntiSpam.pl 82073 2008-05-29 02:58:04Z fyoshimatsu $
8
9package MT::Plugin::TypePadAntiSpam;
10
11use strict;
12
13use base qw( MT::Plugin );
14our $VERSION = '1.0';
15
16my $plugin;
17{
18    my $settings = [
19        ['api_key', { Scope => 'system'} ],
20        ['weight',  { Default => 1, Scope => 'blog'}],
21        ['service_host',  { Default => 'api.antispam.typepad.com', Scope => 'system'}],
22    ];
23    my $about = {
24        name                   => 'TypePad AntiSpam',
25        id                     => 'typepadantispam',
26        key                    => __PACKAGE__,
27        author_name            => 'Six Apart Ltd.',
28        author_link            => 'http://www.sixapart.com/',
29        version                => $VERSION,
30        blog_config_template   => 'config.tmpl',
31        system_config_template => 'system.tmpl',
32        settings               => MT::PluginSettings->new($settings),
33        l10n_class             => 'TypePadAntiSpam::L10N',
34        registry => {
35            callbacks => {
36                handle_spam => \&handle_junk,
37                handle_ham => \&handle_not_junk,
38                'MT::Comment::pre_save' => \&pre_save_obj,
39                'MT::TBPing::pre_save' => \&pre_save_obj,
40            },
41            junk_filters => {
42                'TypePadAntiSpam' => {
43                    label => 'TypePad AntiSpam',
44                    code => \&typepadantispam_score,
45                },
46            },
47            tags => {
48                function => {
49                    TypePadAntiSpamCounter => \&_hdlr_tpas_counter,
50                },
51            },
52            widgets => {
53                typepadantispam => {
54                    label      => 'TypePad AntiSpam',
55                    template   => 'stats_widget.tmpl',
56                    handler    => \&stats_widget, 
57                    set        => 'sidebar',
58                    singular   => 1,
59                    order      => 2.1,
60                    condition  => sub {
61                        return $plugin->api_key ? 1 : 0;
62                    },
63                },
64            },
65        },
66    };
67    $plugin = __PACKAGE__->new($about);
68}
69MT->add_plugin($plugin);
70if (MT->version_number < 4) {
71    MT->add_callback('HandleJunk',    5, $plugin, \&handle_junk);
72    MT->add_callback('HandleNotJunk', 5, $plugin, \&handle_not_junk);
73    MT->register_junk_filter({
74        name => 'TypePadAntiSpam',
75        code => \&typepadantispam_score
76    });
77    require MT::Template::Context;
78    MT::Template::Context->add_tag( TypePadAntiSpamCounter => \&_hdlr_tpas_counter );
79}
80
81#--- plugin handlers
82
83sub instance {
84    return $plugin;
85}
86
87sub description {
88    my $plugin = shift;
89    my $app = MT->instance;
90    my $blog;
91    if ($app->isa('MT::App::CMS')) {
92        $blog = $app->blog;
93    }
94    my $sys_blocked = $plugin->blocked();
95    my $desc = '<p>' . $plugin->translate('TypePad AntiSpam is a free service from Six Apart that helps protect your blog from comment and TrackBack spam. The TypePad AntiSpam plugin will send every comment or TrackBack submitted to your blog to the service for evaluation, and Movable Type will filter items if TypePad AntiSpam determines it is spam. If you discover that TypePad AntiSpam incorrectly classifies an item, simply change its classification by marking it as "Spam" or "Not Spam" from the Manage Comments screen, and TypePad AntiSpam will learn from your actions. Over time the service will improve based on reports from its users, so take care when marking items as "Spam" or "Not Spam."') . '</p>';
96
97    if ($blog) {
98        my $blog_blocked = $blog ? $plugin->blocked($blog) : 0;
99        $desc .= '<p>' . $plugin->translate('So far, TypePad AntiSpam has blocked [quant,_1,message,messages] for this blog, and [quant,_2,message,messages] system-wide.', $blog_blocked, $sys_blocked) . '</p>';
100    } else {
101        $desc .= '<p>' . $plugin->translate('So far, TypePad AntiSpam has blocked [quant,_1,message,messages] system-wide.', $sys_blocked) . '</p>';
102    }
103    return $desc;
104}
105
106sub _hdlr_tpas_counter {
107    my ($ctx, $args, $cond) = @_;
108    my $blog = $ctx->stash('blog');
109    return $ctx->count_format($plugin->blocked($blog), $args);
110}
111
112sub save_config {
113    my $plugin = shift;
114    my ($args, $scope) = @_;
115
116    my $app = MT->instance;
117
118    $scope ||= 'system';
119    if ( $scope eq 'system' ) {
120        my $existing_api_key = $plugin->api_key || '';
121        my $new_api_key = $args->{api_key} || '';
122        if ( ($new_api_key ne '') && ( $new_api_key ne $existing_api_key ) ) {
123            # user assigned a new API key
124            $plugin->require_tpas;
125            local $MT::TypePadAntiSpam::SERVICE_HOST = $args->{service_host}
126                if $args->{service_host};
127            my $url = $app->base . $app->mt_uri;
128            my $res = MT::TypePadAntiSpam->verify( $new_api_key, $url );
129            if ( $res->http_status >= 500 ) {
130                return $plugin->error($plugin->translate("Failed to verify your TypePad AntiSpam API key: [_1]", $res->http_response->message));
131            } elsif ( $res->status != 1 ) {
132                return $plugin->error($plugin->translate("The TypePad AntiSpam API key provided is invalid."));
133            }
134        }
135    }
136
137    my $result = $plugin->SUPER::save_config(@_);
138    return $result if MT->version_number < 4;
139
140    my $user = $app->user;
141    my $blog_id = $app->blog->id if $app->blog;
142
143    my $widget_store = $user->widgets();
144    if ($widget_store && %$widget_store) {
145        my $sys_db = $widget_store->{'dashboard:system'} ||= default_widgets();
146        my $blog_db = $widget_store->{'dashboard:blog:' . $blog_id} ||= default_widgets()
147            if $blog_id;
148
149        my $changed = 0;
150        if ($blog_id && (!exists $blog_db->{'typepadantispam'})) {
151            $blog_db->{'typepadantispam'} =
152                { order => 2.1,  # following mt shortcuts, if shown
153                    set => 'sidebar' };
154            $changed++;
155        }
156        if (!exists $sys_db->{'typepadantispam'}) {
157            $sys_db->{'typepadantispam'} =
158                { order => 2.1,  # following mt shortcuts, if shown
159                    set => 'sidebar' };
160            $changed++;
161        }
162        if ($changed) {
163            $user->widgets( $widget_store );
164            $user->save;
165        }
166    }
167
168    return $result;
169}
170
171sub default_widgets {
172    # FIXME: This should come from the app
173    return {
174        'blog_stats' =>
175          { param => { tab => 'entry' }, order => 1, set => 'main' },
176        'this_is_you-1' => { order => 1, set => 'sidebar' },
177        'mt_shortcuts'  => { order => 2, set => 'sidebar' },
178        'mt_news'       => { order => 3, set => 'sidebar' },
179    };
180}
181
182sub stats_widget {
183    my $app = shift;
184    my ( $tmpl, $param ) = @_;
185    my $blog = $app->blog;
186    if ($blog) {
187        $param->{'blog_blocked'} = $plugin->blocked($blog) || 0;
188    }
189    $param->{'system_blocked'} = $plugin->blocked();
190    $param->{'language'} = lc substr( MT->instance->current_language, 0, 2 );
191    $param->{'use_ssl'} = $app->is_secure;
192    $param->{'api_key'} = $plugin->api_key;
193    return;
194}
195
196sub pre_save_obj {
197    my ($cb, $obj) = @_;
198    if (!$obj->id && $obj->is_junk && ($obj->junk_log =~ m/TypePad AntiSpam says spam/)) {
199        # this was junked due in part to TypePad AntiSpam
200        if (my $blog = $obj->blog) {
201            $plugin->blocked( $blog, $plugin->blocked($blog) + 1 );
202        }
203        # now increment total block count:
204        $plugin->blocked( undef, $plugin->blocked() + 1 );
205    }
206}
207
208sub handle_junk {
209    my ($cb, $app, $thing) = @_;
210    $plugin->require_tpas;
211    my $key    = is_valid_key($thing)       or return;
212    my $sig    = package_signature($thing)  or return;
213    MT::TypePadAntiSpam->submit_spam($sig, $key);
214}
215
216sub handle_not_junk {
217    my ($cb, $app, $thing) = @_;
218    $plugin->require_tpas;
219    my $key    = is_valid_key($thing)       or return;
220    my $sig    = package_signature($thing)  or return;
221    MT::TypePadAntiSpam->submit_ham($sig, $key);
222}
223
224sub typepadantispam_score {
225    my $thing = shift;
226    $plugin->require_tpas;
227    my $key    = is_valid_key($thing)
228        or return MT::JunkFilter::ABSTAIN();
229    my $sig    = package_signature($thing, 1)
230        or return MT::JunkFilter::ABSTAIN();
231    my $res = MT::TypePadAntiSpam->check($sig, $key);
232    my $http_resp = $res->http_response;
233    if ( $http_resp && ! $http_resp->is_success ) {
234        MT->log("TypePad AntiSpam error: " . $http_resp->message);
235    } elsif ( !$http_resp ) {
236        if ( MT::TypePadAntiSpam->errstr ) {
237            MT->log("TypePad AntiSpam error: " . MT::TypePadAntiSpam->errstr );
238        }
239    }
240    return MT::JunkFilter::ABSTAIN()
241        unless $res && $res->http_response->is_success;
242    my $weight = $plugin->get_config_value('weight',
243        'blog:' . $thing->blog_id) || 1;
244    my ($score, $grade) = $res->status
245                        ? ($weight, 'ham')
246                        : (-$weight, 'spam');
247    ($score, ["TypePad AntiSpam says $grade"]);
248}
249
250#--- utility
251
252sub is_valid_key {
253    my $thing = shift;
254    my $r     = MT->request;
255    unless ($r->stash('MT::Plugin::TypePadAntiSpam::api_key')) {
256        my $key = $plugin->api_key || return;
257        $r->stash('MT::Plugin::TypePadAntiSpam::api_key', $key);
258    }
259    $r->stash('MT::Plugin::TypePadAntiSpam::api_key');
260}
261
262sub require_tpas {
263    my $plugin = shift;
264    require MT::TypePadAntiSpam;
265
266    my $host = $plugin->get_config_value('service_host');
267    if ($host) {
268        $MT::TypePadAntiSpam::SERVICE_HOST = $host;
269    }
270}
271
272sub package_signature {
273    my $thing = shift;
274    my ($include_referrer) = @_;
275    my $sig   = MT::TypePadAntiSpam::Signature->new;
276    if ($include_referrer) {
277        $sig->user_agent($ENV{HTTP_USER_AGENT});
278        $sig->referrer($ENV{HTTP_REFERER});
279    }
280    $sig->user_ip($thing->ip);
281    $sig->blog(cache('B' . $thing->blog_id));
282    if (ref $thing eq 'MT::Comment') {
283        my $entry = $thing->entry;
284        $sig->article_date(MT->version_number < 4 ? $entry->created_on : $entry->authored_on);
285        $sig->permalink($entry->permalink);
286        $sig->comment_type('comment');
287        $sig->comment_author($thing->author);
288        $sig->comment_author_email($thing->email);
289        $sig->comment_author_url($thing->url);
290        $sig->comment_content($thing->text);
291    } elsif (ref $thing eq 'MT::TBPing') {
292        my $p = $thing->parent;
293        if ($p->isa('MT::Entry')) {
294            $sig->article_date(MT->version_number < 4 ? $p->created_on : $p->authored_on);
295            $sig->permalink($p->permalink);
296        }
297        $sig->comment_type('trackback');
298        $sig->comment_author($thing->blog_name);
299        $sig->comment_author_url($thing->source_url);
300        $sig->comment_content(join "\n", $thing->title, $thing->excerpt);
301    } else {
302        return;    # don't know what this is.
303    }
304    $sig;
305}
306
307sub cache {
308    my $id    = shift;
309    my $cache = MT->request->stash('MT::Plugin::TypePadAntiSpam::permalinks');
310    unless ($cache) {
311        $cache = {};
312        MT->request->stash('MT::Plugin::TypePadAntiSpam::permalinks', $cache);
313    }
314    unless ($cache->{$id}) {
315        if ($id =~ /^B/) {
316            my $b = MT::Blog->load(substr($id, 1)) or return;
317            $cache->{$id} = $b->site_url;
318        } else {
319            require MT::Entry;
320            my $e = MT::Entry->load($id) or return;
321            $cache->{$id} = $e->permalink;
322        }
323    }
324    $cache->{$id};
325}
326
327sub api_key {
328    my $plugin = shift;
329    my $blog = shift;
330    if (@_) {
331        my $key = shift;
332        $plugin->set_config_value('api_key', $key);
333    } else {
334        return $plugin->get_config_value('api_key');
335    }
336}
337
338sub blocked {
339    my $plugin = shift;
340    my $blog = shift;
341    my $blog_id = (ref($blog) && $blog->isa('MT::Blog')) ? $blog->id : $blog;
342    my $blocked = $plugin->get_config_value('blocked', $blog_id ? 'blog:' . $blog_id : 'system');
343    return $blocked || 0 unless @_;
344    my ($count) = @_;
345    $plugin->set_config_value('blocked', $count, $blog_id ? 'blog:' . $blog_id : 'system');
346    return $count;
347}
348
3491;
Note: See TracBrowser for help on using the browser.