defender 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +0 -6
- data/Rakefile +1 -0
- data/VERSION +1 -1
- data/lib/defender.rb +40 -69
- data/spec/defender_spec.rb +64 -19
- data/spec/spec_helper.rb +1 -0
- metadata +12 -2
data/README.rdoc
CHANGED
@@ -2,12 +2,6 @@
|
|
2
2
|
|
3
3
|
This is a Ruby wrapper of the Defensio[http://defensio.com] spam filtering API. To use this library, you need an API key from Defensio. Go ahead and {get one}[http://defensio.com/signup/].
|
4
4
|
|
5
|
-
== Testing
|
6
|
-
|
7
|
-
To run the tests, you need to pass the api key and owner url with the environment keys API_KEY and API_OWNER_URL, like this:
|
8
|
-
rake spec API_KEY="key1234" API_OWNER_URL="http://myblog.com"
|
9
|
-
The tests will fail with invalid keys and urls.
|
10
|
-
|
11
5
|
== Note on Patches/Pull Requests
|
12
6
|
|
13
7
|
* Fork the project.
|
data/Rakefile
CHANGED
@@ -12,6 +12,7 @@ begin
|
|
12
12
|
gem.authors = ["Henrik Hodne"]
|
13
13
|
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
14
|
gem.add_development_dependency "yard", ">= 0"
|
15
|
+
gem.add_development_dependency "mocha", ">= 0.9.8"
|
15
16
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
16
17
|
end
|
17
18
|
Jeweler::GemcutterTasks.new
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.1
|
data/lib/defender.rb
CHANGED
@@ -5,10 +5,12 @@ class Defender
|
|
5
5
|
# The Defensio API version currently supported by Defender
|
6
6
|
API_VERSION = "1.2"
|
7
7
|
|
8
|
+
ROOT_URL = "http://api.defensio.com/"
|
9
|
+
|
8
10
|
DEFAULT_OPTIONS = {
|
9
|
-
service_type
|
10
|
-
api_key
|
11
|
-
owner_url
|
11
|
+
:service_type => "blog",
|
12
|
+
:api_key => "",
|
13
|
+
:owner_url => ""
|
12
14
|
}
|
13
15
|
|
14
16
|
##
|
@@ -48,13 +50,9 @@ class Defender
|
|
48
50
|
# otherwise.
|
49
51
|
#
|
50
52
|
# @return [Boolean]
|
51
|
-
def spam
|
52
|
-
@spam
|
53
|
-
end
|
53
|
+
def spam?; @spam; end
|
54
54
|
|
55
|
-
def to_s
|
56
|
-
@signature
|
57
|
-
end
|
55
|
+
def to_s; @signature; end
|
58
56
|
end
|
59
57
|
|
60
58
|
##
|
@@ -66,47 +64,39 @@ class Defender
|
|
66
64
|
# Defensio on this blog.
|
67
65
|
#
|
68
66
|
# @return [Float<0..1>]
|
69
|
-
|
67
|
+
def accuracy; @response["accuracy"]; end
|
70
68
|
|
71
69
|
##
|
72
70
|
# The number of spam comments caught by the filter.
|
73
|
-
|
71
|
+
def spam; @response["spam"]; end
|
74
72
|
|
75
73
|
##
|
76
74
|
# The number of ham (legitimate) comments accepted by the filter.
|
77
|
-
|
75
|
+
def ham; @response["ham"]; end
|
78
76
|
|
79
77
|
##
|
80
78
|
# The number of times a legitimate message was retrained from the spambox
|
81
79
|
# (i.e. "de-spammed" by the user)
|
82
|
-
|
80
|
+
def false_positives; @response["false-positives"]; end
|
83
81
|
|
84
82
|
##
|
85
83
|
# The number of times a spam message was retrained from comments box (i.e.
|
86
84
|
# "de-legitimized" by the user)
|
87
|
-
|
85
|
+
def false_negatives; @response["false-negatives"]; end
|
88
86
|
|
89
87
|
##
|
90
88
|
# A boolean value indicating whether Defensio is still in its initial
|
91
89
|
# learning phase.
|
92
90
|
#
|
93
91
|
# @return [Boolean]
|
94
|
-
|
92
|
+
def learning; @response["learning"]; end
|
95
93
|
|
96
94
|
##
|
97
95
|
# More details on the reason(s) why Defensio is still in its initial
|
98
96
|
# learning phase.
|
99
|
-
|
97
|
+
def learning_status; @response["learning-status"]; end
|
100
98
|
|
101
|
-
def initialize(response)
|
102
|
-
@accuracy = response["accuracy"]
|
103
|
-
@spam = response["spam"]
|
104
|
-
@ham = response["ham"]
|
105
|
-
@false_positives = response["false-positives"]
|
106
|
-
@false_negatives = response["false-negatives"]
|
107
|
-
@learning = response["learning"]
|
108
|
-
@learning_status = response["learning-status"]
|
109
|
-
end
|
99
|
+
def initialize(response); @response = response; end
|
110
100
|
end
|
111
101
|
|
112
102
|
attr_accessor :service_type, :api_key, :owner_url
|
@@ -131,11 +121,9 @@ class Defender
|
|
131
121
|
# @return [Hash]
|
132
122
|
def self.options_to_parameters(options)
|
133
123
|
opts = {}
|
134
|
-
options.each do |key,
|
135
|
-
|
136
|
-
|
137
|
-
end
|
138
|
-
opts[key.to_s.gsub("_", "-").downcase] = value.to_s
|
124
|
+
options.each do |key, val|
|
125
|
+
opts[key.to_s.gsub("_", "-").downcase] = val.respond_to?(:strftime) ?
|
126
|
+
val.strftime("%Y/%m/%d") : val.to_s
|
139
127
|
end
|
140
128
|
opts
|
141
129
|
end
|
@@ -162,40 +150,27 @@ class Defender
|
|
162
150
|
# @return [Boolean]
|
163
151
|
# @see http://defensio.com/api/#validate-key
|
164
152
|
def valid_key?
|
165
|
-
|
166
|
-
response = call_action("validate-key")
|
167
|
-
if response["status"] == "success"
|
168
|
-
return true
|
169
|
-
else
|
170
|
-
return false
|
171
|
-
end
|
172
|
-
rescue StandardError
|
173
|
-
return false
|
174
|
-
end
|
153
|
+
call_action("validate-key")["status"] == "success" ? true : false
|
175
154
|
end
|
176
155
|
|
177
156
|
##
|
178
157
|
# Announce an article existence. This should (if feasible) be called when an
|
179
158
|
# article or blogpost is created so Defensio can analyse it.
|
180
159
|
#
|
181
|
-
# @param [
|
182
|
-
# @
|
183
|
-
# @
|
160
|
+
# @param [Hash] opts All options are required.
|
161
|
+
# @option opts [#to_s] :article_title The title of the article
|
162
|
+
# @option opts [#to_s] :article_author The name of the author of the article
|
163
|
+
# @option opts [#to_s] :article_author_email The email address of the person posting the
|
184
164
|
# article.
|
185
|
-
# @
|
186
|
-
# @
|
165
|
+
# @option opts [#to_s] :article_content The content of the article itself.
|
166
|
+
# @option opts [#to_s] :permalink The permalink of the article just posted.
|
187
167
|
# @raise [StandardError] If the call fails, a StandardError is raised with
|
188
168
|
# the error message given from Defensio.
|
189
169
|
# @return [Boolean] Returns true if the article was successfully announced,
|
190
170
|
# raises StandardError otherwise.
|
191
171
|
# @see http://defensio.com/api/#announce-article
|
192
|
-
def announce_article(
|
193
|
-
response = call_action(
|
194
|
-
"article-title" => title.to_s,
|
195
|
-
"article-author" => author.to_s,
|
196
|
-
"article-author-email" => author_email.to_s,
|
197
|
-
"article-content" => content,
|
198
|
-
"permalink" => permalink)
|
172
|
+
def announce_article(opts={})
|
173
|
+
response = call_action(Defender.options_to_parameters(opts))
|
199
174
|
true
|
200
175
|
end
|
201
176
|
|
@@ -258,9 +233,7 @@ class Defender
|
|
258
233
|
# @return [Boolean] Returns true if the comments were successfully marked,
|
259
234
|
# raises StandardError otherwise.
|
260
235
|
def report_false_negatives(signatures)
|
261
|
-
|
262
|
-
"signatures" => signatures.map(&:to_s).join(","))
|
263
|
-
true
|
236
|
+
report_false(:negatives, signatures)
|
264
237
|
end
|
265
238
|
|
266
239
|
##
|
@@ -277,9 +250,7 @@ class Defender
|
|
277
250
|
# @return [Boolean] Returns true if the comments were successfully marked,
|
278
251
|
# raises StandardError otherwise.
|
279
252
|
def report_false_positives(signatures)
|
280
|
-
|
281
|
-
"signatures" => signatures.map(&:to_s).join(","))
|
282
|
-
true
|
253
|
+
report_false(:positives, signatures)
|
283
254
|
end
|
284
255
|
|
285
256
|
##
|
@@ -293,6 +264,12 @@ class Defender
|
|
293
264
|
end
|
294
265
|
|
295
266
|
private
|
267
|
+
def report_false(type, signatures)
|
268
|
+
call_action("report-false-#{type}",
|
269
|
+
"signatures" => signatures.join(","))
|
270
|
+
true
|
271
|
+
end
|
272
|
+
|
296
273
|
##
|
297
274
|
# Returns the url for the given action.
|
298
275
|
#
|
@@ -301,11 +278,7 @@ class Defender
|
|
301
278
|
# @raise [APIKeyError] Raises this if no API key is given.
|
302
279
|
def url(action)
|
303
280
|
raise APIKeyError unless @api_key.length > 0
|
304
|
-
"
|
305
|
-
"#{@service_type}/" \
|
306
|
-
"#{Defender::API_VERSION}/" \
|
307
|
-
"#{action}/" \
|
308
|
-
"#{@api_key}.yaml"
|
281
|
+
"#{ROOT_URL}#{@service_type}/#{API_VERSION}/#{action}/#{@api_key}.yaml"
|
309
282
|
end
|
310
283
|
|
311
284
|
##
|
@@ -317,12 +290,10 @@ class Defender
|
|
317
290
|
# @raise [APIKeyError] If an invalid (or no) API key is given, this is
|
318
291
|
# raised
|
319
292
|
def call_action(action, params={})
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
raise
|
324
|
-
else
|
293
|
+
response = Net::HTTP.post_form(URI.parse(url(action)),
|
294
|
+
{"owner-url" => @owner_url}.merge(params))
|
295
|
+
response.code == 401 ?
|
296
|
+
raise(APIKeyError) :
|
325
297
|
Defender.raise_if_error(YAML.load(response.body)["defensio-result"])
|
326
|
-
end
|
327
298
|
end
|
328
299
|
end
|
data/spec/defender_spec.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
2
|
|
3
3
|
describe "Defender" do
|
4
|
+
before(:each) do
|
5
|
+
@defender = Defender.new(:api_key => "validkey", :owner_url => "validurl")
|
6
|
+
end
|
7
|
+
|
4
8
|
it "should raise a StandardError if a method fails" do
|
5
9
|
lambda do
|
6
10
|
Defender.raise_if_error({
|
@@ -11,26 +15,39 @@ describe "Defender" do
|
|
11
15
|
end
|
12
16
|
|
13
17
|
it "should return the correct URL for any given action" do
|
14
|
-
|
15
|
-
d.instance_eval do
|
18
|
+
@defender.instance_eval do
|
16
19
|
url("foobar")
|
17
|
-
end.should == "http://api.defensio.com/blog/#{Defender::API_VERSION}/foobar/
|
20
|
+
end.should == "http://api.defensio.com/blog/#{Defender::API_VERSION}/foobar/validkey.yaml"
|
18
21
|
end
|
19
22
|
|
20
23
|
it "should correctly identify a valid API key" do
|
21
|
-
|
22
|
-
|
24
|
+
@defender.stubs(:call_action).with("validate-key").returns(
|
25
|
+
{"status" => "success", "message" => ""}
|
26
|
+
)
|
27
|
+
@defender.valid_key?.should be_true
|
23
28
|
end
|
24
29
|
|
25
30
|
it "should correctly identify an invalid API key" do
|
26
|
-
|
27
|
-
|
31
|
+
@defender.stubs(:call_action).with("validate-key").returns(
|
32
|
+
{"status" => "fail", "message" => "Invalid key"}
|
33
|
+
)
|
34
|
+
@defender.valid_key?.should be_false
|
28
35
|
end
|
29
36
|
|
30
37
|
it "should correctly identify a spammy comment" do
|
31
|
-
|
32
|
-
|
33
|
-
|
38
|
+
@defender.
|
39
|
+
stubs(:call_action).
|
40
|
+
with('audit-comment', {
|
41
|
+
"user-ip" => "127.0.0.1",
|
42
|
+
"article-date" => Time.now.strftime("%Y/%m/%d"),
|
43
|
+
"comment-author" => "Henrik Hodne",
|
44
|
+
"comment-type" => "comment",
|
45
|
+
"test-force" => "spam,0.5000",
|
46
|
+
}).
|
47
|
+
returns(
|
48
|
+
{"signature" => "abc123", "spam" => true, "spaminess" => 0.5}
|
49
|
+
)
|
50
|
+
@defender.audit_comment(
|
34
51
|
:user_ip => "127.0.0.1",
|
35
52
|
:article_date => Time.now,
|
36
53
|
:comment_author => "Henrik Hodne",
|
@@ -40,9 +57,19 @@ describe "Defender" do
|
|
40
57
|
end
|
41
58
|
|
42
59
|
it "should correctly identify a meaty comment" do
|
43
|
-
|
44
|
-
|
45
|
-
|
60
|
+
@defender.
|
61
|
+
stubs(:call_action).
|
62
|
+
with('audit-comment', {
|
63
|
+
"user-ip" => "127.0.0.1",
|
64
|
+
"article-date" => Time.now.strftime("%Y/%m/%d"),
|
65
|
+
"comment-author" => "Henrik Hodne",
|
66
|
+
"comment-type" => "comment",
|
67
|
+
"test-force" => "ham,0.1000",
|
68
|
+
}).
|
69
|
+
returns(
|
70
|
+
{"signature" => "abc123", "spam" => false, "spaminess" => 0.1}
|
71
|
+
)
|
72
|
+
@defender.audit_comment(
|
46
73
|
:user_ip => "127.0.0.1",
|
47
74
|
:article_date => Time.now,
|
48
75
|
:comment_author => "Henrik Hodne",
|
@@ -52,9 +79,19 @@ describe "Defender" do
|
|
52
79
|
end
|
53
80
|
|
54
81
|
it "should correctly set the spaminess" do
|
55
|
-
|
56
|
-
|
57
|
-
|
82
|
+
@defender.
|
83
|
+
stubs(:call_action).
|
84
|
+
with('audit-comment', {
|
85
|
+
"user-ip" => "127.0.0.1",
|
86
|
+
"article-date" => Time.now.strftime("%Y/%m/%d"),
|
87
|
+
"comment-author" => "Henrik Hodne",
|
88
|
+
"comment-type" => "comment",
|
89
|
+
"test-force" => "spam,0.5000",
|
90
|
+
}).
|
91
|
+
returns(
|
92
|
+
{"signature" => "abc123", "spam" => true, "spaminess" => 0.5}
|
93
|
+
)
|
94
|
+
@defender.audit_comment(
|
58
95
|
:user_ip => "127.0.0.1",
|
59
96
|
:article_date => Time.now,
|
60
97
|
:comment_author => "Henrik Hodne",
|
@@ -64,8 +101,16 @@ describe "Defender" do
|
|
64
101
|
end
|
65
102
|
|
66
103
|
it "should fail without valid API credentials" do
|
67
|
-
|
68
|
-
|
104
|
+
@defender.
|
105
|
+
stubs(:call_action).
|
106
|
+
with('audit-comment', {
|
107
|
+
"user-ip" => "127.0.0.1",
|
108
|
+
"article-date" => Time.now.strftime("%Y/%m/%d"),
|
109
|
+
"comment-author" => "Henrik Hodne",
|
110
|
+
"comment-type" => "comment",
|
111
|
+
"test-force" => "ham,0.1000",
|
112
|
+
}).
|
113
|
+
raises(StandardError)
|
69
114
|
lambda {
|
70
115
|
d.audit_comment(
|
71
116
|
:user_ip => "127.0.0.1",
|
@@ -74,6 +119,6 @@ describe "Defender" do
|
|
74
119
|
:comment_type => "comment",
|
75
120
|
:test_force => "ham,0.1000"
|
76
121
|
)
|
77
|
-
}.should raise_error(StandardError
|
122
|
+
}.should raise_error(StandardError)
|
78
123
|
end
|
79
124
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: defender
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Henrik Hodne
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-11-
|
12
|
+
date: 2009-11-09 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -32,6 +32,16 @@ dependencies:
|
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: "0"
|
34
34
|
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: mocha
|
37
|
+
type: :development
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 0.9.8
|
44
|
+
version:
|
35
45
|
description: A wrapper of the Defensio spam filtering service.
|
36
46
|
email: henrik.hodne@binaryhex.com
|
37
47
|
executables: []
|