Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c81a7e3
fix: docs/requirements.txt to reduce vulnerabilities
snyk-bot Jun 15, 2025
524d9d6
Merge pull request #337 from AndreiDrang/snyk-fix-0d81c4dc94a617fbc19…
AndreiDrang Jun 15, 2025
af5b966
Update pytest-asyncio requirement from ==0.* to ==1.*
dependabot[bot] Jun 16, 2025
154fe65
Merge pull request #338 from AndreiDrang/dependabot/pip/pytest-asynci…
AndreiDrang Jun 17, 2025
599e0eb
[github-actions] Bump actions/checkout from 4 to 5
dependabot[bot] Aug 12, 2025
59738c8
Merge pull request #339 from AndreiDrang/dependabot/github_actions/ac…
AndreiDrang Aug 13, 2025
af963b8
Update serializer.py
AndreiDrang Aug 28, 2025
90016c2
Update pyproject.toml
AndreiDrang Aug 28, 2025
d278844
Update config.py
AndreiDrang Aug 28, 2025
5046fa3
Update result_handler.py
AndreiDrang Aug 28, 2025
678e3e5
Update serializer.py
AndreiDrang Aug 28, 2025
2c7a6b3
Update base.py
AndreiDrang Aug 28, 2025
31571c6
Update enums.py
AndreiDrang Aug 28, 2025
6690f62
Update result_handler.py
AndreiDrang Aug 28, 2025
0c1618d
Update serializer.py
AndreiDrang Aug 28, 2025
30eafbc
Create vk_captcha.py
AndreiDrang Aug 28, 2025
a570073
v6.5.0
AndreiDrang Aug 28, 2025
661ce2d
Merge pull request #340 from AndreiDrang/feature-new-methods
AndreiDrang Aug 28, 2025
870868e
Update conf.py
AndreiDrang Aug 28, 2025
020e5b0
Update index.rst
AndreiDrang Aug 28, 2025
693367c
Create example.rst
AndreiDrang Aug 28, 2025
1454682
Create example.rst
AndreiDrang Aug 28, 2025
6309018
Update info.md
AndreiDrang Aug 28, 2025
af3c175
Update temu_captcha.py
AndreiDrang Aug 28, 2025
a120cb6
Update serializer.py
AndreiDrang Aug 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/install.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/sphinx.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 2 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ Check our other projects here - `RedPandaDev group <https://red-panda-dev.xyz/bl
modules/prosopo/example.rst
modules/atb-captcha/example.rst
modules/captcha-fox/example.rst
modules/captcha-temu/example.rst
modules/captcha-vk/example.rst
modules/control/example.rst

.. toctree::
Expand Down
12 changes: 12 additions & 0 deletions docs/modules/captcha-temu/example.rst
Original file line number Diff line number Diff line change
@@ -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:
12 changes: 12 additions & 0 deletions docs/modules/captcha-vk/example.rst
Original file line number Diff line number Diff line change
@@ -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:
2 changes: 1 addition & 1 deletion docs/modules/main/info.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion docs/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ keywords = [ "captcha",
"turnstile",
"amazon",
"amazon_waf",
"vk-captcha",
"fox-captcha",
"temu-captcha",
"friendly-captcha"
]
license = "MIT"
Expand All @@ -78,6 +81,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",
Expand Down
2 changes: 1 addition & 1 deletion requirements.test.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
pytest==8.*
coverage==7.*
pytest-asyncio==0.*
pytest-asyncio==1.*
2 changes: 1 addition & 1 deletion src/python_rucaptcha/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "6.4.0"
__version__ = "6.5.0"
76 changes: 49 additions & 27 deletions src/python_rucaptcha/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -30,16 +30,32 @@ def __init__(
rucaptcha_key: str,
method: str,
sleep_time: int = 10,
service_type: str = ServiceEnm.TWOCAPTCHA.value,
**kwargs,
service_type: ServiceEnm | str = ServiceEnm.TWOCAPTCHA,
**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
Expand All @@ -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)
Expand All @@ -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) -> dict:
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
Expand Down Expand Up @@ -90,13 +106,13 @@ def _processing_response(self, **kwargs: dict) -> dict:
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
"""
Expand All @@ -106,7 +122,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
"""
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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
Expand Down
4 changes: 3 additions & 1 deletion src/python_rucaptcha/core/config.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Generator

from tenacity import AsyncRetrying, wait_fixed, stop_after_attempt
from requests.adapters import Retry

Expand All @@ -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`

Expand Down
8 changes: 8 additions & 0 deletions src/python_rucaptcha/core/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Loading
Loading