From c81a7e3ebfe628cf1cda94015465153f20a8255e Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Sun, 15 Jun 2025 06:18:49 +0000 Subject: [PATCH 01/21] fix: docs/requirements.txt to reduce vulnerabilities The following vulnerabilities are fixed by pinning transitive dependencies: - https://snyk.io/vuln/SNYK-PYTHON-REQUESTS-10305723 --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 41e3c962..dccd4a60 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,6 +2,6 @@ sphinx==8.3.0 pallets_sphinx_themes==2.3.0 myst-parser==4.0.1 enum-tools[sphinx]==0.13.0 -requests>=2.32.0 # not directly required, pinned by Snyk to avoid a vulnerability +requests>=2.32.4 # not directly required, pinned by Snyk to avoid a vulnerability urllib3>=2.2.2 # not directly required, pinned by Snyk to avoid a vulnerability zipp>=3.19.1 # not directly required, pinned by Snyk to avoid a vulnerability From af5b9663ed6e29683f92baaa4ebdbd2719f42a81 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 19:05:07 +0000 Subject: [PATCH 02/21] Update pytest-asyncio requirement from ==0.* to ==1.* Updates the requirements on [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) to permit the latest version. - [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases) - [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v0.1.1...v1.0.0) --- updated-dependencies: - dependency-name: pytest-asyncio dependency-version: 1.0.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.test.txt b/requirements.test.txt index 93fd10c3..cc87b91b 100644 --- a/requirements.test.txt +++ b/requirements.test.txt @@ -1,3 +1,3 @@ pytest==8.* coverage==7.* -pytest-asyncio==0.* +pytest-asyncio==1.* From 599e0eb582a0b0bbab366f42d5752a08436ddcab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 21:44:28 +0000 Subject: [PATCH 03/21] [github-actions] Bump actions/checkout from 4 to 5 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 2 +- .github/workflows/install.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/sphinx.yml | 2 +- .github/workflows/test.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fdc419a8..8eac0140 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,7 @@ jobs: python-version: ["3.9", "3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: diff --git a/.github/workflows/install.yml b/.github/workflows/install.yml index 9d269f13..5cd60a2b 100644 --- a/.github/workflows/install.yml +++ b/.github/workflows/install.yml @@ -23,7 +23,7 @@ jobs: python-version: ["3.9", "3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 20a77e69..9fdc4a8c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -27,7 +27,7 @@ jobs: python-version: ["3.12"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: diff --git a/.github/workflows/sphinx.yml b/.github/workflows/sphinx.yml index a9616cc5..ff029675 100644 --- a/.github/workflows/sphinx.yml +++ b/.github/workflows/sphinx.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-python@v5 - name: Build docs requirements diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a2ed40cf..54777c73 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,7 +33,7 @@ jobs: python-version: ["3.11"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: From af963b80d37bd70a043b20beb1d67b3c22cf4a3f Mon Sep 17 00:00:00 2001 From: Andrei Date: Thu, 28 Aug 2025 23:16:22 +0300 Subject: [PATCH 04/21] Update serializer.py --- src/python_rucaptcha/core/serializer.py | 30 ++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/python_rucaptcha/core/serializer.py b/src/python_rucaptcha/core/serializer.py index b74df0eb..07abdd9f 100644 --- a/src/python_rucaptcha/core/serializer.py +++ b/src/python_rucaptcha/core/serializer.py @@ -1,4 +1,4 @@ -from typing import Literal, Optional +from typing import Any, Literal, Optional from msgspec import Struct @@ -7,7 +7,7 @@ class MyBaseModel(Struct): - def to_dict(self): + def to_dict(self)->dict[str, Any]: return {f: getattr(self, f) for f in self.__struct_fields__} @@ -24,18 +24,18 @@ class CreateTaskBaseSer(MyBaseModel): clientKey: str task: TaskSer = {} languagePool: str = "en" - callbackUrl: str = None + callbackUrl: str|None = None soft_id: Literal[APP_KEY] = APP_KEY class GetTaskResultRequestSer(MyBaseModel): clientKey: str - taskId: int = None + taskId: int|None = None class CaptchaOptionsSer(MyBaseModel): sleep_time: int = 10 - service_type: enums.ServiceEnm = enums.ServiceEnm.TWOCAPTCHA.value + service_type: enums.ServiceEnm = enums.ServiceEnm.TWOCAPTCHA url_request: Optional[str] = None url_response: Optional[str] = None @@ -59,16 +59,16 @@ def urls_set(self): class GetTaskResultResponseSer(MyBaseModel): status: str = "ready" - solution: dict = None - cost: float = None - ip: str = None - createTime: int = None - endTime: int = None - solveCount: int = None - taskId: int = None + solution: dict[str, str]|None = None + cost: float = 0.0 + ip: str|None = None + createTime: int|None = None + endTime: int|None = None + solveCount: int|None = None + taskId: int|None = None # control method params - balance: float = None + balance: float|None = None # error info errorId: int = 0 - errorCode: str = None - errorDescription: str = None + errorCode: str|None = None + errorDescription: str|None = None From 90016c243cd214f5ad3b551cab09b0a752262002 Mon Sep 17 00:00:00 2001 From: Andrei Date: Thu, 28 Aug 2025 23:22:12 +0300 Subject: [PATCH 05/21] Update pyproject.toml --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 36297891..0f883c37 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,6 +78,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Framework :: AsyncIO", "Operating System :: Unix", "Operating System :: Microsoft :: Windows", From d27884494a9d8c6c915b31017ae66e7d60557766 Mon Sep 17 00:00:00 2001 From: Andrei Date: Thu, 28 Aug 2025 23:22:15 +0300 Subject: [PATCH 06/21] Update config.py --- src/python_rucaptcha/core/config.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/python_rucaptcha/core/config.py b/src/python_rucaptcha/core/config.py index 4f194704..763fd2d6 100644 --- a/src/python_rucaptcha/core/config.py +++ b/src/python_rucaptcha/core/config.py @@ -1,3 +1,5 @@ +from typing import Generator + from tenacity import AsyncRetrying, wait_fixed, stop_after_attempt from requests.adapters import Retry @@ -8,7 +10,7 @@ # Connection retry generator -def attempts_generator(amount: int = 20): +def attempts_generator(amount: int = 20) -> Generator[int, None, None]: """ Function generates a generator of length equal to `amount` From 5046fa3e1a72144aad6aee203efa2c5a798ef3b1 Mon Sep 17 00:00:00 2001 From: Andrei Date: Thu, 28 Aug 2025 23:22:18 +0300 Subject: [PATCH 07/21] Update result_handler.py --- src/python_rucaptcha/core/result_handler.py | 38 ++++++++++----------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/python_rucaptcha/core/result_handler.py b/src/python_rucaptcha/core/result_handler.py index f7114291..c9ff81f5 100644 --- a/src/python_rucaptcha/core/result_handler.py +++ b/src/python_rucaptcha/core/result_handler.py @@ -1,7 +1,7 @@ import time import asyncio import logging -from typing import Union +from typing import Any import aiohttp import requests @@ -12,7 +12,7 @@ def get_sync_result( get_payload: GetTaskResultRequestSer, sleep_time: int, url_response: str -) -> Union[dict, Exception]: +) -> dict[str, str] | Exception: """ Function periodically send the SYNC request to service and wait for captcha solving result """ @@ -21,26 +21,25 @@ def get_sync_result( for _ in attempts: try: # send a request for the result of solving the captcha - captcha_response = GetTaskResultResponseSer( - **requests.post(url_response, json=get_payload.to_dict()).json(), taskId=get_payload.taskId - ) - logging.warning(f"{captcha_response = }") + result: dict[str, Any] = requests.post(url_response, json=get_payload.to_dict()).json() + logging.info(f"Received captcha sync result - {result = }") + response_ser = GetTaskResultResponseSer(**result, taskId=get_payload.taskId) # if the captcha has not been resolved yet, wait - if captcha_response.status == "processing": + if response_ser.status == "processing": time.sleep(sleep_time) continue - elif captcha_response.status == "ready": + elif response_ser.status == "ready": break - elif captcha_response.errorId != 0: - return captcha_response.to_dict() + elif response_ser.errorId != 0: + return response_ser.to_dict() except Exception as error: return error - return captcha_response.to_dict() + return response_ser.to_dict() async def get_async_result( get_payload: GetTaskResultRequestSer, sleep_time: int, url_response: str -) -> Union[dict, Exception]: +) -> dict[str, str] | Exception: """ Function periodically send the ASYNC request to service and wait for captcha solving result """ @@ -53,17 +52,18 @@ async def get_async_result( async with session.post( url_response, json=get_payload.to_dict(), raise_for_status=True ) as resp: - captcha_response = await resp.json(content_type=None) - captcha_response = GetTaskResultResponseSer(**captcha_response, taskId=get_payload.taskId) + result: dict[str, Any] = await resp.json(content_type=None) + logging.info(f"Received captcha async result - {result = }") + response_ser = GetTaskResultResponseSer(**result, taskId=get_payload.taskId) # if the captcha has not been resolved yet, wait - if captcha_response.status == "processing": + if response_ser.status == "processing": await asyncio.sleep(sleep_time) continue - elif captcha_response.status == "ready": + elif response_ser.status == "ready": break - elif captcha_response.errorId != 0: - return captcha_response.to_dict() + elif response_ser.errorId != 0: + return response_ser.to_dict() except Exception as error: return error - return captcha_response.to_dict() + return response_ser.to_dict() From 678e3e5b7fafb76f6466ed9ca9a1c51a6d15c7c9 Mon Sep 17 00:00:00 2001 From: Andrei Date: Thu, 28 Aug 2025 23:22:21 +0300 Subject: [PATCH 08/21] Update serializer.py --- src/python_rucaptcha/core/serializer.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/python_rucaptcha/core/serializer.py b/src/python_rucaptcha/core/serializer.py index 07abdd9f..8a650465 100644 --- a/src/python_rucaptcha/core/serializer.py +++ b/src/python_rucaptcha/core/serializer.py @@ -7,7 +7,7 @@ class MyBaseModel(Struct): - def to_dict(self)->dict[str, Any]: + def to_dict(self) -> dict[str, Any]: return {f: getattr(self, f) for f in self.__struct_fields__} @@ -24,13 +24,13 @@ class CreateTaskBaseSer(MyBaseModel): clientKey: str task: TaskSer = {} languagePool: str = "en" - callbackUrl: str|None = None + callbackUrl: str | None = None soft_id: Literal[APP_KEY] = APP_KEY class GetTaskResultRequestSer(MyBaseModel): clientKey: str - taskId: int|None = None + taskId: int | None = None class CaptchaOptionsSer(MyBaseModel): @@ -59,16 +59,16 @@ def urls_set(self): class GetTaskResultResponseSer(MyBaseModel): status: str = "ready" - solution: dict[str, str]|None = None + solution: dict[str, str] | None = None cost: float = 0.0 - ip: str|None = None - createTime: int|None = None - endTime: int|None = None - solveCount: int|None = None - taskId: int|None = None + ip: str | None = None + createTime: int | None = None + endTime: int | None = None + solveCount: int | None = None + taskId: int | None = None # control method params - balance: float|None = None + balance: float | None = None # error info errorId: int = 0 - errorCode: str|None = None - errorDescription: str|None = None + errorCode: str | None = None + errorDescription: str | None = None From 2c7a6b3245e991a4ad8a9fc920467edb42a7a1d9 Mon Sep 17 00:00:00 2001 From: Andrei Date: Thu, 28 Aug 2025 23:53:03 +0300 Subject: [PATCH 09/21] Update base.py --- src/python_rucaptcha/core/base.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/python_rucaptcha/core/base.py b/src/python_rucaptcha/core/base.py index 44f32be5..29c36f03 100644 --- a/src/python_rucaptcha/core/base.py +++ b/src/python_rucaptcha/core/base.py @@ -3,7 +3,7 @@ import uuid import base64 import asyncio -from typing import Optional +from typing import Any, Optional from pathlib import Path import aiohttp @@ -30,7 +30,7 @@ def __init__( rucaptcha_key: str, method: str, sleep_time: int = 10, - service_type: str = ServiceEnm.TWOCAPTCHA.value, + service_type: ServiceEnm | str = ServiceEnm.TWOCAPTCHA, **kwargs, ): """ @@ -61,7 +61,7 @@ def __init__( self.session.mount("http://", HTTPAdapter(max_retries=RETRIES)) self.session.mount("https://", HTTPAdapter(max_retries=RETRIES)) - def _processing_response(self, **kwargs: dict) -> dict: + def _processing_response(self, **kwargs: dict[str, Any]) -> dict[str, Any] | Exception: """ Method processing captcha solving task creation result :param kwargs: additional params for Requests library @@ -106,7 +106,7 @@ async def aio_url_read(self, url: str, **kwargs) -> bytes: async with session.get(url=url, **kwargs) as resp: return await resp.content.read() - async def _aio_processing_response(self) -> dict: + async def _aio_processing_response(self) -> dict[str, Any]: """ Method processing async captcha solving task creation result """ From 31571c6d40d57498ced3820f5364fb68a2e85198 Mon Sep 17 00:00:00 2001 From: Andrei Date: Thu, 28 Aug 2025 23:53:06 +0300 Subject: [PATCH 10/21] Update enums.py --- src/python_rucaptcha/core/enums.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/python_rucaptcha/core/enums.py b/src/python_rucaptcha/core/enums.py index ab9a0feb..544be6e6 100644 --- a/src/python_rucaptcha/core/enums.py +++ b/src/python_rucaptcha/core/enums.py @@ -168,3 +168,11 @@ class ProsopoEnm(str, MyEnum): class CaptchaFoxEnm(str, MyEnum): CaptchaFoxTask = "CaptchaFoxTask" + + +class VKCaptchaEnm(str, MyEnum): + VKCaptchaTask = "VKCaptchaTask" + + +class TemuCaptchaEnm(str, MyEnum): + TemuCaptchaTask = "TemuCaptchaTask" From 6690f6276e80c52b83ebba58714a3e972eddc594 Mon Sep 17 00:00:00 2001 From: Andrei Date: Thu, 28 Aug 2025 23:53:08 +0300 Subject: [PATCH 11/21] Update result_handler.py --- src/python_rucaptcha/core/result_handler.py | 58 ++++++++++++++++++--- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/src/python_rucaptcha/core/result_handler.py b/src/python_rucaptcha/core/result_handler.py index c9ff81f5..adc5cfb3 100644 --- a/src/python_rucaptcha/core/result_handler.py +++ b/src/python_rucaptcha/core/result_handler.py @@ -12,10 +12,30 @@ def get_sync_result( get_payload: GetTaskResultRequestSer, sleep_time: int, url_response: str -) -> dict[str, str] | Exception: +) -> dict[str, str]: """ - Function periodically send the SYNC request to service and wait for captcha solving result + Periodically sends a synchronous request to a remote service to retrieve the result + of a CAPTCHA-solving task. + + This function polls the service using blocking HTTP requests until the CAPTCHA is solved, + an error is returned, or the task times out. It handles intermediate states and retries + with a configurable sleep interval between attempts. + + Args: + get_payload (GetTaskResultRequestSer): + Serialized request object containing the task ID and payload data. + sleep_time (int): + Time in seconds to wait between polling attempts when the task is still processing. + url_response (str): + Endpoint URL to query for the CAPTCHA-solving result. + + Returns: + dict[str, str]: + A dictionary containing the final task result. If the task fails or an exception + occurs, the dictionary includes error details such as status, errorId, errorCode, + and errorDescription. """ + response_ser = GetTaskResultResponseSer(taskId=get_payload.taskId) # generator for repeated attempts to connect to the server attempts = attempts_generator() for _ in attempts: @@ -33,16 +53,39 @@ def get_sync_result( elif response_ser.errorId != 0: return response_ser.to_dict() except Exception as error: - return error + response_ser.status = "failed" + response_ser.errorId = 12 + response_ser.errorCode = "System error" + response_ser.errorDescription = str(error) return response_ser.to_dict() async def get_async_result( get_payload: GetTaskResultRequestSer, sleep_time: int, url_response: str -) -> dict[str, str] | Exception: +) -> dict[str, str]: """ - Function periodically send the ASYNC request to service and wait for captcha solving result + Periodically sends an asynchronous request to a remote service to retrieve the result + of a CAPTCHA-solving task. + + This function polls the service at regular intervals until the CAPTCHA is solved, + an error occurs, or the task times out. It uses aiohttp for asynchronous HTTP requests + and handles various response states including 'processing', 'ready', and error conditions. + + Args: + get_payload (GetTaskResultRequestSer): + Serialized request object containing the task ID and payload data. + sleep_time (int): + Time in seconds to wait between polling attempts when the task is still processing. + url_response (str): + Endpoint URL to query for the CAPTCHA-solving result. + + Returns: + dict[str, str]: + A dictionary containing the final task result if successful or partially failed. + If an exception occurs during the request, returns a dictionary with error details + including status, errorId, errorCode, and errorDescription. """ + response_ser = GetTaskResultResponseSer(taskId=get_payload.taskId) # generator for repeated attempts to connect to the server attempts = attempts_generator() async with aiohttp.ClientSession() as session: @@ -65,5 +108,8 @@ async def get_async_result( elif response_ser.errorId != 0: return response_ser.to_dict() except Exception as error: - return error + response_ser.status = "failed" + response_ser.errorId = 12 + response_ser.errorCode = "System error" + response_ser.errorDescription = str(error) return response_ser.to_dict() From 0c1618d7aa58ee4213a2e31bbe7ca6dd6df0c506 Mon Sep 17 00:00:00 2001 From: Andrei Date: Thu, 28 Aug 2025 23:53:10 +0300 Subject: [PATCH 12/21] Update serializer.py --- src/python_rucaptcha/core/serializer.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/python_rucaptcha/core/serializer.py b/src/python_rucaptcha/core/serializer.py index 8a650465..081bb109 100644 --- a/src/python_rucaptcha/core/serializer.py +++ b/src/python_rucaptcha/core/serializer.py @@ -1,4 +1,4 @@ -from typing import Any, Literal, Optional +from typing import Any, Literal from msgspec import Struct @@ -35,10 +35,10 @@ class GetTaskResultRequestSer(MyBaseModel): class CaptchaOptionsSer(MyBaseModel): sleep_time: int = 10 - service_type: enums.ServiceEnm = enums.ServiceEnm.TWOCAPTCHA + service_type: enums.ServiceEnm | str = enums.ServiceEnm.TWOCAPTCHA - url_request: Optional[str] = None - url_response: Optional[str] = None + url_request: str = f"http://api.{enums.ServiceEnm.TWOCAPTCHA.value}.com/2captcha/in.php" + url_response: str = f"http://api.{enums.ServiceEnm.TWOCAPTCHA.value}.com/2captcha/res.php" def urls_set(self): """ From 30eafbc87c68247c4a5224fab5bcaf7db2735a4b Mon Sep 17 00:00:00 2001 From: Andrei Date: Thu, 28 Aug 2025 23:53:12 +0300 Subject: [PATCH 13/21] Create vk_captcha.py --- src/python_rucaptcha/vk_captcha.py | 126 +++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 src/python_rucaptcha/vk_captcha.py diff --git a/src/python_rucaptcha/vk_captcha.py b/src/python_rucaptcha/vk_captcha.py new file mode 100644 index 00000000..d465e7eb --- /dev/null +++ b/src/python_rucaptcha/vk_captcha.py @@ -0,0 +1,126 @@ +from typing import Any + +from .core.base import BaseCaptcha +from .core.enums import VKCaptchaEnm + + +class VKCaptcha(BaseCaptcha): + def __init__( + self, + websiteURL: str, + websiteKey: str, + userAgent: str, + proxyType: str, + proxyAddress: str, + proxyPort: str, + *args, + **kwargs, + ): + """ + The class is used to work with CaptchaFox. + + Args: + rucaptcha_key: User API key + websiteURL: Full URL of the captcha page + websiteKey: The value of the `key` parameter. + It can be found in the page source code or captured in network requests during page loading. + userAgent: User-Agent of your browser will be used to load the captcha. + Use only modern browser's User-Agents + proxyType: Proxy type - `http`, `socks4`, `socks5` + proxyAddress: Proxy IP address or hostname + proxyPort: Proxy port + method: Captcha type + kwargs: Not required params for task creation request + + Examples: + >>> CaptchaFox(rucaptcha_key="aa9011f31111181111168611f1151122", + ... websiteURL="3ceb8624-1970-4e6b-91d5-70317b70b651", + ... websiteKey="sk_xtNxpk6fCdFbxh1_xJeGflSdCE9tn99G", + ... userAgent="Mozilla/5.0 .....", + ... proxyType="socks5", + ... proxyAddress="1.2.3.4", + ... proxyPort="445", + ... ).captcha_handler() + { + "errorId":0, + "status":"ready", + "solution":{ + "token":"142000f.....er" + }, + "cost":"0.002", + "ip":"1.2.3.4", + "createTime":1692863536, + "endTime":1692863556, + "solveCount":0, + "taskId": 73243152973, + } + + >>> await CaptchaFox(rucaptcha_key="aa9011f31111181111168611f1151122", + ... websiteURL="3ceb8624-1970-4e6b-91d5-70317b70b651", + ... websiteKey="sk_xtNxpk6fCdFbxh1_xJeGflSdCE9tn99G", + ... userAgent="Mozilla/5.0 .....", + ... proxyType="socks5", + ... proxyAddress="1.2.3.4", + ... proxyPort="445", + ... ).aio_captcha_handler() + { + "errorId":0, + "status":"ready", + "solution":{ + "token":"142000f.....er" + }, + "cost":"0.002", + "ip":"1.2.3.4", + "createTime":1692863536, + "endTime":1692863556, + "solveCount":0, + "taskId": 73243152973, + } + + Returns: + Dict with full server response + + Notes: + https://2captcha.com/api-docs/captchafox + + https://rucaptcha.com/api-docs/captchafox + """ + super().__init__(method=VKCaptchaEnm.VKCaptchaTask, *args, **kwargs) + + self.create_task_payload["task"].update( + { + "websiteURL": websiteURL, + "websiteKey": websiteKey, + "userAgent": userAgent, + "proxyType": proxyType, + "proxyAddress": proxyAddress, + "proxyPort": proxyPort, + } + ) + + def captcha_handler(self, **kwargs: dict[str, Any]) -> dict[str, Any]: + """ + Sync solving method + + Args: + kwargs: additional params for `requests` library + + Returns: + Dict with full server response + + Notes: + Check class docstirng for more info + """ + return self._processing_response(**kwargs) + + async def aio_captcha_handler(self) -> dict[str, Any]: + """ + Async solving method + + Returns: + Dict with full server response + + Notes: + Check class docstirng for more info + """ + return await self._aio_processing_response() From a5700737442a974222e2d2bf1a18a6ac5b806435 Mon Sep 17 00:00:00 2001 From: Andrei Date: Fri, 29 Aug 2025 00:54:27 +0300 Subject: [PATCH 14/21] v6.5.0 --- pyproject.toml | 3 + src/python_rucaptcha/__version__.py | 2 +- src/python_rucaptcha/core/base.py | 70 +++++++---- src/python_rucaptcha/core/serializer.py | 25 +++- src/python_rucaptcha/image_captcha.py | 10 +- src/python_rucaptcha/temu_captcha.py | 158 ++++++++++++++++++++++++ src/python_rucaptcha/vk_captcha.py | 28 ++--- 7 files changed, 247 insertions(+), 49 deletions(-) create mode 100644 src/python_rucaptcha/temu_captcha.py diff --git a/pyproject.toml b/pyproject.toml index 0f883c37..8175c19c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,6 +66,9 @@ keywords = [ "captcha", "turnstile", "amazon", "amazon_waf", + "vk-captcha", + "fox-captcha", + "temu-captcha", "friendly-captcha" ] license = "MIT" diff --git a/src/python_rucaptcha/__version__.py b/src/python_rucaptcha/__version__.py index f0a00b37..b61ecefd 100644 --- a/src/python_rucaptcha/__version__.py +++ b/src/python_rucaptcha/__version__.py @@ -1 +1 @@ -__version__ = "6.4.0" +__version__ = "6.5.0" diff --git a/src/python_rucaptcha/core/base.py b/src/python_rucaptcha/core/base.py index 29c36f03..af5ff582 100644 --- a/src/python_rucaptcha/core/base.py +++ b/src/python_rucaptcha/core/base.py @@ -31,15 +31,31 @@ def __init__( method: str, sleep_time: int = 10, service_type: ServiceEnm | str = ServiceEnm.TWOCAPTCHA, - **kwargs, + **kwargs: dict[str, Any], ): """ - :param rucaptcha_key: User API key - :param method: Captcha type - :param sleep_time: Time to wait for captcha solution - :param service_type: URL with which the program will work, "2captcha" option is possible (standard) - and "rucaptcha" - :param kwargs: Designed to pass OPTIONAL parameters to the payload for a request to RuCaptcha + Base class for interacting with CAPTCHA-solving services such as 2Captcha and RuCaptcha. + + This class handles the setup of request payloads, session configuration, and service-specific + parameters required to submit CAPTCHA tasks and retrieve their results. It supports optional + customization of task parameters via keyword arguments and includes retry logic for HTTP requests. + + Args: + rucaptcha_key (str): + API key provided by the CAPTCHA-solving service. + method (str): + Type of CAPTCHA to solve (e.g., "ImageToText", "ReCaptchaV2"). + sleep_time (int, optional): + Time in seconds to wait between polling attempts. Defaults to 10. + service_type (ServiceEnm | str, optional): + Service provider to use. Accepts `ServiceEnm.TWOCAPTCHA` or `"rucaptcha"`. Defaults to TWOCAPTCHA. + **kwargs (dict[str, Any]): + Optional parameters to be injected into the task payload (e.g., `websiteURL`, `siteKey`, `proxy`). + + Example: + >>> captcha = BaseCaptcha("your-api-key", method="ReCaptchaV2", websiteURL="https://example.com", siteKey="abc123") + >>> captcha.create_task_payload + {'clientKey': 'your-api-key', 'task': {'type': 'ReCaptchaV2', 'websiteURL': 'https://example.com', 'siteKey': 'abc123'}} """ self.result = GetTaskResultResponseSer() # assign args to validator @@ -48,7 +64,7 @@ def __init__( # prepare create task payload self.create_task_payload = CreateTaskBaseSer( - clientKey=rucaptcha_key, task=TaskSer(type=method).to_dict() + clientKey=rucaptcha_key, task=TaskSer(type=method) ).to_dict() # prepare get task result data payload self.get_task_payload = GetTaskResultRequestSer(clientKey=rucaptcha_key) @@ -61,7 +77,7 @@ def __init__( self.session.mount("http://", HTTPAdapter(max_retries=RETRIES)) self.session.mount("https://", HTTPAdapter(max_retries=RETRIES)) - def _processing_response(self, **kwargs: dict[str, Any]) -> dict[str, Any] | Exception: + def _processing_response(self, **kwargs: dict[str, Any]) -> dict[str, Any]: """ Method processing captcha solving task creation result :param kwargs: additional params for Requests library @@ -90,13 +106,13 @@ def _processing_response(self, **kwargs: dict[str, Any]) -> dict[str, Any] | Exc url_response=self.params.url_response, ) - def url_open(self, url: str, **kwargs): + def url_open(self, url: str, **kwargs: dict[str, Any]): """ Method open links """ return self.session.get(url=url, **kwargs) - async def aio_url_read(self, url: str, **kwargs) -> bytes: + async def aio_url_read(self, url: str, **kwargs: dict[str, Any]) -> bytes | None: """ Async method read bytes from link """ @@ -167,23 +183,24 @@ def _file_const_saver(self, content: bytes, file_path: str, file_extension: str def _body_file_processing( self, - save_format: SaveFormatsEnm, + save_format: SaveFormatsEnm | str, file_path: str, file_extension: str = "png", - captcha_link: Optional[str] = None, - captcha_file: Optional[str] = None, - captcha_base64: Optional[bytes] = None, - **kwargs, + image_key: str = "body", + captcha_link: str | None = None, + captcha_file: str | None = None, + captcha_base64: bytes | None = None, + **kwargs: dict[str, Any], ): # if a local file link is passed if captcha_file: self.create_task_payload["task"].update( - {"body": base64.b64encode(self._local_file_captcha(captcha_file)).decode("utf-8")} + {image_key: base64.b64encode(self._local_file_captcha(captcha_file)).decode("utf-8")} ) # if the file is transferred in base64 encoding elif captcha_base64: self.create_task_payload["task"].update( - {"body": base64.b64encode(captcha_base64).decode("utf-8")} + {image_key: base64.b64encode(captcha_base64).decode("utf-8")} ) # if a URL is passed elif captcha_link: @@ -192,7 +209,9 @@ def _body_file_processing( # according to the value of the passed parameter, select the function to save the image if save_format == SaveFormatsEnm.CONST.value: self._file_const_saver(content, file_path, file_extension=file_extension) - self.create_task_payload["task"].update({"body": base64.b64encode(content).decode("utf-8")}) + self.create_task_payload["task"].update( + {image_key: base64.b64encode(content).decode("utf-8")} + ) except Exception as error: self.result.errorId = 12 self.result.errorCode = self.NO_CAPTCHA_ERR @@ -204,23 +223,24 @@ def _body_file_processing( async def _aio_body_file_processing( self, - save_format: SaveFormatsEnm, + save_format: SaveFormatsEnm | str, file_path: str, file_extension: str = "png", + image_key: str = "body", captcha_link: Optional[str] = None, captcha_file: Optional[str] = None, captcha_base64: Optional[bytes] = None, - **kwargs, + **kwargs: dict[str, Any], ): # if a local file link is passed if captcha_file: self.create_task_payload["task"].update( - {"body": base64.b64encode(self._local_file_captcha(captcha_file)).decode("utf-8")} + {image_key: base64.b64encode(self._local_file_captcha(captcha_file)).decode("utf-8")} ) # if the file is transferred in base64 encoding elif captcha_base64: self.create_task_payload["task"].update( - {"body": base64.b64encode(captcha_base64).decode("utf-8")} + {image_key: base64.b64encode(captcha_base64).decode("utf-8")} ) # if a URL is passed elif captcha_link: @@ -229,7 +249,9 @@ async def _aio_body_file_processing( # according to the value of the passed parameter, select the function to save the image if save_format == SaveFormatsEnm.CONST.value: self._file_const_saver(content, file_path, file_extension=file_extension) - self.create_task_payload["task"].update({"body": base64.b64encode(content).decode("utf-8")}) + self.create_task_payload["task"].update( + {image_key: base64.b64encode(content).decode("utf-8")} + ) except Exception as error: self.result.errorId = 12 self.result.errorCode = self.NO_CAPTCHA_ERR diff --git a/src/python_rucaptcha/core/serializer.py b/src/python_rucaptcha/core/serializer.py index 081bb109..a12b4b14 100644 --- a/src/python_rucaptcha/core/serializer.py +++ b/src/python_rucaptcha/core/serializer.py @@ -1,4 +1,6 @@ from typing import Any, Literal +from decimal import Decimal +from datetime import date, datetime from msgspec import Struct @@ -8,7 +10,26 @@ class MyBaseModel(Struct): def to_dict(self) -> dict[str, Any]: - return {f: getattr(self, f) for f in self.__struct_fields__} + result = {} + for field in self.__struct_fields__: + value = getattr(self, field) + + if isinstance(value, MyBaseModel): + result[field] = value.to_dict() + + elif isinstance(value, (list, tuple)) and all(isinstance(el, Struct) for el in value): + result[field] = [el.to_dict() for el in value] + + elif isinstance(value, (date, datetime)): + result[field] = value.isoformat() + + elif isinstance(value, Decimal): + result[field] = str(value) + + else: + result[field] = value + + return result """ @@ -22,7 +43,7 @@ class TaskSer(MyBaseModel): class CreateTaskBaseSer(MyBaseModel): clientKey: str - task: TaskSer = {} + task: TaskSer languagePool: str = "en" callbackUrl: str | None = None soft_id: Literal[APP_KEY] = APP_KEY diff --git a/src/python_rucaptcha/image_captcha.py b/src/python_rucaptcha/image_captcha.py index 7f78bb92..18a3e7e0 100644 --- a/src/python_rucaptcha/image_captcha.py +++ b/src/python_rucaptcha/image_captcha.py @@ -1,5 +1,5 @@ import shutil -from typing import Union, Optional +from typing import Any, Union, Optional from .core.base import BaseCaptcha from .core.enums import SaveFormatsEnm, ImageCaptchaEnm @@ -181,8 +181,8 @@ def captcha_handler( captcha_link: Optional[str] = None, captcha_file: Optional[str] = None, captcha_base64: Optional[bytes] = None, - **kwargs, - ) -> dict: + **kwargs: dict[str, Any], + ) -> dict[str, Any]: """ Sync solving method @@ -215,8 +215,8 @@ async def aio_captcha_handler( captcha_link: Optional[str] = None, captcha_file: Optional[str] = None, captcha_base64: Optional[bytes] = None, - **kwargs, - ) -> dict: + **kwargs: dict[str, Any], + ) -> dict[str, Any]: """ Async solving method diff --git a/src/python_rucaptcha/temu_captcha.py b/src/python_rucaptcha/temu_captcha.py new file mode 100644 index 00000000..d2e9deb7 --- /dev/null +++ b/src/python_rucaptcha/temu_captcha.py @@ -0,0 +1,158 @@ +import shutil +from typing import Any, Union + +from .core.base import BaseCaptcha +from .core.enums import SaveFormatsEnm, TemuCaptchaEnm + + +class TemuCaptcha(BaseCaptcha): + def __init__( + self, + save_format: Union[str, SaveFormatsEnm] = SaveFormatsEnm.TEMP, + img_clearing: bool = True, + img_path: str = "PythonRuCaptchaImages", + *args, + **kwargs: dict[str, Any], + ): + """ + Solve TemuImageTask CAPTCHA via 2Captcha/RuCaptcha API. + + This class creates and monitors TemuImageTask jobs, which require + a base64‐encoded background image plus an array of movable image + pieces (parts) in base64 format. It extends BaseCaptcha to handle + the low‐level request/response workflow. + + Args: + save_format (str | SaveFormatsEnm): Where to save temporary images. + - SaveFormatsEnm.TEMP: use system temp directory + - SaveFormatsEnm.CONST: keep files in img_path until deletion + img_clearing (bool): If True and save_format is CONST, delete the + img_path directory when this instance is destroyed. + img_path (str): Directory under which to store downloaded or decoded + images before sending to the API. + *args: Positional args forwarded to BaseCaptcha constructor (e.g. + client_key, method override). + **kwargs: Keyword args forwarded to BaseCaptcha for task creation. + Common params include: + - redirectUri: URL to confirm CAPTCHA resolution + - any other API‐supported parameters + Examples: + >>> captcha = TemuCaptcha( + ... clientKey="YOUR_API_KEY", + ... userAgent="Mozilla/5.0 ...", + ... proxyType="socks5", + ... proxyAddress="1.2.3.4", + ... proxyPort="1080" + ... ) + >>> response = captcha.captcha_handler( + ... parts=["part1_b64", "part2_b64", "part3_b64"], + ... captcha_base64=b"full_image_b64", + ... timeout=120 + ... ) + >>> print(response) + { + "errorId": 0, + "status": "ready", + "solution": { + "coordinates": [{"x":155,"y":358}, {"x":152,"y":153}, {"x":251,"y":333}] + }, + "cost": "0.0012", + "createTime": 1754563182, + "endTime": 1754563190, + "taskId": 80306543329, + "ip": "46.53.232.76", + "solveCount": 1 + } + + Notes: + https://2captcha.com/api-docs/temu-captcha + + https://rucaptcha.com/api-docs/temu-captcha + """ + super().__init__(method=TemuCaptchaEnm.TemuCaptchaTask, *args, **kwargs) + + self.save_format = save_format + self.img_clearing = img_clearing + self.img_path = img_path + + def captcha_handler( + self, + parts: list[str], + captcha_link: str | None = None, + captcha_file: str | None = None, + captcha_base64: bytes | None = None, + **kwargs: dict[str, Any], + ) -> dict[str, Any]: + """ + Synchronously solve a TemuImageTask. + + Args: + parts (list[str]): List of base64‐encoded strings for each + movable image piece. + captcha_link (str | None): URL to background image. Overrides + captcha_file and captcha_base64 if provided. + captcha_file (str | None): Path to an image file to read & send. + captcha_base64 (bytes | None): Raw bytes or base64 string of + the background image. + **kwargs: Passed through to the HTTP request call (e.g. timeout, + headers). + + Returns: + dict[str, Any]: Full JSON response from the 2Captcha/RuCaptcha API, + including errorId, taskId, status, solution, cost, times, etc. + """ + self.create_task_payload["task"].update({"parts": parts}) + self._body_file_processing( + save_format=self.save_format, + file_path=self.img_path, + captcha_link=captcha_link, + captcha_file=captcha_file, + captcha_base64=captcha_base64, + image_key="image", + **kwargs, + ) + if not self.result.errorId: + return self._processing_response(**kwargs) + return self.result.to_dict() + + async def aio_captcha_handler( + self, + parts: list[str], + captcha_link: str | None = None, + captcha_file: str | None = None, + captcha_base64: bytes | None = None, + ) -> dict[str, Any]: + """ + Asynchronously solve a TemuImageTask. + + Args: + parts (list[str]): List of base64‐encoded strings for each + movable image piece. + captcha_link (str | None): URL to background image. + captcha_file (str | None): Path to an image file to read & send. + captcha_base64 (bytes | None): Raw bytes or base64 string of image. + **kwargs: Passed through to the async HTTP request call. + + Returns: + dict[str, Any]: API response containing task status and solution. + """ + self.create_task_payload["task"].update({"parts": parts}) + await self._aio_body_file_processing( + save_format=self.save_format, + file_path=self.img_path, + captcha_link=captcha_link, + captcha_file=captcha_file, + captcha_base64=captcha_base64, + image_key="image", + **kwargs, + ) + if not self.result.errorId: + return await self._aio_processing_response() + return self.result.to_dict() + + def __del__(self): + """ + Cleanup saved images folder if configured to do so. + """ + if self.save_format == SaveFormatsEnm.CONST.value and self.img_clearing: + shutil.rmtree(self.img_path) diff --git a/src/python_rucaptcha/vk_captcha.py b/src/python_rucaptcha/vk_captcha.py index d465e7eb..23d9f6a8 100644 --- a/src/python_rucaptcha/vk_captcha.py +++ b/src/python_rucaptcha/vk_captcha.py @@ -7,23 +7,20 @@ class VKCaptcha(BaseCaptcha): def __init__( self, - websiteURL: str, - websiteKey: str, + redirectUri: str, userAgent: str, proxyType: str, proxyAddress: str, proxyPort: str, *args, - **kwargs, + **kwargs: dict[str, Any], ): """ - The class is used to work with CaptchaFox. + The class is used to work with VKCaptchaTask. Args: rucaptcha_key: User API key - websiteURL: Full URL of the captcha page - websiteKey: The value of the `key` parameter. - It can be found in the page source code or captured in network requests during page loading. + redirectUri: The URL that is returned on requests to the captcha API. userAgent: User-Agent of your browser will be used to load the captcha. Use only modern browser's User-Agents proxyType: Proxy type - `http`, `socks4`, `socks5` @@ -33,9 +30,8 @@ def __init__( kwargs: Not required params for task creation request Examples: - >>> CaptchaFox(rucaptcha_key="aa9011f31111181111168611f1151122", - ... websiteURL="3ceb8624-1970-4e6b-91d5-70317b70b651", - ... websiteKey="sk_xtNxpk6fCdFbxh1_xJeGflSdCE9tn99G", + >>> VKCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122", + ... redirectUri="https://id.vk.com/not_robot_captcha?domain=vk.com...", ... userAgent="Mozilla/5.0 .....", ... proxyType="socks5", ... proxyAddress="1.2.3.4", @@ -55,9 +51,8 @@ def __init__( "taskId": 73243152973, } - >>> await CaptchaFox(rucaptcha_key="aa9011f31111181111168611f1151122", - ... websiteURL="3ceb8624-1970-4e6b-91d5-70317b70b651", - ... websiteKey="sk_xtNxpk6fCdFbxh1_xJeGflSdCE9tn99G", + >>> await VKCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122", + ... redirectUri="https://id.vk.com/not_robot_captcha?domain=vk.com...", ... userAgent="Mozilla/5.0 .....", ... proxyType="socks5", ... proxyAddress="1.2.3.4", @@ -81,16 +76,15 @@ def __init__( Dict with full server response Notes: - https://2captcha.com/api-docs/captchafox + https://2captcha.com/api-docs/vk-captcha - https://rucaptcha.com/api-docs/captchafox + https://rucaptcha.com/api-docs/vk-captcha """ super().__init__(method=VKCaptchaEnm.VKCaptchaTask, *args, **kwargs) self.create_task_payload["task"].update( { - "websiteURL": websiteURL, - "websiteKey": websiteKey, + "websiteURL": redirectUri, "userAgent": userAgent, "proxyType": proxyType, "proxyAddress": proxyAddress, From 870868e23a474c44a64292d7944e6e42f4a132ac Mon Sep 17 00:00:00 2001 From: Andrei Date: Fri, 29 Aug 2025 01:05:44 +0300 Subject: [PATCH 15/21] Update conf.py --- docs/conf.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 78eaeb31..8d3f987a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -14,12 +14,14 @@ amazon_waf, mt_captcha, re_captcha, + vk_captcha, atb_captcha, captcha_fox, capy_puzzle, fun_captcha, key_captcha, grid_captcha, + temu_captcha, text_captcha, image_captcha, lemin_captcha, @@ -100,7 +102,6 @@ napoleon_use_param = True napoleon_use_rtype = True napoleon_preprocess_types = True -napoleon_type_aliases = True napoleon_attr_annotations = True autodoc_preserve_defaults = False From 020e5b08532fbd881872be641bf5007e6326ce59 Mon Sep 17 00:00:00 2001 From: Andrei Date: Fri, 29 Aug 2025 01:05:48 +0300 Subject: [PATCH 16/21] Update index.rst --- docs/index.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/index.rst b/docs/index.rst index 9591d647..586851dd 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -53,6 +53,8 @@ Check our other projects here - `RedPandaDev group Date: Fri, 29 Aug 2025 01:05:51 +0300 Subject: [PATCH 17/21] Create example.rst --- docs/modules/captcha-temu/example.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 docs/modules/captcha-temu/example.rst diff --git a/docs/modules/captcha-temu/example.rst b/docs/modules/captcha-temu/example.rst new file mode 100644 index 00000000..e0e6892d --- /dev/null +++ b/docs/modules/captcha-temu/example.rst @@ -0,0 +1,12 @@ +TemuCaptcha +=========== + +To import this module: + +.. code-block:: python + + from python_rucaptcha.temu_captcha import TemuCaptcha + + +.. autoclass:: python_rucaptcha.temu_captcha.TemuCaptcha + :members: \ No newline at end of file From 145468261b5d4946fee2b2ff05d29ffdb3b9dc31 Mon Sep 17 00:00:00 2001 From: Andrei Date: Fri, 29 Aug 2025 01:05:53 +0300 Subject: [PATCH 18/21] Create example.rst --- docs/modules/captcha-vk/example.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 docs/modules/captcha-vk/example.rst diff --git a/docs/modules/captcha-vk/example.rst b/docs/modules/captcha-vk/example.rst new file mode 100644 index 00000000..2d932679 --- /dev/null +++ b/docs/modules/captcha-vk/example.rst @@ -0,0 +1,12 @@ +VKCaptcha +========= + +To import this module: + +.. code-block:: python + + from python_rucaptcha.vk_captcha import VKCaptcha + + +.. autoclass:: python_rucaptcha.vk_captcha.VKCaptcha + :members: \ No newline at end of file From 6309018af82f086e39d61dbca0e8c2f9cbc16257 Mon Sep 17 00:00:00 2001 From: Andrei Date: Fri, 29 Aug 2025 01:05:55 +0300 Subject: [PATCH 19/21] Update info.md --- docs/modules/main/info.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/main/info.md b/docs/modules/main/info.md index 241b33d5..e8d55db8 100644 --- a/docs/modules/main/info.md +++ b/docs/modules/main/info.md @@ -3,7 +3,7 @@ ![](../../_static/RuCaptchaMedium.png) -### [Capsolver](https://www.capsolver.com/?utm_source=github&utm_medium=repo&utm_campaign=scraping&utm_term=python-rucaptcha) +## [Capsolver](https://www.capsolver.com/?utm_source=github&utm_medium=repo&utm_campaign=scraping&utm_term=python-rucaptcha) [![Capsolver](../../_static/capsolver.jpg)](https://www.capsolver.com/?utm_source=github&utm_medium=repo&utm_campaign=scraping&utm_term=python-rucaptcha) From af3c17587eec03b64e763e6a7a30745f0581fa91 Mon Sep 17 00:00:00 2001 From: Andrei Date: Fri, 29 Aug 2025 01:06:01 +0300 Subject: [PATCH 20/21] Update temu_captcha.py --- src/python_rucaptcha/temu_captcha.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/python_rucaptcha/temu_captcha.py b/src/python_rucaptcha/temu_captcha.py index d2e9deb7..43e9859a 100644 --- a/src/python_rucaptcha/temu_captcha.py +++ b/src/python_rucaptcha/temu_captcha.py @@ -36,18 +36,12 @@ def __init__( Common params include: - redirectUri: URL to confirm CAPTCHA resolution - any other API‐supported parameters + Examples: - >>> captcha = TemuCaptcha( - ... clientKey="YOUR_API_KEY", - ... userAgent="Mozilla/5.0 ...", - ... proxyType="socks5", - ... proxyAddress="1.2.3.4", - ... proxyPort="1080" - ... ) + >>> captcha = TemuCaptcha(rucaptcha_key="YOUR_API_KEY") >>> response = captcha.captcha_handler( ... parts=["part1_b64", "part2_b64", "part3_b64"], - ... captcha_base64=b"full_image_b64", - ... timeout=120 + ... captcha_base64=b"full_image_b64" ... ) >>> print(response) { From a120cb67ad9d8287b8e0ad0e0482ab49201b7b0a Mon Sep 17 00:00:00 2001 From: Andrei Date: Fri, 29 Aug 2025 01:19:28 +0300 Subject: [PATCH 21/21] Update serializer.py --- src/python_rucaptcha/core/serializer.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/python_rucaptcha/core/serializer.py b/src/python_rucaptcha/core/serializer.py index a12b4b14..1428c16e 100644 --- a/src/python_rucaptcha/core/serializer.py +++ b/src/python_rucaptcha/core/serializer.py @@ -65,6 +65,9 @@ def urls_set(self): """ Set request/response URLs if they not set previously """ + if isinstance(self.service_type, enums.ServiceEnm): + self.service_type = self.service_type.value + if self.service_type == enums.ServiceEnm.DEATHBYCAPTCHA: self.url_request = f"http://api.{self.service_type}.com/2captcha/in.php" self.url_response = f"http://api.{self.service_type}.com/2captcha/res.php"