defender 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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