Repository: sergey-scat/unicaps
Branch: master
Commit: bd24c99ec4e9
Files: 81
Total size: 298.6 KB
Directory structure:
gitextract_x1qvq890/
├── .github/
│ └── workflows/
│ ├── python-package.yml
│ └── python-publish.yml
├── .gitignore
├── LICENSE
├── README.md
├── acceptance_tests.py
├── examples/
│ ├── README.MD
│ ├── async_capy_puzzle.py
│ ├── async_funcaptcha.py
│ ├── async_geetest.py
│ ├── async_geetest_v4.py
│ ├── async_hcaptcha.py
│ ├── async_image.py
│ ├── async_keycaptcha.py
│ ├── async_recaptcha_v2.py
│ ├── async_recaptcha_v2_enterprise.py
│ ├── async_recaptcha_v2_invisible.py
│ ├── async_recaptcha_v3.py
│ ├── async_text.py
│ ├── capy_puzzle.py
│ ├── funcaptcha.py
│ ├── geetest.py
│ ├── geetest_v4.py
│ ├── hcaptcha.py
│ ├── image.py
│ ├── keycaptcha.py
│ ├── recaptcha_v2.py
│ ├── recaptcha_v2_enterprise.py
│ ├── recaptcha_v2_invisible.py
│ ├── recaptcha_v3.py
│ ├── requirements.txt
│ ├── run_all.py
│ └── text.py
├── requirements-dev.txt
├── requirements.txt
├── setup.cfg
├── setup.py
├── tests/
│ ├── _helpers.py
│ ├── conftest.py
│ ├── data/
│ │ ├── __init__.py
│ │ └── data.py
│ ├── test_captcha_base.py
│ ├── test_image_captcha.py
│ ├── test_proxy.py
│ ├── test_service_module.py
│ └── test_solver.py
└── unicaps/
├── __init__.py
├── __version__.py
├── _captcha/
│ ├── __init__.py
│ ├── base.py
│ ├── capy.py
│ ├── funcaptcha.py
│ ├── geetest.py
│ ├── geetest_v4.py
│ ├── hcaptcha.py
│ ├── image.py
│ ├── keycaptcha.py
│ ├── recaptcha_v2.py
│ ├── recaptcha_v3.py
│ ├── text.py
│ └── tiktok.py
├── _misc/
│ ├── __init__.py
│ └── proxy.py
├── _service/
│ ├── __init__.py
│ ├── anti_captcha.py
│ ├── azcaptcha.py
│ ├── base.py
│ ├── captcha_guru.py
│ ├── cptch_net.py
│ ├── deathbycaptcha.py
│ ├── rucaptcha.py
│ └── twocaptcha.py
├── _solver.py
├── _solver_async.py
├── _transport/
│ ├── __init__.py
│ ├── base.py
│ └── http_transport.py
├── captcha.py
├── common.py
├── exceptions.py
└── proxy.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/python-package.yml
================================================
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
name: Python package
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11']
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip setuptools wheel
if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=12 --max-line-length=100 --statistics --per-file-ignores="tests/conftest.py:E402"
- name: Test with pytest
run: |
pytest ./tests -v
================================================
FILE: .github/workflows/python-publish.yml
================================================
# This workflow will upload a Python Package using Twine when a release is created
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
name: Upload Python Package
on:
release:
types: [published]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip setuptools wheel
pip install build
- name: Build package
run: python -m build
- name: Publish package
uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
with:
user: __token__
password: ${{ secrets.PYPI_PASSWORD }}
================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# Wing Pro
*.wpr
*.wpu
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2020 Sergey Totmyanin
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
# Unicaps
[](https://pypi.python.org/pypi/unicaps/)
[](https://pypi.python.org/pypi/unicaps/)
[](https://pypi.python.org/pypi/unicaps/)
[](https://www.codefactor.io/repository/github/sergey-scat/unicaps)
Unicaps is a unified Python API for CAPTCHA solving services.
⚠ **PLEASE NOTE**
⚠ A solving service API key is required to use this package!
⚠ The list of the supported services you can find in the table below.
## Key Features
- A unified Python interface that is independent of the service used
- Uses native service protocol/endpoints (eg, no needs in patching _hosts_ file)
- Has both synchronous and asynchronous client
- Supports 10 types of CAPTCHAs
- Supports 6 CAPTCHA solving services
- Written Pythonic way and is intended for humans
## Installation
```pip install -U unicaps```
## Simple Usage Example
```python
>>> from unicaps import CaptchaSolver, CaptchaSolvingService
>>> solver = CaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, api_key="")
>>> solver.get_balance()
2.84161
>>> solved = solver.solve_image_captcha(open("captcha.jpg", "rb"), is_phrase=False, is_case_sensitive=True)
>>> solved.solution.text
'w93Bx'
>>> solved.cost
0.00078
>>> solved.report_good()
True
```
## Asynchronous Example
```python
import asyncio
from pathlib import Path
from unicaps import AsyncCaptchaSolver, CaptchaSolvingService
API_KEY = ''
async def main():
async with AsyncCaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, API_KEY) as solver:
solved = await solver.solve_image_captcha(
Path("captcha.jpg"),
is_phrase=False,
is_case_sensitive=True
)
print(f'CAPTCHA text: {solved.solution.text}')
await solved.report_good()
if __name__ == '__main__':
asyncio.run(main())
```
## Supported CAPTCHAs / Services
| CAPTCHA➡ \ Service⬇| Image | Text | [reCAPTCHA v2](https://developers.google.com/recaptcha/docs/display) | [reCAPTCHA v3](https://developers.google.com/recaptcha/docs/v3) | [FunCaptcha](https://funcaptcha.com/fc/api/nojs/?pkey=69A21A01-CC7B-B9C6-0F9A-E7FA06677FFC) | [KeyCAPTCHA](https://www.keycaptcha.com/) | [Geetest](https://www.geetest.com/en/demo) | [Geetest v4](https://www.geetest.com/en/demo) | [hCaptcha](https://www.hcaptcha.com/) | [Capy](https://www.capy.me/)
| ------------- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
| [2captcha.com](http://2captcha.com/?from=8754088) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| [anti-captcha.com](http://getcaptchasolution.com/vus77mnl48) | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ |
| [azcaptcha.com](https://azcaptcha.com) | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ |
| [cap.guru](https://cap.guru/ru/reg/?ref=127872) | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ❌ |
| [deathbycaptcha.com](https://www.deathbycaptcha.com/?refid=1236988509) | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ |
| [rucaptcha.com](https://rucaptcha.com?from=9863637) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
### Image CAPTCHA
| Service | Regular | Case Sensitive | Phrase | Numbers only | Letters only | Math | Length | Language | Comment for worker
| ------------- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
| [2captcha.com](http://2captcha.com/?from=8754088) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | Cyrillic/Latin | ✅ |
| [anti-captcha.com](http://getcaptchasolution.com/vus77mnl48) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | Latin | ✅ |
| [azcaptcha.com](https://azcaptcha.com/) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | Latin | ✅ |
| [cap.guru](https://cap.guru/ru/reg/?ref=127872) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | Latin | ✅ |
| [deathbycaptcha.com](http://deathbycaptcha.com/?refid=1236988509) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | Latin | ❌ |
| [rucaptcha.com](https://rucaptcha.com?from=9863637) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | Cyrillic/Latin | ✅ |
### Text CAPTCHA
What is this?
Text Captcha is a type of captcha that is represented as text and doesn't contain images. Usually you have to answer a question to pass the verification.
For example: "If tomorrow is Saturday, what day is today?".
| Service | Language |
| ------------- | :---: |
| [2captcha.com](http://2captcha.com/?from=8754088) | English, Russian |
| [anti-captcha.com](http://getcaptchasolution.com/vus77mnl48) | ❌ |
| [azcaptcha.com](https://azcaptcha.com/) | ❌ |
| [cap.guru](https://cap.guru/ru/reg/?ref=127872) | ❌ |
| [deathbycaptcha.com](http://deathbycaptcha.com/?refid=1236988509) | ❌ |
| [rucaptcha.com](https://rucaptcha.com?from=9863637) | English, Russian |
### reCAPTCHA v2
| Service | Regular | Invisible | Enterprise | Google service1 | Proxy2 | Cookies3 | User-Agent4 |
| ------------- | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
| [2captcha.com](http://2captcha.com/?from=8754088) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| [anti-captcha.com](http://getcaptchasolution.com/vus77mnl48) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| [azcaptcha.com](https://azcaptcha.com/) | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ |
| [cap.guru](https://cap.guru/ru/reg/?ref=127872) | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ |
| [deathbycaptcha.com](http://deathbycaptcha.com/?refid=1236988509) | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ |
| [rucaptcha.com](https://rucaptcha.com?from=9863637) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
1 Support of solving reCAPTCHA on Google services (e.g. Google Search)
2 Support of solving via proxy server
3 Support of passing custom cookies
4 Support of passing custom User-Agent header
### reCAPTCHA v3
| Service | Regular | Enterprise | Proxy | Cookies | User-Agent |
| ------------- | :---: | :---: | :---: | :---: | :---: |
| [2captcha.com](http://2captcha.com/?from=8754088) | ✅ | ✅ | ❌ | ❌ | ❌ |
| [anti-captcha.com](http://getcaptchasolution.com/vus77mnl48) | ✅ | ✅ | ❌ | ❌ | ❌ |
| [azcaptcha.com](https://azcaptcha.com/) | ✅ | ❌ | ✅ | ❌ | ❌ |
| [cap.guru](https://cap.guru/ru/reg/?ref=127872) | ✅ | ❌ | ✅ | ✅ | ✅ |
| [deathbycaptcha.com](http://deathbycaptcha.com/?refid=1236988509) | ✅ | ❌ | ✅ | ❌ | ❌ |
| [rucaptcha.com](https://rucaptcha.com?from=9863637) | ✅ | ✅ | ❌ | ❌ | ❌ |
### FunCaptcha (Arkose Labs)
| Service | Regular | Data (BLOB) | Proxy | Cookies | User-Agent |
| ------------- | :---: | :---: | :---: | :---: | :---: |
| [2captcha.com](http://2captcha.com/?from=8754088) | ✅ | ✅ | ✅ | ❌ | ✅ |
| [anti-captcha.com](http://getcaptchasolution.com/vus77mnl48) | ✅ | ✅ | ✅ | ❌ | ✅ |
| [azcaptcha.com](https://azcaptcha.com/) | ✅ | ✅ | ✅ | ❌ | ✅ |
| [cap.guru](https://cap.guru/ru/reg/?ref=127872) | ❌ | ❌ | ❌ | ❌ | ❌ |
| [deathbycaptcha.com](http://deathbycaptcha.com/?refid=1236988509) | ✅ | ❌ | ✅ | ❌ | ❌ |
| [rucaptcha.com](https://rucaptcha.com?from=9863637) | ✅ | ✅ | ✅ | ❌ | ✅ |
### KeyCAPTCHA
| Service | Regular | Proxy | Cookies | User-Agent |
| ------------- | :---: | :---: | :---: | :---: |
| [2captcha.com](http://2captcha.com/?from=8754088) | ✅ | ❌ | ❌ | ❌ |
| [anti-captcha.com](http://getcaptchasolution.com/vus77mnl48) | ❌ | ❌ | ❌ | ❌ |
| [azcaptcha.com](https://azcaptcha.com/) | ❌ | ❌ | ❌ | ❌ |
| [cap.guru](https://cap.guru/ru/reg/?ref=127872) | ❌ | ❌ | ❌ | ❌ |
| [deathbycaptcha.com](http://deathbycaptcha.com/?refid=1236988509) | ❌ | ❌ | ❌ | ❌ |
| [rucaptcha.com](https://rucaptcha.com?from=9863637) | ✅ | ❌ | ❌ | ❌ |
### Geetest
| Service | Regular | API server | GetLib | Proxy | Cookies | User-Agent |
| ------------- | :---: | :---: | :---: | :---: | :---: | :---: |
| [2captcha.com](http://2captcha.com/?from=8754088) | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ |
| [anti-captcha.com](http://getcaptchasolution.com/vus77mnl48) | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
| [azcaptcha.com](https://azcaptcha.com/) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| [cap.guru](https://captcha.guru/ru/reg/?ref=127872) | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ |
| [deathbycaptcha.com](http://deathbycaptcha.com/?refid=1236988509) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| [rucaptcha.com](https://rucaptcha.com?from=9863637) | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ |
### Geetest v4
| Service | Regular | Proxy | Cookies | User-Agent |
| ------------- | :---: | :---: | :---: | :---: |
| [2captcha.com](http://2captcha.com/?from=8754088) | ✅ | ✅ | ❌ | ✅ |
| [anti-captcha.com](http://getcaptchasolution.com/vus77mnl48) | ✅ | ✅ | ❌ | ✅ |
| [azcaptcha.com](https://azcaptcha.com/) | ❌ | ❌ | ❌ | ❌ |
| [cap.guru](https://cap.guru/ru/reg/?ref=127872) | ❌ | ❌ | ❌ | ❌ |
| [deathbycaptcha.com](http://deathbycaptcha.com/?refid=1236988509) | ❌ | ❌ | ❌ | ❌ |
| [rucaptcha.com](https://rucaptcha.com?from=9863637) | ✅ | ✅ | ❌ | ✅ |
### hCaptcha
| Service | Regular | Invisible | Custom Data | Proxy | Cookies | User-Agent |
| ------------- | :---: | :---: | :---: | :---: | :---: | :---: |
| [2captcha.com](http://2captcha.com/?from=8754088) | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
| [anti-captcha.com](http://getcaptchasolution.com/vus77mnl48) | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ |
| [azcaptcha.com](https://azcaptcha.com/) | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ |
| [cap.guru](https://cap.guru/ru/reg/?ref=127872) | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ |
| [deathbycaptcha.com](http://deathbycaptcha.com/?refid=1236988509) | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ |
| [rucaptcha.com](https://rucaptcha.com?from=9863637) | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
### Capy Puzzle
| Service | Regular | API server | Proxy | Cookies | User-Agent |
| ------------- | :---: | :---: | :---: | :---: | :---: |
| [2captcha.com](http://2captcha.com/?from=8754088) | ✅ | ✅ | ✅ | ❌ | ❌ |
| [anti-captcha.com](http://getcaptchasolution.com/vus77mnl48) | ❌ | ❌ | ❌ | ❌ | ❌ |
| [azcaptcha.com](https://azcaptcha.com/) | ❌ | ❌ | ❌ | ❌ | ❌ |
| [cap.guru](https://cap.guru/ru/reg/?ref=127872) | ❌ | ❌ | ❌ | ❌ | ❌ |
| [deathbycaptcha.com](http://deathbycaptcha.com/?refid=1236988509) | ❌ | ❌ | ❌ | ❌ | ❌ |
| [rucaptcha.com](https://rucaptcha.com?from=9863637) | ✅ | ✅ | ✅ | ❌ | ❌ |
## Supported Proxy types
| Service | HTTP | HTTPS | SOCKS 4 | SOCKS 5 |
| ------------- | :---: | :---: | :---: | :---: |
| [2captcha.com](http://2captcha.com/?from=8754088) | ✅ | ✅ | ✅ | ✅ |
| [anti-captcha.com](http://getcaptchasolution.com/vus77mnl48) | ✅ | ✅ | ✅ | ✅ |
| [azcaptcha.com](https://azcaptcha.com/) | ✅ | ✅ | ✅ | ✅ |
| [cap.guru](https://cap.guru/ru/reg/?ref=127872) | ✅ | ✅ | ✅ | ✅ |
| [deathbycaptcha.com](http://deathbycaptcha.com/?refid=1236988509) | ✅ | ❌ | ❌ | ❌ |
| [rucaptcha.com](https://rucaptcha.com?from=9863637) | ✅ | ✅ | ✅ | ✅ |
## How to...
### Common
Get balance
```python
from unicaps import CaptchaSolver, CaptchaSolvingService
# init captcha solver
with CaptchaSolver(CaptchaSolvingService.ANTI_CAPTCHA, "") as solver:
balance = solver.get_balance()
```
Get service status (is the service is up?)
```python
from unicaps import CaptchaSolver, CaptchaSolvingService
# init captcha solver
with CaptchaSolver(CaptchaSolvingService.ANTI_CAPTCHA, "") as solver:
# get status of the service (True - everything is Okay, False - probably the service is down)
status = solver.get_status()
```
Get technical details after solving
```python
from unicaps import CaptchaSolver, CaptchaSolvingService
# init captcha solver and solve the captcha
with CaptchaSolver(CaptchaSolvingService.ANTI_CAPTCHA, "") as solver:
solved = solver.solve_...(...)
# get cost of the solving
cost = solved.cost
# get cookies (if any)
cookies = solved.cookies
# report good captcha
solved.report_good()
# report bad captcha
solved.report_bad()
# get solving start time
start_time = solved.start_time
# get solving end time
end_time = solved.end_time
```
### CAPTCHAs
Solve Image CAPTCHA
```python
import pathlib
from unicaps import CaptchaSolver, CaptchaSolvingService
from unicaps.common import CaptchaCharType, CaptchaAlphabet
# image file: it can be a Path, file-object or bytes.
image_file = pathlib.Path(r'/tmp/captcha.png')
# init captcha solver
with CaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, "") as solver:
# solve CAPTCHA
solved = solver.solve_image_captcha(
image=image_file,
char_type=CaptchaCharType.ALPHA, # optional
is_phrase=False, # optional
is_case_sensitive=True, # optional
is_math=False, # optional
min_len=4, # optional
max_len=6, # optional
alphabet=CaptchaAlphabet.LATIN, # optional
comment='Type RED letters only' # optional
)
# get CAPTCHA text
token = solved.solution.text
```
Solve reCAPTCHA v2
```python
from unicaps import CaptchaSolver, CaptchaSolvingService
# get page URL and site_key from your page
page_url = ...
site_key = ...
# init captcha solver
with CaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, "") as solver:
# solve CAPTCHA
solved = solver.solve_recaptcha_v2(
site_key=site_key,
page_url=page_url,
data_s='', # optional
api_domain='<"google.com" or "recaptcha.net">' # optional
)
# get response token
token = solved.solution.token
```
Solve reCAPTCHA v2 Invisible
```python
from unicaps import CaptchaSolver, CaptchaSolvingService
# get page url and site_key from your page
page_url = ...
site_key = ...
# init captcha solver
with CaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, "") as solver:
# solve CAPTCHA
solved = solver.solve_recaptcha_v2(
site_key=site_key,
page_url=page_url,
is_invisible=True,
data_s='', # optional
api_domain='<"google.com" or "recaptcha.net">' # optional
)
# get response token
token = solved.solution.token
```
Solve reCAPTCHA v2 Enterprise
```python
from unicaps import CaptchaSolver, CaptchaSolvingService
# get page URL, site_key and data_s from your page
page_url = ...
site_key = ...
data_s = ...
# init captcha solver
with CaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, "") as solver:
# solve CAPTCHA
solved = solver.solve_recaptcha_v2(
site_key=site_key,
page_url=page_url,
is_enterprise=True,
data_s=data_s, # optional
api_domain='<"google.com" or "recaptcha.net">' # optional
)
# get response token
token = solved.solution.token
```
Solve reCAPTCHA v3
```python
from unicaps import CaptchaSolver, CaptchaSolvingService
# get CAPTCHA params from the target page/site
page_url = ...
site_key = ...
action = ...
min_score = 0.7
# init captcha solver
with CaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, "") as solver:
# solve CAPTCHA
solved = solver.solve_recaptcha_v3(
site_key=site_key,
page_url=page_url,
action=action, # optional
min_score=min_score, # optional
api_domain='<"google.com" or "recaptcha.net">' # optional
)
# get response token
token = solved.solution.token
```
Solve reCAPTCHA v3 Enterprise
```python
from unicaps import CaptchaSolver, CaptchaSolvingService
# get CAPTCHA params from the target page/site
page_url = ...
site_key = ...
action = ...
min_score = 0.7
# init captcha solver
with CaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, "") as solver:
# solve CAPTCHA
solved = solver.solve_recaptcha_v3(
site_key=site_key,
page_url=page_url,
is_enterprise=True,
action=action, # optional
min_score=min_score, # optional
api_domain='<"google.com" or "recaptcha.net">' # optional
)
# get response token
token = solved.solution.token
```
Solve hCaptcha
```python
from unicaps import CaptchaSolver, CaptchaSolvingService
# get CAPTCHA params from the target page/site
page_url = ...
site_key = ...
# init captcha solver
with CaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, "") as solver:
# solve CAPTCHA
solved = solver.solve_hcaptcha(
site_key=site_key,
page_url=page_url,
api_domain=<"hcaptcha.com" or "js.hcaptcha.com"> # optional
)
# get response token
token = solved.solution.token
```
Solve hCaptcha Invisible
```python
from unicaps import CaptchaSolver, CaptchaSolvingService
# get CAPTCHA params from the target page/site
page_url = ...
site_key = ...
# init captcha solver
with CaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, "") as solver:
# solve CAPTCHA
solved = solver.solve_hcaptcha(
site_key=site_key,
page_url=page_url,
is_invisible=True,
api_domain=<"hcaptcha.com" or "js.hcaptcha.com"> # optional
)
# get response token
token = solved.solution.token
```
Solve FunCaptcha
```python
from unicaps import CaptchaSolver, CaptchaSolvingService
# get CAPTCHA params from the target page/site
public_key = ...
page_url = ...
# init captcha solver
with CaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, "") as solver:
# solve CAPTCHA
solved = solver.solve_funcaptcha(
public_key=public_key,
page_url=page_url,
service_url='', # optional
blob='' # optional
)
# get response token
token = solved.solution.token
```
Solve KeyCaptcha
```python
from unicaps import CaptchaSolver, CaptchaSolvingService
# get CAPTCHA params from the target page/site
page_url = ...
user_id = ...
session_id = ...
ws_sign = ...
ws_sign2 = ...
# init captcha solver
with CaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, "") as solver:
# solve CAPTCHA
solved = solver.solve_keycaptcha(
page_url=page_url,
user_id=user_id,
session_id=session_id,
ws_sign=ws_sign,
ws_sign2=ws_sign2
)
# get response token
token = solved.solution.token
```
Solve Geetest
```python
from unicaps import CaptchaSolver, CaptchaSolvingService
# get CAPTCHA params from the target page/site
page_url = ...
gt_key = ...
challenge = ...
# init captcha solver
with CaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, "") as solver:
# solve CAPTCHA
solved = solver.solve_geetest(
page_url=page_url,
gt_key=gt_key,
challenge=challenge,
api_server='' # optional
)
# get response token
token = solved.solution.token
```
Solve Geetest v4
```python
from unicaps import CaptchaSolver, CaptchaSolvingService
# get CAPTCHA params from the target page/site
page_url = ...
captcha_id = ...
# init captcha solver
with CaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, "") as solver:
# solve CAPTCHA
solved = solver.solve_geetest_v4(
page_url=page_url,
captcha_id=captcha_id
)
# get solution data
lot_number = solved.solution.lot_number
pass_token = solved.solution.pass_token
gen_time = solved.solution.gen_time
captcha_output = solved.solution.captcha_output
```
Solve Capy Puzzle
```python
from unicaps import CaptchaSolver, CaptchaSolvingService
# get CAPTCHA params from the target page/site
site_key = ...
page_url = ...
# init captcha solver
with CaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, "") as solver:
# solve CAPTCHA
solved = solver.solve_capy_puzzle(
site_key=site_key,
page_url=page_url,
api_server='', # optional
challenge_type='<"puzzle" or "avatar">' # optional
)
# get solution data
captchakey = solved.solution.captchakey
challengekey = solved.solution.challengekey
answer = solved.solution.answer
```
Solve a text CAPTCHA
```python
from unicaps import CaptchaSolver, CaptchaSolvingService
from unicaps.common import CaptchaAlphabet, WorkerLanguage
# init captcha solver
with CaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, "") as solver:
# solve CAPTCHA
solved = solver.solve_text_captcha(
text='Si mañana es domingo, ¿qué día es hoy?',
alphabet=CaptchaAlphabet.LATIN, # optional
language=WorkerLanguage.SPANISH # optional
)
# get answer
answer = solved.solution.text # Sábado
```
### Error handling
Catch exceptions
```python
from unicaps import CaptchaSolver, CaptchaSolvingService, exceptions
# init captcha solver
with CaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, "") as solver:
# solve CAPTCHA
try:
solved = solver.solve_recaptcha_v2(
site_key=site_key,
page_url=page_url
)
except exceptions.AccessDeniedError: # wrong API key or the current IP is banned
pass
except exceptions.LowBalanceError: # low balance
pass
except exceptions.ServiceTooBusy: # no available slots to solve CAPTCHA
pass
except exceptions.SolutionWaitTimeout: # haven't received a solution within N minutes
pass
except exceptions.TooManyRequestsError: # request limit exceeded
pass
except exceptions.BadInputDataError: # bad CAPTCHA data (bad image, wrong URL, etc.)
pass
except exceptions.UnableToSolveError: # CAPTCHA unsolvable
pass
except exceptions.ProxyError: # bad proxy
pass
except exceptions.NetworkError: # network connection error
pass
else:
# get response token
token = solved.solution.token
```
### Misc
Create a task and wait for the result
```python
from unicaps import CaptchaSolver, CaptchaSolvingService
from unicaps.captcha import RecaptchaV2
# get page URL and site_key from your page
page_url = ...
site_key = ...
# init captcha solver
with CaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, "") as solver:
# create a task
task = solver.create_task(
RecaptchaV2(site_key, page_url)
)
# print task ID
print(task.task_id)
# wait for task to be completed
solved = task.wait()
# get response token
token = solved.solution.token
```
Add proxy, cookies and User-Agent
```python
from unicaps import CaptchaSolver, CaptchaSolvingService
from unicaps.proxy import ProxyServer
# get page URL and site_key from your page
page_url = ...
site_key = ...
proxy = 'http://user:password@domain.com:8080'
user_agent = ''
cookies = {'name': 'value', ...}
# init captcha solver
with CaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, "") as solver:
# solve CAPTCHA
solved = solver.solve_recaptcha_v2(
site_key=site_key,
page_url=page_url,
proxy=ProxyServer(proxy),
user_agent=user_agent,
cookies=cookies
)
# get response token
token = solved.solution.token
```
## Real-life code examples
[Examples](https://github.com/sergey-scat/unicaps/tree/master/examples)
## Buy me a coffee
https://www.buymeacoffee.com/sergey.scat
================================================
FILE: acceptance_tests.py
================================================
"""
Acceptance tests
"""
import asyncio
import logging
import os
from importlib import import_module
from unicaps import CaptchaSolver, AsyncCaptchaSolver
from unicaps.__version__ import __version__
logging.basicConfig(level=logging.INFO)
SERVICES = {
'2captcha.com': 'API_KEY_2CAPTCHA',
'anti-captcha.com': 'API_KEY_ANTICAPTCHA',
'azcaptcha.com': 'API_KEY_AZCAPTCHA',
'captcha.guru': 'API_KEY_CAPTCHA_GURU',
'cptch.net': 'API_KEY_CPTCH_NET',
'deathbycaptcha.com': 'API_KEY_DEATHBYCAPTCHA'
}
EXAMPLES = [
'image',
'recaptcha_v2',
'recaptcha_v2_invisible',
'recaptcha_v2_enterprise',
'recaptcha_v3',
'hcaptcha',
'keycaptcha',
'geetest',
'geetest_v4',
'capy_puzzle',
'text',
'funcaptcha'
]
def main():
for service_name, env_var_name in SERVICES.items():
api_key = os.getenv(env_var_name)
if not api_key:
logging.error(
'Unable to read API key for the "%s" service. '
'The environment variable "%s" doesn\'t exist!',
service_name,
env_var_name
)
continue
logging.info('######### Service: %s #########', service_name)
with CaptchaSolver(service_name, api_key) as solver:
status = solver.get_status()
balance = solver.get_balance()
logging.info('Status: %s. Balance: %.2f', 'OK' if status else 'ERROR', balance)
for example_name in EXAMPLES:
logging.info('Current module: %s', example_name)
module = import_module(f'examples.{example_name}')
try:
module.run(solver)
except Exception:
logging.exception('%s run exception', module)
async def async_main():
for service_name, env_var_name in SERVICES.items():
api_key = os.getenv(env_var_name)
if not api_key:
logging.error(
'Unable to read API key for the "%s" service. '
'The environment variable "%s" doesn\'t exist!',
service_name,
env_var_name
)
continue
logging.info('######### Service: %s #########', service_name)
async with AsyncCaptchaSolver(service_name, api_key) as async_solver:
status = await async_solver.get_status()
balance = await async_solver.get_balance()
logging.info(
'Status: %s. Balance: %.2f',
'OK' if status else 'ERROR',
balance
)
for example_name in EXAMPLES:
logging.info('Current module: %s', example_name)
module = import_module(f'examples.async_{example_name}')
try:
await module.run(async_solver)
except Exception:
logging.exception('%s run exception', module)
if __name__ == '__main__':
main()
asyncio.run(async_main(), debug=False)
================================================
FILE: examples/README.MD
================================================
# Real-life examples
Here are some real-life examples of solving captchas, both with a synchronous client and asynchronous.
By default, all examples use 2captcha.com.
## Prerequisites
- The `httpx` (with HTTP/2 support) and `lxml` packages must be installed before running the examples:
```pip install httpx[http2] lxml```
- You must also specify your API key:
- either through an environment variable (`API_KEY_2CAPTCHA`);
- or directly in the code (replace the `` line with the key value).
- For the reCAPTCHA v2 Enterprise examples, you must specify the proxy address (in the format `http://:@:`) using the `HTTP_PROXY_SERVER` environment variable.
## Proxy usage example
See examples on reCAPTCHA v2 Enterprise.
================================================
FILE: examples/async_capy_puzzle.py
================================================
"""
Capy Puzzle CAPTCHA solving example
"""
import asyncio
import os
from urllib.parse import urlparse, parse_qs
import httpx
from lxml import html # type: ignore
from unicaps import AsyncCaptchaSolver, CaptchaSolvingService, exceptions # type: ignore
URL = 'https://www.capy.me/products/puzzle_captcha/'
API_KEY = os.getenv('API_KEY_2CAPTCHA', default='')
USER_AGENT = ('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
'(KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36')
async def main():
""" Init AsyncCaptchaSolver and run the example """
async with AsyncCaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, API_KEY) as solver:
await run(solver)
async def run(solver):
""" Solve Capy Puzzle CAPTCHA """
# create an HTTP2 session
async with httpx.AsyncClient(http2=True,
headers={'User-Agent': USER_AGENT}) as session:
# open page and extract CAPTCHA URL
response = await session.get(URL)
page = html.document_fromstring(response.text)
capy_url = page.xpath('//script[contains(@src, "/puzzle/get_js/")]')[0].attrib['src']
capy_url_parsed = urlparse(capy_url)
api_server = f'{capy_url_parsed.scheme}://{capy_url_parsed.hostname}'
site_key = parse_qs(capy_url_parsed.query)['k'][0]
# solve Capy Puzzle CAPTCHA
try:
solved = await solver.solve_capy_puzzle(
site_key=site_key,
page_url=URL,
api_server=api_server,
user_agent=USER_AGENT
)
except exceptions.UnicapsException as exc:
print(f'Capy Puzzle CAPTCHA solving exception: {str(exc)}')
return False, None
# post solved captcha token
response = await session.post(
URL,
data={
'capy_captchakey': solved.solution.captchakey,
'capy_challengekey': solved.solution.challengekey,
'capy_answer': solved.solution.answer
}
)
page = html.document_fromstring(response.text)
# check the result
if page.xpath('//div[@class="result success"]'):
print('The Capy Puzzle CAPTCHA has been solved correctly!')
# report good CAPTCHA
await solved.report_good()
return True, solved
print('The Capy Puzzle CAPTCHA has not been solved correctly!')
# report bad CAPTCHA
await solved.report_bad()
return False, solved
if __name__ == '__main__':
asyncio.run(main())
================================================
FILE: examples/async_funcaptcha.py
================================================
"""
FunCaptcha solving example
"""
import asyncio
import os
import re
import httpx
from lxml import html # type: ignore
from unicaps import AsyncCaptchaSolver, CaptchaSolvingService, exceptions # type: ignore
URL = 'https://client-demo.arkoselabs.com/solo-animals'
URL_VERIFY = 'https://client-demo.arkoselabs.com/solo-animals/verify'
API_KEY = os.getenv('API_KEY_2CAPTCHA', default='')
async def main():
""" Init AsyncCaptchaSolver and run the example """
async with AsyncCaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, API_KEY) as solver:
await run(solver)
async def run(solver):
""" Load and solve FunCaptcha """
# create an HTTP2 session
async with httpx.AsyncClient(http2=True) as session:
# open page and extract CAPTCHA URL
response = await session.get(URL)
page = html.document_fromstring(response.text)
# extract Public Key and Service URL values
script = page.xpath('//script[contains(text(), "public_key")]')[0].text
regexp = re.search(
r'public_key: ?"([0-9A-Z-]+)",\s*surl: ?"(.*)",',
script
)
public_key = regexp.group(1)
service_url = regexp.group(2)
# solve FunCaptcha
try:
solved = await solver.solve_funcaptcha(
public_key=public_key,
page_url=URL,
service_url=service_url
)
except exceptions.UnicapsException as exc:
print(f'FunCaptcha solving exception: {str(exc)}')
return False, None
# post solved captcha token
response = await session.post(
URL_VERIFY,
data={
'name': 'test',
'verification-token': solved.solution.token,
'fc-token': solved.solution.token
}
)
# check the result
if 'Solved!
' in response.text:
print('The FunCaptcha has been solved correctly!')
# report good CAPTCHA
await solved.report_good()
return True, solved
print('The FunCaptcha has not been solved correctly!')
# report bad CAPTCHA
await solved.report_bad()
return False, solved
if __name__ == '__main__':
asyncio.run(main())
================================================
FILE: examples/async_geetest.py
================================================
"""
GeeTest solving example
"""
import asyncio
import os
import time
import httpx
from unicaps import AsyncCaptchaSolver, CaptchaSolvingService, exceptions # type: ignore
URL = 'https://2captcha.com/demo/geetest'
INIT_PARAMS_URL = 'https://2captcha.com/api/v1/captcha-demo/gee-test/init-params?t={ms}'
URL_VERIFY = 'https://2captcha.com/api/v1/captcha-demo/gee-test/verify'
API_KEY = os.getenv('API_KEY_2CAPTCHA', default='')
async def main():
""" Init AsyncCaptchaSolver and run the example """
async with AsyncCaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, API_KEY) as solver:
await run(solver)
async def run(solver):
""" Solve GeeTest """
# create an HTTP2 session
async with httpx.AsyncClient(http2=True) as session:
# open page and extract CAPTCHA URL
response = await session.get(INIT_PARAMS_URL.format(ms=int(time.time() * 1000)))
init_params = response.json()
# solve GeeTest
try:
solved = await solver.solve_geetest(
page_url=URL,
gt_key=init_params['gt'],
challenge=init_params['challenge']
)
except exceptions.UnicapsException as exc:
print(f'GeeTest solving exception: {str(exc)}')
return False, None
# post solved captcha token
response = await session.post(
URL_VERIFY,
json={
'geetest_challenge': solved.solution.challenge,
'geetest_seccode': solved.solution.seccode,
'geetest_validate': solved.solution.validate
}
)
# check the result
if response.json().get('success'):
print('The GeeTest has been solved correctly!')
# report good CAPTCHA
await solved.report_good()
return True, solved
print('The GeeTest has not been solved correctly!')
# report bad CAPTCHA
await solved.report_bad()
return False, solved
if __name__ == '__main__':
asyncio.run(main())
================================================
FILE: examples/async_geetest_v4.py
================================================
"""
GeeTest v4 solving example
"""
import asyncio
import os
import re
from urllib.parse import urljoin
import httpx
from lxml import html # type: ignore
from unicaps import AsyncCaptchaSolver, CaptchaSolvingService, exceptions # type: ignore
URL = 'https://2captcha.com/demo/geetest-v4'
URL_VERIFY = 'https://2captcha.com/api/v1/captcha-demo/gee-test-v4/verify'
API_KEY = os.getenv('API_KEY_2CAPTCHA', default='')
async def main():
""" Init AsyncCaptchaSolver and run the example """
async with AsyncCaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, API_KEY) as solver:
await run(solver)
async def run(solver):
""" Solve GeeTest v4 """
# create an HTTP2 session
async with httpx.AsyncClient(http2=True) as session:
# open page and extract CAPTCHA URL
response = await session.get(URL)
page = html.document_fromstring(response.text)
captcha_js_url = page.xpath(
'//script[@data-chunk="pages-CaptchaDemo" and '
'starts-with(@src, "/dist/web/pages-CaptchaDemo.")]'
)[0].attrib['src']
captcha_js_url = urljoin(URL, captcha_js_url)
# load pages-CaptchaDemo js-file and extract captcha ID
response = await session.get(captcha_js_url)
regexp = re.search(
r'window\.initGeetest4\(\{\s*captchaId:\s?"([0-9a-z]+)"',
response.text
)
# solve GeeTest v4
try:
solved = await solver.solve_geetest_v4(
page_url=URL,
captcha_id=regexp.group(1)
)
except exceptions.UnicapsException as exc:
print(f'GeeTestV4 solving exception: {str(exc)}')
return False, None
# post solved captcha token
response = await session.post(
URL_VERIFY,
json={
'captcha_id': solved.solution.captcha_id,
'lot_number': solved.solution.lot_number,
'pass_token': solved.solution.pass_token,
'gen_time': solved.solution.gen_time,
'captcha_output': solved.solution.captcha_output
}
)
# check the result
if response.json().get('result') == 'success':
print('The GeeTest v4 has been solved correctly!')
# report good CAPTCHA
await solved.report_good()
return True, solved
print('The GeeTest v4 has not been solved correctly!')
# report bad CAPTCHA
await solved.report_bad()
return False, solved
if __name__ == '__main__':
asyncio.run(main())
================================================
FILE: examples/async_hcaptcha.py
================================================
# -*- coding: UTF-8 -*-
"""
hCaptcha solving example
"""
import asyncio
import os
import httpx
from lxml import html # type: ignore
from unicaps import AsyncCaptchaSolver, CaptchaSolvingService, exceptions # type: ignore
URL = 'https://accounts.hcaptcha.com/demo'
API_KEY = os.getenv('API_KEY_2CAPTCHA', default='')
async def main():
""" Init AsyncCaptchaSolver and run the example """
async with AsyncCaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, API_KEY) as solver:
await run(solver)
async def run(solver):
""" Solve hCaptcha """
# make a session and go to URL
async with httpx.AsyncClient(http2=True) as session:
response = await session.get(URL)
# parse page and get site-key
page = html.document_fromstring(response.text)
site_key = page.cssselect('.h-captcha')[0].attrib['data-sitekey']
# parse form data
page_form_data = page.xpath('//form//input')
form_data = {}
for input_data in page_form_data:
if input_data.xpath('@name'):
form_data[input_data.xpath('@name')[0]] = next(
iter(input_data.xpath('@value')),
None
)
# solve hCaptcha
try:
solved = await solver.solve_hcaptcha(
site_key=site_key,
page_url=URL
)
except exceptions.UnicapsException as exc:
print(f'hCaptcha solving exception: {str(exc)}')
return False, None
# add token to form data
form_data['h-captcha-response'] = solved.solution.token
form_data['g-recaptcha-response'] = solved.solution.token
# post form data
response = await session.post(URL, data=form_data)
page = html.document_fromstring(response.text)
# check the result
if page.xpath('//div[text()="Verification Success!"]'):
print('The hCaptcha has been solved correctly!')
# report good CAPTCHA
await solved.report_good()
return True, solved
print('The hCaptcha has not been solved correctly!')
# report bad CAPTCHA
await solved.report_bad()
return False, solved
if __name__ == '__main__':
asyncio.run(main())
================================================
FILE: examples/async_image.py
================================================
# -*- coding: UTF-8 -*-
"""
Image CAPTCHA solving example
"""
import asyncio
import os
import httpx
from lxml import html # type: ignore
from unicaps import AsyncCaptchaSolver, CaptchaSolvingService, exceptions # type: ignore
from unicaps.common import CaptchaCharType, CaptchaAlphabet # type: ignore
URL = 'https://democaptcha.com/demo-form-eng/image.html'
API_KEY = os.getenv('API_KEY_2CAPTCHA', default='')
async def main():
""" Init AsyncCaptchaSolver and run the example """
async with AsyncCaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, API_KEY) as solver:
await run(solver)
async def run(solver):
""" Solve image CAPTCHA """
# make a session and go to URL
async with httpx.AsyncClient(http2=True) as session:
response = await session.get(URL)
# parse page and get captcha URL
page = html.document_fromstring(response.text)
# fetch captcha image
captcha_url = page.cssselect('#htest_image')[0].attrib['src']
captcha_response = await session.get(captcha_url)
# solve the captcha
try:
solved = await solver.solve_image_captcha(
image=captcha_response.content, # binary image data
char_type=CaptchaCharType.ALPHANUMERIC, # consists of alphanumeric characters
is_phrase=False, # not a phrase (no whitespaces)
is_case_sensitive=True, # case-sensitive text
is_math=False, # no calculation needed
alphabet=CaptchaAlphabet.LATIN # latin alphabet being used
)
except exceptions.UnicapsException as exc:
print(f'Image CAPTCHA solving exception: {str(exc)}')
return False, None
form_data = {}
# parse form data
page_form_data = page.xpath('//form//input')
for input_data in page_form_data:
if input_data.xpath('@name'):
form_data[str(input_data.xpath('@name')[0])] = str(
next(iter(input_data.xpath('@value')), None)
)
form_data['message'] = 'test'
# add token to form data
form_data['vericode'] = solved.solution.text
# post form data and parse result page
response = await session.post(URL, data=form_data)
page = html.document_fromstring(response.text)
# check the result
if page.xpath('//h2[text()="Your message has been sent (actually not), thank you!"]'):
print('The Image CAPTCHA has been solved correctly!')
# report good CAPTCHA
await solved.report_good()
return True, solved
print('The Image CAPTCHA has not been solved correctly!')
# report bad CAPTCHA
await solved.report_bad()
return False, solved
if __name__ == '__main__':
asyncio.run(main())
================================================
FILE: examples/async_keycaptcha.py
================================================
"""
KeyCaptcha solving example
"""
import asyncio
import os
import re
from urllib.parse import urljoin
import httpx
from lxml import html # type: ignore
from unicaps import AsyncCaptchaSolver, CaptchaSolvingService, exceptions # type: ignore
URL = 'https://2captcha.com/demo/keycaptcha'
URL_VERIFY = 'https://2captcha.com/api/v1/captcha-demo/key-captcha/verify'
API_KEY = os.getenv('API_KEY_2CAPTCHA', default='')
async def main():
""" Init AsyncCaptchaSolver and run the example """
async with AsyncCaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, API_KEY) as solver:
await run(solver)
async def run(solver):
""" Get and solve KeyCaptcha """
# create an HTTP2 session
async with httpx.AsyncClient(http2=True) as session:
# open page and extract CAPTCHA URL
response = await session.get(URL)
page = html.document_fromstring(response.text)
# extract captcha URL, parse page and get captcha params
captcha_url = page.xpath('//iframe[@name="key-captcha-widget"]')[0].attrib['src']
captcha_url = urljoin(URL, captcha_url)
response = await session.get(captcha_url)
captcha_page = html.document_fromstring(response.text)
script = captcha_page.xpath('//script[contains(text(), "var s_s_c_user_id")]')[0].text
def extract_var_value(var_name):
return re.search(fr"var {var_name} = '(.+)';", script).group(1)
s_s_c_user_id = extract_var_value('s_s_c_user_id')
s_s_c_session_id = extract_var_value('s_s_c_session_id')
s_s_c_web_server_sign = extract_var_value('s_s_c_web_server_sign')
s_s_c_web_server_sign2 = extract_var_value('s_s_c_web_server_sign2')
# solve KeyCaptcha
try:
solved = await solver.solve_keycaptcha(
page_url=URL,
user_id=s_s_c_user_id,
session_id=s_s_c_session_id,
ws_sign=s_s_c_web_server_sign,
ws_sign2=s_s_c_web_server_sign2
)
except exceptions.UnicapsException as exc:
print(f'KeyCaptcha solving exception: {str(exc)}')
return False, None
# post solved captcha token
response = await session.post(
URL_VERIFY,
json={'capcode': solved.solution.token}
)
# check the result
if response.json().get('success'):
print('The KeyCaptcha has been solved correctly!')
# report good CAPTCHA
await solved.report_good()
return True, solved
print('The KeyCaptcha has not been solved correctly!')
# report bad CAPTCHA
await solved.report_bad()
return False, solved
if __name__ == '__main__':
asyncio.run(main())
================================================
FILE: examples/async_recaptcha_v2.py
================================================
"""
reCAPTCHA v2 async solving example
"""
import asyncio
import os
import httpx
from lxml import html # type: ignore
from unicaps import AsyncCaptchaSolver, CaptchaSolvingService, exceptions # type: ignore
URL = 'https://recaptcha-demo.appspot.com/recaptcha-v2-checkbox.php'
API_KEY = os.getenv('API_KEY_2CAPTCHA', default='')
async def main():
""" Init AsyncCaptchaSolver and run the example """
async with AsyncCaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, API_KEY) as solver:
await run(solver)
async def run(solver):
""" Get and solve reCAPTCHA v2 """
# make a session and go to URL
async with httpx.AsyncClient(http2=True) as session:
response = await session.get(URL)
# parse page and get site-key
page = html.document_fromstring(response.text)
site_key = page.cssselect('.g-recaptcha')[0].attrib['data-sitekey']
# parse form data
page_form_data = page.xpath('//form//input')
form_data = {}
for input_data in page_form_data:
form_data[input_data.xpath('@name')[0]] = input_data.xpath('@value')[0]
# solve reCAPTCHA
try:
solved = await solver.solve_recaptcha_v2(
site_key=site_key,
page_url=URL
)
except exceptions.UnicapsException as exc:
print(f'reCAPTCHA v2 solving exception: {str(exc)}')
return False, None
# add token to form data
form_data['g-recaptcha-response'] = solved.solution.token
# post form data
response = await session.post(URL, data=form_data)
page = html.document_fromstring(response.text)
# check the result
if page.xpath('//h2[text()="Success!"]'):
print('The reCAPTCHA v2 has been solved correctly!')
# report good CAPTCHA
await solved.report_good()
return True, solved
print('The reCAPTCHA v2 has not been solved correctly!')
# report bad CAPTCHA
await solved.report_bad()
return False, solved
if __name__ == '__main__':
asyncio.run(main())
================================================
FILE: examples/async_recaptcha_v2_enterprise.py
================================================
# -*- coding: UTF-8 -*-
"""
reCAPTCHA v2 Enterprise solving example
"""
import asyncio
import os
import random
import string
import httpx
from unicaps import AsyncCaptchaSolver, CaptchaSolvingService, exceptions # type: ignore
from unicaps.proxy import ProxyServer # type: ignore
URL = 'https://store.steampowered.com/join'
URL_REFRESH_CAPTCHA = 'https://store.steampowered.com/join/refreshcaptcha/'
URL_VERIFY_EMAIL = 'https://store.steampowered.com/join/ajaxverifyemail'
API_KEY = os.getenv('API_KEY_2CAPTCHA', default='')
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0'
PROXY = os.getenv(
'HTTP_PROXY_SERVER',
default='http://:@:'
)
def get_random_word(length):
""" Generate a random word of a given length """
letters = string.ascii_lowercase + string.digits
return ''.join(random.choice(letters) for i in range(length))
async def main():
""" Init AsyncCaptchaSolver and run the example """
async with AsyncCaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, API_KEY) as solver:
await run(solver)
async def run(solver):
""" Get and solve CAPTCHA """
# make a session, update headers and proxies
async with httpx.AsyncClient(proxies=PROXY) as session:
session.headers.update({
'User-Agent': USER_AGENT,
})
# open the "Join" page just to get session cookies
response = await session.get(URL)
# get reCAPTCHA params
response = await session.post(
URL_REFRESH_CAPTCHA,
data=dict(count=1)
)
captcha_params = response.json()
# solve reCAPTCHA
try:
solved = await solver.solve_recaptcha_v2(
site_key=captcha_params['sitekey'],
page_url=URL,
data_s=captcha_params['s'],
is_enterprise=True,
proxy=ProxyServer(PROXY),
user_agent=USER_AGENT,
cookies=dict(session.cookies)
)
except exceptions.UnicapsException as exc:
print(f'reCAPTCHA v2 Enterprise solving exception: {str(exc)}')
return False, None
# generate email address
email = f'random_{get_random_word(10)}@gmail.com'
# verify email
response = await session.post(
URL_VERIFY_EMAIL,
data=dict(
email=email,
captchagid=captcha_params['gid'],
captcha_text=solved.solution.token,
elang=0
)
)
response_data = response.json()
print(f"Email: {email}\nResult: {response_data['details']}")
# check the result
if 'the CAPTCHA appears to be invalid' not in response_data['details']:
print('The reCAPTCHA v2 Enterprise has been solved correctly!')
# report good CAPTCHA
await solved.report_good()
return True, solved
print('The reCAPTCHA v2 Enterprise has not been solved correctly!')
# report bad CAPTCHA
await solved.report_bad()
return False, solved
if __name__ == '__main__':
asyncio.run(main())
================================================
FILE: examples/async_recaptcha_v2_invisible.py
================================================
# -*- coding: UTF-8 -*-
"""
reCAPTCHA v2 invisible solving example
"""
import asyncio
import os
import httpx
from lxml import html # type: ignore
from unicaps import AsyncCaptchaSolver, CaptchaSolvingService, exceptions # type: ignore
URL = 'https://recaptcha-demo.appspot.com/recaptcha-v2-invisible.php'
API_KEY = os.getenv('API_KEY_2CAPTCHA', default='')
async def main():
""" Init AsyncCaptchaSolver and run the example """
async with AsyncCaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, API_KEY) as solver:
await run(solver)
async def run(solver):
""" Get and solve reCAPTCHA v2 invisible """
# make a session and go to URL
async with httpx.AsyncClient(http2=True) as session:
response = await session.get(URL)
# parse page and get site-key
page = html.document_fromstring(response.text)
site_key = page.cssselect('.g-recaptcha')[0].attrib['data-sitekey']
# parse form data
page_form_data = page.xpath('//form//input')
form_data = {}
for input_data in page_form_data:
form_data[input_data.xpath('@name')[0]] = input_data.xpath('@value')[0]
# solve reCAPTCHA
try:
solved = await solver.solve_recaptcha_v2(
site_key=site_key,
page_url=URL,
is_invisible=True
)
except exceptions.UnicapsException as exc:
print(f'reCAPTCHA v2 (invisible) solving exception: {str(exc)}')
return False, None
# add token to form data
form_data['g-recaptcha-response'] = solved.solution.token
# post form data
response = await session.post(URL, data=form_data)
page = html.document_fromstring(response.text)
# check the result
if page.xpath('//h2[text()="Success!"]'):
print('The Invisible reCAPTCHA v2 has been solved correctly!')
# report good CAPTCHA
await solved.report_good()
return True, solved
print('The Invisible reCAPTCHA v2 has not been solved correctly!')
# report bad CAPTCHA
await solved.report_bad()
return False, solved
if __name__ == '__main__':
asyncio.run(main())
================================================
FILE: examples/async_recaptcha_v3.py
================================================
# -*- coding: UTF-8 -*-
"""
reCAPTCHA v3 solving example
"""
import asyncio
import os
import re
from pprint import pprint
import httpx
from unicaps import AsyncCaptchaSolver, CaptchaSolvingService, exceptions # type: ignore
URL = 'https://recaptcha-demo.appspot.com/recaptcha-v3-request-scores.php'
VERIFY_URL = ('https://recaptcha-demo.appspot.com/recaptcha-v3-verify.php'
'?action={action}&token={token}')
MIN_SCORE = 0.7
API_KEY = os.getenv('API_KEY_2CAPTCHA', default='')
async def main():
""" Init AsyncCaptchaSolver and run the example """
async with AsyncCaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, API_KEY) as solver:
await run(solver)
async def run(solver):
""" Get and solve CAPTCHA """
# make a session and go to URL
async with httpx.AsyncClient(http2=True) as session:
response = await session.get(URL)
# extract site-key and action from the page source using regular expression
regexp = re.search(
r"grecaptcha.execute\('([a-zA-Z0-9\-_]{40})', ?\{action: ?'(.*)'\}\)'",
response.text
)
site_key = regexp.group(1)
action = regexp.group(2)
# solve reCAPTCHA
try:
solved = await solver.solve_recaptcha_v3(
site_key=site_key,
page_url=URL,
action=action,
min_score=MIN_SCORE
)
except exceptions.UnicapsException as exc:
print(f'reCAPTCHA v3 solving exception: {str(exc)}')
return False, None
# verify token and print the result
response = await session.get(
VERIFY_URL.format(
action=action,
token=solved.solution.token
)
)
result = response.json()
pprint(result)
# check the result
if not result['score']:
print('The reCAPTCHA v3 has not been solved correctly! Error codes: ' + ', '.join(
result['error-codes']))
# report bad CAPTCHA
await solved.report_bad()
return False, solved
if result['score'] < MIN_SCORE:
print(
f'Solved reCAPTCHA v3 score ({result["score"]}) is less than requested ({MIN_SCORE})!'
)
# report bad CAPTCHA
await solved.report_bad()
return False, solved
print('The reCAPTCHA v3 has been solved correctly!')
# report good CAPTCHA
await solved.report_good()
return True, solved
if __name__ == '__main__':
asyncio.run(main())
================================================
FILE: examples/async_text.py
================================================
"""
TextCaptcha solving example
"""
import asyncio
import os
from unicaps import AsyncCaptchaSolver, CaptchaSolvingService, exceptions # type: ignore
from unicaps.common import WorkerLanguage # type: ignore
API_KEY = os.getenv('API_KEY_2CAPTCHA', default='')
TEXT_CAPTCHA = 'If tomorrow is Sunday, what day is today?'
TEXT_CAPTCHA_ANSWER = 'saturday'
async def main():
""" Init AsyncCaptchaSolver and run the example """
async with AsyncCaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, API_KEY) as solver:
await run(solver)
async def run(solver):
""" TextCaptcha solving func """
# solve TextCaptcha
try:
solved = await solver.solve_text_captcha(
text=TEXT_CAPTCHA,
language=WorkerLanguage.ENGLISH
)
except exceptions.UnicapsException as exc:
print(f'TextCaptcha solving exception: {str(exc)}')
return False, None
# check the result
if solved.solution.text.lower() == TEXT_CAPTCHA_ANSWER:
print('The TextCaptcha has been solved correctly!')
# report good CAPTCHA
await solved.report_good()
return True, solved
print('The TextCaptcha has not been solved correctly!')
# report bad CAPTCHA
await solved.report_bad()
return False, solved
if __name__ == '__main__':
asyncio.run(main())
================================================
FILE: examples/capy_puzzle.py
================================================
"""
Capy Puzzle CAPTCHA solving example
"""
import os
from urllib.parse import urlparse, parse_qs
import httpx
from lxml import html # type: ignore
from unicaps import CaptchaSolver, CaptchaSolvingService, exceptions # type: ignore
URL = 'https://www.capy.me/products/puzzle_captcha/'
API_KEY = os.getenv('API_KEY_2CAPTCHA', default='')
USER_AGENT = ('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
'(KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36')
def run(solver):
""" Solve Capy Puzzle CAPTCHA """
# create an HTTP2 session
with httpx.Client(http2=True, headers={'User-Agent': USER_AGENT}) as session:
# open page and extract CAPTCHA URL
response = session.get(URL)
page = html.document_fromstring(response.text)
capy_url = page.xpath('//script[contains(@src, "/puzzle/get_js/")]')[0].attrib['src']
capy_url_parsed = urlparse(capy_url)
api_server = f'{capy_url_parsed.scheme}://{capy_url_parsed.hostname}'
site_key = parse_qs(capy_url_parsed.query)['k'][0]
# solve Capy Puzzle CAPTCHA
try:
solved = solver.solve_capy_puzzle(
site_key=site_key,
page_url=URL,
api_server=api_server,
user_agent=USER_AGENT
)
except exceptions.UnicapsException as exc:
print(f'Capy Puzzle CAPTCHA solving exception: {str(exc)}')
return False, None
# post solved captcha token
response = session.post(
URL,
data={
'capy_captchakey': solved.solution.captchakey,
'capy_challengekey': solved.solution.challengekey,
'capy_answer': solved.solution.answer
}
)
page = html.document_fromstring(response.text)
# check the result
if page.xpath('//div[@class="result success"]'):
print('The Capy Puzzle CAPTCHA has been solved correctly!')
# report good CAPTCHA
solved.report_good()
return True, solved
print('The Capy Puzzle CAPTCHA has not been solved correctly!')
# report bad CAPTCHA
solved.report_bad()
return False, solved
if __name__ == '__main__':
with CaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, API_KEY) as captcha_solver:
run(captcha_solver)
================================================
FILE: examples/funcaptcha.py
================================================
"""
FunCaptcha solving example
"""
import os
import re
import httpx
from lxml import html # type: ignore
from unicaps import CaptchaSolver, CaptchaSolvingService, exceptions # type: ignore
URL = 'https://client-demo.arkoselabs.com/solo-animals'
URL_VERIFY = 'https://client-demo.arkoselabs.com/solo-animals/verify'
API_KEY = os.getenv('API_KEY_2CAPTCHA', default='')
def run(solver):
""" Load and solve FunCaptcha """
# create an HTTP2 session
with httpx.Client(http2=True) as session:
# open page and extract CAPTCHA URL
response = session.get(URL)
page = html.document_fromstring(response.text)
# extract Public Key and Service URL values
script = page.xpath('//script[contains(text(), "public_key")]')[0].text
regexp = re.search(
r'public_key: ?"([0-9A-Z-]+)",\s*surl: ?"(.*)",',
script
)
public_key = regexp.group(1)
service_url = regexp.group(2)
# solve FunCaptcha
try:
solved = solver.solve_funcaptcha(
public_key=public_key,
page_url=URL,
service_url=service_url
)
except exceptions.UnicapsException as exc:
print(f'FunCaptcha solving exception: {str(exc)}')
return False, None
# post solved captcha token
response = session.post(
URL_VERIFY,
data={
'name': 'test',
'verification-token': solved.solution.token,
'fc-token': solved.solution.token
}
)
# check the result
if 'Solved!
' in response.text:
print('The FunCaptcha has been solved correctly!')
# report good CAPTCHA
solved.report_good()
return True, solved
print('The FunCaptcha has not been solved correctly!')
# report bad CAPTCHA
solved.report_bad()
return False, solved
if __name__ == '__main__':
with CaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, API_KEY) as captcha_solver:
run(captcha_solver)
================================================
FILE: examples/geetest.py
================================================
"""
GeeTest solving example
"""
import os
import time
import httpx
from unicaps import CaptchaSolver, CaptchaSolvingService, exceptions # type: ignore
URL = 'https://2captcha.com/demo/geetest'
INIT_PARAMS_URL = 'https://2captcha.com/api/v1/captcha-demo/gee-test/init-params?t={ms}'
URL_VERIFY = 'https://2captcha.com/api/v1/captcha-demo/gee-test/verify'
API_KEY = os.getenv('API_KEY_2CAPTCHA', default='')
def run(solver):
""" Solve GeeTest """
# create an HTTP2 session
with httpx.Client(http2=True) as session:
# open page and extract CAPTCHA URL
response = session.get(INIT_PARAMS_URL.format(ms=int(time.time() * 1000)))
init_params = response.json()
# solve GeeTest
try:
solved = solver.solve_geetest(
page_url=URL,
gt_key=init_params['gt'],
challenge=init_params['challenge']
)
except exceptions.UnicapsException as exc:
print(f'GeeTest solving exception: {str(exc)}')
return False, None
# post solved captcha token
response = session.post(
URL_VERIFY,
json={
'geetest_challenge': solved.solution.challenge,
'geetest_seccode': solved.solution.seccode,
'geetest_validate': solved.solution.validate
}
)
# check the result
if response.json().get('success'):
print('The GeeTest has been solved correctly!')
# report good CAPTCHA
solved.report_good()
return True, solved
print('The GeeTest has not been solved correctly!')
# report bad CAPTCHA
solved.report_bad()
return False, solved
if __name__ == '__main__':
with CaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, API_KEY) as captcha_solver:
run(captcha_solver)
================================================
FILE: examples/geetest_v4.py
================================================
"""
GeeTest solving example
"""
import os
import re
from urllib.parse import urljoin
import httpx
from lxml import html # type: ignore
from unicaps import CaptchaSolver, CaptchaSolvingService, exceptions # type: ignore
URL = 'https://2captcha.com/demo/geetest-v4'
URL_VERIFY = 'https://2captcha.com/api/v1/captcha-demo/gee-test-v4/verify'
API_KEY = os.getenv('API_KEY_2CAPTCHA', default='')
def run(solver):
""" Solve GeeTest v4 """
# create an HTTP2 session
with httpx.Client(http2=True) as session:
# open page and extract pages-CaptchaDemo URL
response = session.get(URL)
page = html.document_fromstring(response.text)
captcha_js_url = page.xpath(
'//script[@data-chunk="pages-CaptchaDemo" and '
'starts-with(@src, "/dist/web/pages-CaptchaDemo.")]'
)[0].attrib['src']
captcha_js_url = urljoin(URL, captcha_js_url)
# load pages-CaptchaDemo js-file and extract captcha ID
response = session.get(captcha_js_url)
regexp = re.search(
r'window\.initGeetest4\(\{\s*captchaId:\s?"([0-9a-z]+)"',
response.text
)
# solve GeeTest v4
try:
solved = solver.solve_geetest_v4(
page_url=URL,
captcha_id=regexp.group(1)
)
except exceptions.UnicapsException as exc:
print(f'GeeTestV4 solving exception: {str(exc)}')
return False, None
# post solved captcha token
response = session.post(
URL_VERIFY,
json={
'captcha_id': solved.solution.captcha_id,
'lot_number': solved.solution.lot_number,
'pass_token': solved.solution.pass_token,
'gen_time': solved.solution.gen_time,
'captcha_output': solved.solution.captcha_output
}
)
# check the result
if response.json().get('result') == 'success':
print('The GeeTest v4 has been solved correctly!')
# report good CAPTCHA
solved.report_good()
return True, solved
print('The GeeTest v4 has not been solved correctly!')
# report bad CAPTCHA
solved.report_bad()
return False, solved
if __name__ == '__main__':
with CaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, API_KEY) as captcha_solver:
run(captcha_solver)
================================================
FILE: examples/hcaptcha.py
================================================
# -*- coding: UTF-8 -*-
"""
hCaptcha solving example
"""
import os
import httpx
from lxml import html # type: ignore
from unicaps import CaptchaSolver, CaptchaSolvingService, exceptions # type: ignore
URL = 'https://accounts.hcaptcha.com/demo'
API_KEY = os.getenv('API_KEY_2CAPTCHA', default='')
def run(solver):
""" Solve hCaptcha """
# make a session and go to URL
with httpx.Client(http2=True) as session:
response = session.get(URL)
# parse page and get site-key
page = html.document_fromstring(response.text)
site_key = page.cssselect('.h-captcha')[0].attrib['data-sitekey']
# parse form data
page_form_data = page.xpath('//form//input')
form_data = {}
for input_data in page_form_data:
if input_data.xpath('@name'):
form_data[input_data.xpath('@name')[0]] = next(
iter(input_data.xpath('@value')),
None
)
# solve hCaptcha
try:
solved = solver.solve_hcaptcha(
site_key=site_key,
page_url=URL
)
except exceptions.UnicapsException as exc:
print(f'hCaptcha solving exception: {str(exc)}')
return False, None
# add token to form data
form_data['h-captcha-response'] = solved.solution.token
form_data['g-recaptcha-response'] = solved.solution.token
# post form data
response = session.post(URL, data=form_data)
page = html.document_fromstring(response.text)
# check the result
if page.xpath('//div[text()="Verification Success!"]'):
print('The hCaptcha has been solved correctly!')
# report good CAPTCHA
solved.report_good()
return True, solved
print('The hCaptcha has not been solved correctly!')
# report bad CAPTCHA
solved.report_bad()
return False, solved
if __name__ == '__main__':
with CaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, API_KEY) as captcha_solver:
run(captcha_solver)
================================================
FILE: examples/image.py
================================================
# -*- coding: UTF-8 -*-
"""
Image CAPTCHA solving example
"""
import os
import httpx
from lxml import html # type: ignore
from unicaps import CaptchaSolver, CaptchaSolvingService, exceptions # type: ignore
from unicaps.common import CaptchaCharType, CaptchaAlphabet # type: ignore
URL = 'https://democaptcha.com/demo-form-eng/image.html'
API_KEY = os.getenv('API_KEY_2CAPTCHA', default='')
def run(solver):
""" Solve image CAPTCHA """
# make a session and go to URL
with httpx.Client(http2=True) as session:
response = session.get(URL)
response.raise_for_status()
# parse page and get captcha URL
page = html.document_fromstring(response.text)
# fetch captcha image
captcha_url = page.cssselect('#htest_image')[0].attrib['src']
captcha_response = session.get(captcha_url)
captcha_response.raise_for_status()
# solve the captcha
try:
solved = solver.solve_image_captcha(
image=captcha_response.content, # binary image data
char_type=CaptchaCharType.ALPHANUMERIC, # consists of alphanumeric characters
is_phrase=False, # not a phrase (no whitespaces)
is_case_sensitive=True, # case-sensitive text
is_math=False, # no calculation needed
alphabet=CaptchaAlphabet.LATIN # latin alphabet being used
)
except exceptions.UnicapsException as exc:
print(f'Image CAPTCHA solving exception: {str(exc)}')
return False, None
form_data = {}
# parse form data
page_form_data = page.xpath('//form//input')
for input_data in page_form_data:
if input_data.xpath('@name'):
form_data[str(input_data.xpath('@name')[0])] = str(
next(iter(input_data.xpath('@value')), None)
)
form_data['message'] = 'test'
# add token to form data
form_data['vericode'] = solved.solution.text
# post form data and parse result page
response = session.post(URL, data=form_data)
response.raise_for_status()
page = html.document_fromstring(response.text)
# check the result
if page.xpath('//h2[text()="Your message has been sent (actually not), thank you!"]'):
print('The Image CAPTCHA has been solved correctly!')
# report good CAPTCHA
solved.report_good()
return True, solved
print('The Image CAPTCHA has not been solved correctly!')
# report bad CAPTCHA
solved.report_bad()
return False, solved
if __name__ == '__main__':
with CaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, API_KEY) as captcha_solver:
run(captcha_solver)
================================================
FILE: examples/keycaptcha.py
================================================
"""
KeyCaptcha solving example
"""
import os
import re
from urllib.parse import urljoin
import httpx
from lxml import html # type: ignore
from unicaps import CaptchaSolver, CaptchaSolvingService, exceptions # type: ignore
URL = 'https://2captcha.com/demo/keycaptcha'
URL_VERIFY = 'https://2captcha.com/api/v1/captcha-demo/key-captcha/verify'
API_KEY = os.getenv('API_KEY_2CAPTCHA', default='')
def run(solver):
""" Get and solve KeyCaptcha """
# create an HTTP2 session
with httpx.Client(http2=True) as session:
# open page and extract CAPTCHA URL
response = session.get(URL)
page = html.document_fromstring(response.text)
# extract captcha URL, parse page and get captcha params
captcha_url = page.xpath('//iframe[@name="key-captcha-widget"]')[0].attrib['src']
captcha_url = urljoin(URL, captcha_url)
response = session.get(captcha_url)
captcha_page = html.document_fromstring(response.text)
script = captcha_page.xpath('//script[contains(text(), "var s_s_c_user_id")]')[0].text
def extract_var_value(var_name):
return re.search(fr"var {var_name} = '(.+)';", script).group(1)
s_s_c_user_id = extract_var_value('s_s_c_user_id')
s_s_c_session_id = extract_var_value('s_s_c_session_id')
s_s_c_web_server_sign = extract_var_value('s_s_c_web_server_sign')
s_s_c_web_server_sign2 = extract_var_value('s_s_c_web_server_sign2')
# solve KeyCaptcha
try:
solved = solver.solve_keycaptcha(
page_url=URL,
user_id=s_s_c_user_id,
session_id=s_s_c_session_id,
ws_sign=s_s_c_web_server_sign,
ws_sign2=s_s_c_web_server_sign2
)
except exceptions.UnicapsException as exc:
print(f'KeyCaptcha solving exception: {str(exc)}')
return False, None
# post solved captcha token
response = session.post(
URL_VERIFY,
json={'capcode': solved.solution.token}
)
# check the result
if response.json().get('success'):
print('The KeyCaptcha has been solved correctly!')
# report good CAPTCHA
solved.report_good()
return True, solved
print('The KeyCaptcha has not been solved correctly!')
# report bad CAPTCHA
solved.report_bad()
return False, solved
if __name__ == '__main__':
with CaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, API_KEY) as captcha_solver:
run(captcha_solver)
================================================
FILE: examples/recaptcha_v2.py
================================================
# -*- coding: UTF-8 -*-
"""
reCAPTCHA v2 solving example
"""
import os
import httpx
from lxml import html # type: ignore
from unicaps import CaptchaSolver, CaptchaSolvingService, exceptions # type: ignore
URL = 'https://recaptcha-demo.appspot.com/recaptcha-v2-checkbox.php'
API_KEY = os.getenv('API_KEY_2CAPTCHA', default='')
def run(solver):
""" Get and solve reCAPTCHA v2 """
# make a session and go to URL
with httpx.Client(http2=True) as session:
response = session.get(URL)
# parse page and get data-sitekey value
page = html.document_fromstring(response.text)
site_key = page.cssselect('.g-recaptcha')[0].attrib['data-sitekey']
# parse form data
page_form_data = page.xpath('//form//input')
form_data = {}
for input_data in page_form_data:
form_data[input_data.xpath('@name')[0]] = input_data.xpath('@value')[0]
# solve reCAPTCHA
try:
solved = solver.solve_recaptcha_v2(
site_key=site_key,
page_url=URL
)
except exceptions.UnicapsException as exc:
print(f'reCAPTCHA v2 solving exception: {str(exc)}')
return False, None
# add token to form data
form_data['g-recaptcha-response'] = solved.solution.token
# post form data
response = session.post(URL, data=form_data)
page = html.document_fromstring(response.text)
# check the result
if page.xpath('//h2[text()="Success!"]'):
print('The reCAPTCHA v2 has been solved correctly!')
# report good CAPTCHA
solved.report_good()
return True, solved
print('The reCAPTCHA v2 has not been solved correctly!')
# report bad CAPTCHA
solved.report_bad()
return False, solved
if __name__ == '__main__':
with CaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, API_KEY) as captcha_solver:
run(captcha_solver)
================================================
FILE: examples/recaptcha_v2_enterprise.py
================================================
# -*- coding: UTF-8 -*-
"""
reCAPTCHA v2 Enterprise solving example
"""
import os
import random
import string
from urllib.parse import urlparse
import httpx
from lxml import html # type: ignore
from unicaps import CaptchaSolver, CaptchaSolvingService, exceptions # type: ignore
from unicaps.proxy import ProxyServer # type: ignore
URL = 'https://store.steampowered.com/join'
URL_REFRESH_CAPTCHA = 'https://store.steampowered.com/join/refreshcaptcha/'
URL_VERIFY_EMAIL = 'https://store.steampowered.com/join/ajaxverifyemail'
API_KEY = os.getenv('API_KEY_2CAPTCHA', default='')
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0'
PROXY = os.getenv(
'HTTP_PROXY_SERVER',
default='http://:@:'
)
def get_random_word(length):
""" Generate a random word of a given length """
letters = string.ascii_lowercase + string.digits
return ''.join(random.choice(letters) for i in range(length))
def run(solver):
""" Get and solve CAPTCHA """
# make a session, update headers and proxies
with httpx.Client(proxies=PROXY) as session:
session.headers.update({
'User-Agent': USER_AGENT,
})
# open the "Join" page
response = session.get(URL)
response.raise_for_status()
# parse the page, then find and extract reCAPTCHA domain
page = html.document_fromstring(response.text)
recaptcha_url = page.xpath(
'//script[contains(@src, "recaptcha/enterprise.js")]')[0].attrib['src']
recaptcha_domain = urlparse(recaptcha_url).netloc
# get reCAPTCHA params
captcha_params = session.post(
URL_REFRESH_CAPTCHA,
data=dict(count=1)
).json()
# solve reCAPTCHA
try:
solved = solver.solve_recaptcha_v2(
site_key=captcha_params['sitekey'],
page_url=URL,
is_enterprise=True,
data_s=captcha_params['s'],
api_domain=recaptcha_domain,
proxy=ProxyServer(PROXY),
user_agent=USER_AGENT
)
except exceptions.UnicapsException as exc:
print(f'reCAPTCHA v2 Enterprise solving exception: {str(exc)}')
return False, None
# generate email address
email = f'random_{get_random_word(10)}@gmail.com'
# verify email
response = session.post(
URL_VERIFY_EMAIL,
data=dict(
email=email,
captchagid=captcha_params['gid'],
captcha_text=solved.solution.token,
elang=0
)
)
response.raise_for_status()
response_data = response.json()
print(f"Email: {email}\nResult: {response_data['details']}")
# check the result
if 'the CAPTCHA appears to be invalid' not in response_data['details']:
print('The reCAPTCHA v2 Enterprise has been solved correctly!')
# report good CAPTCHA
solved.report_good()
return True, solved
print('The reCAPTCHA v2 Enterprise has not been solved correctly!')
# report bad CAPTCHA
solved.report_bad()
return False, solved
if __name__ == '__main__':
with CaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, API_KEY) as captcha_solver:
run(captcha_solver)
================================================
FILE: examples/recaptcha_v2_invisible.py
================================================
# -*- coding: UTF-8 -*-
"""
reCAPTCHA v2 invisible solving example
"""
import os
import httpx
from lxml import html # type: ignore
from unicaps import CaptchaSolver, CaptchaSolvingService, exceptions # type: ignore
URL = 'https://recaptcha-demo.appspot.com/recaptcha-v2-invisible.php'
API_KEY = os.getenv('API_KEY_2CAPTCHA', default='')
def run(solver):
""" Get and solve reCAPTCHA v2 invisible """
# make a session and go to URL
with httpx.Client(http2=True) as session:
response = session.get(URL)
# parse page and get site-key
page = html.document_fromstring(response.text)
site_key = page.cssselect('.g-recaptcha')[0].attrib['data-sitekey']
# parse form data
page_form_data = page.xpath('//form//input')
form_data = {}
for input_data in page_form_data:
form_data[input_data.xpath('@name')[0]] = input_data.xpath('@value')[0]
# solve reCAPTCHA
try:
solved = solver.solve_recaptcha_v2(
site_key=site_key,
page_url=URL,
is_invisible=True
)
except exceptions.UnicapsException as exc:
print(f'reCAPTCHA v2 (invisible) solving exception: {str(exc)}')
return False, None
# add token to form data
form_data['g-recaptcha-response'] = solved.solution.token
# post form data
response = session.post(URL, data=form_data)
page = html.document_fromstring(response.text)
# check the result
if page.xpath('//h2[text()="Success!"]'):
print('The Invisible reCAPTCHA v2 has been solved correctly!')
# report good CAPTCHA
solved.report_good()
return True, solved
print('The Invisible reCAPTCHA v2 has not been solved correctly!')
# report bad CAPTCHA
solved.report_bad()
return False, solved
if __name__ == '__main__':
with CaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, API_KEY) as captcha_solver:
run(captcha_solver)
================================================
FILE: examples/recaptcha_v3.py
================================================
# -*- coding: UTF-8 -*-
"""
reCAPTCHA v3 solving example
"""
import os
import re
from pprint import pprint
import httpx
from unicaps import CaptchaSolver, CaptchaSolvingService, exceptions # type: ignore
URL = 'https://recaptcha-demo.appspot.com/recaptcha-v3-request-scores.php'
VERIFY_URL = ('https://recaptcha-demo.appspot.com/recaptcha-v3-verify.php'
'?action={action}&token={token}')
MIN_SCORE = 0.7
API_KEY = os.getenv('API_KEY_2CAPTCHA', default='')
def run(solver):
""" Get and solve CAPTCHA """
# make a session and go to URL
with httpx.Client(http2=True) as session:
response = session.get(URL)
# extract site-key and action from the page source using regular expression
regexp = re.search(
r"grecaptcha.execute\('([a-zA-Z0-9\-_]{40})', ?\{action: ?'(.*)'\}\)'",
response.text
)
site_key = regexp.group(1)
action = regexp.group(2)
# solve reCAPTCHA
try:
solved = solver.solve_recaptcha_v3(
site_key=site_key,
page_url=URL,
action=action,
min_score=MIN_SCORE
)
except exceptions.UnicapsException as exc:
print(f'reCAPTCHA v3 solving exception: {str(exc)}')
return False, None
# verify token and print the result
response = session.get(
VERIFY_URL.format(
action=action,
token=solved.solution.token
)
)
result = response.json()
pprint(result)
# check the result
if not result['score']:
print(
'The reCAPTCHA v3 has not been solved correctly! Error codes: ' + ', '.join(
result['error-codes'])
)
# report bad CAPTCHA
solved.report_bad()
return False, solved
if result['score'] < MIN_SCORE:
print(
f'Solved reCAPTCHA v3 score ({result["score"]}) is less than requested ({MIN_SCORE})!'
)
# report bad CAPTCHA
solved.report_bad()
return False, solved
print('The reCAPTCHA v3 has been solved correctly!')
# report good CAPTCHA
solved.report_good()
return True, solved
if __name__ == '__main__':
with CaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, API_KEY) as captcha_solver:
run(captcha_solver)
================================================
FILE: examples/requirements.txt
================================================
httpx[http2]
lxml
================================================
FILE: examples/run_all.py
================================================
# -*- coding: UTF-8 -*-
"""
All examples runner
"""
from importlib import import_module
import logging
import os
from unicaps import CaptchaSolver # type: ignore
# services dict: key is a name of CAPTCHA solving service, value is an env variable containing
# the API key
SERVICES = {
'2captcha.com': 'API_KEY_2CAPTCHA',
'anti-captcha.com': 'API_KEY_ANTICAPTCHA',
'azcaptcha.com': 'API_KEY_AZCAPTCHA',
'captcha.guru': 'API_KEY_CAPTCHA_GURU',
'cptch.net': 'API_KEY_CPTCH_NET'
}
# list of modules containing CAPTCHA solving examples
EXAMPLES = [
'image',
'recaptcha_v2',
'recaptcha_v2_invisible',
'recaptcha_v2_enterprise',
'recaptcha_v3',
'hcaptcha',
'keycaptcha',
'geetest',
'geetest_v4',
'capy_puzzle',
'text',
'funcaptcha'
]
logging.basicConfig(level=logging.DEBUG)
if __name__ == '__main__':
for service_name, env_var_name in SERVICES.items():
api_key = os.getenv(env_var_name)
print(f'######### Service: {service_name} #########')
# init captcha solver
with CaptchaSolver(service_name, api_key) as solver:
for example_name in EXAMPLES:
print(example_name)
module = import_module(example_name)
module.run(solver)
print()
================================================
FILE: examples/text.py
================================================
"""
TextCaptcha solving example
"""
import os
from unicaps import CaptchaSolver, CaptchaSolvingService, exceptions # type: ignore
from unicaps.common import WorkerLanguage # type: ignore
API_KEY = os.getenv('API_KEY_2CAPTCHA', default='')
TEXT_CAPTCHA = 'If tomorrow is Sunday, what day is today?'
TEXT_CAPTCHA_ANSWER = 'saturday'
def run(solver):
""" Solve TextCaptcha """
# solve TextCaptcha
try:
solved = solver.solve_text_captcha(
text=TEXT_CAPTCHA,
language=WorkerLanguage.ENGLISH
)
except exceptions.UnicapsException as exc:
print(f'TextCaptcha solving exception: {str(exc)}')
return False, None
# check the result
if solved.solution.text.lower() == TEXT_CAPTCHA_ANSWER:
print('The TextCaptcha has been solved correctly!')
# report good CAPTCHA
solved.report_good()
return True, solved
print('The TextCaptcha has not been solved correctly!')
# report bad CAPTCHA
solved.report_bad()
return False, solved
if __name__ == '__main__':
with CaptchaSolver(CaptchaSolvingService.TWOCAPTCHA, API_KEY) as captcha_solver:
run(captcha_solver)
================================================
FILE: requirements-dev.txt
================================================
flake8>=3.8.3
pytest>=6.0.1
tox>=3.17.1
================================================
FILE: requirements.txt
================================================
httpx>=0.22.0
enforce-typing
================================================
FILE: setup.cfg
================================================
[pycodestyle]
ignore = E701
max-line-length = 100
[pylint.checkers]
max-line-length = 100
max-args = 8
max-attributes = 12
[pylint.messages_control]
disable = E0402,R0903,W0236
================================================
FILE: setup.py
================================================
import setuptools
with open("README.md", "r", encoding="utf-8") as fh:
long_description = fh.read()
setuptools.setup(
name="unicaps",
version="1.3.0",
author="Sergey Scat",
author_email="py.unicaps@gmail.com",
description="Universal CAPTCHA Solver for humans",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/sergey-scat/unicaps",
packages=setuptools.find_packages(),
install_requires=["httpx>=0.22.0", "enforce-typing>=1.0.0"],
classifiers=[
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11"
],
python_requires='>=3.7',
)
================================================
FILE: tests/_helpers.py
================================================
"""
Helpers
"""
import collections.abc
def dict_update(src_dict, upd_dict):
""" Updates nested dict recursively """
for key, value in upd_dict.items():
if isinstance(value, collections.abc.Mapping):
src_dict[key] = dict_update(src_dict.get(key, {}), value)
else:
src_dict[key] = value
return src_dict
================================================
FILE: tests/conftest.py
================================================
# -*- coding: UTF-8 -*-
import enum
import importlib
import os.path
import random
import string
import sys
from pytest import fixture
cd = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(1, cd)
from unicaps._captcha import CaptchaType
@fixture()
def services():
pass
@fixture()
def captcha_service():
pass
def _gen_random_word(length):
letters = string.ascii_lowercase
return ''.join(random.choice(letters) for i in range(length))
def _get_random_value(field):
cls = getattr(field.type, '__args__', [field.type])[0]
if cls is str:
return _gen_random_word(10)
if cls is bytes:
return _gen_random_word(32).encode('utf-8')
if cls is int:
return random.randint(1, 255)
if cls is bool:
return bool(random.randint(0, 1))
if issubclass(cls, enum.Enum):
return getattr(cls, cls._member_names_[0])
return cls()
@fixture(scope="module", params=CaptchaType)
def captcha_class(request):
captcha_package = importlib.import_module('unicaps.captcha')
return getattr(captcha_package, request.param.value)
@fixture(scope="module")
def captcha_instance(captcha_class):
# return random_dataclass_init(captcha_class)
params = captcha_class.__dataclass_fields__.copy()
for name, field in params.items():
params[name] = _get_random_value(field)
# for ImageCaptcha
if name == 'image':
params[name] = params[name][:6] + b'Exif' + params[name][10:]
return captcha_class(**params)
================================================
FILE: tests/data/__init__.py
================================================
================================================
FILE: tests/data/data.py
================================================
# -*- coding: UTF-8 -*-
"""
Test data
"""
import base64
import json
import os.path
import pathlib
from random import choice
from unittest import mock
from unicaps import CaptchaSolvingService, exceptions as exc # type: ignore
from unicaps.captcha import ( # type: ignore
CaptchaType, ImageCaptcha, RecaptchaV2, RecaptchaV3, FunCaptcha, TextCaptcha,
KeyCaptcha, GeeTest, GeeTestV4, HCaptcha, CapyPuzzle, TikTokCaptcha
)
from unicaps.common import CaptchaAlphabet, CaptchaCharType, WorkerLanguage # type: ignore
from unicaps.proxy import ProxyServer # type: ignore
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
API_KEY = 'test'
IMAGE_FILE_PATH = os.path.join(CURRENT_DIR, 'image.jpg')
IMAGE_FILE_PATHLIB = pathlib.Path(IMAGE_FILE_PATH)
IMAGE_FILE_FILEOBJECT = open(IMAGE_FILE_PATH, 'rb')
with open(IMAGE_FILE_PATH, 'rb') as f:
IMAGE_FILE_BYTES = f.read()
IMAGE_FILE_BASE64 = base64.b64encode(IMAGE_FILE_BYTES)
IMAGE_FILE_BASE64_STR = IMAGE_FILE_BASE64.decode('ascii')
PROXY_ADDRESS = 'http://login:password@example.com:8080'
PROXY_OBJ = ProxyServer(PROXY_ADDRESS)
PROXY_TYPE = 'HTTP'
COOKIES = {'cookie1': 'value1', 'cookie2': 'value2'}
INPUT_TEST_DATA_FOR_TASK_PREPARE_FUNC = {
1: (ImageCaptcha(IMAGE_FILE_BYTES), None, None, None),
2: (ImageCaptcha(IMAGE_FILE_PATHLIB), None, None, None),
3: (ImageCaptcha(IMAGE_FILE_FILEOBJECT, is_phrase=True), None, None, None),
4: (ImageCaptcha(IMAGE_FILE_BYTES, is_case_sensitive=True), None, None, None),
5: (ImageCaptcha(IMAGE_FILE_BYTES, char_type=CaptchaCharType.ALPHA), None, None, None),
6: (ImageCaptcha(IMAGE_FILE_BYTES, is_math=True), None, None, None),
7: (ImageCaptcha(IMAGE_FILE_BYTES, min_len=1), None, None, None),
8: (ImageCaptcha(IMAGE_FILE_BYTES, max_len=10), None, None, None),
9: (ImageCaptcha(IMAGE_FILE_BYTES, alphabet=CaptchaAlphabet.LATIN), None, None, None),
10: (ImageCaptcha(IMAGE_FILE_BYTES, language=WorkerLanguage.ENGLISH), None, None, None),
11: (ImageCaptcha(IMAGE_FILE_BYTES, comment='test'), None, None, None),
12: (RecaptchaV2('test1', 'test2'), None, None, None),
13: (RecaptchaV2('test1', 'test2', is_invisible=True), None, None, None),
14: (RecaptchaV2('test1', 'test2', data_s='test3'), None, None, None),
15: (RecaptchaV3('test1', 'test2'), None, None, None),
16: (RecaptchaV3('test1', 'test2', action='test3'), None, None, None),
17: (RecaptchaV3('test1', 'test2', min_score=0.9), None, None, None),
18: (FunCaptcha('test1', 'test2'), None, None, None),
19: (FunCaptcha('test1', 'test2', service_url='test3'), None, None, None),
20: (FunCaptcha('test1', 'test2', no_js=True), None, None, None),
21: (TextCaptcha('test1'), None, None, None),
22: (TextCaptcha('test1', alphabet=CaptchaAlphabet.LATIN), None, None, None),
23: (TextCaptcha('test1', language=WorkerLanguage.ENGLISH), None, None, None),
24: (KeyCaptcha('test1', 'test2', 'test3', 'test4', 'test5'), None, None, None),
25: (GeeTest('test1', 'test2', 'test3'), None, None, None),
26: (GeeTest('test1', 'test2', 'test3', api_server='test4'), None, None, None),
27: (HCaptcha('test1', 'test2'), None, None, None),
28: (CapyPuzzle('test1', 'test2', 'test3'), None, None, None),
29: (TikTokCaptcha('test1'), None, None, COOKIES),
30: (RecaptchaV2('test1', 'test2'), PROXY_OBJ, None, None),
31: (RecaptchaV2('test1', 'test2'), PROXY_OBJ, 'User-Agent1', None),
32: (RecaptchaV2('test1', 'test2'), PROXY_OBJ, 'User-Agent1', COOKIES),
33: (RecaptchaV2('test1', 'test2', data_s="test3", is_enterprise=True), None, None, None),
34: (RecaptchaV3('test1', 'test2', is_enterprise=True), None, None, None),
35: (TikTokCaptcha('test1', aid=1459), None, None, COOKIES),
36: (TikTokCaptcha('test1', host='test2'), None, None, COOKIES),
37: (TikTokCaptcha('https://www.tiktok.com/login/phone-or-email/email'), None, None, COOKIES),
38: (TikTokCaptcha('https://ads.tiktok.com/i18n/signup'), None, None, COOKIES),
39: (GeeTestV4('test1', 'test2'), None, None, None),
40: (RecaptchaV2('test1', 'test2', api_domain='recaptcha.net'), None, None, None),
41: (RecaptchaV3('test1', 'test2', api_domain='recaptcha.net'), None, None, None),
42: (FunCaptcha('test1', 'test2', blob='test3'), None, None, None),
}
BASE_TASK_REQUEST_DATA = {
CaptchaSolvingService.TWOCAPTCHA: dict(
method='POST',
url='https://2captcha.com/in.php',
headers={'Accept': 'application/json'},
data=dict(key=API_KEY, json=1, soft_id=2738)
),
CaptchaSolvingService.RUCAPTCHA: dict(
method='POST',
url='https://rucaptcha.com/in.php',
headers={'Accept': 'application/json'},
data=dict(key=API_KEY, json=1, soft_id=2738)
),
CaptchaSolvingService.ANTI_CAPTCHA: dict(
method='POST',
json=dict(clientKey=API_KEY, softId=940),
headers={'Accept': 'application/json'},
url='https://api.anti-captcha.com/createTask'
),
CaptchaSolvingService.AZCAPTCHA: dict(
method='POST',
url='http://azcaptcha.com/in.php',
data=dict(key=API_KEY, json=1)
),
CaptchaSolvingService.CPTCH_NET: dict(
method='POST',
url='https://cptch.net/in.php',
data=dict(key=API_KEY, json=1, soft_id="164")
),
CaptchaSolvingService.DEATHBYCAPTCHA: dict(
method='POST',
headers={'Accept': 'application/json'},
url='http://api.dbcapi.me/api/captcha',
data=dict(authtoken=API_KEY)
)
}
OUTPUT_TEST_DATA_FOR_TASK_PREPARE_FUNC = {
CaptchaSolvingService.TWOCAPTCHA: {
1: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR)},
2: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR)},
3: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR, phrase=1)},
4: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR, regsense=1)},
5: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR, numeric=2)},
6: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR, calc=1)},
7: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR, min_len=1)},
8: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR, max_len=10)},
9: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR, language=2)},
10: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR, lang='en')},
11: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR, textinstructions='test')},
12: {'data': dict(method='userrecaptcha', googlekey='test1', pageurl='test2', invisible=0)},
13: {'data': dict(method='userrecaptcha', googlekey='test1', pageurl='test2', invisible=1)},
14: {'data': {'method': 'userrecaptcha', 'googlekey': 'test1', 'pageurl': 'test2',
'invisible': 0, 'data-s': 'test3'}},
15: {'data': dict(method='userrecaptcha', version='v3', googlekey='test1',
pageurl='test2')},
16: {'data': dict(method='userrecaptcha', version='v3', googlekey='test1', pageurl='test2',
action='test3')},
17: {'data': dict(method='userrecaptcha', version='v3', googlekey='test1', pageurl='test2',
min_score=0.9)},
18: {'data': dict(method='funcaptcha', publickey='test1', pageurl='test2')},
19: {'data': dict(method='funcaptcha', publickey='test1', pageurl='test2', surl='test3')},
20: {'data': dict(method='funcaptcha', publickey='test1', pageurl='test2', nojs=1)},
21: {'data': dict(textcaptcha='test1')},
22: {'data': dict(textcaptcha='test1', language=2)},
23: {'data': dict(textcaptcha='test1', lang='en')},
24: {'data': dict(method='keycaptcha', pageurl='test1', s_s_c_user_id='test2',
s_s_c_session_id='test3', s_s_c_web_server_sign='test4',
s_s_c_web_server_sign2='test5')},
25: {'data': dict(method='geetest', pageurl='test1', gt='test2', challenge='test3')},
26: {'data': dict(method='geetest', pageurl='test1', gt='test2', challenge='test3',
api_server='test4')},
27: {'data': dict(method='hcaptcha', sitekey='test1', pageurl='test2', invisible=0)},
28: {'data': dict(method='capy', captchakey='test1', pageurl='test2', api_server='test3')},
29: {'data': dict(method='tiktok', pageurl='test1', aid=None, host=None,
cookies='cookie1:value1;cookie2:value2')},
30: {'data': dict(method='userrecaptcha', googlekey='test1', pageurl='test2', invisible=0,
proxy=PROXY_ADDRESS.split('://', maxsplit=1)[1],
proxytype=PROXY_TYPE)},
31: {'data': dict(method='userrecaptcha', googlekey='test1', pageurl='test2', invisible=0,
proxy=PROXY_ADDRESS.split('://', maxsplit=1)[1],
proxytype=PROXY_TYPE,
userAgent='User-Agent1')},
32: {'data': dict(method='userrecaptcha', googlekey='test1', pageurl='test2', invisible=0,
proxy=PROXY_ADDRESS.split('://', maxsplit=1)[1],
proxytype=PROXY_TYPE,
userAgent='User-Agent1',
cookies='cookie1:value1;cookie2:value2')},
33: {'data': {'method': 'userrecaptcha', 'googlekey': 'test1', 'pageurl': 'test2',
'invisible': 0, 'data-s': 'test3', 'enterprise': 1}},
34: {'data': dict(method='userrecaptcha', version='v3', googlekey='test1', pageurl='test2',
enterprise=1)},
35: {'data': dict(method='tiktok', pageurl='test1', aid=1459, host=None,
cookies='cookie1:value1;cookie2:value2')},
36: {'data': dict(method='tiktok', pageurl='test1', aid=None, host='test2',
cookies='cookie1:value1;cookie2:value2')},
37: {'data': dict(method='tiktok',
pageurl='https://www.tiktok.com/login/phone-or-email/email',
aid=1459, host='https://www-useast1a.tiktok.com',
cookies='cookie1:value1;cookie2:value2')},
38: {'data': dict(method='tiktok', pageurl='https://ads.tiktok.com/i18n/signup',
aid=1583, host='https://verify-sg.byteoversea.com',
cookies='cookie1:value1;cookie2:value2')},
39: {'data': dict(method='geetest_v4 ', pageurl='test1', captcha_id='test2')},
40: {'data': dict(method='userrecaptcha', googlekey='test1', pageurl='test2',
invisible=0, domain='recaptcha.net')},
41: {'data': dict(method='userrecaptcha', version='v3', googlekey='test1', pageurl='test2',
domain='recaptcha.net')},
42: {'data': {'method': 'funcaptcha', 'publickey': 'test1', 'pageurl': 'test2',
'data[blob]': 'test3'}},
},
CaptchaSolvingService.ANTI_CAPTCHA: {
1: {'json': dict(task=dict(type='ImageToTextTask', body=IMAGE_FILE_BASE64_STR))},
2: {'json': dict(task=dict(type='ImageToTextTask', body=IMAGE_FILE_BASE64_STR))},
3: {'json': dict(task=dict(type='ImageToTextTask', body=IMAGE_FILE_BASE64_STR,
phrase=True))},
4: {'json': dict(task=dict(type='ImageToTextTask', body=IMAGE_FILE_BASE64_STR, case=True))},
5: {'json': dict(task=dict(type='ImageToTextTask', body=IMAGE_FILE_BASE64_STR, numeric=2))},
6: {'json': dict(task=dict(type='ImageToTextTask', body=IMAGE_FILE_BASE64_STR, math=True))},
7: {'json': dict(task=dict(type='ImageToTextTask', body=IMAGE_FILE_BASE64_STR,
minLength=1))},
8: {'json': dict(task=dict(type='ImageToTextTask', body=IMAGE_FILE_BASE64_STR,
maxLength=10))},
9: {'json': dict(task=dict(type='ImageToTextTask', body=IMAGE_FILE_BASE64_STR))},
10: {'json': dict(task=dict(type='ImageToTextTask', body=IMAGE_FILE_BASE64_STR),
languagePool='en')},
11: {'json': dict(task=dict(type='ImageToTextTask', body=IMAGE_FILE_BASE64_STR,
comment='test'))},
12: {'json': dict(task=dict(type='NoCaptchaTaskProxyless', websiteKey='test1',
websiteURL='test2', isInvisible=False))},
13: {'json': dict(task=dict(type='NoCaptchaTaskProxyless', websiteKey='test1',
websiteURL='test2', isInvisible=True))},
14: {'json': dict(task=dict(type='NoCaptchaTaskProxyless', websiteKey='test1',
websiteURL='test2', isInvisible=False,
recaptchaDataSValue='test3'))},
15: {'json': dict(task=dict(type='RecaptchaV3TaskProxyless', websiteKey='test1',
websiteURL='test2'))},
16: {'json': dict(task=dict(type='RecaptchaV3TaskProxyless', websiteKey='test1',
websiteURL='test2', pageAction='test3'))},
17: {'json': dict(task=dict(type='RecaptchaV3TaskProxyless', websiteKey='test1',
websiteURL='test2', minScore=0.9))},
18: {'json': dict(task=dict(type='FunCaptchaTaskProxyless', websitePublicKey='test1',
websiteURL='test2'))},
19: {'json': dict(task=dict(type='FunCaptchaTaskProxyless', websitePublicKey='test1',
websiteURL='test2', funcaptchaApiJSSubdomain='test3'))},
20: {'json': dict(task=dict(type='FunCaptchaTaskProxyless', websitePublicKey='test1',
websiteURL='test2'))},
21: None,
22: None,
23: None,
24: None,
25: {'json': dict(task=dict(type='GeeTestTaskProxyless', websiteURL='test1',
gt='test2', challenge='test3'))},
26: {'json': dict(task=dict(type='GeeTestTaskProxyless', websiteURL='test1',
gt='test2', challenge='test3',
geetestApiServerSubdomain='test4'))},
27: {'json': dict(task=dict(type='HCaptchaTaskProxyless', websiteKey='test1',
websiteURL='test2', isInvisible=False))},
28: None,
29: None,
30: {'json': dict(task=dict(type='NoCaptchaTask', websiteKey='test1',
websiteURL='test2', isInvisible=False,
proxyType=PROXY_OBJ.proxy_type.value,
proxyAddress=PROXY_OBJ.get_ip_address(),
proxyPort=PROXY_OBJ.port,
proxyLogin=PROXY_OBJ.login,
proxyPassword=PROXY_OBJ.password))},
31: {'json': dict(task=dict(type='NoCaptchaTask', websiteKey='test1',
websiteURL='test2', isInvisible=False,
proxyType=PROXY_OBJ.proxy_type.value,
proxyAddress=PROXY_OBJ.get_ip_address(),
proxyPort=PROXY_OBJ.port,
proxyLogin=PROXY_OBJ.login,
proxyPassword=PROXY_OBJ.password,
userAgent='User-Agent1'))},
32: {'json': dict(task=dict(type='NoCaptchaTask', websiteKey='test1',
websiteURL='test2', isInvisible=False,
proxyType=PROXY_OBJ.proxy_type.value,
proxyAddress=PROXY_OBJ.get_ip_address(),
proxyPort=PROXY_OBJ.port,
proxyLogin=PROXY_OBJ.login,
proxyPassword=PROXY_OBJ.password,
userAgent='User-Agent1',
cookies='cookie1=value1; cookie2=value2'))},
33: {'json': dict(task=dict(type='RecaptchaV2EnterpriseTaskProxyless', websiteKey='test1',
websiteURL='test2', isInvisible=False,
enterprisePayload=dict(s='test3')))},
34: {'json': dict(task=dict(type='RecaptchaV3TaskProxyless', websiteKey='test1',
websiteURL='test2', isEnterprise=True))},
35: None,
36: None,
37: None,
38: None,
39: {'json': dict(task=dict(type='GeeTestTaskProxyless', websiteURL='test1',
gt='test2', version=4))},
40: {'json': dict(task=dict(type='NoCaptchaTaskProxyless', websiteKey='test1',
websiteURL='test2', isInvisible=False,
apiDomain='recaptcha.net'))},
41: {'json': dict(task=dict(type='RecaptchaV3TaskProxyless', websiteKey='test1',
websiteURL='test2', apiDomain='recaptcha.net'))},
42: {'json': dict(task=dict(type='FunCaptchaTaskProxyless', websitePublicKey='test1',
websiteURL='test2', data='{"blob": "test3"}'))},
},
CaptchaSolvingService.AZCAPTCHA: {
1: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR)},
2: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR)},
3: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR, phrase=1)},
4: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR, regsense=1)},
5: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR, numeric=2)},
6: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR, calc=1)},
7: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR, min_len=1)},
8: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR, max_len=10)},
9: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR, language=2)},
10: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR, lang='en')},
11: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR, textinstructions='test')},
12: {'data': dict(method='userrecaptcha', googlekey='test1', pageurl='test2', invisible=0)},
13: {'data': dict(method='userrecaptcha', googlekey='test1', pageurl='test2', invisible=1)},
14: {'data': {'method': 'userrecaptcha', 'googlekey': 'test1', 'pageurl': 'test2',
'invisible': 0, 'data-s': 'test3'}},
15: {'data': dict(method='userrecaptcha', version='v3', googlekey='test1',
pageurl='test2')},
16: {'data': dict(method='userrecaptcha', version='v3', googlekey='test1', pageurl='test2',
action='test3')},
17: {'data': dict(method='userrecaptcha', version='v3', googlekey='test1', pageurl='test2',
min_score=0.9)},
18: {'data': dict(method='funcaptcha', publickey='test1', pageurl='test2')},
19: {'data': dict(method='funcaptcha', publickey='test1', pageurl='test2', surl='test3')},
20: {'data': dict(method='funcaptcha', publickey='test1', pageurl='test2')},
21: None,
22: None,
23: None,
24: None,
25: None,
26: None,
27: {'data': dict(method='hcaptcha', sitekey='test1', pageurl='test2')},
28: None,
29: None,
30: {'data': dict(method='userrecaptcha', googlekey='test1', pageurl='test2', invisible=0,
proxy=PROXY_ADDRESS.split('://', maxsplit=1)[1],
proxytype=PROXY_TYPE)},
31: {'data': dict(method='userrecaptcha', googlekey='test1', pageurl='test2', invisible=0,
proxy=PROXY_ADDRESS.split('://', maxsplit=1)[1],
proxytype=PROXY_TYPE,
userAgent='User-Agent1')},
32: {'data': dict(method='userrecaptcha', googlekey='test1', pageurl='test2', invisible=0,
proxy=PROXY_ADDRESS.split('://', maxsplit=1)[1],
proxytype=PROXY_TYPE,
userAgent='User-Agent1',
cookies='cookie1:value1;cookie2:value2')},
33: {'data': {'method': 'userrecaptcha', 'googlekey': 'test1', 'pageurl': 'test2',
'invisible': 0, 'data-s': 'test3'}},
34: {'data': dict(method='userrecaptcha', version='v3', googlekey='test1',
pageurl='test2')},
35: None,
36: None,
37: None,
38: None,
39: None,
40: {'data': {'method': 'userrecaptcha', 'googlekey': 'test1', 'pageurl': 'test2',
'invisible': 0}},
41: {'data': dict(method='userrecaptcha', version='v3', googlekey='test1',
pageurl='test2')},
42: {'data': {'method': 'funcaptcha', 'publickey': 'test1', 'pageurl': 'test2',
'data[blob]': 'test3'}},
},
CaptchaSolvingService.CPTCH_NET: {
1: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR)},
2: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR)},
3: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR, phrase=1)},
4: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR, regsense=1)},
5: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR, numeric=2)},
6: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR, calc=1)},
7: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR, min_len=1)},
8: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR, max_len=10)},
9: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR, language=2)},
10: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR, lang='en')},
11: {'data': dict(method='base64', body=IMAGE_FILE_BASE64_STR, textinstructions='test')},
12: {'data': dict(method='userrecaptcha', googlekey='test1', pageurl='test2', invisible=0)},
13: {'data': dict(method='userrecaptcha', googlekey='test1', pageurl='test2', invisible=1)},
14: {'data': {'method': 'userrecaptcha', 'googlekey': 'test1', 'pageurl': 'test2',
'invisible': 0}},
15: {'data': dict(method='userrecaptcha', version='v3', googlekey='test1',
pageurl='test2')},
16: {'data': dict(method='userrecaptcha', version='v3', googlekey='test1', pageurl='test2',
action='test3')},
17: {'data': dict(method='userrecaptcha', version='v3', googlekey='test1', pageurl='test2',
min_score=0.9)},
18: None,
19: None,
20: None,
21: None,
22: None,
23: None,
24: None,
25: None,
26: None,
27: None,
28: None,
29: None,
30: {'data': dict(method='userrecaptcha', googlekey='test1', pageurl='test2', invisible=0)},
31: {'data': dict(method='userrecaptcha', googlekey='test1', pageurl='test2', invisible=0)},
32: {'data': dict(method='userrecaptcha', googlekey='test1', pageurl='test2', invisible=0)},
33: {'data': dict(method='userrecaptcha', googlekey='test1', pageurl='test2', invisible=0)},
34: {'data': dict(method='userrecaptcha', version='v3', googlekey='test1',
pageurl='test2')},
35: None,
36: None,
37: None,
38: None,
39: None,
40: {'data': {'method': 'userrecaptcha', 'googlekey': 'test1', 'pageurl': 'test2',
'invisible': 0}},
41: {'data': dict(method='userrecaptcha', version='v3', googlekey='test1',
pageurl='test2')},
42: None,
},
CaptchaSolvingService.DEATHBYCAPTCHA: {
1: {'data': dict(captchafile='base64:' + IMAGE_FILE_BASE64_STR)},
2: {'data': dict(captchafile='base64:' + IMAGE_FILE_BASE64_STR)},
3: {'data': dict(captchafile='base64:' + IMAGE_FILE_BASE64_STR)},
4: {'data': dict(captchafile='base64:' + IMAGE_FILE_BASE64_STR)},
5: {'data': dict(captchafile='base64:' + IMAGE_FILE_BASE64_STR)},
6: {'data': dict(captchafile='base64:' + IMAGE_FILE_BASE64_STR)},
7: {'data': dict(captchafile='base64:' + IMAGE_FILE_BASE64_STR)},
8: {'data': dict(captchafile='base64:' + IMAGE_FILE_BASE64_STR)},
9: {'data': dict(captchafile='base64:' + IMAGE_FILE_BASE64_STR)},
10: {'data': dict(captchafile='base64:' + IMAGE_FILE_BASE64_STR)},
11: {'data': dict(captchafile='base64:' + IMAGE_FILE_BASE64_STR)},
12: {'data': dict(type=4,
token_params=json.dumps({'googlekey': 'test1', 'pageurl': 'test2'}))},
13: {'data': dict(type=4,
token_params=json.dumps({'googlekey': 'test1', 'pageurl': 'test2'}))},
14: {'data': dict(type=4, token_params=json.dumps({'googlekey': 'test1', 'pageurl': 'test2',
'data-s': 'test3'}))},
15: {'data': dict(type=5,
token_params=json.dumps({'googlekey': 'test1', 'pageurl': 'test2'}))},
16: {'data': dict(type=5, token_params=json.dumps({'googlekey': 'test1', 'pageurl': 'test2',
'action': 'test3'}))},
17: {'data': dict(type=5, token_params=json.dumps({'googlekey': 'test1', 'pageurl': 'test2',
'min_score': 0.9}))},
18: {'data': dict(type=6,
funcaptcha_params=json.dumps({'publickey': 'test1',
'pageurl': 'test2'}))},
19: {'data': dict(type=6,
funcaptcha_params=json.dumps({'publickey': 'test1',
'pageurl': 'test2'}))},
20: {'data': dict(type=6,
funcaptcha_params=json.dumps({'publickey': 'test1',
'pageurl': 'test2'}))},
21: None,
22: None,
23: None,
24: None,
25: None,
26: None,
27: {'data': dict(type=7,
hcaptcha_params=json.dumps({'sitekey': 'test1', 'pageurl': 'test2'}))},
28: None,
29: None,
30: {'data': dict(
type=4,
token_params=json.dumps(
dict(
googlekey='test1',
pageurl='test2',
proxy=PROXY_ADDRESS,
proxytype=PROXY_TYPE
)
)
)},
31: {'data': dict(
type=4,
token_params=json.dumps(
dict(
googlekey='test1',
pageurl='test2',
proxy=PROXY_ADDRESS,
proxytype=PROXY_TYPE
)
)
)},
32: {'data': dict(
type=4,
token_params=json.dumps(
dict(
googlekey='test1',
pageurl='test2',
proxy=PROXY_ADDRESS,
proxytype=PROXY_TYPE
)
)
)},
33: {'data': dict(
type=4,
token_params=json.dumps(
{'googlekey': 'test1', 'pageurl': 'test2', 'data-s': 'test3'}
)
)},
34: {'data': dict(
type=5,
token_params=json.dumps(
dict(googlekey='test1', pageurl='test2')
)
)},
35: None,
36: None,
37: None,
38: None,
39: None,
40: {'data': dict(
type=4,
token_params=json.dumps(
dict(googlekey='test1', pageurl='test2')
)
)},
41: {'data': dict(
type=5,
token_params=json.dumps(
dict(googlekey='test1', pageurl='test2')
)
)},
42: {'data': dict(
type=6,
funcaptcha_params=json.dumps(
dict(publickey='test1', pageurl='test2')
)
)},
}
}
OUTPUT_TEST_DATA_FOR_TASK_PREPARE_FUNC[CaptchaSolvingService.RUCAPTCHA] = (
OUTPUT_TEST_DATA_FOR_TASK_PREPARE_FUNC[CaptchaSolvingService.TWOCAPTCHA]
)
def get_http_resp_obj(ret_value, status_code=200, reason_phrase='OK', is_success=True,
is_error=False):
""" Mocked response object """
obj = mock.Mock()
obj.json = ret_value.copy
obj.status_code = status_code
obj.reason_phrase = reason_phrase
obj.is_success = is_success
obj.is_error = is_error
return obj
INPUT_TEST_LIST_FOR_TASK_PARSE_RESPONSE_FUNC = {
1: CaptchaType.IMAGE,
2: CaptchaType.RECAPTCHAV2,
3: CaptchaType.RECAPTCHAV3,
4: CaptchaType.TEXT,
5: CaptchaType.FUNCAPTCHA,
6: CaptchaType.KEYCAPTCHA,
7: CaptchaType.GEETEST,
8: CaptchaType.HCAPTCHA,
9: CaptchaType.CAPY,
10: CaptchaType.TIKTOK,
11: CaptchaType.GEETESTV4
}
INPUT_TEST_DATA_FOR_TASK_PARSE_RESPONSE_FUNC = {
CaptchaSolvingService.TWOCAPTCHA: {
1: get_http_resp_obj(dict(status=1, request='1234567890')),
2: get_http_resp_obj(dict(status=1, request='1234567890')),
3: get_http_resp_obj(dict(status=1, request='1234567890')),
4: get_http_resp_obj(dict(status=1, request='1234567890')),
5: get_http_resp_obj(dict(status=1, request='1234567890')),
6: get_http_resp_obj(dict(status=1, request='1234567890')),
7: get_http_resp_obj(dict(status=1, request='1234567890')),
8: get_http_resp_obj(dict(status=1, request='1234567890')),
9: get_http_resp_obj(dict(status=1, request='1234567890')),
10: get_http_resp_obj(dict(status=1, request='1234567890')),
11: get_http_resp_obj(dict(status=1, request='1234567890')),
},
CaptchaSolvingService.ANTI_CAPTCHA: {
1: get_http_resp_obj(dict(errorId=0, taskId='1234567890')),
2: get_http_resp_obj(dict(errorId=0, taskId='1234567890')),
3: get_http_resp_obj(dict(errorId=0, taskId='1234567890')),
4: None,
5: get_http_resp_obj(dict(errorId=0, taskId='1234567890')),
6: None,
7: get_http_resp_obj(dict(errorId=0, taskId='1234567890')),
8: get_http_resp_obj(dict(errorId=0, taskId='1234567890')),
9: None,
10: None,
11: get_http_resp_obj(dict(errorId=0, taskId='1234567890')),
},
CaptchaSolvingService.AZCAPTCHA: {
1: get_http_resp_obj(dict(status=1, request='1234567890')),
2: get_http_resp_obj(dict(status=1, request='1234567890')),
3: get_http_resp_obj(dict(status=1, request='1234567890')),
4: None,
5: get_http_resp_obj(dict(status=1, request='1234567890')),
6: None,
7: None,
8: get_http_resp_obj(dict(status=1, request='1234567890')),
9: None,
10: None,
11: None,
},
CaptchaSolvingService.CPTCH_NET: {
1: get_http_resp_obj(dict(status=1, request='1234567890')),
2: get_http_resp_obj(dict(status=1, request='1234567890')),
3: get_http_resp_obj(dict(status=1, request='1234567890')),
4: None,
5: None,
6: None,
7: None,
8: None,
9: None,
10: None,
11: None,
},
CaptchaSolvingService.DEATHBYCAPTCHA: {
1: get_http_resp_obj(dict(status=0, captcha='1234567890', is_correct=True, text='test')),
2: get_http_resp_obj(dict(status=0, captcha='1234567890', is_correct=True, text='test')),
3: get_http_resp_obj(dict(status=0, captcha='1234567890', is_correct=True, text='test')),
4: None,
5: get_http_resp_obj(dict(status=0, captcha='1234567890', is_correct=True, text='test')),
6: None,
7: None,
8: get_http_resp_obj(dict(status=0, captcha='1234567890', is_correct=True, text='test')),
9: None,
10: None,
11: None,
}
}
INPUT_TEST_DATA_FOR_TASK_PARSE_RESPONSE_FUNC[CaptchaSolvingService.RUCAPTCHA] = (
INPUT_TEST_DATA_FOR_TASK_PARSE_RESPONSE_FUNC[CaptchaSolvingService.TWOCAPTCHA]
)
OUTPUT_TEST_DATA_FOR_TASK_PARSE_RESPONSE_FUNC = {
CaptchaSolvingService.TWOCAPTCHA: {
1: dict(task_id='1234567890', extra={}),
2: dict(task_id='1234567890', extra={}),
3: dict(task_id='1234567890', extra={}),
4: dict(task_id='1234567890', extra={}),
5: dict(task_id='1234567890', extra={}),
6: dict(task_id='1234567890', extra={}),
7: dict(task_id='1234567890', extra={}),
8: dict(task_id='1234567890', extra={}),
9: dict(task_id='1234567890', extra={}),
10: dict(task_id='1234567890', extra={}),
11: dict(task_id='1234567890', extra={}),
},
CaptchaSolvingService.ANTI_CAPTCHA: {
1: dict(task_id='1234567890', extra={}),
2: dict(task_id='1234567890', extra={}),
3: dict(task_id='1234567890', extra={}),
4: None,
5: dict(task_id='1234567890', extra={}),
6: None,
7: dict(task_id='1234567890', extra={}),
8: dict(task_id='1234567890', extra={}),
9: None,
10: None,
11: dict(task_id='1234567890', extra={}),
},
CaptchaSolvingService.AZCAPTCHA: {
1: dict(task_id='1234567890', extra={}),
2: dict(task_id='1234567890', extra={}),
3: dict(task_id='1234567890', extra={}),
4: None,
5: dict(task_id='1234567890', extra={}),
6: None,
7: None,
8: dict(task_id='1234567890', extra={}),
9: None,
10: None,
11: None,
},
CaptchaSolvingService.CPTCH_NET: {
1: dict(task_id='1234567890', extra={}),
2: dict(task_id='1234567890', extra={}),
3: dict(task_id='1234567890', extra={}),
4: None,
5: None,
6: None,
7: None,
8: None,
9: None,
10: None,
11: None,
},
CaptchaSolvingService.DEATHBYCAPTCHA: {
1: dict(task_id='1234567890', extra={}),
2: dict(task_id='1234567890', extra={}),
3: dict(task_id='1234567890', extra={}),
4: None,
5: dict(task_id='1234567890', extra={}),
6: None,
7: None,
8: dict(task_id='1234567890', extra={}),
9: None,
10: None,
11: None,
}
}
OUTPUT_TEST_DATA_FOR_TASK_PARSE_RESPONSE_FUNC[CaptchaSolvingService.RUCAPTCHA] = (
OUTPUT_TEST_DATA_FOR_TASK_PARSE_RESPONSE_FUNC[CaptchaSolvingService.TWOCAPTCHA]
)
OUTPUT_TEST_DATA_FOR_TASK_PARSE_RESPONSE_FUNC_WITH_EXC = {
1: exc.ServiceError,
2: exc.AccessDeniedError,
3: exc.LowBalanceError,
4: exc.ServiceTooBusy,
5: exc.TooManyRequestsError,
6: exc.MalformedRequestError,
7: exc.BadInputDataError,
8: exc.UnableToSolveError,
9: exc.ProxyError
}
INPUT_TEST_DATA_FOR_TASK_PARSE_RESPONSE_FUNC_WITH_EXC = {
CaptchaSolvingService.TWOCAPTCHA: {
1: get_http_resp_obj(dict(status=0, request=choice(
['REPORT_NOT_RECORDED', 'ERROR_IP_ADDRES']))),
2: get_http_resp_obj(dict(status=0, request=choice(
['ERROR_WRONG_USER_KEY', 'ERROR_KEY_DOES_NOT_EXIST', 'ERROR_IP_NOT_ALLOWED',
'IP_BANNED']))),
3: get_http_resp_obj(dict(status=0, request='ERROR_ZERO_BALANCE')),
4: get_http_resp_obj(dict(status=0, request='ERROR_NO_SLOT_AVAILABLE')),
5: get_http_resp_obj(dict(status=0, request=choice(
['MAX_USER_TURN', 'ERROR: 1001', 'ERROR: 1005']))),
6: get_http_resp_obj(dict(status=0, request=choice(
['ERROR_WRONG_ID_FORMAT', 'ERROR_WRONG_CAPTCHA_ID']))),
7: get_http_resp_obj(dict(status=0, request=choice(
'ERROR_UPLOAD ERROR_ZERO_CAPTCHA_FILESIZE ERROR_TOO_BIG_CAPTCHA_FILESIZE '
'ERROR_WRONG_FILE_EXTENSION ERROR_IMAGE_TYPE_NOT_SUPPORTED ERROR_PAGEURL '
'ERROR_BAD_TOKEN_OR_PAGEURL ERROR_GOOGLEKEY ERROR_BAD_PARAMETERS '
'ERROR_TOKEN_EXPIRED ERROR_EMPTY_ACTION'.split()))),
8: get_http_resp_obj(dict(status=0, request=choice(
'ERROR_CAPTCHAIMAGE_BLOCKED ERROR_CAPTCHA_UNSOLVABLE ERROR_BAD_DUPLICATES'.split()))),
9: get_http_resp_obj(dict(status=0, request=choice(
['ERROR_BAD_PROXY', 'ERROR_PROXY_CONNECTION_FAILED']))),
},
CaptchaSolvingService.ANTI_CAPTCHA: {
1: get_http_resp_obj(dict(errorId=2, errorCode='UNKNOWN_ERROR')),
2: get_http_resp_obj(dict(errorId=3, errorCode=choice(
['ERROR_KEY_DOES_NOT_EXIST', 'ERROR_IP_NOT_ALLOWED', 'ERROR_IP_BLOCKED']))),
3: get_http_resp_obj(dict(errorId=4, errorCode='ERROR_ZERO_BALANCE')),
4: get_http_resp_obj(dict(errorId=4, errorCode='ERROR_NO_SLOT_AVAILABLE')),
5: None,
6: get_http_resp_obj(dict(errorId=4, errorCode=choice(
'ERROR_NO_SUCH_METHOD ERROR_NO_SUCH_CAPCHA_ID ERROR_TASK_ABSENT '
'ERROR_TASK_NOT_SUPPORTED ERROR_FUNCAPTCHA_NOT_ALLOWED'.split()))),
7: get_http_resp_obj(dict(errorId=4, errorCode=choice(
'ERROR_ZERO_CAPTCHA_FILESIZE ERROR_TOO_BIG_CAPTCHA_FILESIZE '
'ERROR_IMAGE_TYPE_NOT_SUPPORTED ERROR_EMPTY_COMMENT ERROR_INCORRECT_SESSION_DATA '
'ERROR_RECAPTCHA_INVALID_SITEKEY ERROR_RECAPTCHA_INVALID_DOMAIN '
'ERROR_RECAPTCHA_OLD_BROWSER ERROR_TOKEN_EXPIRED ERROR_INVISIBLE_RECAPTCHA'.split()))),
8: get_http_resp_obj(dict(errorId=4, errorCode=choice(
'ERROR_CAPTCHA_UNSOLVABLE ERROR_BAD_DUPLICATES ERROR_RECAPTCHA_TIMEOUT '
'ERROR_FAILED_LOADING_WIDGET'.split()))),
9: get_http_resp_obj(dict(errorId=4, errorCode=choice(
'ERROR_PROXY_CONNECT_REFUSED ERROR_PROXY_CONNECT_TIMEOUT ERROR_PROXY_READ_TIMEOUT '
'ERROR_PROXY_BANNED ERROR_PROXY_TRANSPARENT ERROR_PROXY_HAS_NO_IMAGE_SUPPORT '
'ERROR_PROXY_INCOMPATIBLE_HTTP_VERSION ERROR_PROXY_NOT_AUTHORISED'.split()))),
},
CaptchaSolvingService.CPTCH_NET: {
1: get_http_resp_obj(dict(status=0, request='UNKNOWN_ERROR')),
2: get_http_resp_obj(dict(status=0, request=choice(
['ERROR_WRONG_USER_KEY', 'ERROR_KEY_DOES_NOT_EXIST']))),
3: get_http_resp_obj(dict(status=0, request='ERROR_ZERO_BALANCE')),
4: None,
5: None,
6: get_http_resp_obj(dict(status=0, request='ERROR_WRONG_CAPTCHA_ID')),
7: get_http_resp_obj(dict(status=0, request=choice(
'ERROR_UPLOAD ERROR_ZERO_CAPTCHA_FILESIZE ERROR_TOO_BIG_CAPTCHA_FILESIZE '
'ERROR_PAGEURL ERROR_GOOGLEKEY ERROR'.split()))),
8: get_http_resp_obj(dict(status=0, request='ERROR_CAPTCHA_UNSOLVABLE')),
9: None,
},
CaptchaSolvingService.DEATHBYCAPTCHA: {
1: get_http_resp_obj(dict(status=255)),
2: get_http_resp_obj(dict(status=255, error=choice(
['token authentication disabled', 'not-logged-in', 'banned']))),
3: get_http_resp_obj(dict(status=255, error='insufficient-funds')),
4: get_http_resp_obj(dict(status=255, error='service-overload')),
5: None,
6: get_http_resp_obj(dict(status=255, error=choice(
['upload-failed', 'invalid-captcha']))),
7: get_http_resp_obj(dict(status=255, error=choice(
['ERROR_PAGEURL', 'Invalid base64-encoded CAPTCHA', 'Not a (CAPTCHA) image',
'Empty CAPTCHA image', 'ERROR_GOOGLEKEY', 'ERROR_PAGEURL',
'ERROR_PUBLICKEY', 'ERROR_SITEKEY', 'ERROR_ACTION', 'ERROR_MIN_SCORE',
'ERROR_MIN_SCORE_NOT_FLOAT']))),
8: None,
9: get_http_resp_obj(dict(status=255, error=choice(
['ERROR_PROXYTYPE', 'ERROR_PROXY']))),
}
}
INPUT_TEST_DATA_FOR_TASK_PARSE_RESPONSE_FUNC_WITH_EXC[CaptchaSolvingService.RUCAPTCHA] = (
INPUT_TEST_DATA_FOR_TASK_PARSE_RESPONSE_FUNC_WITH_EXC[CaptchaSolvingService.TWOCAPTCHA]
)
INPUT_TEST_DATA_FOR_TASK_PARSE_RESPONSE_FUNC_WITH_EXC[CaptchaSolvingService.AZCAPTCHA] = (
INPUT_TEST_DATA_FOR_TASK_PARSE_RESPONSE_FUNC_WITH_EXC[CaptchaSolvingService.TWOCAPTCHA]
)
================================================
FILE: tests/test_captcha_base.py
================================================
# -*- coding: UTF-8 -*-
from unicaps._captcha import CaptchaType
from unicaps._captcha.base import BaseCaptcha, BaseCaptchaSolution
def test_captcha_class_base(captcha_class):
"""
Checks base class of Captcha class.
"""
assert issubclass(captcha_class, BaseCaptcha)
def test_captcha_class_get_type(captcha_class):
"""
Checks get_type() function of Captcha class.
"""
assert isinstance(captcha_class.get_type(), CaptchaType)
def test_captcha_class_get_solution_class(captcha_class):
"""
Checks get_solution_class() function of Captcha class.
"""
assert issubclass(captcha_class.get_solution_class(), BaseCaptchaSolution)
def test_captcha_class_get_optional_data(captcha_instance):
"""
Checks get_optional_data() function of Captcha class.
"""
assert isinstance(captcha_instance.get_optional_data(), dict)
def test_captchasolution_class_get_type(captcha_class):
"""
Checks get_type() function of CaptchaSolution class.
"""
assert captcha_class.get_type() is captcha_class.get_solution_class().get_type()
def test_captchasolution_class_get_captcha_class(captcha_class):
"""
Checks get_type() function of CaptchaSolution class.
"""
assert captcha_class is captcha_class.get_solution_class().get_captcha_class()
def test_captchasolution_obj_get_string(captcha_class):
"""
Checks get_type() function of CaptchaSolution class.
"""
solution_class = captcha_class.get_solution_class()
dc_fields = solution_class.__dataclass_fields__
field_values = []
for i, field in enumerate(dc_fields.values(), start=1):
if field.type == str:
value = f'test{i}'
elif field.type == dict:
value = {f'test{i}': f'test{i}'}
else:
value = None
field_values.append(value)
solution_obj = solution_class(*field_values)
assert str(solution_obj) == '\n'.join(str(v) for v in field_values)
================================================
FILE: tests/test_image_captcha.py
================================================
# -*- coding: UTF-8 -*-
import base64
import os.path
import pathlib
import pytest
from unicaps.captcha import ImageCaptcha
from unicaps.exceptions import BadInputDataError
DATA_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data')
IMAGE_FILE = os.path.join(DATA_DIR, 'image.jpg')
@pytest.fixture
def image_path():
return pathlib.Path(IMAGE_FILE)
@pytest.fixture
def image_bytes():
return open(IMAGE_FILE, 'rb').read()
@pytest.fixture
def image_base64(image_bytes):
return base64.b64encode(image_bytes)
def test_image_from_path(image_path, image_bytes):
""" Pathlib input test"""
captcha = ImageCaptcha(image=image_path)
assert image_bytes == captcha.get_image_bytes()
def test_image_from_binary(image_bytes):
""" Binary input test"""
captcha = ImageCaptcha(image=image_bytes)
assert image_bytes == captcha.get_image_bytes()
def test_not_an_image():
""" Not an image input test """
with pytest.raises(TypeError):
ImageCaptcha(image='wrong_image')
def test_bad_image():
""" Bad image input test """
with pytest.raises(BadInputDataError):
ImageCaptcha(image=b'bad_image_data')
================================================
FILE: tests/test_proxy.py
================================================
# -*- coding: UTF-8 -*-
"""
Proxy tests
"""
from unicaps.proxy import ProxyServer, ProxyServerType
def test_proxy_from_string():
proxy = ProxyServer('http://login:password@address:8080')
assert proxy.address == 'address'
assert proxy.proxy_type == ProxyServerType.HTTP
assert proxy.port == 8080
assert proxy.login == 'login'
assert proxy.password == 'password'
def test_proxy_wo_auth_from_string():
proxy = ProxyServer('https://address:8080')
assert proxy.address == 'address'
assert proxy.proxy_type == ProxyServerType.HTTPS
assert proxy.port == 8080
assert proxy.login is None
assert proxy.password is None
def test_proxy_address_only():
proxy = ProxyServer('address')
assert proxy.address == 'address'
assert proxy.proxy_type == ProxyServerType.HTTP
assert proxy.port == 80
assert proxy.login is None
assert proxy.password is None
def test_proxy_all():
proxy = ProxyServer('address', ProxyServerType.SOCKS4, 80, 'login', 'password')
assert proxy.address == 'address'
assert proxy.proxy_type == ProxyServerType.SOCKS4
assert proxy.port == 80
assert proxy.login == 'login'
assert proxy.password == 'password'
def test_proxy_to_string():
proxy = ProxyServer('address', ProxyServerType.SOCKS5, 80, 'login', 'password')
assert str(proxy) == 'socks5://login:password@address:80'
================================================
FILE: tests/test_service_module.py
================================================
# -*- coding: UTF-8 -*-
"""
Service module tests
"""
import importlib
import inspect
from copy import deepcopy
from unittest import mock
import pytest
from unicaps._captcha import CaptchaType
from unicaps._service import SOLVING_SERVICE
from data.data import (BASE_TASK_REQUEST_DATA, INPUT_TEST_DATA_FOR_TASK_PREPARE_FUNC,
OUTPUT_TEST_DATA_FOR_TASK_PREPARE_FUNC,
INPUT_TEST_LIST_FOR_TASK_PARSE_RESPONSE_FUNC,
INPUT_TEST_DATA_FOR_TASK_PARSE_RESPONSE_FUNC,
OUTPUT_TEST_DATA_FOR_TASK_PARSE_RESPONSE_FUNC,
INPUT_TEST_DATA_FOR_TASK_PARSE_RESPONSE_FUNC_WITH_EXC,
OUTPUT_TEST_DATA_FOR_TASK_PARSE_RESPONSE_FUNC_WITH_EXC)
from _helpers import dict_update
# dict of captcha services with lists of supported captchas
SERVICE_MODULES_FOR_TEST = {
'anti_captcha': (CaptchaType.IMAGE, CaptchaType.RECAPTCHAV2, CaptchaType.RECAPTCHAV3,
CaptchaType.FUNCAPTCHA, CaptchaType.GEETEST, CaptchaType.GEETESTV4,
CaptchaType.HCAPTCHA),
'twocaptcha': (CaptchaType.IMAGE, CaptchaType.TEXT, CaptchaType.RECAPTCHAV2,
CaptchaType.RECAPTCHAV3, CaptchaType.FUNCAPTCHA, CaptchaType.KEYCAPTCHA,
CaptchaType.GEETEST, CaptchaType.GEETESTV4, CaptchaType.HCAPTCHA,
CaptchaType.CAPY, CaptchaType.TIKTOK),
'rucaptcha': (CaptchaType.IMAGE, CaptchaType.TEXT, CaptchaType.RECAPTCHAV2,
CaptchaType.RECAPTCHAV3, CaptchaType.FUNCAPTCHA, CaptchaType.KEYCAPTCHA,
CaptchaType.GEETEST, CaptchaType.GEETESTV4, CaptchaType.HCAPTCHA,
CaptchaType.CAPY, CaptchaType.TIKTOK),
'azcaptcha': (CaptchaType.IMAGE, CaptchaType.RECAPTCHAV2, CaptchaType.RECAPTCHAV3,
CaptchaType.HCAPTCHA, CaptchaType.FUNCAPTCHA),
'cptch_net': (CaptchaType.IMAGE, CaptchaType.RECAPTCHAV2, CaptchaType.RECAPTCHAV3),
'deathbycaptcha': (CaptchaType.IMAGE, CaptchaType.RECAPTCHAV2, CaptchaType.RECAPTCHAV3,
CaptchaType.HCAPTCHA, CaptchaType.FUNCAPTCHA)
}
BASE_REQUESTS = ('GetBalance', 'GetStatus', 'ReportGood', 'ReportBad')
TASK_REQUEST_PREPARE_PARAMS = ('self', 'captcha', 'proxy', 'user_agent', 'cookies')
SOLUTION_REQUEST_PREPARE_PARAMS = ('self', 'task')
REQUEST_PARSE_RESPONSE_PARAMS = ('self', 'response')
@pytest.fixture(scope="module", params=SERVICE_MODULES_FOR_TEST)
def service_module(request):
return importlib.import_module('unicaps._service.' + request.param)
@pytest.fixture(scope="module")
def service_instance(service_module):
return getattr(service_module, "Service")("test")
@pytest.fixture(scope="module")
def json_response_obj():
def get_obj(ret_value):
obj = mock.Mock()
obj.json = lambda: ret_value
return obj
return get_obj
def check_if_class_is_present(module, class_name, is_not=False):
if is_not:
assert not hasattr(module, class_name), \
f'{class_name} is found in service module {module.__name__}'
else:
assert hasattr(module, class_name), \
f'{class_name} is not found in service module {module.__name__}'
def is_captcha_supported(service_module, captcha_type):
module_name = service_module.__name__.split('.')[-1]
if captcha_type in SERVICE_MODULES_FOR_TEST[module_name]:
return True
return False
def get_request(service_module, captcha_type, req_type='Request'):
request_name = captcha_type.value + req_type
return request_name, getattr(service_module, request_name)
def test_if_service_class_is_present(service_module):
""" Checks if Service() class is declared in the module """
check_if_class_is_present(service_module, 'Service')
@pytest.mark.parametrize("req", BASE_REQUESTS)
def test_if_base_request_is_present(service_module, req):
""" Checks if all of the base requests are present in the module file """
check_if_class_is_present(service_module, req + 'Request')
@pytest.mark.parametrize("captcha_type", CaptchaType)
def test_if_task_request_is_present(service_module, captcha_type):
""" Checks if all of the task requests are present in the module file """
check_if_class_is_present(
service_module,
captcha_type.value + 'TaskRequest',
not is_captcha_supported(service_module, captcha_type)
)
@pytest.mark.parametrize("captcha_type", CaptchaType)
def test_if_solution_request_is_present(service_module, captcha_type):
""" Checks if all of the solution requests are present in the module file """
check_if_class_is_present(
service_module,
captcha_type.value + 'SolutionRequest',
not is_captcha_supported(service_module, captcha_type)
)
@pytest.mark.parametrize("req", BASE_REQUESTS)
def test_base_request_signature_of_prepare_func(service_module, req):
""" Checks signature of the TaskRequest.prepare() function """
request_name = req + 'Request'
request_class = getattr(service_module, request_name)
if req in ('ReportGood', 'ReportBad'):
standard = ('self', 'solved_captcha')
else:
standard = ('self',)
params = tuple(inspect.signature(request_class.prepare).parameters)
assert params == standard, \
f"Incorrect signature of {request_name}.prepare() func: {', '.join(params)}"
@pytest.mark.parametrize("captcha_type", CaptchaType)
def test_task_request_signature_of_prepare_func(service_module, captcha_type):
""" Checks signature of the TaskRequest.prepare() function """
if is_captcha_supported(service_module, captcha_type):
request_name, request_class = get_request(service_module, captcha_type, 'TaskRequest')
params = tuple(inspect.signature(request_class.prepare).parameters)
assert params == TASK_REQUEST_PREPARE_PARAMS, \
f"Incorrect signature of {request_name}.prepare() func: {', '.join(params)}"
@pytest.mark.parametrize("captcha_type", CaptchaType)
def test_solution_request_signature_of_prepare_func(service_module, captcha_type):
""" Checks signature of the TaskRequest.prepare() function """
if is_captcha_supported(service_module, captcha_type):
request_name, request_class = get_request(service_module, captcha_type, 'SolutionRequest')
params = tuple(inspect.signature(request_class.prepare).parameters)
assert params == SOLUTION_REQUEST_PREPARE_PARAMS, \
f"Incorrect signature of {request_name}.prepare() func: {', '.join(params)}"
@pytest.mark.parametrize("req", BASE_REQUESTS)
def test_base_request_signature_of_parse_response_func(service_module, req):
""" Checks signature of the TaskRequest.prepare() function """
request_name = req + 'Request'
request_class = getattr(service_module, request_name)
params = tuple(inspect.signature(request_class.parse_response).parameters)
assert params == REQUEST_PARSE_RESPONSE_PARAMS, \
f"Incorrect signature of {request_name}.prepare() func: {', '.join(params)}"
@pytest.mark.parametrize("captcha_type, req_type",
[(t, r) for t in CaptchaType for r in ('TaskRequest', 'SolutionRequest')])
def test_captcha_request_signature_of_parse_response_func(service_module, captcha_type, req_type):
"""
Checks signature of the TaskRequest.parse_response() and
SolutionRequest.parse_response() functions.
"""
if not is_captcha_supported(service_module, captcha_type):
pytest.skip("CAPTCHA is not supported!")
request_name, request_class = get_request(service_module, captcha_type, req_type)
params = tuple(inspect.signature(request_class.parse_response).parameters)
assert params == REQUEST_PARSE_RESPONSE_PARAMS, \
f"Incorrect signature of {request_name}.parse_response() func: {', '.join(params)}"
@pytest.mark.parametrize("test_id,input_data", INPUT_TEST_DATA_FOR_TASK_PREPARE_FUNC.items())
def test_task_request_return_value_of_prepare_func(service_module, test_id, input_data):
service_type = {v: k for k, v in SOLVING_SERVICE.items()}[service_module]
captcha_type = input_data[0].get_type()
if not is_captcha_supported(service_module, captcha_type):
pytest.skip("CAPTCHA is not supported!")
request_name, request_class = get_request(service_module, captcha_type, 'TaskRequest')
# service instance
service = service_module.Service('test')
# request instance
request_instance = request_class(service)
result_dict = request_instance.prepare(*input_data)
standard_result = deepcopy(BASE_TASK_REQUEST_DATA[service_type])
dict_update(standard_result, OUTPUT_TEST_DATA_FOR_TASK_PREPARE_FUNC[service_type][test_id])
assert result_dict == standard_result
@pytest.mark.parametrize("test_id,captcha_type",
INPUT_TEST_LIST_FOR_TASK_PARSE_RESPONSE_FUNC.items())
def test_task_request_return_value_of_parse_response_func(service_module, test_id, captcha_type):
service_type = {v: k for k, v in SOLVING_SERVICE.items()}[service_module]
if not is_captcha_supported(service_module, captcha_type):
pytest.skip("CAPTCHA is not supported!")
request_name, request_class = get_request(service_module, captcha_type, 'TaskRequest')
# service instance
service = service_module.Service('test')
input_data = INPUT_TEST_DATA_FOR_TASK_PARSE_RESPONSE_FUNC[service_type][test_id]
# request instance
request_instance = request_class(service)
result_dict = request_instance.parse_response(input_data)
standard_result = deepcopy(OUTPUT_TEST_DATA_FOR_TASK_PARSE_RESPONSE_FUNC[service_type][test_id])
assert result_dict == standard_result
@pytest.mark.parametrize("test_id,captcha_type,exc_type",
[(i, c, e) for i, e in
OUTPUT_TEST_DATA_FOR_TASK_PARSE_RESPONSE_FUNC_WITH_EXC.items()
for c in CaptchaType])
def test_task_request_exception_of_parse_response_func(service_module, test_id, captcha_type,
exc_type):
service_type = {v: k for k, v in SOLVING_SERVICE.items()}[service_module]
if not is_captcha_supported(service_module, captcha_type):
pytest.skip("CAPTCHA is not supported!")
request_name, request_class = get_request(service_module, captcha_type, 'TaskRequest')
# service instance
service = service_module.Service('test')
# get input data, skip test
input_data = INPUT_TEST_DATA_FOR_TASK_PARSE_RESPONSE_FUNC_WITH_EXC[service_type][test_id]
if not input_data:
pytest.skip("The service doesn't support the testing exception!")
# request instance
request_instance = request_class(service)
expected_exception = (
OUTPUT_TEST_DATA_FOR_TASK_PARSE_RESPONSE_FUNC_WITH_EXC[test_id]
)
with pytest.raises(expected_exception):
request_instance.parse_response(input_data)
================================================
FILE: tests/test_solver.py
================================================
# -*- coding: UTF-8 -*-
"""
CaptchaSolver tests
"""
from unittest.mock import Mock
import pytest
from unicaps import CaptchaSolver, CaptchaSolvingService
from unicaps.captcha import CaptchaType
API_KEY = 'TEST_API_KEY'
@pytest.fixture(scope="module")
def captcha_solver():
return CaptchaSolver('2captcha.com', API_KEY)
@pytest.fixture()
def mocked_captcha_solver(captcha_solver, monkeypatch):
monkeypatch.setattr(captcha_solver, '_service', Mock())
return captcha_solver
def test_solver_init():
service = CaptchaSolvingService.ANTI_CAPTCHA
solver = CaptchaSolver(service, API_KEY)
assert solver.service_name == service
assert solver.api_key == API_KEY
def test_solver_init_from_string():
solver = CaptchaSolver('2captcha.com', API_KEY)
assert solver.service_name == CaptchaSolvingService.TWOCAPTCHA
assert solver.api_key == API_KEY
def test_solver_bad_init():
with pytest.raises(ValueError):
CaptchaSolver('2captcha', API_KEY)
def test_solver_bad_init2():
with pytest.raises(ValueError):
CaptchaSolver(b'2captcha.com', API_KEY)
# @pytest.mark.parametrize("captcha_type", CaptchaType)
def test_call_solve_func(mocked_captcha_solver, captcha_instance):
mapping = {
CaptchaType.IMAGE: 'image_captcha',
CaptchaType.TEXT: 'text_captcha',
CaptchaType.RECAPTCHAV2: 'recaptcha_v2',
CaptchaType.RECAPTCHAV3: 'recaptcha_v3',
CaptchaType.HCAPTCHA: 'hcaptcha',
CaptchaType.FUNCAPTCHA: 'funcaptcha',
CaptchaType.KEYCAPTCHA: 'keycaptcha',
CaptchaType.GEETEST: 'geetest',
CaptchaType.CAPY: 'capy_puzzle',
CaptchaType.TIKTOK: 'tiktok',
CaptchaType.GEETESTV4: 'geetest_v4'
}
func = getattr(mocked_captcha_solver, f'solve_{mapping[captcha_instance.get_type()]}')
func(
**{k: v for k, v in captcha_instance.__dict__.items() if not k.startswith('_')}
)
================================================
FILE: unicaps/__init__.py
================================================
# -*- coding: UTF-8 -*-
"""
Unicaps package
~~~~~~~~~~~~~~~
"""
# pylint: disable=unused-import,import-error
from ._solver import CaptchaSolver
from ._solver_async import AsyncCaptchaSolver
from ._service import CaptchaSolvingService
__all__ = ('CaptchaSolver', 'AsyncCaptchaSolver', 'CaptchaSolvingService')
================================================
FILE: unicaps/__version__.py
================================================
""" Version info """
__title__ = 'unicaps'
__description__ = 'Python CAPTCHA solving for Humans.'
__url__ = 'https://github.com/sergey-scat/unicaps'
__version__ = '1.3.0'
__author__ = 'Sergey Scat'
__license__ = 'Apache 2.0'
__copyright__ = 'Copyright 2020-2024 Sergey Scat'
================================================
FILE: unicaps/_captcha/__init__.py
================================================
# -*- coding: UTF-8 -*-
""" CAPTCHAs """
# pylint: disable=unused-import,import-error
from .image import ImageCaptcha
from .text import TextCaptcha
from .recaptcha_v2 import RecaptchaV2
from .recaptcha_v3 import RecaptchaV3
from .hcaptcha import HCaptcha
from .funcaptcha import FunCaptcha
from .keycaptcha import KeyCaptcha
from .geetest import GeeTest
from .geetest_v4 import GeeTestV4
from .capy import CapyPuzzle
from .tiktok import TikTokCaptcha
from .base import CaptchaType
__all__ = (
'ImageCaptcha',
'TextCaptcha',
'RecaptchaV2',
'RecaptchaV3',
'HCaptcha',
'FunCaptcha',
'KeyCaptcha',
'GeeTest',
'GeeTestV4',
'CapyPuzzle',
'TikTokCaptcha',
'CaptchaType'
)
================================================
FILE: unicaps/_captcha/base.py
================================================
# -*- coding: UTF-8 -*-
"""
Base CAPTCHA stuff
"""
import enum
import importlib
from abc import ABC
from dataclasses import asdict, dataclass, fields, MISSING
from typing import Dict
class CaptchaType(enum.Enum):
""" Captcha type enumeration """
IMAGE = "ImageCaptcha"
RECAPTCHAV2 = "RecaptchaV2"
RECAPTCHAV3 = "RecaptchaV3"
TEXT = "TextCaptcha"
FUNCAPTCHA = "FunCaptcha"
GEETEST = "GeeTest"
GEETESTV4 = "GeeTestV4"
HCAPTCHA = "HCaptcha"
KEYCAPTCHA = "KeyCaptcha"
CAPY = "CapyPuzzle"
TIKTOK = "TikTokCaptcha"
@dataclass
class BaseCaptcha(ABC):
""" Base class for any CAPTCHA """
@classmethod
def get_type(cls) -> CaptchaType:
""" Return CaptchaType """
return CaptchaType(cls.__name__)
@classmethod
def get_solution_class(cls) -> 'BaseCaptchaSolution':
""" Return appropriate solution class """
return getattr(importlib.import_module(cls.__module__), cls.__name__ + "Solution")
def get_optional_data(self, **kwargs) -> Dict:
"""
Return a dict with all optional fields requested (that are not None)
as a dict with given names.
:return: :dict:Dictionary of optional not None fields with given names and those values
:rtype: dict
"""
result = {}
if not kwargs:
# get all optional params
kwargs = {
field.name: (field.name, None) for field in fields(self)
if field.default is not MISSING
}
for opt_field in kwargs:
opt_field_value = getattr(self, opt_field)
field_name, converter = kwargs[opt_field]
if opt_field_value is not None:
if callable(converter):
opt_field_value = converter(opt_field_value)
result[field_name] = opt_field_value
return result
@dataclass
class BaseCaptchaSolution(ABC):
""" Base class for any CAPTCHA solution """
@classmethod
def get_type(cls) -> CaptchaType:
""" Returns CaptchaType """
return CaptchaType(cls.__name__.split("Solution", maxsplit=1)[0])
@classmethod
def get_captcha_class(cls) -> BaseCaptcha:
""" Returns appropriate solution class """
return getattr(
importlib.import_module(cls.__module__),
cls.__name__.split("Solution", maxsplit=1)[0]
)
def __str__(self):
return '\n'.join(str(getattr(self, field.name)) for field in fields(self))
def as_dict(self):
""" Get solution data as Python dictionary """
return asdict(self)
================================================
FILE: unicaps/_captcha/capy.py
================================================
# -*- coding: UTF-8 -*-
"""
Capy Puzzle
"""
from dataclasses import dataclass
from typing import Optional
from enforce_typing import enforce_types # type: ignore
from .base import BaseCaptcha, BaseCaptchaSolution
@enforce_types
@dataclass
class CapyPuzzle(BaseCaptcha):
""" Capy Puzzle CAPTCHA """
site_key: str
page_url: str
api_server: Optional[str] = None
@enforce_types
@dataclass
class CapyPuzzleSolution(BaseCaptchaSolution):
""" Capy Puzzle CAPTCHA solution """
captchakey: str
challengekey: str
answer: str
================================================
FILE: unicaps/_captcha/funcaptcha.py
================================================
# -*- coding: UTF-8 -*-
"""
FunCaptcha
"""
from dataclasses import dataclass
from typing import Optional
from enforce_typing import enforce_types # type: ignore
from .base import BaseCaptcha, BaseCaptchaSolution
@enforce_types
@dataclass
class FunCaptcha(BaseCaptcha):
""" FunCaptcha """
public_key: str
page_url: str
service_url: Optional[str] = None
no_js: Optional[bool] = None
blob: Optional[str] = None
@enforce_types
@dataclass
class FunCaptchaSolution(BaseCaptchaSolution):
""" FunCaptcha solution """
token: str
================================================
FILE: unicaps/_captcha/geetest.py
================================================
# -*- coding: UTF-8 -*-
"""
GeeTest CAPTCHA
"""
from dataclasses import dataclass
from typing import Optional
from enforce_typing import enforce_types # type: ignore
from .base import BaseCaptcha, BaseCaptchaSolution
@enforce_types
@dataclass
class GeeTest(BaseCaptcha):
""" GeeTest """
page_url: str
gt_key: str
challenge: str
api_server: Optional[str] = None
@enforce_types
@dataclass
class GeeTestSolution(BaseCaptchaSolution):
""" GeeTest solution """
challenge: str
validate: str
seccode: str
================================================
FILE: unicaps/_captcha/geetest_v4.py
================================================
# -*- coding: UTF-8 -*-
"""
GeeTest v4 CAPTCHA
"""
from dataclasses import dataclass
from typing import Optional
from enforce_typing import enforce_types # type: ignore
from .base import BaseCaptcha, BaseCaptchaSolution
@enforce_types
@dataclass
class GeeTestV4(BaseCaptcha):
""" GeeTest v4 """
page_url: str
captcha_id: str
@enforce_types
@dataclass
class GeeTestV4Solution(BaseCaptchaSolution):
""" GeeTest v4 solution """
captcha_id: str
lot_number: str
pass_token: str
gen_time: str
captcha_output: str
================================================
FILE: unicaps/_captcha/hcaptcha.py
================================================
# -*- coding: UTF-8 -*-
"""
hCaptcha
"""
from dataclasses import dataclass
from typing import Optional
from enforce_typing import enforce_types # type: ignore
from .base import BaseCaptcha, BaseCaptchaSolution
@enforce_types
@dataclass
class HCaptcha(BaseCaptcha):
""" hCaptcha """
site_key: str
page_url: str
is_invisible: bool = False
api_domain: Optional[str] = None
@enforce_types
@dataclass
class HCaptchaSolution(BaseCaptchaSolution):
""" hCaptcha solution """
token: str
================================================
FILE: unicaps/_captcha/image.py
================================================
# -*- coding: UTF-8 -*-
"""
Image CAPTCHA
"""
import base64
import imghdr
import io
import pathlib
from dataclasses import dataclass
from typing import Union, Optional
from enforce_typing import enforce_types # type: ignore
from .base import BaseCaptcha, BaseCaptchaSolution
from ..common import CaptchaAlphabet, CaptchaCharType, WorkerLanguage
from ..exceptions import BadInputDataError
@enforce_types
@dataclass
class ImageCaptcha(BaseCaptcha):
""" Image CAPTCHA """
image: Union[bytes, io.RawIOBase, io.BufferedIOBase, pathlib.Path]
char_type: Optional[CaptchaCharType] = None
is_phrase: Optional[bool] = None
is_case_sensitive: Optional[bool] = None
is_math: Optional[bool] = None
min_len: Optional[int] = None
max_len: Optional[int] = None
alphabet: Optional[CaptchaAlphabet] = None
language: Optional[WorkerLanguage] = None
comment: Optional[str] = None
def __post_init__(self):
self._image_bytes = None
self.get_image_bytes()
def get_image_bytes(self) -> bytes:
""" Bytes image """
if self._image_bytes is None:
if isinstance(self.image, bytes):
self._image_bytes = self.image # type: ignore
elif isinstance(self.image, (io.RawIOBase, io.BufferedIOBase)):
self._image_bytes = self.image.read()
elif isinstance(self.image, pathlib.Path):
self._image_bytes = self.image.read_bytes()
# check image type
self.get_image_type()
return self._image_bytes
def get_image_base64(self) -> bytes:
""" BASE64 image """
return base64.b64encode(self.get_image_bytes())
def get_image_type(self) -> str:
""" Get type of image file/data """
image_type = imghdr.what(None, h=self._image_bytes)
if not image_type:
raise BadInputDataError("Unable to recognize image type!")
return image_type
@enforce_types
@dataclass
class ImageCaptchaSolution(BaseCaptchaSolution):
""" Image CAPTCHA solution """
text: str
================================================
FILE: unicaps/_captcha/keycaptcha.py
================================================
# -*- coding: UTF-8 -*-
"""
KeyCaptcha
"""
from dataclasses import dataclass
from enforce_typing import enforce_types # type: ignore
from .base import BaseCaptcha, BaseCaptchaSolution
@enforce_types
@dataclass
class KeyCaptcha(BaseCaptcha):
""" KeyCaptcha """
page_url: str
user_id: str
session_id: str
ws_sign: str
ws_sign2: str
@enforce_types
@dataclass
class KeyCaptchaSolution(BaseCaptchaSolution):
""" KeyCaptcha solution """
token: str
================================================
FILE: unicaps/_captcha/recaptcha_v2.py
================================================
# -*- coding: UTF-8 -*-
"""
Google reCAPTCHA v2
"""
from dataclasses import dataclass
from typing import Optional
from enforce_typing import enforce_types # type: ignore
from .base import BaseCaptcha, BaseCaptchaSolution
@enforce_types
@dataclass
class RecaptchaV2(BaseCaptcha):
""" Google reCAPTCHA v2 """
site_key: str
page_url: str
is_invisible: bool = False
is_enterprise: bool = False
data_s: Optional[str] = None
api_domain: Optional[str] = None
@enforce_types
@dataclass
class RecaptchaV2Solution(BaseCaptchaSolution):
""" Google reCAPTCHA v2 solution """
token: str
================================================
FILE: unicaps/_captcha/recaptcha_v3.py
================================================
# -*- coding: UTF-8 -*-
"""
Google reCAPTCHA v3
"""
from dataclasses import dataclass
from typing import Optional
from enforce_typing import enforce_types # type: ignore
from .base import BaseCaptcha, BaseCaptchaSolution
@enforce_types
@dataclass
class RecaptchaV3(BaseCaptcha):
""" Google reCAPTCHA v3 """
site_key: str
page_url: str
is_enterprise: bool = False
action: Optional[str] = None
min_score: Optional[float] = None
api_domain: Optional[str] = None
@enforce_types
@dataclass
class RecaptchaV3Solution(BaseCaptchaSolution):
""" Google reCAPTCHA v3 solution """
token: str
================================================
FILE: unicaps/_captcha/text.py
================================================
# -*- coding: UTF-8 -*-
"""
Text CAPTCHA
"""
from dataclasses import dataclass
from typing import Optional
from enforce_typing import enforce_types # type: ignore
from .base import BaseCaptcha, BaseCaptchaSolution
from ..common import CaptchaAlphabet, WorkerLanguage
@enforce_types
@dataclass
class TextCaptcha(BaseCaptcha):
""" Text CAPTCHA """
text: str
alphabet: Optional[CaptchaAlphabet] = None
language: Optional[WorkerLanguage] = None
@enforce_types
@dataclass
class TextCaptchaSolution(BaseCaptchaSolution):
""" Text CAPTCHA solution """
text: str
================================================
FILE: unicaps/_captcha/tiktok.py
================================================
# -*- coding: UTF-8 -*-
"""
TikTokCaptcha
"""
from dataclasses import dataclass
from typing import Optional
from enforce_typing import enforce_types # type: ignore
from .base import BaseCaptcha, BaseCaptchaSolution
@enforce_types
@dataclass
class TikTokCaptcha(BaseCaptcha):
""" TikTokCaptcha """
page_url: str
aid: Optional[int] = None
host: Optional[str] = None
@enforce_types
@dataclass
class TikTokCaptchaSolution(BaseCaptchaSolution):
""" TikTokCaptcha solution """
cookies: dict
================================================
FILE: unicaps/_misc/__init__.py
================================================
# -*- coding: UTF-8 -*-
"""
Miscellaneous stuff
"""
================================================
FILE: unicaps/_misc/proxy.py
================================================
# -*- coding: UTF-8 -*-
"""
Proxy Server representation
"""
import socket
from dataclasses import dataclass
from enum import Enum
from typing import Optional
def _is_ip_address(value):
try:
socket.inet_aton(value)
return True
except socket.error:
return False
class ProxyServerType(Enum):
""" Type of proxy server """
HTTP = 'http'
HTTPS = 'https'
SOCKS4 = 'socks4'
SOCKS5 = 'socks5'
@dataclass
class ProxyServer:
""" Represents Proxy server """
address: str
proxy_type: ProxyServerType = ProxyServerType.HTTP
port: int = 80
login: Optional[str] = None
password: Optional[str] = None
def __post_init__(self):
proxy_string = self.address
if '://' in proxy_string:
proxy_type, proxy_string = proxy_string.split('://')
self.proxy_type = ProxyServerType(proxy_type.lower())
if '@' in proxy_string:
credentials, proxy_string = proxy_string.split('@')
self.login, self.password = credentials.split(':', maxsplit=1)
if ':' in proxy_string:
self.address, port = proxy_string.split(':', maxsplit=1)
self.port = int(port)
else:
self.address = proxy_string
def __str__(self):
return self.get_string(including_type=True)
def get_string(self, including_type=False):
""" Get proxy as string like [://][:@]: """
proxy_string = ''
if including_type:
proxy_string += self.proxy_type.value + '://'
if self.login:
proxy_string += self.login + ':' + self.password + '@'
return proxy_string + self.address + ':' + str(self.port)
def get_ip_address(self):
""" Get IP address by hostname """
if not _is_ip_address(self.address):
return socket.gethostbyname(self.address)
return self.address
================================================
FILE: unicaps/_service/__init__.py
================================================
# -*- coding: UTF-8 -*-
"""
Certain services related stuff
"""
import enum
# pylint: disable=import-self
from . import (
anti_captcha, azcaptcha, captcha_guru, cptch_net, deathbycaptcha, rucaptcha, twocaptcha
)
class CaptchaSolvingService(enum.Enum):
""" CAPTCHA solving service enumeration """
ANTI_CAPTCHA = "anti-captcha.com"
AZCAPTCHA = "azcaptcha.com"
CAPTCHA_GURU = "cap.guru"
CPTCH_NET = "cptch.net"
DEATHBYCAPTCHA = "deathbycaptcha.com"
RUCAPTCHA = "rucaptcha.com"
TWOCAPTCHA = "2captcha.com"
# supported CAPTCHA solving services
SOLVING_SERVICE = {
CaptchaSolvingService.ANTI_CAPTCHA: anti_captcha,
CaptchaSolvingService.AZCAPTCHA: azcaptcha,
CaptchaSolvingService.CAPTCHA_GURU: captcha_guru,
CaptchaSolvingService.CPTCH_NET: cptch_net,
CaptchaSolvingService.DEATHBYCAPTCHA: deathbycaptcha,
CaptchaSolvingService.RUCAPTCHA: rucaptcha,
CaptchaSolvingService.TWOCAPTCHA: twocaptcha
}
================================================
FILE: unicaps/_service/anti_captcha.py
================================================
# -*- coding: UTF-8 -*-
"""
anti-captcha.com service
"""
import json
from .base import HTTPService
from .._transport.http_transport import HTTPRequestJSON # type: ignore
from .. import exceptions
from .._captcha import CaptchaType
from ..common import WorkerLanguage
__all__ = [
'Service', 'GetBalanceRequest', 'GetStatusRequest',
'ReportGoodRequest', 'ReportBadRequest',
'ImageCaptchaTaskRequest', 'ImageCaptchaSolutionRequest',
'RecaptchaV2TaskRequest', 'RecaptchaV2SolutionRequest',
'RecaptchaV3TaskRequest', 'RecaptchaV3SolutionRequest',
'FunCaptchaTaskRequest', 'FunCaptchaSolutionRequest',
'GeeTestTaskRequest', 'GeeTestSolutionRequest',
'GeeTestV4TaskRequest', 'GeeTestV4SolutionRequest',
'HCaptchaTaskRequest', 'HCaptchaSolutionRequest',
]
class Service(HTTPService):
""" Main service class for anti-captcha """
BASE_URL = 'https://api.anti-captcha.com'
def _post_init(self):
""" Init settings """
for captcha_type in self.settings:
self.settings[captcha_type].polling_interval = 2
if captcha_type in (CaptchaType.IMAGE,):
self.settings[captcha_type].polling_delay = 5
self.settings[captcha_type].solution_timeout = 90
else:
self.settings[captcha_type].polling_delay = 10
self.settings[captcha_type].solution_timeout = 300
class Request(HTTPRequestJSON):
""" Common Request class for anti-captcha """
def prepare(self, **kwargs) -> dict:
""" Prepares request """
request = super().prepare(**kwargs)
request.update(
dict(
method="POST",
json=dict(clientKey=self._service.api_key)
)
)
return request
def parse_response(self, response) -> dict:
""" Parses response and checks for errors """
response_data = super().parse_response(response)
error_id = response_data.pop("errorId")
if error_id == 0:
return response_data
# ############# #
# handle errors #
# ############# #
error_code = response_data.get("errorCode", f'ERROR {error_id}')
error_text = response_data.get("errorDescription", "")
error_msg = f"{error_code}: {error_text}"
# pylint: disable=no-else-raise
if error_code in ('ERROR_WRONG_USER_KEY', 'ERROR_KEY_DOES_NOT_EXIST',
'ERROR_IP_NOT_ALLOWED', 'ERROR_IP_BLOCKED'):
raise exceptions.AccessDeniedError(error_msg)
elif error_code in ('ERROR_ZERO_BALANCE',):
raise exceptions.LowBalanceError(error_msg)
elif error_code in ('ERROR_NO_SLOT_AVAILABLE',):
raise exceptions.ServiceTooBusy(error_msg)
elif error_code in ('ERROR_NO_SUCH_METHOD', 'ERROR_NO_SUCH_CAPCHA_ID', 'ERROR_TASK_ABSENT',
'ERROR_TASK_NOT_SUPPORTED', 'ERROR_FUNCAPTCHA_NOT_ALLOWED'):
raise exceptions.MalformedRequestError(error_msg)
elif error_code in ('ERROR_ZERO_CAPTCHA_FILESIZE', 'ERROR_TOO_BIG_CAPTCHA_FILESIZE',
'ERROR_WRONG_FILE_EXTENSION', 'ERROR_IMAGE_TYPE_NOT_SUPPORTED',
'ERROR_UPLOAD', 'ERROR_PAGEURL', 'ERROR_BAD_TOKEN_OR_PAGEURL',
'ERROR_GOOGLEKEY', 'ERROR_EMPTY_COMMENT',
'ERROR_INCORRECT_SESSION_DATA', 'ERROR_RECAPTCHA_INVALID_SITEKEY',
'ERROR_RECAPTCHA_INVALID_DOMAIN', 'ERROR_RECAPTCHA_OLD_BROWSER',
'ERROR_TOKEN_EXPIRED', 'ERROR_INVISIBLE_RECAPTCHA'):
raise exceptions.BadInputDataError(error_msg)
elif error_code in ('ERROR_CAPTCHAIMAGE_BLOCKED', 'ERROR_CAPTCHA_UNSOLVABLE',
'ERROR_BAD_DUPLICATES', 'ERROR_RECAPTCHA_TIMEOUT',
'ERROR_FAILED_LOADING_WIDGET'):
raise exceptions.UnableToSolveError(error_msg)
elif error_code in ('ERROR_PROXY_CONNECT_REFUSED', 'ERROR_PROXY_CONNECT_TIMEOUT',
'ERROR_PROXY_READ_TIMEOUT', 'ERROR_PROXY_BANNED',
'ERROR_PROXY_TRANSPARENT', 'ERROR_PROXY_HAS_NO_IMAGE_SUPPORT',
'ERROR_PROXY_INCOMPATIBLE_HTTP_VERSION', 'ERROR_PROXY_NOT_AUTHORISED'):
raise exceptions.ProxyError(error_msg)
raise exceptions.ServiceError(error_msg)
class GetBalanceRequest(Request):
""" GetBalance Request class """
def prepare(self) -> dict: # type: ignore
""" Prepares request """
request = super().prepare()
request.update(dict(url=self._service.BASE_URL + "/getBalance"))
return request
def parse_response(self, response) -> dict:
""" Parses response and returns task_id """
return dict(balance=float(super().parse_response(response)['balance']))
class GetStatusRequest(GetBalanceRequest):
""" GetStatus Request class """
def parse_response(self, response) -> dict:
""" Parses response and returns task_id """
try:
return super().parse_response(response)
except exceptions.UnicapsException:
return {}
class ReportGoodRequest(Request):
""" ReportGood Request class """
# pylint: disable=arguments-differ
def prepare(self, solved_captcha) -> dict: # type: ignore
""" Prepares request """
raise exceptions.UnicapsException(
"Report for good CAPTCHA is not supported by the current service!"
)
class ReportBadRequest(Request):
""" ReportBad Request class """
# pylint: disable=arguments-differ
def prepare(self, solved_captcha) -> dict: # type: ignore
""" Prepares request """
request = super().prepare(solved_captcha=solved_captcha)
captcha_type = solved_captcha.task.captcha.get_type()
if captcha_type == CaptchaType.IMAGE:
uri = "/reportIncorrectImageCaptcha"
elif captcha_type in (CaptchaType.RECAPTCHAV2, CaptchaType.RECAPTCHAV3):
uri = "/reportIncorrectRecaptcha"
else:
raise exceptions.UnicapsException(
f"Report for bad {captcha_type.value} is not supported!"
)
request.update(dict(url=self._service.BASE_URL + uri))
request["json"].update(dict(taskId=int(solved_captcha.captcha_id)))
return request
class TaskRequest(Request):
""" Request class for requests to /createTask """
# pylint: disable=arguments-differ,unused-argument
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepare a request """
request = super().prepare(
captcha=captcha,
proxy=proxy,
user_agent=user_agent,
cookies=cookies
)
request.update(dict(url=self._service.BASE_URL + "/createTask"))
request["json"].update(
dict(task={},
softId=940)
)
# add proxy
if proxy:
request["json"]["task"].update(
dict(
proxyType=proxy.proxy_type.value,
# Anti-captcha supports IP addresses only
proxyAddress=proxy.get_ip_address(),
proxyPort=proxy.port
)
)
if proxy.login:
request["json"]["task"].update(
dict(
proxyLogin=proxy.login,
proxyPassword=proxy.password
)
)
if user_agent:
request["json"]["task"]["userAgent"] = user_agent
if cookies:
request["json"]["task"]["cookies"] = '; '.join(f'{k}={v}'for k, v in cookies.items())
return request
def parse_response(self, response) -> dict:
""" Parses response and returns task_id """
response_data = super().parse_response(response)
return {"task_id": response_data.pop("taskId"),
"extra": response_data}
class SolutionRequest(Request):
""" Request class for requests to /getTaskResult """
# pylint: disable=arguments-differ
def prepare(self, task) -> dict: # type: ignore
""" Prepare a request """
request = super().prepare(task=task)
request.update(dict(url=self._service.BASE_URL + "/getTaskResult"))
request["json"].update(dict(taskId=str(task.task_id)))
return request
def parse_response(self, response) -> dict:
""" Parses response and returns solution and cost """
response_data = super().parse_response(response)
if response_data["status"] != "ready":
raise exceptions.SolutionNotReadyYet()
solution_data = response_data["solution"]
solution_class = self.source_data['task'].captcha.get_solution_class()
captcha_type = self.source_data['task'].captcha.get_type()
args = []
kwargs = {}
if captcha_type in (CaptchaType.IMAGE,):
args.append(solution_data.pop('text'))
elif captcha_type in (CaptchaType.RECAPTCHAV2, CaptchaType.RECAPTCHAV3,
CaptchaType.HCAPTCHA):
args.append(solution_data.pop('gRecaptchaResponse'))
elif captcha_type in (CaptchaType.FUNCAPTCHA,):
args.append(solution_data.pop('token'))
elif captcha_type in (CaptchaType.GEETEST, CaptchaType.GEETESTV4):
kwargs.update(solution_data)
else:
kwargs.update(solution_data)
solution = solution_class(*args, **kwargs)
if "cost" in response_data:
cost = response_data.pop("cost")
else:
cost = None
return dict(
solution=solution,
cost=cost,
extra=response_data
)
class ImageCaptchaTaskRequest(TaskRequest):
""" ImageCaptchaTask Request class """
# pylint: disable=arguments-differ,signature-differs
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepare a request """
request = super().prepare(
captcha=captcha,
proxy=None,
user_agent=None,
cookies=None
)
task_data = dict(
type="ImageToTextTask",
body=captcha.get_image_base64().decode('ascii')
)
task_data.update(
captcha.get_optional_data(
is_case_sensitive=('case', None),
is_phrase=('phrase', None),
is_math=('math', None),
char_type=('numeric', lambda v: v.value if v.value in (1, 2) else None),
min_len=('minLength', None),
max_len=('maxLength', None),
comment=('comment', None)
)
)
request['json']['task'].update(task_data)
# set workers pool language
if captcha.language:
request['json']['languagePool'] = (
'rn' if captcha.language == WorkerLanguage.RUSSIAN else 'en'
)
return request
class ImageCaptchaSolutionRequest(SolutionRequest):
""" Image CAPTCHA solution request """
class RecaptchaV2TaskRequest(TaskRequest):
""" reCAPTCHA v2 task Request class """
# pylint: disable=arguments-differ,signature-differs
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepares request """
if proxy:
kwargs = dict(captcha=captcha, proxy=proxy, user_agent=user_agent, cookies=cookies)
task_type = "RecaptchaV2EnterpriseTask" if captcha.is_enterprise else "NoCaptchaTask"
else:
kwargs = dict(captcha=captcha, proxy=None, user_agent=None, cookies=None)
task_type = ("RecaptchaV2EnterpriseTaskProxyless" if captcha.is_enterprise
else "NoCaptchaTaskProxyless")
request = super().prepare(**kwargs)
request['json']['task'].update(
dict(
type=task_type,
websiteURL=captcha.page_url,
websiteKey=captcha.site_key,
isInvisible=captcha.is_invisible
)
)
# if enterprise captcha
if captcha.is_enterprise:
request['json']['task']['enterprisePayload'] = dict(s=captcha.data_s)
else:
# set optional data if any
request['json']['task'].update(
captcha.get_optional_data(
data_s=('recaptchaDataSValue', None)
)
)
# set optional api_domain if any
request['json']['task'].update(
captcha.get_optional_data(
api_domain=('apiDomain', None)
)
)
return request
class RecaptchaV2SolutionRequest(SolutionRequest):
""" reCAPTCHA v2 solution request """
class RecaptchaV3TaskRequest(TaskRequest):
""" reCAPTCHA v3 task Request class """
# pylint: disable=arguments-differ,signature-differs
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepares request """
request = super().prepare(
captcha=captcha,
proxy=None,
user_agent=None,
cookies=None
)
request['json']['task'].update(
dict(
type="RecaptchaV3TaskProxyless",
websiteURL=captcha.page_url,
websiteKey=captcha.site_key
)
)
# set optional data if any
request['json']['task'].update(
captcha.get_optional_data(
min_score=('minScore', None),
action=('pageAction', None),
api_domain=('apiDomain', None)
)
)
# if enterprise captcha
if captcha.is_enterprise:
request['json']['task']['isEnterprise'] = True
return request
class RecaptchaV3SolutionRequest(SolutionRequest):
""" reCAPTCHA v3 solution request """
class FunCaptchaTaskRequest(TaskRequest):
""" FunCaptcha task Request class """
# pylint: disable=arguments-differ,signature-differs
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepares request """
if proxy:
kwargs = dict(captcha=captcha, proxy=proxy, user_agent=user_agent, cookies=cookies)
task_type = "FunCaptchaTask"
else:
kwargs = dict(captcha=captcha, proxy=None, user_agent=None, cookies=None)
task_type = "FunCaptchaTaskProxyless"
request = super().prepare(**kwargs)
request['json']['task'].update(
dict(
type=task_type,
websiteURL=captcha.page_url,
websitePublicKey=captcha.public_key
)
)
# set optional data if any
request['json']['task'].update(
captcha.get_optional_data(
service_url=('funcaptchaApiJSSubdomain', None),
)
)
# add blob value
if captcha.blob:
request['json']['task']['data'] = json.dumps(dict(blob=captcha.blob))
return request
class FunCaptchaSolutionRequest(SolutionRequest):
""" FunCaptcha solution request """
class GeeTestTaskRequest(TaskRequest):
""" GeeTest task Request class """
# pylint: disable=arguments-differ,signature-differs
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepares request """
if proxy:
kwargs = dict(captcha=captcha, proxy=proxy, user_agent=user_agent, cookies=cookies)
task_type = "GeeTestTask"
else:
kwargs = dict(captcha=captcha, proxy=None, user_agent=None, cookies=None)
task_type = "GeeTestTaskProxyless"
request = super().prepare(**kwargs)
request['json']['task'].update(
dict(
type=task_type,
websiteURL=captcha.page_url,
gt=captcha.gt_key,
challenge=captcha.challenge
)
)
# set optional data if any
request['json']['task'].update(
captcha.get_optional_data(
api_server=('geetestApiServerSubdomain', None),
)
)
return request
class GeeTestSolutionRequest(SolutionRequest):
""" GeeTest solution request """
class GeeTestV4TaskRequest(TaskRequest):
""" GeeTest task Request class """
# pylint: disable=arguments-differ,signature-differs
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepares request """
if proxy:
kwargs = dict(captcha=captcha, proxy=proxy, user_agent=user_agent, cookies=cookies)
task_type = "GeeTestTask"
else:
kwargs = dict(captcha=captcha, proxy=None, user_agent=None, cookies=None)
task_type = "GeeTestTaskProxyless"
request = super().prepare(**kwargs)
request['json']['task'].update(
dict(
type=task_type,
websiteURL=captcha.page_url,
gt=captcha.captcha_id,
version=4
)
)
return request
class GeeTestV4SolutionRequest(SolutionRequest):
""" GeeTest solution request """
class HCaptchaTaskRequest(TaskRequest):
""" hCaptcha task Request class """
# pylint: disable=arguments-differ,signature-differs
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepares request """
if proxy:
kwargs = dict(captcha=captcha, proxy=proxy, user_agent=user_agent, cookies=cookies)
task_type = "HCaptchaTask"
else:
kwargs = dict(captcha=captcha, proxy=None, user_agent=user_agent, cookies=None)
task_type = "HCaptchaTaskProxyless"
request = super().prepare(**kwargs)
request['json']['task'].update(
dict(
type=task_type,
websiteURL=captcha.page_url,
websiteKey=captcha.site_key,
isInvisible=captcha.is_invisible
)
)
return request
class HCaptchaSolutionRequest(SolutionRequest):
""" hCaptcha solution request """
================================================
FILE: unicaps/_service/azcaptcha.py
================================================
# -*- coding: UTF-8 -*-
"""
azcaptcha.com service
"""
from .base import HTTPService
from .._transport.http_transport import HTTPRequestJSON # type: ignore
from .. import exceptions
from .._captcha import CaptchaType
from ..common import CaptchaAlphabet
__all__ = [
'Service', 'GetBalanceRequest', 'GetStatusRequest',
'ReportGoodRequest', 'ReportBadRequest',
'ImageCaptchaTaskRequest', 'ImageCaptchaSolutionRequest',
'RecaptchaV2TaskRequest', 'RecaptchaV2SolutionRequest',
'RecaptchaV3TaskRequest', 'RecaptchaV3SolutionRequest',
'HCaptchaTaskRequest', 'HCaptchaSolutionRequest',
'FunCaptchaTaskRequest', 'FunCaptchaSolutionRequest'
]
class Service(HTTPService):
""" Main service class for 2captcha """
BASE_URL = 'http://azcaptcha.com'
def _post_init(self):
""" Init settings """
for captcha_type in self.settings:
self.settings[captcha_type].polling_interval = 5
self.settings[captcha_type].solution_timeout = 180
if captcha_type in (CaptchaType.RECAPTCHAV2, CaptchaType.HCAPTCHA):
self.settings[captcha_type].polling_delay = 20
self.settings[captcha_type].solution_timeout = 300
elif captcha_type in (CaptchaType.RECAPTCHAV3,):
self.settings[captcha_type].polling_delay = 15
else:
self.settings[captcha_type].polling_delay = 5
class Request(HTTPRequestJSON):
""" Common Request class for 2captcha """
def parse_response(self, response) -> dict:
""" Parse response and checks for errors """
response_data = super().parse_response(response)
if response_data.pop("status") == 1:
return response_data
###############
# handle errors
###############
error_code = response_data["request"]
error_text = response_data.get("error_text", "")
error_msg = f"{error_code}: {error_text}"
if error_code == 'CAPCHA_NOT_READY': # pylint: disable=no-else-raise
raise exceptions.SolutionNotReadyYet()
elif error_code in ('ERROR_WRONG_USER_KEY', 'ERROR_KEY_DOES_NOT_EXIST',
'ERROR_IP_NOT_ALLOWED', 'IP_BANNED'):
raise exceptions.AccessDeniedError(error_msg)
elif error_code in ('ERROR_ZERO_BALANCE',):
raise exceptions.LowBalanceError(error_msg)
elif error_code in ('ERROR_NO_SLOT_AVAILABLE',):
# If server returns ERROR_NO_SLOT_AVAILABLE make a 5 seconds timeout before sending
# next request.
# time.sleep(5)
raise exceptions.ServiceTooBusy(error_msg)
elif error_code in ('MAX_USER_TURN',) or error_code.startswith('ERROR:'):
raise exceptions.TooManyRequestsError(error_msg)
elif error_code in ('ERROR_WRONG_ID_FORMAT', 'ERROR_WRONG_CAPTCHA_ID'):
raise exceptions.MalformedRequestError(error_msg)
elif error_code in ('ERROR_ZERO_CAPTCHA_FILESIZE', 'ERROR_TOO_BIG_CAPTCHA_FILESIZE',
'ERROR_WRONG_FILE_EXTENSION', 'ERROR_IMAGE_TYPE_NOT_SUPPORTED',
'ERROR_UPLOAD', 'ERROR_PAGEURL', 'ERROR_BAD_TOKEN_OR_PAGEURL',
'ERROR_GOOGLEKEY', 'ERROR_BAD_PARAMETERS', 'ERROR_TOKEN_EXPIRED',
'ERROR_EMPTY_ACTION'):
raise exceptions.BadInputDataError(error_msg)
elif error_code in ('ERROR_CAPTCHAIMAGE_BLOCKED', 'ERROR_CAPTCHA_UNSOLVABLE',
'ERROR_BAD_DUPLICATES'):
raise exceptions.UnableToSolveError(error_msg)
elif error_code in ('ERROR_BAD_PROXY', 'ERROR_PROXY_CONNECTION_FAILED'):
raise exceptions.ProxyError(error_msg)
raise exceptions.ServiceError(error_msg)
class InRequest(Request):
""" Request class for requests to /in.php """
def prepare(self, **kwargs) -> dict:
""" Prepare request """
request = super().prepare(**kwargs)
request.update(
dict(
method="POST",
url=self._service.BASE_URL + "/in.php",
data=dict(
key=self._service.api_key,
json=1,
# soft_id=2738
)
)
)
# azcaptcha.com doesn't like headers - returns ERROR_UPLOAD
if 'headers' in request:
del request['headers']
return request
class ResRequest(Request):
""" Request class for requests to /res.php """
def prepare(self, **kwargs) -> dict:
""" Prepare request """
request = super().prepare(**kwargs)
request.update(
dict(
method="GET",
url=self._service.BASE_URL + "/res.php",
params=dict(
key=self._service.api_key,
json=1
)
)
)
# azcaptcha.com doesn't like headers - returns ERROR_UPLOAD
if 'headers' in request:
del request['headers']
return request
class GetBalanceRequest(ResRequest):
""" GetBalance Request class """
def prepare(self) -> dict: # type: ignore
""" Prepare request """
request = super().prepare()
request["params"].update(dict(action="getbalance"))
return request
def parse_response(self, response) -> dict:
""" Parse response and return balance """
return {'balance': float(super().parse_response(response)["request"])}
class GetStatusRequest(GetBalanceRequest):
""" GetStatus Request class """
def parse_response(self, response) -> dict:
""" Parse response and return status """
try:
return super().parse_response(response)
except exceptions.UnicapsException:
return {}
class ReportGoodRequest(ResRequest):
""" ReportGood Request class """
# pylint: disable=arguments-differ
def prepare(self, solved_captcha) -> dict: # type: ignore
""" Prepare request """
request = super().prepare(solved_captcha=solved_captcha)
request["params"].update(
dict(
action="reportgood",
id=solved_captcha.captcha_id
)
)
return request
class ReportBadRequest(ResRequest):
""" ReportBad Request class """
# pylint: disable=arguments-differ
def prepare(self, solved_captcha) -> dict: # type: ignore
""" Prepare request """
request = super().prepare(solved_captcha=solved_captcha)
request["params"].update(
dict(
action="reportbad",
id=solved_captcha.captcha_id
)
)
return request
class TaskRequest(InRequest):
""" Common Task Request class """
# pylint: disable=arguments-differ,unused-argument
def prepare(self, captcha, proxy, user_agent, cookies):
request = super().prepare(
captcha=captcha,
proxy=proxy,
user_agent=user_agent,
cookies=cookies
)
if proxy:
request['data'].update(
dict(
proxy=proxy.get_string(),
proxytype=proxy.proxy_type.value.upper()
)
)
if cookies:
request['data']['cookies'] = ';'.join([f'{k}:{v}' for k, v in cookies.items()])
if user_agent:
request['data']['userAgent'] = user_agent
return request
def parse_response(self, response) -> dict:
""" Parse response and return task_id """
response_data = super().parse_response(response)
return dict(
task_id=response_data.pop("request"),
extra=response_data
)
class SolutionRequest(ResRequest):
""" Common Solution Request class """
# pylint: disable=arguments-differ
def prepare(self, task) -> dict: # type: ignore
""" Prepare a request """
request = super().prepare(task=task)
request["params"].update(
dict(action="get", id=task.task_id)
)
return request
def parse_response(self, response) -> dict:
""" Parse response and return solution and cost """
response_data = super().parse_response(response)
# get solution class
solution_class = self.source_data['task'].captcha.get_solution_class()
return dict(
solution=solution_class(response_data.pop("request")),
cost=None,
extra=response_data
)
class ImageCaptchaTaskRequest(TaskRequest):
""" ImageCaptchaTask Request class """
# pylint: disable=arguments-differ,unused-argument,signature-differs
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepare request """
request = super().prepare(
captcha=captcha,
proxy=None,
user_agent=None,
cookies=None
)
# add required params
request['data'].update(
dict(
method="base64",
body=captcha.get_image_base64().decode('ascii')
)
)
# add optional params
request['data'].update(
captcha.get_optional_data(
is_phrase=('phrase', lambda v: int(bool(v))),
is_case_sensitive=('regsense', lambda v: int(bool(v))),
char_type=('numeric', lambda v: v.value),
is_math=('calc', lambda v: int(bool(v))),
min_len=('min_len', None),
max_len=('max_len', None),
alphabet=('language',
lambda v: {CaptchaAlphabet.CYRILLIC: 1,
CaptchaAlphabet.LATIN: 2}.get(v, 0)),
language=('lang', lambda v: v.value),
comment=('textinstructions', None),
)
)
return request
class ImageCaptchaSolutionRequest(SolutionRequest):
""" Image CAPTCHA solution request """
class RecaptchaV2TaskRequest(TaskRequest):
""" reCAPTCHA v2 task request """
# pylint: disable=arguments-differ,signature-differs
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepare request """
request = super().prepare(
captcha=captcha,
proxy=proxy,
user_agent=user_agent,
cookies=cookies
)
request['data'].update(
dict(
method="userrecaptcha",
googlekey=captcha.site_key,
pageurl=captcha.page_url,
invisible=int(captcha.is_invisible)
)
)
# add optional params
request['data'].update(
captcha.get_optional_data(
data_s=('data-s', None)
)
)
return request
class RecaptchaV2SolutionRequest(SolutionRequest):
""" reCAPTCHA v2 solution request """
class RecaptchaV3TaskRequest(TaskRequest):
""" reCAPTCHA v3 task request """
# pylint: disable=arguments-differ,signature-differs
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepare request """
request = super().prepare(
captcha=captcha,
proxy=proxy,
user_agent=user_agent,
cookies=cookies
)
request['data'].update(
dict(
method="userrecaptcha",
version="v3",
googlekey=captcha.site_key,
pageurl=captcha.page_url,
)
)
# add optional params
request['data'].update(
captcha.get_optional_data(
action=('action', None),
min_score=('min_score', None)
)
)
return request
class RecaptchaV3SolutionRequest(SolutionRequest):
""" reCAPTCHA v3 solution request """
class HCaptchaTaskRequest(TaskRequest):
""" HCaptcha task request """
# pylint: disable=arguments-differ,signature-differs
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepare request """
request = super().prepare(
captcha=captcha,
proxy=proxy,
user_agent=user_agent,
cookies=cookies
)
request['data'].update(
dict(
method="hcaptcha",
sitekey=captcha.site_key,
pageurl=captcha.page_url
)
)
return request
class HCaptchaSolutionRequest(SolutionRequest):
""" HCaptcha solution request """
class FunCaptchaTaskRequest(TaskRequest):
""" FunCaptcha task request """
# pylint: disable=arguments-differ,signature-differs
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepare request """
request = super().prepare(
captcha=captcha,
proxy=proxy,
user_agent=user_agent,
cookies=cookies
)
request['data'].update(
dict(
method="funcaptcha",
publickey=captcha.public_key,
pageurl=captcha.page_url
)
)
# add optional params
request['data'].update(
captcha.get_optional_data(
service_url=('surl', None),
blob=('data[blob]', None),
)
)
return request
class FunCaptchaSolutionRequest(SolutionRequest):
""" FunCaptcha solution request """
================================================
FILE: unicaps/_service/base.py
================================================
# -*- coding: UTF-8 -*-
"""
Base service stuff
"""
import asyncio
import time
from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import datetime
from inspect import getmodule
from timeit import default_timer as timer
from typing import Dict, Optional, Tuple
from .._transport.http_transport import StandardHTTPTransport # type: ignore
from .._captcha import CaptchaType
from .._captcha.base import BaseCaptcha, BaseCaptchaSolution
from .._misc.proxy import ProxyServer
from ..exceptions import UnicapsException, SolutionWaitTimeout, SolutionNotReadyYet
class BaseService(ABC):
""" Base class for all services """
def __init__(self, api_key: str):
self.api_key = api_key
self._transport = self._init_transport()
self._module = getmodule(self)
self._settings = {captcha_type: Settings() for captcha_type in self.supported_captchas}
self._post_init()
@abstractmethod
def _init_transport(self):
pass
def _post_init(self):
pass
def _make_request(self, request_class, *args):
request_class = request_class + "Request"
if not hasattr(self._module, request_class):
raise UnicapsException(f"{request_class} is not supported by the current service!")
request = getattr(self._module, request_class)(self)
return self._transport.make_request(request, *args)
async def _make_request_async(self, request_class, *args):
request_class = request_class + "Request"
if not hasattr(self._module, request_class):
raise UnicapsException(f"{request_class} is not supported by the current service!")
request = getattr(self._module, request_class)(self)
return await self._transport.make_request_async(request, *args)
@property
def supported_captchas(self) -> Tuple[CaptchaType, ...]:
""" List of supported captchas """
captchas = []
for captcha_type in CaptchaType:
if hasattr(self._module, captcha_type.value + "TaskRequest"):
captchas.append(captcha_type)
return tuple(captchas)
@property
def settings(self) -> Dict[CaptchaType, 'Settings']:
""" Service settings """
return self._settings
def solve_captcha(self, captcha: BaseCaptcha, proxy: Optional[ProxyServer] = None,
user_agent: Optional[str] = None,
cookies: Optional[Dict[str, str]] = None) -> 'SolvedCaptcha':
""" Solves captcha and returns SolvedCaptcha object """
start_time = datetime.now()
task = self.create_task(captcha, proxy, user_agent, cookies)
solution, cost, extra = self.wait_for_solution(task)
end_time = datetime.now()
return SolvedCaptcha(task, solution, start_time, end_time,
cost=cost, extra=extra)
async def solve_captcha_async(self, captcha: BaseCaptcha, proxy: Optional[ProxyServer] = None,
user_agent: Optional[str] = None,
cookies: Optional[Dict[str, str]] = None) -> 'AsyncSolvedCaptcha':
""" Solves captcha and returns SolvedCaptcha object (async) """
start_time = datetime.now()
task = await self.create_task_async(captcha, proxy, user_agent, cookies)
solution, cost, extra = await self.wait_for_solution_async(task)
end_time = datetime.now()
return AsyncSolvedCaptcha(task, solution, start_time, end_time,
cost=cost, extra=extra)
def create_task(self, captcha: BaseCaptcha, proxy: Optional[ProxyServer] = None,
user_agent: Optional[str] = None,
cookies: Optional[Dict[str, str]] = None) -> 'CaptchaTask':
""" Creates task for solving a CAPTCHA """
captcha_type = captcha.get_type()
if captcha_type not in self.supported_captchas:
raise UnicapsException(f"{captcha_type} is not supported by the current service!")
result = self._make_request(
f"{captcha_type.value}Task", captcha, proxy, user_agent, cookies
)
task_id = str(result["task_id"])
return CaptchaTask(self, captcha, task_id, result.get("extra"))
async def create_task_async(self, captcha: BaseCaptcha, proxy: Optional[ProxyServer] = None,
user_agent: Optional[str] = None,
cookies: Optional[Dict[str, str]] = None) -> 'AsyncCaptchaTask':
""" Creates CAPTCHA solving task (async) """
captcha_type = captcha.get_type()
if captcha_type not in self.supported_captchas:
raise UnicapsException(f"{captcha_type} is not supported by the current service!")
result = await self._make_request_async(
f"{captcha_type.value}Task", captcha, proxy, user_agent, cookies
)
task_id = str(result["task_id"])
return AsyncCaptchaTask(self, captcha, task_id, result.get("extra"))
def get_task_result(self, task: 'CaptchaTask') -> Tuple[BaseCaptchaSolution,
Optional[float], Dict]:
""" Returns CAPTCHA solution """
result = self._make_request(f"{task.captcha.get_type().value}Solution", task)
return (
result['solution'], # type: ignore
float(result['cost']) if result.get('cost') else None,
result.get("extra") or {}
)
async def get_task_result_async(self, task: 'CaptchaTask') -> Tuple[BaseCaptchaSolution,
Optional[float], Dict]:
""" Returns CAPTCHA solution """
result = await self._make_request_async(f"{task.captcha.get_type().value}Solution", task)
return (
result['solution'], # type: ignore
float(result['cost']) if result.get('cost') else None,
result.get("extra") or {}
)
def wait_for_solution(self, task) -> Tuple[BaseCaptchaSolution, Optional[float], Dict]:
""" Wait for CAPTCHA solution """
settings = self._settings[task.captcha.get_type()]
start_time = timer()
time.sleep(settings.polling_delay)
while True:
if timer() - start_time > settings.solution_timeout:
raise SolutionWaitTimeout(
f"Couldn't receive a solution in {settings.solution_timeout} seconds!"
)
try:
return task.get_result()
except SolutionNotReadyYet:
time.sleep(settings.polling_interval)
async def wait_for_solution_async(self, task) -> Tuple[BaseCaptchaSolution,
Optional[float], Dict]:
""" Wait for CAPTCHA solution """
settings = self._settings[task.captcha.get_type()]
start_time = timer()
await asyncio.sleep(settings.polling_delay)
while True:
if timer() - start_time > settings.solution_timeout:
raise SolutionWaitTimeout(
f"Couldn't receive a solution in {settings.solution_timeout} seconds!"
)
try:
return await task.get_result()
except SolutionNotReadyYet:
await asyncio.sleep(settings.polling_interval)
def get_balance(self):
""" Get account balance """
balance = self._make_request("GetBalance").get('balance')
if balance is not None:
balance = float(balance)
return balance
async def get_balance_async(self):
""" Get account balance """
response = await self._make_request_async("GetBalance")
balance = response.get('balance')
if balance is not None:
balance = float(balance)
return balance
def get_status(self) -> bool:
""" Get service status """
return bool(self._make_request("GetStatus"))
async def get_status_async(self) -> bool:
""" Get service status """
return bool(await self._make_request_async("GetStatus"))
def report_good(self, solved_captcha: 'SolvedCaptcha', raise_exc: bool = False) -> bool:
""" Report good CAPTCHA """
result = False
try:
result = self._make_request("ReportGood", solved_captcha)
except UnicapsException:
if raise_exc:
raise
return bool(result)
async def report_good_async(self, solved_captcha: 'SolvedCaptcha',
raise_exc: bool = False) -> bool:
""" Report good CAPTCHA """
result = False
try:
result = await self._make_request_async("ReportGood", solved_captcha)
except UnicapsException:
if raise_exc:
raise
return bool(result)
def report_bad(self, solved_captcha: 'SolvedCaptcha', raise_exc: bool = False) -> bool:
""" Report bad CAPTCHA """
result = False
try:
result = self._make_request("ReportBad", solved_captcha)
except UnicapsException:
if raise_exc:
raise
return bool(result)
async def report_bad_async(self, solved_captcha: 'SolvedCaptcha',
raise_exc: bool = False) -> bool:
""" Report bad CAPTCHA """
result = False
try:
result = await self._make_request_async("ReportBad", solved_captcha)
except UnicapsException:
if raise_exc:
raise
return bool(result)
@abstractmethod
def close(self):
""" Close connections """
@abstractmethod
async def close_async(self):
""" Close connections (async) """
class HTTPService(BaseService):
""" Standard HTTP Service """
def _init_transport(self):
return StandardHTTPTransport()
def close(self):
""" Close connections """
self._transport.close()
async def close_async(self):
""" Close connections (async) """
await self._transport.close_async()
@dataclass
class Settings:
""" Service settings """
polling_delay: int = 5 # seconds before starting to check for sollution
polling_interval: int = 2 # seconds between checks
solution_timeout: int = 300 # seconds is solution timeout
class CaptchaTask:
""" Task for CAPTCHA solving """
def __init__(self, service, captcha: BaseCaptcha, task_id: str, extra: Dict = None):
self._service = service
self._captcha = captcha
self._task_id = task_id
self._extra = extra or {}
self._result = None
@property
def task_id(self) -> str:
""" Task ID """
return self._task_id
@property
def captcha(self) -> BaseCaptcha:
""" Source CAPTCHA """
return self._captcha
@property
def extra(self) -> Dict:
""" Task extra data """
return self._extra
def get_result(self) -> Optional[BaseCaptchaSolution]:
""" Gets solution """
if self._result is None:
self._result = self._service.get_task_result(self)
return self._result
def is_done(self) -> bool:
""" Checks if solution is ready """
return bool(self._result)
def wait(self) -> BaseCaptchaSolution:
""" Waits for solution """
return self._service.wait_for_solution(self)
class AsyncCaptchaTask(CaptchaTask):
""" Task for CAPTCHA solving """
async def get_result(self) -> Optional[BaseCaptchaSolution]: # type: ignore
""" Gets solution """
if self._result is None:
self._result = await self._service.get_task_result_async(self)
return self._result
async def wait(self) -> BaseCaptchaSolution: # type: ignore
""" Waits for solution """
return await self._service.wait_for_solution_async(self)
class SolvedCaptcha:
""" Solved CAPTCHA object """
def __init__(self, task: CaptchaTask, solution: BaseCaptchaSolution, start_time: datetime,
end_time: datetime, cost: Optional[float] = None, cookies: Optional[dict] = None,
extra: dict = None):
if not task.is_done():
raise UnicapsException("CAPTCHA is not solved yet!")
self._task = task
self._solution = solution
self._start_time = start_time
self._end_time = end_time
self._cost = cost
self._cookies = cookies or {}
self._extra = extra or {}
@property
def captcha_id(self) -> str:
""" CAPTCHA ID (usually it's the same as task ID) """
return self._task.task_id
@property
def task(self) -> CaptchaTask:
""" Task for solving """
return self._task
@property
def solution(self) -> BaseCaptchaSolution:
""" CAPTCHA solution """
return self._solution
@property
def start_time(self) -> datetime:
""" Start solving at """
return self._start_time
@property
def end_time(self) -> datetime:
""" End solving at """
return self._end_time
@property
def cost(self) -> Optional[float]:
""" The cost of solved CAPTCHA """
return self._cost
@property
def cookies(self) -> dict:
""" Cookies """
return self._cookies
@property
def extra(self) -> dict:
""" Extra data from the service """
return self._extra
def report_good(self, raise_exc: bool = False) -> bool:
""" Report good CAPTCHA """
# pylint: disable=protected-access
return self._task._service.report_good(self, raise_exc=raise_exc)
def report_bad(self, raise_exc: bool = False) -> bool:
""" Report bad CAPTCHA """
# pylint: disable=protected-access
return self._task._service.report_bad(self, raise_exc=raise_exc)
class AsyncSolvedCaptcha(SolvedCaptcha):
""" Solved CAPTCHA object (async) """
async def report_good(self, raise_exc: bool = False) -> bool: # type: ignore
""" Report good CAPTCHA """
# pylint: disable=protected-access
return await self._task._service.report_good_async(self, raise_exc=raise_exc)
async def report_bad(self, raise_exc: bool = False) -> bool: # type: ignore
""" Report bad CAPTCHA """
# pylint: disable=protected-access
return await self._task._service.report_bad_async(self, raise_exc=raise_exc)
================================================
FILE: unicaps/_service/captcha_guru.py
================================================
"""
cap.guru service
"""
# pylint: disable=unused-import
from .twocaptcha import (
Service as Service2Captcha, GetBalanceRequest, GetStatusRequest,
ReportGoodRequest, ReportBadRequest,
ImageCaptchaTaskRequest, ImageCaptchaSolutionRequest,
RecaptchaV2TaskRequest, RecaptchaV2SolutionRequest,
RecaptchaV3TaskRequest, RecaptchaV3SolutionRequest,
HCaptchaTaskRequest, HCaptchaSolutionRequest,
GeeTestTaskRequest, GeeTestSolutionRequest
)
__all__ = [
'Service', 'GetBalanceRequest', 'GetStatusRequest',
'ReportGoodRequest', 'ReportBadRequest',
'ImageCaptchaTaskRequest', 'ImageCaptchaSolutionRequest',
'RecaptchaV2TaskRequest', 'RecaptchaV2SolutionRequest',
'RecaptchaV3TaskRequest', 'RecaptchaV3SolutionRequest',
'HCaptchaTaskRequest', 'HCaptchaSolutionRequest',
'GeeTestTaskRequest', 'GeeTestSolutionRequest'
]
class Service(Service2Captcha):
""" Main service class for cap.guru """
BASE_URL = 'http://api.cap.guru'
def _decorator(cls):
""" Decorator for *TaskRequest class """
# pylint: disable=missing-function-docstring
class Wrapper:
""" A wrapper for *TaskRequest class """
def __init__(self, *args, **kwargs):
self.decorated_obj = cls(*args, **kwargs)
def prepare(self, *args, **kwargs):
result = self.decorated_obj.prepare(*args, **kwargs)
if 'data' in result:
result['params'] = result.pop('data')
result['method'] = 'GET'
if 'soft_id' in result['params']:
del result['params']['soft_id']
result['params']['softguru'] = '127872'
return result
def parse_response(self, *args, **kwargs):
return self.decorated_obj.parse_response(*args, **kwargs)
def process_response(self, *args, **kwargs):
return self.decorated_obj.process_response(*args, **kwargs)
return Wrapper
ImageCaptchaTaskRequest = _decorator(ImageCaptchaTaskRequest) # type: ignore
RecaptchaV2TaskRequest = _decorator(RecaptchaV2TaskRequest) # type: ignore
RecaptchaV3TaskRequest = _decorator(RecaptchaV3TaskRequest) # type: ignore
HCaptchaTaskRequest = _decorator(HCaptchaTaskRequest) # type: ignore
GeeTestTaskRequest = _decorator(GeeTestTaskRequest) # type: ignore
================================================
FILE: unicaps/_service/cptch_net.py
================================================
# -*- coding: UTF-8 -*-
"""
cptch.net service
"""
from .base import HTTPService
from .._transport.http_transport import HTTPRequestJSON # type: ignore
from .. import exceptions
from .._captcha import CaptchaType
from ..common import CaptchaAlphabet
__all__ = [
'Service', 'GetBalanceRequest', 'GetStatusRequest',
'ReportGoodRequest', 'ReportBadRequest',
'ImageCaptchaTaskRequest', 'ImageCaptchaSolutionRequest',
'RecaptchaV2TaskRequest', 'RecaptchaV2SolutionRequest',
'RecaptchaV3TaskRequest', 'RecaptchaV3SolutionRequest'
]
class Service(HTTPService):
""" Main service class for 2captcha """
BASE_URL = 'https://cptch.net'
def _post_init(self):
""" Init settings """
for captcha_type in self.settings:
self.settings[captcha_type].polling_interval = 5
self.settings[captcha_type].solution_timeout = 180
if captcha_type in (CaptchaType.RECAPTCHAV2,):
self.settings[captcha_type].polling_delay = 20
self.settings[captcha_type].solution_timeout = 300
elif captcha_type in (CaptchaType.RECAPTCHAV3,):
self.settings[captcha_type].polling_delay = 15
else:
self.settings[captcha_type].polling_delay = 5
class Request(HTTPRequestJSON):
""" Common Request class for 2captcha """
def parse_response(self, response) -> dict:
""" Parse response and checks for errors """
response_data = super().parse_response(response)
if response_data.pop("status") == 1:
return response_data
###############
# handle errors
###############
error_code = response_data["request"]
error_text = response_data.get("error_text", "")
error_msg = f"{error_code}: {error_text}"
if error_code == 'CAPCHA_NOT_READY': # pylint: disable=no-else-raise
raise exceptions.SolutionNotReadyYet()
elif error_code in ('ERROR_WRONG_USER_KEY', 'ERROR_KEY_DOES_NOT_EXIST',
'ERROR_IP_NOT_ALLOWED', 'IP_BANNED'):
raise exceptions.AccessDeniedError(error_msg)
elif error_code in ('ERROR_ZERO_BALANCE',):
raise exceptions.LowBalanceError(error_msg)
elif error_code in ('ERROR_NO_SLOT_AVAILABLE',):
# If server returns ERROR_NO_SLOT_AVAILABLE make a 5 seconds timeout before sending
# next request.
# time.sleep(5)
raise exceptions.ServiceTooBusy(error_msg)
elif error_code in ('MAX_USER_TURN',) or error_code.startswith('ERROR:'):
raise exceptions.TooManyRequestsError(error_msg)
elif error_code in ('ERROR_WRONG_ID_FORMAT', 'ERROR_WRONG_CAPTCHA_ID'):
raise exceptions.MalformedRequestError(error_msg)
elif error_code in ('ERROR_ZERO_CAPTCHA_FILESIZE', 'ERROR_TOO_BIG_CAPTCHA_FILESIZE',
'ERROR_WRONG_FILE_EXTENSION', 'ERROR_IMAGE_TYPE_NOT_SUPPORTED',
'ERROR_UPLOAD', 'ERROR_PAGEURL', 'ERROR_BAD_TOKEN_OR_PAGEURL',
'ERROR_GOOGLEKEY', 'ERROR_BAD_PARAMETERS', 'ERROR_TOKEN_EXPIRED',
'ERROR_EMPTY_ACTION', 'ERROR'):
raise exceptions.BadInputDataError(error_msg)
elif error_code in ('ERROR_CAPTCHAIMAGE_BLOCKED', 'ERROR_CAPTCHA_UNSOLVABLE',
'ERROR_BAD_DUPLICATES'):
raise exceptions.UnableToSolveError(error_msg)
raise exceptions.ServiceError(error_msg)
class InRequest(Request):
""" Request class for requests to /in.php """
def prepare(self, **kwargs) -> dict:
""" Prepare request """
request = super().prepare(**kwargs)
request.update(
dict(
method="POST",
url=self._service.BASE_URL + "/in.php",
data=dict(
key=self._service.api_key,
json=1,
soft_id="164"
)
)
)
# cptch.net doesn't like headers - returns ERROR_UPLOAD
if 'headers' in request:
del request['headers']
return request
class ResRequest(Request):
""" Request class for requests to /res.php """
def prepare(self, **kwargs) -> dict:
""" Prepare request """
request = super().prepare(**kwargs)
request.update(
dict(
method="GET",
url=self._service.BASE_URL + "/res.php",
params=dict(
key=self._service.api_key,
json=1
)
)
)
# cptch.net doesn't like headers - returns ERROR_UPLOAD
if 'headers' in request:
del request['headers']
return request
class GetBalanceRequest(ResRequest):
""" GetBalance Request class """
def prepare(self) -> dict: # type: ignore
""" Prepare request """
request = super().prepare()
request["params"].update(dict(action="getbalance"))
return request
def parse_response(self, response) -> dict:
""" Parse response and return balance """
return {'balance': float(super().parse_response(response)["request"])}
class GetStatusRequest(GetBalanceRequest):
""" GetStatus Request class """
def parse_response(self, response) -> dict:
""" Parse response and return status """
try:
return super().parse_response(response)
except exceptions.UnicapsException:
return {}
class ReportGoodRequest(ResRequest):
""" ReportGood Request class """
# pylint: disable=arguments-differ
def prepare(self, solved_captcha) -> dict: # type: ignore
""" Prepare request """
request = super().prepare(solved_captcha=solved_captcha)
request["params"].update(
dict(
action="reportgood",
id=solved_captcha.captcha_id
)
)
return request
class ReportBadRequest(ResRequest):
""" ReportBad Request class """
# pylint: disable=arguments-differ
def prepare(self, solved_captcha) -> dict: # type: ignore
""" Prepare request """
request = super().prepare(solved_captcha=solved_captcha)
request["params"].update(
dict(
action="reportbad",
id=solved_captcha.captcha_id
)
)
return request
class TaskRequest(InRequest):
""" Common Task Request class """
def parse_response(self, response) -> dict:
""" Parse response and return task_id """
response_data = super().parse_response(response)
return dict(
task_id=response_data.pop("request"),
extra=response_data
)
class SolutionRequest(ResRequest):
""" Common Solution Request class """
# pylint: disable=arguments-differ
def prepare(self, task) -> dict: # type: ignore
""" Prepare request """
request = super().prepare(task=task)
request["params"].update(
dict(action="get2", id=task.task_id)
)
return request
def parse_response(self, response) -> dict:
""" Parse response and return solution and cost """
response_data = super().parse_response(response)
# get solution class
solution_class = self.source_data['task'].captcha.get_solution_class()
# get token and captcha cost
token, cost = response_data["request"].rsplit('|', maxsplit=1)
return dict(
solution=solution_class(token),
cost=cost,
extra=response_data
)
class ImageCaptchaTaskRequest(TaskRequest):
""" ImageCaptchaTask Request class """
# pylint: disable=arguments-differ,unused-argument,signature-differs
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepare request """
request = super().prepare(
captcha=captcha,
proxy=proxy,
user_agent=user_agent,
cookies=cookies
)
# add required params
request['data'].update(
dict(
method="base64",
body=captcha.get_image_base64().decode('ascii')
)
)
# add optional params
request['data'].update(
captcha.get_optional_data(
is_phrase=('phrase', lambda v: int(bool(v))),
is_case_sensitive=('regsense', lambda v: int(bool(v))),
char_type=('numeric', lambda v: v.value),
is_math=('calc', lambda v: int(bool(v))),
min_len=('min_len', None),
max_len=('max_len', None),
alphabet=('language',
lambda v: {CaptchaAlphabet.CYRILLIC: 1,
CaptchaAlphabet.LATIN: 2}.get(v, 0)),
language=('lang', lambda v: v.value),
comment=('textinstructions', None),
)
)
return request
class ImageCaptchaSolutionRequest(SolutionRequest):
""" Image CAPTCHA solution request """
class RecaptchaV2TaskRequest(TaskRequest):
""" reCAPTCHA v2 task request """
# pylint: disable=arguments-differ,signature-differs
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepare request """
request = super().prepare(
captcha=captcha,
proxy=proxy,
user_agent=user_agent,
cookies=cookies
)
request['data'].update(
dict(
method="userrecaptcha",
googlekey=captcha.site_key,
pageurl=captcha.page_url,
invisible=int(captcha.is_invisible)
)
)
return request
class RecaptchaV2SolutionRequest(SolutionRequest):
""" reCAPTCHA v2 solution request """
class RecaptchaV3TaskRequest(TaskRequest):
""" reCAPTCHA v3 task request """
# pylint: disable=arguments-differ,signature-differs
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepare request """
request = super().prepare(
captcha=captcha,
proxy=proxy,
user_agent=user_agent,
cookies=cookies
)
request['data'].update(
dict(
method="userrecaptcha",
version="v3",
googlekey=captcha.site_key,
pageurl=captcha.page_url,
)
)
# add optional params
request['data'].update(
captcha.get_optional_data(
action=('action', None),
min_score=('min_score', None)
)
)
return request
class RecaptchaV3SolutionRequest(SolutionRequest):
""" reCAPTCHA v3 solution request """
================================================
FILE: unicaps/_service/deathbycaptcha.py
================================================
# -*- coding: UTF-8 -*-
"""
deathbycaptcha.com service
"""
import json
from .base import HTTPService
from .._transport.http_transport import HTTPRequestJSON # type: ignore
from .. import exceptions
from .._captcha import CaptchaType
__all__ = [
'Service', 'GetBalanceRequest', 'GetStatusRequest',
'ReportGoodRequest', 'ReportBadRequest',
'ImageCaptchaTaskRequest', 'ImageCaptchaSolutionRequest',
'RecaptchaV2TaskRequest', 'RecaptchaV2SolutionRequest',
'RecaptchaV3TaskRequest', 'RecaptchaV3SolutionRequest',
'FunCaptchaTaskRequest', 'FunCaptchaSolutionRequest',
'HCaptchaTaskRequest', 'HCaptchaSolutionRequest'
]
class Service(HTTPService):
""" Main service class for deathbycaptcha """
BASE_URL = 'http://api.dbcapi.me/api'
def _post_init(self):
""" Init settings """
self._transport.settings['handle_http_errors'] = False
for captcha_type in self.settings:
self.settings[captcha_type].polling_delay = 5
self.settings[captcha_type].polling_interval = 2
self.settings[captcha_type].solution_timeout = 180
if captcha_type in (CaptchaType.RECAPTCHAV2, CaptchaType.HCAPTCHA):
self.settings[captcha_type].polling_delay = 15
self.settings[captcha_type].solution_timeout = 200
elif captcha_type in (CaptchaType.RECAPTCHAV3,):
self.settings[captcha_type].polling_delay = 15
class Request(HTTPRequestJSON):
""" Common Request class for deathbycaptcha """
def prepare(self, **kwargs) -> dict:
""" Prepare the request """
request = super().prepare(**kwargs)
method = kwargs.get('method', 'GET')
data_or_params = 'data' if method == 'POST' else 'params'
request.update({
'method': kwargs.get('method', 'GET'),
'url': self._service.BASE_URL + kwargs.get('url', ''),
data_or_params: dict(
authtoken=self._service.api_key
)
})
return request
def parse_response(self, response) -> dict:
""" Parse response and check for errors """
response_data = super().parse_response(response)
status = response_data.get('status')
if (response.status_code == 303 or response.is_success) and status == 0:
response_data.pop('status')
return response_data
#################
# handle errors #
#################
if response_data.get('error'):
error_text = response_data['error']
elif response.is_error:
error_text = f'[{response.status_code} {response.reason_phrase}]'
else:
error_text = 'Unknown error'
error_msg = f"{status}: {error_text}"
if error_text in ('token authentication disabled', 'not-logged-in', 'banned'):
raise exceptions.AccessDeniedError(error_msg)
if error_text in ('insufficient-funds',):
raise exceptions.LowBalanceError(error_msg)
if error_text in ('service-overload',):
raise exceptions.ServiceTooBusy(error_msg)
if error_text in ('upload-failed', 'invalid-captcha'):
raise exceptions.MalformedRequestError(error_msg)
if error_text in ('ERROR_PAGEURL', 'Invalid base64-encoded CAPTCHA',
'Not a (CAPTCHA) image', 'Empty CAPTCHA image', 'ERROR_GOOGLEKEY',
'ERROR_PAGEURL', 'ERROR_PUBLICKEY', 'ERROR_SITEKEY', 'ERROR_ACTION',
'ERROR_MIN_SCORE', 'ERROR_MIN_SCORE_NOT_FLOAT'):
raise exceptions.BadInputDataError(error_msg)
if error_text in ('ERROR_PROXYTYPE', 'ERROR_PROXY'):
raise exceptions.ProxyError(error_msg)
raise exceptions.ServiceError(error_msg)
class PostRequest(Request):
""" Request class for POST requests """
def prepare(self, **kwargs) -> dict:
""" Prepare request """
return super().prepare(method="POST", **kwargs)
class GetRequest(Request):
""" Request class for GET requests """
def prepare(self, **kwargs) -> dict:
""" Prepare request """
return super().prepare(method="GET", **kwargs)
class GetBalanceRequest(GetRequest):
""" GetBalance Request class """
def prepare(self) -> dict:
""" Prepare request """
return super().prepare(url='')
def parse_response(self, response) -> dict:
""" Parse response and return balance """
return {
'balance': float(super().parse_response(response)["balance"]) / 100
}
class GetStatusRequest(GetRequest):
""" GetStatus Request class """
def prepare(self):
""" Prepare request """
return super().prepare(url='/status')
def parse_response(self, response) -> dict:
""" Parse response and return status """
try:
response_data = super().parse_response(response)
if response_data.get('is_service_overloaded'):
return {}
return response_data
except exceptions.UnicapsException:
return {}
class ReportGoodRequest(PostRequest):
""" ReportGood Request class """
# pylint: disable=arguments-differ
def prepare(self, solved_captcha) -> dict: # type: ignore
""" Prepares request """
raise exceptions.UnicapsException(
"Report for good CAPTCHA is not supported by the current service!"
)
class ReportBadRequest(PostRequest):
""" ReportBad Request class """
# pylint: disable=arguments-differ
def prepare(self, solved_captcha) -> dict: # type: ignore
""" Prepare request """
return super().prepare(
url=f'/captcha/{solved_captcha.captcha_id}/report',
solved_captcha=solved_captcha
)
class TaskRequest(PostRequest):
""" Common Task Request class """
# pylint: disable=arguments-differ,unused-argument
def prepare(self, captcha, proxy, user_agent, cookies):
""" Prepare a request """
request = super().prepare(
url='/captcha',
captcha=captcha,
proxy=proxy,
user_agent=user_agent,
cookies=cookies
)
return request
def parse_response(self, response) -> dict:
""" Parse response and return captcha_id """
response_data = super().parse_response(response)
# raise the BadInputDataError if CAPTCHA is not correct
if not response_data.pop('is_correct'):
raise exceptions.BadInputDataError('is_correct=false')
if 'text' in response_data:
response_data.pop('text')
return dict(
task_id=response_data.pop("captcha"),
extra=response_data
)
class SolutionRequest(GetRequest):
""" Common Solution Request class """
# pylint: disable=arguments-differ
def prepare(self, task) -> dict: # type: ignore
""" Prepare request """
return super().prepare(url=f'/captcha/{task.task_id}', task=task)
def parse_response(self, response) -> dict:
""" Parse response and return solution and cost """
response_data = super().parse_response(response)
# raise the UnableToSolveError if CAPTCHA is not correct
if not response_data.pop('is_correct'):
raise exceptions.UnableToSolveError('is_correct=false')
# the empty text field means that solving is in progress
text = response_data.pop("text")
if not text:
raise exceptions.SolutionNotReadyYet()
# get solution class and prepare a solution object
solution_class = self.source_data['task'].captcha.get_solution_class()
solution = solution_class(text)
response_data.pop("captcha")
return dict(
solution=solution,
extra=response_data
)
class ImageCaptchaTaskRequest(TaskRequest):
""" ImageCaptchaTask Request class """
# pylint: disable=arguments-differ,unused-argument,signature-differs
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepare request """
request = super().prepare(
captcha=captcha,
proxy=proxy,
user_agent=user_agent,
cookies=cookies
)
# add required params
request['data'].update(dict(
captchafile='base64:' + captcha.get_image_base64().decode('ascii')
))
return request
class ImageCaptchaSolutionRequest(SolutionRequest):
""" Image CAPTCHA solution request """
class RecaptchaV2TaskRequest(TaskRequest):
""" reCAPTCHA v2 task request """
# pylint: disable=arguments-differ,signature-differs
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepare request """
request = super().prepare(
captcha=captcha,
proxy=proxy,
user_agent=user_agent,
cookies=cookies
)
data = {
"googlekey": captcha.site_key,
"pageurl": captcha.page_url
}
data.update(
captcha.get_optional_data(
data_s=('data-s', None),
)
)
request['data'].update(
dict(
type=4,
token_params=_dumps(data, proxy)
)
)
return request
class RecaptchaV2SolutionRequest(SolutionRequest):
""" reCAPTCHA v2 solution request """
class RecaptchaV3TaskRequest(TaskRequest):
""" reCAPTCHA v3 task request """
# pylint: disable=arguments-differ,signature-differs
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepare request """
request = super().prepare(
captcha=captcha,
proxy=proxy,
user_agent=user_agent,
cookies=cookies
)
data = {
"googlekey": captcha.site_key,
"pageurl": captcha.page_url
}
data.update(
captcha.get_optional_data(
action=('action', None),
min_score=('min_score', None),
)
)
request['data'].update(
dict(
type=5,
token_params=_dumps(data, proxy)
)
)
return request
class RecaptchaV3SolutionRequest(SolutionRequest):
""" reCAPTCHA v3 solution request """
class FunCaptchaTaskRequest(TaskRequest):
""" FunCaptcha task request """
# pylint: disable=arguments-differ,signature-differs
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepare request """
request = super().prepare(
captcha=captcha,
proxy=proxy,
user_agent=user_agent,
cookies=cookies
)
data = {
"publickey": captcha.public_key,
"pageurl": captcha.page_url
}
request['data'].update(
dict(
type=6,
funcaptcha_params=_dumps(data, proxy)
)
)
return request
class FunCaptchaSolutionRequest(SolutionRequest):
""" FunCaptcha solution request """
class HCaptchaTaskRequest(TaskRequest):
""" HCaptcha task request """
# pylint: disable=arguments-differ,signature-differs
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepare request """
request = super().prepare(
captcha=captcha,
proxy=proxy,
user_agent=user_agent,
cookies=cookies
)
data = {
"sitekey": captcha.site_key,
"pageurl": captcha.page_url
}
request['data'].update(
dict(
type=7,
hcaptcha_params=_dumps(data, proxy)
)
)
return request
class HCaptchaSolutionRequest(SolutionRequest):
""" HCaptcha solution request """
def _dumps(data, proxy):
if proxy:
data.update(
dict(
proxy=proxy.get_string(including_type=True),
proxytype=proxy.proxy_type.value.upper()
)
)
return json.dumps(data)
================================================
FILE: unicaps/_service/rucaptcha.py
================================================
# -*- coding: UTF-8 -*-
"""
rucaptcha.com service
"""
# pylint: disable=unused-import
from .twocaptcha import (
Service as Service2Captcha, GetBalanceRequest, GetStatusRequest,
ReportGoodRequest, ReportBadRequest,
ImageCaptchaTaskRequest, ImageCaptchaSolutionRequest,
RecaptchaV2TaskRequest, RecaptchaV2SolutionRequest,
RecaptchaV3TaskRequest, RecaptchaV3SolutionRequest,
TextCaptchaTaskRequest, TextCaptchaSolutionRequest,
FunCaptchaTaskRequest, FunCaptchaSolutionRequest,
KeyCaptchaTaskRequest, KeyCaptchaSolutionRequest,
GeeTestTaskRequest, GeeTestSolutionRequest,
GeeTestV4TaskRequest, GeeTestV4SolutionRequest,
HCaptchaTaskRequest, HCaptchaSolutionRequest,
CapyPuzzleTaskRequest, CapyPuzzleSolutionRequest,
TikTokCaptchaTaskRequest, TikTokCaptchaSolutionRequest
)
__all__ = [
'Service', 'GetBalanceRequest', 'GetStatusRequest',
'ReportGoodRequest', 'ReportBadRequest',
'ImageCaptchaTaskRequest', 'ImageCaptchaSolutionRequest',
'RecaptchaV2TaskRequest', 'RecaptchaV2SolutionRequest',
'RecaptchaV3TaskRequest', 'RecaptchaV3SolutionRequest',
'TextCaptchaTaskRequest', 'TextCaptchaSolutionRequest',
'FunCaptchaTaskRequest', 'FunCaptchaSolutionRequest',
'KeyCaptchaTaskRequest', 'KeyCaptchaSolutionRequest',
'GeeTestTaskRequest', 'GeeTestSolutionRequest',
'GeeTestV4TaskRequest', 'GeeTestV4SolutionRequest',
'HCaptchaTaskRequest', 'HCaptchaSolutionRequest',
'CapyPuzzleTaskRequest', 'CapyPuzzleSolutionRequest',
'TikTokCaptchaTaskRequest', 'TikTokCaptchaSolutionRequest'
]
class Service(Service2Captcha):
""" Main service class for rucaptcha """
BASE_URL = 'https://rucaptcha.com'
================================================
FILE: unicaps/_service/twocaptcha.py
================================================
# -*- coding: UTF-8 -*-
"""
2captcha.com service
"""
from .base import HTTPService
from .._transport.http_transport import HTTPRequestJSON # type: ignore
from .. import exceptions
from .._captcha import CaptchaType
from ..common import CaptchaAlphabet
__all__ = [
'Service', 'GetBalanceRequest', 'GetStatusRequest',
'ReportGoodRequest', 'ReportBadRequest',
'ImageCaptchaTaskRequest', 'ImageCaptchaSolutionRequest',
'RecaptchaV2TaskRequest', 'RecaptchaV2SolutionRequest',
'RecaptchaV3TaskRequest', 'RecaptchaV3SolutionRequest',
'TextCaptchaTaskRequest', 'TextCaptchaSolutionRequest',
'FunCaptchaTaskRequest', 'FunCaptchaSolutionRequest',
'KeyCaptchaTaskRequest', 'KeyCaptchaSolutionRequest',
'GeeTestTaskRequest', 'GeeTestSolutionRequest',
'GeeTestV4TaskRequest', 'GeeTestV4SolutionRequest',
'HCaptchaTaskRequest', 'HCaptchaSolutionRequest',
'CapyPuzzleTaskRequest', 'CapyPuzzleSolutionRequest',
'TikTokCaptchaTaskRequest', 'TikTokCaptchaSolutionRequest'
]
class Service(HTTPService):
""" Main service class for 2captcha """
BASE_URL = 'https://2captcha.com'
def _post_init(self):
""" Init settings """
for captcha_type in self.settings:
self.settings[captcha_type].polling_delay = 5
self.settings[captcha_type].polling_interval = 5
self.settings[captcha_type].solution_timeout = 180
if captcha_type in (CaptchaType.RECAPTCHAV2, CaptchaType.HCAPTCHA):
self.settings[captcha_type].polling_delay = 20
self.settings[captcha_type].solution_timeout = 300
elif captcha_type in (CaptchaType.RECAPTCHAV3,):
self.settings[captcha_type].polling_delay = 15
elif captcha_type in (CaptchaType.TIKTOK,):
self.settings[captcha_type].polling_interval = 1
class Request(HTTPRequestJSON):
""" Common Request class for 2captcha """
def parse_response(self, response) -> dict:
""" Parse response and checks for errors """
response_data = super().parse_response(response)
if response_data.pop("status") == 1:
return response_data
###############
# handle errors
###############
error_code = response_data["request"]
error_text = response_data.get("error_text", "")
error_msg = f"{error_code}: {error_text}"
if error_code == 'CAPCHA_NOT_READY': # pylint: disable=no-else-raise
raise exceptions.SolutionNotReadyYet()
elif error_code in ('ERROR_WRONG_USER_KEY', 'ERROR_KEY_DOES_NOT_EXIST',
'ERROR_IP_NOT_ALLOWED', 'IP_BANNED'):
raise exceptions.AccessDeniedError(error_msg)
elif error_code in ('ERROR_ZERO_BALANCE',):
raise exceptions.LowBalanceError(error_msg)
elif error_code in ('ERROR_NO_SLOT_AVAILABLE',):
# If server returns ERROR_NO_SLOT_AVAILABLE make a 5 seconds timeout before sending
# next request.
# time.sleep(5)
raise exceptions.ServiceTooBusy(error_msg)
elif error_code in ('MAX_USER_TURN',) or error_code.startswith('ERROR:'):
raise exceptions.TooManyRequestsError(error_msg)
elif error_code in ('ERROR_WRONG_ID_FORMAT', 'ERROR_WRONG_CAPTCHA_ID'):
raise exceptions.MalformedRequestError(error_msg)
elif error_code in ('ERROR_ZERO_CAPTCHA_FILESIZE', 'ERROR_TOO_BIG_CAPTCHA_FILESIZE',
'ERROR_WRONG_FILE_EXTENSION', 'ERROR_IMAGE_TYPE_NOT_SUPPORTED',
'ERROR_UPLOAD', 'ERROR_PAGEURL', 'ERROR_BAD_TOKEN_OR_PAGEURL',
'ERROR_GOOGLEKEY', 'ERROR_BAD_PARAMETERS', 'ERROR_TOKEN_EXPIRED',
'ERROR_EMPTY_ACTION'):
raise exceptions.BadInputDataError(error_msg)
elif error_code in ('ERROR_CAPTCHAIMAGE_BLOCKED', 'ERROR_CAPTCHA_UNSOLVABLE',
'ERROR_BAD_DUPLICATES'):
raise exceptions.UnableToSolveError(error_msg)
elif error_code in ('ERROR_BAD_PROXY', 'ERROR_PROXY_CONNECTION_FAILED'):
raise exceptions.ProxyError(error_msg)
raise exceptions.ServiceError(error_msg)
class InRequest(Request):
""" Request class for requests to /in.php """
def prepare(self, **kwargs) -> dict:
""" Prepare request """
request = super().prepare(**kwargs)
request.update(
dict(
method="POST",
url=self._service.BASE_URL + "/in.php",
data=dict(
key=self._service.api_key,
json=1,
soft_id=2738
)
)
)
return request
class ResRequest(Request):
""" Request class for requests to /res.php """
def prepare(self, **kwargs) -> dict:
""" Prepare request """
request = super().prepare(**kwargs)
request.update(
dict(
method="GET",
url=self._service.BASE_URL + "/res.php",
params=dict(
key=self._service.api_key,
json=1
)
)
)
return request
class GetBalanceRequest(ResRequest):
""" GetBalance Request class """
def prepare(self) -> dict:
""" Prepare request """
request = super().prepare()
request["params"].update(dict(action="getbalance"))
return request
def parse_response(self, response) -> dict:
""" Parse response and return balance """
return {'balance': float(super().parse_response(response)["request"])}
class GetStatusRequest(GetBalanceRequest):
""" GetStatus Request class """
def parse_response(self, response) -> dict:
""" Parse response and return status """
try:
return super().parse_response(response)
except exceptions.UnicapsException:
return {}
class ReportGoodRequest(ResRequest):
""" ReportGood Request class """
# pylint: disable=arguments-differ
def prepare(self, solved_captcha) -> dict: # type: ignore
""" Prepare request """
request = super().prepare(solved_captcha=solved_captcha)
request["params"].update(
dict(
action="reportgood",
id=solved_captcha.captcha_id
)
)
return request
class ReportBadRequest(ResRequest):
""" ReportBad Request class """
# pylint: disable=arguments-differ
def prepare(self, solved_captcha) -> dict: # type: ignore
""" Prepare request """
request = super().prepare(solved_captcha=solved_captcha)
request["params"].update(
dict(
action="reportbad",
id=solved_captcha.captcha_id
)
)
return request
class TaskRequest(InRequest):
""" Common Task Request class """
# pylint: disable=arguments-differ,unused-argument
def prepare(self, captcha, proxy, user_agent, cookies):
""" Prepare a request """
request = super().prepare(
captcha=captcha,
proxy=proxy,
user_agent=user_agent,
cookies=cookies
)
if proxy:
request['data'].update(
dict(
proxy=proxy.get_string(),
proxytype=proxy.proxy_type.value.upper()
)
)
if cookies:
request['data']['cookies'] = ';'.join([f'{k}:{v}' for k, v in cookies.items()])
if user_agent:
request['data']['userAgent'] = user_agent
return request
def parse_response(self, response) -> dict:
""" Parse response and return task_id """
response_data = super().parse_response(response)
return dict(
task_id=response_data.pop("request"),
extra=response_data
)
class SolutionRequest(ResRequest):
""" Common Solution Request class """
# pylint: disable=arguments-differ
def prepare(self, task) -> dict: # type: ignore
""" Prepare request """
request = super().prepare(task=task)
request["params"].update(
dict(action="get2", id=task.task_id)
)
return request
def parse_response(self, response) -> dict:
""" Parse response and return solution and cost """
response_data = super().parse_response(response)
solution_data = response_data.pop("request")
solution_class = self.source_data['task'].captcha.get_solution_class()
captcha_type = self.source_data['task'].captcha.get_type()
if captcha_type == CaptchaType.GEETEST:
solution = solution_class(
challenge=solution_data['geetest_challenge'],
validate=solution_data['geetest_validate'],
seccode=solution_data['geetest_seccode']
)
elif captcha_type == CaptchaType.GEETESTV4:
solution = solution_class(
captcha_id=solution_data['captcha_id'],
lot_number=solution_data['lot_number'],
pass_token=solution_data['pass_token'],
gen_time=solution_data['gen_time'],
captcha_output=solution_data['captcha_output'],
)
elif captcha_type == CaptchaType.CAPY:
solution = solution_class(**solution_data)
elif captcha_type in (CaptchaType.TIKTOK,):
solution = solution_class(
dict(
[key_value.split(':', maxsplit=1) for key_value in solution_data.split(';')]
)
)
else:
solution = solution_class(solution_data)
price = None
if 'price' in response_data:
price = response_data.pop("price")
return dict(
solution=solution,
cost=price,
extra=response_data
)
class ImageCaptchaTaskRequest(TaskRequest):
""" ImageCaptchaTask Request class """
# pylint: disable=arguments-differ,unused-argument,signature-differs
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepare request """
request = super().prepare(
captcha=captcha,
proxy=proxy,
user_agent=user_agent,
cookies=cookies
)
# add required params
request['data'].update(
dict(
method="base64",
body=captcha.get_image_base64().decode('ascii')
)
)
# add optional params
request['data'].update(
captcha.get_optional_data(
is_phrase=('phrase', lambda v: int(bool(v))),
is_case_sensitive=('regsense', lambda v: int(bool(v))),
char_type=('numeric', lambda v: v.value),
is_math=('calc', lambda v: int(bool(v))),
min_len=('min_len', None),
max_len=('max_len', None),
alphabet=('language',
lambda v: {CaptchaAlphabet.CYRILLIC: 1,
CaptchaAlphabet.LATIN: 2}.get(v, 0)),
language=('lang', lambda v: v.value),
comment=('textinstructions', None),
)
)
return request
class ImageCaptchaSolutionRequest(SolutionRequest):
""" Image CAPTCHA solution request """
class RecaptchaV2TaskRequest(TaskRequest):
""" reCAPTCHA v2 task request """
# pylint: disable=arguments-differ,signature-differs
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepare request """
request = super().prepare(
captcha=captcha,
proxy=proxy,
user_agent=user_agent,
cookies=cookies
)
request['data'].update(
dict(
method="userrecaptcha",
googlekey=captcha.site_key,
pageurl=captcha.page_url,
invisible=int(captcha.is_invisible)
)
)
# add optional params
request['data'].update(
captcha.get_optional_data(
data_s=('data-s', None),
api_domain=('domain', None)
)
)
# check if enterprise captcha
if captcha.is_enterprise:
request['data']['enterprise'] = 1
return request
class RecaptchaV2SolutionRequest(SolutionRequest):
""" reCAPTCHA v2 solution request """
class RecaptchaV3TaskRequest(TaskRequest):
""" reCAPTCHA v3 task request """
# pylint: disable=arguments-differ,signature-differs
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepare request """
request = super().prepare(
captcha=captcha,
proxy=proxy,
user_agent=user_agent,
cookies=cookies
)
request['data'].update(
dict(
method="userrecaptcha",
version="v3",
googlekey=captcha.site_key,
pageurl=captcha.page_url
)
)
# add optional params
request['data'].update(
captcha.get_optional_data(
action=('action', None),
min_score=('min_score', None),
api_domain=('domain', None)
)
)
# check if enterprise captcha
if captcha.is_enterprise:
request['data']['enterprise'] = 1
return request
class RecaptchaV3SolutionRequest(SolutionRequest):
""" reCAPTCHA v3 solution request """
class TextCaptchaTaskRequest(TaskRequest):
""" TextCaptcha task request """
# pylint: disable=arguments-differ,unused-argument,signature-differs
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepare request """
request = super().prepare(
captcha=captcha,
proxy=proxy,
user_agent=user_agent,
cookies=cookies
)
request['data'].update(
dict(
textcaptcha=captcha.text
)
)
# add optional params
request['data'].update(
captcha.get_optional_data(
alphabet=('language',
lambda v: {CaptchaAlphabet.CYRILLIC: 1,
CaptchaAlphabet.LATIN: 2}.get(v, 0)),
language=('lang', lambda v: v.value)
)
)
return request
class TextCaptchaSolutionRequest(SolutionRequest):
""" TextCaptcha solution request """
class FunCaptchaTaskRequest(TaskRequest):
""" FunCaptcha task request """
# pylint: disable=arguments-differ,signature-differs
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepare request """
request = super().prepare(
captcha=captcha,
proxy=proxy,
user_agent=user_agent,
cookies=cookies
)
request['data'].update(
dict(
method="funcaptcha",
publickey=captcha.public_key,
pageurl=captcha.page_url
)
)
# add optional params
request['data'].update(
captcha.get_optional_data(
service_url=('surl', None),
no_js=('nojs', None),
blob=('data[blob]', None),
)
)
return request
class FunCaptchaSolutionRequest(SolutionRequest):
""" FunCaptcha solution request """
class KeyCaptchaTaskRequest(TaskRequest):
""" KeyCaptcha task request """
# pylint: disable=arguments-differ,signature-differs
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepare request """
request = super().prepare(
captcha=captcha,
proxy=proxy,
user_agent=user_agent,
cookies=cookies
)
request['data'].update(
dict(
method="keycaptcha",
s_s_c_user_id=captcha.user_id,
s_s_c_session_id=captcha.session_id,
s_s_c_web_server_sign=captcha.ws_sign,
s_s_c_web_server_sign2=captcha.ws_sign2,
pageurl=captcha.page_url
)
)
return request
class KeyCaptchaSolutionRequest(SolutionRequest):
""" KeyCaptcha solution request """
class GeeTestTaskRequest(TaskRequest):
""" GeeTest task request """
# pylint: disable=arguments-differ,signature-differs
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepare request """
request = super().prepare(
captcha=captcha,
proxy=proxy,
user_agent=user_agent,
cookies=cookies
)
request['data'].update(
dict(
method="geetest",
gt=captcha.gt_key,
challenge=captcha.challenge,
pageurl=captcha.page_url
)
)
# add optional params
request['data'].update(
captcha.get_optional_data(
api_server=('api_server', None)
)
)
return request
class GeeTestSolutionRequest(SolutionRequest):
""" GeeTest solution request """
class HCaptchaTaskRequest(TaskRequest):
""" HCaptcha task request """
# pylint: disable=arguments-differ,signature-differs
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepare request """
request = super().prepare(
captcha=captcha,
proxy=proxy,
user_agent=user_agent,
cookies=cookies
)
request['data'].update(
dict(
method="hcaptcha",
sitekey=captcha.site_key,
pageurl=captcha.page_url,
invisible=int(captcha.is_invisible)
)
)
# add optional params
request['data'].update(
captcha.get_optional_data(
api_domain=('domain', None)
)
)
return request
class HCaptchaSolutionRequest(SolutionRequest):
""" HCaptcha solution request """
class CapyPuzzleTaskRequest(TaskRequest):
""" CapyPuzzle task request """
# pylint: disable=arguments-differ,signature-differs
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepare request """
request = super().prepare(
captcha=captcha,
proxy=proxy,
user_agent=user_agent,
cookies=cookies
)
request['data'].update(
dict(
method="capy",
captchakey=captcha.site_key,
pageurl=captcha.page_url
)
)
# add optional params
request['data'].update(
captcha.get_optional_data(
api_server=('api_server', None)
)
)
return request
class CapyPuzzleSolutionRequest(SolutionRequest):
""" CapyPuzzle solution request """
class TikTokCaptchaTaskRequest(TaskRequest):
""" TikTokCaptcha task request """
# pylint: disable=arguments-differ,signature-differs
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepare request """
# default values for additional params
# see https://2captcha.com/2captcha-api#solving_tiktok for details
add_params_mapping = {
'https://www.tiktok.com/login/phone-or-email/email': {
'aid': 1459,
'host': 'https://www-useast1a.tiktok.com'
},
'https://ads.tiktok.com/i18n/signup': {
'aid': 1583,
'host': 'https://verify-sg.byteoversea.com'
},
'default': {'aid': None, 'host': None}
}
add_params = add_params_mapping[next(
(url for url in add_params_mapping if captcha.page_url.startswith(url)),
'default'
)]
request = super().prepare(
captcha=captcha,
proxy=proxy,
user_agent=user_agent,
cookies=cookies
)
request['data'].update(
dict(
method="tiktok",
pageurl=captcha.page_url,
aid=add_params['aid'] if captcha.aid is None else captcha.aid,
host=add_params['host'] if captcha.host is None else captcha.host
)
)
return request
class TikTokCaptchaSolutionRequest(SolutionRequest):
""" TikTokCaptcha solution request """
class GeeTestV4TaskRequest(TaskRequest):
""" GeeTest v4 task request """
# pylint: disable=arguments-differ,signature-differs
def prepare(self, captcha, proxy, user_agent, cookies) -> dict: # type: ignore
""" Prepare request """
request = super().prepare(
captcha=captcha,
proxy=proxy,
user_agent=user_agent,
cookies=cookies
)
request['data'].update(
dict(
method="geetest_v4 ",
captcha_id=captcha.captcha_id,
pageurl=captcha.page_url
)
)
return request
class GeeTestV4SolutionRequest(SolutionRequest):
""" GeeTest v4 solution request """
================================================
FILE: unicaps/_solver.py
================================================
# -*- coding: UTF-8 -*-
"""
CaptchaSolver class
"""
import io
import pathlib
from typing import Union
from .captcha import (
ImageCaptcha, TextCaptcha, RecaptchaV2, RecaptchaV3, HCaptcha, FunCaptcha, KeyCaptcha, GeeTest,
GeeTestV4, CapyPuzzle, TikTokCaptcha
)
from ._captcha.base import BaseCaptcha # type: ignore
from ._service import CaptchaSolvingService, SOLVING_SERVICE
from ._service.base import SolvedCaptcha, CaptchaTask
class CaptchaSolver:
"""Main captcha solver :class:`CaptchaSolver ` object.
:param service_name: captcha solving service to use (enum CaptchaSolvingService or str).
:param api_key: API key to access the solving service.
"""
def __init__(self, service_name: Union[CaptchaSolvingService, str], api_key: str):
# check service_name
if isinstance(service_name, CaptchaSolvingService):
self.service_name = service_name
elif isinstance(service_name, str):
try:
self.service_name = CaptchaSolvingService(service_name)
except ValueError as exc:
raise ValueError(
f"'{service_name}' is not a valid CaptchaSolvingService. "
"Please use one of the following values: " + ', '.join(
[f"'{i.value}'" for i in CaptchaSolvingService]
)
) from exc
else:
raise ValueError(
'"service_name" param must be an instance of str or CaptchaSolvingService!'
)
self.api_key = api_key
self._service = SOLVING_SERVICE[self.service_name].Service(api_key) # type: ignore
def _solve_captcha(self, captcha_class, *args, **kwargs):
proxy = kwargs.pop('proxy') if 'proxy' in kwargs else None
user_agent = kwargs.pop('user_agent') if 'user_agent' in kwargs else None
cookies = kwargs.pop('cookies') if 'cookies' in kwargs else None
return self._service.solve_captcha(
captcha_class(*args, **kwargs),
proxy=proxy,
user_agent=user_agent,
cookies=cookies
)
def solve_image_captcha(self,
image: Union[bytes, io.RawIOBase, io.BufferedIOBase, pathlib.Path],
**kwargs) -> SolvedCaptcha:
r"""Solves image CAPTCHA.
:param image: binary file, bytes or pathlib.Path object containing image with CAPTCHA
:param char_type: (optional) Character type.
:param is_phrase: (optional) Boolean. True if CAPTCHA contains more than one word.
:param is_case_sensitive: (optional) Boolean.
:param is_math: (optional) Boolean. True if CAPTCHA requires calculation.
:param min_len: (optional) Integer. Minimum length of the CAPTCHA's text.
:param max_len: (optional) Integer. Maximum length of the CAPTCHA's text.
:param alphabet: (optional) Alphabet used in the CAPTCHA.
:param language: (optional) Language.
:param comment: (optional) String. Text instructions for worker.
:return: :class:`SolvedCaptcha ` object
:rtype: unicaps.SolvedCaptcha
"""
return self._solve_captcha(ImageCaptcha, image, **kwargs)
def solve_text_captcha(self, text: str, **kwargs) -> SolvedCaptcha:
r"""Solves text CAPTCHA.
:param text: String with text captcha task.
:param alphabet: (optional) Alphabet used in the CAPTCHA.
:param language: (optional) Language.
:return: :class:`SolvedCaptcha ` object
:rtype: unicaps.SolvedCaptcha
"""
return self._solve_captcha(TextCaptcha, text, **kwargs)
def solve_recaptcha_v2(self, site_key: str, page_url: str, **kwargs) -> SolvedCaptcha:
r"""Solves reCAPTCHA v2.
:param site_key: Value of "data-sitekey" (or "k") parameter.
:param page_url: Full URL of the page with CAPTCHA.
:param is_invisible: (optional) Invisible reCAPTCHA flag.
:param is_enterprise: (optional) reCAPTCHA Enterprise flag.
:param data_s: (optional) Value of "data-s" parameter.
:param api_domain: (optional) Domain used to load the captcha: google.com or recaptcha.net.
:param proxy: (optional) Proxy to use while solving the CAPTCHA.
:param user_agent: (optional) User-Agent to use while solving the CAPTCHA.
:param cookies: (optional) Cookies to use while solving the CAPTCHA.
:return: :class:`SolvedCaptcha ` object
:rtype: unicaps.SolvedCaptcha
"""
return self._solve_captcha(RecaptchaV2, site_key, page_url, **kwargs)
def solve_recaptcha_v3(self, site_key: str, page_url: str, **kwargs) -> SolvedCaptcha:
r"""Solves reCAPTCHA v3.
:param site_key: Value of "render" parameter.
:param page_url: Full URL of the page with CAPTCHA.
:param is_enterprise: (optional) reCAPTCHA Enterprise flag (default: False).
:param action: (optional) Widget action value.
:param min_score: (optional) Filters a worker with corresponding score.
:param api_domain: (optional) Domain used to load the captcha: google.com or recaptcha.net.
:param proxy: (optional) Proxy to use while solving the CAPTCHA.
:param user_agent: (optional) User-Agent to use while solving the CAPTCHA.
:param cookies: (optional) Cookies to use while solving the CAPTCHA.
:return: :class:`SolvedCaptcha ` object
:rtype: unicaps.SolvedCaptcha
"""
return self._solve_captcha(RecaptchaV3, site_key, page_url, **kwargs)
def solve_hcaptcha(self, site_key: str, page_url: str, **kwargs) -> SolvedCaptcha:
r"""Solves hCaptcha.
:param site_key: hCaptcha website key.
:param page_url: Full URL of the page with CAPTCHA.
:param is_invisible: (optional) Invisible hCaptcha flag (default: False).
:param api_domain: (optional) API domain: hcaptcha.com or js.hcaptcha.com.
:param proxy: (optional) Proxy to use while solving the CAPTCHA.
:param user_agent: (optional) User-Agent to use while solving the CAPTCHA.
:param cookies: (optional) Cookies to use while solving the CAPTCHA.
:return: :class:`SolvedCaptcha ` object
:rtype: unicaps.SolvedCaptcha
"""
return self._solve_captcha(HCaptcha, site_key, page_url, **kwargs)
def solve_funcaptcha(self, public_key: str, page_url: str, **kwargs) -> SolvedCaptcha:
r"""Solves FunCaptcha.
:param public_key: FunCaptcha public key.
:param page_url: Full URL of the page with CAPTCHA.
:param service_url: (optional) Service URL.
:param no_js: (optional) Disable JavaScript.
:param blob: (optional) The "blob" value of CAPTCHA.
:param proxy: (optional) Proxy to use while solving the CAPTCHA.
:param user_agent: (optional) User-Agent to use while solving the CAPTCHA.
:param cookies: (optional) Cookies to use while solving the CAPTCHA.
:return: :class:`SolvedCaptcha ` object
:rtype: unicaps.SolvedCaptcha
"""
return self._solve_captcha(FunCaptcha, public_key, page_url, **kwargs)
def solve_keycaptcha(self, page_url: str, user_id: str, session_id: str, ws_sign: str,
ws_sign2: str, **kwargs) -> SolvedCaptcha:
r"""Solves KeyCaptcha.
:param page_url: Full URL of the page with CAPTCHA.
:param user_id: Value of "s_s_c_user_id" parameter.
:param session_id: Value of "s_s_c_session_id" parameter.
:param ws_sign: Value of "s_s_c_web_server_sign" parameter.
:param ws_sign2: Value of "s_s_c_web_server_sign2" parameter.
:return: :class:`SolvedCaptcha ` object
:rtype: unicaps.SolvedCaptcha
"""
return self._solve_captcha(
KeyCaptcha, page_url, user_id, session_id, ws_sign, ws_sign2, **kwargs
)
def solve_geetest(self, page_url: str, gt_key: str, challenge: str,
**kwargs) -> SolvedCaptcha:
r"""Solves GeeTest.
:param page_url: Full URL of the page with CAPTCHA.
:param gt_key: Public website key (static).
:param challenge: Dynamic challenge key.
:param api_server: (optional) API domain
:return: :class:`SolvedCaptcha ` object
:rtype: unicaps.SolvedCaptcha
"""
return self._solve_captcha(GeeTest, page_url, gt_key, challenge, **kwargs)
def solve_geetest_v4(self, page_url: str, captcha_id: str, **kwargs) -> SolvedCaptcha:
r"""Solves GeeTestV4.
:param page_url: Full URL of the page with CAPTCHA.
:param captcha_id: Value of captcha_id parameter you found on target website.
:return: :class:`SolvedCaptcha ` object
:rtype: unicaps.SolvedCaptcha
"""
return self._solve_captcha(GeeTestV4, page_url, captcha_id, **kwargs)
def solve_capy_puzzle(self, site_key: str, page_url: str, **kwargs) -> SolvedCaptcha:
r"""Solves Capy Puzzle CAPTCHA.
:param site_key: Public website key (static).
:param page_url: Full URL of the page with CAPTCHA.
:param api_server: (optional) The domain part of script URL you found on page.
:param proxy: (optional) Proxy to use while solving the CAPTCHA.
:param user_agent: (optional) User-Agent to use while solving the CAPTCHA.
:param cookies: (optional) Cookies to use while solving the CAPTCHA.
:return: :class:`SolvedCaptcha ` object
:rtype: unicaps.SolvedCaptcha
"""
return self._solve_captcha(CapyPuzzle, site_key, page_url, **kwargs)
def solve_tiktok(self, page_url: str, **kwargs) -> SolvedCaptcha:
r"""Solves TikTokCaptcha.
:param page_url: Full URL of the page with CAPTCHA.
:param aid: (optional) The aid parameter value for the page.
:param host: (optional) The host parameter value for the page.
:param proxy: (optional) Proxy to use while solving the CAPTCHA.
:param user_agent: (optional) User-Agent to use while solving the CAPTCHA.
:param cookies: (optional) Cookies to use while solving the CAPTCHA.
:return: :class:`SolvedCaptcha ` object
:rtype: unicaps.SolvedCaptcha
"""
return self._solve_captcha(TikTokCaptcha, page_url, **kwargs)
def create_task(self, captcha: BaseCaptcha) -> CaptchaTask:
"""Create task to solve CAPTCHA
:param captcha: Captcha to solve.
:return: :class:`CaptchaTask ` object
:rtype: unicaps.CaptchaTask
"""
return self._service.create_task(captcha)
def get_balance(self) -> float:
"""Get account balance
:return: :float:Balance amount
:rtype: float
"""
return self._service.get_balance()
def get_status(self) -> bool:
"""Get service status
:return: :bool:Service status
:rtype: bool
"""
return self._service.get_status()
def close(self) -> None:
"""Close all connections"""
self._service.close()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.close()
================================================
FILE: unicaps/_solver_async.py
================================================
"""
AsyncCaptchaSolver class
"""
import io
import pathlib
from typing import Union
from .captcha import (
ImageCaptcha, TextCaptcha, RecaptchaV2, RecaptchaV3, HCaptcha, FunCaptcha, KeyCaptcha, GeeTest,
GeeTestV4, CapyPuzzle, TikTokCaptcha
)
from ._captcha.base import BaseCaptcha # type: ignore
from ._service.base import AsyncSolvedCaptcha, AsyncCaptchaTask
from ._solver import CaptchaSolver
class AsyncCaptchaSolver(CaptchaSolver):
"""Main captcha solver :class:`AsyncCaptchaSolver ` object.
:param service_name: captcha solving service to use (enum CaptchaSolvingService or str).
:param api_key: API key to access the solving service.
"""
async def _solve_captcha_async(self, captcha_class, *args, **kwargs):
proxy = kwargs.pop('proxy') if 'proxy' in kwargs else None
user_agent = kwargs.pop('user_agent') if 'user_agent' in kwargs else None
cookies = kwargs.pop('cookies') if 'cookies' in kwargs else None
return await self._service.solve_captcha_async(
captcha_class(*args, **kwargs),
proxy=proxy,
user_agent=user_agent,
cookies=cookies
)
async def solve_image_captcha(self, # type: ignore
image: Union[bytes, io.RawIOBase, io.BufferedIOBase,
pathlib.Path],
**kwargs) -> AsyncSolvedCaptcha: # type: ignore
r"""Solves image CAPTCHA.
:param image: binary file, bytes or pathlib.Path object containing image with CAPTCHA
:param char_type: (optional) Character type.
:param is_phrase: (optional) Boolean. True if CAPTCHA contains more than one word.
:param is_case_sensitive: (optional) Boolean.
:param is_math: (optional) Boolean. True if CAPTCHA requires calculation.
:param min_len: (optional) Integer. Minimum length of the CAPTCHA's text.
:param max_len: (optional) Integer. Maximum length of the CAPTCHA's text.
:param alphabet: (optional) Alphabet used in the CAPTCHA.
:param language: (optional) Language.
:param comment: (optional) String. Text instructions for worker.
:return: :class:`AsyncSolvedCaptcha ` object
:rtype: unicaps.AsyncSolvedCaptcha
"""
return await self._solve_captcha_async(ImageCaptcha, image, **kwargs)
async def solve_text_captcha(self, text: str, **kwargs) -> AsyncSolvedCaptcha: # type: ignore
r"""Solves text CAPTCHA.
:param text: String with text captcha task.
:param alphabet: (optional) Alphabet used in the CAPTCHA.
:param language: (optional) Language.
:return: :class:`AsyncSolvedCaptcha ` object
:rtype: unicaps.AsyncSolvedCaptcha
"""
return await self._solve_captcha_async(TextCaptcha, text, **kwargs)
async def solve_recaptcha_v2(self, site_key: str, page_url: str, # type: ignore
**kwargs) -> AsyncSolvedCaptcha:
r"""Solves reCAPTCHA v2.
:param site_key: Value of "data-sitekey" (or "k") parameter.
:param page_url: Full URL of the page with CAPTCHA.
:param is_invisible: (optional) Invisible reCAPTCHA flag.
:param is_enterprise: (optional) reCAPTCHA Enterprise flag.
:param data_s: (optional) Value of "data-s" parameter.
:param api_domain: (optional) Domain used to load the captcha: google.com or recaptcha.net.
:param proxy: (optional) Proxy to use while solving the CAPTCHA.
:param user_agent: (optional) User-Agent to use while solving the CAPTCHA.
:param cookies: (optional) Cookies to use while solving the CAPTCHA.
:return: :class:`AsyncSolvedCaptcha ` object
:rtype: unicaps.AsyncSolvedCaptcha
"""
return await self._solve_captcha_async(RecaptchaV2, site_key, page_url, **kwargs)
async def solve_recaptcha_v3(self, site_key: str, page_url: str, # type: ignore
**kwargs) -> AsyncSolvedCaptcha:
r"""Solves reCAPTCHA v3.
:param site_key: Value of "render" parameter.
:param page_url: Full URL of the page with CAPTCHA.
:param is_enterprise: (optional) reCAPTCHA Enterprise flag.
:param action: (optional) Widget action value.
:param min_score: (optional) Filters a worker with corresponding score.
:param api_domain: (optional) Domain used to load the captcha: google.com or recaptcha.net.
:param proxy: (optional) Proxy to use while solving the CAPTCHA.
:param user_agent: (optional) User-Agent to use while solving the CAPTCHA.
:param cookies: (optional) Cookies to use while solving the CAPTCHA.
:return: :class:`AsyncSolvedCaptcha ` object
:rtype: unicaps.AsyncSolvedCaptcha
"""
return await self._solve_captcha_async(RecaptchaV3, site_key, page_url, **kwargs)
async def solve_hcaptcha(self, site_key: str, page_url: str, # type: ignore
**kwargs) -> AsyncSolvedCaptcha:
r"""Solves hCaptcha.
:param site_key: hCaptcha website key.
:param page_url: Full URL of the page with CAPTCHA.
:param is_invisible: (optional) Invisible hCaptcha flag (default: False).
:param api_domain: (optional) API domain: hcaptcha.com or js.hcaptcha.com.
:param proxy: (optional) Proxy to use while solving the CAPTCHA.
:param user_agent: (optional) User-Agent to use while solving the CAPTCHA.
:param cookies: (optional) Cookies to use while solving the CAPTCHA.
:return: :class:`AsyncSolvedCaptcha ` object
:rtype: unicaps.AsyncSolvedCaptcha
"""
return await self._solve_captcha_async(HCaptcha, site_key, page_url, **kwargs)
async def solve_funcaptcha(self, public_key: str, page_url: str, # type: ignore
**kwargs) -> AsyncSolvedCaptcha:
r"""Solves FunCaptcha.
:param public_key: FunCaptcha public key.
:param page_url: Full URL of the page with CAPTCHA.
:param service_url: (optional) Service URL.
:param no_js: (optional) Disable JavaScript.
:param blob: (optional) The "blob" value of CAPTCHA.
:param proxy: (optional) Proxy to use while solving the CAPTCHA.
:param user_agent: (optional) User-Agent to use while solving the CAPTCHA.
:param cookies: (optional) Cookies to use while solving the CAPTCHA.
:return: :class:`AsyncSolvedCaptcha ` object
:rtype: unicaps.AsyncSolvedCaptcha
"""
return await self._solve_captcha_async(FunCaptcha, public_key, page_url, **kwargs)
async def solve_keycaptcha(self, page_url: str, user_id: str, session_id: str, # type: ignore
ws_sign: str, ws_sign2: str, **kwargs) -> AsyncSolvedCaptcha:
r"""Solves KeyCaptcha.
:param page_url: Full URL of the page with CAPTCHA.
:param user_id: Value of "s_s_c_user_id" parameter.
:param session_id: Value of "s_s_c_session_id" parameter.
:param ws_sign: Value of "s_s_c_web_server_sign" parameter.
:param ws_sign2: Value of "s_s_c_web_server_sign2" parameter.
:return: :class:`AsyncSolvedCaptcha ` object
:rtype: unicaps.AsyncSolvedCaptcha
"""
return await self._solve_captcha_async(
KeyCaptcha, page_url, user_id, session_id, ws_sign, ws_sign2, **kwargs
)
async def solve_geetest(self, page_url: str, gt_key: str, challenge: str, # type: ignore
**kwargs) -> AsyncSolvedCaptcha:
r"""Solves GeeTest.
:param page_url: Full URL of the page with CAPTCHA.
:param gt_key: Public website key (static).
:param challenge: Dynamic challenge key.
:param api_server: (optional) API domain
:return: :class:`AsyncSolvedCaptcha ` object
:rtype: unicaps.AsyncSolvedCaptcha
"""
return await self._solve_captcha_async(GeeTest, page_url, gt_key, challenge, **kwargs)
async def solve_geetest_v4(self, page_url: str, captcha_id: str, # type: ignore
**kwargs) -> AsyncSolvedCaptcha:
r"""Solves GeeTestV4.
:param page_url: Full URL of the page with CAPTCHA.
:param captcha_id: Value of captcha_id parameter you found on target website.
:return: :class:`AsyncSolvedCaptcha ` object
:rtype: unicaps.AsyncSolvedCaptcha
"""
return await self._solve_captcha_async(GeeTestV4, page_url, captcha_id, **kwargs)
async def solve_capy_puzzle(self, site_key: str, page_url: str, # type: ignore
**kwargs) -> AsyncSolvedCaptcha:
r"""Solves Capy.
:param site_key: Public website key (static).
:param page_url: Full URL of the page with CAPTCHA.
:param api_server: (optional) The domain part of script URL you found on page.
:param proxy: (optional) Proxy to use while solving the CAPTCHA.
:param user_agent: (optional) User-Agent to use while solving the CAPTCHA.
:param cookies: (optional) Cookies to use while solving the CAPTCHA.
:return: :class:`AsyncSolvedCaptcha ` object
:rtype: unicaps.AsyncSolvedCaptcha
"""
return await self._solve_captcha_async(CapyPuzzle, site_key, page_url, **kwargs)
async def solve_tiktok(self, page_url: str, **kwargs) -> AsyncSolvedCaptcha: # type: ignore
r"""Solves TikTokCaptcha.
:param page_url: Full URL of the page with CAPTCHA.
:param aid: (optional) The aid parameter value for the page.
:param host: (optional) The host parameter value for the page.
:param proxy: (optional) Proxy to use while solving the CAPTCHA.
:param user_agent: (optional) User-Agent to use while solving the CAPTCHA.
:param cookies: (optional) Cookies to use while solving the CAPTCHA.
:return: :class:`AsyncSolvedCaptcha ` object
:rtype: unicaps.AsyncSolvedCaptcha
"""
return await self._solve_captcha_async(TikTokCaptcha, page_url, **kwargs)
async def create_task(self, captcha: BaseCaptcha) -> AsyncCaptchaTask: # type: ignore
"""Create task to solve CAPTCHA
:param captcha: Captcha to solve.
:return: :class:`AsyncCaptchaTask ` object
:rtype: unicaps.AsyncCaptchaTask
"""
return await self._service.create_task_async(captcha)
async def get_balance(self) -> float: # type: ignore
"""Get account balance
:return: :float:Balance amount
:rtype: float
"""
return await self._service.get_balance_async()
async def get_status(self) -> bool: # type: ignore
"""Get service status
:return: :bool:Service status
:rtype: bool
"""
return await self._service.get_status_async()
async def close(self) -> None: # type: ignore
"""Close all connections"""
await self._service.close_async()
async def __aenter__(self):
return self
async def __aexit__(self, exc_type, exc_value, traceback):
await self.close()
================================================
FILE: unicaps/_transport/__init__.py
================================================
# -*- coding: UTF-8 -*-
"""
Transport related stuff
"""
from .http_transport import StandardHTTPTransport, HTTPRequestJSON
__all__ = 'StandardHTTPTransport', 'HTTPRequestJSON'
================================================
FILE: unicaps/_transport/base.py
================================================
# -*- coding: UTF-8 -*-
"""
Base transport stuff
"""
from abc import ABC, abstractmethod
from typing import Optional, Any
class BaseRequest(ABC):
""" Base request class """
def __init__(self, service):
# solving service instance
self._service = service
# source request data (not None if a request in process)
self.source_data = None
@abstractmethod
def prepare(self, **kwargs) -> dict:
""" Prepare request data """
self.source_data = kwargs
return {}
@abstractmethod
def parse_response(self, response: Any) -> dict:
""" Parse response """
if self.source_data is None:
raise RuntimeError('The Request.prepare() method must be called first!')
return {}
def process_response(self, response: Any) -> dict:
""" Parse response and clean source request data """
response = self.parse_response(response)
self.source_data = None
return response
class BaseTransport(ABC): # pylint: disable=too-few-public-methods
""" Base transport class """
def __init__(self, settings: Optional[dict] = None):
self.settings = settings or {}
@abstractmethod
def _make_request(self, request_data: dict) -> Any:
""" Abstract method to make a request """
@abstractmethod
async def _make_request_async(self, request_data: dict) -> Any:
""" Abstract method to make a request """
def make_request(self, request: BaseRequest, *args) -> dict:
""" Makes a request to the service """
response = self._make_request(request.prepare(*args))
return request.process_response(response)
async def make_request_async(self, request: BaseRequest, *args) -> dict:
""" Makes a request to the service """
response = await self._make_request_async(request.prepare(*args))
return request.process_response(response)
@abstractmethod
def close(self):
""" Close connections """
@abstractmethod
async def close_async(self):
""" Close connections (async) """
================================================
FILE: unicaps/_transport/http_transport.py
================================================
# -*- coding: UTF-8 -*-
"""
Transport and requests for HTTP protocol
"""
from json.decoder import JSONDecodeError
from typing import Optional, Dict
import httpx
from .base import BaseTransport, BaseRequest # type: ignore
from ..exceptions import NetworkError, ServiceError # type: ignore
from ..__version__ import __version__ # type: ignore
HTTP_RETRY_MAX_COUNT = 5 # max retry count in case of http(s) errors
HTTP_RETRY_BACKOFF_FACTOR = 0.5 # backoff factor for Retry
HTTP_RETRY_STATUS_FORCELIST = {500, 502, 503, 504} # status forcelist for Retry
class StandardHTTPTransport(BaseTransport): # pylint: disable=too-few-public-methods
""" Standard HTTP Transport """
def __init__(self, settings: Optional[Dict] = None):
super().__init__(settings)
self.settings.setdefault('max_retries', HTTP_RETRY_MAX_COUNT)
self.settings.setdefault('handle_http_errors', True)
default_headers = {
'User-Agent': f'python-unicaps/{__version__}'
}
self.session = httpx.Client(
headers=default_headers,
timeout=httpx.Timeout(timeout=30)
)
self.session_async = httpx.AsyncClient(
headers=default_headers,
timeout=httpx.Timeout(timeout=30)
)
def _make_request(self, request_data: Dict) -> httpx.Response:
if 'headers' not in request_data:
request_data['headers'] = {}
try:
response = self.session.request(**request_data)
except httpx.TimeoutException as exc:
raise NetworkError('Timeout') from exc
except httpx.RequestError as exc:
raise NetworkError('RequestError') from exc
if self.settings['handle_http_errors']:
try:
response.raise_for_status()
except httpx.HTTPStatusError as exc:
raise NetworkError('HTTPStatusError') from exc
return response
async def _make_request_async(self, request_data: Dict) -> httpx.Response:
if 'headers' not in request_data:
request_data['headers'] = {}
try:
response = await self.session_async.request(**request_data)
except httpx.TimeoutException as exc:
raise NetworkError('Timeout') from exc
except httpx.RequestError as exc:
raise NetworkError('RequestError') from exc
if self.settings['handle_http_errors']:
try:
response.raise_for_status()
except httpx.HTTPStatusError as exc:
raise NetworkError('HTTPStatusError') from exc
return response
def close(self):
""" Close connections """
self.session.close()
async def close_async(self):
""" Close connections (async) """
await self.session_async.aclose()
class HTTPRequestJSON(BaseRequest):
""" HTTP Request that returns JSON response """
def prepare(self, **kwargs) -> Dict:
""" Prepares request """
request = super().prepare(**kwargs)
request.update(
dict(headers={'Accept': 'application/json'})
)
return request
def parse_response(self, response: httpx.Response) -> Dict:
""" Parses response """
try:
return response.json()
except JSONDecodeError as exc:
raise ServiceError("Unable to parse response from the server: bad JSON") from exc
================================================
FILE: unicaps/captcha.py
================================================
# -*- coding: UTF-8 -*-
"""
Supported CAPTCHAs
~~~~~~~~~~~~~~~~~~
"""
# pylint: disable=unused-import,import-error
from ._captcha import (ImageCaptcha, TextCaptcha, RecaptchaV2, RecaptchaV3, HCaptcha, FunCaptcha,
KeyCaptcha, GeeTest, GeeTestV4, CapyPuzzle, TikTokCaptcha, CaptchaType)
__all__ = (
'ImageCaptcha',
'TextCaptcha',
'RecaptchaV2',
'RecaptchaV3',
'HCaptcha',
'FunCaptcha',
'KeyCaptcha',
'GeeTest',
'GeeTestV4',
'CapyPuzzle',
'TikTokCaptcha',
'CaptchaType'
)
================================================
FILE: unicaps/common.py
================================================
# -*- coding: UTF-8 -*-
"""
CAPTCHA common stuff
"""
import enum
class CaptchaAlphabet(enum.Enum):
""" Alphabet used in the CAPTCHA """
LATIN = 'latin'
CYRILLIC = 'cyrillic'
class CaptchaCharType(enum.Enum):
""" Character types used in CAPTCHA """
NUMERIC = 1
ALPHA = 2
ALPHA_OR_NUMERIC = 3
ALPHANUMERIC = 4
class WorkerLanguage(enum.Enum):
""" Worker's language to solve the CAPTCHA """
ENGLISH = 'en'
RUSSIAN = 'ru'
SPANISH = 'es'
PORTUGUESE = 'pt'
UKRAINIAN = 'uk'
VIETNAMESE = 'vi'
FRENCH = 'fr'
INDONESIAN = 'id'
ARAB = 'ar'
JAPANESE = 'ja'
TURKISH = 'tr'
GERMAN = 'de'
CHINESE = 'zh'
# PHILIPPINE = 'fil'
POLISH = 'pl'
THAI = 'th'
ITALIAN = 'it'
DUTCH = 'nl'
SLOVAK = 'sk'
BULGARIAN = 'bg'
ROMANIAN = 'ro'
HUNGARIAN = 'hu'
KOREAN = 'ko'
CZECH = 'cs'
AZERBAIJANI = 'az'
PERSIAN = 'fa'
BENGALI = 'bn'
GREEK = 'el'
LITHUANIAN = 'lt'
LATVIAN = 'lv'
SWEDISH = 'sv'
SERBIAN = 'sr'
CROATIAN = 'hr'
HEBREW = 'he'
HINDI = 'hi'
NORWEGIAN = 'nb'
SLOVENIAN = 'sl'
DANISH = 'da'
UZBEK = 'uz'
FINNISH = 'fi'
CATALAN = 'ca'
GEORGIAN = 'ka'
MALAY = 'ms'
TELUGU = 'te'
ESTONIAN = 'et'
MALAYALAM = 'ml'
BELORUSSIAN = 'be'
KAZAKH = 'kk'
MARATHI = 'mr'
NEPALI = 'ne'
BURMESE = 'my'
BOSNIAN = 'bs'
ARMENIAN = 'hy'
MACEDONIAN = 'mk'
PUNJABI = 'pa'
================================================
FILE: unicaps/exceptions.py
================================================
# -*- coding: UTF-8 -*-
"""
unicaps.exceptions
~~~~~~~~~~~~~~~~~~~
This module contains the set of Unicaps' exceptions.
"""
class UnicapsException(Exception):
"""Main exception class"""
class SolutionNotReadyYet(UnicapsException):
"""CAPTCHA solving in progress"""
class ServiceError(UnicapsException):
"""Main service-related exception class"""
class CaptchaError(UnicapsException):
"""CAPTCHA-related exception"""
class NetworkError(UnicapsException):
"""
Network Connection Error
Service returned 5xx status code
"""
class ProxyError(UnicapsException):
"""
Bad proxy
"""
class AccessDeniedError(ServiceError):
"""
Wrong API key
IP banned
IP not allowed
"""
class LowBalanceError(ServiceError):
"""
Low balance
"""
class ServiceTooBusy(ServiceError):
"""
No available slots
"""
class SolutionWaitTimeout(ServiceError):
"""
Didn't receive solution within N minutes
"""
class TooManyRequestsError(ServiceError):
"""
Exceeded request limit
"""
class MalformedRequestError(ServiceError):
"""
Exceeded request limit
"""
class BadInputDataError(CaptchaError):
"""
Not supported image file
Empty file
Image file is too big
Bad captcha data (eg, wrong googlekey, bad page URL, etc.)
"""
class UnableToSolveError(CaptchaError):
"""
Captcha unsolvable
"""
================================================
FILE: unicaps/proxy.py
================================================
# -*- coding: UTF-8 -*-
"""
Proxy
"""
# pylint: disable=unused-import,import-error
from ._misc.proxy import ProxyServer, ProxyServerType
__all__ = 'ProxyServer', 'ProxyServerType'