viking 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,384 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+ require 'yaml'
4
+
5
+ # = Defensio
6
+ # Adapted from code originally by Technoweenie. Updated to the 1.2 API, and
7
+ # refactored.
8
+ #
9
+ # = License
10
+ # Technoweenie fails to mention the license of his original code, so I assume
11
+ # that it is either under MIT or public domain. As such, I release this code
12
+ # under the MIT license.
13
+ #
14
+ # Copyright (c) 2008, James Herdman
15
+ #
16
+ # = Important Note
17
+ # * most documentation below is adapted from the Defensio API (v 1.2) manual
18
+ # * unless otherwise stated, all arguments are expected to be Strings
19
+ module Viking
20
+ class Defensio < Base
21
+
22
+ class << self
23
+
24
+ attr_accessor :host
25
+ attr_accessor :port
26
+ attr_accessor :api_version
27
+ attr_accessor :standard_headers
28
+ attr_accessor :service_type
29
+
30
+ end
31
+
32
+ attr_accessor :proxy_port
33
+ attr_accessor :proxy_host
34
+ attr_reader :last_response
35
+
36
+ self.service_type = :blog
37
+ self.host = 'api.defensio.com'
38
+ self.api_version = '1.2'
39
+ self.standard_headers = {
40
+ 'User-Agent' => "Viking (Ruby Gem) v#{Viking::VERSION}",
41
+ 'Content-Type' => 'application/x-www-form-urlencoded'
42
+ }
43
+
44
+ # Create a new instance of the Akismet class
45
+ #
46
+ # ==== Arguments
47
+ # Arguments are provided in the form of a Hash with the following keys
48
+ # (as Symbols) available:
49
+ #
50
+ # +api_key+:: your Defensio API key
51
+ # +blog+:: the blog associated with your api key
52
+ #
53
+ # The following keys are available and are entirely optional. They are
54
+ # available incase communication with Akismet's servers requires a
55
+ # proxy port and/or host:
56
+ #
57
+ # * +proxy_port+
58
+ # * +proxy_host+
59
+ def initialize(options)
60
+ super
61
+ self.verify_options = false
62
+ end
63
+
64
+ # This action verifies that the key is valid for the owner calling the
65
+ # service. A user must have a valid API key in order to use Defensio.
66
+ #
67
+ # ==== Returns
68
+ # true, false
69
+ def verified?
70
+ return false if invalid_options?
71
+ (@verify_options ||= call_defensio('validate-key'))[:status] == 'success'
72
+ end
73
+
74
+ # This action should be invoked upon the publication of an article to
75
+ # announce its existence. The actual content of the article is sent to
76
+ # Defensio for analysis.
77
+ #
78
+ # ==== Arguments
79
+ # Provided in a Hash with the following keys:
80
+ #
81
+ # +owner_url+ (*required*)::
82
+ # the URL of the owner using Defensio. Note that this value should be
83
+ # provided in your default options for Viking and will be automatically
84
+ # inserted into your call.
85
+ # +article_author+ (*required*)::
86
+ # the name of the author of the article
87
+ # +article_author_email+ (*required*)::
88
+ # the email address of the article's author
89
+ # +article_title+ (*required*)::
90
+ # the title of the article
91
+ # +article_content+ (*required*)::
92
+ # the contents of the article
93
+ # +permalink+ (*required*)::
94
+ # the permalink of the article
95
+ #
96
+ # ==== Returns
97
+ # Hash::
98
+ # contains server response. Should things go awry, the full response
99
+ # will be provided.
100
+ #
101
+ # ===== Response structure
102
+ # The following are the fields returned from the server and their possible
103
+ # values:
104
+ #
105
+ # +status+::
106
+ # indicates whether or not the key is valid for this blog. Either
107
+ # 'success' or 'fail'.
108
+ # +message+::
109
+ # the message provided by the action, if applicable
110
+ # +api_version+::
111
+ # the API version used to process the request
112
+ def check_article(options = {})
113
+ return false if invalid_options?
114
+ call_defensio 'announce-article', options
115
+ end
116
+
117
+ # This central action determines not only whether Defensio thinks a
118
+ # comment is spam or not, but also a measure of its "spaminess", i.e. its
119
+ # relative likelihood of being spam.
120
+ #
121
+ # It should be noted that one of Defensio's key features is its ability to
122
+ # rank spam according to how "spammy" it appears to be. In order to make
123
+ # the most of the Defensio system in their applications, developers should
124
+ # take advantage of the spaminess value returned by this function, to
125
+ # build interfaces that make it easy for the user to quickly sort through
126
+ # and manage their spamboxes.
127
+ #
128
+ # ==== Arguments
129
+ # Provide arguments in a Hash. The following keys are available:
130
+ #
131
+ # +owner_url+ (*required*)::
132
+ # the URL of the owner using Defensio. Note that this value should be
133
+ # provided in your default options for Viking and will be automatically
134
+ # inserted into your call.
135
+ # +user_ip+ (*required*)::
136
+ # the IP address of whomever is posting the comment
137
+ # +article_date+ <~strftime> (*required*)::
138
+ # the date the original blog article was posted
139
+ # +comment_author+ (*required*)::
140
+ # the name of the author of the comment
141
+ # +comment_type+ (*required*)::
142
+ # the type of comment being posted to the article. This is expected to
143
+ # be any of the following: 'comment', 'trackback', 'pingback', 'other'
144
+ # +comment_content+::
145
+ # the content of the comment in question
146
+ # +comment_author_email+::
147
+ # the email address of the comment's author
148
+ # +permalink+::
149
+ # the permalink of the blog post to which the comment is being posted
150
+ # +referrer+::
151
+ # the URL of the site that brought the commenter to this page
152
+ # +user_logged_in+::
153
+ # whether or not the user leaving the comment is logged into the client
154
+ # platform. Expected to be either +true+ or +false+.
155
+ # +trusted_user+::
156
+ # whether or not the user is an administrator or modertor or editor of
157
+ # the blog. This should only ever be true if the blogging platform can
158
+ # guarentee that the user has been authenticated and authorized for this
159
+ # role. This is expected to be either +true+ or +false+.
160
+ # +openid+::
161
+ # the OpenID URL of the currently logged in user. Must be used in
162
+ # conjunction with +user_logged_in+ as +true+. OpenID authentication
163
+ # must be taken care of by your application.
164
+ # +test_force+::
165
+ # <b>FOR TESTING PURPOSES ONLY</b>: use this parameter to force the
166
+ # outcome of +audit_comment+. Optionally affix (with a comma) a desired
167
+ # +spaminess+ return value (in the range 0 to 1) (e.g. "spam,x.xxxx"
168
+ # "ham,x.xxxx" )
169
+ #
170
+ # ==== Returns
171
+ # Hash::
172
+ # contains server response. Should things go awry, the full response
173
+ # will be provided.
174
+ #
175
+ # ===== Response structure
176
+ # The following are the fields returned from the server and their possible
177
+ # values:
178
+ #
179
+ # +status+::
180
+ # indicates whether or not the key is valid for this blog. Either
181
+ # 'success' or 'fail'.
182
+ # +message+::
183
+ # the message provided by the action, if applicable
184
+ # +api_version+::
185
+ # the API version used to process the request
186
+ # +signature+::
187
+ # this uniquely identifies a message in the Defensio system. This should
188
+ # be retained by the client for retraining purposes.
189
+ # +spam+::
190
+ # whether or not Defensio believes the comment to be spam. This will be
191
+ # either +true+ or +false+
192
+ # +spaminess+::
193
+ # a value indicating the relative likelihood that a comment is spam.
194
+ # This should be retained to aid in building spam sorting interfaces.
195
+ def check_comment(options = {})
196
+ return false if invalid_options?
197
+ options[:article_date] = options[:article_date].strftime("%Y/%m/%d") # e.g. 2007/05/16
198
+ call_defensio 'audit-comment', options
199
+ end
200
+
201
+ # This action is used to retrain false negatives. That is to say, to
202
+ # indicate to the filter that comments originally tagged as "ham" (i.e.
203
+ # legitimate) were in fact spam.
204
+ #
205
+ # Retraining the filter in this manner contributes to a personalized
206
+ # learning effect on the filtering algorithm that will improve accuracy
207
+ # for each user over time.
208
+ #
209
+ # ==== Arguments
210
+ # Provide arguments in a Hash. The following keys are available:
211
+ #
212
+ # +owner_url+ (*required*)::
213
+ # the URL of the owner using Defensio. Note that this value should be
214
+ # provided in your default options for Viking and will be automatically
215
+ # inserted into your call.
216
+ # +signatures+ (comma separated Strings)(*required*)::
217
+ # a comma separated list of signatures (or single entry) to be submitted
218
+ # for retraining. The signatures were provided by Defensio when a
219
+ # comment was first audited.
220
+ #
221
+ # ==== Returns
222
+ # Hash::
223
+ # contains server response. Should things go awry, the full response
224
+ # will be provided.
225
+ #
226
+ # ===== Response structure
227
+ # The following are the fields returned from the server and their possible
228
+ # values:
229
+ #
230
+ # +status+::
231
+ # indicates whether or not the key is valid for this blog. Either
232
+ # 'success' or 'fail'.
233
+ # +message+::
234
+ # the message provided by the action, if applicable
235
+ # +api_version+::
236
+ # the API version used to process the request
237
+ def mark_as_spam(options = {})
238
+ return false if invalid_options?
239
+ call_defensio 'report-false-negatives', options
240
+ end
241
+
242
+ # This action is used to retrain false positives. That is to say, to
243
+ # indicate to the filter that comments originally tagged as spam were in
244
+ # fact "ham" (i.e. legitimate comments).
245
+ #
246
+ # Retraining the filter in this manner contributes to a personalized
247
+ # learning effect on the filtering algorithm that will improve accuracy
248
+ # for each user over time.
249
+ #
250
+ # ==== Arguments
251
+ # Provide arguments in a Hash. The following keys are available:
252
+ #
253
+ # +owner_url+ (*required*)::
254
+ # the URL of the owner using Defensio. Note that this value should be
255
+ # provided in your default options for Viking and will be automatically
256
+ # inserted into your call.
257
+ # +signatures+ (comma separated Strings)(*required*)::
258
+ # a comma separated list of signatures (or single entry) to be submitted
259
+ # for retraining. The signatures were provided by Defensio when a
260
+ # comment was first audited.
261
+ #
262
+ # ==== Returns
263
+ # Hash::
264
+ # contains server response. Should things go awry, the full response
265
+ # will be provided.
266
+ #
267
+ # ===== Response structure
268
+ # The following are the fields returned from the server and their possible
269
+ # values:
270
+ #
271
+ # +status+::
272
+ # indicates whether or not the key is valid for this blog. Either
273
+ # 'success' or 'fail'.
274
+ # +message+::
275
+ # the message provided by the action, if applicable
276
+ # +api_version+::
277
+ # the API version used to process the request
278
+ def mark_as_ham(options = {})
279
+ return false if invalid_options?
280
+ call_defensio 'report-false-positives', options
281
+ end
282
+
283
+ # This action returns basic statistics regarding the performance of
284
+ # Defensio since activation
285
+ #
286
+ # ==== Returns
287
+ # Hash::
288
+ # contains server response. Should things go awry, the full response
289
+ # will be provided.
290
+ #
291
+ # ===== Response structure
292
+ # The following are the fields returned from the server and their possible
293
+ # values:
294
+ #
295
+ # +status+::
296
+ # indicates whether or not the key is valid for this blog. Either
297
+ # 'success' or 'fail'.
298
+ # +message+::
299
+ # the message provided by the action, if applicable
300
+ # +api_version+::
301
+ # the API version used to process the request
302
+ # +accuracy+::
303
+ # a value between 0 and 1 representing the percentage of comments
304
+ # correctly identified as spam or ham by Defensio on this blog
305
+ # +spam+::
306
+ # the number of spam comments caught by the filter
307
+ # +ham+::
308
+ # the number of legitimate comments caught by the filter
309
+ # +false_positives+::
310
+ # the number of times legitimate messages have been retrained (i.e.
311
+ # "de-spammed") by the user
312
+ # +false_negatives+::
313
+ # the number of times a comments had to be marked as spam by the user
314
+ # +learning+::
315
+ # whether or not Defensio is still in its initial learning phase (either
316
+ # +true+ or +false+)
317
+ # +learning_status+::
318
+ # more reasons on why Defensio is still learning
319
+ def stats
320
+ return false if invalid_options?
321
+ call_defensio 'get-stats'
322
+ end
323
+
324
+ # Formats a URL for use with the Defensio service.
325
+ #
326
+ # ==== Arguments
327
+ # +action+ <String>:: the action you wish to call
328
+ #
329
+ # ==== Returns
330
+ # String
331
+ #
332
+ # ==== Example
333
+ # > defensio.url('get-stats')
334
+ # => '/blog/1.2/get-stats/1234abc.yaml'
335
+ def url(action)
336
+ URI.escape(
337
+ [
338
+ '', # ensures opening /
339
+ self.class.service_type,
340
+ self.class.api_version,
341
+ action,
342
+ self.options[:api_key]
343
+ ].join('/')
344
+ ) << '.yaml'
345
+ end
346
+
347
+ protected
348
+
349
+ def call_defensio(action, params = {})
350
+ resp = defensio_http.post(
351
+ url(action),
352
+ data(params),
353
+ self.class.standard_headers
354
+ )
355
+ log_request(url(action), data, resp)
356
+ process_response_body(resp.body)
357
+ end
358
+
359
+ def defensio_http
360
+ Net::HTTP.new(
361
+ self.class.host,
362
+ self.class.port,
363
+ options[:proxy_host],
364
+ options[:proxy_port]
365
+ )
366
+ end
367
+
368
+ def data(params = {})
369
+ params.update('owner-url' => self.options[:blog] || options[:owner_url]).dasherize_keys.to_query
370
+ end
371
+
372
+ private
373
+
374
+ attr_accessor :verify_options
375
+
376
+ def process_response_body(response_body)
377
+ data = YAML.load(response_body)
378
+ return data['defensio-result'].symbolize_keys
379
+ rescue
380
+ { :data => data, :status => 'fail' }
381
+ end
382
+
383
+ end
384
+ end
@@ -0,0 +1,3 @@
1
+ module Viking
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe Object do
4
+
5
+ describe '#to_param' do
6
+
7
+ it 'should return a representation of an object' do
8
+ 1.to_param.should == '1'
9
+ end
10
+
11
+ end
12
+
13
+ describe '#to_query' do
14
+
15
+ it 'should CGI escape an object and its associated key' do
16
+ 'foo'.to_query('bar').should == 'bar=foo'
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hash do
4
+
5
+ describe '#symbolize_keys' do
6
+
7
+ it 'should convert all keys to symbols' do
8
+ { 'foo' => 'bar', :baz => 1 }.symbolize_keys.should == { :foo => 'bar', :baz => 1 }
9
+ end
10
+
11
+ it 'should handle bad keys' do
12
+ { nil => 'bar' }.symbolize_keys[nil].should == 'bar'
13
+ end
14
+
15
+ end
16
+
17
+ describe '#dasherize_keys' do
18
+
19
+ it 'should convert all all underscores in keys to dashes' do
20
+ { 'foo_bar' => 'baz' }.dasherize_keys.should == { 'foo-bar' => 'baz' }
21
+ end
22
+
23
+ end
24
+
25
+ describe '#to_query' do
26
+
27
+ it 'should convert to a valid URI query' do
28
+ { :foo => 'baz', :bar => 1 }.to_query.should == 'bar=1&foo=baz'
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+
35
+ describe Array, '#to_query' do
36
+
37
+ it 'should convert to a valid URI query' do
38
+ [:foo, :bar].to_query('baz').should == 'baz%5B%5D=foo&baz%5B%5D=bar'
39
+ end
40
+
41
+ end