solvemedia 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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +91 -0
- data/Rakefile +1 -0
- data/lib/solvemedia/ad_copy_error.rb +6 -0
- data/lib/solvemedia/constants.rb +6 -0
- data/lib/solvemedia/controller_methods.rb +39 -0
- data/lib/solvemedia/railtie.rb +28 -0
- data/lib/solvemedia/version.rb +3 -0
- data/lib/solvemedia/view_helpers.rb +29 -0
- data/lib/solvemedia.rb +151 -0
- data/solvemedia.gemspec +20 -0
- metadata +57 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b6db952947e80394a9fa2cd0fd3cf411aeeab69f
|
4
|
+
data.tar.gz: b72433e90e90419f653925dc707dfab211f5aec0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 493bd6688ec5736ab23915e074018576cb91a9f2df4054de33a51b9d5bc9f51139d4ea4bbcd1569df2b0f0a2258c4d84d707fbcf3f03c4f00beb0b8155e8ee9f
|
7
|
+
data.tar.gz: 64378fd7d8f7b307d93184a1e3d843e96a4b340232f76a881ea23eb91ccf32a73ece8840b5249b944fe96f732da64929c6ad7de1071da0e5d7113b3e7bd471e2
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Solve Media, Inc.
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
# SolveMedia
|
2
|
+
|
3
|
+
Solve Media's patent-pending technology turns CAPTCHA into branded
|
4
|
+
TYPE-IN™ Ads. Solve Media's technology improves site security, and every
|
5
|
+
time a user types a brand message into a TYPE-IN™ Ad, we share the
|
6
|
+
revenue with our publisher partners.
|
7
|
+
|
8
|
+
The Solve Media gem makes it easy to use the Solve Media unit in your Ruby and
|
9
|
+
Rails projects.
|
10
|
+
|
11
|
+
## Note
|
12
|
+
|
13
|
+
This gem supercedes and replaces the earlier gem from Solve Media. This new gem
|
14
|
+
provides improved compatibility with Rails 3 and is now also usable in pure Ruby
|
15
|
+
as well. If you previously used the old gem with Rails 3, you should uninstall
|
16
|
+
it, install this one, and configure the keys as below. Your view and controller
|
17
|
+
code should continue to work without alteration.
|
18
|
+
|
19
|
+
This gem is not directly backward compatible with Rails 2. Rails 2 users should
|
20
|
+
continue to use the old gem.
|
21
|
+
|
22
|
+
## Installation
|
23
|
+
|
24
|
+
Add this line to your application's Gemfile:
|
25
|
+
|
26
|
+
gem 'solvemedia'
|
27
|
+
|
28
|
+
And then execute:
|
29
|
+
|
30
|
+
$ bundle
|
31
|
+
|
32
|
+
Or install it yourself as:
|
33
|
+
|
34
|
+
$ gem install solvemedia
|
35
|
+
|
36
|
+
## Usage
|
37
|
+
|
38
|
+
### Setting API Keys
|
39
|
+
Before using Solve Media, you need to
|
40
|
+
[sign up for an account](https://portal.solvemedia.com/portal/public/signup)
|
41
|
+
and get a set of API keys.
|
42
|
+
|
43
|
+
To use within a Rails 3 project, you must set these keys within the app config.
|
44
|
+
Inside config/application.rb:
|
45
|
+
|
46
|
+
config.solvemedia.ckey = "Your Challenge (Public) Key"
|
47
|
+
config.solvemedia.vkey = "Your Verification (Private) Key"
|
48
|
+
config.solvemedia.hkey = "Your Authentication Hash Key"
|
49
|
+
|
50
|
+
In addition, you can set these keys on a per-environment basis by using the
|
51
|
+
environment configuration files under config/environment/. For instance, you
|
52
|
+
may wish to create a second set of keys with the security mode
|
53
|
+
set to "Security" instead of "Revenue", to avoid receiving ads during
|
54
|
+
development.
|
55
|
+
|
56
|
+
### Displaying the Puzzle
|
57
|
+
|
58
|
+
To display the Solve Media puzzle within one of your form views, simply call
|
59
|
+
{SolveMedia::ViewHelpers#solvemedia_puzzle solvemedia_puzzle}.
|
60
|
+
|
61
|
+
<% form_for(@user) do |f| %>
|
62
|
+
#...
|
63
|
+
<p>
|
64
|
+
<%= solvemedia_puzzle %>
|
65
|
+
</p>
|
66
|
+
#...
|
67
|
+
<% end %>
|
68
|
+
|
69
|
+
### Verifying the Response
|
70
|
+
|
71
|
+
The {SolveMedia::ControllerMethods#verify_solvemedia_puzzle verify_solvemedia_puzzle} method verifies the user's input, returning
|
72
|
+
`true` if the user solved the puzzle correctly.
|
73
|
+
|
74
|
+
respond_to do |format|
|
75
|
+
if verify_solvemedia_puzzle && @user.save
|
76
|
+
#...
|
77
|
+
else
|
78
|
+
#...
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
`verify_solvemedia_puzzle` can also be used to add an error to a model object
|
83
|
+
if the verification fails:
|
84
|
+
|
85
|
+
respond_to do |format|
|
86
|
+
if verify_solvemedia_puzzle(:model => @user, :error_message => 'Solve Media puzzle input is invalid') && @user.save
|
87
|
+
#...
|
88
|
+
else
|
89
|
+
#...
|
90
|
+
end
|
91
|
+
end
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module SolveMedia
|
2
|
+
module ControllerMethods
|
3
|
+
# Controller method to verify a Solve Media puzzle. Assumes a form with
|
4
|
+
# the puzzle is being processed by the calling method.
|
5
|
+
#
|
6
|
+
# Calls {SolveMedia.verify} internally.
|
7
|
+
#
|
8
|
+
# @param [Hash] options
|
9
|
+
#
|
10
|
+
# @option options [Boolean] :validate_response (true) Validate the
|
11
|
+
# response from the Solve Media server
|
12
|
+
# @option options [Integer] :timeout (5) Time in seconds to wait for a
|
13
|
+
# response form the Solve Media server
|
14
|
+
# @option options [Object<ActiveRecord::Base>] :model ActiveRecord model
|
15
|
+
# object to which error is added
|
16
|
+
# @option options [String] :error_message Custom error message to add to
|
17
|
+
# the model. Does nothing if +:model+ is not present
|
18
|
+
#
|
19
|
+
# @raise [AdCopyError] if +validate_response+ is true and the response
|
20
|
+
# cannot be verified
|
21
|
+
# @raise [Timeout::Error] if the request to the verification server takes
|
22
|
+
# longer than expected
|
23
|
+
#
|
24
|
+
# @return [Boolean] Was the user's answer correct?
|
25
|
+
#
|
26
|
+
def verify_solvemedia_puzzle(options={})
|
27
|
+
ver_options = { :validate_response => options[:validate_response] || true,
|
28
|
+
:timeout => options[:timeout] || 5
|
29
|
+
}
|
30
|
+
verified = SolveMedia.verify(params[:adcopy_challenge], params[:adcopy_response], VKEY, HKEY, request.remote_ip)
|
31
|
+
if options[:model] && !verified
|
32
|
+
options[:model].valid?
|
33
|
+
options[:model].errors.add(:base, options[:error_message] || "Please fill out the Solve Media puzzle")
|
34
|
+
end
|
35
|
+
|
36
|
+
return verified
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "solvemedia/view_helpers"
|
2
|
+
require "solvemedia/controller_methods"
|
3
|
+
module SolveMedia
|
4
|
+
# Integrates the Solve Media library into the Rails project.
|
5
|
+
# Adds {SolveMedia::ViewHelpers} and {SolveMedia::ControllerMethods} to
|
6
|
+
# to the project.
|
7
|
+
class Railtie < Rails::Railtie
|
8
|
+
config.solvemedia = ActiveSupport::OrderedOptions.new
|
9
|
+
|
10
|
+
initializer "solvemedia.configure" do |app|
|
11
|
+
SolveMedia::CKEY = app.config.solvemedia[:ckey]
|
12
|
+
SolveMedia::VKEY = app.config.solvemedia[:vkey]
|
13
|
+
SolveMedia::HKEY = app.config.solvemedia[:hkey]
|
14
|
+
|
15
|
+
unless (SolveMedia::CKEY && SolveMedia::VKEY && SolveMedia::HKEY)
|
16
|
+
raise AdCopyError, "Solve Media API keys not found. Keys can be obtained at #{SIGNUP_URL}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
initializer "solvemedia.view_helpers" do
|
21
|
+
ActionView::Base.send :include, ViewHelpers
|
22
|
+
end
|
23
|
+
|
24
|
+
initializer "solvemedia.controller_methods" do
|
25
|
+
ActionController::Base.send :include, ControllerMethods
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module SolveMedia
|
2
|
+
module ViewHelpers
|
3
|
+
# View helper to insert the Solve Media puzzle HTML.
|
4
|
+
#
|
5
|
+
# Can be set to use either the standard version or the AJAX version. For
|
6
|
+
# more complex uses of the AJAX version, such as multi-puzzle, you should
|
7
|
+
# not use this method and instead use purpose-written Javascript in your
|
8
|
+
# view.
|
9
|
+
#
|
10
|
+
# Calls {SolveMedia.puzzle} internally.
|
11
|
+
#
|
12
|
+
# @see https://portal.solvemedia.com/portal/help/pub/themewiz
|
13
|
+
# Documentation for theme, lang, and size
|
14
|
+
# @see https://portal.solvemedia.com/portal/help/pub/ajax
|
15
|
+
# AJAX API documentation
|
16
|
+
# @see SolveMedia.puzzle
|
17
|
+
#
|
18
|
+
#
|
19
|
+
# @param [Hash] options
|
20
|
+
# @option (see SolveMedia.puzzle)
|
21
|
+
#
|
22
|
+
# @raise (see SolveMedia.puzzle)
|
23
|
+
#
|
24
|
+
# @return [String] HTML for the puzzle, marked as +html_safe+.
|
25
|
+
def solvemedia_puzzle(options={})
|
26
|
+
return SolveMedia.puzzle(CKEY, options).html_safe
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/solvemedia.rb
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
require "solvemedia/version"
|
2
|
+
require "solvemedia/constants"
|
3
|
+
require "solvemedia/ad_copy_error"
|
4
|
+
require "solvemedia/railtie" if defined? ::Rails::Railtie
|
5
|
+
require 'net/http'
|
6
|
+
require 'timeout'
|
7
|
+
|
8
|
+
# Methods for using the Solve Media service. These methods are called internally
|
9
|
+
# by the Rails Railtie; those not using Rails may call these methods directly.
|
10
|
+
module SolveMedia
|
11
|
+
|
12
|
+
# Returns the HTML for the Solve Media puzzle.
|
13
|
+
# Can be set to use either the standard version or the AJAX version. For more
|
14
|
+
# complex uses of the AJAX version, such as multi-puzzle, you should not use
|
15
|
+
# this method and instead use purpose-written Javascript in your view.
|
16
|
+
#
|
17
|
+
# For theme, lang, and size options, see
|
18
|
+
# (https://portal.solvemedia.com/portal/help/pub/themewiz)
|
19
|
+
#
|
20
|
+
# For more about the AJAX puzzle, see
|
21
|
+
# (https://portal.solvemedia.com/portal/help/pub/ajax)
|
22
|
+
#
|
23
|
+
# @param [String] ckey Your challenge (public) key
|
24
|
+
# @param [Hash] options
|
25
|
+
# @option options [Integer] :tabindex (nil) HTML tabindex
|
26
|
+
# @option options [String] :theme ('purple')
|
27
|
+
# @option options [String] :lang ('en')
|
28
|
+
# @option options [String] :size ('300x150') Widget size. Please note that
|
29
|
+
# 300x150 is the only size which can display ads.
|
30
|
+
# @option options [Boolean] :use_SSL (false) Set to +true+ if using the puzzle
|
31
|
+
# on an HTTPS site
|
32
|
+
# @option options [Boolean] :ajax (false) Uses the AJAX api (see above)
|
33
|
+
# @option options [String] :ajax_div ID of the div element into which the
|
34
|
+
# puzzle is inserted, if using AJAX. Required if +ajax+ is set to +true+.
|
35
|
+
#
|
36
|
+
# @raise [AdCopyError] if key is not set
|
37
|
+
# @raise [AdCopyError] if AJAX puzzle is selected but no container div is
|
38
|
+
# specified
|
39
|
+
#
|
40
|
+
# @return [String] HTML string containing code to display the puzzle
|
41
|
+
#
|
42
|
+
def self.puzzle(ckey, options = {})
|
43
|
+
raise AdCopyError, "Solve Media API keys not found. Keys can be obtained at #{SIGNUP_URL}" unless ckey
|
44
|
+
|
45
|
+
options = { :tabindex => nil,
|
46
|
+
:theme => 'purple',
|
47
|
+
:lang => 'en',
|
48
|
+
:size => '300x150',
|
49
|
+
:use_SSL => false,
|
50
|
+
:ajax => false
|
51
|
+
}.merge(options)
|
52
|
+
|
53
|
+
server = options[:use_SSL] ? SolveMedia::API_SECURE_SERVER : SolveMedia::API_SERVER
|
54
|
+
|
55
|
+
if options[:ajax]
|
56
|
+
puts options.inspect
|
57
|
+
raise AdCopyError, "No div specified for AJAX puzzle" unless options[:ajax_div]
|
58
|
+
aopts = {:theme => options[:theme], :lang => options[:lang], :size => options[:size]}
|
59
|
+
aopts[:tabindex] = options[:tabindex] if options[:tabindex]
|
60
|
+
|
61
|
+
output = <<-EOF
|
62
|
+
<script src="#{server}/papi/challenge.ajax"></script>
|
63
|
+
<script type="text/javascript">
|
64
|
+
function loadSolveMediaCaptcha(){
|
65
|
+
if(window.ACPuzzle) {
|
66
|
+
ACPuzzle.create('#{ckey}', '#{options[:ajax_div]}', {#{aopts.map{|k,v| "#{k}:'#{v}'" }.join(', ') }});
|
67
|
+
} else {
|
68
|
+
setTimeout(loadSolveMediaCaptcha, 50);
|
69
|
+
}
|
70
|
+
}
|
71
|
+
loadSolveMediaCaptcha();
|
72
|
+
</script>
|
73
|
+
EOF
|
74
|
+
else
|
75
|
+
output = []
|
76
|
+
|
77
|
+
output << %{<script type="text/javascript">}
|
78
|
+
output << " var ACPuzzleOptions = {"
|
79
|
+
output << %{ tabindex: #{options[:tabindex]},} unless options[:tabindex].nil?
|
80
|
+
output << %{ theme: '#{options[:theme]}',}
|
81
|
+
output << %{ lang: '#{options[:lang]}',}
|
82
|
+
output << %{ size: '#{options[:size]}'}
|
83
|
+
output << " };"
|
84
|
+
output << %{</script>}
|
85
|
+
|
86
|
+
output << %{<script type="text/javascript"}
|
87
|
+
output << %{ src="#{server}/papi/challenge.script?k=#{ckey}">}
|
88
|
+
output << %{</script>}
|
89
|
+
|
90
|
+
output << %{<noscript>}
|
91
|
+
output << %{ <iframe src="#{server}/papi/challenge.noscript?k=#{ckey}"}
|
92
|
+
output << %{ height="300" width="500" frameborder="0"></iframe><br/>}
|
93
|
+
output << %{ <textarea name="adcopy_challenge" rows="3" cols="40">}
|
94
|
+
output << %{ </textarea>}
|
95
|
+
output << %{ <input type="hidden" name="adcopy_response"}
|
96
|
+
output << %{ value="manual_challenge"/>}
|
97
|
+
output << %{</noscript>}
|
98
|
+
output = output.join("\n")
|
99
|
+
end
|
100
|
+
return output
|
101
|
+
end
|
102
|
+
|
103
|
+
# Sends a POST request to the Solve Media server in order to verify the user's input.
|
104
|
+
#
|
105
|
+
# @param [String] challenge The challenge id. Normally found in the form
|
106
|
+
# field +adcopy_challenge+
|
107
|
+
# @param [String] response The user's response to the puzzle. Normally found
|
108
|
+
# in the form field +acdopy_response+
|
109
|
+
# @param [String] vkey Your verification (private) key
|
110
|
+
# @param [String] hkey Your hash key
|
111
|
+
# @param [String] remote_ip The IP from which the form was submitted
|
112
|
+
# @param [Hash] options
|
113
|
+
#
|
114
|
+
# @option options [Boolean] :validate_response (true) Validate the response
|
115
|
+
# from the Solve Media server
|
116
|
+
# @option options [Integer] :timeout (5) Amount of time in seconds before the
|
117
|
+
# request should time out
|
118
|
+
#
|
119
|
+
# @return [Boolean] Was the user's answer correct?
|
120
|
+
#
|
121
|
+
# @raise [AdCopyError] if +validate_response+ is true and the response
|
122
|
+
# cannot be verified
|
123
|
+
# @raise [Timeout::Error] if the request to the verification server takes
|
124
|
+
# longer than expected
|
125
|
+
#
|
126
|
+
def self.verify(challenge, response, vkey, hkey, remote_ip, options = {})
|
127
|
+
options = { :validate_response => true,
|
128
|
+
:timeout => 5,
|
129
|
+
}.merge(options)
|
130
|
+
|
131
|
+
#Send POST to SolveMedia
|
132
|
+
result = nil
|
133
|
+
Timeout::timeout(options[:timeout]) do
|
134
|
+
result = Net::HTTP.post_form URI.parse("#{SolveMedia::VERIFY_SERVER}/papi/verify"), {
|
135
|
+
"privatekey" => vkey,
|
136
|
+
"challenge" => challenge,
|
137
|
+
"response" => response,
|
138
|
+
"remoteip" => remote_ip
|
139
|
+
}
|
140
|
+
end
|
141
|
+
|
142
|
+
answer, error, authenticator = result.body.split("\n")
|
143
|
+
|
144
|
+
#validate the response
|
145
|
+
if options[:validate_response] && authenticator != Digest::SHA1.hexdigest("#{answer}#{challenge}#{hkey}")
|
146
|
+
raise AdCopyError, "SolveMedia Error: Unable to Validate Response"
|
147
|
+
end
|
148
|
+
|
149
|
+
return answer.downcase == "true" ? true : false
|
150
|
+
end
|
151
|
+
end
|
data/solvemedia.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'solvemedia/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "solvemedia"
|
8
|
+
gem.version = SolveMedia::VERSION
|
9
|
+
gem.authors = ["Tyler Cunnion"]
|
10
|
+
gem.email = ["tyler@solvemedia.com"]
|
11
|
+
gem.description = %q{Solve Media CAPTCHA Replacement}
|
12
|
+
gem.summary = %q{Library for implementing the Solve Media CAPTCHA solution.
|
13
|
+
Contains basic Ruby library plus Railtie for Rails 3+.}
|
14
|
+
gem.homepage = "http://www.solvemedia.com/"
|
15
|
+
|
16
|
+
gem.files = `git ls-files`.split($/)
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
|
+
gem.require_paths = ["lib"]
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: solvemedia
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tyler Cunnion
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-11-26 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Solve Media CAPTCHA Replacement
|
14
|
+
email:
|
15
|
+
- tyler@solvemedia.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- .gitignore
|
21
|
+
- Gemfile
|
22
|
+
- LICENSE.txt
|
23
|
+
- README.md
|
24
|
+
- Rakefile
|
25
|
+
- lib/solvemedia.rb
|
26
|
+
- lib/solvemedia/ad_copy_error.rb
|
27
|
+
- lib/solvemedia/constants.rb
|
28
|
+
- lib/solvemedia/controller_methods.rb
|
29
|
+
- lib/solvemedia/railtie.rb
|
30
|
+
- lib/solvemedia/version.rb
|
31
|
+
- lib/solvemedia/view_helpers.rb
|
32
|
+
- solvemedia.gemspec
|
33
|
+
homepage: http://www.solvemedia.com/
|
34
|
+
licenses: []
|
35
|
+
metadata: {}
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options: []
|
38
|
+
require_paths:
|
39
|
+
- lib
|
40
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - '>='
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
requirements: []
|
51
|
+
rubyforge_project:
|
52
|
+
rubygems_version: 2.1.11
|
53
|
+
signing_key:
|
54
|
+
specification_version: 4
|
55
|
+
summary: Library for implementing the Solve Media CAPTCHA solution. Contains basic
|
56
|
+
Ruby library plus Railtie for Rails 3+.
|
57
|
+
test_files: []
|