viking 1.0.0

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.
@@ -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