defender 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|