deathbycaptcha 4.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.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in deathbycaptcha.gemspec
4
+ gemspec
@@ -0,0 +1,20 @@
1
+ Copyright (C) 2011 by Infosimples. http://www.infosimples.com.br/
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:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
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.
@@ -0,0 +1,88 @@
1
+ == DeathByCaptcha
2
+
3
+ DeathByCaptcha is a Ruby API for acessing the http://www.deathbycaptcha.com services.
4
+
5
+ It supports HTTP and socket-based connections, with the latter being recommended for having faster responses and overall better performance.
6
+
7
+ When using socket connections, make sure that outgoing TCP traffic to <b>api.deathbycaptcha.com</b> to the ports range in <b>8123-8130</b> is not blocked by your firewall.
8
+
9
+ === Thread-safety note
10
+
11
+ The API is thread-safe, which means it is perfectly fine to share a client instance between multiple threads.
12
+
13
+ === Installation
14
+
15
+ You can install the latest DeathByCaptcha gem with:
16
+
17
+ gem install deathbycaptcha
18
+
19
+ You can add it to your Gemfile:
20
+
21
+ gem 'deathbycaptcha'
22
+
23
+ === Examples
24
+
25
+ ==== Create a client
26
+
27
+ ===== HTTP client
28
+
29
+ require 'deathbycaptcha'
30
+
31
+ client = DeathByCaptcha.http_client('myusername', 'mypassword')
32
+
33
+ ===== Socket client
34
+
35
+ require 'deathbycaptcha'
36
+
37
+ client = DeathByCaptcha.socket_client('myusername', 'mypassword')
38
+
39
+ ==== Verbose mode (for debugging purposes)
40
+
41
+ client.is_verbose = true
42
+
43
+ ==== Decoding captcha
44
+
45
+ ===== From URL
46
+
47
+ response = client.decode 'http://www.phpcaptcha.org/securimage/securimage_show.php'
48
+
49
+ puts "captcha id: #{response['captcha']}, solution: #{response['text']}, is_correct: #{response['is_correct']}}"
50
+
51
+ ===== From file path
52
+
53
+ response = client.decode 'path/to/my/captcha/file'
54
+
55
+ puts "captcha id: #{response['captcha']}, solution: #{response['text']}, is_correct: #{response['is_correct']}}"
56
+
57
+ ===== From a file
58
+
59
+ file = File.open('path/to/my/captcha/file', 'r')
60
+
61
+ response = client.decode file
62
+
63
+ puts "captcha id: #{response['captcha']}, solution: #{response['text']}, is_correct: #{response['is_correct']}}"
64
+
65
+ ===== From raw content
66
+
67
+ raw_content = File.open('path/to/my/captcha/file', 'r').read
68
+
69
+ response = client.decode(raw_content, :raw_content => true)
70
+
71
+ puts "captcha id: #{response['captcha']}, solution: #{response['text']}, is_correct: #{response['is_correct']}}"
72
+
73
+ ==== Get the solution of a captcha
74
+
75
+ puts client.get_captcha('130920620')['text'] # where 130920620 is the captcha id
76
+
77
+ ==== Get user account information
78
+
79
+ puts client.get_user
80
+
81
+ === Maintainers
82
+
83
+ * Rafael Barbolo Lopes (http://github.com/barbolo)
84
+ * Rafael Ivan Garcia (http://github.com/rafaelivan)
85
+
86
+ == License
87
+
88
+ MIT License. Copyright (C) 2011 by Infosimples. http://www.infosimples.com.br
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "deathbycaptcha/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.add_dependency 'rest-client', '~> 1.6.1'
7
+ s.add_dependency 'json', '~> 1.4.6'
8
+
9
+ s.name = "deathbycaptcha"
10
+ s.version = DeathByCaptcha::VERSION
11
+ s.platform = Gem::Platform::RUBY
12
+ s.authors = ["Rafael Barbolo Lopes, Rafael Ivan Garcia"]
13
+ s.email = ["tech@infosimples.com.br"]
14
+ s.homepage = "http://github.com/infosimples/deathbycaptcha"
15
+ s.summary = %q{Ruby API for DeathByCaptcha (Captcha Solver as a Service)}
16
+ s.description = %q{Ruby API for DeathByCaptcha (Captcha Solver as a Service)}
17
+
18
+ s.rubyforge_project = "deathbycaptcha"
19
+
20
+ s.files = `git ls-files`.split("\n")
21
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
23
+ s.require_paths = ["lib"]
24
+ end
@@ -0,0 +1,9 @@
1
+ require 'deathbycaptcha/version'
2
+ require 'deathbycaptcha/config'
3
+ require 'deathbycaptcha/error'
4
+ require 'deathbycaptcha/client'
5
+ require 'deathbycaptcha/http_client'
6
+ require 'deathbycaptcha/socket_client'
7
+
8
+ module DeathByCaptcha
9
+ end
@@ -0,0 +1,197 @@
1
+ require 'logger'
2
+ require 'digest/sha1'
3
+ require 'rest_client'
4
+
5
+ module DeathByCaptcha
6
+
7
+ #
8
+ # DeathByCaptcha API Client
9
+ #
10
+ class Client
11
+
12
+ attr_accessor :config
13
+
14
+ def initialize(username, password, extra={})
15
+ data = {
16
+ :is_verbose => false, # If true, prints messages during execution
17
+ :logger_output => STDOUT, # Logger output path or IO instance
18
+ :api_version => API_VERSION, # API version (used as user-agent with http requests)
19
+ :software_vendor_id => 0, # API unique software ID
20
+ :max_captcha_file_size => 64 * 1024, # Maximum CAPTCHA image filesize, currently 64K
21
+ :default_timeout => 60, # Default CAPTCHA timeout
22
+ :polls_interval => 5, # Default decode polling interval
23
+ :http_base_url => 'http://api.deathbycaptcha.com/api', # Base HTTP API url
24
+ :http_response_type => 'application/json', # Preferred HTTP API server's response content type, do not change
25
+ :socket_host => 'api.deathbycaptcha.com', # Socket API server's host
26
+ :socket_port => (8123..8130).map {|p| p}, # Socket API server's ports range
27
+ :username => username, # DeathByCaptcha username
28
+ :password_hashed => Digest::SHA1.hexdigest(password), # DeathByCaptcha user's password encrypted
29
+ :password => '', # DeathByCaptcha user's password not encrypted
30
+ :password_is_hashed => true # True, if the password is hashed
31
+ }.merge(extra)
32
+
33
+ @config = DeathByCaptcha::Config.new(data) # Config instance
34
+ @logger = Logger.new(@config.logger_output) # Logger
35
+
36
+ end
37
+
38
+ #
39
+ # Fetch the user's details -- balance, rate and banned status
40
+ #
41
+ def get_user
42
+ raise DeathByCaptcha::Errors::NotImplemented
43
+ end
44
+
45
+ #
46
+ # Fetch the user's balance (in US cents)
47
+ #
48
+ def get_balance
49
+ raise DeathByCaptcha::Errors::NotImplemented
50
+ end
51
+
52
+ #
53
+ # Fetch a CAPTCHA details -- its numeric ID, text and correctness
54
+ #
55
+ def get_captcha(cid)
56
+ raise DeathByCaptcha::Errors::NotImplemented
57
+ end
58
+
59
+ #
60
+ # Fetch a CAPTCHA text
61
+ #
62
+ def get_text(cid)
63
+ raise DeathByCaptcha::Errors::NotImplemented
64
+ end
65
+
66
+ #
67
+ # Report a CAPTCHA as incorrectly solved
68
+ #
69
+ def report(cid)
70
+ raise DeathByCaptcha::Errors::NotImplemented
71
+ end
72
+
73
+ #
74
+ # Remove an unsolved CAPTCHA
75
+ #
76
+ def remove(cid)
77
+ raise DeathByCaptcha::Errors::NotImplemented
78
+ end
79
+
80
+ #
81
+ # Upload a CAPTCHA
82
+ #
83
+ # Accepts file names, file objects or urls, and an optional flag telling
84
+ # whether the CAPTCHA is case-sensitive or not. Returns CAPTCHA details
85
+ # on success.
86
+ #
87
+ def upload(captcha, options={})
88
+ raise DeathByCaptcha::Errors::NotImplemented
89
+ end
90
+
91
+ #
92
+ # Try to solve a CAPTCHA.
93
+ #
94
+ # See Client.upload() for arguments details.
95
+ #
96
+ # Uploads a CAPTCHA, polls for its status periodically with arbitrary
97
+ # timeout (in seconds). Removes unsolved CAPTCHAs. Returns CAPTCHA
98
+ # details if (correctly) solved.
99
+ #
100
+ def decode(captcha, options={})
101
+ options = {:timeout => config.default_timeout, :is_case_sensitive => false, :is_raw_content => false}.merge(options)
102
+ deadline = Time.now + options[:timeout]
103
+ c = upload(captcha, options)
104
+ if c
105
+ while deadline > Time.now and (c['text'].nil? or c['text'].empty?)
106
+ sleep(config.polls_interval)
107
+ c = get_captcha(c['captcha'])
108
+ end
109
+
110
+ if c['text']
111
+ if c['is_correct']
112
+ return c
113
+ end
114
+ else
115
+ remove(c['captcha'])
116
+ end
117
+
118
+ end
119
+
120
+
121
+ end
122
+
123
+ #
124
+ # Protected methods.
125
+ #
126
+ protected
127
+
128
+ #
129
+ # Return a hash with the user's credentials
130
+ #
131
+ def userpwd
132
+ if config.password_is_hashed
133
+ return {:username => config.username, :password => config.password_hashed, :is_hashed => '1'}
134
+ else
135
+ return {:username => config.username, :password => config.password}
136
+ end
137
+ end
138
+
139
+ #
140
+ # Private methods.
141
+ #
142
+ private
143
+
144
+ #
145
+ # Log a command and a message
146
+ #
147
+ def log(cmd, msg='')
148
+ if @config.is_verbose
149
+ @logger.info "#{cmd}: #{msg}"
150
+ end
151
+ end
152
+
153
+ #
154
+ # Return the File instance that corresponds to the captcha
155
+ #
156
+ # captcha can be:
157
+ # => a raw file content if is_raw_content is true
158
+ # => a File if its kind of File
159
+ # => a url if it's a String and starts with 'http://'
160
+ # => a filesystem path otherwise
161
+ #
162
+ def load_file(captcha, is_raw_content=false)
163
+ file = nil
164
+ if is_raw_content
165
+ # Create a temporary file, write the raw content and return it
166
+ tmp_file_path = File.join(Dir.tmpdir, "captcha_#{Time.now.to_i}_#{rand}")
167
+ File.open(tmp_file_path, 'w') {|f| f.write captcha}
168
+ file = File.open(tmp_file_path, 'r')
169
+
170
+ elsif captcha.kind_of? File
171
+ # simply return the file
172
+ file = captcha
173
+
174
+ elsif captcha.kind_of? String and captcha[0,7] == 'http://'
175
+ # Create a temporary file, download the file, write it to tempfile and return it
176
+ tmp_file_path = File.join(Dir.tmpdir, "captcha_#{Time.now.to_i}_#{rand}")
177
+ File.open(tmp_file_path, 'w') {|f| f.write RestClient.get(captcha)}
178
+ file = File.open(tmp_file_path, 'r')
179
+
180
+ else
181
+ # Return the File opened
182
+ file = File.open(captcha, 'r')
183
+
184
+ end
185
+
186
+ if file.nil?
187
+ raise DeathByCaptcha::Errors::CaptchaEmpty
188
+ elsif config.max_captcha_file_size <= file.size
189
+ raise DeathByCaptcha::Errors::CaptchaOverflow
190
+ end
191
+
192
+ return file
193
+ end
194
+
195
+ end
196
+
197
+ end
@@ -0,0 +1,42 @@
1
+ module DeathByCaptcha
2
+
3
+ #
4
+ # Config class
5
+ # based on http://mjijackson.com/2010/02/flexible-ruby-config-objects
6
+ #
7
+ class Config
8
+
9
+ def initialize(data={})
10
+ @data = {}
11
+ update!(data)
12
+ end
13
+
14
+ def update!(data)
15
+ data.each do |key, value|
16
+ self[key] = value
17
+ end
18
+ end
19
+
20
+ def [](key)
21
+ @data[key.to_sym]
22
+ end
23
+
24
+ def []=(key, value)
25
+ if value.class == Hash
26
+ @data[key.to_sym] = Config.new(value)
27
+ else
28
+ @data[key.to_sym] = value
29
+ end
30
+ end
31
+
32
+ def method_missing(sym, *args)
33
+ if sym.to_s =~ /(.+)=$/
34
+ self[$1] = args.first
35
+ else
36
+ self[sym]
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -0,0 +1,62 @@
1
+ module DeathByCaptcha
2
+ module Errors
3
+
4
+
5
+ #
6
+ # Custom Error class for rescuing from DeathByCaptcha API errors
7
+ #
8
+ class Error < StandardError
9
+
10
+ def initialize(message)
11
+ super "#{message} (DEATHBYCAPTCHA API ERROR)"
12
+ end
13
+
14
+ end
15
+
16
+ #
17
+ # Raised when a method tries to access a not implemented method
18
+ #
19
+ class NotImplemented < Error
20
+ def initialize
21
+ super 'The requested functionality was not implemented'
22
+ end
23
+ end
24
+
25
+ #
26
+ # Raised when a HTTP call fails
27
+ #
28
+ class CallError < Error
29
+ def initialize
30
+ super 'HTTP call failed'
31
+ end
32
+ end
33
+
34
+ #
35
+ # Raised when the user is not allowed to access the API
36
+ #
37
+ class AccessDenied < Error
38
+ def initialize
39
+ super 'Access denied, please check your credentials and/or balance'
40
+ end
41
+ end
42
+
43
+ #
44
+ # Raised when the captcha file could not be loaded or is empty
45
+ #
46
+ class CaptchaEmpty
47
+ def initialize
48
+ super 'CAPTCHA image is empty or could not be loaded'
49
+ end
50
+ end
51
+
52
+ #
53
+ # Raised when the size of the captcha file is too big
54
+ #
55
+ class CaptchaOverflow
56
+ def initialize
57
+ super 'CAPTCHA image is too big'
58
+ end
59
+ end
60
+
61
+ end
62
+ end
@@ -0,0 +1,93 @@
1
+ require 'rest_client'
2
+ require 'json'
3
+ require 'digest/md5'
4
+
5
+ module DeathByCaptcha
6
+
7
+ #
8
+ # DeathByCaptcha HTTP API client
9
+ #
10
+ class HTTPClient < DeathByCaptcha::Client
11
+
12
+ def get_user
13
+ call('user', userpwd)
14
+ end
15
+
16
+ def get_captcha(cid)
17
+ call("captcha/#{cid}")
18
+ end
19
+
20
+ def report(cid)
21
+ call("captcha/#{cid}/report", userpwd)['is_correct']
22
+ end
23
+
24
+ def remove(cid)
25
+ not call("captcha/#{cid}/remove", userpwd)['captcha']
26
+ end
27
+
28
+ def a(nome, test=false, valor="verdade")
29
+ puts nome,test,valor
30
+ end
31
+
32
+ #
33
+ # Protected methods.
34
+ #
35
+ protected
36
+
37
+ def upload(captcha, options={})
38
+ options = {:is_case_sensitive => false, :is_raw_content => false}.merge(options)
39
+ data = userpwd
40
+ data[:swid] = config.software_vendor_id
41
+ data[:is_case_sensitive] = options[:is_case_sensitive] ? 1 : 0
42
+ data[:captchafile] = load_file(captcha, options[:is_raw_content])
43
+ response = call('captcha', data)
44
+ return response if response['captcha']
45
+ end
46
+
47
+ #
48
+ # Private methods.
49
+ #
50
+ private
51
+
52
+ def call(cmd, payload={}, headers={})
53
+ headers['Accept'] = config.http_response_type if headers['Accept'].nil?
54
+ headers['User-Agent'] = config.api_version if headers['User-Agent'].nil?
55
+
56
+ log('SEND', "#{cmd} #{payload}")
57
+
58
+ begin
59
+ url = "#{config.http_base_url}/#{cmd}"
60
+
61
+ if payload.empty?
62
+ response = RestClient.get(url, headers)
63
+ else
64
+ response = RestClient.post(url, payload, headers)
65
+ end
66
+
67
+ log('RECV', "#{response.size} #{response}")
68
+
69
+ return JSON.load(response)
70
+
71
+ rescue RestClient::Unauthorized => exc
72
+ raise DeathByCaptcha::Errors::AccessDenied
73
+
74
+ rescue RestClient::RequestFailed => exc
75
+ raise DeathByCaptcha::Errors::AccessDenied
76
+
77
+ else
78
+ raise DeathByCaptcha::Errors::CallError
79
+
80
+ end
81
+
82
+ return {}
83
+
84
+ end
85
+
86
+ end
87
+
88
+
89
+ def self.http_client(username, password, extra={})
90
+ DeathByCaptcha::HTTPClient.new(username, password, extra)
91
+ end
92
+
93
+ end
@@ -0,0 +1,249 @@
1
+ require 'json'
2
+ require 'digest/md5'
3
+ require 'thread'
4
+ require 'socket'
5
+ require 'base64'
6
+
7
+ module DeathByCaptcha
8
+
9
+ #
10
+ # DeathByCaptcha Socket API client
11
+ #
12
+ class SocketClient < DeathByCaptcha::Client
13
+
14
+ #
15
+ # Socket API server's host & ports range.
16
+ #
17
+ @@socket_host = 'api.deathbycaptcha.com'
18
+ @@socket_ports = (8123...8131).to_a
19
+
20
+ def initialize(username, password, extra = {})
21
+ @mutex = Mutex.new
22
+ @socket = nil
23
+
24
+ super(username, password, extra)
25
+ end
26
+
27
+ def get_user
28
+ call('user', userpwd)
29
+ end
30
+
31
+ def get_captcha(cid)
32
+ call('captcha', {:captcha => cid})
33
+ end
34
+
35
+ def report(cid)
36
+ call("captcha/#{cid}/report", userpwd)[:is_correct]
37
+
38
+ data = userpwd
39
+ data['captcha'] = cid
40
+ not call('report', data)[:is_correct]
41
+ end
42
+
43
+ def remove(cid)
44
+ not call("captcha/#{cid}/remove", userpwd)[:captcha]
45
+
46
+ data = userpwd
47
+ data['captcha'] = cid
48
+ not call('remove', data)[:captcha]
49
+ end
50
+
51
+ #
52
+ # Protected methods.
53
+ #
54
+ protected
55
+
56
+ def upload(captcha, is_case_sensitive=false, is_raw_content=false)
57
+ data = userpwd
58
+ data[:captcha] = Base64.encode64(load_file(captcha, is_raw_content).read)
59
+
60
+ data[:is_case_sensitive] = is_case_sensitive ? 1 : 0
61
+ response = call('upload', data)
62
+
63
+ response
64
+ end
65
+
66
+ #
67
+ # Private methods.
68
+ #
69
+ private
70
+
71
+ def connect
72
+ unless @socket
73
+ log('CONN')
74
+
75
+ begin
76
+ random_port = @@socket_ports[rand(@@socket_ports.size)]
77
+
78
+ # Creates a new Socket.
79
+ addr = Socket.pack_sockaddr_in(random_port, @@socket_host)
80
+
81
+ @socket = Socket.new(:INET, :STREAM)
82
+ @socket.connect_nonblock(addr)
83
+ rescue Exception => e
84
+ if e.errno == 36 # EINPROGRESS
85
+ # Nothing.
86
+ else
87
+ close # Closes the socket.
88
+ log('CONN', 'Could not connect.')
89
+ log('CONN', e.backtrace.join('\n'))
90
+
91
+ raise e
92
+ end
93
+ end
94
+
95
+ end
96
+
97
+ @socket
98
+ end
99
+
100
+ def close
101
+ if @socket
102
+ log('CLOSE')
103
+
104
+ begin
105
+ @socket.close
106
+ rescue Exception => e
107
+ log('CLOSE', 'Could not close socket.')
108
+ log('CLOSE', e.backtrace.join('\n'))
109
+ ensure
110
+ @socket = nil
111
+ end
112
+ end
113
+ end
114
+
115
+ def send(sock, buf)
116
+ # buf += '\n'
117
+ fds = [sock]
118
+
119
+ deadline = Time.now.to_f + 3 * config.polls_interval
120
+ while deadline > Time.now.to_f and not buf.empty? do
121
+ _, wr, ex = IO.select([], fds, fds, config.polls_interval)
122
+
123
+ if ex and ex.any?
124
+ raise IOError.new('send(): select() excepted')
125
+ elsif wr
126
+ while buf and not buf.empty? do
127
+ begin
128
+ sent = wr.first.send(buf, 0)
129
+ buf = buf[sent, buf.size - sent]
130
+ rescue Exception => e
131
+ if [35, 36].include? e.errno
132
+ break
133
+ else
134
+ raise e
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ unless buf.empty?
142
+ raise IOError.new('send() timed out')
143
+ else
144
+ return self
145
+ end
146
+ end
147
+
148
+ def recv(sock)
149
+ fds = [sock]
150
+ buf = ''
151
+
152
+ deadline = Time.now.to_f() + 3 * config.polls_interval
153
+ while deadline > Time.now.to_f do
154
+ rd, _, ex = IO.select(fds, [], fds, config.polls_interval)
155
+
156
+ if ex and ex.any?
157
+ raise IOError.new('send(): select() excepted')
158
+ elsif rd
159
+ while true do
160
+ begin
161
+ s = rd.first.recv_nonblock(256)
162
+ rescue Exception => e
163
+ if [35, 36].include? e.errno
164
+ break
165
+ else
166
+ raise e
167
+ end
168
+ else
169
+ if not s
170
+ raise IOError.new('recv(): connection lost')
171
+ else
172
+ buf += s
173
+ end
174
+ end
175
+ end
176
+
177
+ break if buf.size > 0
178
+ end
179
+ end
180
+
181
+ return buf[0, buf.size - 1] if buf.size > 0
182
+ raise IOError.new('recv() timed out')
183
+ end
184
+
185
+ def call(cmd, data = {})
186
+ data = {} if data.nil?
187
+ data.merge!({:cmd => cmd, :version => config.api_version})
188
+
189
+ request = data.to_json
190
+ log('SEND', request.to_s)
191
+
192
+ response = nil
193
+
194
+ (0...1).each do
195
+ # Locks other threads.
196
+ # If another thread has already acquired the lock, this thread will be locked.
197
+ @mutex.lock
198
+
199
+ begin
200
+ sock = connect
201
+ send(sock, request)
202
+
203
+ response = recv(sock)
204
+ rescue Exception => e
205
+ log('SEND', e.message)
206
+ log('SEND', e.backtrace.join('\n'))
207
+ close
208
+ else
209
+ # If no exception raised.
210
+ break
211
+ ensure
212
+ @mutex.unlock
213
+ end
214
+
215
+ end
216
+
217
+ if response.nil?
218
+ msg = 'Connection timed out during API request'
219
+ log('SEND', msg)
220
+
221
+ raise Exception.new(msg)
222
+ end
223
+
224
+ log('RECV', response.to_s)
225
+
226
+ begin
227
+ response = JSON.load(response)
228
+ rescue Exception => e
229
+ raise Exception.new('Invalid API response')
230
+ end
231
+
232
+ if 0x00 < response['status'] and 0x10 > response['status']
233
+ raise DeathByCaptcha::Errors::AccessDenied
234
+ elsif 0xff == response['status']
235
+ raise Exception.new('API server error occured')
236
+ else
237
+ return response
238
+ end
239
+
240
+ end
241
+
242
+ end
243
+
244
+
245
+ def self.socket_client(username, password, extra={})
246
+ DeathByCaptcha::SocketClient.new(username, password, extra)
247
+ end
248
+
249
+ end
@@ -0,0 +1,4 @@
1
+ module DeathByCaptcha
2
+ VERSION = "4.0.0"
3
+ API_VERSION = "DBC/Ruby v4.0.0"
4
+ end
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: deathbycaptcha
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 4
7
+ - 0
8
+ - 0
9
+ version: 4.0.0
10
+ platform: ruby
11
+ authors:
12
+ - Rafael Barbolo Lopes, Rafael Ivan Garcia
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-04-07 00:00:00 -03:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rest-client
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 1
30
+ - 6
31
+ - 1
32
+ version: 1.6.1
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: json
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ segments:
44
+ - 1
45
+ - 4
46
+ - 6
47
+ version: 1.4.6
48
+ type: :runtime
49
+ version_requirements: *id002
50
+ description: Ruby API for DeathByCaptcha (Captcha Solver as a Service)
51
+ email:
52
+ - tech@infosimples.com.br
53
+ executables: []
54
+
55
+ extensions: []
56
+
57
+ extra_rdoc_files: []
58
+
59
+ files:
60
+ - .gitignore
61
+ - Gemfile
62
+ - MIT-LICENSE
63
+ - README.rdoc
64
+ - Rakefile
65
+ - deathbycaptcha.gemspec
66
+ - lib/deathbycaptcha.rb
67
+ - lib/deathbycaptcha/client.rb
68
+ - lib/deathbycaptcha/config.rb
69
+ - lib/deathbycaptcha/error.rb
70
+ - lib/deathbycaptcha/http_client.rb
71
+ - lib/deathbycaptcha/socket_client.rb
72
+ - lib/deathbycaptcha/version.rb
73
+ has_rdoc: true
74
+ homepage: http://github.com/infosimples/deathbycaptcha
75
+ licenses: []
76
+
77
+ post_install_message:
78
+ rdoc_options: []
79
+
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ segments:
88
+ - 0
89
+ version: "0"
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ segments:
96
+ - 0
97
+ version: "0"
98
+ requirements: []
99
+
100
+ rubyforge_project: deathbycaptcha
101
+ rubygems_version: 1.3.7
102
+ signing_key:
103
+ specification_version: 3
104
+ summary: Ruby API for DeathByCaptcha (Captcha Solver as a Service)
105
+ test_files: []
106
+