From e9feb598dba1ce9fdca84d059622c7ddbc356641 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 16:53:59 +0000 Subject: [PATCH 01/10] Update msgspec requirement from <0.20,>=0.18 to >=0.18,<0.21 Updates the requirements on [msgspec](https://github.com/jcrist/msgspec) to permit the latest version. - [Release notes](https://github.com/jcrist/msgspec/releases) - [Changelog](https://github.com/jcrist/msgspec/blob/main/docs/changelog.md) - [Commits](https://github.com/jcrist/msgspec/compare/0.18.0...0.20.0) --- updated-dependencies: - dependency-name: msgspec dependency-version: 0.20.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3657f1c..0eede2d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,7 +84,7 @@ classifiers = [ dependencies = [ "requests>=2.21.0", "aiohttp>=3.9.2", - "msgspec>=0.18,<0.20", + "msgspec>=0.18,<0.21", "tenacity>=8,<10" ] From a074514bb8e5e3b300911ad8d2c0ebe9b42ed846 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 1 Dec 2025 20:37:15 +0300 Subject: [PATCH 02/10] Update sphinx from 8.3.0 to 9.0.1 --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index e44b97a..103fd48 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,3 +1,3 @@ -sphinx==8.3.0 +sphinx==9.0.1 pallets_sphinx_themes==2.3.0 myst-parser==4.0.1 From bb918f5cc63517994cf2e79eb93b3e9e67d0fd33 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Thu, 4 Dec 2025 06:54:40 +0300 Subject: [PATCH 03/10] Update sphinx from 9.0.1 to 9.0.3 --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 103fd48..b90b6b1 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,3 +1,3 @@ -sphinx==9.0.1 +sphinx==9.0.3 pallets_sphinx_themes==2.3.0 myst-parser==4.0.1 From 3c3b7aa62dbc9c1f27e104296f2b8ba5b86bf671 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Wed, 31 Dec 2025 18:54:28 +0300 Subject: [PATCH 04/10] Update sphinx from 9.0.3 to 9.1.0 --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index b90b6b1..dbddd28 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,3 +1,3 @@ -sphinx==9.0.3 +sphinx==9.1.0 pallets_sphinx_themes==2.3.0 myst-parser==4.0.1 From 464013d6311cebbe60b5444e8438fa4abff4b64e Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Thu, 15 Jan 2026 12:16:18 +0300 Subject: [PATCH 05/10] Update myst-parser from 4.0.1 to 5.0.0 --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index b90b6b1..e8d1706 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,3 +1,3 @@ sphinx==9.0.3 pallets_sphinx_themes==2.3.0 -myst-parser==4.0.1 +myst-parser==5.0.0 From b2cef44e3412ec105d5e5d1c799a62c5aa7b8aa6 Mon Sep 17 00:00:00 2001 From: Andrei Date: Sun, 15 Feb 2026 23:05:22 +0300 Subject: [PATCH 06/10] Add hierarchical AGENTS.md knowledge base --- AGENTS.md | 84 ++++++++++++++++++++++++++ src/python3_anticaptcha/AGENTS.md | 41 +++++++++++++ src/python3_anticaptcha/core/AGENTS.md | 23 +++++++ tests/AGENTS.md | 24 ++++++++ 4 files changed, 172 insertions(+) create mode 100644 AGENTS.md create mode 100644 src/python3_anticaptcha/AGENTS.md create mode 100644 src/python3_anticaptcha/core/AGENTS.md create mode 100644 tests/AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..774059f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,84 @@ +# PROJECT KNOWLEDGE BASE + +**Generated:** 2026-02-15 +**Commit:** 3a0e55c +**Branch:** master + +## OVERVIEW +Python client for AntiCaptcha service API. Supports 12 captcha types with sync/async handlers using msgspec for serialization. + +## STRUCTURE +``` +python3-anticaptcha/ +├── src/python3_anticaptcha/ # Main package +│ ├── core/ # Shared: base classes, enums, serializer +│ ├── recaptcha_v2.py # Individual captcha modules +│ ├── recaptcha_v3.py +│ └── ... # 10 more captcha types +├── tests/ # pytest + asyncio +├── docs/ # Sphinx documentation +├── Makefile # Build/test/lint commands +└── .github/workflows/ # 5 CI workflows (test, install, lint, build, sphinx) +``` + +## WHERE TO LOOK +| Task | Location | Notes | +|------|----------|-------| +| Add new captcha type | `src/python3_anticaptcha/` | Copy existing module pattern | +| Base classes | `core/base.py` | CaptchaParams, CaptchaResponse | +| Enums | `core/enum.py` | CaptchaTypeEnm, ProxyTypeEnm, ResponseStatusEnm | +| HTTP handling | `core/captcha_instrument.py` | SynchronousInstrument, AsyncInstrument | +| Serialization | `core/serializer.py` | msgspec.Struct base | +| Config | `config.py` | API key, urls | +| Tests | `tests/` | pytest-asyncio, mock server | +| CI | `.github/workflows/` | 5 separate workflows | + +## CODE MAP + +| Symbol | Type | Location | Role | +|--------|------|----------|------| +| ReCaptchaV2 | Class | recaptcha_v2.py | Google reCAPTCHA v2 | +| ReCaptchaV3 | Class | recaptcha_v3.py | Google reCAPTCHA v3 | +| ImageToText | Class | image_to_text.py | Text from image | +| FunCaptcha | Class | funcaptcha.py | Arkose Labs | +| GeeTest | Class | geetest.py | GeeTest captcha | +| Turnstile | Class | turnstile.py | Cloudflare Turnstile | +| FriendlyCaptcha | Class | friendly_captcha.py | FriendlyCaptcha | +| Prosopo | Class | prosopo.py | Prosopo captcha | +| AmazonWAF | Class | amazon_waf.py | Amazon WAF captcha | +| CustomTask | Class | custom_task.py | Custom task template | +| Control | Class | control.py | Balance/status | +| CaptchaParams | Base | core/base.py | Parent for all params | +| CaptchaResponse | Base | core/base.py | Parent for responses | +| SynchronousInstrument | Class | core/captcha_instrument.py | Sync HTTP client | +| AsyncInstrument | Class | core/captcha_instrument.py | Async HTTP client | + +## CONVENTIONS +- **Format**: Black + isort (120 char, py310 target) +- **Imports**: isort with black profile, length_sort +- **Docstrings**: Google-style (Args/Returns/Examples/Notes) +- **Serialization**: msgspec.Struct with type annotations +- **Type hints**: Union[X,Y], Optional[X] syntax (not |) +- **Async**: All captcha classes have `captcha_handler()` sync + `aio_captcha_handler()` async + +## ANTI-PATTERNS (THIS PROJECT) +- **SSL**: `verify=False` in sio_captcha_instrument.py:32 - INTENTIONAL for proxy support, suppresses urllib3 warnings +- **apiDomain**: DO NOT use in ReCaptcha - deprecated by AntiCaptcha +- **Proxy restrictions**: Never use hostnames, transparent proxies, local networks (192.*.*, 10.*.*, 127.*.*) +- **Exceptions**: Only ValueError raised - no custom exception hierarchy +- **Bare except**: Used in some cleanup - avoid expanding + +## COMMANDS +```bash +make tests # pytest + coverage +make lint # autoflake/black/isort --check +make build # python3 -m build +make doc # sphinx-build +make upload # twine upload dist/* +``` + +## NOTES +- API_KEY required as env var or constructor param +- Tests use mock server (tests.static.responses) +- Manual PyPI upload (make upload) - no auto-publish +- Docs deploy only on release branch diff --git a/src/python3_anticaptcha/AGENTS.md b/src/python3_anticaptcha/AGENTS.md new file mode 100644 index 0000000..d5f557c --- /dev/null +++ b/src/python3_anticaptcha/AGENTS.md @@ -0,0 +1,41 @@ +# python3_anticaptcha Package + +## OVERVIEW +Main package - 12 captcha handler classes with sync/async API. + +## STRUCTURE +``` +python3_anticaptcha/ +├── __init__.py # Exports version only +├── config.py # API URLs, key handling +├── core/ # Shared utilities (see core/AGENTS.md) +├── recaptcha_v2.py # Captcha type modules +├── recaptcha_v3.py +├── image_to_text.py +├── funcaptcha.py +├── geetest.py +├── turnstile.py +├── friendly_captcha.py +├── prosopo.py +├── amazon_waf.py +├── custom_task.py +└── control.py +``` + +## WHERE TO LOOK +| Task | Location | +|------|----------| +| Add captcha type | Copy existing module, register in CaptchaTypeEnm | +| Modify HTTP client | `core/captcha_instrument.py` | +| Change serialization | `core/serializer.py` | +| Add enum | `core/enum.py` | + +## CONVENTIONS +- Each captcha module: one class with `captcha_handler()` + `aio_captcha_handler()` +- Inherit from `CaptchaParams` (core/base.py) +- Params passed as constructor kwargs +- Return `CaptchaResponse` subclass + +## ANTI-PATTERNS +- Don't add to `__init__.py` exports - users import directly +- Don't modify core enums without updating all handlers diff --git a/src/python3_anticaptcha/core/AGENTS.md b/src/python3_anticaptcha/core/AGENTS.md new file mode 100644 index 0000000..654c288 --- /dev/null +++ b/src/python3_anticaptcha/core/AGENTS.md @@ -0,0 +1,23 @@ +# Core Module + +## OVERVIEW +Shared utilities: base classes, enums, HTTP instruments, serialization. + +## WHERE TO LOOK +| Component | File | +|-----------|------| +| Base classes | base.py - CaptchaParams, CaptchaResponse | +| Enums | enum.py - CaptchaTypeEnm, ProxyTypeEnm, ResponseStatusEnm, SaveFormatsEnm | +| HTTP sync | captcha_instrument.py - SynchronousInstrument | +| HTTP async | captcha_instrument.py - AsyncInstrument | +| Serialization | serializer.py - msgspec.Struct configs | + +## CONVENTIONS +- All params classes inherit CaptchaParams +- All response classes inherit CaptchaResponse +- msgspec.Struct for fast serialization +- HTTP instruments handle session lifecycle + +## ANTI-PATTERNS +- `verify=False` in sio_captcha_instrument.py:32 - INTENTIONAL, don't "fix" +- Don't add new enums without updating parent package diff --git a/tests/AGENTS.md b/tests/AGENTS.md new file mode 100644 index 0000000..e0fe468 --- /dev/null +++ b/tests/AGENTS.md @@ -0,0 +1,24 @@ +# Tests + +## OVERVIEW +pytest + pytest-asyncio with mock server responses. + +## WHERE TO LOOK +| Task | Location | +|------|----------| +| Test patterns | tests/*.py | +| Mock responses | tests/static/responses.py | +| Fixtures | Conftest in each test file | + +## CONVENTIONS +- asyncio_mode = auto (pytest.ini) +- API_KEY from env or mock +- Each captcha type has dedicated test file +- Mock server responses in static/responses.py + +## COMMANDS +```bash +pytest # Run all tests +pytest -k recaptcha # Run specific tests +make tests # pytest + coverage +``` From e8393194a135a8aa0e66bf1de6395e5d951f6cc2 Mon Sep 17 00:00:00 2001 From: Andrei Date: Sun, 15 Feb 2026 23:11:29 +0300 Subject: [PATCH 07/10] Improve README with code examples and clearer documentation --- README.md | 157 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 122 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 41ad12f..c9619aa 100644 --- a/README.md +++ b/README.md @@ -1,66 +1,153 @@ # python3-anticaptcha -[![AntiCaptcha.png](https://s.vyjava.xyz/files/2024/12-December/17/b18528fc/AntiCaptcha.png)](https://vyjava.xyz/dashboard/image/b18528fc-8572-4167-9d2f-abaacf4e1053) - -
- [![PyPI version](https://badge.fury.io/py/python3-anticaptcha.svg)](https://badge.fury.io/py/python3-anticaptcha) [![Python versions](https://img.shields.io/pypi/pyversions/python3-anticaptcha.svg?logo=python&logoColor=FBE072)](https://badge.fury.io/py/python3-anticaptcha) [![Downloads](https://static.pepy.tech/badge/python3-anticaptcha/month)](https://pepy.tech/project/python3-anticaptcha) [![Static Badge](https://img.shields.io/badge/docs-Sphinx-green?label=Documentation&labelColor=gray)](https://andreidrang.github.io/python3-anticaptcha/) - -[![Code Climate](https://codeclimate.com/github/AndreiDrang/python3-anticaptcha/badges/gpa.svg)](https://codeclimate.com/github/AndreiDrang/python3-anticaptcha) -[![Codacy Badge](https://app.codacy.com/project/badge/Grade/7f49780f2edb48d4b133833887c850e8)](https://www.codacy.com/gh/AndreiDrang/python3-anticaptcha/dashboard?utm_source=github.com&utm_medium=referral&utm_content=AndreiDrang/python3-anticaptcha&utm_campaign=Badge_Grade) -[![codecov](https://codecov.io/gh/AndreiDrang/python3-anticaptcha/branch/main/graph/badge.svg?token=W92nfZY6Tz)](https://codecov.io/gh/AndreiDrang/python3-anticaptcha) - -[![Sphinx docs](https://github.com/AndreiDrang/python3-anticaptcha/actions/workflows/sphinx.yml/badge.svg?branch=release)](https://github.com/AndreiDrang/python3-anticaptcha/actions/workflows/sphinx.yml) -[![Building](https://github.com/AndreiDrang/python3-anticaptcha/actions/workflows/build.yml/badge.svg)](https://github.com/AndreiDrang/python3-anticaptcha/actions/workflows/build.yml) -[![Installation check](https://github.com/AndreiDrang/python3-anticaptcha/actions/workflows/install.yml/badge.svg?branch=main)](https://github.com/AndreiDrang/python3-anticaptcha/actions/workflows/install.yml) [![Test](https://github.com/AndreiDrang/python3-anticaptcha/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/AndreiDrang/python3-anticaptcha/actions/workflows/test.yml) [![Lint](https://github.com/AndreiDrang/python3-anticaptcha/actions/workflows/lint.yml/badge.svg?branch=main)](https://github.com/AndreiDrang/python3-anticaptcha/actions/workflows/lint.yml) +Python 3 client library for [AntiCaptcha](https://getcaptchasolution.com/vchfpctqyz) service - solve reCAPTCHA, hCaptcha, image captchas, and more programmatically. -Python 3 library for [AntiCaptcha](https://getcaptchasolution.com/vchfpctqyz) service API. +## Why use this library? -The library is intended for software developers and is used to work with the [AntiCaptcha](https://getcaptchasolution.com/vchfpctqyz) service API. -Tested on UNIX based OS. +AntiCaptcha is a paid captcha solving service. This library provides a clean Python interface to: +- Submit captchas to AntiCaptcha's worker network +- Poll for results automatically +- Handle proxy rotation for high-volume requests +- Support both synchronous and asynchronous workflows -Love Rust? Me too! Check AntiCaptcha API binding for Rust - [Rust-AntiCaptcha crate](https://crates.io/crates/rust-anticaptcha). +## Supported Captcha Types -## How to install? +| Type | Class | Use Case | +|------|-------|----------| +| reCAPTCHA v2 | `ReCaptchaV2` | Google reCAPTCHA V2 checkbox/invisible | +| reCAPTCHA v3 | `ReCaptchaV3` | Google reCAPTCHA V3 score-based | +| Image Captcha | `ImageToText` | Classic text-from-image captchas | +| Image Coordinates | `ImageToCoordinates` | Click-on-image captchas | +| FunCaptcha | `FunCaptcha` | Arkose Labs (formerly FunCaptcha) | +| GeeTest | `GeeTest` | Chinese GeeTest captcha | +| Turnstile | `Turnstile` | Cloudflare Turnstile | +| FriendlyCaptcha | `FriendlyCaptcha` | FriendlyCaptcha puzzles | +| Prosopo | `Prosopo` | Prosopo captcha | +| Amazon WAF | `AmazonWAF` | AWS WAF Captcha | -We recommend using the latest version of Python. `python3-anticaptcha` supports Python 3.7+. +## Quick Start -#### pip +### 1. Install ```bash pip install python3-anticaptcha ``` +### 2. Get Your API Key -## How to test? +1. Log into [AntiCaptcha](https://getcaptchasolution.com/vchfpctqyzclients/settings/apisetup) +2. Copy your API key from the "Setup" section -1. You need set ``API_KEY`` in your environment(get this value from you account). -2. Run command ``make tests``, from root directory. +### 3. Solve a reCAPTCHA -### Additional info -1. [Library usage examples && Docs](https://andreidrang.github.io/python3-anticaptcha/) -2. [AntiCaptcha errors list](https://getcaptchasolution.com/vchfpctqyzapidoc/errors) +```python +from python3_anticaptcha import ReCaptchaV2 +from python3_anticaptcha.core.enum import CaptchaTypeEnm +# Basic usage (no proxy) +result = ReCaptchaV2( + api_key="YOUR_API_KEY", + captcha_type=CaptchaTypeEnm.RecaptchaV2TaskProxyless, + websiteURL="https://example.com/page-with-captcha", + websiteKey="6LeIxAKTAAAAAJ309xRj9YBN2aaaaaaaaa", # sitekey from the page +).captcha_handler() + +print(result["solution"]["gRecaptchaResponse"]) +``` + +### 4. Solve with Proxy + +```python +from python3_anticaptcha import ReCaptchaV2 +from python3_anticaptcha.core.enum import CaptchaTypeEnm, ProxyTypeEnm + +result = ReCaptchaV2( + api_key="YOUR_API_KEY", + captcha_type=CaptchaTypeEnm.RecaptchaV2Task, + websiteURL="https://example.com/page-with-captcha", + websiteKey="6LeIxAKTAAAAAJ309xRj9YBN2aaaaaaaaa", + proxyType=ProxyTypeEnm.HTTP, + proxyAddress="123.45.67.89", + proxyPort=8080, + proxyLogin="proxy_user", + proxyPassword="proxy_pass", + userAgent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", +).captcha_handler() +``` -### How to get API Key to work with the library -1. On the page - https://getcaptchasolution.com/vchfpctqyzclients/settings/apisetup -2. Find it: [![img.png](https://s.vyjava.xyz/files/2024/12-December/17/5d6a902c/img.png)](https://vyjava.xyz/dashboard/image/5d6a902c-6997-47dd-af2a-734bde9bd1fb) +### 5. Async Usage -### Contacts +```python +import asyncio +from python3_anticaptcha import ReCaptchaV2 +from python3_anticaptcha.core.enum import CaptchaTypeEnm -If you have any questions, please send a message to the [Telegram](https://t.me/pythoncaptcha) chat room. +async def solve(): + result = await ReCaptchaV2( + api_key="YOUR_API_KEY", + captcha_type=CaptchaTypeEnm.RecaptchaV2TaskProxyless, + websiteURL="https://example.com/page-with-captcha", + websiteKey="6LeIxAKTAAAAAJ309xRj9YBN2aaaaaaaaa", + ).aio_captcha_handler() + return result + +result = asyncio.run(solve()) +``` + +## Environment Variable + +Set `API_KEY` to avoid passing it in code: + +```bash +export API_KEY="your_api_key_here" +``` + +```python +# Now you can omit api_key parameter +from python3_anticaptcha import ImageToText + +result = ImageToText(captcha_file="captcha.png").captcha_handler() +``` + +## Configuration Options + +All captcha classes support these common parameters: + +| Parameter | Type | Description | +|-----------|------|-------------| +| `api_key` | str | Your AntiCaptcha API key (or set `API_KEY` env var) | +| `sleep_time` | int | Seconds between result polls (default: 10) | + +## Documentation + +- [Full Documentation](https://andreidrang.github.io/python3-anticaptcha/) - Detailed API reference +- [AntiCaptcha Errors](https://getcaptchasolution.com/vchfpctqyzapidoc/errors) - Error code meanings + +## Development + +```bash +# Run tests +make tests + +# Run linters +make lint + +# Build package +make build +``` -Or email python-captcha@pm.me +## Contacts -
+- Telegram: [pythoncaptcha](https://t.me/pythoncaptcha) +- Email: python-captcha@pm.me -## 💰 Sponsorship +--- -This project is supported by [TokenBel.info](https://dashboard.tokenbel.info/?utm_source=pypi), which helps maintain its development and acts as a sponsor. -TokenBel is an information platform for investing in tokens, providing analytics, financial data, and market insights. +Love Rust? Check out [Rust-AntiCaptcha](https://crates.io/crates/rust-anticaptcha) - same API for Rust projects. From e06d7c2157458b3c45b63c906f79019cb0957447 Mon Sep 17 00:00:00 2001 From: Andrei Date: Sun, 15 Feb 2026 23:15:28 +0300 Subject: [PATCH 08/10] Update build.yml --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 92a2fc9..1b43f04 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,7 +20,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11", "3.12"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v3 From c74210f9975eac7e23c22195ce7f73b36217df5a Mon Sep 17 00:00:00 2001 From: Andrei Date: Sun, 15 Feb 2026 23:17:39 +0300 Subject: [PATCH 09/10] Update pyproject.toml --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 0eede2d..ace4c13 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,6 +76,8 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Framework :: AsyncIO", "Operating System :: Unix", "Operating System :: Microsoft :: Windows", From 276bf9725de6b87fb016840ffe618c7cbce02c31 Mon Sep 17 00:00:00 2001 From: Andrei Date: Mon, 16 Feb 2026 00:09:41 +0300 Subject: [PATCH 10/10] Add Altcha captcha support - Add AltchaTask and AltchaTaskProxyless to CaptchaTypeEnm - Add Altcha captcha handler class (altcha.py) - Add test suite for Altcha (test_altcha.py) --- src/python3_anticaptcha/altcha.py | 148 +++++++++++++++++++++++++++ src/python3_anticaptcha/core/enum.py | 3 + tests/test_altcha.py | 109 ++++++++++++++++++++ 3 files changed, 260 insertions(+) create mode 100644 src/python3_anticaptcha/altcha.py create mode 100644 tests/test_altcha.py diff --git a/src/python3_anticaptcha/altcha.py b/src/python3_anticaptcha/altcha.py new file mode 100644 index 0000000..f2201dc --- /dev/null +++ b/src/python3_anticaptcha/altcha.py @@ -0,0 +1,148 @@ +from typing import Union, Optional + +from .core.base import CaptchaParams +from .core.enum import ProxyTypeEnm, CaptchaTypeEnm + +__all__ = ("Altcha",) + + +class Altcha(CaptchaParams): + def __init__( + self, + api_key: str, + captcha_type: Union[CaptchaTypeEnm, str], + websiteURL: str, + challengeURL: Optional[str] = None, + challengeJSON: Optional[str] = None, + proxyType: Optional[Union[ProxyTypeEnm, str]] = None, + proxyAddress: Optional[str] = None, + proxyPort: Optional[int] = None, + proxyLogin: Optional[str] = None, + proxyPassword: Optional[str] = None, + userAgent: Optional[str] = None, + sleep_time: Optional[int] = 10, + ): + """ + The class is used to work with Altcha. + + Args: + api_key: Capsolver API key + captcha_type: Captcha type + websiteURL: Address of the webpage + challengeURL: Full URL to the challenge page (use either challengeURL or challengeJSON) + challengeJSON: JSON-encoded challenge data string (use either challengeURL or challengeJSON) + proxyType: Type of the proxy + proxyAddress: Proxy IP address IPv4/IPv6. Not allowed to use: + host names instead of IPs, + transparent proxies (where client IP is visible), + proxies from local networks (192.., 10.., 127...) + proxyPort: Proxy port. + proxyLogin: Proxy login. + proxyPassword: Proxy password. + userAgent: Browser UserAgent. + sleep_time: The waiting time between requests to get the result of the Captcha + + Examples: + >>> Altcha(api_key="99d7d111a0111dc11184111c8bb111da", + ... captcha_type="AltchaTaskProxyless", + ... websiteURL="https://example.com/", + ... challengeURL="/api/challenge" + ... ).captcha_handler() + { + "errorId": 0, + "errorCode": None, + "errorDescription": None, + "status":"ready", + "solution":{ + "token":"0.Qz0.....f1", + "userAgent":"Mozilla/.....36" + }, + "cost": 0.002, + "ip": "46.53.249.230", + "createTime": 1679004358, + "endTime": 1679004368, + "solveCount": 0, + "taskId": 396687629 + } + + >>> await Altcha(api_key="99d7d111a0111dc11184111c8bb111da", + ... captcha_type="AltchaTaskProxyless", + ... websiteURL="https://example.com/", + ... challengeJSON='{"algorithm":"SHA-256","challenge":"..."}' + ... ).aio_captcha_handler() + { + "errorId": 0, + "errorCode": None, + "errorDescription": None, + "status":"ready", + "solution":{ + "token":"0.Qz0.....f1", + "userAgent":"Mozilla/.....36" + }, + "cost": 0.002, + "ip": "46.53.249.230", + "createTime": 1679004358, + "endTime": 1679004368, + "solveCount": 0, + "taskId": 396687629 + } + + >>> Altcha(api_key="99d7d111a0111dc11184111c8bb111da", + ... captcha_type="AltchaTask", + ... websiteURL="https://example.com/", + ... challengeURL="/api/challenge", + ... proxyType="http", + ... proxyAddress="1.2.3.4", + ... proxyPort=8080 + ... ).captcha_handler() + { + "errorId": 0, + "errorCode": None, + "errorDescription": None, + "status":"ready", + "solution":{ + "token":"0.Qz0.....f1", + "userAgent":"Mozilla/.....36" + }, + "cost": 0.002, + "ip": "46.53.249.230", + "createTime": 1679004358, + "endTime": 1679004368, + "solveCount": 0, + "taskId": 396687629 + } + + Notes: + https://anti-captcha.com/apidoc/task-types/AltchaTask + + https://anti-captcha.com/apidoc/task-types/AltchaTaskProxyless + """ + + super().__init__(api_key=api_key, sleep_time=sleep_time) + + # validation of the received parameters + if captcha_type == CaptchaTypeEnm.AltchaTask: + self.task_params = dict( + type=captcha_type, + websiteURL=websiteURL, + challengeURL=challengeURL, + challengeJSON=challengeJSON, + proxyType=proxyType, + proxyAddress=proxyAddress, + proxyPort=proxyPort, + proxyLogin=proxyLogin, + proxyPassword=proxyPassword, + userAgent=userAgent, + ) + elif captcha_type == CaptchaTypeEnm.AltchaTaskProxyless: + self.task_params = dict( + type=captcha_type, + websiteURL=websiteURL, + challengeURL=challengeURL, + challengeJSON=challengeJSON, + ) + else: + raise ValueError( + f"Invalid `captcha_type` parameter set for `{self.__class__.__name__}`, \ + available - {CaptchaTypeEnm.AltchaTaskProxyless.value, CaptchaTypeEnm.AltchaTask.value}" + ) diff --git a/src/python3_anticaptcha/core/enum.py b/src/python3_anticaptcha/core/enum.py index 308bd10..d7c1618 100644 --- a/src/python3_anticaptcha/core/enum.py +++ b/src/python3_anticaptcha/core/enum.py @@ -64,6 +64,9 @@ class CaptchaTypeEnm(str, MyEnum): # AmazonWAF AmazonTask = "AmazonTask" AmazonTaskProxyless = "AmazonTaskProxyless" + # Altcha + AltchaTask = "AltchaTask" + AltchaTaskProxyless = "AltchaTaskProxyless" # Custom AntiGateTask = "AntiGateTask" diff --git a/tests/test_altcha.py b/tests/test_altcha.py new file mode 100644 index 0000000..28e395f --- /dev/null +++ b/tests/test_altcha.py @@ -0,0 +1,109 @@ +import pytest + +from tests.conftest import BaseTest +from python3_anticaptcha.core.enum import ProxyTypeEnm, CaptchaTypeEnm +from python3_anticaptcha.altcha import Altcha +from python3_anticaptcha.core.serializer import GetTaskResultResponseSer +from python3_anticaptcha.core.context_instr import AIOContextManager, SIOContextManager + + +class TestAltcha(BaseTest): + websiteURL = "https://example.com/" + challengeURL = "/api/challenge" + + def test_sio_success(self): + instance = Altcha( + api_key=self.API_KEY, + websiteURL=self.websiteURL, + challengeURL=self.challengeURL, + captcha_type=CaptchaTypeEnm.AltchaTaskProxyless, + ) + result = instance.captcha_handler() + + assert isinstance(result, dict) + ser_result = GetTaskResultResponseSer(**result) + assert ser_result.errorId == 0 + assert ser_result.taskId is not None + assert ser_result.cost != 0.0 + + async def test_aio_success(self): + instance = Altcha( + api_key=self.API_KEY, + websiteURL=self.websiteURL, + challengeURL=self.challengeURL, + captcha_type=CaptchaTypeEnm.AltchaTaskProxyless, + ) + result = await instance.aio_captcha_handler() + + assert isinstance(result, dict) + ser_result = GetTaskResultResponseSer(**result) + assert ser_result.errorId == 0 + assert ser_result.taskId is not None + assert ser_result.cost != 0.0 + + def test_err_captcha_type(self): + with pytest.raises(ValueError): + Altcha( + api_key=self.API_KEY, + websiteURL=self.websiteURL, + challengeURL=self.challengeURL, + captcha_type=self.get_random_string(length=10), + ) + + @pytest.mark.parametrize("proxyType", ProxyTypeEnm) + def test_proxy_args(self, proxyType: ProxyTypeEnm): + proxy_args = self.get_proxy_args() + proxy_args.update({"proxyType": proxyType}) + instance = Altcha( + api_key=self.API_KEY, + websiteURL=self.websiteURL, + challengeURL=self.challengeURL, + captcha_type=CaptchaTypeEnm.AltchaTask, + **proxy_args, + ) + for key, value in proxy_args.items(): + assert instance.task_params[key] == value + + def test_context(self, mocker): + context_enter_spy = mocker.spy(SIOContextManager, "__enter__") + context_exit_spy = mocker.spy(SIOContextManager, "__exit__") + with Altcha( + api_key=self.API_KEY, + websiteURL=self.websiteURL, + challengeURL=self.challengeURL, + captcha_type=CaptchaTypeEnm.AltchaTaskProxyless, + ) as instance: + assert context_enter_spy.call_count == 1 + assert context_exit_spy.call_count == 1 + + async def test_aio_context(self, mocker): + context_enter_spy = mocker.spy(AIOContextManager, "__aenter__") + context_exit_spy = mocker.spy(AIOContextManager, "__aexit__") + async with Altcha( + api_key=self.API_KEY, + websiteURL=self.websiteURL, + challengeURL=self.challengeURL, + captcha_type=CaptchaTypeEnm.AltchaTaskProxyless, + ) as instance: + assert context_enter_spy.call_count == 1 + assert context_exit_spy.call_count == 1 + + def test_err_context(self): + with pytest.raises(ValueError): + with Altcha( + api_key=self.API_KEY, + websiteURL=self.websiteURL, + challengeURL=self.challengeURL, + captcha_type=CaptchaTypeEnm.AltchaTaskProxyless, + ) as instance: + raise ValueError("Test error") + + async def test_err_aio_context(self): + with pytest.raises(ValueError): + async with Altcha( + api_key=self.API_KEY, + websiteURL=self.websiteURL, + challengeURL=self.challengeURL, + captcha_type=CaptchaTypeEnm.AltchaTaskProxyless, + ) as instance: + raise ValueError("Test error")