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 [![PyPI pyversions](https://img.shields.io/pypi/pyversions/unicaps.png)](https://pypi.python.org/pypi/unicaps/) [![PyPI version](https://img.shields.io/pypi/v/unicaps)](https://pypi.python.org/pypi/unicaps/) [![PyPI status](https://img.shields.io/pypi/status/unicaps.png)](https://pypi.python.org/pypi/unicaps/) [![CodeFactor](https://www.codefactor.io/repository/github/sergey-scat/unicaps/badge)](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'