deathbycaptcha 4.1.2 → 4.1.3
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/README.md +9 -14
- data/lib/deathbycaptcha.rb +2 -2
- data/lib/deathbycaptcha/client.rb +54 -56
- data/lib/deathbycaptcha/config.rb +3 -3
- data/lib/deathbycaptcha/{error.rb → errors.rb} +15 -15
- data/lib/deathbycaptcha/http_client.rb +36 -36
- data/lib/deathbycaptcha/socket_client.rb +55 -71
- data/lib/deathbycaptcha/version.rb +3 -3
- metadata +18 -10
data/README.md
CHANGED
|
@@ -12,11 +12,6 @@ Thread-safety note
|
|
|
12
12
|
|
|
13
13
|
The API is thread-safe, which means it is perfectly fine to share a client instance between multiple threads.
|
|
14
14
|
|
|
15
|
-
Latest version
|
|
16
|
-
--------------
|
|
17
|
-
|
|
18
|
-
The latest version of this API is 4.1.1.
|
|
19
|
-
|
|
20
15
|
Installation
|
|
21
16
|
------------
|
|
22
17
|
|
|
@@ -38,23 +33,23 @@ Examples
|
|
|
38
33
|
require 'deathbycaptcha'
|
|
39
34
|
|
|
40
35
|
client = DeathByCaptcha.http_client('myusername', 'mypassword')
|
|
41
|
-
|
|
36
|
+
|
|
42
37
|
#### Socket client
|
|
43
38
|
|
|
44
39
|
require 'deathbycaptcha'
|
|
45
40
|
|
|
46
41
|
client = DeathByCaptcha.socket_client('myusername', 'mypassword')
|
|
47
|
-
|
|
42
|
+
|
|
48
43
|
#### Verbose mode (for debugging purposes)
|
|
49
44
|
|
|
50
45
|
client.config.is_verbose = true
|
|
51
|
-
|
|
46
|
+
|
|
52
47
|
#### Decoding captcha
|
|
53
48
|
|
|
54
49
|
##### From URL
|
|
55
|
-
|
|
50
|
+
|
|
56
51
|
response = client.decode 'http://www.phpcaptcha.org/securimage/securimage_show.php'
|
|
57
|
-
|
|
52
|
+
|
|
58
53
|
puts "captcha id: #{response['captcha']}, solution: #{response['text']}, is_correct: #{response['is_correct']}}"
|
|
59
54
|
|
|
60
55
|
##### From file path
|
|
@@ -62,7 +57,7 @@ Examples
|
|
|
62
57
|
response = client.decode 'path/to/my/captcha/file'
|
|
63
58
|
|
|
64
59
|
puts "captcha id: #{response['captcha']}, solution: #{response['text']}, is_correct: #{response['is_correct']}}"
|
|
65
|
-
|
|
60
|
+
|
|
66
61
|
##### From a file
|
|
67
62
|
|
|
68
63
|
file = File.open('path/to/my/captcha/file', 'r')
|
|
@@ -70,7 +65,7 @@ Examples
|
|
|
70
65
|
response = client.decode file
|
|
71
66
|
|
|
72
67
|
puts "captcha id: #{response['captcha']}, solution: #{response['text']}, is_correct: #{response['is_correct']}}"
|
|
73
|
-
|
|
68
|
+
|
|
74
69
|
##### From raw content
|
|
75
70
|
|
|
76
71
|
raw_content = File.open('path/to/my/captcha/file', 'r').read
|
|
@@ -80,9 +75,9 @@ Examples
|
|
|
80
75
|
puts "captcha id: #{response['captcha']}, solution: #{response['text']}, is_correct: #{response['is_correct']}}"
|
|
81
76
|
|
|
82
77
|
#### Get the solution of a captcha
|
|
83
|
-
|
|
78
|
+
|
|
84
79
|
puts client.get_captcha('130920620')['text'] # where 130920620 is the captcha id
|
|
85
|
-
|
|
80
|
+
|
|
86
81
|
#### Get user account information
|
|
87
82
|
|
|
88
83
|
puts client.get_user
|
data/lib/deathbycaptcha.rb
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
require 'deathbycaptcha/version'
|
|
2
2
|
require 'deathbycaptcha/config'
|
|
3
|
-
require 'deathbycaptcha/
|
|
3
|
+
require 'deathbycaptcha/errors'
|
|
4
4
|
require 'deathbycaptcha/client'
|
|
5
5
|
require 'deathbycaptcha/http_client'
|
|
6
6
|
require 'deathbycaptcha/socket_client'
|
|
7
7
|
|
|
8
|
-
module DeathByCaptcha
|
|
8
|
+
module DeathByCaptcha
|
|
9
9
|
end
|
|
@@ -3,73 +3,72 @@ require 'digest/sha1'
|
|
|
3
3
|
require 'rest_client'
|
|
4
4
|
|
|
5
5
|
module DeathByCaptcha
|
|
6
|
-
|
|
6
|
+
|
|
7
7
|
#
|
|
8
8
|
# DeathByCaptcha API Client
|
|
9
9
|
#
|
|
10
10
|
class Client
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
attr_accessor :config
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
def initialize(username, password, extra = {})
|
|
15
15
|
data = {
|
|
16
|
-
:is_verbose
|
|
17
|
-
:logger_output
|
|
18
|
-
:api_version
|
|
19
|
-
:software_vendor_id
|
|
20
|
-
:max_captcha_file_size
|
|
21
|
-
:default_timeout
|
|
22
|
-
:polls_interval
|
|
23
|
-
:http_base_url
|
|
24
|
-
:http_response_type
|
|
25
|
-
:socket_host
|
|
26
|
-
:
|
|
27
|
-
:username
|
|
28
|
-
:password
|
|
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.dbcapi.me/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.dbcapi.me', # Socket API server's host.
|
|
26
|
+
:socket_ports => (8123..8130).map { |p| p }, # Socket API server's ports range.
|
|
27
|
+
:username => username, # DeathByCaptcha username.
|
|
28
|
+
:password => password, # DeathByCaptcha user's password not encrypted.
|
|
29
29
|
}.merge(extra)
|
|
30
|
-
|
|
31
|
-
@config = DeathByCaptcha::Config.new(data) # Config instance
|
|
32
|
-
@logger = Logger.new(@config.logger_output) # Logger
|
|
33
|
-
|
|
30
|
+
|
|
31
|
+
@config = DeathByCaptcha::Config.new(data) # Config instance.
|
|
32
|
+
@logger = Logger.new(@config.logger_output) # Logger.
|
|
34
33
|
end
|
|
35
|
-
|
|
34
|
+
|
|
36
35
|
#
|
|
37
|
-
# Fetch the user's details -- balance, rate and banned status
|
|
36
|
+
# Fetch the user's details -- balance, rate and banned status.
|
|
38
37
|
#
|
|
39
38
|
def get_user
|
|
40
39
|
raise DeathByCaptcha::Errors::NotImplemented
|
|
41
40
|
end
|
|
42
|
-
|
|
41
|
+
|
|
43
42
|
#
|
|
44
|
-
# Fetch the user's balance (in US cents)
|
|
43
|
+
# Fetch the user's balance (in US cents).
|
|
45
44
|
#
|
|
46
45
|
def get_balance
|
|
47
46
|
raise DeathByCaptcha::Errors::NotImplemented
|
|
48
47
|
end
|
|
49
|
-
|
|
48
|
+
|
|
50
49
|
#
|
|
51
|
-
# Fetch a CAPTCHA details -- its numeric ID, text and correctness
|
|
50
|
+
# Fetch a CAPTCHA details -- its numeric ID, text and correctness.
|
|
52
51
|
#
|
|
53
52
|
def get_captcha(cid)
|
|
54
53
|
raise DeathByCaptcha::Errors::NotImplemented
|
|
55
54
|
end
|
|
56
|
-
|
|
55
|
+
|
|
57
56
|
#
|
|
58
|
-
# Fetch a CAPTCHA text
|
|
57
|
+
# Fetch a CAPTCHA text.
|
|
59
58
|
#
|
|
60
59
|
def get_text(cid)
|
|
61
60
|
raise DeathByCaptcha::Errors::NotImplemented
|
|
62
61
|
end
|
|
63
|
-
|
|
62
|
+
|
|
64
63
|
#
|
|
65
|
-
# Report a CAPTCHA as incorrectly solved
|
|
64
|
+
# Report a CAPTCHA as incorrectly solved.
|
|
66
65
|
#
|
|
67
66
|
def report(cid)
|
|
68
67
|
raise DeathByCaptcha::Errors::NotImplemented
|
|
69
68
|
end
|
|
70
|
-
|
|
69
|
+
|
|
71
70
|
#
|
|
72
|
-
# Upload a CAPTCHA
|
|
71
|
+
# Upload a CAPTCHA.
|
|
73
72
|
#
|
|
74
73
|
# Accepts file names, file objects or urls, and an optional flag telling
|
|
75
74
|
# whether the CAPTCHA is case-sensitive or not. Returns CAPTCHA details
|
|
@@ -78,7 +77,7 @@ module DeathByCaptcha
|
|
|
78
77
|
def upload(captcha, options = {})
|
|
79
78
|
raise DeathByCaptcha::Errors::NotImplemented
|
|
80
79
|
end
|
|
81
|
-
|
|
80
|
+
|
|
82
81
|
#
|
|
83
82
|
# Try to solve a CAPTCHA.
|
|
84
83
|
#
|
|
@@ -90,47 +89,46 @@ module DeathByCaptcha
|
|
|
90
89
|
#
|
|
91
90
|
def decode(captcha, options = {})
|
|
92
91
|
options = {
|
|
93
|
-
:timeout
|
|
94
|
-
:is_case_sensitive
|
|
95
|
-
:is_raw_content
|
|
92
|
+
:timeout => config.default_timeout,
|
|
93
|
+
:is_case_sensitive => false,
|
|
94
|
+
:is_raw_content => false
|
|
96
95
|
}.merge(options)
|
|
97
|
-
|
|
96
|
+
|
|
98
97
|
deadline = Time.now + options[:timeout]
|
|
99
98
|
c = upload(captcha, options)
|
|
100
99
|
if c
|
|
101
|
-
|
|
100
|
+
|
|
102
101
|
while deadline > Time.now and (c['text'].nil? or c['text'].empty?)
|
|
103
102
|
sleep(config.polls_interval)
|
|
104
103
|
c = get_captcha(c['captcha'])
|
|
105
104
|
end
|
|
106
|
-
|
|
105
|
+
|
|
107
106
|
if c['text']
|
|
108
107
|
return c if c['is_correct']
|
|
109
108
|
else
|
|
110
109
|
remove(c['captcha'])
|
|
111
110
|
end
|
|
112
|
-
|
|
111
|
+
|
|
113
112
|
end
|
|
114
|
-
|
|
115
113
|
end
|
|
116
|
-
|
|
114
|
+
|
|
117
115
|
#
|
|
118
116
|
# Protected methods.
|
|
119
117
|
#
|
|
120
118
|
protected
|
|
121
|
-
|
|
119
|
+
|
|
122
120
|
#
|
|
123
121
|
# Return a hash with the user's credentials
|
|
124
122
|
#
|
|
125
123
|
def userpwd
|
|
126
124
|
{ :username => config.username, :password => config.password }
|
|
127
125
|
end
|
|
128
|
-
|
|
126
|
+
|
|
129
127
|
#
|
|
130
128
|
# Private methods.
|
|
131
129
|
#
|
|
132
130
|
private
|
|
133
|
-
|
|
131
|
+
|
|
134
132
|
#
|
|
135
133
|
# Log a command and a message
|
|
136
134
|
#
|
|
@@ -150,41 +148,41 @@ module DeathByCaptcha
|
|
|
150
148
|
# => a filesystem path otherwise
|
|
151
149
|
#
|
|
152
150
|
def load_file(captcha, is_raw_content = false)
|
|
153
|
-
|
|
151
|
+
|
|
154
152
|
file = nil
|
|
155
|
-
|
|
153
|
+
|
|
156
154
|
if is_raw_content
|
|
157
155
|
# Create a temporary file, write the raw content and return it
|
|
158
156
|
tmp_file_path = File.join(Dir.tmpdir, "captcha_#{Time.now.to_i}_#{rand}")
|
|
159
157
|
File.open(tmp_file_path, 'wb') { |f| f.write captcha }
|
|
160
158
|
file = File.open(tmp_file_path, 'r')
|
|
161
|
-
|
|
159
|
+
|
|
162
160
|
elsif captcha.kind_of? File
|
|
163
161
|
# simply return the file
|
|
164
162
|
file = captcha
|
|
165
|
-
|
|
163
|
+
|
|
166
164
|
elsif captcha.kind_of? String and captcha.match(/^https?:\/\//i)
|
|
167
165
|
# Create a temporary file, download the file, write it to tempfile and return it
|
|
168
166
|
tmp_file_path = File.join(Dir.tmpdir, "captcha_#{Time.now.to_i}_#{rand}")
|
|
169
167
|
File.open(tmp_file_path, 'wb') { |f| f.write RestClient.get(captcha) }
|
|
170
168
|
file = File.open(tmp_file_path, 'r')
|
|
171
|
-
|
|
169
|
+
|
|
172
170
|
else
|
|
173
171
|
# Return the File opened
|
|
174
172
|
file = File.open(captcha, 'r')
|
|
175
|
-
|
|
173
|
+
|
|
176
174
|
end
|
|
177
|
-
|
|
175
|
+
|
|
178
176
|
if file.nil?
|
|
179
177
|
raise DeathByCaptcha::Errors::CaptchaEmpty
|
|
180
178
|
elsif config.max_captcha_file_size <= File.size?(file).to_i
|
|
181
179
|
raise DeathByCaptcha::Errors::CaptchaOverflow
|
|
182
180
|
end
|
|
183
|
-
|
|
181
|
+
|
|
184
182
|
file
|
|
185
|
-
|
|
183
|
+
|
|
186
184
|
end
|
|
187
|
-
|
|
185
|
+
|
|
188
186
|
end
|
|
189
|
-
|
|
187
|
+
|
|
190
188
|
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
module DeathByCaptcha
|
|
1
|
+
module DeathByCaptcha
|
|
2
2
|
|
|
3
3
|
#
|
|
4
4
|
# Config class
|
|
@@ -6,7 +6,7 @@ module DeathByCaptcha
|
|
|
6
6
|
#
|
|
7
7
|
class Config
|
|
8
8
|
|
|
9
|
-
def initialize(data={})
|
|
9
|
+
def initialize(data = {})
|
|
10
10
|
@data = {}
|
|
11
11
|
update!(data)
|
|
12
12
|
end
|
|
@@ -38,5 +38,5 @@ module DeathByCaptcha
|
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
end
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
end
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
module DeathByCaptcha
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
module Errors
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
#
|
|
6
|
-
# Custom Error class for rescuing from DeathByCaptcha API errors
|
|
6
|
+
# Custom Error class for rescuing from DeathByCaptcha API errors.
|
|
7
7
|
#
|
|
8
8
|
class Error < StandardError
|
|
9
9
|
|
|
@@ -14,56 +14,56 @@ module DeathByCaptcha
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
#
|
|
17
|
-
# Raised when a method tries to access a not implemented method
|
|
17
|
+
# Raised when a method tries to access a not implemented method.
|
|
18
18
|
#
|
|
19
19
|
class NotImplemented < Error
|
|
20
20
|
def initialize
|
|
21
21
|
super('The requested functionality was not implemented')
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
#
|
|
26
|
-
# Raised when a HTTP call fails
|
|
26
|
+
# Raised when a HTTP call fails.
|
|
27
27
|
#
|
|
28
28
|
class CallError < Error
|
|
29
29
|
def initialize
|
|
30
30
|
super('HTTP call failed')
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
#
|
|
35
|
-
# Raised when the user is not allowed to access the API
|
|
35
|
+
# Raised when the user is not allowed to access the API.
|
|
36
36
|
#
|
|
37
37
|
class AccessDenied < Error
|
|
38
38
|
def initialize
|
|
39
39
|
super('Access denied, please check your credentials and/or balance')
|
|
40
40
|
end
|
|
41
41
|
end
|
|
42
|
-
|
|
42
|
+
|
|
43
43
|
#
|
|
44
|
-
# Raised when the captcha file could not be loaded or is empty
|
|
44
|
+
# Raised when the captcha file could not be loaded or is empty.
|
|
45
45
|
#
|
|
46
46
|
class CaptchaEmpty
|
|
47
47
|
def initialize
|
|
48
48
|
super('CAPTCHA image is empty or could not be loaded')
|
|
49
49
|
end
|
|
50
50
|
end
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
#
|
|
53
|
-
# Raised when the size of the captcha file is too big
|
|
53
|
+
# Raised when the size of the captcha file is too big.
|
|
54
54
|
#
|
|
55
55
|
class CaptchaOverflow
|
|
56
56
|
def initialize
|
|
57
57
|
super('CAPTCHA image is too big')
|
|
58
58
|
end
|
|
59
59
|
end
|
|
60
|
-
|
|
60
|
+
|
|
61
61
|
class ServiceOverload
|
|
62
62
|
def initialize
|
|
63
63
|
super('CAPTCHA was rejected due to service overload, try again later')
|
|
64
64
|
end
|
|
65
65
|
end
|
|
66
|
-
|
|
66
|
+
|
|
67
67
|
end
|
|
68
|
-
|
|
68
|
+
|
|
69
69
|
end
|
|
@@ -3,16 +3,16 @@ require 'json'
|
|
|
3
3
|
require 'digest/md5'
|
|
4
4
|
|
|
5
5
|
module DeathByCaptcha
|
|
6
|
-
|
|
6
|
+
|
|
7
7
|
#
|
|
8
8
|
# DeathByCaptcha HTTP API client
|
|
9
9
|
#
|
|
10
10
|
class HTTPClient < DeathByCaptcha::Client
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
def get_user
|
|
13
13
|
call('user', userpwd)
|
|
14
14
|
end
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
def get_captcha(cid)
|
|
17
17
|
call("captcha/#{cid}")
|
|
18
18
|
end
|
|
@@ -20,79 +20,79 @@ module DeathByCaptcha
|
|
|
20
20
|
def report(cid)
|
|
21
21
|
call("captcha/#{cid}/report", userpwd)['is_correct']
|
|
22
22
|
end
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
def remove(cid)
|
|
25
25
|
not call("captcha/#{cid}/remove", userpwd)['captcha']
|
|
26
26
|
end
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
#
|
|
29
29
|
# Protected methods.
|
|
30
30
|
#
|
|
31
31
|
protected
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
def upload(captcha, options = {})
|
|
34
34
|
options = {
|
|
35
|
-
:is_case_sensitive
|
|
36
|
-
:is_raw_content
|
|
35
|
+
:is_case_sensitive => false,
|
|
36
|
+
:is_raw_content => false
|
|
37
37
|
}.merge(options)
|
|
38
|
-
|
|
39
|
-
data
|
|
40
|
-
data[:swid]
|
|
41
|
-
data[:is_case_sensitive]
|
|
42
|
-
data[:captchafile]
|
|
43
|
-
response
|
|
44
|
-
|
|
38
|
+
|
|
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
|
+
|
|
45
45
|
return response if response['captcha']
|
|
46
46
|
end
|
|
47
|
-
|
|
47
|
+
|
|
48
48
|
#
|
|
49
49
|
# Private methods.
|
|
50
50
|
#
|
|
51
51
|
private
|
|
52
|
-
|
|
52
|
+
|
|
53
53
|
def call(cmd, payload = {}, headers = {})
|
|
54
|
-
|
|
55
|
-
headers
|
|
56
|
-
headers['Accept']
|
|
54
|
+
|
|
55
|
+
headers = {} unless headers.is_a?(Hash)
|
|
56
|
+
headers['Accept'] = config.http_response_type if headers['Accept'].nil?
|
|
57
57
|
headers['User-Agent'] = config.api_version if headers['User-Agent'].nil?
|
|
58
|
-
|
|
58
|
+
|
|
59
59
|
log('SEND', "#{cmd} #{payload}")
|
|
60
|
-
|
|
60
|
+
|
|
61
61
|
begin
|
|
62
62
|
url = "#{config.http_base_url}/#{cmd}"
|
|
63
|
-
|
|
63
|
+
|
|
64
64
|
if payload.empty?
|
|
65
65
|
response = RestClient.get(url, headers)
|
|
66
66
|
else
|
|
67
67
|
response = RestClient.post(url, payload, headers)
|
|
68
68
|
end
|
|
69
|
-
|
|
69
|
+
|
|
70
70
|
log('RECV', "#{response.size} #{response}")
|
|
71
|
-
|
|
71
|
+
|
|
72
72
|
return JSON.load(response)
|
|
73
|
-
|
|
73
|
+
|
|
74
74
|
rescue RestClient::Unauthorized => exc
|
|
75
75
|
raise DeathByCaptcha::Errors::AccessDenied
|
|
76
|
-
|
|
76
|
+
|
|
77
77
|
rescue RestClient::RequestFailed => exc
|
|
78
|
-
raise DeathByCaptcha::Errors::
|
|
79
|
-
|
|
78
|
+
raise DeathByCaptcha::Errors::CallError
|
|
79
|
+
|
|
80
80
|
rescue RestClient::ServiceUnavailable => exc
|
|
81
81
|
raise DeathByCaptcha::Errors::ServiceOverload
|
|
82
|
-
|
|
82
|
+
|
|
83
83
|
else
|
|
84
84
|
raise DeathByCaptcha::Errors::CallError
|
|
85
|
-
|
|
85
|
+
|
|
86
86
|
end
|
|
87
|
-
|
|
87
|
+
|
|
88
88
|
return {}
|
|
89
|
-
|
|
89
|
+
|
|
90
90
|
end
|
|
91
|
-
|
|
91
|
+
|
|
92
92
|
end
|
|
93
|
-
|
|
93
|
+
|
|
94
94
|
def self.http_client(username, password, extra = {})
|
|
95
95
|
DeathByCaptcha::HTTPClient.new(username, password, extra)
|
|
96
96
|
end
|
|
97
|
-
|
|
97
|
+
|
|
98
98
|
end
|
|
@@ -5,109 +5,93 @@ require 'socket'
|
|
|
5
5
|
require 'base64'
|
|
6
6
|
|
|
7
7
|
module DeathByCaptcha
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
#
|
|
10
10
|
# DeathByCaptcha Socket API client
|
|
11
11
|
#
|
|
12
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
|
-
|
|
13
|
+
|
|
20
14
|
def initialize(username, password, extra = {})
|
|
21
|
-
@mutex
|
|
15
|
+
@mutex = Mutex.new
|
|
22
16
|
@socket = nil
|
|
23
|
-
|
|
17
|
+
|
|
24
18
|
super(username, password, extra)
|
|
25
19
|
end
|
|
26
|
-
|
|
20
|
+
|
|
27
21
|
def get_user
|
|
28
22
|
call('user')
|
|
29
23
|
end
|
|
30
|
-
|
|
24
|
+
|
|
31
25
|
def get_captcha(cid)
|
|
32
|
-
call('captcha', {:captcha => cid})
|
|
26
|
+
call('captcha', { :captcha => cid })
|
|
33
27
|
end
|
|
34
28
|
|
|
35
29
|
def report(cid)
|
|
36
|
-
|
|
37
|
-
call('report', data)[:is_correct]
|
|
30
|
+
call('report', { :captcha => cid })[:is_correct]
|
|
38
31
|
end
|
|
39
|
-
|
|
32
|
+
|
|
40
33
|
#
|
|
41
34
|
# Protected methods.
|
|
42
35
|
#
|
|
43
36
|
protected
|
|
44
|
-
|
|
37
|
+
|
|
45
38
|
def upload(captcha, is_case_sensitive = false, is_raw_content = false)
|
|
46
39
|
data = {
|
|
47
|
-
:captcha
|
|
48
|
-
:is_case_sensitive
|
|
40
|
+
:captcha => Base64.encode64(load_file(captcha, is_raw_content).read),
|
|
41
|
+
:is_case_sensitive => (is_case_sensitive ? 1 : 0)
|
|
49
42
|
}
|
|
50
|
-
|
|
43
|
+
|
|
51
44
|
call('upload', data)
|
|
52
45
|
end
|
|
53
|
-
|
|
46
|
+
|
|
54
47
|
#
|
|
55
48
|
# Private methods.
|
|
56
49
|
#
|
|
57
50
|
private
|
|
58
|
-
|
|
51
|
+
|
|
59
52
|
def connect
|
|
60
53
|
unless @socket
|
|
61
54
|
log('CONN')
|
|
62
|
-
|
|
63
55
|
begin
|
|
64
|
-
random_port =
|
|
65
|
-
|
|
56
|
+
random_port = config.socket_ports[rand(config.socket_ports.size)]
|
|
66
57
|
# Creates a new Socket.
|
|
67
|
-
addr = Socket.pack_sockaddr_in(random_port,
|
|
68
|
-
|
|
69
|
-
@socket = Socket.new(
|
|
58
|
+
addr = Socket.pack_sockaddr_in(random_port, config.socket_host)
|
|
59
|
+
|
|
60
|
+
@socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
|
|
70
61
|
@socket.connect_nonblock(addr)
|
|
62
|
+
rescue Errno::EINPROGRESS
|
|
63
|
+
log("INPROG", 'Waiting...')
|
|
71
64
|
rescue Exception => e
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
log('CONN', 'Could not connect.')
|
|
77
|
-
log('CONN', e.backtrace.join('\n'))
|
|
78
|
-
|
|
79
|
-
raise e
|
|
80
|
-
end
|
|
65
|
+
close # Closes the socket.
|
|
66
|
+
log('CONN', 'Could not connect.')
|
|
67
|
+
log('CONN', e.backtrace.join("\n"))
|
|
68
|
+
raise e
|
|
81
69
|
end
|
|
82
|
-
|
|
83
70
|
end
|
|
84
|
-
|
|
85
71
|
@socket
|
|
86
72
|
end
|
|
87
|
-
|
|
73
|
+
|
|
88
74
|
def close
|
|
89
75
|
if @socket
|
|
90
76
|
log('CLOSE')
|
|
91
|
-
|
|
92
77
|
begin
|
|
93
78
|
@socket.close
|
|
94
79
|
rescue Exception => e
|
|
95
80
|
log('CLOSE', 'Could not close socket.')
|
|
96
|
-
log('CLOSE', e.backtrace.join(
|
|
81
|
+
log('CLOSE', e.backtrace.join("\n"))
|
|
97
82
|
ensure
|
|
98
83
|
@socket = nil
|
|
99
84
|
end
|
|
100
85
|
end
|
|
101
86
|
end
|
|
102
|
-
|
|
87
|
+
|
|
103
88
|
def send(sock, buf)
|
|
104
89
|
# buf += '\n'
|
|
105
90
|
fds = [sock]
|
|
106
|
-
|
|
107
91
|
deadline = Time.now.to_f + 3 * config.polls_interval
|
|
108
92
|
while deadline > Time.now.to_f and not buf.empty? do
|
|
109
93
|
_, wr, ex = IO.select([], fds, fds, config.polls_interval)
|
|
110
|
-
|
|
94
|
+
|
|
111
95
|
if ex and ex.any?
|
|
112
96
|
raise IOError.new('send(): select() excepted')
|
|
113
97
|
elsif wr
|
|
@@ -116,7 +100,7 @@ module DeathByCaptcha
|
|
|
116
100
|
sent = wr.first.send(buf, 0)
|
|
117
101
|
buf = buf[sent, buf.size - sent]
|
|
118
102
|
rescue Exception => e
|
|
119
|
-
if [35, 36].include? e.errno
|
|
103
|
+
if [35, 36].include? e.errno
|
|
120
104
|
break
|
|
121
105
|
else
|
|
122
106
|
raise e
|
|
@@ -125,28 +109,29 @@ module DeathByCaptcha
|
|
|
125
109
|
end
|
|
126
110
|
end
|
|
127
111
|
end
|
|
128
|
-
|
|
112
|
+
|
|
129
113
|
unless buf.empty?
|
|
130
114
|
raise IOError.new('send() timed out')
|
|
131
115
|
else
|
|
132
116
|
return self
|
|
133
117
|
end
|
|
134
118
|
end
|
|
135
|
-
|
|
119
|
+
|
|
136
120
|
def recv(sock)
|
|
137
121
|
fds = [sock]
|
|
138
122
|
buf = ''
|
|
139
|
-
|
|
123
|
+
|
|
140
124
|
deadline = Time.now.to_f() + 3 * config.polls_interval
|
|
141
125
|
while deadline > Time.now.to_f do
|
|
142
126
|
rd, _, ex = IO.select(fds, [], fds, config.polls_interval)
|
|
143
|
-
|
|
144
127
|
if ex and ex.any?
|
|
145
128
|
raise IOError.new('send(): select() excepted')
|
|
146
129
|
elsif rd
|
|
147
130
|
while true do
|
|
148
131
|
begin
|
|
149
132
|
s = rd.first.recv_nonblock(256)
|
|
133
|
+
rescue Errno::EAGAIN
|
|
134
|
+
break
|
|
150
135
|
rescue Exception => e
|
|
151
136
|
if [35, 36].include? e.errno
|
|
152
137
|
break
|
|
@@ -161,41 +146,40 @@ module DeathByCaptcha
|
|
|
161
146
|
end
|
|
162
147
|
end
|
|
163
148
|
end
|
|
164
|
-
|
|
165
149
|
break if buf.size > 0
|
|
166
150
|
end
|
|
167
151
|
end
|
|
168
|
-
|
|
152
|
+
|
|
169
153
|
return buf[0, buf.size] if buf.size > 0
|
|
170
154
|
raise IOError.new('recv() timed out')
|
|
171
155
|
end
|
|
172
|
-
|
|
156
|
+
|
|
173
157
|
def call(cmd, data = {})
|
|
174
158
|
data = {} if data.nil?
|
|
175
159
|
data.merge!({ :cmd => cmd, :version => config.api_version })
|
|
176
|
-
|
|
160
|
+
|
|
177
161
|
request = data.to_json
|
|
178
162
|
log('SEND', request.to_s)
|
|
179
|
-
|
|
163
|
+
|
|
180
164
|
response = nil
|
|
181
|
-
|
|
165
|
+
|
|
182
166
|
(0...1).each do
|
|
183
167
|
if not @socket and cmd != 'login'
|
|
184
168
|
call('login', userpwd)
|
|
185
169
|
end
|
|
186
|
-
|
|
170
|
+
|
|
187
171
|
# Locks other threads.
|
|
188
172
|
# If another thread has already acquired the lock, this thread will be locked.
|
|
189
173
|
@mutex.lock
|
|
190
|
-
|
|
174
|
+
|
|
191
175
|
begin
|
|
192
176
|
sock = connect
|
|
193
177
|
send(sock, request)
|
|
194
|
-
|
|
178
|
+
|
|
195
179
|
response = recv(sock)
|
|
196
180
|
rescue Exception => e
|
|
197
181
|
log('SEND', e.message)
|
|
198
|
-
log('SEND', e.backtrace.join(
|
|
182
|
+
log('SEND', e.backtrace.join("\n"))
|
|
199
183
|
close
|
|
200
184
|
else
|
|
201
185
|
# If no exception raised.
|
|
@@ -203,24 +187,24 @@ module DeathByCaptcha
|
|
|
203
187
|
ensure
|
|
204
188
|
@mutex.unlock
|
|
205
189
|
end
|
|
206
|
-
|
|
190
|
+
|
|
207
191
|
end
|
|
208
|
-
|
|
192
|
+
|
|
209
193
|
if response.nil?
|
|
210
194
|
msg = 'Connection timed out during API request'
|
|
211
195
|
log('SEND', msg)
|
|
212
|
-
|
|
196
|
+
|
|
213
197
|
raise Exception.new(msg)
|
|
214
198
|
end
|
|
215
|
-
|
|
199
|
+
|
|
216
200
|
log('RECV', response.to_s)
|
|
217
|
-
|
|
201
|
+
|
|
218
202
|
begin
|
|
219
203
|
response = JSON.load(response)
|
|
220
204
|
rescue Exception => e
|
|
221
205
|
raise Exception.new('Invalid API response')
|
|
222
206
|
end
|
|
223
|
-
|
|
207
|
+
|
|
224
208
|
if 0x00 < response['status'] and 0x10 > response['status']
|
|
225
209
|
raise DeathByCaptcha::Errors::AccessDenied
|
|
226
210
|
elsif 0xff == response['status']
|
|
@@ -228,13 +212,13 @@ module DeathByCaptcha
|
|
|
228
212
|
else
|
|
229
213
|
return response
|
|
230
214
|
end
|
|
231
|
-
|
|
215
|
+
|
|
232
216
|
end
|
|
233
|
-
|
|
217
|
+
|
|
234
218
|
end
|
|
235
|
-
|
|
236
|
-
def self.socket_client(username, password, extra={})
|
|
219
|
+
|
|
220
|
+
def self.socket_client(username, password, extra = {})
|
|
237
221
|
DeathByCaptcha::SocketClient.new(username, password, extra)
|
|
238
222
|
end
|
|
239
|
-
|
|
223
|
+
|
|
240
224
|
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
module DeathByCaptcha
|
|
2
|
-
VERSION = "4.1.
|
|
3
|
-
API_VERSION = "DBC/Ruby v4.1.
|
|
4
|
-
end
|
|
2
|
+
VERSION = "4.1.3"
|
|
3
|
+
API_VERSION = "DBC/Ruby v4.1.2"
|
|
4
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: deathbycaptcha
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.1.
|
|
4
|
+
version: 4.1.3
|
|
5
5
|
prerelease:
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
@@ -9,12 +9,11 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2012-
|
|
13
|
-
default_executable:
|
|
12
|
+
date: 2012-12-27 00:00:00.000000000 Z
|
|
14
13
|
dependencies:
|
|
15
14
|
- !ruby/object:Gem::Dependency
|
|
16
15
|
name: rest-client
|
|
17
|
-
requirement:
|
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
|
18
17
|
none: false
|
|
19
18
|
requirements:
|
|
20
19
|
- - ~>
|
|
@@ -22,10 +21,15 @@ dependencies:
|
|
|
22
21
|
version: 1.6.1
|
|
23
22
|
type: :runtime
|
|
24
23
|
prerelease: false
|
|
25
|
-
version_requirements:
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
none: false
|
|
26
|
+
requirements:
|
|
27
|
+
- - ~>
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: 1.6.1
|
|
26
30
|
- !ruby/object:Gem::Dependency
|
|
27
31
|
name: json
|
|
28
|
-
requirement:
|
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
33
|
none: false
|
|
30
34
|
requirements:
|
|
31
35
|
- - ! '>='
|
|
@@ -33,7 +37,12 @@ dependencies:
|
|
|
33
37
|
version: 1.4.6
|
|
34
38
|
type: :runtime
|
|
35
39
|
prerelease: false
|
|
36
|
-
version_requirements:
|
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
41
|
+
none: false
|
|
42
|
+
requirements:
|
|
43
|
+
- - ! '>='
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: 1.4.6
|
|
37
46
|
description: Ruby API for DeathByCaptcha (Captcha Solver as a Service)
|
|
38
47
|
email:
|
|
39
48
|
- tech@infosimples.com.br
|
|
@@ -50,11 +59,10 @@ files:
|
|
|
50
59
|
- lib/deathbycaptcha.rb
|
|
51
60
|
- lib/deathbycaptcha/client.rb
|
|
52
61
|
- lib/deathbycaptcha/config.rb
|
|
53
|
-
- lib/deathbycaptcha/
|
|
62
|
+
- lib/deathbycaptcha/errors.rb
|
|
54
63
|
- lib/deathbycaptcha/http_client.rb
|
|
55
64
|
- lib/deathbycaptcha/socket_client.rb
|
|
56
65
|
- lib/deathbycaptcha/version.rb
|
|
57
|
-
has_rdoc: true
|
|
58
66
|
homepage: http://www.infosimples.com.br/en/open-source/captcha-solving-api/
|
|
59
67
|
licenses: []
|
|
60
68
|
post_install_message:
|
|
@@ -75,7 +83,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
75
83
|
version: '0'
|
|
76
84
|
requirements: []
|
|
77
85
|
rubyforge_project: deathbycaptcha
|
|
78
|
-
rubygems_version: 1.
|
|
86
|
+
rubygems_version: 1.8.24
|
|
79
87
|
signing_key:
|
|
80
88
|
specification_version: 3
|
|
81
89
|
summary: Ruby API for DeathByCaptcha (Captcha Solver as a Service)
|