deathbycaptcha 4.1.5 → 5.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 -4
- data/CHANGELOG.md +20 -0
- data/Gemfile +1 -1
- data/{MIT-LICENSE → LICENSE.txt} +4 -2
- data/README.md +132 -51
- data/Rakefile +2 -2
- data/captchas/1.png +0 -0
- data/deathbycaptcha.gemspec +21 -21
- data/lib/deathbycaptcha.rb +11 -6
- data/lib/deathbycaptcha/client.rb +143 -129
- data/lib/deathbycaptcha/client/http.rb +135 -0
- data/lib/deathbycaptcha/client/socket.rb +113 -0
- data/lib/deathbycaptcha/exceptions.rb +62 -0
- data/lib/deathbycaptcha/models.rb +16 -0
- data/lib/deathbycaptcha/models/captcha.rb +20 -0
- data/lib/deathbycaptcha/models/server_status.rb +20 -0
- data/lib/deathbycaptcha/models/user.rb +28 -0
- data/lib/deathbycaptcha/patches.rb +9 -0
- data/lib/deathbycaptcha/version.rb +3 -3
- data/spec/credentials.yml.example +3 -0
- data/spec/lib/client_spec.rb +94 -0
- data/spec/spec_helper.rb +21 -0
- metadata +59 -39
- data/lib/deathbycaptcha/config.rb +0 -42
- data/lib/deathbycaptcha/errors.rb +0 -69
- data/lib/deathbycaptcha/http_client.rb +0 -98
- data/lib/deathbycaptcha/socket_client.rb +0 -224
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 85231874d833eab0bd84ac29b980d0c00bcb308d
|
4
|
+
data.tar.gz: 91e8dec5412a607e32032e6f8b209a969d288ca7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2d397c364a4b95457252ddd07bdf9a52e8bc0d298932a591515812074310cb98492278cf8679e49e4c1c5b2b33b2eed9faeaff009347699856130724899f7829
|
7
|
+
data.tar.gz: 9f0e76625b2565b2340866068dc10b9290259d2346ec91f8505ea70234e8ca2df09ec0b41a0d573d064b6c74a8128ebbad8dca21fdc61de91c6a102a4264ba0c
|
data/.gitignore
CHANGED
@@ -1,4 +1,17 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
Gemfile.lock
|
4
|
-
|
1
|
+
/.bundle/
|
2
|
+
/.yardoc
|
3
|
+
/Gemfile.lock
|
4
|
+
/_yardoc/
|
5
|
+
/coverage/
|
6
|
+
/doc/
|
7
|
+
/pkg/
|
8
|
+
/spec/reports/
|
9
|
+
/tmp/
|
10
|
+
/spec/credentials.yml
|
11
|
+
*.bundle
|
12
|
+
*.so
|
13
|
+
*.o
|
14
|
+
*.a
|
15
|
+
mkmf.log
|
16
|
+
.ruby-version
|
17
|
+
.ruby-gemset
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
### Comming next
|
2
|
+
|
3
|
+
* Review Socket client, check if it's worth keeping a pool of open connections
|
4
|
+
* Add more tests
|
5
|
+
|
6
|
+
### 5.0.0 - 2015-01-26
|
7
|
+
|
8
|
+
* The code has been completely refactored/rewritten
|
9
|
+
* Bugs reported until 2014-12-31 fixed
|
10
|
+
* Suggestions from community have been taken in consideration
|
11
|
+
* We no longer follow the versioninig system of the official clients of
|
12
|
+
DeathByCaptcha. We have bumped to version 5.0.0 and will use Semantic Versioning
|
13
|
+
from now on.
|
14
|
+
|
15
|
+
### 4.x
|
16
|
+
|
17
|
+
The code was a port from the official DeathByCaptcha python client.
|
18
|
+
|
19
|
+
It didn't have many Ruby conventions, so we decided to abandon it and write a
|
20
|
+
new version 5.0.0.
|
data/Gemfile
CHANGED
data/{MIT-LICENSE → LICENSE.txt}
RENAMED
@@ -1,4 +1,6 @@
|
|
1
|
-
Copyright (
|
1
|
+
Copyright (c) 2015 Rafael Barbolo
|
2
|
+
|
3
|
+
MIT License
|
2
4
|
|
3
5
|
Permission is hereby granted, free of charge, to any person obtaining
|
4
6
|
a copy of this software and associated documentation files (the
|
@@ -17,4 +19,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
19
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
20
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
21
|
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.
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -1,94 +1,175 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
Developed by [Infosimples](https://infosimples.com), a brazilian company that
|
2
|
+
offers [data extraction solutions](https://infosimples.com/en/data-engineering)
|
3
|
+
and [Ruby on Rails development](https://infosimples.com/en/software-development).
|
3
4
|
|
4
|
-
DeathByCaptcha is a Ruby API for acessing the http://www.deathbycaptcha.com services.
|
5
5
|
|
6
|
-
|
6
|
+
# DeathByCaptcha
|
7
7
|
|
8
|
-
|
8
|
+
DeathByCaptcha is a Ruby API for DeathByCaptcha - http://www.deathbycaptcha.com.
|
9
9
|
|
10
|
-
Thread-safety note
|
11
|
-
------------------
|
12
10
|
|
13
|
-
|
11
|
+
## Installation
|
14
12
|
|
15
|
-
|
16
|
-
------------
|
13
|
+
Add this line to your application's Gemfile:
|
17
14
|
|
18
|
-
|
15
|
+
```ruby
|
16
|
+
gem 'deathbycaptcha', '~> 5.0.0'
|
17
|
+
```
|
19
18
|
|
20
|
-
|
19
|
+
And then execute:
|
21
20
|
|
22
|
-
|
21
|
+
$ bundle
|
23
22
|
|
24
|
-
|
23
|
+
Or install it yourself as:
|
25
24
|
|
26
|
-
|
27
|
-
--------
|
25
|
+
$ gem install deathbycaptcha
|
28
26
|
|
29
|
-
### Create a client
|
30
27
|
|
31
|
-
|
28
|
+
## Usage
|
32
29
|
|
33
|
-
|
30
|
+
1. **Create a client**
|
34
31
|
|
35
|
-
|
32
|
+
```ruby
|
33
|
+
# Create a client (:socket and :http clients are available)
|
34
|
+
#
|
35
|
+
client = DeathByCaptcha.new('myusername', 'mypassword', :socket)
|
36
|
+
```
|
36
37
|
|
37
|
-
|
38
|
+
2. **Solve a captcha**
|
38
39
|
|
39
|
-
|
40
|
+
There are two methods available: **decode** and **decode!**
|
41
|
+
* **decode** doesn't raise exceptions.
|
42
|
+
* **decode!** may raise a *DeathByCaptcha::Error* if something goes wrong.
|
40
43
|
|
41
|
-
|
44
|
+
If the solution is not available, an empty captcha object will be returned.
|
42
45
|
|
43
|
-
|
46
|
+
```ruby
|
47
|
+
captcha = client.decode(url: 'http://bit.ly/1xXZcKo')
|
48
|
+
captcha.text # Solution of the captcha
|
49
|
+
captcha.id # Numeric ID of the captcha solved by DeathByCaptcha
|
50
|
+
captcha.is_correct # true if the solution is correct
|
51
|
+
```
|
44
52
|
|
45
|
-
|
53
|
+
You can also specify *path*, *file*, *raw* and *raw64* when decoding an image.
|
46
54
|
|
47
|
-
|
55
|
+
```ruby
|
56
|
+
client.decode(path: 'path/to/my/captcha/file')
|
48
57
|
|
49
|
-
|
58
|
+
client.decode(file: File.open('path/to/my/captcha/file', 'rb'))
|
50
59
|
|
51
|
-
|
60
|
+
client.decode(raw: File.open('path/to/my/captcha/file', 'rb').read)
|
52
61
|
|
53
|
-
|
62
|
+
client.decode(raw64: Base64.encode64(File.open('path/to/my/captcha/file', 'rb').read))
|
63
|
+
```
|
54
64
|
|
55
|
-
|
65
|
+
> Internally, the gem will always convert the image to raw64 (binary base64 encoded).
|
56
66
|
|
57
|
-
|
67
|
+
3. **Retrieve a previously solved captcha**
|
58
68
|
|
59
|
-
|
69
|
+
```ruby
|
70
|
+
captcha = client.captcha('130920620') # with 130920620 as the captcha id
|
71
|
+
```
|
60
72
|
|
61
|
-
|
73
|
+
4. **Report incorrectly solved captcha for refund**
|
62
74
|
|
63
|
-
|
75
|
+
```ruby
|
76
|
+
captcha = client.report!('130920620') # with 130920620 as the captcha id
|
77
|
+
```
|
64
78
|
|
65
|
-
|
79
|
+
> ***Warning:*** *do not abuse on this method, otherwise you may get banned*
|
66
80
|
|
67
|
-
|
81
|
+
5. **Retrieve your user information (which has the current credit balance)**
|
68
82
|
|
69
|
-
|
83
|
+
```ruby
|
84
|
+
user = client.user()
|
85
|
+
user.is_banned # true if the user is banned
|
86
|
+
user.balance # Credit balance in US cents
|
87
|
+
user.rate # Captcha rate, i.e. charges for one solved captcha in US cents
|
88
|
+
user.id # Numeric ID of your account
|
89
|
+
```
|
70
90
|
|
71
|
-
|
91
|
+
6. **Retrieve DeathByCaptcha server status**
|
72
92
|
|
73
|
-
|
93
|
+
```ruby
|
94
|
+
status = client.status()
|
95
|
+
status.todays_accuracy # Current accuracy of DeathByCaptcha
|
96
|
+
status.solved_in # Estimated seconds to solve a captcha right now
|
97
|
+
status.is_service_overloaded # true if DeathByCaptcha is overloaded/unresponsive
|
98
|
+
```
|
74
99
|
|
75
|
-
|
100
|
+
## Notes
|
76
101
|
|
77
|
-
####
|
102
|
+
#### Thread-safety
|
78
103
|
|
79
|
-
|
104
|
+
The API is thread-safe, which means it is perfectly fine to share a client
|
105
|
+
instance between multiple threads.
|
80
106
|
|
81
|
-
####
|
107
|
+
#### HTTP and Socket clients
|
82
108
|
|
83
|
-
|
109
|
+
The API supports HTTP and socket-based connections, with the latter being
|
110
|
+
recommended for having faster responses and overall better performance. The two
|
111
|
+
clients have the same methods/interface.
|
84
112
|
|
85
|
-
|
86
|
-
|
113
|
+
When using the socket client, make sure that outgoing TCP traffic to
|
114
|
+
**api.dbcapi.me** to the ports in range **8123-8130** is not blocked by your
|
115
|
+
firewall.
|
87
116
|
|
88
|
-
|
89
|
-
* Rafael Ivan Garcia (http://github.com/rafaelivan)
|
117
|
+
#### Ruby dependencies
|
90
118
|
|
91
|
-
|
92
|
-
|
119
|
+
DeathByCaptcha >= 5.0.0 don't require specific dependencies. That saves you
|
120
|
+
memmory and avoid conflicts with other gems.
|
93
121
|
|
94
|
-
|
122
|
+
#### Input image format
|
123
|
+
|
124
|
+
Any format you use in the decode method (url, file, path, raw, raw64) will
|
125
|
+
always be converted to a raw64, which is a binary base64 encoded string. So, if
|
126
|
+
you already have this format available on your side, there's no need to do
|
127
|
+
convertions before calling the API.
|
128
|
+
|
129
|
+
> Our recomendation is to never convert your image format, unless needed. Let
|
130
|
+
> the gem convert internally. It may save you resources (CPU, memmory and IO).
|
131
|
+
|
132
|
+
#### Versioning
|
133
|
+
|
134
|
+
We no longer follow the versioninig system of the official clients of
|
135
|
+
DeathByCaptcha. We have bumped to version 5.0.0 and will use
|
136
|
+
[Semantic Versioning](http://semver.org/) from now on.
|
137
|
+
|
138
|
+
#### Upgrade from 4.x to 5.x
|
139
|
+
|
140
|
+
Any 5.x version is incompatible with any 4.x version. Please, review your code
|
141
|
+
before upgrading.
|
142
|
+
|
143
|
+
#### Ruby versions
|
144
|
+
|
145
|
+
This gem has been tested on the following versions of Ruby:
|
146
|
+
|
147
|
+
* MRI 2.2.0
|
148
|
+
* MRI 2.1.5
|
149
|
+
* MRI 2.0.0
|
150
|
+
* MRI 1.9.3
|
151
|
+
|
152
|
+
# Maintainers
|
153
|
+
|
154
|
+
* [Débora Setton Fernandes](http://github.com/deborasetton)
|
155
|
+
* [Rafael Barbolo](http://github.com/barbolo)
|
156
|
+
* [Rafael Ivan Garcia](http://github.com/rafaelivan)
|
157
|
+
|
158
|
+
|
159
|
+
## Contributing
|
160
|
+
|
161
|
+
1. Fork it ( https://github.com/infosimples/deathbycaptcha/fork )
|
162
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
163
|
+
3. **Run/add tests (RSpec)**
|
164
|
+
4. Commit your changes (`git commit -am 'Add some feature'`)
|
165
|
+
5. Push to the branch (`git push origin my-new-feature`)
|
166
|
+
6. Create a new Pull Request
|
167
|
+
7. Yay. Thanks for contributing :)
|
168
|
+
|
169
|
+
All contributors:
|
170
|
+
https://github.com/infosimples/deathbycaptcha/graphs/contributors
|
171
|
+
|
172
|
+
|
173
|
+
# License
|
174
|
+
|
175
|
+
MIT License. Copyright (C) 2011-2015 Infosimples. https://infosimples.com/
|
data/Rakefile
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
require
|
2
|
-
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
data/captchas/1.png
ADDED
Binary file
|
data/deathbycaptcha.gemspec
CHANGED
@@ -1,24 +1,24 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'deathbycaptcha/version'
|
4
5
|
|
5
|
-
Gem::Specification.new do |
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
s.homepage = "http://www.infosimples.com.br/en/open-source/captcha-solving-api/"
|
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)}
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "deathbycaptcha"
|
8
|
+
spec.version = DeathByCaptcha::VERSION
|
9
|
+
spec.authors = ["Débora Setton Fernandes, Rafael Barbolo, Rafael Ivan Garcia"]
|
10
|
+
spec.email = ["team@infosimples.com.br"]
|
11
|
+
spec.summary = %q{Ruby API for DeathByCaptcha (Captcha Solver as a Service)}
|
12
|
+
spec.description = %q{DeathByCaptcha allows you to solve captchas with manual labor}
|
13
|
+
spec.homepage = "https://github.com/infosimples/deathbycaptcha"
|
14
|
+
spec.license = "MIT"
|
17
15
|
|
18
|
-
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
end
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "rspec", "~> 3.1"
|
24
|
+
end
|
data/lib/deathbycaptcha.rb
CHANGED
@@ -1,9 +1,14 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
3
|
-
require '
|
4
|
-
require '
|
5
|
-
require '
|
6
|
-
require 'deathbycaptcha/socket_client'
|
1
|
+
require 'base64'
|
2
|
+
require 'json'
|
3
|
+
require 'bigdecimal'
|
4
|
+
require 'socket'
|
5
|
+
require 'net/http'
|
7
6
|
|
8
7
|
module DeathByCaptcha
|
9
8
|
end
|
9
|
+
|
10
|
+
require 'deathbycaptcha/client'
|
11
|
+
require 'deathbycaptcha/exceptions'
|
12
|
+
require 'deathbycaptcha/models'
|
13
|
+
require 'deathbycaptcha/patches'
|
14
|
+
require 'deathbycaptcha/version'
|
@@ -1,188 +1,202 @@
|
|
1
|
-
require 'logger'
|
2
|
-
require 'digest/sha1'
|
3
|
-
require 'rest_client'
|
4
|
-
|
5
1
|
module DeathByCaptcha
|
6
2
|
|
3
|
+
# Create a DeathByCaptcha API client. This is a shortcut to
|
4
|
+
# DeathByCaptcha::Client.create.
|
7
5
|
#
|
8
|
-
|
6
|
+
def self.new(*args)
|
7
|
+
DeathByCaptcha::Client.create(*args)
|
8
|
+
end
|
9
|
+
|
10
|
+
# DeathByCaptcha::Client is a common interface inherited by DBC clients like
|
11
|
+
# DeathByCaptcha::Client::HTTP and DeathByCaptcha::Client::Socket.
|
9
12
|
#
|
10
13
|
class Client
|
11
14
|
|
12
|
-
attr_accessor :
|
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.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
|
-
}.merge(extra)
|
30
|
-
|
31
|
-
@config = DeathByCaptcha::Config.new(data) # Config instance.
|
32
|
-
@logger = Logger.new(@config.logger_output) # Logger.
|
33
|
-
end
|
34
|
-
|
35
|
-
#
|
36
|
-
# Fetch the user's details -- balance, rate and banned status.
|
37
|
-
#
|
38
|
-
def get_user
|
39
|
-
raise DeathByCaptcha::Errors::NotImplemented
|
40
|
-
end
|
15
|
+
attr_accessor :username, :password, :timeout, :polling
|
41
16
|
|
17
|
+
# Create a DeathByCaptcha API client
|
42
18
|
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
19
|
+
# @param [String] username Username of the DeathByCaptcha account.
|
20
|
+
# @param [String] password Password of the DeathByCaptcha account.
|
21
|
+
# @param [Symbol] connection Connection type (:socket, :http)
|
22
|
+
# @param [Hash] options Options hash.
|
23
|
+
# @option options [Integer] :timeout (60) Seconds before giving up.
|
24
|
+
# @option options [Integer] :polling (5) Seconds for polling the solution.
|
49
25
|
#
|
50
|
-
#
|
26
|
+
# @return [DeathByCaptcha::Client] A Socket or HTTP Client instance.
|
51
27
|
#
|
52
|
-
def
|
53
|
-
|
28
|
+
def self.create(username, password, connection = :socket, options = {})
|
29
|
+
case connection
|
30
|
+
when :socket
|
31
|
+
DeathByCaptcha::Client::Socket.new(username, password, options)
|
32
|
+
when :http
|
33
|
+
DeathByCaptcha::Client::HTTP.new(username, password, options)
|
34
|
+
else
|
35
|
+
raise DeathByCaptcha::InvalidClientConnection
|
36
|
+
end
|
54
37
|
end
|
55
38
|
|
39
|
+
# Create a DeathByCaptcha client.
|
56
40
|
#
|
57
|
-
#
|
58
|
-
#
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
41
|
+
# @param [String] username Username of the DeathByCaptcha account.
|
42
|
+
# @param [String] password Password of the DeathByCaptcha account.
|
43
|
+
# @param [Hash] options Options hash.
|
44
|
+
# @option options [Integer] :timeout (60) Seconds before giving up.
|
45
|
+
# @option options [Integer] :polling (5) Seconds for polling the solution.
|
63
46
|
#
|
64
|
-
#
|
47
|
+
# @return [DeathByCaptcha::Client] A Client instance.
|
65
48
|
#
|
66
|
-
def
|
67
|
-
|
49
|
+
def initialize(username, password, options = {})
|
50
|
+
self.username = username
|
51
|
+
self.password = password
|
52
|
+
self.timeout = options[:timeout] || 60
|
53
|
+
self.polling = options[:polling] || 5
|
68
54
|
end
|
69
55
|
|
56
|
+
# Decode the text from an image (i.e. solve a captcha).
|
70
57
|
#
|
71
|
-
#
|
58
|
+
# @param [Hash] options Options hash.
|
59
|
+
# @option options [String] :url URL of the image to be decoded.
|
60
|
+
# @option options [String] :path File path of the image to be decoded.
|
61
|
+
# @option options [File] :file File instance with image to be decoded.
|
62
|
+
# @option options [String] :raw Binary content of the image to be decoded.
|
63
|
+
# @option options [String] :raw64 Binary content encoded in base64 of the image to be decoded.
|
72
64
|
#
|
73
|
-
#
|
74
|
-
# whether the CAPTCHA is case-sensitive or not. Returns CAPTCHA details
|
75
|
-
# on success.
|
65
|
+
# @return [DeathByCaptcha::Captcha] The captcha (with solution) or an empty hash if something goes wrong.
|
76
66
|
#
|
77
|
-
def
|
78
|
-
|
67
|
+
def decode(options = {})
|
68
|
+
decode!(options)
|
69
|
+
rescue DeathByCaptcha::Error
|
70
|
+
DeathByCaptcha::Captcha.new
|
79
71
|
end
|
80
72
|
|
73
|
+
# Decode the text from an image (i.e. solve a captcha).
|
81
74
|
#
|
82
|
-
#
|
83
|
-
#
|
84
|
-
#
|
75
|
+
# @param [Hash] options Options hash.
|
76
|
+
# @option options [String] :url URL of the image to be decoded.
|
77
|
+
# @option options [String] :path File path of the image to be decoded.
|
78
|
+
# @option options [File] :file File instance with image to be decoded.
|
79
|
+
# @option options [String] :raw Binary content of the image to be decoded.
|
80
|
+
# @option options [String] :raw64 Binary content encoded in base64 of the image to be decoded.
|
85
81
|
#
|
86
|
-
#
|
87
|
-
# timeout (in seconds). Removes unsolved CAPTCHAs. Returns CAPTCHA
|
88
|
-
# details if (correctly) solved.
|
82
|
+
# @return [DeathByCaptcha::Captcha] The captcha (with solution if an error is not raised).
|
89
83
|
#
|
90
|
-
def decode(
|
91
|
-
|
92
|
-
:timeout => config.default_timeout,
|
93
|
-
:is_case_sensitive => false,
|
94
|
-
:is_raw_content => false
|
95
|
-
}.merge(options)
|
84
|
+
def decode!(options = {})
|
85
|
+
deadline = Time.now + self.timeout
|
96
86
|
|
97
|
-
|
98
|
-
|
99
|
-
if c
|
87
|
+
raw64 = load_captcha(options)
|
88
|
+
raise DeathByCaptcha::InvalidCaptcha if raw64.to_s.empty?
|
100
89
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
90
|
+
_captcha = self.upload(raw64)
|
91
|
+
while _captcha.text.to_s.empty? && deadline > Time.now
|
92
|
+
sleep(self.polling)
|
93
|
+
_captcha = self.captcha(_captcha.id)
|
94
|
+
end
|
105
95
|
|
106
|
-
|
107
|
-
|
108
|
-
else
|
109
|
-
remove(c['captcha'])
|
110
|
-
end
|
96
|
+
raise DeathByCaptcha::IncorrectSolution if !_captcha.is_correct
|
97
|
+
raise DeathByCaptcha::Timeout if _captcha.text.to_s.empty?
|
111
98
|
|
112
|
-
|
99
|
+
_captcha
|
113
100
|
end
|
114
101
|
|
102
|
+
# Retrieve information from an uploaded captcha.
|
103
|
+
#
|
104
|
+
# @param [Integer] captcha_id Numeric ID of the captcha.
|
115
105
|
#
|
116
|
-
#
|
106
|
+
# @return [DeathByCaptcha::Captcha] The captcha object.
|
117
107
|
#
|
118
|
-
|
108
|
+
def captcha(captcha_id)
|
109
|
+
raise NotImplementedError
|
110
|
+
end
|
119
111
|
|
112
|
+
# Report incorrectly solved captcha for refund.
|
113
|
+
#
|
114
|
+
# @param [Integer] captcha_id Numeric ID of the captcha.
|
120
115
|
#
|
121
|
-
#
|
116
|
+
# @return [DeathByCaptcha::Captcha] The captcha object.
|
122
117
|
#
|
123
|
-
def
|
124
|
-
|
118
|
+
def report!(captcha_id)
|
119
|
+
raise NotImplementedError
|
125
120
|
end
|
126
121
|
|
122
|
+
# Retrieve your user information (which has the current credit balance).
|
127
123
|
#
|
128
|
-
#
|
124
|
+
# @return [DeathByCaptcha::User] The user object.
|
129
125
|
#
|
130
|
-
|
126
|
+
def user
|
127
|
+
raise NotImplementedError
|
128
|
+
end
|
131
129
|
|
130
|
+
# Retrieve DeathByCaptcha server status.
|
132
131
|
#
|
133
|
-
#
|
132
|
+
# @return [DeathByCaptcha::ServerStatus] The server status object.
|
134
133
|
#
|
135
|
-
def
|
136
|
-
|
137
|
-
@logger.info "#{cmd}: #{msg}"
|
138
|
-
end
|
134
|
+
def status
|
135
|
+
raise NotImplementedError
|
139
136
|
end
|
140
137
|
|
138
|
+
# Upload a captcha to DeathByCaptcha.
|
141
139
|
#
|
142
|
-
#
|
140
|
+
# This method will not return the solution. It's only useful if you want to
|
141
|
+
# implement your own "decode" function.
|
143
142
|
#
|
144
|
-
# captcha
|
145
|
-
# => a raw file content if is_raw_content is true
|
146
|
-
# => a File if its kind of File
|
147
|
-
# => a url if it's a String and starts with 'http://'
|
148
|
-
# => a filesystem path otherwise
|
143
|
+
# @return [DeathByCaptcha::Captcha] The captcha object (not solved yet).
|
149
144
|
#
|
150
|
-
def
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
if is_raw_content
|
155
|
-
# Create a temporary file, write the raw content and return it
|
156
|
-
tmp_file_path = File.join(Dir.tmpdir, "captcha_#{Time.now.to_i}_#{rand}")
|
157
|
-
File.open(tmp_file_path, 'wb') { |f| f.write captcha }
|
158
|
-
file = File.open(tmp_file_path, 'r')
|
159
|
-
|
160
|
-
elsif captcha.kind_of? File
|
161
|
-
# simply return the file
|
162
|
-
file = captcha
|
145
|
+
def upload(raw64)
|
146
|
+
raise NotImplementedError
|
147
|
+
end
|
163
148
|
|
164
|
-
|
165
|
-
# Create a temporary file, download the file, write it to tempfile and return it
|
166
|
-
tmp_file_path = File.join(Dir.tmpdir, "captcha_#{Time.now.to_i}_#{rand}")
|
167
|
-
File.open(tmp_file_path, 'wb') { |f| f.write RestClient.get(captcha, headers) }
|
168
|
-
file = File.open(tmp_file_path, 'r')
|
149
|
+
private
|
169
150
|
|
151
|
+
# Load a captcha raw content encoded in base64 from options.
|
152
|
+
#
|
153
|
+
# @param [Hash] options Options hash.
|
154
|
+
# @option options [String] :url URL of the image to be decoded.
|
155
|
+
# @option options [String] :path File path of the image to be decoded.
|
156
|
+
# @option options [File] :file File instance with image to be decoded.
|
157
|
+
# @option options [String] :raw Binary content of the image to be decoded.
|
158
|
+
# @option options [String] :raw64 Binary content encoded in base64 of the image to be decoded.
|
159
|
+
#
|
160
|
+
# @return [String] The binary image base64 encoded.
|
161
|
+
#
|
162
|
+
def load_captcha(options)
|
163
|
+
if options[:raw64]
|
164
|
+
options[:raw64]
|
165
|
+
elsif options[:raw]
|
166
|
+
Base64.encode64(options[:raw])
|
167
|
+
elsif options[:file]
|
168
|
+
Base64.encode64(options[:file].read())
|
169
|
+
elsif options[:path]
|
170
|
+
Base64.encode64(File.open(options[:path], 'rb').read)
|
171
|
+
elsif options[:url]
|
172
|
+
Base64.encode64(open_url(options[:url]))
|
170
173
|
else
|
171
|
-
|
172
|
-
file = File.open(captcha, 'r')
|
173
|
-
|
174
|
+
''
|
174
175
|
end
|
176
|
+
rescue
|
177
|
+
''
|
178
|
+
end
|
179
|
+
|
180
|
+
def open_url(url)
|
181
|
+
uri = URI(url)
|
175
182
|
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
183
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
184
|
+
|
185
|
+
if uri.scheme == 'https'
|
186
|
+
http.use_ssl = true
|
187
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
180
188
|
end
|
181
189
|
|
182
|
-
|
190
|
+
res = http.get(uri.request_uri)
|
183
191
|
|
192
|
+
if (redirect = res.header['location'])
|
193
|
+
open_url(redirect)
|
194
|
+
else
|
195
|
+
res.body
|
196
|
+
end
|
184
197
|
end
|
185
|
-
|
186
198
|
end
|
187
|
-
|
188
199
|
end
|
200
|
+
|
201
|
+
require 'deathbycaptcha/client/http'
|
202
|
+
require 'deathbycaptcha/client/socket'
|