From 992f01a043568a1653aafc31d5fb3f1db3a368c5 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 7 Mar 2026 21:21:38 +0000 Subject: [PATCH 1/2] Add comprehensive docstrings to all public API classes and methods Improve IDE experience (hover tooltips, autocomplete descriptions, help()) by adding RST-format docstrings to every public class and method across the library: task classes, sync/async clients, Job/AsyncJob, Proxy, and exceptions. Includes usage examples, parameter descriptions, and cross-references. https://claude.ai/code/session_012EAJ6fLNGswentS5LFX771 --- python_anticaptcha/__init__.py | 20 +++ python_anticaptcha/async_client.py | 127 +++++++++++++++- python_anticaptcha/exceptions.py | 23 +++ python_anticaptcha/proxy.py | 32 ++++ python_anticaptcha/sync_client.py | 124 ++++++++++++++- python_anticaptcha/tasks.py | 232 ++++++++++++++++++++++++++++- 6 files changed, 551 insertions(+), 7 deletions(-) diff --git a/python_anticaptcha/__init__.py b/python_anticaptcha/__init__.py index dfa2bfd..db879a6 100644 --- a/python_anticaptcha/__init__.py +++ b/python_anticaptcha/__init__.py @@ -1,3 +1,23 @@ +"""Python client library for the `Anticaptcha.com `_ API. + +Solve ReCAPTCHA v2/v3, hCaptcha, FunCaptcha, GeeTest, image-to-text, and +AntiGate tasks using human workers. Supports both synchronous (``requests``) +and asynchronous (``httpx``) usage. + +Quick start:: + + from python_anticaptcha import AnticaptchaClient, NoCaptchaTaskProxylessTask + + with AnticaptchaClient("my-api-key") as client: + task = NoCaptchaTaskProxylessTask(website_url, site_key) + job = client.create_task(task) + job.join() + print(job.get_solution_response()) + +For async usage, install with ``pip install python-anticaptcha[async]`` and use +:class:`AsyncAnticaptchaClient`. +""" + import contextlib from importlib.metadata import PackageNotFoundError, version diff --git a/python_anticaptcha/async_client.py b/python_anticaptcha/async_client.py index c496573..8028408 100644 --- a/python_anticaptcha/async_client.py +++ b/python_anticaptcha/async_client.py @@ -19,6 +19,19 @@ class AsyncJob: + """An async handle to a submitted captcha-solving task. + + Returned by :meth:`AsyncAnticaptchaClient.create_task`. Use :meth:`join` + to wait for completion, then call one of the ``get_*`` methods to + retrieve the solution. + + Example:: + + job = await client.create_task(task) + await job.join() + print(job.get_solution_response()) # for ReCAPTCHA / hCaptcha + """ + client: AsyncAnticaptchaClient task_id: int _last_result: dict[str, Any] | None = None @@ -31,38 +44,80 @@ async def _update(self) -> None: self._last_result = await self.client.getTaskResult(self.task_id) async def check_is_ready(self) -> bool: + """Poll the API once and return whether the task is complete. + + :returns: ``True`` if the solution is ready, ``False`` otherwise. + """ await self._update() assert self._last_result is not None return self._last_result["status"] == "ready" - def get_solution_response(self) -> str: # Recaptcha + def get_solution_response(self) -> str: + """Return the ``gRecaptchaResponse`` token. + + Use after solving ReCAPTCHA v2, ReCAPTCHA v3, or hCaptcha tasks. + Call this only after :meth:`join` has returned. + """ assert self._last_result is not None return self._last_result["solution"]["gRecaptchaResponse"] def get_solution(self) -> dict[str, Any]: + """Return the full solution dictionary from the API response. + + Useful for task types where the solution has multiple fields + (e.g. GeeTest returns ``challenge``, ``validate``, ``seccode``). + Call this only after :meth:`join` has returned. + """ assert self._last_result is not None return self._last_result["solution"] - def get_token_response(self) -> str: # Funcaptcha + def get_token_response(self) -> str: + """Return the ``token`` string from the solution. + + Use after solving FunCaptcha tasks. + Call this only after :meth:`join` has returned. + """ assert self._last_result is not None return self._last_result["solution"]["token"] def get_answers(self) -> dict[str, str]: + """Return the ``answers`` dictionary from the solution. + + Use after solving AntiGate tasks. + Call this only after :meth:`join` has returned. + """ assert self._last_result is not None return self._last_result["solution"]["answers"] - def get_captcha_text(self) -> str: # Image + def get_captcha_text(self) -> str: + """Return the recognized text from an image captcha. + + Use after solving :class:`ImageToTextTask` tasks. + Call this only after :meth:`join` has returned. + """ assert self._last_result is not None return self._last_result["solution"]["text"] def get_cells_numbers(self) -> list[int]: + """Return the list of selected cell numbers from a grid captcha. + + Call this only after :meth:`join` has returned. + """ assert self._last_result is not None return self._last_result["solution"]["cellNumbers"] async def report_incorrect_image(self) -> bool: + """Report that an image captcha was solved incorrectly. + + :returns: ``True`` if the report was accepted. + """ return await self.client.reportIncorrectImage(self.task_id) async def report_incorrect_recaptcha(self) -> bool: + """Report that a ReCAPTCHA was solved incorrectly. + + :returns: ``True`` if the report was accepted. + """ return await self.client.reportIncorrectRecaptcha(self.task_id) def __repr__(self) -> str: @@ -109,6 +164,28 @@ async def join( class AsyncAnticaptchaClient: + """Asynchronous client for the Anticaptcha.com API. + + Mirrors :class:`AnticaptchaClient` but all network methods are coroutines. + Requires the ``httpx`` package — install with + ``pip install python-anticaptcha[async]``. + + Can be used as an async context manager:: + + async with AsyncAnticaptchaClient("my-api-key") as client: + job = await client.create_task(task) + await job.join() + + :param client_key: Your Anticaptcha API key. If omitted, the + ``ANTICAPTCHA_API_KEY`` environment variable is used. + :param language_pool: Language pool for workers — ``"en"`` (default) + or ``"rn"`` (Russian). + :param host: API hostname (default: ``"api.anti-captcha.com"``). + :param use_ssl: Use HTTPS (default: ``True``). + :raises ImportError: If ``httpx`` is not installed. + :raises AnticaptchaException: If no API key is provided. + """ + client_key = None CREATE_TASK_URL = "/createTask" TASK_RESULT_URL = "/getTaskResult" @@ -155,6 +232,10 @@ async def __aexit__( return False async def close(self) -> None: + """Close the underlying HTTP session. + + Called automatically when using the client as an async context manager. + """ await self.session.aclose() def __repr__(self) -> str: @@ -179,6 +260,12 @@ async def _check_response(self, response: dict[str, Any]) -> None: raise AnticaptchaException(response["errorId"], response["errorCode"], response["errorDescription"]) async def createTask(self, task: BaseTask) -> AsyncJob: + """Submit a captcha task and return an :class:`AsyncJob` handle. + + :param task: A task instance (e.g. :class:`NoCaptchaTaskProxylessTask`). + :returns: An :class:`AsyncJob` that can be polled with :meth:`AsyncJob.join`. + :raises AnticaptchaException: If the API returns an error. + """ request = { "clientKey": self.client_key, "task": task.serialize(), @@ -196,12 +283,23 @@ async def createTask(self, task: BaseTask) -> AsyncJob: return AsyncJob(self, response["taskId"]) async def getTaskResult(self, task_id: int) -> dict[str, Any]: + """Fetch the current result/status of a task. + + :param task_id: The task ID returned when the task was created. + :returns: Raw API response dictionary with ``status`` and ``solution`` keys. + :raises AnticaptchaException: If the API returns an error. + """ request = {"clientKey": self.client_key, "taskId": task_id} response = (await self.session.post(urljoin(self.base_url, self.TASK_RESULT_URL), json=request)).json() await self._check_response(response) return response async def getBalance(self) -> float: + """Return the current account balance in USD. + + :returns: Account balance as a float (e.g. ``3.50``). + :raises AnticaptchaException: If the API returns an error. + """ request = { "clientKey": self.client_key, "softId": self.SOFT_ID, @@ -211,18 +309,41 @@ async def getBalance(self) -> float: return response["balance"] async def getAppStats(self, soft_id: int, mode: str) -> dict[str, Any]: + """Retrieve application statistics. + + :param soft_id: Application ID. + :param mode: Statistics mode (e.g. ``"errors"``, ``"views"``, ``"downloads"``). + :returns: Raw API response dictionary with statistics data. + :raises AnticaptchaException: If the API returns an error. + """ request = {"clientKey": self.client_key, "softId": soft_id, "mode": mode} response = (await self.session.post(urljoin(self.base_url, self.APP_STAT_URL), json=request)).json() await self._check_response(response) return response async def reportIncorrectImage(self, task_id: int) -> bool: + """Report that an image captcha was solved incorrectly. + + Use this to get a refund and improve solver accuracy. + + :param task_id: The task ID of the incorrectly solved task. + :returns: ``True`` if the report was accepted. + :raises AnticaptchaException: If the API returns an error. + """ request = {"clientKey": self.client_key, "taskId": task_id} response = (await self.session.post(urljoin(self.base_url, self.REPORT_IMAGE_URL), json=request)).json() await self._check_response(response) return bool(response.get("status", False)) async def reportIncorrectRecaptcha(self, task_id: int) -> bool: + """Report that a ReCAPTCHA was solved incorrectly. + + Use this to get a refund and improve solver accuracy. + + :param task_id: The task ID of the incorrectly solved task. + :returns: ``True`` if the report was accepted. + :raises AnticaptchaException: If the API returns an error. + """ request = {"clientKey": self.client_key, "taskId": task_id} response = (await self.session.post(urljoin(self.base_url, self.REPORT_RECAPTCHA_URL), json=request)).json() await self._check_response(response) diff --git a/python_anticaptcha/exceptions.py b/python_anticaptcha/exceptions.py index c9f3a31..02d68a7 100644 --- a/python_anticaptcha/exceptions.py +++ b/python_anticaptcha/exceptions.py @@ -2,6 +2,24 @@ class AnticaptchaException(Exception): + """Base exception for all Anticaptcha API errors. + + Raised when the API returns a non-zero ``errorId``. Inspect + :attr:`error_code` to determine the cause:: + + try: + job = client.create_task(task) + except AnticaptchaException as e: + if e.error_code == "ERROR_ZERO_BALANCE": + print("Please top up your balance") + else: + raise + + :param error_id: Numeric error ID from the API (or a local identifier). + :param error_code: Error code string (e.g. ``"ERROR_ZERO_BALANCE"``). + :param error_description: Human-readable error description. + """ + def __init__( self, error_id: int | str | None, @@ -16,9 +34,12 @@ def __init__( AnticatpchaException = AnticaptchaException +"""Backward-compatible alias (legacy misspelling).""" class InvalidWidthException(AnticaptchaException): + """Raised when an invalid grid width is specified.""" + def __init__(self, width: int) -> None: self.width = width msg = f"Invalid width ({self.width}). Can be one of these: 100, 50, 33, 25." @@ -26,6 +47,8 @@ def __init__(self, width: int) -> None: class MissingNameException(AnticaptchaException): + """Raised when a required ``name`` parameter is missing during serialization.""" + def __init__(self, cls: type) -> None: self.cls = cls msg = 'Missing name data in {0}. Provide {0}.__init__(name="X") or {0}.serialize(name="X")'.format( diff --git a/python_anticaptcha/proxy.py b/python_anticaptcha/proxy.py index c2c5493..2e60f11 100644 --- a/python_anticaptcha/proxy.py +++ b/python_anticaptcha/proxy.py @@ -6,6 +6,21 @@ @dataclass(frozen=True) class Proxy: + """Immutable representation of a proxy server. + + Use :meth:`parse_url` to build from a URL string, then pass the proxy + parameters to a proxy-enabled task class with :meth:`to_kwargs`:: + + proxy = Proxy.parse_url("socks5://user:pass@host:1080") + task = NoCaptchaTask(url, key, user_agent=UA, **proxy.to_kwargs()) + + :param proxy_type: Protocol — ``"http"``, ``"socks4"``, or ``"socks5"``. + :param proxy_address: Hostname or IP address. + :param proxy_port: Port number. + :param proxy_login: Username for authentication (default: ``""``). + :param proxy_password: Password for authentication (default: ``""``). + """ + proxy_type: str proxy_address: str proxy_port: int @@ -14,6 +29,13 @@ class Proxy: @classmethod def parse_url(cls, url: str) -> Proxy: + """Create a :class:`Proxy` from a URL string. + + :param url: Proxy URL, e.g. ``"socks5://user:pass@host:1080"`` + or ``"http://host:8080"``. + :returns: A new :class:`Proxy` instance. + :raises ValueError: If the URL is missing a hostname or port. + """ parsed = urlparse(url) if not parsed.hostname or not parsed.port: raise ValueError(f"Invalid proxy URL: {url}") @@ -26,6 +48,16 @@ def parse_url(cls, url: str) -> Proxy: ) def to_kwargs(self) -> dict[str, str | int]: + """Convert to a keyword-arguments dictionary for task constructors. + + The returned dictionary can be unpacked directly into any + proxy-enabled task class:: + + task = NoCaptchaTask(url, key, user_agent=UA, **proxy.to_kwargs()) + + :returns: Dictionary with ``proxy_type``, ``proxy_address``, + ``proxy_port``, ``proxy_login``, and ``proxy_password`` keys. + """ return { "proxy_type": self.proxy_type, "proxy_address": self.proxy_address, diff --git a/python_anticaptcha/sync_client.py b/python_anticaptcha/sync_client.py index 461e66a..195da0f 100644 --- a/python_anticaptcha/sync_client.py +++ b/python_anticaptcha/sync_client.py @@ -18,6 +18,19 @@ class Job: + """A handle to a submitted captcha-solving task. + + Returned by :meth:`AnticaptchaClient.create_task`. Use :meth:`join` to + wait for completion, then call one of the ``get_*`` methods to retrieve + the solution. + + Example:: + + job = client.create_task(task) + job.join() + print(job.get_solution_response()) # for ReCAPTCHA / hCaptcha + """ + client: AnticaptchaClient task_id: int _last_result: dict[str, Any] | None = None @@ -30,31 +43,65 @@ def _update(self) -> None: self._last_result = self.client.getTaskResult(self.task_id) def check_is_ready(self) -> bool: + """Poll the API once and return whether the task is complete. + + :returns: ``True`` if the solution is ready, ``False`` otherwise. + """ self._update() assert self._last_result is not None return self._last_result["status"] == "ready" - def get_solution_response(self) -> str: # Recaptcha + def get_solution_response(self) -> str: + """Return the ``gRecaptchaResponse`` token. + + Use after solving ReCAPTCHA v2, ReCAPTCHA v3, or hCaptcha tasks. + Call this only after :meth:`join` has returned. + """ assert self._last_result is not None return self._last_result["solution"]["gRecaptchaResponse"] def get_solution(self) -> dict[str, Any]: + """Return the full solution dictionary from the API response. + + Useful for task types where the solution has multiple fields + (e.g. GeeTest returns ``challenge``, ``validate``, ``seccode``). + Call this only after :meth:`join` has returned. + """ assert self._last_result is not None return self._last_result["solution"] - def get_token_response(self) -> str: # Funcaptcha + def get_token_response(self) -> str: + """Return the ``token`` string from the solution. + + Use after solving FunCaptcha tasks. + Call this only after :meth:`join` has returned. + """ assert self._last_result is not None return self._last_result["solution"]["token"] def get_answers(self) -> dict[str, str]: + """Return the ``answers`` dictionary from the solution. + + Use after solving AntiGate tasks. + Call this only after :meth:`join` has returned. + """ assert self._last_result is not None return self._last_result["solution"]["answers"] - def get_captcha_text(self) -> str: # Image + def get_captcha_text(self) -> str: + """Return the recognized text from an image captcha. + + Use after solving :class:`ImageToTextTask` tasks. + Call this only after :meth:`join` has returned. + """ assert self._last_result is not None return self._last_result["solution"]["text"] def get_cells_numbers(self) -> list[int]: + """Return the list of selected cell numbers from a grid captcha. + + Call this only after :meth:`join` has returned. + """ assert self._last_result is not None return self._last_result["solution"]["cellNumbers"] @@ -67,9 +114,17 @@ def report_incorrect(self) -> bool: return self.client.reportIncorrectImage(self.task_id) def report_incorrect_image(self) -> bool: + """Report that an image captcha was solved incorrectly. + + :returns: ``True`` if the report was accepted. + """ return self.client.reportIncorrectImage(self.task_id) def report_incorrect_recaptcha(self) -> bool: + """Report that a ReCAPTCHA was solved incorrectly. + + :returns: ``True`` if the report was accepted. + """ return self.client.reportIncorrectRecaptcha(self.task_id) def __repr__(self) -> str: @@ -116,6 +171,25 @@ def join( class AnticaptchaClient: + """Synchronous client for the Anticaptcha.com API. + + Create tasks, poll for results, check your balance, and report + incorrect solutions. Can be used as a context manager to ensure the + underlying HTTP session is closed:: + + with AnticaptchaClient("my-api-key") as client: + job = client.create_task(task) + job.join() + + :param client_key: Your Anticaptcha API key. If omitted, the + ``ANTICAPTCHA_API_KEY`` environment variable is used. + :param language_pool: Language pool for workers — ``"en"`` (default) + or ``"rn"`` (Russian). + :param host: API hostname (default: ``"api.anti-captcha.com"``). + :param use_ssl: Use HTTPS (default: ``True``). + :raises AnticaptchaException: If no API key is provided. + """ + client_key = None CREATE_TASK_URL = "/createTask" TASK_RESULT_URL = "/getTaskResult" @@ -158,6 +232,10 @@ def __exit__( return False def close(self) -> None: + """Close the underlying HTTP session. + + Called automatically when using the client as a context manager. + """ self.session.close() def __repr__(self) -> str: @@ -181,6 +259,12 @@ def _check_response(self, response: dict[str, Any]) -> None: raise AnticaptchaException(response["errorId"], response["errorCode"], response["errorDescription"]) def createTask(self, task: BaseTask) -> Job: + """Submit a captcha task and return a :class:`Job` handle. + + :param task: A task instance (e.g. :class:`NoCaptchaTaskProxylessTask`). + :returns: A :class:`Job` that can be polled with :meth:`Job.join`. + :raises AnticaptchaException: If the API returns an error. + """ request = { "clientKey": self.client_key, "task": task.serialize(), @@ -235,12 +319,23 @@ def createTaskSmee(self, task: BaseTask, timeout: int = MAXIMUM_JOIN_TIME) -> Jo raise AnticaptchaException(None, 250, "No matching task response received from smee.io") def getTaskResult(self, task_id: int) -> dict[str, Any]: + """Fetch the current result/status of a task. + + :param task_id: The task ID returned when the task was created. + :returns: Raw API response dictionary with ``status`` and ``solution`` keys. + :raises AnticaptchaException: If the API returns an error. + """ request = {"clientKey": self.client_key, "taskId": task_id} response = self.session.post(urljoin(self.base_url, self.TASK_RESULT_URL), json=request).json() self._check_response(response) return response def getBalance(self) -> float: + """Return the current account balance in USD. + + :returns: Account balance as a float (e.g. ``3.50``). + :raises AnticaptchaException: If the API returns an error. + """ request = { "clientKey": self.client_key, "softId": self.SOFT_ID, @@ -250,18 +345,41 @@ def getBalance(self) -> float: return response["balance"] def getAppStats(self, soft_id: int, mode: str) -> dict[str, Any]: + """Retrieve application statistics. + + :param soft_id: Application ID. + :param mode: Statistics mode (e.g. ``"errors"``, ``"views"``, ``"downloads"``). + :returns: Raw API response dictionary with statistics data. + :raises AnticaptchaException: If the API returns an error. + """ request = {"clientKey": self.client_key, "softId": soft_id, "mode": mode} response = self.session.post(urljoin(self.base_url, self.APP_STAT_URL), json=request).json() self._check_response(response) return response def reportIncorrectImage(self, task_id: int) -> bool: + """Report that an image captcha was solved incorrectly. + + Use this to get a refund and improve solver accuracy. + + :param task_id: The task ID of the incorrectly solved task. + :returns: ``True`` if the report was accepted. + :raises AnticaptchaException: If the API returns an error. + """ request = {"clientKey": self.client_key, "taskId": task_id} response = self.session.post(urljoin(self.base_url, self.REPORT_IMAGE_URL), json=request).json() self._check_response(response) return bool(response.get("status", False)) def reportIncorrectRecaptcha(self, task_id: int) -> bool: + """Report that a ReCAPTCHA was solved incorrectly. + + Use this to get a refund and improve solver accuracy. + + :param task_id: The task ID of the incorrectly solved task. + :returns: ``True`` if the report was accepted. + :raises AnticaptchaException: If the API returns an error. + """ request = {"clientKey": self.client_key, "taskId": task_id} response = self.session.post(urljoin(self.base_url, self.REPORT_RECAPTCHA_URL), json=request).json() self._check_response(response) diff --git a/python_anticaptcha/tasks.py b/python_anticaptcha/tasks.py index f123498..9b01fc0 100644 --- a/python_anticaptcha/tasks.py +++ b/python_anticaptcha/tasks.py @@ -6,9 +6,22 @@ class BaseTask: + """Base class for all Anticaptcha task types. + + Each subclass represents a specific captcha type (ReCAPTCHA, hCaptcha, etc.) + and serializes its parameters into the format expected by the Anticaptcha API. + + You do not use this class directly — instead, instantiate one of the concrete + task classes and pass it to :meth:`AnticaptchaClient.create_task`. + """ + type: str | None = None def serialize(self, **result: Any) -> dict[str, Any]: + """Serialize the task into a dictionary for the Anticaptcha API request. + + :returns: Dictionary with task parameters including the ``type`` field. + """ result["type"] = self.type return result @@ -19,6 +32,14 @@ def __repr__(self) -> str: class UserAgentMixin(BaseTask): + """Mixin that adds a ``user_agent`` parameter to a task. + + Required by proxy-enabled task variants so the captcha solver can + emulate the same browser. + + :param user_agent: Browser User-Agent string to use when solving. + """ + def __init__(self, *args: Any, **kwargs: Any) -> None: self.userAgent: str = kwargs.pop("user_agent") super().__init__(*args, **kwargs) @@ -30,6 +51,14 @@ def serialize(self, **result: Any) -> dict[str, Any]: class CookieMixin(BaseTask): + """Mixin that adds an optional ``cookies`` parameter to a task. + + Pass cookies when the target page requires them for the captcha to render + correctly. + + :param cookies: Cookie string to include with the request (optional). + """ + def __init__(self, *args: Any, **kwargs: Any) -> None: self.cookies: str = kwargs.pop("cookies", "") super().__init__(*args, **kwargs) @@ -42,6 +71,19 @@ def serialize(self, **result: Any) -> dict[str, Any]: class ProxyMixin(BaseTask): + """Mixin that adds proxy parameters to a task. + + Use this (via proxy-enabled task variants like :class:`NoCaptchaTask`) when + the captcha must be solved through a specific proxy. You can build the + keyword arguments conveniently with :meth:`Proxy.to_kwargs`. + + :param proxy_type: Proxy protocol — ``"http"``, ``"socks4"``, or ``"socks5"``. + :param proxy_address: Proxy server hostname or IP address. + :param proxy_port: Proxy server port. + :param proxy_login: Username for proxy authentication (empty string if none). + :param proxy_password: Password for proxy authentication (empty string if none). + """ + def __init__(self, *args: Any, **kwargs: Any) -> None: self.proxyType: str = kwargs.pop("proxy_type") self.proxyAddress: str = kwargs.pop("proxy_address") @@ -62,6 +104,29 @@ def serialize(self, **result: Any) -> dict[str, Any]: class NoCaptchaTaskProxylessTask(BaseTask): + """Solve a Google ReCAPTCHA v2 challenge without a proxy. + + This is the most common task type. The solver will access the target page + directly from Anticaptcha's servers. + + After the job completes, retrieve the token with + :meth:`Job.get_solution_response`. + + :param website_url: Full URL of the page where the captcha appears. + :param website_key: The ``data-sitekey`` value from the ReCAPTCHA element. + :param website_s_token: Optional ``data-s`` token for Google Search captchas. + :param is_invisible: Set to ``True`` for invisible ReCAPTCHA. The system + auto-detects this, so the parameter is optional. + :param recaptcha_data_s_value: Value of the ``data-s`` parameter if present. + + Example:: + + task = NoCaptchaTaskProxylessTask( + website_url="https://example.com", + website_key="6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-", + ) + """ + type = "NoCaptchaTaskProxyless" websiteURL = None websiteKey = None @@ -99,18 +164,61 @@ def serialize(self, **result: Any) -> dict[str, Any]: class RecaptchaV2TaskProxyless(NoCaptchaTaskProxylessTask): + """Solve a Google ReCAPTCHA v2 challenge without a proxy (newer API type name). + + Identical to :class:`NoCaptchaTaskProxylessTask` but uses the updated + ``RecaptchaV2TaskProxyless`` type identifier. + """ + type = "RecaptchaV2TaskProxyless" class NoCaptchaTask(ProxyMixin, UserAgentMixin, CookieMixin, NoCaptchaTaskProxylessTask): + """Solve a Google ReCAPTCHA v2 challenge through a proxy. + + Same as :class:`NoCaptchaTaskProxylessTask` but additionally requires + proxy and user-agent parameters. Use :class:`Proxy` to build the proxy + keyword arguments conveniently:: + + proxy = Proxy.parse_url("socks5://user:pass@host:port") + task = NoCaptchaTask(url, site_key, user_agent=UA, **proxy.to_kwargs()) + + :param user_agent: Browser User-Agent string. + :param cookies: Optional cookie string (default: ``""``). + :param proxy_type: Proxy protocol (``"http"``, ``"socks4"``, ``"socks5"``). + :param proxy_address: Proxy server hostname or IP. + :param proxy_port: Proxy server port. + :param proxy_login: Proxy username (empty string if none). + :param proxy_password: Proxy password (empty string if none). + """ + type = "NoCaptchaTask" class RecaptchaV2Task(NoCaptchaTask): + """Solve a Google ReCAPTCHA v2 challenge through a proxy (newer API type name). + + Identical to :class:`NoCaptchaTask` but uses the updated + ``RecaptchaV2Task`` type identifier. + """ + type = "RecaptchaV2Task" class FunCaptchaProxylessTask(BaseTask): + """Solve an Arkose Labs FunCaptcha challenge without a proxy. + + After the job completes, retrieve the token with + :meth:`Job.get_token_response`. + + :param website_url: Full URL of the page where the captcha appears. + :param website_key: The FunCaptcha public key (e.g. + ``"DE0B0BB7-1EE4-4D70-1853-31B835D4506B"``). + :param subdomain: Custom FunCaptcha API subdomain, if the site uses one + (e.g. ``"mysite-api.arkoselabs.com"``). + :param data: Additional data blob required by some FunCaptcha implementations. + """ + type = "FunCaptchaTaskProxyless" websiteURL = None websiteKey = None @@ -144,10 +252,43 @@ def serialize(self, **result: Any) -> dict[str, Any]: class FunCaptchaTask(ProxyMixin, UserAgentMixin, CookieMixin, FunCaptchaProxylessTask): + """Solve an Arkose Labs FunCaptcha challenge through a proxy. + + Same as :class:`FunCaptchaProxylessTask` but additionally requires + proxy, user-agent, and optional cookie parameters. + """ + type = "FunCaptchaTask" class ImageToTextTask(BaseTask): + """Solve a classic image-based captcha by extracting text from an image. + + The image is automatically base64-encoded. You can pass a file path, + raw ``bytes``, or an open binary file object. + + After the job completes, retrieve the text with + :meth:`Job.get_captcha_text`. + + :param image: Captcha image as a file path (``str`` or ``Path``), raw + ``bytes``, or a binary file-like object. + :param phrase: ``True`` if the answer contains multiple words. + :param case: ``True`` if the answer is case-sensitive. + :param numeric: ``0`` — no requirements, ``1`` — numbers only, + ``2`` — letters only. + :param math: ``True`` if the captcha is a math expression to solve. + :param min_length: Minimum number of characters in the answer. + :param max_length: Maximum number of characters in the answer. + :param comment: Hint text shown to the worker (e.g. ``"Enter red letters"``). + :param website_url: URL of the page where the captcha was found (optional, + used for context). + + Example:: + + task = ImageToTextTask("captcha.png") + task = ImageToTextTask(open("captcha.png", "rb").read()) + """ + type = "ImageToTextTask" _body = None phrase = None @@ -213,6 +354,22 @@ def serialize(self, **result: Any) -> dict[str, Any]: class RecaptchaV3TaskProxyless(BaseTask): + """Solve a Google ReCAPTCHA v3 challenge (score-based, proxyless only). + + ReCAPTCHA v3 returns a score (0.0–1.0) rather than a visual challenge. + You must specify the minimum acceptable score and the page action. + + After the job completes, retrieve the token with + :meth:`Job.get_solution_response`. + + :param website_url: Full URL of the page where the captcha appears. + :param website_key: The ``data-sitekey`` value from the ReCAPTCHA element. + :param min_score: Minimum score threshold (e.g. ``0.3``, ``0.7``, ``0.9``). + :param page_action: The action value from ``grecaptcha.execute(key, {action: ...})``. + :param is_enterprise: Set to ``True`` if the site uses the Enterprise version + of ReCAPTCHA v3. + """ + type = "RecaptchaV3TaskProxyless" websiteURL = None websiteKey = None @@ -248,6 +405,15 @@ def serialize(self, **result: Any) -> dict[str, Any]: class HCaptchaTaskProxyless(BaseTask): + """Solve an hCaptcha challenge without a proxy. + + After the job completes, retrieve the token with + :meth:`Job.get_solution_response`. + + :param website_url: Full URL of the page where the captcha appears. + :param website_key: The ``data-sitekey`` value from the hCaptcha element. + """ + type = "HCaptchaTaskProxyless" websiteURL = None websiteKey = None @@ -265,10 +431,31 @@ def serialize(self, **result: Any) -> dict[str, Any]: class HCaptchaTask(ProxyMixin, UserAgentMixin, CookieMixin, HCaptchaTaskProxyless): + """Solve an hCaptcha challenge through a proxy. + + Same as :class:`HCaptchaTaskProxyless` but additionally requires + proxy, user-agent, and optional cookie parameters. + """ + type = "HCaptchaTask" class RecaptchaV2EnterpriseTaskProxyless(BaseTask): + """Solve a Google ReCAPTCHA v2 Enterprise challenge without a proxy. + + Use this for sites that use the Enterprise version of ReCAPTCHA v2. + + After the job completes, retrieve the token with + :meth:`Job.get_solution_response`. + + :param website_url: Full URL of the page where the captcha appears. + :param website_key: The ``data-sitekey`` value from the ReCAPTCHA element. + :param enterprise_payload: Optional dictionary with Enterprise-specific + parameters (e.g. ``{"s": "...", "action": "..."}``) or ``None``. + :param api_domain: Custom API domain if the site uses a non-standard + ReCAPTCHA endpoint (e.g. ``"recaptcha.net"``) or ``None``. + """ + type = "RecaptchaV2EnterpriseTaskProxyless" websiteURL = None websiteKey = None @@ -302,10 +489,28 @@ def serialize(self, **result: Any) -> dict[str, Any]: class RecaptchaV2EnterpriseTask(ProxyMixin, UserAgentMixin, CookieMixin, RecaptchaV2EnterpriseTaskProxyless): + """Solve a Google ReCAPTCHA v2 Enterprise challenge through a proxy. + + Same as :class:`RecaptchaV2EnterpriseTaskProxyless` but additionally requires + proxy, user-agent, and optional cookie parameters. + """ + type = "RecaptchaV2EnterpriseTask" class GeeTestTaskProxyless(BaseTask): + """Solve a GeeTest (slide / click) captcha without a proxy. + + After the job completes, use :meth:`Job.get_solution` to get the full + solution dictionary containing ``challenge``, ``validate``, and ``seccode``. + + :param website_url: Full URL of the page where the captcha appears. + :param gt: The ``gt`` parameter value from the GeeTest script. + :param challenge: The ``challenge`` token obtained from the GeeTest API. + :param subdomain: Custom GeeTest API subdomain, if the site uses one. + :param lib: Custom ``getLib`` parameter value, if required. + """ + type = "GeeTestTaskProxyless" websiteURL = None gt = None @@ -343,10 +548,31 @@ def serialize(self, **result: Any) -> dict[str, Any]: class GeeTestTask(ProxyMixin, UserAgentMixin, GeeTestTaskProxyless): + """Solve a GeeTest captcha through a proxy. + + Same as :class:`GeeTestTaskProxyless` but additionally requires + proxy and user-agent parameters. + """ + type = "GeeTestTask" class AntiGateTaskProxyless(BaseTask): + """Solve a custom AntiGate task using a predefined template. + + AntiGate tasks use templates to automate complex browser-based actions. + Browse available templates at https://anti-captcha.com/antigate. + + After the job completes, use :meth:`Job.get_solution` to get the full + solution dictionary. + + :param website_url: Full URL of the page to process. + :param template_name: Name of the AntiGate template + (e.g. ``"Sign up on MailChimp"``). + :param variables: Dictionary of template variables (keys and values + depend on the template). + """ + type = "AntiGateTask" websiteURL = None templateName = None @@ -374,4 +600,8 @@ def serialize(self, **result: Any) -> dict[str, Any]: class AntiGateTask(ProxyMixin, AntiGateTaskProxyless): - pass + """Solve a custom AntiGate task through a proxy. + + Same as :class:`AntiGateTaskProxyless` but additionally requires + proxy parameters. + """ From 55cad9e2666fe8a900a0585acbfd73f858dd18d7 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 8 Mar 2026 10:33:16 +0000 Subject: [PATCH 2/2] Add Sphinx doc-comments for snake_case aliases The snake_case method aliases (create_task, get_balance, etc.) were invisible in Sphinx-generated docs because bare attribute assignments are not picked up by autodoc without documentation. Adding #: comments makes them appear with cross-references to the original camelCase methods. https://claude.ai/code/session_012EAJ6fLNGswentS5LFX771 --- python_anticaptcha/async_client.py | 6 ++++++ python_anticaptcha/sync_client.py | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/python_anticaptcha/async_client.py b/python_anticaptcha/async_client.py index 8028408..0306d43 100644 --- a/python_anticaptcha/async_client.py +++ b/python_anticaptcha/async_client.py @@ -350,9 +350,15 @@ async def reportIncorrectRecaptcha(self, task_id: int) -> bool: return response["status"] == "success" # Snake_case aliases + #: Alias for :meth:`createTask`. create_task = createTask + #: Alias for :meth:`getTaskResult`. get_task_result = getTaskResult + #: Alias for :meth:`getBalance`. get_balance = getBalance + #: Alias for :meth:`getAppStats`. get_app_stats = getAppStats + #: Alias for :meth:`reportIncorrectImage`. report_incorrect_image = reportIncorrectImage + #: Alias for :meth:`reportIncorrectRecaptcha`. report_incorrect_recaptcha = reportIncorrectRecaptcha diff --git a/python_anticaptcha/sync_client.py b/python_anticaptcha/sync_client.py index 195da0f..83812aa 100644 --- a/python_anticaptcha/sync_client.py +++ b/python_anticaptcha/sync_client.py @@ -386,10 +386,17 @@ def reportIncorrectRecaptcha(self, task_id: int) -> bool: return response["status"] == "success" # Snake_case aliases + #: Alias for :meth:`createTask`. create_task = createTask + #: Alias for :meth:`createTaskSmee`. create_task_smee = createTaskSmee + #: Alias for :meth:`getTaskResult`. get_task_result = getTaskResult + #: Alias for :meth:`getBalance`. get_balance = getBalance + #: Alias for :meth:`getAppStats`. get_app_stats = getAppStats + #: Alias for :meth:`reportIncorrectImage`. report_incorrect_image = reportIncorrectImage + #: Alias for :meth:`reportIncorrectRecaptcha`. report_incorrect_recaptcha = reportIncorrectRecaptcha