ruby-akismet 0.9.3 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +38 -12
- data/lib/akismet.rb +101 -32
- data/test/akismet_test.rb +94 -25
- metadata +5 -5
data/README.rdoc
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
|
1
|
+
Ruby library for the Akismet anti-spam service.
|
2
2
|
|
3
3
|
= Usage
|
4
4
|
|
5
5
|
First you need an Akismet (or Typepad Antispam) API key. Then you need
|
6
|
-
to setup a few configuration
|
6
|
+
to setup a few configuration variables:
|
7
7
|
|
8
8
|
Akismet.key = '123456789'
|
9
9
|
Akismet.blog = 'http://example.com'
|
@@ -12,37 +12,61 @@ To use Typepad Antispam, just specify the host:
|
|
12
12
|
|
13
13
|
Akismet.host = 'api.antispam.typepad.com'
|
14
14
|
|
15
|
-
Then you need to call any of the methods with a few attributes,
|
16
|
-
|
15
|
+
Then you need to call any of the methods with a few attributes, and possibly
|
16
|
+
an ActionDispatch::Request object.
|
17
17
|
|
18
|
-
|
18
|
+
== Documentation
|
19
|
+
|
20
|
+
Check rubydoc.info:
|
19
21
|
|
20
22
|
http://rubydoc.info/github/ysbaddaden/ruby-akismet/master/frames
|
21
23
|
|
24
|
+
Or generate your own:
|
25
|
+
|
26
|
+
~/src/ruby-akismet$ rake rdoc
|
27
|
+
|
22
28
|
= Integrate with Ruby on Rails
|
23
29
|
|
24
|
-
|
30
|
+
ruby-akismet integrates nicely with Ruby on Rails, but isn't tied to it except
|
31
|
+
for the ActionDispatch::Request object, which isn't required. It should be
|
32
|
+
easily integratable with your favorite framework like Sinatra and Merb.
|
33
|
+
|
34
|
+
== Rails 3
|
35
|
+
|
36
|
+
Add this gem to your Gemfile:
|
25
37
|
|
26
38
|
gem 'ruby-akismet', :require => 'akismet'
|
27
39
|
|
28
|
-
|
40
|
+
== Rails 2
|
41
|
+
|
42
|
+
First install the gem:
|
29
43
|
|
30
44
|
gem install ruby-akismet
|
31
45
|
|
32
|
-
Then add
|
46
|
+
Then add it to your app:
|
33
47
|
|
34
48
|
config.gem 'ruby-akismet', :lib => 'akismet'
|
35
49
|
|
36
|
-
|
50
|
+
== Configuration
|
51
|
+
|
52
|
+
Create an initializer file like <tt>config/initializers/akismet.rb</tt>
|
37
53
|
with your configuration:
|
38
54
|
|
39
|
-
Akismet.key
|
40
|
-
Akismet.blog
|
55
|
+
Akismet.key = '123456789'
|
56
|
+
Akismet.blog = 'http://example.com'
|
57
|
+
Akismet.logger = Rails.logger
|
58
|
+
|
59
|
+
== Usage
|
41
60
|
|
42
|
-
|
61
|
+
ruby-akismet is meant to be used on the controller side and not on the model
|
62
|
+
side, because the Akismet API requires some data that's only available from
|
63
|
+
the HTTP request --like the user and proxy IP, referer, etc.
|
64
|
+
|
65
|
+
Here is a Rails 3 example:
|
43
66
|
|
44
67
|
class CommentsController < ApplicationController
|
45
68
|
before_filter :set_post
|
69
|
+
|
46
70
|
respond_to :html, :xml
|
47
71
|
|
48
72
|
def create
|
@@ -84,6 +108,8 @@ Then in your controller call the appropriate methods (rails 3 example):
|
|
84
108
|
|
85
109
|
= Author
|
86
110
|
|
111
|
+
- Julien Portalier <ysbaddaden@gmail.com>
|
112
|
+
|
87
113
|
ruby-akismet is a complete rewrite of Akismetor by Ryan Bates and
|
88
114
|
Levy Carneiro Jr. that you can find at http://github.com/levycarneiro/akismetor
|
89
115
|
|
data/lib/akismet.rb
CHANGED
@@ -1,46 +1,91 @@
|
|
1
1
|
require 'net/http'
|
2
2
|
|
3
3
|
# Akismet compatible library for checking spams.
|
4
|
+
#
|
5
|
+
# Before calling any method, you must configure a blog (your website
|
6
|
+
# homepage) and your Akismet or Typepad Antispam API key.
|
7
|
+
#
|
8
|
+
# Akismet.key = '123456789'
|
9
|
+
# Akismet.blog = 'http://example.com'
|
10
|
+
#
|
4
11
|
class Akismet
|
5
|
-
|
6
|
-
|
12
|
+
# Raised whenever a command is issued but the API key hasn't been configured.
|
13
|
+
class MissingKey < StandardError
|
14
|
+
end
|
7
15
|
|
8
|
-
|
9
|
-
|
10
|
-
@@blog = nil
|
11
|
-
@@extra_headers = [
|
12
|
-
'HTTP_REMOTE_ADDR',
|
13
|
-
'HTTP_CLIENT_IP',
|
14
|
-
'HTTP_X_FORWARDED_FOR',
|
15
|
-
'HTTP_CONNECTION'
|
16
|
-
]
|
16
|
+
VERSION = '1.0.0'.freeze
|
17
|
+
API_VERSION = '1.1'.freeze
|
17
18
|
|
18
19
|
class << self
|
19
|
-
# Configure an alternate API server
|
20
|
+
# Configure an alternate API host server (defaults to
|
21
|
+
# <tt>'rest.akismet.com'</tt>).
|
20
22
|
def host=(host)
|
21
23
|
@@host = host
|
22
24
|
end
|
23
25
|
|
26
|
+
def host
|
27
|
+
@@host
|
28
|
+
end
|
29
|
+
|
24
30
|
# Configure your API key (required).
|
25
31
|
def key=(key)
|
26
32
|
@@key = key
|
27
33
|
end
|
28
34
|
|
29
|
-
|
35
|
+
def key
|
36
|
+
@@key
|
37
|
+
end
|
38
|
+
|
39
|
+
# Configure your homepage URL (required).
|
30
40
|
def blog=(blog)
|
31
41
|
@@blog = blog
|
32
42
|
end
|
33
43
|
|
34
|
-
|
35
|
-
|
44
|
+
def blog
|
45
|
+
@@blog
|
46
|
+
end
|
47
|
+
|
48
|
+
def logger=(logger)
|
49
|
+
@@logger = logger
|
50
|
+
end
|
51
|
+
|
52
|
+
def logger
|
53
|
+
@@logger
|
54
|
+
end
|
55
|
+
|
56
|
+
# Configure an Array of extra HTTP headers to pass to the Akismet server
|
57
|
+
# to extract from the request object.
|
58
|
+
#
|
59
|
+
# Defaults to:
|
60
|
+
#
|
61
|
+
# [
|
62
|
+
# 'HTTP_REMOTE_ADDR',
|
63
|
+
# 'HTTP_CLIENT_IP',
|
64
|
+
# 'HTTP_X_FORWARDED_FOR',
|
65
|
+
# 'HTTP_CONNECTION'
|
66
|
+
# ]
|
36
67
|
#
|
37
|
-
#
|
68
|
+
# Examples:
|
69
|
+
#
|
70
|
+
# # replaces the actual list:
|
71
|
+
# Akismet.extra_headers = ['HTTP_REMOTE_ADDR']
|
72
|
+
#
|
73
|
+
# # appends a header to the list:
|
74
|
+
# Akismet.extra_headers << 'HTTP_ACCEPT_CHARSET'
|
75
|
+
#
|
76
|
+
# # appends multiple headers to the list:
|
77
|
+
# Akismet.extra_headers << ['HTTP_ACCEPT_CHARSET', 'HTTP_ACCEPT_LANGUAGE']
|
38
78
|
#
|
39
|
-
# Akismet.extra_headers = ['HTTP_REMOTE_ADDR', 'HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR']
|
40
79
|
def extra_headers=(headers)
|
41
80
|
@@extra_headers = headers
|
42
81
|
end
|
43
82
|
|
83
|
+
def extra_headers
|
84
|
+
@@extra_headers.flatten!
|
85
|
+
@@extra_headers.uniq!
|
86
|
+
@@extra_headers
|
87
|
+
end
|
88
|
+
|
44
89
|
# Checks if a key is valid or not.
|
45
90
|
def valid_key?(key)
|
46
91
|
call('verify-key', :key => key) == "valid"
|
@@ -50,18 +95,25 @@ class Akismet
|
|
50
95
|
#
|
51
96
|
# Required attributes:
|
52
97
|
#
|
53
|
-
# - <
|
54
|
-
# - <
|
55
|
-
# - <
|
56
|
-
# - <
|
57
|
-
# - <
|
98
|
+
# - <tt>:permalink</tt>
|
99
|
+
# - <tt>:comment_author</tt>
|
100
|
+
# - <tt>:comment_author_url</tt>
|
101
|
+
# - <tt>:comment_author_email</tt>
|
102
|
+
# - <tt>:comment_content</tt>
|
103
|
+
#
|
104
|
+
# Those are also required, but will be extracted from the
|
105
|
+
# +request+ object if available:
|
58
106
|
#
|
59
|
-
#
|
107
|
+
# - <tt>:user_ip</tt>
|
108
|
+
# - <tt>:user_agent</tt>
|
109
|
+
# - <tt>:referrer</tt> (check spelling!)
|
60
110
|
#
|
61
|
-
#
|
62
|
-
#
|
63
|
-
#
|
64
|
-
#
|
111
|
+
# Plus more relevant HTTP headers from extra_headers.
|
112
|
+
#
|
113
|
+
# Note that request is supposed to be an instance of
|
114
|
+
# ActionDispatch::Request or ActionController::Request. If not, the object
|
115
|
+
# must respond to +remote_ip+ (IP as string) and +headers+
|
116
|
+
# (an array of HTTP headers).
|
65
117
|
#
|
66
118
|
def spam?(attributes, request = nil)
|
67
119
|
call('comment-check', attributes, request) == "true"
|
@@ -79,7 +131,7 @@ class Akismet
|
|
79
131
|
end
|
80
132
|
|
81
133
|
# Submits a false-positive comment as non-spam to Akismet.
|
82
|
-
# Takes the same attributes than
|
134
|
+
# Takes the same attributes than spam?.
|
83
135
|
def submit_ham(attributes)
|
84
136
|
call('submit-ham', attributes)
|
85
137
|
end
|
@@ -90,6 +142,17 @@ class Akismet
|
|
90
142
|
end
|
91
143
|
end
|
92
144
|
|
145
|
+
self.host = 'rest.akismet.com'
|
146
|
+
self.key = nil
|
147
|
+
self.blog = nil
|
148
|
+
self.logger = nil
|
149
|
+
self.extra_headers = [
|
150
|
+
'HTTP_REMOTE_ADDR',
|
151
|
+
'HTTP_CLIENT_IP',
|
152
|
+
'HTTP_X_FORWARDED_FOR',
|
153
|
+
'HTTP_CONNECTION'
|
154
|
+
]
|
155
|
+
|
93
156
|
def initialize(command, attributes, request = nil)
|
94
157
|
@command = command
|
95
158
|
@attributes = attributes
|
@@ -97,13 +160,15 @@ class Akismet
|
|
97
160
|
end
|
98
161
|
|
99
162
|
def call
|
163
|
+
self.class.logger.debug { " AKISMET #{@command} #{post_attributes}" } if self.class.logger
|
164
|
+
|
100
165
|
http = Net::HTTP.new(http_host, 80)
|
101
166
|
http.post(http_path, post_attributes, http_headers).body
|
102
167
|
end
|
103
168
|
|
104
169
|
private
|
105
170
|
def attributes
|
106
|
-
@attributes[:blog] ||=
|
171
|
+
@attributes[:blog] ||= self.class.blog
|
107
172
|
|
108
173
|
unless @command == 'verify-key'
|
109
174
|
@attributes[:comment_type] ||= 'comment'
|
@@ -112,7 +177,10 @@ class Akismet
|
|
112
177
|
@attributes[:user_ip] = @request.remote_ip
|
113
178
|
@attributes[:user_agent] = @request.headers["HTTP_USER_AGENT"]
|
114
179
|
@attributes[:referrer] = @request.headers["HTTP_REFERER"]
|
115
|
-
|
180
|
+
|
181
|
+
self.class.extra_headers.each do |h|
|
182
|
+
@attributes[h] = @request.headers[h]
|
183
|
+
end
|
116
184
|
end
|
117
185
|
end
|
118
186
|
|
@@ -133,9 +201,10 @@ class Akismet
|
|
133
201
|
|
134
202
|
def http_host
|
135
203
|
unless @command == 'verify-key'
|
136
|
-
"
|
204
|
+
raise MissingKey.new("Required Akismet.key is nil.") unless self.class.key
|
205
|
+
"#{self.class.key}.#{self.class.host}"
|
137
206
|
else
|
138
|
-
"#{
|
207
|
+
"#{self.class.host}"
|
139
208
|
end
|
140
209
|
end
|
141
210
|
|
data/test/akismet_test.rb
CHANGED
@@ -1,60 +1,129 @@
|
|
1
1
|
require 'test/unit'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
gem 'actionpack'
|
6
|
+
gem 'rack'
|
7
|
+
require 'action_dispatch'
|
8
|
+
|
2
9
|
require File.expand_path("../../lib/akismet.rb", __FILE__)
|
3
10
|
|
4
|
-
# TODO: Test with an ActionDispatch::TestRequest object.
|
5
11
|
class AkismetTest < Test::Unit::TestCase
|
6
12
|
def setup
|
7
13
|
Akismet.host = 'api.antispam.typepad.com'
|
8
14
|
Akismet.key = '123456789'
|
9
15
|
Akismet.blog = 'http://www.example.com/'
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
{
|
14
|
-
:user_ip => '127.0.0.1',
|
15
|
-
:user_agent => 'Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10.5; en-US; rv:1.9.0.3) Gecko/2008092414 Firefox/3.0.3',
|
16
|
-
:referrer => 'http://www.example.com/posts',
|
17
|
-
:permalink => 'http://www.example.com/posts/1',
|
18
|
-
:comment_author => 'Julien Portalier',
|
19
|
-
:comment_author_url => 'http://ysbaddaden.wordpress.com/',
|
20
|
-
:comment_author_email => 'julien@example.com',
|
21
|
-
:comment_content => 'this is a normal comment',
|
22
|
-
}
|
23
|
-
end
|
24
|
-
|
25
|
-
def invalid_attributes
|
26
|
-
invalid = valid_attributes.dup
|
27
|
-
invalid[:comment_author] = 'viagra-test-123'
|
28
|
-
invalid
|
16
|
+
|
17
|
+
Akismet.logger = Logger.new(File.expand_path("../test.log", __FILE__))
|
18
|
+
Akismet.logger.level = Logger::DEBUG
|
29
19
|
end
|
30
20
|
|
31
21
|
def test_valid_key
|
32
|
-
assert Akismet.valid_key?(
|
22
|
+
assert Akismet.valid_key?(Akismet.key)
|
33
23
|
end
|
34
24
|
|
35
25
|
# def test_invalid_key
|
36
26
|
# assert !Akismet.valid_key?('abc123')
|
37
27
|
# end
|
38
28
|
|
29
|
+
def test_should_not_fail_with_no_logger
|
30
|
+
Akismet.logger = nil
|
31
|
+
assert Akismet.valid_key?(Akismet.key)
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_should_raise_missing_key
|
35
|
+
Akismet.key = nil
|
36
|
+
assert_raise(Akismet::MissingKey) { Akismet.spam?(full_spam_attributes) }
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_should_not_raise_missing_key_for_valid_key
|
40
|
+
Akismet.key = nil
|
41
|
+
assert_nothing_raised { Akismet.valid_key?('123456789') }
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_extra_headers
|
45
|
+
Akismet.extra_headers = ['HTTP_REMOTE_ADDR']
|
46
|
+
assert_equal ['HTTP_REMOTE_ADDR'], Akismet.extra_headers
|
47
|
+
|
48
|
+
Akismet.extra_headers << 'HTTP_CLIENT_IP'
|
49
|
+
assert_equal ['HTTP_REMOTE_ADDR', 'HTTP_CLIENT_IP'], Akismet.extra_headers
|
50
|
+
|
51
|
+
Akismet.extra_headers << ['HTTP_X_FORWARDED_FOR', 'HTTP_CONNECTION']
|
52
|
+
assert_equal ['HTTP_REMOTE_ADDR', 'HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_CONNECTION'],
|
53
|
+
Akismet.extra_headers
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_spam_with_actiondispatch_request
|
57
|
+
assert Akismet.spam?(spam_attributes, actiondispatch_request)
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_ham_with_actiondispatch_request
|
61
|
+
assert !Akismet.spam?(ham_attributes, actiondispatch_request)
|
62
|
+
end
|
63
|
+
|
39
64
|
def test_spam
|
40
|
-
assert Akismet.spam?(
|
65
|
+
assert Akismet.spam?(full_spam_attributes)
|
41
66
|
end
|
42
67
|
|
43
68
|
def test_not_spam
|
44
|
-
assert !Akismet.spam?(
|
69
|
+
assert !Akismet.spam?(full_ham_attributes)
|
45
70
|
end
|
46
71
|
|
47
72
|
def test_ham
|
48
|
-
assert Akismet.ham?(
|
73
|
+
assert Akismet.ham?(full_ham_attributes)
|
49
74
|
end
|
50
75
|
|
51
76
|
def test_not_ham
|
52
|
-
assert !Akismet.ham?(
|
77
|
+
assert !Akismet.ham?(full_spam_attributes)
|
53
78
|
end
|
54
79
|
|
55
80
|
def test_submit_spam
|
81
|
+
assert Akismet.submit_spam(spam_attributes)
|
56
82
|
end
|
57
83
|
|
58
84
|
def test_submit_ham
|
85
|
+
assert Akismet.submit_ham(ham_attributes)
|
59
86
|
end
|
87
|
+
|
88
|
+
protected
|
89
|
+
def ham_attributes
|
90
|
+
{
|
91
|
+
:permalink => 'http://www.example.com/posts/1',
|
92
|
+
:comment_author => 'Julien Portalier',
|
93
|
+
:comment_author_url => 'http://ysbaddaden.wordpress.com/',
|
94
|
+
:comment_author_email => 'julien@example.com',
|
95
|
+
:comment_content => 'this is a normal comment'
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
def spam_attributes
|
100
|
+
ham_attributes.merge(:comment_author => 'viagra-test-123')
|
101
|
+
end
|
102
|
+
|
103
|
+
def additional_attributes
|
104
|
+
{
|
105
|
+
:user_ip => '127.0.0.1',
|
106
|
+
:user_agent => 'Mozilla/5.0 (X11; U; Linux i686; fr-FR) AppleWebKit/534.7 (KHTML, like Gecko) Ubuntu/10.04 Chromium/7.0.517.41 Chrome/7.0.517.41 Safari/534.7',
|
107
|
+
:referrer => 'http://www.example.com/posts'
|
108
|
+
}
|
109
|
+
end
|
110
|
+
|
111
|
+
def full_spam_attributes
|
112
|
+
spam_attributes.merge(additional_attributes)
|
113
|
+
end
|
114
|
+
|
115
|
+
def full_ham_attributes
|
116
|
+
ham_attributes.merge(additional_attributes)
|
117
|
+
end
|
118
|
+
|
119
|
+
def actiondispatch_request
|
120
|
+
request = ActionDispatch::TestRequest.new(
|
121
|
+
'HTTP_REFERER' => 'http://www.example.com/posts/1',
|
122
|
+
'HTTP_REMOTE_ADDR' => '127.0.0.1',
|
123
|
+
'HTTP_USER_AGENT' => 'Mozilla/5.0 (X11; U; Linux i686; fr-FR) AppleWebKit/534.7 (KHTML, like Gecko) Ubuntu/10.04 Chromium/7.0.517.41 Chrome/7.0.517.41 Safari/534.7',
|
124
|
+
'HTTP_CONNECTION' => 'close'
|
125
|
+
)
|
126
|
+
request.remote_addr = '127.0.0.1'
|
127
|
+
request
|
128
|
+
end
|
60
129
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-akismet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
|
+
- 1
|
7
8
|
- 0
|
8
|
-
-
|
9
|
-
|
10
|
-
version: 0.9.3
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Julien Portalier
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-10-
|
18
|
+
date: 2010-10-24 00:00:00 +02:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|