defender 0.1.1 → 0.2.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.
data/LICENSE CHANGED
@@ -1,20 +1,20 @@
1
- Copyright (c) 2009 Henrik Hodne
1
+ Copyright (c) 2009-2010 Henrik Hodne
2
2
 
3
- Permission is hereby granted, free of charge, to any person obtaining
4
- a copy of this software and associated documentation files (the
5
- "Software"), to deal in the Software without restriction, including
6
- without limitation the rights to use, copy, modify, merge, publish,
7
- distribute, sublicense, and/or sell copies of the Software, and to
8
- permit persons to whom the Software is furnished to do so, subject to
9
- the following conditions:
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
10
 
11
- The above copyright notice and this permission notice shall be
12
- included in all copies or substantial portions of the Software.
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
13
 
14
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc CHANGED
@@ -2,6 +2,10 @@
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
+ Thanks to Defensio for the excellent documentation, the documentation in the code is more or less copied from them.
6
+
7
+ Defender follows the {Semantic Versioning}[http://semver.org/] spec.
8
+
5
9
  == Note on Patches/Pull Requests
6
10
 
7
11
  * Fork the project.
@@ -15,4 +19,4 @@ This is a Ruby wrapper of the Defensio[http://defensio.com] spam filtering API.
15
19
 
16
20
  == Copyright
17
21
 
18
- Copyright (c) 2009 Henrik Hodne. See LICENSE for details.
22
+ Copyright (c) 2009-2010 Henrik Hodne. See LICENSE for details.
data/Rakefile CHANGED
@@ -10,9 +10,10 @@ begin
10
10
  gem.email = "henrik.hodne@binaryhex.com"
11
11
  gem.homepage = "http://github.com/dvyjones/defender"
12
12
  gem.authors = ["Henrik Hodne"]
13
- gem.add_development_dependency "rspec", ">= 1.2.9"
14
- gem.add_development_dependency "yard", ">= 0"
15
- gem.add_development_dependency "mocha", ">= 0.9.8"
13
+ gem.add_dependency "httparty", "~> 0.4.3"
14
+ gem.add_development_dependency "rspec", "~> 1.2.9"
15
+ gem.add_development_dependency "yard", "~> 0.4.0"
16
+ gem.add_development_dependency "fakeweb", "~> 1.2.7"
16
17
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
17
18
  end
18
19
  Jeweler::GemcutterTasks.new
@@ -62,9 +63,12 @@ task :default => :spec
62
63
 
63
64
  begin
64
65
  require 'yard'
65
- YARD::Rake::YardocTask.new
66
+ YARD::Rake::YardocTask.new do |conf|
67
+ #conf.options = ["-mmarkdown"]
68
+ conf.files = ["lib/**/*.rb", "-", "LICENSE"]
69
+ end
66
70
  rescue LoadError
67
- task :yardoc do
71
+ task :yard do
68
72
  abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
69
73
  end
70
74
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.2.0
data/defender.gemspec ADDED
@@ -0,0 +1,70 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{defender}
8
+ s.version = "0.2.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Henrik Hodne"]
12
+ s.date = %q{2010-01-20}
13
+ s.description = %q{A wrapper of the Defensio spam filtering service.}
14
+ s.email = %q{henrik.hodne@binaryhex.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "defender.gemspec",
27
+ "lib/defender.rb",
28
+ "lib/defender/document.rb",
29
+ "lib/defender/statistics.rb",
30
+ "spec/defender_spec.rb",
31
+ "spec/document_spec.rb",
32
+ "spec/spec.opts",
33
+ "spec/spec_helper.rb",
34
+ "spec/statistics_spec.rb"
35
+ ]
36
+ s.homepage = %q{http://github.com/dvyjones/defender}
37
+ s.rdoc_options = ["--charset=UTF-8"]
38
+ s.require_paths = ["lib"]
39
+ s.rubygems_version = %q{1.3.5}
40
+ s.summary = %q{Ruby API wrapper for Defensio}
41
+ s.test_files = [
42
+ "spec/spec_helper.rb",
43
+ "spec/document_spec.rb",
44
+ "spec/defender_spec.rb",
45
+ "spec/statistics_spec.rb"
46
+ ]
47
+
48
+ if s.respond_to? :specification_version then
49
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
50
+ s.specification_version = 3
51
+
52
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
53
+ s.add_runtime_dependency(%q<httparty>, ["~> 0.4.3"])
54
+ s.add_development_dependency(%q<rspec>, ["~> 1.2.9"])
55
+ s.add_development_dependency(%q<yard>, ["~> 0.4.0"])
56
+ s.add_development_dependency(%q<fakeweb>, ["~> 1.2.7"])
57
+ else
58
+ s.add_dependency(%q<httparty>, ["~> 0.4.3"])
59
+ s.add_dependency(%q<rspec>, ["~> 1.2.9"])
60
+ s.add_dependency(%q<yard>, ["~> 0.4.0"])
61
+ s.add_dependency(%q<fakeweb>, ["~> 1.2.7"])
62
+ end
63
+ else
64
+ s.add_dependency(%q<httparty>, ["~> 0.4.3"])
65
+ s.add_dependency(%q<rspec>, ["~> 1.2.9"])
66
+ s.add_dependency(%q<yard>, ["~> 0.4.0"])
67
+ s.add_dependency(%q<fakeweb>, ["~> 1.2.7"])
68
+ end
69
+ end
70
+
data/lib/defender.rb CHANGED
@@ -1,299 +1,67 @@
1
- require 'yaml'
2
- require 'net/http'
1
+ require 'httparty'
2
+
3
+ require 'defender/document'
4
+ require 'defender/statistics'
5
+
6
+ module Defender
7
+ VERSION = "0.2.0"
8
+
9
+ include HTTParty
3
10
 
4
- class Defender
5
11
  # The Defensio API version currently supported by Defender
6
- API_VERSION = "1.2"
7
-
8
- ROOT_URL = "http://api.defensio.com/"
9
-
10
- DEFAULT_OPTIONS = {
11
- :service_type => "blog",
12
- :api_key => "",
13
- :owner_url => ""
14
- }
15
-
16
- ##
17
- # Raised if an invalid or no API key is given
18
- class APIKeyError < StandardError; end
19
-
20
- ##
21
- # The response from the {Defender#audit_comment} method. Should only be
22
- # initialized by the library.
23
- class CommentResponse
12
+ API_VERSION = "2.0"
13
+
14
+ # HTTParty config
15
+ format :json
16
+ base_uri "api.defensio.com/#{API_VERSION}/users"
17
+
18
+ class << self
24
19
  ##
25
- # A message signature that uniquely identifies the comment in the Defensio
26
- # system. This signature should be stored by the client for retraining
27
- # purposes.
28
- attr_reader :signature
29
-
20
+ # Your Defensio API key. You need to register at defensio.com to get a key.
21
+ attr_accessor :api_key
22
+
30
23
  ##
31
- # A value indicating the relative likelihood of the comment being spam.
32
- # This value should be stored by the client for use in building convenient
33
- # spam sorting user-interfaces.
24
+ # The URL that will be called when Defensio is done analyzing a comment with
25
+ # asynchronous callbacks. You should be able to pass the request parameters
26
+ # straight into {Document#set_attributes}. The signature will be in the
27
+ # `signature` parameter.
34
28
  #
35
- # @return [Float] A value between 0 and 1.
36
- attr_reader :spaminess
37
-
38
- ##
39
- # Initialize a CommentResponse. Should only be called by the library.
29
+ # *IMPORTANT*: Defensio will NOT retry unsuccessful callbacks to your
30
+ # server. If you do not see a POST originating from Defensio after 5
31
+ # minutes, call {Document#refresh!} on the document to obtain the analysis
32
+ # result.
40
33
  #
41
- # @param [Hash] response The response from the audit-comment call.
42
- def initialize(response)
43
- @signature = response["signature"]
44
- @spam = response["spam"]
45
- @spaminess = response["spaminess"].to_f
46
- end
47
-
48
- ##
49
- # Returns true if Defensio marked the comment as spam, returns false
50
- # otherwise.
34
+ # Occasionally, Defensio may perform more than one POST request to your
35
+ # server for the same document. For example, if new evidence indicates that
36
+ # a document is unwanted, even though it was originally identified as
37
+ # legitimate, Defensio might notify you that the classification has changed.
51
38
  #
52
- # @return [Boolean]
53
- def spam?; @spam; end
54
-
55
- def to_s; @signature; end
56
- end
57
-
58
- ##
59
- # The response from the {Defender#statistics} method. Should only be
60
- # initialized by the library.
61
- class Statistics
62
- ##
63
- # Describes the percentage of comments correctly identified as spam/ham by
64
- # Defensio on this blog.
39
+ # If you do not provide this and use asynchronous calling, you need to call
40
+ # {Document#refresh!} to get the analysis result.
65
41
  #
66
- # @return [Float<0..1>]
67
- def accuracy; @response["accuracy"]; end
68
-
69
- ##
70
- # The number of spam comments caught by the filter.
71
- def spam; @response["spam"]; end
72
-
73
- ##
74
- # The number of ham (legitimate) comments accepted by the filter.
75
- def ham; @response["ham"]; end
76
-
77
- ##
78
- # The number of times a legitimate message was retrained from the spambox
79
- # (i.e. "de-spammed" by the user)
80
- def false_positives; @response["false-positives"]; end
81
-
82
- ##
83
- # The number of times a spam message was retrained from comments box (i.e.
84
- # "de-legitimized" by the user)
85
- def false_negatives; @response["false-negatives"]; end
86
-
87
- ##
88
- # A boolean value indicating whether Defensio is still in its initial
89
- # learning phase.
42
+ # You can debug callbacks using http://postbin.org. See the Defensio API
43
+ # documents for the format of the requests.
90
44
  #
91
- # @return [Boolean]
92
- def learning; @response["learning"]; end
93
-
94
- ##
95
- # More details on the reason(s) why Defensio is still in its initial
96
- # learning phase.
97
- def learning_status; @response["learning-status"]; end
98
-
99
- def initialize(response); @response = response; end
100
- end
101
-
102
- attr_accessor :service_type, :api_key, :owner_url
103
-
104
- ##
105
- # Raises a StandardError with the error message from Defensio if the
106
- # response is a "failed" one.
107
- #
108
- # @param [Hash] response The return value from {#call_action}.
109
- def self.raise_if_error(response)
110
- if response["status"] == "fail"
111
- raise StandardError, response["message"]
112
- end
113
- response
114
- end
115
-
116
- ##
117
- # Converts a hash with symbol keys and underscores to a hash with string
118
- # keys and hyphens. Calls #strftime or #to_s on the values.
119
- #
120
- # @param [Hash] options Input options.
121
- # @return [Hash]
122
- def self.options_to_parameters(options)
123
- opts = {}
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
127
- end
128
- opts
45
+ # @return [String]
46
+ attr_accessor :async_callback
129
47
  end
130
48
 
131
49
  ##
132
- # Initialize Defender
50
+ # Determines if the given API key is valid or not. This should only be used
51
+ # when configuring the client and prior to every content analysis (Document
52
+ # POST).
133
53
  #
134
- # @param [Hash] opts The options hash.
135
- # @option opts ["blog","app"] :service_type ("blog") The service type. May be
136
- # "app" (use of Defender within an application) or "blog" (use of Defender
137
- # to support a blogging platform).
138
- # @option opts [String] :api_key Your API key. This option is required, the
139
- # method calls will fail without it.
140
- def initialize(opts={})
141
- opts = DEFAULT_OPTIONS.merge(opts)
142
- @service_type = opts[:service_type]
143
- @api_key = opts[:api_key]
144
- @owner_url = opts[:owner_url]
145
- end
146
-
147
- ##
148
- # Checks if the given key is valid.
149
- #
150
- # @return [Boolean]
151
- # @see http://defensio.com/api/#validate-key
152
- def valid_key?
153
- call_action("validate-key")["status"] == "success" ? true : false
154
- end
155
-
156
- ##
157
- # Announce an article existence. This should (if feasible) be called when an
158
- # article or blogpost is created so Defensio can analyse it.
54
+ # Set the API key using {Defender.api_key}.
159
55
  #
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
164
- # article.
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.
167
- # @raise [StandardError] If the call fails, a StandardError is raised with
168
- # the error message given from Defensio.
169
- # @return [Boolean] Returns true if the article was successfully announced,
170
- # raises StandardError otherwise.
171
- # @see http://defensio.com/api/#announce-article
172
- def announce_article(opts={})
173
- response = call_action(Defender.options_to_parameters(opts))
174
- true
175
- end
176
-
177
- ##
178
- # Check if a comment is spam. This is the central action of Defensio.
179
- #
180
- # @param [Hash] opts All options are recommended, but only required if noted.
181
- # @option opts [#to_s] :user_ip The IP address of whomever is posting the
182
- # comment. This option is required.
183
- # @option opts [#to_s, #strftime] :article_date The date the original blog
184
- # article was posted. If a string is given, it must be in the format
185
- # "yyyy/mm/dd". This option is required.
186
- # @option opts [#to_s] :comment_author The name of the author of the comment.
187
- # This option is required.
188
- # @option opts ["comment", "trackback", "pingback", "other"] :comment_type
189
- # The type of the comment being posted to the blog. This option is required
190
- # @option opts [#to_s] :comment_content The actual content of the comment
191
- # (strongly recommended to be included where ever possible).
192
- # @option opts [#to_s] :comment_author_email The email address of the person
193
- # posting the comment.
194
- # @option opts [#to_s] :comment_author_url The URL of the person posting the
195
- # comment.
196
- # @option opts [#to_s] :permalink The permalink of the blog post to which
197
- # the comment is being posted.
198
- # @option opts [#to_s] :referrer The URL of the site that brought commenter
199
- # to this page.
200
- # @option opts [Boolean] :user_logged_in Whether or not the user posting
201
- # the comment is logged-into the blogging platform
202
- # @option opts [Boolean] :trusted_user Whether or not the user is an
203
- # administrator, moderator or editor of this blog; the client should pass
204
- # true only if blogging platform can guarantee that the user has been
205
- # authenticated and has a role of responsibility on this blog.
206
- # @option opts [#to_s] :openid The OpenID URL of the currently logged in
207
- # user. Must be used in conjunction with :user_logged_in => true. OpenID
208
- # authentication must be taken care of by your application.
209
- # @option opts [#to_s] :test_force For testing purposes only: Use this
210
- # parameter to force the outcome of audit_comment. Optionally affix (with
211
- # a comma) a desired spaminess return value (in the range 0 to 1).
212
- # Example: "spam,0.5000" or "ham,0.0010".
213
- # @raise [StandardError] If the call fails, a StandardError is raised with
214
- # the error message given from Defensio.
215
- # @return [Defender::CommentResponse]
216
- # @see http://defensio.com/api/#audit-comment
217
- def audit_comment(opts={})
218
- response = call_action("audit-comment", Defender.options_to_parameters(opts))
219
- return CommentResponse.new(response)
220
- end
221
-
222
- ##
223
- # This action is used to retrain false negatives. False negatives are
224
- # comments that were originally tagged as "ham" (i.e. legitimate) but were
225
- # in fact spam.
226
- #
227
- # @param [Array<#to_s, CommentResponse>] signatures List of signatures (may
228
- # contain a single entry) of the comments to be submitted for retraining.
229
- # Note that a signature for each comment was originally provided by the
230
- # {#audit_comment} method.
231
- # @raise [StandardError] If the call fails, a StandardError is raised with
232
- # the error message given from Defensio.
233
- # @return [Boolean] Returns true if the comments were successfully marked,
234
- # raises StandardError otherwise.
235
- def report_false_negatives(signatures)
236
- report_false(:negatives, signatures)
237
- end
238
-
239
- ##
240
- # This action is used to retrain false negatives. False negatives are
241
- # comments that were originally tagged as spam but were in fact "ham" (i.e.
242
- # legitimate).
243
- #
244
- # @param [Array<#to_s, CommentResponse>] signatures List of signatures (may
245
- # contain a single entry) of the comments to be submitted for retraining.
246
- # Note that a signature for each comment was originally provided by the
247
- # {#audit_comment} method.
248
- # @raise [StandardError] If the call fails, a StandardError is raised with
249
- # the error message given from Defensio.
250
- # @return [Boolean] Returns true if the comments were successfully marked,
251
- # raises StandardError otherwise.
252
- def report_false_positives(signatures)
253
- report_false(:positives, signatures)
254
- end
255
-
256
- ##
257
- # This action returns basic statistics regarding the performance of Defensio
258
- # since activation.
259
- #
260
- # @return [Defender::Statistics]
261
- def statistics
262
- response = call_action("get-stats")
263
- return Statistics.new(response)
264
- end
265
-
266
- private
267
- def report_false(type, signatures)
268
- call_action("report-false-#{type}",
269
- "signatures" => signatures.join(","))
270
- true
271
- end
272
-
273
- ##
274
- # Returns the url for the given action.
275
- #
276
- # @param [#to_s] action The action to generate the URL for.
277
- # @return [String] The URL for the action.
278
- # @raise [APIKeyError] Raises this if no API key is given.
279
- def url(action)
280
- raise APIKeyError unless @api_key.length > 0
281
- "#{ROOT_URL}#{@service_type}/#{API_VERSION}/#{action}/#{@api_key}.yaml"
282
- end
283
-
284
- ##
285
- # Backend function for calling an action.
286
- #
287
- # @param [#to_s] action The action to call.
288
- # @param [Hash] params The parameters for the action.
289
- # @return [Hash] The raw response, only parsed from YAML.
290
- # @raise [APIKeyError] If an invalid (or no) API key is given, this is
291
- # raised
292
- def call_action(action, params={})
293
- response = Net::HTTP.post_form(URI.parse(url(action)),
294
- {"owner-url" => @owner_url}.merge(params))
295
- response.code == 401 ?
296
- raise(APIKeyError) :
297
- Defender.raise_if_error(YAML.load(response.body)["defensio-result"])
56
+ # @return [Boolean] Whether the API key was valid or not.
57
+ def self.check_api_key
58
+ key = Defender.api_key
59
+ return false unless key
60
+ resp = get("/#{key}.json")['defensio-result']
61
+ if resp['status'] == 'success'
62
+ return true
63
+ else
64
+ return false
298
65
  end
66
+ end
299
67
  end