anti_captcha 2.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 +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: []
|