anti_captcha 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +141 -0
- data/Rakefile +6 -0
- data/anti_captcha.gemspec +27 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/anti_captcha.rb +39 -0
- data/lib/anti_captcha/client.rb +385 -0
- data/lib/anti_captcha/errors.rb +19 -0
- data/lib/anti_captcha/http.rb +39 -0
- data/lib/anti_captcha/models/image_to_text_solution.rb +5 -0
- data/lib/anti_captcha/models/no_captcha_solution.rb +5 -0
- data/lib/anti_captcha/models/solution.rb +5 -0
- data/lib/anti_captcha/models/task_result.rb +6 -0
- data/lib/anti_captcha/version.rb +4 -0
- metadata +106 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ea4b4a53f966ab69db9ea92a2575ee32afe55c88
|
4
|
+
data.tar.gz: 13674c466b8c24af5b4e1e995b56151fff45d1a4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d6fa39db3ce217ed087511f235f0a6af458cdc78899f5b3e51c886064e64eb9ad7087a61c1e007b36d5600a13b7ece8b89738e1bf4591ffe2d7ca92d66d1899a
|
7
|
+
data.tar.gz: 7d3b40a511221ddd807698b06c55b13e98ec10b3dcf48284266444bb43aca3a38f1f39a9976db68295d14f3d53164936b3ff7985f10e743e4a55f907295db1a8
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
10
|
+
orientation.
|
11
|
+
|
12
|
+
## Our Standards
|
13
|
+
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
15
|
+
include:
|
16
|
+
|
17
|
+
* Using welcoming and inclusive language
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
19
|
+
* Gracefully accepting constructive criticism
|
20
|
+
* Focusing on what is best for the community
|
21
|
+
* Showing empathy towards other community members
|
22
|
+
|
23
|
+
Examples of unacceptable behavior by participants include:
|
24
|
+
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
+
advances
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
+
* Public or private harassment
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
30
|
+
address, without explicit permission
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
+
professional setting
|
33
|
+
|
34
|
+
## Our Responsibilities
|
35
|
+
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
38
|
+
response to any instances of unacceptable behavior.
|
39
|
+
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
+
threatening, offensive, or harmful.
|
45
|
+
|
46
|
+
## Scope
|
47
|
+
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
+
when an individual is representing the project or its community. Examples of
|
50
|
+
representing a project or community include using an official project e-mail
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
53
|
+
further defined and clarified by project maintainers.
|
54
|
+
|
55
|
+
## Enforcement
|
56
|
+
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
+
reported by contacting the project team at rafael.ivan@infosimples.com.br. All
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
63
|
+
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
66
|
+
members of the project's leadership.
|
67
|
+
|
68
|
+
## Attribution
|
69
|
+
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
+
available at [http://contributor-covenant.org/version/1/4][version]
|
72
|
+
|
73
|
+
[homepage]: http://contributor-covenant.org
|
74
|
+
[version]: http://contributor-covenant.org/version/1/4/
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Rafael Ivan Garcia
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
Developed by [Infosimples](https://infosimples.com).
|
2
|
+
|
3
|
+
# AntiCaptcha
|
4
|
+
|
5
|
+
AntiCaptcha is a Ruby API for Anti-Captcha - [Anti-Captcha.com](http://getcaptchasolution.com/ipuz16klxh).
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'anti_captcha'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install anti_captcha
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
1. **Create a client**
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
# Create a client
|
29
|
+
client = AntiCaptcha.new('my_key')
|
30
|
+
```
|
31
|
+
|
32
|
+
2. **Solve Image CAPTCHA**
|
33
|
+
|
34
|
+
There are two methods available:
|
35
|
+
|
36
|
+
- `decode_image`: solves image CAPTCHAs. It doesn't raise exceptions.
|
37
|
+
- `decode_image!`: solves image CAPTCHAs. It may raise an `AntiCaptcha::Error` if something goes wrong.
|
38
|
+
|
39
|
+
If the solution is not available, an empty solution object will be returned.
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
solution = client.decode_image!(path: 'path/to/my/captcha/file')
|
43
|
+
solution.text # CAPTCHA solution.
|
44
|
+
solution.url # Image URL.
|
45
|
+
solution.task_result.task_id # The ID of the task.
|
46
|
+
```
|
47
|
+
|
48
|
+
You can also specify *file*, *body* and *body64* when decoding an image.
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
client.decode_image!(file: File.open('path/to/my/captcha/file', 'rb'))
|
52
|
+
|
53
|
+
client.decode_image!(body: File.open('path/to/my/captcha/file', 'rb').read)
|
54
|
+
|
55
|
+
client.decode_image!(body64: Base64.encode64(File.open('path/to/my/captcha/file', 'rb').read))
|
56
|
+
```
|
57
|
+
|
58
|
+
> Internally, the gem will always convert the image to body64 (binary base64 encoded).
|
59
|
+
|
60
|
+
3. **Report incorrectly solved image CAPTCHA for refund**
|
61
|
+
|
62
|
+
It is only possible to report incorrectly solved image CAPTCHAs.
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
client.report_incorrect_image_catpcha!(task_id)
|
66
|
+
```
|
67
|
+
|
68
|
+
4. **Get your account balance**
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
client.get_balance!
|
72
|
+
```
|
73
|
+
|
74
|
+
5. **Get current stats of a queue.**
|
75
|
+
|
76
|
+
Queue IDs:
|
77
|
+
- `1` Standart ImageToText, English language.
|
78
|
+
- `2` Standart ImageToText, Russian language.
|
79
|
+
- `5` Recaptcha NoCaptcha tasks.
|
80
|
+
- `6` Recaptcha Proxyless task.
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
client.get_queue_stats!(queue_id)
|
84
|
+
```
|
85
|
+
|
86
|
+
6. **Clickable CAPTCHAs (e.g. "No CAPTCHA reCAPTCHA")**
|
87
|
+
|
88
|
+
This method allows you to solve CAPTCHAs similar to
|
89
|
+
[reCAPTCHA v2](https://support.google.com/recaptcha/?hl=en#6262736).
|
90
|
+
|
91
|
+
There are two methods available:
|
92
|
+
|
93
|
+
- `decode_nocaptcha`: solves NoCaptcha CAPTCHAs. It doesn't raise exceptions.
|
94
|
+
- `decode_nocaptcha!`: solves NoCaptcha CAPTCHAs. It may raise an `AntiCaptcha::Error` if something goes wrong.
|
95
|
+
|
96
|
+
**Send the `website_key` and `website_url` parameters**
|
97
|
+
|
98
|
+
This method requires no browser emulation. You can send two parameters that
|
99
|
+
identify the website in which the CAPTCHA is found.
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
options = {
|
103
|
+
website_key: 'xyz',
|
104
|
+
website_url: 'http://example.com/example=1'
|
105
|
+
}
|
106
|
+
|
107
|
+
solution = client.decode_nocaptcha!(options)
|
108
|
+
solution.g_recaptcha_response # Solution of the captcha
|
109
|
+
```
|
110
|
+
|
111
|
+
The solution (`solution.g_recaptcha_response`) will be a code that validates
|
112
|
+
the form, like the following:
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
"1JJHJ_VuuHAqJKxcaasbTsqw-L1Sm4gD57PTeaEr9-MaETG1vfu2H5zlcwkjsRoZoHxx6V9yUDw8Ig-hYD8kakmSnnjNQd50w_Y_tI3aDLp-s_7ZmhH6pcaoWWsid5hdtMXyvrP9DscDuCLBf7etLle8caPWSaYCpAq9DOTtj5NpSg6-OeCJdGdkjsakFUMeGeqmje87wSajcjmdjl_w4XZBY2zy8fUH6XoAGZ6AeCTulIljBQDObQynKDd-rutPvKNxZasDk-LbhTfw508g1lu9io6jnvm3kbAdnkfZ0x0PkGiUMHU7hnuoW6bXo2Yn_Zt5tDWL7N7wFtY6B0k7cTy73f8er508zReOuoyz2NqL8smDCmcJu05ajkPGt20qzpURMwHaw"
|
116
|
+
```
|
117
|
+
|
118
|
+
## Notes
|
119
|
+
|
120
|
+
#### Ruby dependencies
|
121
|
+
|
122
|
+
AntiCaptcha doesn't require specific dependencies. That saves you memory and
|
123
|
+
avoid conflicts with other gems.
|
124
|
+
|
125
|
+
#### Input image format
|
126
|
+
|
127
|
+
Any format you use in the decode method (file, path, body, body64) will always
|
128
|
+
be converted to a body64, which is a binary base64 encoded string. So, if you
|
129
|
+
already have this format available on your side, there's no need to do
|
130
|
+
convertions before calling the API.
|
131
|
+
|
132
|
+
> Our recomendation is to never convert your image format, unless needed. Let
|
133
|
+
> the gem convert internally. It may save you resources (CPU, memory and IO).
|
134
|
+
|
135
|
+
#### Versioning
|
136
|
+
|
137
|
+
AntiCaptcha gem uses [Semantic Versioning](http://semver.org/).
|
138
|
+
|
139
|
+
# License
|
140
|
+
|
141
|
+
MIT License. Copyright (C) 2011-2015 Infosimples. https://infosimples.com/
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "anti_captcha/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "anti_captcha"
|
8
|
+
spec.version = AntiCaptcha::VERSION
|
9
|
+
spec.authors = ["Infosimples"]
|
10
|
+
spec.email = ["team@infosimples.com.br"]
|
11
|
+
|
12
|
+
spec.summary = %q{Ruby API for Anti Captcha (CAPTCHA Solver as a Service)}
|
13
|
+
spec.description = %q{TwoCaptcha is an automated CAPTCHA solving service}
|
14
|
+
spec.homepage = "https://github.com/infosimples/anti_captcha"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
spec.bindir = "exe"
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.15"
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
27
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "anti_captcha"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/lib/anti_captcha.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'json'
|
3
|
+
require 'net/http'
|
4
|
+
require 'openssl'
|
5
|
+
|
6
|
+
#
|
7
|
+
# The module AntiCaptcha contains all the code for the anti_captcha gem.
|
8
|
+
# It acts as a safely namespace that isolates logic of AntiCaptcha from any
|
9
|
+
# project that uses it.
|
10
|
+
#
|
11
|
+
module AntiCaptcha
|
12
|
+
#
|
13
|
+
# Creates an Anti Captcha API client. This is a shortcut to
|
14
|
+
# AntiCaptcha::Client.new.
|
15
|
+
#
|
16
|
+
def self.new(key, options = {})
|
17
|
+
AntiCaptcha::Client.new(key, options)
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# Base class of a model object returned by AntiCaptcha API.
|
22
|
+
#
|
23
|
+
class Model
|
24
|
+
def initialize(values = {})
|
25
|
+
values.each do |key, value|
|
26
|
+
send("#{key}=", value) if respond_to?("#{key}=")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
require 'anti_captcha/http'
|
33
|
+
require 'anti_captcha/errors'
|
34
|
+
require 'anti_captcha/models/solution'
|
35
|
+
require 'anti_captcha/models/image_to_text_solution'
|
36
|
+
require 'anti_captcha/models/no_captcha_solution'
|
37
|
+
require 'anti_captcha/models/task_result'
|
38
|
+
require 'anti_captcha/client'
|
39
|
+
require 'anti_captcha/version'
|
@@ -0,0 +1,385 @@
|
|
1
|
+
module AntiCaptcha
|
2
|
+
# AntiCaptcha::Client is a client that communicates with the Anti Captcha API:
|
3
|
+
# https://anti-captcha.com.
|
4
|
+
#
|
5
|
+
class Client
|
6
|
+
BASE_URL = 'https://api.anti-captcha.com/:action'
|
7
|
+
|
8
|
+
attr_accessor :client_key, :timeout, :polling
|
9
|
+
|
10
|
+
#
|
11
|
+
# Creates a client for the Anti Captcha API.
|
12
|
+
#
|
13
|
+
# @param [String] client_key The key of the Anti Captcha account.
|
14
|
+
# @param [Hash] options Options hash.
|
15
|
+
# @option options [Integer] :timeout (60) Seconds before giving up of a
|
16
|
+
# captcha being solved.
|
17
|
+
# @option options [Integer] :polling (5) Seconds before checking answer
|
18
|
+
# again.
|
19
|
+
#
|
20
|
+
# @return [AntiCaptcha::Client] A Client instance.
|
21
|
+
#
|
22
|
+
def initialize(client_key, options = {})
|
23
|
+
self.client_key = client_key
|
24
|
+
self.timeout = options[:timeout] || 60
|
25
|
+
self.polling = options[:polling] || 5
|
26
|
+
end
|
27
|
+
|
28
|
+
#
|
29
|
+
# Decodes an image CAPTCHA.
|
30
|
+
#
|
31
|
+
# @see `AntiCaptcha::Client#decode_image!`
|
32
|
+
#
|
33
|
+
def decode_image(options)
|
34
|
+
decode_image!(options)
|
35
|
+
rescue
|
36
|
+
AntiCaptcha::ImageToTextSolution.new
|
37
|
+
end
|
38
|
+
|
39
|
+
#
|
40
|
+
# Decodes an image CAPTCHA.
|
41
|
+
#
|
42
|
+
# @param [Hash] options Options hash.
|
43
|
+
# @option options [String] :body64 File body encoded in base64. Make sure to
|
44
|
+
# send it without line breaks.
|
45
|
+
# @option options [String] :body Binary file body.
|
46
|
+
# @option options [String] :path File path of the image to be decoded.
|
47
|
+
# @option options [File] :file File instance with image to be
|
48
|
+
# decoded.
|
49
|
+
# @option options [Boolean] :phrase If the worker must enter an answer
|
50
|
+
# with at least one "space".
|
51
|
+
# @option options [Boolean] :case If the answer must be entered with case
|
52
|
+
# sensitivity.
|
53
|
+
# @option options [Integer] :numeric 0 - no requirements; 1 - only numbers
|
54
|
+
# are allowed; 2 - any letters are
|
55
|
+
# allowed except numbers.
|
56
|
+
# @option options [Boolean] :math If the answer must be calculated.
|
57
|
+
# @option options [Integer] :min_length Defines minimum length of the
|
58
|
+
# answer. 0 - no requirements.
|
59
|
+
# @option options [Integer] :max_length Defines maximum length of the
|
60
|
+
# answer. 0 - no requirements.
|
61
|
+
#
|
62
|
+
# @return [AntiCaptcha::ImageToTextSolution] The solution of the image
|
63
|
+
# CAPTCHA.
|
64
|
+
#
|
65
|
+
def decode_image!(options)
|
66
|
+
started_at = Time.now
|
67
|
+
|
68
|
+
options[:body64] = load_captcha(options)
|
69
|
+
task = create_task!('ImageToTextTask', options)
|
70
|
+
|
71
|
+
if task['taskId']
|
72
|
+
api_result = get_task_result!(task['taskId'])
|
73
|
+
|
74
|
+
while api_result['status'] != 'ready'
|
75
|
+
sleep(polling)
|
76
|
+
api_result = get_task_result!(task['taskId'])
|
77
|
+
raise AntiCaptcha::Timeout if (Time.now - started_at) > timeout
|
78
|
+
end
|
79
|
+
|
80
|
+
task_result = AntiCaptcha::TaskResult.new(
|
81
|
+
task_id: task['taskId'],
|
82
|
+
error_id: api_result['errorId'],
|
83
|
+
error_code: api_result['errorCode'],
|
84
|
+
error_description: api_result['errorDescription'],
|
85
|
+
status: api_result['status'],
|
86
|
+
cost: api_result['cost'],
|
87
|
+
ip: api_result['ip'],
|
88
|
+
create_time: api_result['createTime'],
|
89
|
+
end_time: api_result['endTime'],
|
90
|
+
solve_count: api_result['solveCount']
|
91
|
+
)
|
92
|
+
|
93
|
+
return AntiCaptcha::ImageToTextSolution.new(
|
94
|
+
api_response: api_result,
|
95
|
+
task_result: task_result,
|
96
|
+
url: api_result['solution']['url'],
|
97
|
+
text: api_result['solution']['text']
|
98
|
+
)
|
99
|
+
|
100
|
+
else
|
101
|
+
raise AntiCaptcha.raise_error('taskId not received from Anti Captcha.')
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
#
|
106
|
+
# Decodes a NoCaptcha CAPTCHA.
|
107
|
+
#
|
108
|
+
# @see `AntiCaptcha::Client#decode_nocaptcha!`
|
109
|
+
#
|
110
|
+
def decode_nocaptcha(options, proxy = nil)
|
111
|
+
decode_nocaptcha!(options, proxy)
|
112
|
+
rescue
|
113
|
+
AntiCaptcha::NoCaptchaSolution.new
|
114
|
+
end
|
115
|
+
|
116
|
+
#
|
117
|
+
# Decodes a NoCaptcha CAPTCHA.
|
118
|
+
#
|
119
|
+
# @param [Hash] options Options hash.
|
120
|
+
# @option options [String] :website_url
|
121
|
+
# @option options [String] :website_key
|
122
|
+
# @option options [String] :language_pool
|
123
|
+
#
|
124
|
+
# @param [Hash] proxy Not mandatory. A hash with configs of the proxy that
|
125
|
+
# has to be used. Defaults to `nil`.
|
126
|
+
# @option proxy [String] :proxy_type
|
127
|
+
# @option proxy [String] :proxy_address
|
128
|
+
# @option proxy [String] :proxy_port
|
129
|
+
# @option proxy [String] :proxy_login
|
130
|
+
# @option proxy [String] :proxy_login
|
131
|
+
# @option proxy [String] :proxy_password
|
132
|
+
# @option proxy [String] :user_agent
|
133
|
+
#
|
134
|
+
# @return [AntiCaptcha::NoCaptchaSolution] The solution of the NoCaptcha.
|
135
|
+
#
|
136
|
+
def decode_nocaptcha!(options, proxy = nil)
|
137
|
+
started_at = Time.now
|
138
|
+
|
139
|
+
if proxy.nil?
|
140
|
+
task_type = 'NoCaptchaTaskProxyless'
|
141
|
+
hsh = options
|
142
|
+
else
|
143
|
+
task_type = 'NoCaptchaTask'
|
144
|
+
hsh = options.merge(proxy)
|
145
|
+
end
|
146
|
+
|
147
|
+
task = create_task!(task_type, hsh)
|
148
|
+
|
149
|
+
if task['taskId']
|
150
|
+
api_result = get_task_result!(task['taskId'])
|
151
|
+
|
152
|
+
while api_result['status'] != 'ready'
|
153
|
+
sleep(polling)
|
154
|
+
api_result = get_task_result!(task['taskId'])
|
155
|
+
raise AntiCaptcha::Timeout if (Time.now - started_at) > timeout
|
156
|
+
end
|
157
|
+
|
158
|
+
task_result = AntiCaptcha::TaskResult.new(
|
159
|
+
task_id: task['taskId'],
|
160
|
+
error_id: api_result['errorId'],
|
161
|
+
error_code: api_result['errorCode'],
|
162
|
+
error_description: api_result['errorDescription'],
|
163
|
+
status: api_result['status'],
|
164
|
+
cost: api_result['cost'],
|
165
|
+
ip: api_result['ip'],
|
166
|
+
create_time: api_result['createTime'],
|
167
|
+
end_time: api_result['endTime'],
|
168
|
+
solve_count: api_result['solveCount']
|
169
|
+
)
|
170
|
+
|
171
|
+
return AntiCaptcha::NoCaptchaSolution.new(
|
172
|
+
api_response: api_result,
|
173
|
+
task_result: task_result,
|
174
|
+
g_recaptcha_response: api_result['solution']['gRecaptchaResponse'],
|
175
|
+
g_recaptcha_response_md5: api_result['solution']['gRecaptchaResponseMD5']
|
176
|
+
)
|
177
|
+
|
178
|
+
else
|
179
|
+
raise AntiCaptcha.raise_error('taskId not received from Anti Captcha.')
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
#
|
184
|
+
# Creates a task for solving the selected CAPTCHA type.
|
185
|
+
#
|
186
|
+
# @param [String] type The type of the CAPTCHA.
|
187
|
+
# @param [Hash] options Options hash.
|
188
|
+
# # Image to text CAPTCHA
|
189
|
+
# @option options [String] :body64 File body encoded in base64. Make sure to
|
190
|
+
# send it without line breaks.
|
191
|
+
# @option options [Boolean] :phrase If the worker must enter an answer
|
192
|
+
# with at least one "space".
|
193
|
+
# @option options [Boolean] :case If the answer must be entered with case
|
194
|
+
# sensitivity.
|
195
|
+
# @option options [Integer] :numeric 0 - no requirements; 1 - only numbers
|
196
|
+
# are allowed; 2 - any letters are
|
197
|
+
# allowed except numbers.
|
198
|
+
# @option options [Boolean] :math If the answer must be calculated.
|
199
|
+
# @option options [Integer] :min_length Defines minimum length of the
|
200
|
+
# answer. 0 - no requirements.
|
201
|
+
# @option options [Integer] :max_length Defines maximum length of the
|
202
|
+
# answer. 0 - no requirements.
|
203
|
+
# # NoCaptcha
|
204
|
+
# @option options [String] :website_url Address of target web page.
|
205
|
+
# @option options [String] :website_key Recaptcha website key.
|
206
|
+
# @option options [String] :language_pool
|
207
|
+
# @option options [String] :proxy_type
|
208
|
+
# @option options [String] :proxy_address
|
209
|
+
# @option options [String] :proxy_port
|
210
|
+
# @option options [String] :proxy_login
|
211
|
+
# @option options [String] :proxy_login
|
212
|
+
# @option options [String] :proxy_password
|
213
|
+
# @option options [String] :user_agent
|
214
|
+
#
|
215
|
+
# @return [Hash] Information about the task.
|
216
|
+
#
|
217
|
+
def create_task!(type, options)
|
218
|
+
args = {
|
219
|
+
languagePool: (options[:language_pool] || 'en'),
|
220
|
+
softId: '859'
|
221
|
+
}
|
222
|
+
|
223
|
+
case type
|
224
|
+
when 'ImageToTextTask'
|
225
|
+
args[:task] = {
|
226
|
+
type: 'ImageToTextTask',
|
227
|
+
body: options[:body64],
|
228
|
+
phrase: options[:phrase],
|
229
|
+
case: options[:case],
|
230
|
+
numeric: options[:numeric],
|
231
|
+
math: options[:math],
|
232
|
+
minLength: options[:min_length],
|
233
|
+
maxLength: options[:max_length]
|
234
|
+
}
|
235
|
+
|
236
|
+
when 'NoCaptchaTaskProxyless'
|
237
|
+
args[:task] = {
|
238
|
+
type: 'NoCaptchaTaskProxyless',
|
239
|
+
websiteURL: options[:website_url],
|
240
|
+
websiteKey: options[:website_key]
|
241
|
+
}
|
242
|
+
|
243
|
+
when 'NoCaptchaTask'
|
244
|
+
args[:task] = {
|
245
|
+
type: 'NoCaptchaTask',
|
246
|
+
websiteURL: options[:website_url],
|
247
|
+
websiteKey: options[:website_key],
|
248
|
+
proxyType: options[:proxy_type],
|
249
|
+
proxyAddress: options[:proxy_address],
|
250
|
+
proxyPort: options[:proxy_port],
|
251
|
+
proxyLogin: options[:proxy_login],
|
252
|
+
proxyPassword: options[:proxy_password],
|
253
|
+
userAgent: options[:user_agent]
|
254
|
+
}
|
255
|
+
|
256
|
+
else
|
257
|
+
message = "Invalid task type: '#{type}'. Allowed types: " +
|
258
|
+
"#{%w(ImageToTextTask NoCaptchaTaskProxyless NoCaptchaTask).join(', ')}"
|
259
|
+
raise AntiCaptcha.raise_error(message)
|
260
|
+
end
|
261
|
+
|
262
|
+
request('createTask', args)
|
263
|
+
end
|
264
|
+
|
265
|
+
#
|
266
|
+
# Creates a task for solving the selected CAPTCHA type.
|
267
|
+
#
|
268
|
+
# @param [String] task_id The ID of the CAPTCHA task.
|
269
|
+
#
|
270
|
+
# @return [Hash] Information about the task.
|
271
|
+
#
|
272
|
+
def get_task_result!(task_id)
|
273
|
+
args = { taskId: task_id }
|
274
|
+
request('getTaskResult', args)
|
275
|
+
end
|
276
|
+
|
277
|
+
#
|
278
|
+
# Retrieves account balance.
|
279
|
+
#
|
280
|
+
# @return [Hash] Information about the account balance.
|
281
|
+
#
|
282
|
+
def get_balance!
|
283
|
+
request('getBalance')
|
284
|
+
end
|
285
|
+
|
286
|
+
#
|
287
|
+
# This method allows you to define if it is a suitable time to upload new
|
288
|
+
# tasks.
|
289
|
+
#
|
290
|
+
# @param [String] queue_id The ID of the queue. Options:
|
291
|
+
# 1 - standart ImageToText, English language.
|
292
|
+
# 2 - standart ImageToText, Russian language.
|
293
|
+
# 5 - Recaptcha NoCaptcha tasks.
|
294
|
+
# 6 - Recaptcha Proxyless task.
|
295
|
+
#
|
296
|
+
# @return [Hash] Information about the queue.
|
297
|
+
#
|
298
|
+
def get_queue_stats!(queue_id)
|
299
|
+
args = { queueId: queue_id }
|
300
|
+
request('getQueueStats', args)
|
301
|
+
end
|
302
|
+
|
303
|
+
#
|
304
|
+
# Complaints are accepted only for image CAPTCHAs. A complaint is checked by
|
305
|
+
# 5 workers, 3 of them must confirm it.
|
306
|
+
#
|
307
|
+
# @param [String] task_id The ID of the CAPTCHA task.
|
308
|
+
#
|
309
|
+
# @return [Hash] Information about the complaint.
|
310
|
+
#
|
311
|
+
def report_incorrect_image_catpcha!(task_id)
|
312
|
+
args = { taskId: task_id }
|
313
|
+
request('getTaskResult', args)
|
314
|
+
end
|
315
|
+
|
316
|
+
#
|
317
|
+
# Private methods.
|
318
|
+
#
|
319
|
+
private
|
320
|
+
|
321
|
+
#
|
322
|
+
# Performs an HTTP request to the Anti Captcha API.
|
323
|
+
#
|
324
|
+
# @param [String] action API method name.
|
325
|
+
# @param [Hash] payload Data to be sent through the HTTP request.
|
326
|
+
#
|
327
|
+
# @return [String] Response from the Anti Captcha API.
|
328
|
+
#
|
329
|
+
def request(action, payload = nil)
|
330
|
+
payload ||= {}
|
331
|
+
|
332
|
+
response = AntiCaptcha::HTTP.post_request(
|
333
|
+
url: BASE_URL.gsub(':action', action),
|
334
|
+
timeout: self.timeout,
|
335
|
+
json_payload: payload.merge(clientKey: self.client_key).to_json
|
336
|
+
)
|
337
|
+
|
338
|
+
response = JSON.load(response)
|
339
|
+
validate_response(response)
|
340
|
+
|
341
|
+
response
|
342
|
+
end
|
343
|
+
|
344
|
+
#
|
345
|
+
# Validates the response from Anti Captcha API.
|
346
|
+
#
|
347
|
+
# @param [Hash] response The response from Anti Captcha API.
|
348
|
+
#
|
349
|
+
# @raise [AntiCaptcha::Error] if Anti Captcha API responds with an error.
|
350
|
+
#
|
351
|
+
def validate_response(response)
|
352
|
+
if response['errorId'].to_i > 0
|
353
|
+
raise AntiCaptcha.raise_error(response['errorId'],
|
354
|
+
response['errorCode'], response['errorDescription'])
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
# Loads a CAPTCHA raw content encoded in base64 from options.
|
359
|
+
#
|
360
|
+
# @param [Hash] options Options hash.
|
361
|
+
# @option options [String] :path File path of the image to be decoded.
|
362
|
+
# @option options [File] :file File instance with image to be decoded.
|
363
|
+
# @option options [String] :body Binary content of the image to bedecoded.
|
364
|
+
# @option options [String] :body64 Binary content encoded in base64 of the
|
365
|
+
# image to be decoded.
|
366
|
+
#
|
367
|
+
# @return [String] The binary image base64 encoded.
|
368
|
+
#
|
369
|
+
def load_captcha(options)
|
370
|
+
if options[:body64]
|
371
|
+
options[:body64]
|
372
|
+
elsif options[:body]
|
373
|
+
Base64.encode64(options[:body])
|
374
|
+
elsif options[:file]
|
375
|
+
Base64.encode64(options[:file].read)
|
376
|
+
elsif options[:path]
|
377
|
+
Base64.encode64(File.open(options[:path], 'rb').read)
|
378
|
+
else
|
379
|
+
raise AntiCaptcha::ArgumentError.new('Illegal image format.')
|
380
|
+
end
|
381
|
+
rescue
|
382
|
+
raise AntiCaptcha::Error
|
383
|
+
end
|
384
|
+
end
|
385
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module AntiCaptcha
|
2
|
+
#
|
3
|
+
# This is the base AntiCaptcha exception class. Rescue it if you want to
|
4
|
+
# catch any exception that might be raised.
|
5
|
+
#
|
6
|
+
class Error < Exception
|
7
|
+
end
|
8
|
+
|
9
|
+
class ArgumentError < Error
|
10
|
+
end
|
11
|
+
|
12
|
+
class Timeout < Error
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.raise_error(error_id, error_code, error_description)
|
16
|
+
message = "ID: #{error_id} | CODE: #{error_code} | #{error_description}"
|
17
|
+
raise AntiCaptcha::Error.new(message)
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module AntiCaptcha
|
2
|
+
#
|
3
|
+
# AntiCaptcha::HTTP exposes common HTTP routines.
|
4
|
+
#
|
5
|
+
class HTTP
|
6
|
+
|
7
|
+
#
|
8
|
+
# Performs a POST HTTP request.
|
9
|
+
# Anti Captcha API supports only POST requests.
|
10
|
+
#
|
11
|
+
# @param [Hash] options Options hash.
|
12
|
+
# @param options [String] url URL to be requested.
|
13
|
+
# @param options [Hash] payload Data to be sent through the HTTP request.
|
14
|
+
# @param options [Integer] timeout HTTP open/read timeout in seconds.
|
15
|
+
#
|
16
|
+
# @return [String] Response body of the HTTP request.
|
17
|
+
#
|
18
|
+
def self.post_request(options = {})
|
19
|
+
uri = URI(options[:url])
|
20
|
+
payload = options[:json_payload] || '{}'
|
21
|
+
timeout = options[:timeout] || 60
|
22
|
+
headers = { 'User-Agent' => AntiCaptcha::USER_AGENT,
|
23
|
+
'Content-Type' => 'application/json' }
|
24
|
+
|
25
|
+
req = Net::HTTP::Post.new(uri.request_uri, headers)
|
26
|
+
req.body = payload
|
27
|
+
|
28
|
+
http = Net::HTTP.new(uri.hostname, uri.port)
|
29
|
+
http.use_ssl = true if (uri.scheme == 'https')
|
30
|
+
http.open_timeout = timeout
|
31
|
+
http.read_timeout = timeout
|
32
|
+
res = http.request(req)
|
33
|
+
res.body
|
34
|
+
|
35
|
+
rescue Net::OpenTimeout, Net::ReadTimeout
|
36
|
+
raise AntiCaptcha::Error.new('Request timed out.')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
metadata
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: anti_captcha
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 2.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Infosimples
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-09-25 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.15'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.15'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
description: TwoCaptcha is an automated CAPTCHA solving service
|
56
|
+
email:
|
57
|
+
- team@infosimples.com.br
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".rspec"
|
64
|
+
- ".travis.yml"
|
65
|
+
- CODE_OF_CONDUCT.md
|
66
|
+
- Gemfile
|
67
|
+
- LICENSE.txt
|
68
|
+
- README.md
|
69
|
+
- Rakefile
|
70
|
+
- anti_captcha.gemspec
|
71
|
+
- bin/console
|
72
|
+
- bin/setup
|
73
|
+
- lib/anti_captcha.rb
|
74
|
+
- lib/anti_captcha/client.rb
|
75
|
+
- lib/anti_captcha/errors.rb
|
76
|
+
- lib/anti_captcha/http.rb
|
77
|
+
- lib/anti_captcha/models/image_to_text_solution.rb
|
78
|
+
- lib/anti_captcha/models/no_captcha_solution.rb
|
79
|
+
- lib/anti_captcha/models/solution.rb
|
80
|
+
- lib/anti_captcha/models/task_result.rb
|
81
|
+
- lib/anti_captcha/version.rb
|
82
|
+
homepage: https://github.com/infosimples/anti_captcha
|
83
|
+
licenses:
|
84
|
+
- MIT
|
85
|
+
metadata: {}
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options: []
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
requirements: []
|
101
|
+
rubyforge_project:
|
102
|
+
rubygems_version: 2.5.1
|
103
|
+
signing_key:
|
104
|
+
specification_version: 4
|
105
|
+
summary: Ruby API for Anti Captcha (CAPTCHA Solver as a Service)
|
106
|
+
test_files: []
|