mikehale-akismet 0.0.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/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :patch: 1
3
+ :major: 0
4
+ :minor: 0
data/lib/akismet.rb ADDED
@@ -0,0 +1,64 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+
4
+ class Akismet
5
+ USER_AGENT = "Akismet-rb/1.0 | Akismet/1.11"
6
+
7
+ def initialize(key, url)
8
+ @key = key
9
+ @url = url
10
+ end
11
+
12
+ def verify_key
13
+ response = Net::HTTP.start('rest.akismet.com', 80) do |http|
14
+ http.post('/1.1/verify-key', post_data(:key => @key, :blog => @url), {'User-Agent' => USER_AGENT})
15
+ end
16
+
17
+ case response.body
18
+ when "invalid"
19
+ raise Akismet::VerifyException, response.to_hash["x-akismet-debug-help"], caller
20
+ when "valid"
21
+ true
22
+ end
23
+ end
24
+
25
+ def submit_spam(args)
26
+ call_akismet('submit-spam', args)
27
+ end
28
+
29
+ def submit_ham(args)
30
+ call_akismet('submit-ham', args)
31
+ end
32
+
33
+ def spam?(args)
34
+ call_akismet('comment-check', args)
35
+ end
36
+
37
+ def ham?(args)
38
+ !spam?(args)
39
+ end
40
+
41
+ def call_akismet(method, args)
42
+ args.update(:blog => @url)
43
+
44
+ response = Net::HTTP.start("#{@key}.rest.akismet.com", 80) do |http|
45
+ http.post("/1.1/#{method}", post_data(args), {'User-Agent' => USER_AGENT})
46
+ end
47
+
48
+ case response.body
49
+ when "true"
50
+ true
51
+ when "false"
52
+ false
53
+ end
54
+ end
55
+
56
+ def post_data(hash)
57
+ hash.inject([]) do |memo, hash|
58
+ k, v = hash
59
+ memo << "#{k}=#{URI.escape(v)}"
60
+ end.join('&')
61
+ end
62
+
63
+ class VerifyException < Exception; end
64
+ end
@@ -0,0 +1,83 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe "Akismet" do
4
+ include SpecHttp
5
+
6
+ def params
7
+ {
8
+ :user_ip => "1.2.3.4",
9
+ :referrer => "http://othersite.com",
10
+ :permalink => "http://example.com/post",
11
+ :comment_type => "comment",
12
+ :comment_author => "joe smith",
13
+ :comment_author_email => "joe@smith.com",
14
+ :comment_author_url => "blog.smith.com"
15
+ }
16
+ end
17
+
18
+ before do
19
+ @akismet = Akismet.new('thekey', 'http://example.com')
20
+ app = lambda do |env|
21
+ spam = env["rack.input"].include?('viagra')
22
+ [200, {}, [spam ? "true" : "false"]]
23
+ end
24
+ map Rack::URLMap.new("http://thekey.rest.akismet.com/1.1/comment-check" => app)
25
+ end
26
+
27
+ it "should verify the key" do
28
+ map Rack::URLMap.new("http://rest.akismet.com/" => lambda { |env| [200, {}, ["valid"]]})
29
+
30
+ @akismet.verify_key.should == true
31
+ request.post?.should == true
32
+ request.env['HTTP_USER_AGENT'].should == "Akismet-rb/1.0 | Akismet/1.11"
33
+ request.body.should include('http://example.com')
34
+ response.status.should == 200
35
+ end
36
+
37
+ it "should not verify an invalid key" do
38
+ map Rack::URLMap.new("http://rest.akismet.com/" => lambda { |env| [200, {'x-akismet-debug-help' => 'sorry!'}, ["invalid"]]})
39
+ lambda {@akismet.verify_key}.should raise_error Akismet::VerifyException
40
+ response['x-akismet-debug-help'].should == 'sorry!'
41
+ end
42
+
43
+ it "should detect spam" do
44
+ @akismet.spam?(params.update(:comment_content => "viagra-test-123")).should == true
45
+ request.env.has_key?('HTTP_USER_AGENT').should == true
46
+ request.body.should include("blog=http://example.com")
47
+ request.body.should include("user_ip=1.2.3.4")
48
+ request.body.should include("referrer=http://othersite.com")
49
+ request.body.should include("permalink=http://example.com/post")
50
+ request.body.should include("comment_type=comment")
51
+ request.body.should include("comment_author=joe%20smith")
52
+ request.body.should include("comment_author_email=joe@smith.com")
53
+ request.body.should include("comment_author_url=blog.smith.com")
54
+ request.body.should include("comment_content=viagra-test-123")
55
+ end
56
+
57
+ it "should detect ham" do
58
+ @akismet.ham?(params.update(:comment_content => "not spam")).should == true
59
+ end
60
+
61
+ it "should submit spam" do
62
+ map Rack::URLMap.new("http://thekey.rest.akismet.com/1.1/submit-spam" => lambda { |env| [200, {}, "true"]})
63
+ @akismet.submit_spam(params.update(:comment_content => "this-is-spam"))
64
+ request.script_name.should == "/1.1/submit-spam"
65
+
66
+ request.env.has_key?('HTTP_USER_AGENT').should == true
67
+ request.body.should include("blog=http://example.com")
68
+ request.body.should include("user_ip=1.2.3.4")
69
+ request.body.should include("referrer=http://othersite.com")
70
+ request.body.should include("permalink=http://example.com/post")
71
+ request.body.should include("comment_type=comment")
72
+ request.body.should include("comment_author=joe%20smith")
73
+ request.body.should include("comment_author_email=joe@smith.com")
74
+ request.body.should include("comment_author_url=blog.smith.com")
75
+ request.body.should include("comment_content=this-is-spam")
76
+ end
77
+
78
+ it "should submit ham" do
79
+ map Rack::URLMap.new("http://thekey.rest.akismet.com/1.1/submit-ham" => lambda { |env| [200, {}, "true"]})
80
+ @akismet.submit_ham(params.update(:comment_content => "this-is-ham"))
81
+ request.script_name.should == "/1.1/submit-ham"
82
+ end
83
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,6 @@
1
+ --colour
2
+ --format
3
+ progress
4
+ --loadby
5
+ mtime
6
+ --reverse
@@ -0,0 +1,26 @@
1
+ require 'spec'
2
+ require 'rr'
3
+ require 'rack/urlmap'
4
+ require File.dirname(__FILE__) + '/spec_http'
5
+ require 'ruby-debug'
6
+
7
+ class MethodSpy
8
+ def initialize(delegate, &block)
9
+ @delegate = delegate
10
+ @filter = block
11
+ end
12
+
13
+ def method_missing(symbol, *args, &block)
14
+ result = @delegate.send(symbol, *args, &block)
15
+ p [symbol, args, result, block]
16
+ result
17
+ end
18
+ end
19
+
20
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
21
+
22
+ require 'akismet'
23
+
24
+ Spec::Runner.configure do |config|
25
+ config.mock_with :rr
26
+ end
data/spec/spec_http.rb ADDED
@@ -0,0 +1,164 @@
1
+ require 'uri'
2
+ require 'rack/request'
3
+ require 'rack/response'
4
+ require 'rack/utils'
5
+
6
+ module SpecHttp
7
+ def map(map)
8
+ @socket = SocketInterceptor.new(map)
9
+ stub(TCPSocket).open(anything, numeric) { @socket }
10
+ end
11
+
12
+ def request
13
+ Rack::Request.new(@socket.env)
14
+ end
15
+
16
+ def response
17
+ @socket.response
18
+ end
19
+ end
20
+
21
+ class SocketInterceptor < StringIO
22
+ attr_reader :env, :response
23
+
24
+ def initialize(map)
25
+ @map = map
26
+ super()
27
+ end
28
+
29
+ def write(string)
30
+ raise "Already responded" if self.string.size > 0
31
+ @written = '' unless @written
32
+ @written << string
33
+ string.size
34
+ end
35
+
36
+ # this depends on Net::HTTP calling socket.sysread after it has finished all writes
37
+ alias :orig_sysread :sysread
38
+ def sysread(size)
39
+ set_string unless self.string.size > 0
40
+ orig_sysread(size)
41
+ end
42
+
43
+ def set_string
44
+ response_io = StringIO.new
45
+ @env = Rack::RequestStringParser.env(@written)
46
+ Rack::Handler::StringIO.run(app(@env), @env, response_io)
47
+ @response = Rack::Handler::StringIO.response
48
+ self.string = response_io.string
49
+ end
50
+
51
+ def app(env)
52
+ app_to_use = @map if !@map.respond_to? :mapping
53
+ unless app_to_use
54
+ path = env["PATH_INFO"].to_s.squeeze("/")
55
+ hHost, sName, sPort = env.values_at('HTTP_HOST','SERVER_NAME','SERVER_PORT')
56
+ @map.mapping.each { |host, location, app|
57
+ next unless (hHost == host || sName == host \
58
+ || (host.nil? && (hHost == sName || hHost == sName+':'+sPort)))
59
+ next unless location == path[0, location.size]
60
+ next unless path[location.size] == nil || path[location.size] == ?/
61
+
62
+ env["SCRIPT_NAME"] += location
63
+ env["PATH_INFO"] = path[location.size..-1]
64
+ app_to_use = app
65
+ }
66
+ raise "No application associated with #{env['rack.url_scheme']}://#{hHost}#{path}" unless app_to_use
67
+ end
68
+ app_to_use
69
+ end
70
+ end
71
+
72
+ module Rack
73
+ class URLMap
74
+ attr_reader :mapping
75
+ end
76
+
77
+ class RequestStringParser
78
+ def self.env_key(key)
79
+ key = key.split('-').join('_').upcase
80
+ key = "HTTP_#{key}" unless key =~ /content/i
81
+ key
82
+ end
83
+
84
+ def self.env(input)
85
+ lines = input.split("\r\n")
86
+
87
+ # find blank line which seperates the headers from the body
88
+ index_of_blank = nil
89
+ lines.each_with_index{|e,i|
90
+ index_of_blank = i if e == ""
91
+ }
92
+
93
+ type, uri = lines.first.split(/\s+/)
94
+
95
+ if index_of_blank
96
+ headers = lines[1..index_of_blank]
97
+ body = lines[(index_of_blank + 1)..-1].first
98
+ else
99
+ headers = lines[1..-1]
100
+ end
101
+
102
+ headers = headers.inject({}){|h,e|
103
+ k,v = e.split(/:\s+/)
104
+ h.merge! env_key(k) => v if k
105
+ h
106
+ }
107
+
108
+ uri = URI(uri)
109
+ env = headers.dup
110
+ env["SERVER_NAME"] = uri.host
111
+ env["SERVER_PORT"] = uri.port ? uri.port.to_s : "80"
112
+ env["QUERY_STRING"] = uri.query.to_s
113
+ env["PATH_INFO"] = (!uri.path || uri.path.empty?) ? "/" : uri.path
114
+ env["SCRIPT_NAME"] = ""
115
+ env["REQUEST_METHOD"] = type
116
+ env["rack.url_scheme"] = uri.scheme || "http"
117
+ env["rack.input"] = body
118
+ env
119
+ end
120
+ end
121
+
122
+ module Handler
123
+ class StringIO
124
+ def self.response
125
+ @@response
126
+ end
127
+
128
+ def self.run(app, env={}, output=StringIO.new)
129
+ env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
130
+ env["QUERY_STRING"] ||= ""
131
+ env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
132
+ env["REQUEST_PATH"] ||= "/"
133
+
134
+ status, headers, body = app.call(env)
135
+ @@response = Rack::Response.new(body, status, headers)
136
+ begin
137
+ send_headers output, status, headers
138
+ send_body output, body
139
+ output.rewind
140
+ ensure
141
+ body.close if body.respond_to? :close
142
+ end
143
+ end
144
+
145
+ def self.send_headers(output, status, headers)
146
+ output.print "HTTP/1.1 #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]}\r\n"
147
+ headers.each { |k, vs|
148
+ vs.each { |v|
149
+ output.print "#{k}: #{v}\r\n"
150
+ }
151
+ }
152
+ output.print "\r\n"
153
+ output.flush
154
+ end
155
+
156
+ def self.send_body(output, body)
157
+ body.each { |part|
158
+ output.print part
159
+ output.flush
160
+ }
161
+ end
162
+ end
163
+ end
164
+ end
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mikehale-akismet
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Michael Hale
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-07 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: TODO
17
+ email: mikehale@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - VERSION.yml
26
+ - lib/akismet.rb
27
+ - spec/akismet_spec.rb
28
+ - spec/spec.opts
29
+ - spec/spec_helper.rb
30
+ - spec/spec_http.rb
31
+ has_rdoc: true
32
+ homepage: http://github.com/mikehale/akismet
33
+ post_install_message:
34
+ rdoc_options:
35
+ - --inline-source
36
+ - --charset=UTF-8
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ version:
51
+ requirements: []
52
+
53
+ rubyforge_project:
54
+ rubygems_version: 1.2.0
55
+ signing_key:
56
+ specification_version: 2
57
+ summary: TODO
58
+ test_files: []
59
+