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
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/README.md b/README.md
index 41ad12f..c9619aa 100644
--- a/README.md
+++ b/README.md
@@ -1,66 +1,153 @@
# python3-anticaptcha
-[](https://vyjava.xyz/dashboard/image/b18528fc-8572-4167-9d2f-abaacf4e1053)
-
-
-
[](https://badge.fury.io/py/python3-anticaptcha)
[](https://badge.fury.io/py/python3-anticaptcha)
[](https://pepy.tech/project/python3-anticaptcha)
[](https://andreidrang.github.io/python3-anticaptcha/)
-
-[](https://codeclimate.com/github/AndreiDrang/python3-anticaptcha)
-[](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)
-[](https://codecov.io/gh/AndreiDrang/python3-anticaptcha)
-
-[](https://github.com/AndreiDrang/python3-anticaptcha/actions/workflows/sphinx.yml)
-[](https://github.com/AndreiDrang/python3-anticaptcha/actions/workflows/build.yml)
-[](https://github.com/AndreiDrang/python3-anticaptcha/actions/workflows/install.yml)
[](https://github.com/AndreiDrang/python3-anticaptcha/actions/workflows/test.yml)
[](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: [](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.
diff --git a/docs/requirements.txt b/docs/requirements.txt
index e44b97a..22cb762 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,3 +1,3 @@
-sphinx==8.3.0
+sphinx==9.1.0
pallets_sphinx_themes==2.3.0
-myst-parser==4.0.1
+myst-parser==5.0.0
diff --git a/pyproject.toml b/pyproject.toml
index 3657f1c..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",
@@ -84,7 +86,7 @@ classifiers = [
dependencies = [
"requests>=2.21.0",
"aiohttp>=3.9.2",
- "msgspec>=0.18,<0.20",
+ "msgspec>=0.18,<0.21",
"tenacity>=8,<10"
]
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/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/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/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/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
+```
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")