From ff14f1aae1c247fce7125402782dc208e6d816d0 Mon Sep 17 00:00:00 2001 From: Andrei Date: Thu, 14 Dec 2023 18:17:55 +0300 Subject: [PATCH 01/11] Update __version__.py --- src/python_rucaptcha/__version__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python_rucaptcha/__version__.py b/src/python_rucaptcha/__version__.py index 8833022e..7f8a859d 100644 --- a/src/python_rucaptcha/__version__.py +++ b/src/python_rucaptcha/__version__.py @@ -1 +1 @@ -__version__ = "6.1" +__version__ = "6.1.1" From 19b9f1674564ee35320eb965e4cf147bdc506910 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 17:31:22 +0000 Subject: [PATCH 02/11] Bump black from 23.10.0 to 23.12.0 Bumps [black](https://github.com/psf/black) from 23.10.0 to 23.12.0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/compare/23.10.0...23.12.0) --- updated-dependencies: - dependency-name: black dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.style.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.style.txt b/requirements.style.txt index 48f83b59..df939544 100644 --- a/requirements.style.txt +++ b/requirements.style.txt @@ -1,4 +1,4 @@ # codestyle isort==5.* -black==23.10.0 +black==23.12.0 autoflake==2.* From ddcefc21065f02cfe5bf49a5730cb5f19037aaa2 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Fri, 22 Dec 2023 14:40:46 +0300 Subject: [PATCH 03/11] Update pydantic from 2.5.2 to 2.5.3 --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index c1ff2541..07ec7697 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,6 +2,6 @@ sphinx==7.2.6 pallets_sphinx_themes==2.1.1 myst-parser==2.0.0 autodoc_pydantic==2.0.1 -pydantic==2.5.2 +pydantic==2.5.3 pydantic-settings==2.1.0 enum-tools[sphinx]==0.11.0 From 892108ceec32359fb7880c19bb02ec4cb9986c6a Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 3 Jan 2024 20:59:44 +0300 Subject: [PATCH 04/11] Update enums.py --- src/python_rucaptcha/core/enums.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/python_rucaptcha/core/enums.py b/src/python_rucaptcha/core/enums.py index 1ca00112..1d999f14 100644 --- a/src/python_rucaptcha/core/enums.py +++ b/src/python_rucaptcha/core/enums.py @@ -144,3 +144,8 @@ class CoordinatesCaptchaEnm(str, MyEnum): class GridCaptchaEnm(str, MyEnum): GridTask = "GridTask" + + +class FriendlyCaptchaEnm(str, MyEnum): + FriendlyCaptchaTaskProxyless = "FriendlyCaptchaTaskProxyless" + FriendlyCaptchaTask = "FriendlyCaptchaTask" From b7a6a1e718d2f0af4d801ef41763bd558ac31485 Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 3 Jan 2024 20:59:46 +0300 Subject: [PATCH 05/11] Create friendly_captcha.py --- src/python_rucaptcha/friendly_captcha.py | 124 +++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 src/python_rucaptcha/friendly_captcha.py diff --git a/src/python_rucaptcha/friendly_captcha.py b/src/python_rucaptcha/friendly_captcha.py new file mode 100644 index 00000000..472c6542 --- /dev/null +++ b/src/python_rucaptcha/friendly_captcha.py @@ -0,0 +1,124 @@ +from typing import Union + +from .core.base import BaseCaptcha +from .core.enums import FriendlyCaptchaEnm + + +class FriendlyCaptcha(BaseCaptcha): + def __init__( + self, + websiteURL: str, + websiteKey: str, + method: Union[str, FriendlyCaptchaEnm] = FriendlyCaptchaEnm.FriendlyCaptchaTaskProxyless, + *args, + **kwargs, + ): + """ + The class is used to work with Friendly Captcha. + + Args: + rucaptcha_key: User API key + websiteURL: The full URL of target web page where the captcha is loaded. We do not open the page, + not a problem if it is available only for authenticated users + websiteKey: The value of `data-sitekey` attribute of captcha's `div` element on page. + method: Captcha type + + Examples: + >>> FriendlyCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122", + ... websiteKey="2FZFEVS1FZCGQ9", + ... websiteURL="https://example.com", + ... method=FriendlyCaptchaEnm.FriendlyCaptchaTaskProxyless.value + ... ).captcha_handler() + { + "errorId":0, + "status":"ready", + "solution":{ + "token":"PUZZLE_Abc1dEFghIJKLM2no34P56q7rStu8v" + }, + "cost":"0.00299", + "ip":"1.2.3.4", + "createTime":1692863536, + "endTime":1692863556, + "solveCount":1, + "taskId":75190409731 + } + + >>> FriendlyCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122", + ... websiteKey="2FZFEVS1FZCGQ9", + ... websiteURL="https://example.com", + ... method=FriendlyCaptchaEnm.FriendlyCaptchaTaskProxyless.value + ... ).captcha_handler() + { + "errorId":0, + "status":"ready", + "solution":{ + "token":"PUZZLE_Abc1dEFghIJKLM2no34P56q7rStu8v" + }, + "cost":"0.00299", + "ip":"1.2.3.4", + "createTime":1692863536, + "endTime":1692863556, + "solveCount":1, + "taskId":75190409731 + } + + >>> await FriendlyCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122", + ... websiteKey="2FZFEVS1FZCGQ9", + ... websiteURL="https://example.com", + ... method=FriendlyCaptchaEnm.FriendlyCaptchaTaskProxyless.value + ... ).aio_captcha_handler() + { + "errorId":0, + "status":"ready", + "solution":{ + "token":"PUZZLE_Abc1dEFghIJKLM2no34P56q7rStu8v" + }, + "cost":"0.00299", + "ip":"1.2.3.4", + "createTime":1692863536, + "endTime":1692863556, + "solveCount":1, + "taskId":75190409731 + } + + Returns: + Dict with full server response + + Notes: + https://rucaptcha.com/api-docs/friendly-captcha + """ + super().__init__(method=method, *args, **kwargs) + + self.create_task_payload["task"].update({"websiteURL": websiteURL, "websiteKey": websiteKey}) + + # check user params + if method not in FriendlyCaptchaEnm.list_values(): + raise ValueError(f"Invalid method parameter set, available - {FriendlyCaptchaEnm.list_values()}") + + def captcha_handler(self, **kwargs) -> dict: + """ + 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: + """ + Async solving method + + Returns: + Dict with full server response + + Notes: + Check class docstirng for more info + """ + return await self._aio_processing_response() From 6a366e57024365c54a130f709f5cfc9f297608e3 Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 3 Jan 2024 21:41:44 +0300 Subject: [PATCH 06/11] Update setup.py --- src/setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/setup.py b/src/setup.py index b9c2cba2..8f5cfe52 100644 --- a/src/setup.py +++ b/src/setup.py @@ -115,6 +115,7 @@ def run(self): turnstile amazon amazon_waf + friendly-captcha """, python_requires=REQUIRES_PYTHON, zip_safe=False, From f3efb678b441994e36343983cd008c532411bfb9 Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 3 Jan 2024 21:41:46 +0300 Subject: [PATCH 07/11] Create test_friendly_captcha.py --- tests/test_friendly_captcha.py | 131 +++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 tests/test_friendly_captcha.py diff --git a/tests/test_friendly_captcha.py b/tests/test_friendly_captcha.py new file mode 100644 index 00000000..717c27c1 --- /dev/null +++ b/tests/test_friendly_captcha.py @@ -0,0 +1,131 @@ +import pytest + +from tests.conftest import BaseTest +from python_rucaptcha.core.enums import FriendlyCaptchaEnm +from python_rucaptcha.core.serializer import GetTaskResultResponseSer +from python_rucaptcha.friendly_captcha import FriendlyCaptcha + + +class TestFriendlyCaptcha(BaseTest): + websiteURL = "https://example.cc/foo/bar.html" + websiteKey = "SAb83IIB" + + kwargs_params = { + "proxyType": "socks5", + "proxyAddress": BaseTest.proxyAddress, + "proxyPort": BaseTest.proxyPort, + } + + def test_methods_exists(self): + assert "captcha_handler" in FriendlyCaptcha.__dict__.keys() + assert "aio_captcha_handler" in FriendlyCaptcha.__dict__.keys() + + @pytest.mark.parametrize("method", FriendlyCaptchaEnm.list_values()) + def test_args(self, method: str): + instance = FriendlyCaptcha( + rucaptcha_key=self.RUCAPTCHA_KEY, + websiteURL=self.websiteURL, + websiteKey=self.websiteKey, + method=method, + ) + assert instance.create_task_payload["clientKey"] == self.RUCAPTCHA_KEY + assert instance.create_task_payload["task"]["type"] == method + assert instance.create_task_payload["task"]["websiteURL"] == self.websiteURL + assert instance.create_task_payload["task"]["websiteKey"] == self.websiteKey + + def test_kwargs(self): + instance = FriendlyCaptcha( + rucaptcha_key=self.RUCAPTCHA_KEY, + websiteURL=self.websiteURL, + websiteKey=self.websiteKey, + method=FriendlyCaptchaEnm.FriendlyCaptchaTaskProxyless, + **self.kwargs_params, + ) + assert set(self.kwargs_params.keys()).issubset(set(instance.create_task_payload["task"].keys())) + assert set(self.kwargs_params.values()).issubset(set(instance.create_task_payload["task"].values())) + + """ + Success tests + """ + + def test_basic_data(self): + instance = FriendlyCaptcha( + rucaptcha_key=self.RUCAPTCHA_KEY, + websiteURL=self.websiteURL, + websiteKey=self.websiteKey, + method=FriendlyCaptchaEnm.FriendlyCaptchaTaskProxyless.value, + ) + + result = instance.captcha_handler() + + assert isinstance(result, dict) is True + if not result["errorId"]: + assert result["status"] in ("ready", "processing") + assert isinstance(result["taskId"], int) is True + else: + assert result["errorId"] in (1, 12) + assert result["errorCode"] == "ERROR_CAPTCHA_UNSOLVABLE" + + assert result.keys() == GetTaskResultResponseSer().to_dict().keys() + + async def test_aio_basic_data(self): + instance = FriendlyCaptcha( + rucaptcha_key=self.RUCAPTCHA_KEY, + websiteURL=self.websiteURL, + websiteKey=self.websiteKey, + method=FriendlyCaptchaEnm.FriendlyCaptchaTaskProxyless.value, + ) + + result = await instance.aio_captcha_handler() + + assert isinstance(result, dict) is True + if not result["errorId"]: + assert result["status"] in ("ready", "processing") + assert isinstance(result["taskId"], int) is True + else: + assert result["errorId"] in (1, 12) + assert result["errorCode"] in ("ERROR_CAPTCHA_UNSOLVABLE", FriendlyCaptcha.NO_CAPTCHA_ERR) + + assert result.keys() == GetTaskResultResponseSer().to_dict().keys() + + def test_context_basic_data(self): + with FriendlyCaptcha( + rucaptcha_key=self.RUCAPTCHA_KEY, + websiteURL=self.websiteURL, + websiteKey=self.websiteKey, + method=FriendlyCaptchaEnm.FriendlyCaptchaTaskProxyless.value, + ) as instance: + assert instance + + async def test_context_aio_basic_data(self): + async with FriendlyCaptcha( + rucaptcha_key=self.RUCAPTCHA_KEY, + websiteURL=self.websiteURL, + websiteKey=self.websiteKey, + method=FriendlyCaptchaEnm.FriendlyCaptchaTaskProxyless.value, + ) as instance: + assert instance + + """ + Fail tests + """ + + def test_wrong_method(self): + with pytest.raises(ValueError): + FriendlyCaptcha( + rucaptcha_key=self.RUCAPTCHA_KEY, + websiteURL=self.websiteURL, + websiteKey=self.websiteKey, + method=self.get_random_string(length=5), + ) + + def test_no_websiteURL(self): + with pytest.raises(TypeError): + FriendlyCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY, websiteKey=self.websiteKey) + + def test_no_websiteKey(self): + with pytest.raises(TypeError): + FriendlyCaptcha( + rucaptcha_key=self.RUCAPTCHA_KEY, + websiteURL=self.websiteURL, + ) From b6931f9802ab7f80a9917ad956c9371fb7c47a30 Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 3 Jan 2024 21:42:20 +0300 Subject: [PATCH 08/11] 6.1.2 --- src/python_rucaptcha/__version__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python_rucaptcha/__version__.py b/src/python_rucaptcha/__version__.py index 7f8a859d..0237e6dc 100644 --- a/src/python_rucaptcha/__version__.py +++ b/src/python_rucaptcha/__version__.py @@ -1 +1 @@ -__version__ = "6.1.1" +__version__ = "6.1.2" From 367a1bbba24861375ab13da69d5698115527c0ec Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 3 Jan 2024 21:48:47 +0300 Subject: [PATCH 09/11] Update conf.py --- docs/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/conf.py b/docs/conf.py index 33348f1c..66dc8526 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -21,6 +21,7 @@ lemin_captcha, rotate_captcha, datadome_captcha, + friendly_captcha, cyber_siara_captcha, draw_around_captcha, bounding_box_captcha, From 8440ae2a9dfcec474103b4d07414b4475d3f28de Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 3 Jan 2024 21:48:51 +0300 Subject: [PATCH 10/11] Update info.rst --- docs/modules/enum/info.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/modules/enum/info.rst b/docs/modules/enum/info.rst index 18eebd3c..5579ff9f 100644 --- a/docs/modules/enum/info.rst +++ b/docs/modules/enum/info.rst @@ -76,3 +76,6 @@ To import this module: .. autoenum:: python_rucaptcha.core.enums.GridCaptchaEnm :members: + +.. autoenum:: python_rucaptcha.core.enums.FriendlyCaptchaEnm + :members: From c3d241381cff483d914d06b38ed2d50d70583904 Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 3 Jan 2024 21:50:00 +0300 Subject: [PATCH 11/11] Create example.rst --- docs/modules/friendly-captcha/example.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 docs/modules/friendly-captcha/example.rst diff --git a/docs/modules/friendly-captcha/example.rst b/docs/modules/friendly-captcha/example.rst new file mode 100644 index 00000000..f1872c00 --- /dev/null +++ b/docs/modules/friendly-captcha/example.rst @@ -0,0 +1,12 @@ +FriendlyCaptcha +=============== + +To import this module: + +.. code-block:: python + + from python_rucaptcha.friendly_captcha import FriendlyCaptcha + + +.. autoclass:: python_rucaptcha.friendly_captcha.FriendlyCaptcha + :members: \ No newline at end of file