spamster 0.0.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Spamster [![Build Status](https://secure.travis-ci.org/balexand/spamster.png)](http://travis-ci.org/balexand/spamster)
2
2
 
3
- Simple spam filtering.
3
+ Simple spam filtering that works with any Ruby application and can be set up in minutes. It does not depend on any specific ORM or framework, although it includes optional Rack middleware. It uses Akismet or TypePad AntiSpam.
4
4
 
5
5
  ## Installation
6
6
 
@@ -14,29 +14,75 @@ And then execute:
14
14
 
15
15
  $ bundle
16
16
 
17
- Or install it yourself as:
17
+ ## Usage
18
18
 
19
- $ gem install spamster
19
+ ### Configuration
20
20
 
21
- ## Usage
21
+ First you'll need to sign up for an API key from [Akismet](https://akismet.com/signup/) or [TypePad AntiSpam](http://antispam.typepad.com/info/get-api-key.html). Then configure Spamster like:
22
+
23
+ ```ruby
24
+ Spamster.use_akismet("your-api-key", "http://yoursite.com/")
25
+ ```
26
+
27
+ or...
28
+
29
+ ```ruby
30
+ Spamster.use_typepad("your-api-key", "http://yoursite.com/")
31
+ ```
32
+
33
+ If you're building a Rack app (all Rails 3+ apps are Rack apps), then you'll probably want to use the optional Rack middleware so Spamster can automatically fill in the `referrer`, `user_agent`, and `user_ip` params. If you're using Rails then add the middleware like this:
34
+
35
+ ```ruby
36
+ Rails.application.config.middleware.use Spamster::Rack::Middleware
37
+ ```
38
+
39
+ If you're using Rails, then a suggested location to keep the above configuration is in an initializer file.
40
+
41
+ ### Sanity check
22
42
 
23
- ### Quick start
43
+ First check that your key is valid:
24
44
 
25
- Start by signing up for an API key [here](https://akismet.com/signup/). Then configure Spamster like:
45
+ ```ruby
46
+ Spamster.key_valid? # => true
47
+ ```
48
+
49
+ Then check that it detects spam using the name `viagra-test-123`:
50
+
51
+ ```ruby
52
+ Spamster.spam?(user_ip: "222.222.222.222", user_agent: "Mozilla", comment_author: "viagra-test-123") # => true
53
+ ```
54
+
55
+ And if you want to see the HTTP requests/responses while debugging:
26
56
 
27
57
  ```ruby
28
- Spamster.blog = "http://yoursite.com/"
29
- Spamster.key = "your-api-key"
58
+ Spamster.debug_output = $stderr
30
59
  ```
31
60
 
32
- FIXME middleware
33
- FIXME usage
61
+ ### Model mixin
62
+
63
+ The easiest way to use Spamster is to include the mixin in your comment model:
64
+
65
+ ```ruby
66
+ class Comment
67
+ attr_accessor :content, :email, :name
68
+
69
+ include Spamster::Model
70
+ spamster_attrs comment_author: :name, comment_author_email: :email, comment_content: :content
71
+ end
72
+
73
+ comment = Comment.new #...
74
+ comment.spamster.spam? # checks for spam
75
+ comment.spamster.spam! # reports a false negative
76
+ comment.spamster.ham! # reports a false positive
77
+ ```
78
+
79
+ For a full list of parameters accepted by `spamster_attrs`, see [Akismet's documentation for `comment-check`](http://akismet.com/development/api/#comment-check).
34
80
 
35
81
  ### Spamster methods
36
82
 
37
83
  #### key_valid?
38
84
 
39
- Checks if the key is valid using [verify-key](http://akismet.com/development/api/#verify-key).
85
+ Checks if the key is valid using [`verify-key`](http://akismet.com/development/api/#verify-key).
40
86
 
41
87
  ```ruby
42
88
  Spamster.key_valid?
@@ -44,7 +90,7 @@ Spamster.key_valid?
44
90
 
45
91
  #### spam?
46
92
 
47
- Checks if a comment is spam using [comment-check](http://akismet.com/development/api/#comment-check)
93
+ Checks if a comment is spam using [`comment-check`](http://akismet.com/development/api/#comment-check).
48
94
 
49
95
  ```ruby
50
96
  Spamster.spam?(user_ip: "222.222.222.222", user_agent: "Mozilla", comment_author: "viagra-test-123")
@@ -52,7 +98,7 @@ Spamster.spam?(user_ip: "222.222.222.222", user_agent: "Mozilla", comment_author
52
98
 
53
99
  ### spam!
54
100
 
55
- Reports a false negative using [submit-spam](http://akismet.com/development/api/#submit-spam)
101
+ Reports a false negative using [`submit-spam`](http://akismet.com/development/api/#submit-spam).
56
102
 
57
103
  ```ruby
58
104
  Spamster.spam!(user_ip: "222.222.222.222", user_agent: "Mozilla", comment_author: "viagra-test-123")
@@ -60,7 +106,7 @@ Spamster.spam!(user_ip: "222.222.222.222", user_agent: "Mozilla", comment_author
60
106
 
61
107
  ### ham!
62
108
 
63
- Reports a false positive using [submit-ham](http://akismet.com/development/api/#submit-ham)
109
+ Reports a false positive using [`submit-ham`](http://akismet.com/development/api/#submit-ham).
64
110
 
65
111
  ```ruby
66
112
  Spamster.ham!(user_ip: "222.222.222.222", user_agent: "Mozilla", comment_author: "viagra-test-123")
@@ -68,8 +114,6 @@ Spamster.ham!(user_ip: "222.222.222.222", user_agent: "Mozilla", comment_author:
68
114
 
69
115
  ## Contributing
70
116
 
71
- 1. Fork it
72
- 2. Create your feature branch (`git checkout -b my-new-feature`)
73
- 3. Commit your changes (`git commit -am 'Added some feature'`)
74
- 4. Push to the branch (`git push origin my-new-feature`)
75
- 5. Create new Pull Request
117
+ Fork it, install dependencies with `bundle`, and run tests with `bundle exec rake`. If you submit a pull request, then remember to include tests.
118
+
119
+ Created by Brian Alexander and released under an MIT License.
@@ -1,54 +1,81 @@
1
+ require 'active_support'
2
+ require 'active_support/core_ext'
1
3
  require 'net/http'
2
4
  require 'spamster/version'
5
+ require 'uri'
3
6
 
4
7
  module Spamster
8
+ autoload :Model, 'spamster/model'
9
+ autoload :Rack, 'spamster/rack/middleware'
10
+
5
11
  class <<self
6
- attr_accessor :blog, :key
12
+ attr_accessor :api_host, :blog, :debug_output, :key, :request_params
13
+
14
+ def use_akismet(key, blog)
15
+ self.api_host = "rest.akismet.com"
16
+ self.blog = blog
17
+ self.key = key
18
+ end
19
+
20
+ def use_typepad(key, blog)
21
+ self.api_host = "api.antispam.typepad.com"
22
+ self.blog = blog
23
+ self.key = key
24
+ end
7
25
 
8
26
  # see http://akismet.com/development/api/#verify-key
9
27
  def key_valid?
10
- check_config
11
28
  params = {:blog => blog, :key => key}
12
- response = Net::HTTP.post_form(URI("http://rest.akismet.com/1.1/verify-key"), params)
29
+ response = perform_post("http://#{api_host}/1.1/verify-key", params)
13
30
  response.body == 'valid'
14
31
  end
15
32
 
16
33
  # see http://akismet.com/development/api/#comment-check
17
34
  def spam?(params)
18
- submit("comment-check", params) == 'true'
35
+ perform_spam_post("comment-check", params) == 'true'
19
36
  end
20
37
 
21
38
  # see http://akismet.com/development/api/#submit-spam
22
39
  def spam!(params)
23
- submit("submit-spam", params)
40
+ perform_spam_post("submit-spam", params)
24
41
  end
25
42
 
26
43
  # see http://akismet.com/development/api/#submit-ham
27
44
  def ham!(params)
28
- submit("submit-ham", params)
45
+ perform_spam_post("submit-ham", params)
29
46
  end
30
47
 
31
48
  private
32
- def check_config
33
- raise "'Spamster.blog' must be set" unless blog
34
- raise "'Spamster.key' must be set" unless key
49
+ def perform_post(url, params)
50
+ [:api_host, :blog, :key].each do |param|
51
+ raise "'Spamster.#{param}' must be set" unless send(param).present?
52
+ end
53
+
54
+ uri = URI(url)
55
+
56
+ http = Net::HTTP.new(uri.host, uri.port)
57
+ http.set_debug_output(debug_output) if debug_output
58
+
59
+ req = Net::HTTP::Post.new(uri.path)
60
+ req.set_form_data(params)
61
+ # Akismet wants User-Agent format: Application Name/Version | Plugin Name/Version
62
+ user_agent = "Spamster/#{VERSION}"
63
+ user_agent = "Rails/#{Rails.version} | " + user_agent if defined?(Rails)
64
+ req['User-Agent'] = user_agent
65
+
66
+ http.start { |h| h.request(req) }
35
67
  end
36
68
 
37
- def check_required_params(params)
38
- # these params are required by spam?, spam!, and ham!
69
+ # checks params and performs post for spam?, spam!, and ham!
70
+ def perform_spam_post(method, params = {})
71
+ params = params.merge(:blog => blog)
72
+ params.merge!(request_params) if request_params
39
73
  [:blog, :user_agent, :user_ip].each do |param|
40
- raise "required param #{param.inspect} is missing" unless params[param]
74
+ raise "required param #{param.inspect} is missing" unless params[param].present?
41
75
  end
42
- end
43
76
 
44
- def submit(method, params = {})
45
- check_config
46
- params = params.merge(:blog => blog)
47
- check_required_params(params)
48
- response = Net::HTTP.post_form(URI("http://#{key}.rest.akismet.com/1.1/#{method}"), params)
77
+ response = perform_post("http://#{key}.#{api_host}/1.1/#{method}", params)
49
78
  response.body
50
79
  end
51
80
  end
52
81
  end
53
-
54
- # FIXME User-Agent
@@ -0,0 +1,23 @@
1
+ require 'active_support/concern'
2
+ require 'spamster/model/proxy'
3
+
4
+ module Spamster
5
+ module Model
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ spamster_attrs({})
10
+ end
11
+
12
+ module ClassMethods
13
+ def spamster_attrs(attrs = nil)
14
+ @spamster_attrs = attrs if attrs
15
+ @spamster_attrs
16
+ end
17
+ end
18
+
19
+ def spamster
20
+ Proxy.new(self)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,29 @@
1
+ module Spamster
2
+ module Model
3
+ class Proxy
4
+ def initialize(model)
5
+ @model = model
6
+ end
7
+
8
+ def spam?
9
+ Spamster.spam?(data)
10
+ end
11
+
12
+ def spam!
13
+ Spamster.spam!(data)
14
+ end
15
+
16
+ def ham!
17
+ Spamster.ham!(data)
18
+ end
19
+
20
+ private
21
+ def data
22
+ Hash[@model.class.spamster_attrs.map do |param, method|
23
+ [param, @model.send(method)]
24
+ end]
25
+ end
26
+ end
27
+ end
28
+ end
29
+
@@ -0,0 +1,22 @@
1
+ module Spamster
2
+ module Rack
3
+ class Middleware
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ params = {}
10
+ params[:referrer] = env['HTTP_REFERER'] if env['HTTP_REFERER']
11
+ params[:user_agent] = env['HTTP_USER_AGENT'] if env['HTTP_USER_AGENT']
12
+ params[:user_ip] = env['REMOTE_ADDR'] if env['REMOTE_ADDR']
13
+ Spamster.request_params = params
14
+
15
+ response = @app.call(env)
16
+
17
+ Spamster.request_params = nil
18
+ response
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,3 +1,3 @@
1
1
  module Spamster
2
- VERSION = "0.0.1"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -4,8 +4,8 @@ require File.expand_path('../lib/spamster/version', __FILE__)
4
4
  Gem::Specification.new do |gem|
5
5
  gem.authors = ["Brian Alexander"]
6
6
  gem.email = ["balexand@gmail.com"]
7
- gem.description = %q{Simple spam filtering that takes minutes to set up.}
8
- gem.summary = %q{Simple spam filtering.}
7
+ gem.description = %q{Simple spam filtering that works with any Ruby application and can be set up in minutes. Uses Akismet or TypePad AntiSpam.}
8
+ gem.summary = %q{Simple spam filtering using Akismet or TypePad AntiSpam.}
9
9
  gem.homepage = "https://github.com/balexand/spamster"
10
10
 
11
11
  gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
@@ -19,5 +19,6 @@ Gem::Specification.new do |gem|
19
19
  gem.add_development_dependency "rspec"
20
20
  gem.add_development_dependency "webmock"
21
21
 
22
+ gem.add_runtime_dependency "activesupport"
22
23
  gem.add_runtime_dependency "jruby-openssl" if RUBY_PLATFORM == "java"
23
24
  end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ class Comment
4
+ include Spamster::Model
5
+ spamster_attrs :comment_author => :name, :comment_author_email => :email, :comment_content => :content
6
+ attr_accessor :content, :email, :name
7
+ end
8
+
9
+ describe Spamster::Model do
10
+ let(:comment) do
11
+ Comment.new.tap do |c|
12
+ c.content = "hello world"
13
+ c.email = "test@example.com"
14
+ c.name = "John"
15
+ end
16
+ end
17
+
18
+ describe "spamster.data" do
19
+ it "should get correct params from model" do
20
+ comment.spamster.send(:data).should == {:comment_author => "John", :comment_author_email => "test@example.com", :comment_content => "hello world"}
21
+ end
22
+ end
23
+
24
+ describe "spamster.spam?" do
25
+ it "should pass params from model" do
26
+ Spamster.should_receive(:spam?).once.with(:comment_author => "John", :comment_author_email => "test@example.com", :comment_content => "hello world")
27
+ comment.spamster.spam?
28
+ end
29
+ end
30
+
31
+ describe "spamster.spam!" do
32
+ it "should pass params from model" do
33
+ Spamster.should_receive(:spam!).once.with(:comment_author => "John", :comment_author_email => "test@example.com", :comment_content => "hello world")
34
+ comment.spamster.spam!
35
+ end
36
+ end
37
+
38
+ describe "spamster.ham!" do
39
+ it "should pass params from model" do
40
+ Spamster.should_receive(:ham!).once.with(:comment_author => "John", :comment_author_email => "test@example.com", :comment_content => "hello world")
41
+ comment.spamster.ham!
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spamster::Rack::Middleware do
4
+ before(:each) do
5
+ Spamster.use_akismet("123abc", "http://example.com/")
6
+ end
7
+
8
+ describe "call" do
9
+ it "should set 'referrer', 'user_agent', and 'user_ip' request_params" do
10
+ stub_request(:post, "http://123abc.rest.akismet.com/1.1/comment-check")
11
+
12
+ app = Object.new
13
+ app.stub(:call) do |env|
14
+ Spamster.request_params.should == {
15
+ :referrer => 'http://referer.com/',
16
+ :user_agent => 'Mozillaish',
17
+ :user_ip => "123.123.123.123"
18
+ }
19
+
20
+ Spamster.spam?(:comment_author => "Test User")
21
+ end
22
+ app.should_receive(:call).once
23
+ Spamster::Rack::Middleware.new(app).call(
24
+ 'HTTP_REFERER' => 'http://referer.com/', 'HTTP_USER_AGENT' => 'Mozillaish', 'REMOTE_ADDR' => '123.123.123.123'
25
+ )
26
+ Spamster.request_params.should be_nil
27
+
28
+ assert_requested(:post, "http://123abc.rest.akismet.com/1.1/comment-check", :times => 1) do |req|
29
+ CGI.parse(req.body).should == {"comment_author"=>["Test User"], "blog"=>["http://example.com/"], "referrer"=>["http://referer.com/"], "user_agent"=>["Mozillaish"], "user_ip"=>["123.123.123.123"]}
30
+ end
31
+ end
32
+ end
33
+ end
@@ -2,28 +2,49 @@ require 'spec_helper'
2
2
 
3
3
  describe Spamster do
4
4
  before(:each) do
5
- Spamster.blog = "http://example.com/"
6
- Spamster.key = "123abc"
5
+ Spamster.use_akismet("123abc", "http://example.com/")
7
6
  end
8
7
 
9
- describe "submit" do
8
+ describe "perform_post" do
10
9
  before(:each) do
11
10
  stub_request(:post, "http://123abc.rest.akismet.com/1.1/comment-check")
12
11
  end
13
12
 
13
+ it "should raise exception if :api_host is not configured" do
14
+ Spamster.api_host = ""
15
+ expect do
16
+ Spamster.send(:perform_post, "http://123abc.rest.akismet.com/1.1/comment-check", {})
17
+ end.to raise_exception{ |e| e.message.should == "'Spamster.api_host' must be set" }
18
+ end
19
+
14
20
  it "should raise exception if :blog is not configured" do
15
- Spamster.blog = nil
21
+ Spamster.blog = ""
16
22
  expect do
17
- Spamster.send(:submit, "comment-check")
23
+ Spamster.send(:perform_post, "http://123abc.rest.akismet.com/1.1/comment-check", {})
18
24
  end.to raise_exception{ |e| e.message.should == "'Spamster.blog' must be set" }
19
25
  end
20
26
 
21
27
  it "should raise exception if :key is not configured" do
22
- Spamster.key = nil
28
+ Spamster.key = " \n"
23
29
  expect do
24
- Spamster.send(:submit, "comment-check")
30
+ Spamster.send(:perform_post, "http://123abc.rest.akismet.com/1.1/comment-check", {})
25
31
  end.to raise_exception{ |e| e.message.should == "'Spamster.key' must be set" }
26
32
  end
33
+
34
+ it "should set User-Agent" do
35
+ Spamster.send(:perform_post, "http://123abc.rest.akismet.com/1.1/comment-check", {})
36
+ assert_requested(:post, "http://123abc.rest.akismet.com/1.1/comment-check", :times => 1, :headers => {'User-Agent' => "Spamster/#{Spamster::VERSION}"})
37
+
38
+ class Rails
39
+ def self.version
40
+ "3.2.2"
41
+ end
42
+ end
43
+
44
+ Spamster.send(:perform_post, "http://123abc.rest.akismet.com/1.1/comment-check", {})
45
+ assert_requested(:post, "http://123abc.rest.akismet.com/1.1/comment-check", :times => 1, :headers => {'User-Agent' => "Rails/3.2.2 | Spamster/#{Spamster::VERSION}"})
46
+ Object.send(:remove_const, :Rails)
47
+ end
27
48
  end
28
49
 
29
50
  describe "key_valid?" do
@@ -67,7 +88,7 @@ describe Spamster do
67
88
 
68
89
  it "should raise exception if required param missing" do
69
90
  expect do
70
- Spamster.spam?(:user_ip => "222.222.222.222")
91
+ Spamster.spam?(:user_agent => " ", :user_ip => "222.222.222.222")
71
92
  end.to raise_exception{ |e| e.message.should == "required param :user_agent is missing" }
72
93
 
73
94
  expect do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spamster
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 1.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-03-28 00:00:00.000000000 Z
12
+ date: 2012-03-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -59,7 +59,24 @@ dependencies:
59
59
  - - ! '>='
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
- description: Simple spam filtering that takes minutes to set up.
62
+ - !ruby/object:Gem::Dependency
63
+ name: activesupport
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: Simple spam filtering that works with any Ruby application and can be
79
+ set up in minutes. Uses Akismet or TypePad AntiSpam.
63
80
  email:
64
81
  - balexand@gmail.com
65
82
  executables: []
@@ -73,8 +90,13 @@ files:
73
90
  - README.md
74
91
  - Rakefile
75
92
  - lib/spamster.rb
93
+ - lib/spamster/model.rb
94
+ - lib/spamster/model/proxy.rb
95
+ - lib/spamster/rack/middleware.rb
76
96
  - lib/spamster/version.rb
77
97
  - spamster.gemspec
98
+ - spec/spamster/model_spec.rb
99
+ - spec/spamster/rack/middleware_spec.rb
78
100
  - spec/spamster_spec.rb
79
101
  - spec/spec_helper.rb
80
102
  homepage: https://github.com/balexand/spamster
@@ -89,18 +111,26 @@ required_ruby_version: !ruby/object:Gem::Requirement
89
111
  - - ! '>='
90
112
  - !ruby/object:Gem::Version
91
113
  version: '0'
114
+ segments:
115
+ - 0
116
+ hash: -3161924574973985912
92
117
  required_rubygems_version: !ruby/object:Gem::Requirement
93
118
  none: false
94
119
  requirements:
95
120
  - - ! '>='
96
121
  - !ruby/object:Gem::Version
97
122
  version: '0'
123
+ segments:
124
+ - 0
125
+ hash: -3161924574973985912
98
126
  requirements: []
99
127
  rubyforge_project:
100
128
  rubygems_version: 1.8.19
101
129
  signing_key:
102
130
  specification_version: 3
103
- summary: Simple spam filtering.
131
+ summary: Simple spam filtering using Akismet or TypePad AntiSpam.
104
132
  test_files:
133
+ - spec/spamster/model_spec.rb
134
+ - spec/spamster/rack/middleware_spec.rb
105
135
  - spec/spamster_spec.rb
106
136
  - spec/spec_helper.rb