From eaa0908d3ff3f18589824df1b94f11f608b28633 Mon Sep 17 00:00:00 2001 From: Andrei Date: Mon, 11 Dec 2023 04:16:39 +0300 Subject: [PATCH 01/32] Update test_datadome.py --- tests/test_datadome.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_datadome.py b/tests/test_datadome.py index 6e01f97b..fd1765ce 100644 --- a/tests/test_datadome.py +++ b/tests/test_datadome.py @@ -5,7 +5,7 @@ from python_rucaptcha.datadome_captcha import DataDomeCaptcha -class TestHCaptcha(BaseTest): +class TestDatadome(BaseTest): websiteURL = "https://www.pokemoncenter.com/" captchaUrl = "https://geo.captcha-delivery.com/captcha/?initialCid=AHrlqAAAAAMAlk-FmAyNOW8AUyTH_g%3D%3D&hash=5B45875B653A484CC79E57036CE9FC&cid=noJuZstmvINksqOxaXWQogbPBd01y3VaH3r-CZ4eqK4roZuelJMHVhO2rR0IySRieoAivkg74B4UpJ.xj.jVNB6-aLaW.Bwvik7__EncryD6COavwx8RmOqgZ7DK_3v&t=fe&referer=https%3A%2F%2Fwww.pokemoncenter.com%2F&s=9817&e=2b1d5a78107ded0dcdc8317aa879979ed5083a2b3a95b734dbe7871679e1403" userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36" From 6b8cf762cb478cc2ee91069d8fae0f088b0bb6b2 Mon Sep 17 00:00:00 2001 From: Andrei Date: Mon, 11 Dec 2023 04:20:53 +0300 Subject: [PATCH 02/32] Create mt_captcha.py --- src/python_rucaptcha/mt_captcha.py | 104 +++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 src/python_rucaptcha/mt_captcha.py diff --git a/src/python_rucaptcha/mt_captcha.py b/src/python_rucaptcha/mt_captcha.py new file mode 100644 index 00000000..25b6a43f --- /dev/null +++ b/src/python_rucaptcha/mt_captcha.py @@ -0,0 +1,104 @@ +from typing import Union + +from .core.base import BaseCaptcha +from .core.enums import MTCaptchaEnm + + +class MTCaptcha(BaseCaptcha): + def __init__( + self, + websiteURL: str, + websiteKey: str, + method: Union[str, MTCaptchaEnm] = MTCaptchaEnm.MtCaptchaTaskProxyless.value, + *args, + **kwargs, + ): + """ + The class is used to work with HCaptcha. + + Args: + rucaptcha_key: User API key + websiteURL: Full URL of the captcha page + websiteKey: The MTCaptcha `sitekey` value found in the page code. + method: Captcha type + kwargs: Not required params for task creation request + + Examples: + >>> MTCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122", + ... websiteURL="https://service.mtcaptcha.com/mtcv1/demo/index.html", + ... websiteKey="MTPublic-DemoKey9M", + ... method=MTCaptchaEnm.MtCaptchaTaskProxyless, + ... ).captcha_handler() + { + "errorId":0, + "status":"ready", + "solution":{ + "token": "datadome=4ZXwCBlyHx9ktZhSnycMF...; Path=/; Secure; SameSite=Lax" + }, + "cost":"0.00299", + "ip":"1.2.3.4", + "createTime":1692863536, + "endTime":1692863556, + "solveCount":1, + "taskId": 73243152973, + } + + >>> await MTCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122", + ... websiteURL="https://service.mtcaptcha.com/mtcv1/demo/index.html", + ... websiteKey="MTPublic-DemoKey9M", + ... method=MTCaptchaEnm.MtCaptchaTaskProxyless, + ... ).aio_captcha_handler() + { + "errorId":0, + "status":"ready", + "solution":{ + "token": "datadome=4ZXwCBlyHx9ktZhSnycMF...; Path=/; Secure; SameSite=Lax" + }, + "cost":"0.00299", + "ip":"1.2.3.4", + "createTime":1692863536, + "endTime":1692863556, + "solveCount":1, + "taskId": 73243152973, + } + + Returns: + Dict with full server response + + Notes: + https://2captcha.com/api-docs/mtcaptcha + """ + super().__init__(method=method, *args, **kwargs) + + self.create_task_payload["task"].update({"websiteURL": websiteURL, "websiteKey": websiteKey}) + # check user params + if method not in MTCaptchaEnm.list_values(): + raise ValueError(f"Invalid method parameter set, available - {MTCaptchaEnm.list_values()}") + + def captcha_handler(self, **kwargs) -> dict: + """ + Sync solving method + + Args: + kwargs: Parameters for the `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 50283742a210b8e84e649bf950123a097483bf0a Mon Sep 17 00:00:00 2001 From: Andrei Date: Mon, 11 Dec 2023 04:20:56 +0300 Subject: [PATCH 03/32] 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 f46ba606..8f6d245f 100644 --- a/src/python_rucaptcha/core/enums.py +++ b/src/python_rucaptcha/core/enums.py @@ -123,3 +123,8 @@ class DataDomeSliderEnm(str, MyEnum): class CyberSiARAEnm(str, MyEnum): AntiCyberSiAraTask = "AntiCyberSiAraTask" AntiCyberSiAraTaskProxyless = "AntiCyberSiAraTaskProxyless" + + +class MTCaptchaEnm(str, MyEnum): + MtCaptchaTask = "MtCaptchaTask" + MtCaptchaTaskProxyless = "MtCaptchaTaskProxyless" From a81d26f672e53f906ebf930cdfe38b29c4b8b863 Mon Sep 17 00:00:00 2001 From: Andrei Date: Mon, 11 Dec 2023 04:20:59 +0300 Subject: [PATCH 04/32] Create test_mtcaptcha.py --- tests/test_mtcaptcha.py | 67 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 tests/test_mtcaptcha.py diff --git a/tests/test_mtcaptcha.py b/tests/test_mtcaptcha.py new file mode 100644 index 00000000..0650205a --- /dev/null +++ b/tests/test_mtcaptcha.py @@ -0,0 +1,67 @@ +import pytest + +from tests.conftest import BaseTest +from python_rucaptcha.core.enums import MTCaptchaEnm +from python_rucaptcha.mt_captcha import MTCaptcha + + +class TestMTCaptcha(BaseTest): + websiteURL = "https://service.mtcaptcha.com/mtcv1/demo/index.html" + websiteKey = "MTPublic-DemoKey9M" + + kwargs_params = { + "proxyType": "socks5", + "proxyAddress": BaseTest.proxyAddress, + "proxyPort": BaseTest.proxyPort, + } + + def test_methods_exists(self): + assert "captcha_handler" in MTCaptcha.__dict__.keys() + assert "aio_captcha_handler" in MTCaptcha.__dict__.keys() + + @pytest.mark.parametrize("method", MTCaptchaEnm.list_values()) + def test_args(self, method: str): + instance = MTCaptcha( + 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 = MTCaptcha( + rucaptcha_key=self.RUCAPTCHA_KEY, + websiteURL=self.websiteURL, + websiteKey=self.websiteKey, + **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())) + + """ + Fail tests + """ + + def test_no_websiteURL(self): + with pytest.raises(TypeError): + MTCaptcha( + rucaptcha_key=self.RUCAPTCHA_KEY, + websiteKey=self.websiteKey, + ) + + def test_no_websiteKey(self): + with pytest.raises(TypeError): + MTCaptcha( + rucaptcha_key=self.RUCAPTCHA_KEY, + websiteURL=self.websiteURL, + ) + + def test_wrong_method(self): + with pytest.raises(ValueError): + MTCaptcha( + rucaptcha_key=self.RUCAPTCHA_KEY, + websiteURL=self.websiteURL, + websiteKey=self.websiteKey, + method=self.get_random_string(length=5), + ) From ee37717d12e8e15e02707cfd475d4df58ecb1a6d Mon Sep 17 00:00:00 2001 From: Andrei Date: Tue, 12 Dec 2023 02:11:29 +0300 Subject: [PATCH 05/32] removed useless tests --- tests/test_amazon.py | 12 ++++++------ tests/test_capypuzzle.py | 22 ++-------------------- tests/test_cutcaptcha.py | 4 ++-- tests/test_funcaptcha.py | 4 ++-- tests/test_geetest.py | 4 ++-- tests/test_hcaptcha.py | 4 ++-- tests/test_key_captcha.py | 4 ++-- tests/test_lemin.py | 4 ++-- tests/test_recaptcha.py | 4 ++-- tests/test_text.py | 2 ++ tests/test_turnstile.py | 4 ++-- 11 files changed, 26 insertions(+), 42 deletions(-) diff --git a/tests/test_amazon.py b/tests/test_amazon.py index a06639f0..d6e4fbf2 100644 --- a/tests/test_amazon.py +++ b/tests/test_amazon.py @@ -11,6 +11,10 @@ class TestAmazonCaptcha(BaseTest): iv = "some-iv-value" context = "some-context-value" + def test_methods_exists(self): + assert "captcha_handler" in AmazonWAF.__dict__.keys() + assert "aio_captcha_handler" in AmazonWAF.__dict__.keys() + @pytest.mark.parametrize("method", AmazonWAFCaptchaEnm.list_values()) def test_args(self, method: str): instance = AmazonWAF( @@ -32,10 +36,6 @@ def test_args(self, method: str): Success tests """ - def test_methods_exists(self): - assert "captcha_handler" in AmazonWAF.__dict__.keys() - assert "aio_captcha_handler" in AmazonWAF.__dict__.keys() - def test_basic_data(self): instance = AmazonWAF( rucaptcha_key=self.RUCAPTCHA_KEY, @@ -56,7 +56,7 @@ def test_context_basic_data(self): context=self.context, method=AmazonWAFCaptchaEnm.AmazonTaskProxyless.value, ) as instance: - assert instance.captcha_handler() + assert instance async def test_aio_basic_data(self): instance = AmazonWAF( @@ -78,7 +78,7 @@ async def test_aio_context_basic_data(self): context=self.context, method=AmazonWAFCaptchaEnm.AmazonTaskProxyless.value, ) as instance: - assert await instance.aio_captcha_handler() + assert instance """ Fail tests diff --git a/tests/test_capypuzzle.py b/tests/test_capypuzzle.py index 0e06c752..faf15100 100644 --- a/tests/test_capypuzzle.py +++ b/tests/test_capypuzzle.py @@ -88,16 +88,7 @@ def test_context_basic_data(self): api_server=self.api_server, version=self.versions[0], ) as instance: - 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() + assert instance async def test_context_aio_basic_data(self): async with CapyPuzzle( @@ -108,16 +99,7 @@ async def test_context_aio_basic_data(self): api_server=self.api_server, version=self.versions[0], ) as instance: - 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"] == "ERROR_CAPTCHA_UNSOLVABLE" - - assert result.keys() == GetTaskResultResponseSer().to_dict().keys() + assert instance """ Fail tests diff --git a/tests/test_cutcaptcha.py b/tests/test_cutcaptcha.py index 9554d355..bfc6dc9a 100644 --- a/tests/test_cutcaptcha.py +++ b/tests/test_cutcaptcha.py @@ -104,7 +104,7 @@ def test_context_basic_data(self): apiKey=self.apiKey, method=CutCaptchaEnm.CutCaptchaTaskProxyless.value, ) as instance: - assert instance.captcha_handler() + assert instance async def test_context_aio_basic_data(self): async with CutCaptcha( @@ -114,7 +114,7 @@ async def test_context_aio_basic_data(self): apiKey=self.apiKey, method=CutCaptchaEnm.CutCaptchaTaskProxyless.value, ) as instance: - assert await instance.aio_captcha_handler() + assert instance """ Fail tests diff --git a/tests/test_funcaptcha.py b/tests/test_funcaptcha.py index 026c8a5f..dcab4039 100644 --- a/tests/test_funcaptcha.py +++ b/tests/test_funcaptcha.py @@ -101,7 +101,7 @@ def test_context_basic_data(self): websitePublicKey=self.publickey, method=FunCaptchaEnm.FunCaptchaTaskProxyless.value, ) as instance: - assert instance.captcha_handler() + assert instance async def test_context_aio_basic_data(self): async with FunCaptcha( @@ -110,7 +110,7 @@ async def test_context_aio_basic_data(self): websitePublicKey=self.publickey, method=FunCaptchaEnm.FunCaptchaTaskProxyless.value, ) as instance: - assert await instance.aio_captcha_handler() + assert instance """ Fail tests diff --git a/tests/test_geetest.py b/tests/test_geetest.py index 9de7667f..26394ecd 100644 --- a/tests/test_geetest.py +++ b/tests/test_geetest.py @@ -106,7 +106,7 @@ def test_context_basic_data(self): method=GeetestEnm.GeeTestTaskProxyless.value, gt=self.gt, ) as instance: - assert instance.captcha_handler(challenge=self.challenge) + assert instance async def test_context_aio_basic_data(self): async with GeeTest( @@ -115,7 +115,7 @@ async def test_context_aio_basic_data(self): method=GeetestEnm.GeeTestTaskProxyless.value, gt=self.gt, ) as instance: - assert await instance.aio_captcha_handler(challenge=self.challenge) + assert instance """ Fail tests diff --git a/tests/test_hcaptcha.py b/tests/test_hcaptcha.py index ca722bb2..302ff6ad 100644 --- a/tests/test_hcaptcha.py +++ b/tests/test_hcaptcha.py @@ -98,7 +98,7 @@ def test_context_basic_data(self): websiteKey=self.sitekey, method=HCaptchaEnm.HCaptchaTaskProxyless.value, ) as instance: - assert instance.captcha_handler() + assert instance async def test_context_aio_basic_data(self): async with HCaptcha( @@ -107,7 +107,7 @@ async def test_context_aio_basic_data(self): websiteKey=self.sitekey, method=HCaptchaEnm.HCaptchaTaskProxyless.value, ) as instance: - assert await instance.aio_captcha_handler() + assert instance """ Fail tests diff --git a/tests/test_key_captcha.py b/tests/test_key_captcha.py index 220e53cb..5d61d24c 100644 --- a/tests/test_key_captcha.py +++ b/tests/test_key_captcha.py @@ -100,7 +100,7 @@ def test_context_basic_data(self): s_s_c_web_server_sign2=self.s_s_c_web_server_sign2, method=KeyCaptchaEnm.KeyCaptchaTaskProxyless.value, ) as instance: - assert instance.captcha_handler() + assert instance async def test_context_aio_basic_data(self): async with KeyCaptcha( @@ -112,7 +112,7 @@ async def test_context_aio_basic_data(self): s_s_c_web_server_sign2=self.s_s_c_web_server_sign2, method=KeyCaptchaEnm.KeyCaptchaTaskProxyless.value, ) as instance: - assert await instance.aio_captcha_handler() + assert instance """ Fail tests diff --git a/tests/test_lemin.py b/tests/test_lemin.py index a585b87f..f1a54d55 100644 --- a/tests/test_lemin.py +++ b/tests/test_lemin.py @@ -88,7 +88,7 @@ def test_context_basic_data(self): div_id=self.div_id, method=LeminCaptchaEnm.LeminTaskProxyless.value, ) as instance: - assert instance.captcha_handler() + assert instance async def test_context_aio_basic_data(self): async with LeminCaptcha( @@ -98,7 +98,7 @@ async def test_context_aio_basic_data(self): div_id=self.div_id, method=LeminCaptchaEnm.LeminTaskProxyless.value, ) as instance: - assert await instance.aio_captcha_handler() + assert instance """ Fail tests diff --git a/tests/test_recaptcha.py b/tests/test_recaptcha.py index e4a2db0a..bdeffbb3 100644 --- a/tests/test_recaptcha.py +++ b/tests/test_recaptcha.py @@ -99,7 +99,7 @@ def test_context_basic_data(self): websiteKey=self.googlekey, method=ReCaptchaEnm.RecaptchaV2TaskProxyless.value, ) as instance: - assert instance.captcha_handler() + assert instance async def test_context_aio_basic_data(self): async with ReCaptcha( @@ -108,7 +108,7 @@ async def test_context_aio_basic_data(self): websiteKey=self.googlekey, method=ReCaptchaEnm.RecaptchaV2TaskProxyless.value, ) as instance: - assert await instance.aio_captcha_handler() + assert instance """ Fail tests diff --git a/tests/test_text.py b/tests/test_text.py index 03a89d3f..c421bed5 100644 --- a/tests/test_text.py +++ b/tests/test_text.py @@ -1,6 +1,7 @@ import pytest from tests.conftest import BaseTest +from python_rucaptcha.core.enums import TextCaptchaEnm from python_rucaptcha.text_captcha import TextCaptcha from python_rucaptcha.core.serializer import GetTaskResultResponseSer @@ -19,6 +20,7 @@ def test_methods_exists(self): def test_args(self): instance = TextCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY) assert instance.create_task_payload["clientKey"] == self.RUCAPTCHA_KEY + assert instance.create_task_payload["task"]["type"] == TextCaptchaEnm.TextCaptchaTask @pytest.mark.parametrize("lang_code, question", questions) def test_basic(self, lang_code: str, question: str): diff --git a/tests/test_turnstile.py b/tests/test_turnstile.py index a1c8badd..d68826e9 100644 --- a/tests/test_turnstile.py +++ b/tests/test_turnstile.py @@ -103,7 +103,7 @@ def test_context_basic_data(self): userAgent=self.useragent, method=TurnstileCaptchaEnm.TurnstileTaskProxyless.value, ) as instance: - assert instance.captcha_handler() + assert instance async def test_context_aio_basic_data(self): async with Turnstile( @@ -113,7 +113,7 @@ async def test_context_aio_basic_data(self): userAgent=self.useragent, method=TurnstileCaptchaEnm.TurnstileTaskProxyless.value, ) as instance: - assert await instance.aio_captcha_handler() + assert instance """ Fail tests From 7b2e79f9955e98e87baeb48f2461f5b6cd09ffd9 Mon Sep 17 00:00:00 2001 From: Andrei Date: Tue, 12 Dec 2023 03:04:29 +0300 Subject: [PATCH 06/32] Create bounding_box_start.jpg --- src/examples/bounding_box_start.jpg | Bin 0 -> 4704 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/examples/bounding_box_start.jpg diff --git a/src/examples/bounding_box_start.jpg b/src/examples/bounding_box_start.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9e5d507419e0cabb9fab9ce7318b534abd613465 GIT binary patch literal 4704 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 Date: Tue, 12 Dec 2023 03:04:42 +0300 Subject: [PATCH 07/32] Create bounding_box_start.png --- src/examples/bounding_box_start.png | Bin 0 -> 443384 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/examples/bounding_box_start.png diff --git a/src/examples/bounding_box_start.png b/src/examples/bounding_box_start.png new file mode 100644 index 0000000000000000000000000000000000000000..7b041cca373a7dab372298cbebdb58c9de2a0c27 GIT binary patch literal 443384 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 literal 0 HcmV?d00001 From 55a3d52713d5909a1e544a8cbad2b4ba136b7309 Mon Sep 17 00:00:00 2001 From: Andrei Date: Tue, 12 Dec 2023 03:04:46 +0300 Subject: [PATCH 08/32] Create bounding_box_captcha.py --- src/python_rucaptcha/bounding_box_captcha.py | 308 +++++++++++++++++++ 1 file changed, 308 insertions(+) create mode 100644 src/python_rucaptcha/bounding_box_captcha.py diff --git a/src/python_rucaptcha/bounding_box_captcha.py b/src/python_rucaptcha/bounding_box_captcha.py new file mode 100644 index 00000000..19cb29b6 --- /dev/null +++ b/src/python_rucaptcha/bounding_box_captcha.py @@ -0,0 +1,308 @@ +import shutil +import logging +from typing import Union, Optional + +from .core.base import BaseCaptcha +from .core.enums import SaveFormatsEnm, BoundingBoxCaptchaEnm + + +class BoundingBoxCaptcha(BaseCaptcha): + def __init__( + self, + save_format: Union[str, SaveFormatsEnm] = SaveFormatsEnm.TEMP, + img_clearing: bool = True, + img_path: str = "PythonRuCaptchaBoundingBox", + *args, + **kwargs, + ): + """ + The class is used to work with Bounding Box. + + Args: + rucaptcha_key: User API key + save_format: The format in which the image will be saved, or as a temporary file - 'temp', + or as a regular image to a folder created by the library - 'const'. + img_clearing: True - delete file after solution, False - don't delete file after solution + img_path: Folder to save captcha images + kwargs: Additional not required params for this captcha type + + Examples: + >>> BoundingBoxCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122", + ... ).captcha_handler(captcha_link="https://rucaptcha.com/dist/web/99581b9d446a509a0a01954438a5e36a.jpg") + { + "errorId":0, + "status":"ready", + "solution":{ + "bounding_boxes": [ + { + "xMin": 310, + "xMax": 385, + "yMin": 231, + "yMax": 308 + } + ] + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + >>> BoundingBoxCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122", + ... ).captcha_handler(captcha_file="src/examples/088636.png") + { + "errorId":0, + "status":"ready", + "solution":{ + "bounding_boxes": [ + { + "xMin": 310, + "xMax": 385, + "yMin": 231, + "yMax": 308 + } + ] + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + >>> with open("src/examples/088636.png", "rb") as f: + ... file_data = f.read() + >>> BoundingBoxCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122" + ... ).captcha_handler(captcha_base64=file_data) + { + "errorId":0, + "status":"ready", + "solution":{ + "bounding_boxes": [ + { + "xMin": 310, + "xMax": 385, + "yMin": 231, + "yMax": 308 + } + ] + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + >>> await BoundingBoxCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122", + ... ).aio_captcha_handler(captcha_link="https://rucaptcha.com/dist/web/99581b9d446a509a0a01954438a5e36a.jpg") + { + "errorId":0, + "status":"ready", + "solution":{ + "bounding_boxes": [ + { + "xMin": 310, + "xMax": 385, + "yMin": 231, + "yMax": 308 + } + ] + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + >>> await BoundingBoxCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122", + ... ).aio_captcha_handler(captcha_file="src/examples/088636.png") + { + "errorId":0, + "status":"ready", + "solution":{ + "bounding_boxes": [ + { + "xMin": 310, + "xMax": 385, + "yMin": 231, + "yMax": 308 + } + ] + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + Death Captcha + + >>> BoundingBoxCaptcha(rucaptcha_key="some_username:some_password", + ... service_type="deathbycaptcha" + ... ).captcha_handler(captcha_file="src/examples/088636.jpg") + { + "errorId":0, + "status":"ready", + "solution":{ + "bounding_boxes": [ + { + "xMin": 310, + "xMax": 385, + "yMin": 231, + "yMax": 308 + } + ] + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + >>> await BoundingBoxCaptcha(rucaptcha_key="some_username:some_password", + ... service_type="deathbycaptcha" + ... ).aio_captcha_handler(captcha_link="https://rucaptcha.com/dist/web/99581b9d446a509a0a01954438a5e36a.jpg") + { + "errorId":0, + "status":"ready", + "solution":{ + "bounding_boxes": [ + { + "xMin": 310, + "xMax": 385, + "yMin": 231, + "yMax": 308 + } + ] + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + >>> with open("src/examples/088636.png", "rb") as f: + ... file_data = f.read() + >>> await BoundingBoxCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122" + ... ).aio_captcha_handler(captcha_base64=file_data) + { + "errorId":0, + "status":"ready", + "solution":{ + "bounding_boxes": [ + { + "xMin": 310, + "xMax": 385, + "yMin": 231, + "yMax": 308 + } + ] + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + Returns: + Dict with full server response + + Notes: + https://2captcha.com/api-docs/bounding-box + + https://rucaptcha.com/api-docs/bounding-box + """ + super().__init__(method=BoundingBoxCaptchaEnm.BoundingBoxTask.value, *args, **kwargs) + + self.save_format = save_format + self.img_clearing = img_clearing + self.img_path = img_path + + def captcha_handler( + self, + captcha_link: Optional[str] = None, + captcha_file: Optional[str] = None, + captcha_base64: Optional[bytes] = None, + **kwargs, + ) -> dict: + """ + Sync solving method + + Args: + captcha_link: Captcha image URL + captcha_file: Captcha image file path + captcha_base64: Captcha image BASE64 info + kwargs: additional params for `requests` library + + Returns: + Dict with full server response + + Notes: + Check class docstirng for more info + """ + 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, + **kwargs, + ) + logging.warning(f"{self.result = }") + logging.warning(f"{self.create_task_payload = }") + if not self.result.errorId: + return self._processing_response(**kwargs) + return self.result.to_dict() + + async def aio_captcha_handler( + self, + captcha_link: Optional[str] = None, + captcha_file: Optional[str] = None, + captcha_base64: Optional[bytes] = None, + **kwargs, + ) -> dict: + """ + Async solving method + + Args: + captcha_link: Captcha image URL + captcha_file: Captcha image file path + captcha_base64: Captcha image BASE64 info + kwargs: additional params for `aiohttp` library + + Returns: + Dict with full server response + + Notes: + Check class docstirng for more info + """ + 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, + **kwargs, + ) + if not self.result.errorId: + return await self._aio_processing_response() + return self.result.to_dict() + + def __del__(self): + if self.save_format == SaveFormatsEnm.CONST.value and self.img_clearing: + shutil.rmtree(self.img_path) From fd7cce71c66f324424e75d0dfce57cb89e13d92f Mon Sep 17 00:00:00 2001 From: Andrei Date: Tue, 12 Dec 2023 03:04:50 +0300 Subject: [PATCH 09/32] Update enums.py --- src/python_rucaptcha/core/enums.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/python_rucaptcha/core/enums.py b/src/python_rucaptcha/core/enums.py index 8f6d245f..891490df 100644 --- a/src/python_rucaptcha/core/enums.py +++ b/src/python_rucaptcha/core/enums.py @@ -128,3 +128,7 @@ class CyberSiARAEnm(str, MyEnum): class MTCaptchaEnm(str, MyEnum): MtCaptchaTask = "MtCaptchaTask" MtCaptchaTaskProxyless = "MtCaptchaTaskProxyless" + + +class BoundingBoxCaptchaEnm(str, MyEnum): + BoundingBoxTask = "BoundingBoxTask" From 8dae4726d894c0a191d9dd8e4c13821c876b28c3 Mon Sep 17 00:00:00 2001 From: Andrei Date: Tue, 12 Dec 2023 03:04:53 +0300 Subject: [PATCH 10/32] Create test_bounding_box.py --- tests/test_bounding_box.py | 160 +++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 tests/test_bounding_box.py diff --git a/tests/test_bounding_box.py b/tests/test_bounding_box.py new file mode 100644 index 00000000..fcf27c3a --- /dev/null +++ b/tests/test_bounding_box.py @@ -0,0 +1,160 @@ +import pytest + +from tests.conftest import BaseTest +from python_rucaptcha.core.enums import SaveFormatsEnm, BoundingBoxCaptchaEnm +from python_rucaptcha.core.serializer import GetTaskResultResponseSer +from python_rucaptcha.bounding_box_captcha import BoundingBoxCaptcha + + +class BaseImageCaptcha(BaseTest): + captcha_file = "src/examples/bounding_box_start.png" + + +class TestImageCaptcha(BaseImageCaptcha): + kwargs_params = { + "comment": "None", + "imgInstructions": "None", + } + """ + Success tests + """ + + def test_methods_exists(self): + assert "captcha_handler" in BoundingBoxCaptcha.__dict__.keys() + assert "aio_captcha_handler" in BoundingBoxCaptcha.__dict__.keys() + + @pytest.mark.parametrize("img_clearing", (True, False)) + @pytest.mark.parametrize("save_format", SaveFormatsEnm.list_values()) + def test_args(self, save_format: str, img_clearing: bool): + instance = BoundingBoxCaptcha( + rucaptcha_key=self.RUCAPTCHA_KEY, + img_clearing=img_clearing, + save_format=save_format, + ) + assert instance.create_task_payload["clientKey"] == self.RUCAPTCHA_KEY + assert instance.create_task_payload["task"]["type"] == BoundingBoxCaptchaEnm.BoundingBoxTask + assert instance.save_format == save_format + assert instance.img_clearing == img_clearing + + def test_kwargs(self): + instance = BoundingBoxCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY, **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())) + + @pytest.mark.parametrize("save_format", SaveFormatsEnm.list_values()) + def test_basic_file(self, save_format): + instance = BoundingBoxCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY, save_format=save_format) + result = instance.captcha_handler(captcha_file=self.captcha_file) + + assert isinstance(result, dict) is True + + if not result["errorId"]: + assert result["status"] == "ready" + assert isinstance(result["solution"]["text"], str) is True + assert isinstance(result["taskId"], int) is True + else: + assert result["errorId"] in (1, 12) + assert result["errorCode"] == "ERROR_CAPTCHA_UNSOLVABLE" + + @pytest.mark.parametrize("save_format", SaveFormatsEnm.list_values()) + def test_basic_base64(self, save_format): + instance = BoundingBoxCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY, save_format=save_format) + + with open(self.captcha_file, "rb") as f: + result = instance.captcha_handler(captcha_base64=f.read()) + + assert isinstance(result, dict) is True + + if not result["errorId"]: + assert result["status"] == "ready" + assert isinstance(result["solution"]["text"], str) is True + 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() + + @pytest.mark.parametrize("save_format", SaveFormatsEnm.list_values()) + async def test_aio_basic_file(self, save_format): + instance = BoundingBoxCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY, save_format=save_format) + + result = await instance.aio_captcha_handler(captcha_file=self.captcha_file) + assert isinstance(result, dict) is True + + if not result["errorId"]: + assert result["status"] == "ready" + assert isinstance(result["solution"]["text"], str) is True + 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() + + @pytest.mark.parametrize("save_format", SaveFormatsEnm.list_values()) + async def test_aio_basic_base64(self, save_format): + instance = BoundingBoxCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY, save_format=save_format) + + with open(self.captcha_file, "rb") as f: + result = await instance.aio_captcha_handler(captcha_base64=f.read()) + + assert isinstance(result, dict) is True + if not result["errorId"]: + assert result["status"] == "ready" + assert isinstance(result["solution"]["text"], str) is True + 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() + + """ + Fail tests + """ + + def test_no_captcha(self): + instance = BoundingBoxCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY) + result = instance.captcha_handler() + assert isinstance(result, dict) is True + assert result["errorId"] == 12 + assert isinstance(result["errorCode"], str) is True + assert result.keys() == GetTaskResultResponseSer().to_dict().keys() + + async def test_aio_no_captcha(self): + instance = BoundingBoxCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY) + result = await instance.aio_captcha_handler() + assert isinstance(result, dict) is True + assert result["errorId"] == 12 + assert isinstance(result["errorCode"], str) is True + assert result.keys() == GetTaskResultResponseSer().to_dict().keys() + + def test_wrong_link(self): + instance = BoundingBoxCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY) + result = instance.captcha_handler(captcha_link=self.get_random_string(length=50)) + assert isinstance(result, dict) is True + assert result["errorId"] == 12 + assert isinstance(result["errorCode"], str) is True + assert result.keys() == GetTaskResultResponseSer().to_dict().keys() + + def test_wrong_base64(self): + instance = BoundingBoxCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY) + result = instance.captcha_handler(captcha_base64=self.get_random_string(length=50).encode(encoding="UTF-8")) + assert isinstance(result, dict) is True + assert result["errorId"] == 15 + assert result["taskId"] is None + + async def test_aio_wrong_link(self): + instance = BoundingBoxCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY) + result = await instance.aio_captcha_handler(captcha_link=self.get_random_string(length=50)) + assert isinstance(result, dict) is True + assert result["errorId"] == 12 + assert isinstance(result["errorCode"], str) is True + assert result.keys() == GetTaskResultResponseSer().to_dict().keys() + + async def test_aio_wrong_base64(self): + instance = BoundingBoxCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY) + result = await instance.aio_captcha_handler( + captcha_base64=self.get_random_string(length=50).encode(encoding="UTF-8") + ) + assert isinstance(result, dict) is True + assert result["errorId"] == 15 + assert result["taskId"] is None From 4e91c08c3d5d5147f019d3c953ec1dfd104a947c Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 13 Dec 2023 20:16:47 +0300 Subject: [PATCH 11/32] Update bounding_box_captcha.py --- src/python_rucaptcha/bounding_box_captcha.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/python_rucaptcha/bounding_box_captcha.py b/src/python_rucaptcha/bounding_box_captcha.py index 19cb29b6..d4279816 100644 --- a/src/python_rucaptcha/bounding_box_captcha.py +++ b/src/python_rucaptcha/bounding_box_captcha.py @@ -9,6 +9,7 @@ class BoundingBoxCaptcha(BaseCaptcha): def __init__( self, + comment: str = "Draw Around Bounding Box", save_format: Union[str, SaveFormatsEnm] = SaveFormatsEnm.TEMP, img_clearing: bool = True, img_path: str = "PythonRuCaptchaBoundingBox", @@ -232,6 +233,7 @@ def __init__( self.save_format = save_format self.img_clearing = img_clearing self.img_path = img_path + self.create_task_payload["task"].update({"comment": comment}) def captcha_handler( self, From e14f3f8c1aaae78ea30aa5daf4c494695b382c40 Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 13 Dec 2023 20:16:53 +0300 Subject: [PATCH 12/32] Create test_draw_around.py --- tests/test_draw_around.py | 160 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 tests/test_draw_around.py diff --git a/tests/test_draw_around.py b/tests/test_draw_around.py new file mode 100644 index 00000000..77fcbac1 --- /dev/null +++ b/tests/test_draw_around.py @@ -0,0 +1,160 @@ +import pytest + +from tests.conftest import BaseTest +from python_rucaptcha.core.enums import SaveFormatsEnm, DrawAroundCaptchaEnm +from python_rucaptcha.core.serializer import GetTaskResultResponseSer +from python_rucaptcha.draw_around_captcha import DrawAroundCaptcha + + +class BaseImageCaptcha(BaseTest): + captcha_file = "src/examples/bounding_box_start.png" + + +class TestDrawAroundCaptchaCaptcha(BaseImageCaptcha): + kwargs_params = { + "comment": "None", + "imgInstructions": "None", + } + """ + Success tests + """ + + def test_methods_exists(self): + assert "captcha_handler" in DrawAroundCaptcha.__dict__.keys() + assert "aio_captcha_handler" in DrawAroundCaptcha.__dict__.keys() + + @pytest.mark.parametrize("img_clearing", (True, False)) + @pytest.mark.parametrize("save_format", SaveFormatsEnm.list_values()) + def test_args(self, save_format: str, img_clearing: bool): + instance = DrawAroundCaptcha( + rucaptcha_key=self.RUCAPTCHA_KEY, + img_clearing=img_clearing, + save_format=save_format, + ) + assert instance.create_task_payload["clientKey"] == self.RUCAPTCHA_KEY + assert instance.create_task_payload["task"]["type"] == DrawAroundCaptchaEnm.DrawAroundTask + assert instance.save_format == save_format + assert instance.img_clearing == img_clearing + + def test_kwargs(self): + instance = DrawAroundCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY, **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())) + + @pytest.mark.parametrize("save_format", SaveFormatsEnm.list_values()) + def test_basic_file(self, save_format): + instance = DrawAroundCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY, save_format=save_format) + result = instance.captcha_handler(captcha_file=self.captcha_file) + + assert isinstance(result, dict) is True + + if not result["errorId"]: + assert result["status"] == "ready" + assert isinstance(result["solution"]["text"], str) is True + assert isinstance(result["taskId"], int) is True + else: + assert result["errorId"] in (1, 12) + assert result["errorCode"] == "ERROR_CAPTCHA_UNSOLVABLE" + + @pytest.mark.parametrize("save_format", SaveFormatsEnm.list_values()) + def test_basic_base64(self, save_format): + instance = DrawAroundCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY, save_format=save_format) + + with open(self.captcha_file, "rb") as f: + result = instance.captcha_handler(captcha_base64=f.read()) + + assert isinstance(result, dict) is True + + if not result["errorId"]: + assert result["status"] == "ready" + assert isinstance(result["solution"]["text"], str) is True + 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() + + @pytest.mark.parametrize("save_format", SaveFormatsEnm.list_values()) + async def test_aio_basic_file(self, save_format): + instance = DrawAroundCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY, save_format=save_format) + + result = await instance.aio_captcha_handler(captcha_file=self.captcha_file) + assert isinstance(result, dict) is True + + if not result["errorId"]: + assert result["status"] == "ready" + assert isinstance(result["solution"]["text"], str) is True + 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() + + @pytest.mark.parametrize("save_format", SaveFormatsEnm.list_values()) + async def test_aio_basic_base64(self, save_format): + instance = DrawAroundCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY, save_format=save_format) + + with open(self.captcha_file, "rb") as f: + result = await instance.aio_captcha_handler(captcha_base64=f.read()) + + assert isinstance(result, dict) is True + if not result["errorId"]: + assert result["status"] == "ready" + assert isinstance(result["solution"]["text"], str) is True + 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() + + """ + Fail tests + """ + + def test_no_captcha(self): + instance = DrawAroundCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY) + result = instance.captcha_handler() + assert isinstance(result, dict) is True + assert result["errorId"] == 12 + assert isinstance(result["errorCode"], str) is True + assert result.keys() == GetTaskResultResponseSer().to_dict().keys() + + async def test_aio_no_captcha(self): + instance = DrawAroundCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY) + result = await instance.aio_captcha_handler() + assert isinstance(result, dict) is True + assert result["errorId"] == 12 + assert isinstance(result["errorCode"], str) is True + assert result.keys() == GetTaskResultResponseSer().to_dict().keys() + + def test_wrong_link(self): + instance = DrawAroundCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY) + result = instance.captcha_handler(captcha_link=self.get_random_string(length=50)) + assert isinstance(result, dict) is True + assert result["errorId"] == 12 + assert isinstance(result["errorCode"], str) is True + assert result.keys() == GetTaskResultResponseSer().to_dict().keys() + + def test_wrong_base64(self): + instance = DrawAroundCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY) + result = instance.captcha_handler(captcha_base64=self.get_random_string(length=50).encode(encoding="UTF-8")) + assert isinstance(result, dict) is True + assert result["errorId"] == 15 + assert result["taskId"] is None + + async def test_aio_wrong_link(self): + instance = DrawAroundCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY) + result = await instance.aio_captcha_handler(captcha_link=self.get_random_string(length=50)) + assert isinstance(result, dict) is True + assert result["errorId"] == 12 + assert isinstance(result["errorCode"], str) is True + assert result.keys() == GetTaskResultResponseSer().to_dict().keys() + + async def test_aio_wrong_base64(self): + instance = DrawAroundCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY) + result = await instance.aio_captcha_handler( + captcha_base64=self.get_random_string(length=50).encode(encoding="UTF-8") + ) + assert isinstance(result, dict) is True + assert result["errorId"] == 15 + assert result["taskId"] is None From 3343d7dde5729df52c2408e75ff9c4957cae384e Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 13 Dec 2023 20:16:56 +0300 Subject: [PATCH 13/32] Create draw_around_captcha.py --- src/python_rucaptcha/draw_around_captcha.py | 359 ++++++++++++++++++++ 1 file changed, 359 insertions(+) create mode 100644 src/python_rucaptcha/draw_around_captcha.py diff --git a/src/python_rucaptcha/draw_around_captcha.py b/src/python_rucaptcha/draw_around_captcha.py new file mode 100644 index 00000000..d21492f2 --- /dev/null +++ b/src/python_rucaptcha/draw_around_captcha.py @@ -0,0 +1,359 @@ +import shutil +import logging +from typing import Union, Optional + +from .core.base import BaseCaptcha +from .core.enums import SaveFormatsEnm, DrawAroundCaptchaEnm + + +class DrawAroundCaptcha(BaseCaptcha): + def __init__( + self, + comment: str = "Draw Around Object", + save_format: Union[str, SaveFormatsEnm] = SaveFormatsEnm.TEMP, + img_clearing: bool = True, + img_path: str = "PythonRuCaptchaBoundingBox", + *args, + **kwargs, + ): + """ + The class is used to work with Bounding Box. + + Args: + rucaptcha_key: User API key + save_format: The format in which the image will be saved, or as a temporary file - 'temp', + or as a regular image to a folder created by the library - 'const'. + img_clearing: True - delete file after solution, False - don't delete file after solution + img_path: Folder to save captcha images + kwargs: Additional not required params for this captcha type + + Examples: + >>> DrawAroundCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122", + ... ).captcha_handler(captcha_link="https://rucaptcha.com/dist/web/99581b9d446a509a0a01954438a5e36a.jpg") + { + "errorId":0, + "status":"ready", + "solution":{ + "canvas":[ + [{ + "x":146, + "y":90 + }], + [{ + "x":158, + "y":69 + }, + { + "x":159, + "y":69 + }] + ], + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + >>> DrawAroundCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122", + ... ).captcha_handler(captcha_file="src/examples/088636.png") + { + "errorId":0, + "status":"ready", + "solution":{ + "canvas":[ + [{ + "x":146, + "y":90 + }], + [{ + "x":158, + "y":69 + }, + { + "x":159, + "y":69 + }] + ], + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + >>> with open("src/examples/088636.png", "rb") as f: + ... file_data = f.read() + >>> DrawAroundCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122" + ... ).captcha_handler(captcha_base64=file_data) + { + "errorId":0, + "status":"ready", + "solution":{ + "canvas":[ + [{ + "x":146, + "y":90 + }], + [{ + "x":158, + "y":69 + }, + { + "x":159, + "y":69 + }] + ], + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + >>> await DrawAroundCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122", + ... ).aio_captcha_handler(captcha_link="https://rucaptcha.com/dist/web/99581b9d446a509a0a01954438a5e36a.jpg") + { + "errorId":0, + "status":"ready", + "solution":{ + "canvas":[ + [{ + "x":146, + "y":90 + }], + [{ + "x":158, + "y":69 + }, + { + "x":159, + "y":69 + }] + ], + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + >>> await DrawAroundCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122", + ... ).aio_captcha_handler(captcha_file="src/examples/088636.png") + { + "errorId":0, + "status":"ready", + "solution":{ + "canvas":[ + [{ + "x":146, + "y":90 + }], + [{ + "x":158, + "y":69 + }, + { + "x":159, + "y":69 + }] + ], + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + Death Captcha + + >>> DrawAroundCaptcha(rucaptcha_key="some_username:some_password", + ... service_type="deathbycaptcha" + ... ).captcha_handler(captcha_file="src/examples/088636.jpg") + { + "errorId":0, + "status":"ready", + "solution":{ + "canvas":[ + [{ + "x":146, + "y":90 + }], + [{ + "x":158, + "y":69 + }, + { + "x":159, + "y":69 + }] + ], + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + >>> await DrawAroundCaptcha(rucaptcha_key="some_username:some_password", + ... service_type="deathbycaptcha" + ... ).aio_captcha_handler(captcha_link="https://rucaptcha.com/dist/web/99581b9d446a509a0a01954438a5e36a.jpg") + { + "errorId":0, + "status":"ready", + "solution":{ + "canvas":[ + [{ + "x":146, + "y":90 + }], + [{ + "x":158, + "y":69 + }, + { + "x":159, + "y":69 + }] + ], + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + >>> with open("src/examples/088636.png", "rb") as f: + ... file_data = f.read() + >>> await DrawAroundCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122" + ... ).aio_captcha_handler(captcha_base64=file_data) + { + "errorId":0, + "status":"ready", + "solution":{ + "canvas":[ + [{ + "x":146, + "y":90 + }], + [{ + "x":158, + "y":69 + }, + { + "x":159, + "y":69 + }] + ], + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + Returns: + Dict with full server response + + Notes: + https://2captcha.com/api-docs/draw-around + + https://rucaptcha.com/api-docs/draw-around + """ + super().__init__(method=DrawAroundCaptchaEnm.DrawAroundTask.value, *args, **kwargs) + + self.save_format = save_format + self.img_clearing = img_clearing + self.img_path = img_path + + self.create_task_payload["task"].update({"comment": comment}) + + def captcha_handler( + self, + captcha_link: Optional[str] = None, + captcha_file: Optional[str] = None, + captcha_base64: Optional[bytes] = None, + **kwargs, + ) -> dict: + """ + Sync solving method + + Args: + captcha_link: Captcha image URL + captcha_file: Captcha image file path + captcha_base64: Captcha image BASE64 info + kwargs: additional params for `requests` library + + Returns: + Dict with full server response + + Notes: + Check class docstirng for more info + """ + 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, + **kwargs, + ) + logging.warning(f"{self.result = }") + logging.warning(f"{self.create_task_payload = }") + if not self.result.errorId: + return self._processing_response(**kwargs) + return self.result.to_dict() + + async def aio_captcha_handler( + self, + captcha_link: Optional[str] = None, + captcha_file: Optional[str] = None, + captcha_base64: Optional[bytes] = None, + **kwargs, + ) -> dict: + """ + Async solving method + + Args: + captcha_link: Captcha image URL + captcha_file: Captcha image file path + captcha_base64: Captcha image BASE64 info + kwargs: additional params for `aiohttp` library + + Returns: + Dict with full server response + + Notes: + Check class docstirng for more info + """ + 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, + **kwargs, + ) + if not self.result.errorId: + return await self._aio_processing_response() + return self.result.to_dict() + + def __del__(self): + if self.save_format == SaveFormatsEnm.CONST.value and self.img_clearing: + shutil.rmtree(self.img_path) From 9f5b0cc5227891eab695897f72464842447627c0 Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 13 Dec 2023 20:16:58 +0300 Subject: [PATCH 14/32] Update enums.py --- src/python_rucaptcha/core/enums.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/python_rucaptcha/core/enums.py b/src/python_rucaptcha/core/enums.py index 891490df..dfbc11be 100644 --- a/src/python_rucaptcha/core/enums.py +++ b/src/python_rucaptcha/core/enums.py @@ -132,3 +132,7 @@ class MTCaptchaEnm(str, MyEnum): class BoundingBoxCaptchaEnm(str, MyEnum): BoundingBoxTask = "BoundingBoxTask" + + +class DrawAroundCaptchaEnm(str, MyEnum): + DrawAroundTask = "DrawAroundTask" From bc8f581cf30231d054fd2df3b22f5e658b25fa81 Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 13 Dec 2023 20:21:30 +0300 Subject: [PATCH 15/32] Update test_bounding_box.py --- tests/test_bounding_box.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_bounding_box.py b/tests/test_bounding_box.py index fcf27c3a..c9f18ab7 100644 --- a/tests/test_bounding_box.py +++ b/tests/test_bounding_box.py @@ -50,7 +50,7 @@ def test_basic_file(self, save_format): if not result["errorId"]: assert result["status"] == "ready" - assert isinstance(result["solution"]["text"], str) is True + assert isinstance(result["solution"], dict) is True assert isinstance(result["taskId"], int) is True else: assert result["errorId"] in (1, 12) @@ -67,7 +67,7 @@ def test_basic_base64(self, save_format): if not result["errorId"]: assert result["status"] == "ready" - assert isinstance(result["solution"]["text"], str) is True + assert isinstance(result["solution"], dict) is True assert isinstance(result["taskId"], int) is True else: assert result["errorId"] in (1, 12) @@ -83,7 +83,7 @@ async def test_aio_basic_file(self, save_format): if not result["errorId"]: assert result["status"] == "ready" - assert isinstance(result["solution"]["text"], str) is True + assert isinstance(result["solution"], dict) is True assert isinstance(result["taskId"], int) is True else: assert result["errorId"] in (1, 12) @@ -100,7 +100,7 @@ async def test_aio_basic_base64(self, save_format): assert isinstance(result, dict) is True if not result["errorId"]: assert result["status"] == "ready" - assert isinstance(result["solution"]["text"], str) is True + assert isinstance(result["solution"], dict) is True assert isinstance(result["taskId"], int) is True else: assert result["errorId"] in (1, 12) From 4130d0fd17d39180cced89691955025103f7d6e6 Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 13 Dec 2023 20:21:32 +0300 Subject: [PATCH 16/32] Update test_draw_around.py --- tests/test_draw_around.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_draw_around.py b/tests/test_draw_around.py index 77fcbac1..965f0a8a 100644 --- a/tests/test_draw_around.py +++ b/tests/test_draw_around.py @@ -50,7 +50,7 @@ def test_basic_file(self, save_format): if not result["errorId"]: assert result["status"] == "ready" - assert isinstance(result["solution"]["text"], str) is True + assert isinstance(result["solution"], dict) is True assert isinstance(result["taskId"], int) is True else: assert result["errorId"] in (1, 12) @@ -67,7 +67,7 @@ def test_basic_base64(self, save_format): if not result["errorId"]: assert result["status"] == "ready" - assert isinstance(result["solution"]["text"], str) is True + assert isinstance(result["solution"], dict) is True assert isinstance(result["taskId"], int) is True else: assert result["errorId"] in (1, 12) @@ -83,7 +83,7 @@ async def test_aio_basic_file(self, save_format): if not result["errorId"]: assert result["status"] == "ready" - assert isinstance(result["solution"]["text"], str) is True + assert isinstance(result["solution"], dict) is True assert isinstance(result["taskId"], int) is True else: assert result["errorId"] in (1, 12) @@ -100,7 +100,7 @@ async def test_aio_basic_base64(self, save_format): assert isinstance(result, dict) is True if not result["errorId"]: assert result["status"] == "ready" - assert isinstance(result["solution"]["text"], str) is True + assert isinstance(result["solution"], dict) is True assert isinstance(result["taskId"], int) is True else: assert result["errorId"] in (1, 12) From a58e4727c210204453ec4ee5b1dcf89320462434 Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 13 Dec 2023 20:23:38 +0300 Subject: [PATCH 17/32] Update test_cutcaptcha.py --- tests/test_cutcaptcha.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/test_cutcaptcha.py b/tests/test_cutcaptcha.py index bfc6dc9a..1b4d4b81 100644 --- a/tests/test_cutcaptcha.py +++ b/tests/test_cutcaptcha.py @@ -65,8 +65,7 @@ def test_basic_data(self): assert isinstance(result, dict) is True if not result["errorId"]: - assert result["status"] == "ready" - assert isinstance(result["solution"], dict) is True + assert result["status"] in ("ready", "processing") assert isinstance(result["taskId"], int) is True else: assert result["errorId"] in (1, 12) @@ -87,8 +86,7 @@ async def test_aio_basic_data(self): assert isinstance(result, dict) is True if not result["errorId"]: - assert result["status"] == "ready" - assert isinstance(result["solution"], dict) is True + assert result["status"] in ("ready", "processing") assert isinstance(result["taskId"], int) is True else: assert result["errorId"] in (1, 12) From d2cf922656ecb463ab52f41c760319d8500c428f Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 13 Dec 2023 20:28:31 +0300 Subject: [PATCH 18/32] Update test_draw_around.py --- tests/test_draw_around.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_draw_around.py b/tests/test_draw_around.py index 965f0a8a..cfc51b05 100644 --- a/tests/test_draw_around.py +++ b/tests/test_draw_around.py @@ -10,7 +10,7 @@ class BaseImageCaptcha(BaseTest): captcha_file = "src/examples/bounding_box_start.png" -class TestDrawAroundCaptchaCaptcha(BaseImageCaptcha): +class TestDrawAroundCaptcha(BaseImageCaptcha): kwargs_params = { "comment": "None", "imgInstructions": "None", From 6da25745625afbdbc6a5de761daa67f7234e1a89 Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 13 Dec 2023 20:28:33 +0300 Subject: [PATCH 19/32] Create test_coordinates.py --- tests/test_coordinates.py | 160 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 tests/test_coordinates.py diff --git a/tests/test_coordinates.py b/tests/test_coordinates.py new file mode 100644 index 00000000..93daf013 --- /dev/null +++ b/tests/test_coordinates.py @@ -0,0 +1,160 @@ +import pytest + +from tests.conftest import BaseTest +from python_rucaptcha.core.enums import SaveFormatsEnm, CoordinatesCaptchaEnm +from python_rucaptcha.core.serializer import GetTaskResultResponseSer +from python_rucaptcha.coordinates_captcha import CoordinatesCaptcha + + +class BaseImageCaptcha(BaseTest): + captcha_file = "src/examples/bounding_box_start.png" + + +class TestCoordinatesCaptcha(BaseImageCaptcha): + kwargs_params = { + "comment": "None", + "imgInstructions": "None", + } + """ + Success tests + """ + + def test_methods_exists(self): + assert "captcha_handler" in CoordinatesCaptcha.__dict__.keys() + assert "aio_captcha_handler" in CoordinatesCaptcha.__dict__.keys() + + @pytest.mark.parametrize("img_clearing", (True, False)) + @pytest.mark.parametrize("save_format", SaveFormatsEnm.list_values()) + def test_args(self, save_format: str, img_clearing: bool): + instance = CoordinatesCaptcha( + rucaptcha_key=self.RUCAPTCHA_KEY, + img_clearing=img_clearing, + save_format=save_format, + ) + assert instance.create_task_payload["clientKey"] == self.RUCAPTCHA_KEY + assert instance.create_task_payload["task"]["type"] == CoordinatesCaptchaEnm.CoordinatesTask + assert instance.save_format == save_format + assert instance.img_clearing == img_clearing + + def test_kwargs(self): + instance = CoordinatesCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY, **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())) + + @pytest.mark.parametrize("save_format", SaveFormatsEnm.list_values()) + def test_basic_file(self, save_format): + instance = CoordinatesCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY, save_format=save_format) + result = instance.captcha_handler(captcha_file=self.captcha_file) + + assert isinstance(result, dict) is True + + if not result["errorId"]: + assert result["status"] == "ready" + assert isinstance(result["solution"], dict) is True + assert isinstance(result["taskId"], int) is True + else: + assert result["errorId"] in (1, 12) + assert result["errorCode"] == "ERROR_CAPTCHA_UNSOLVABLE" + + @pytest.mark.parametrize("save_format", SaveFormatsEnm.list_values()) + def test_basic_base64(self, save_format): + instance = CoordinatesCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY, save_format=save_format) + + with open(self.captcha_file, "rb") as f: + result = instance.captcha_handler(captcha_base64=f.read()) + + assert isinstance(result, dict) is True + + if not result["errorId"]: + assert result["status"] == "ready" + assert isinstance(result["solution"], dict) is True + 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() + + @pytest.mark.parametrize("save_format", SaveFormatsEnm.list_values()) + async def test_aio_basic_file(self, save_format): + instance = CoordinatesCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY, save_format=save_format) + + result = await instance.aio_captcha_handler(captcha_file=self.captcha_file) + assert isinstance(result, dict) is True + + if not result["errorId"]: + assert result["status"] == "ready" + assert isinstance(result["solution"], dict) is True + 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() + + @pytest.mark.parametrize("save_format", SaveFormatsEnm.list_values()) + async def test_aio_basic_base64(self, save_format): + instance = CoordinatesCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY, save_format=save_format) + + with open(self.captcha_file, "rb") as f: + result = await instance.aio_captcha_handler(captcha_base64=f.read()) + + assert isinstance(result, dict) is True + if not result["errorId"]: + assert result["status"] == "ready" + assert isinstance(result["solution"], dict) is True + 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() + + """ + Fail tests + """ + + def test_no_captcha(self): + instance = CoordinatesCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY) + result = instance.captcha_handler() + assert isinstance(result, dict) is True + assert result["errorId"] == 12 + assert isinstance(result["errorCode"], str) is True + assert result.keys() == GetTaskResultResponseSer().to_dict().keys() + + async def test_aio_no_captcha(self): + instance = CoordinatesCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY) + result = await instance.aio_captcha_handler() + assert isinstance(result, dict) is True + assert result["errorId"] == 12 + assert isinstance(result["errorCode"], str) is True + assert result.keys() == GetTaskResultResponseSer().to_dict().keys() + + def test_wrong_link(self): + instance = CoordinatesCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY) + result = instance.captcha_handler(captcha_link=self.get_random_string(length=50)) + assert isinstance(result, dict) is True + assert result["errorId"] == 12 + assert isinstance(result["errorCode"], str) is True + assert result.keys() == GetTaskResultResponseSer().to_dict().keys() + + def test_wrong_base64(self): + instance = CoordinatesCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY) + result = instance.captcha_handler(captcha_base64=self.get_random_string(length=50).encode(encoding="UTF-8")) + assert isinstance(result, dict) is True + assert result["errorId"] == 15 + assert result["taskId"] is None + + async def test_aio_wrong_link(self): + instance = CoordinatesCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY) + result = await instance.aio_captcha_handler(captcha_link=self.get_random_string(length=50)) + assert isinstance(result, dict) is True + assert result["errorId"] == 12 + assert isinstance(result["errorCode"], str) is True + assert result.keys() == GetTaskResultResponseSer().to_dict().keys() + + async def test_aio_wrong_base64(self): + instance = CoordinatesCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY) + result = await instance.aio_captcha_handler( + captcha_base64=self.get_random_string(length=50).encode(encoding="UTF-8") + ) + assert isinstance(result, dict) is True + assert result["errorId"] == 15 + assert result["taskId"] is None From e24c959b3677de67f21c1b18799e7043c90b5b7d Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 13 Dec 2023 20:28:35 +0300 Subject: [PATCH 20/32] Create coordinates_captcha.py --- src/python_rucaptcha/coordinates_captcha.py | 295 ++++++++++++++++++++ 1 file changed, 295 insertions(+) create mode 100644 src/python_rucaptcha/coordinates_captcha.py diff --git a/src/python_rucaptcha/coordinates_captcha.py b/src/python_rucaptcha/coordinates_captcha.py new file mode 100644 index 00000000..981981ed --- /dev/null +++ b/src/python_rucaptcha/coordinates_captcha.py @@ -0,0 +1,295 @@ +import shutil +import logging +from typing import Union, Optional + +from .core.base import BaseCaptcha +from .core.enums import SaveFormatsEnm, CoordinatesCaptchaEnm + + +class CoordinatesCaptcha(BaseCaptcha): + def __init__( + self, + comment: str = "Draw Coordinates", + save_format: Union[str, SaveFormatsEnm] = SaveFormatsEnm.TEMP, + img_clearing: bool = True, + img_path: str = "PythonRuCaptchaBoundingBox", + *args, + **kwargs, + ): + """ + The class is used to work with Bounding Box. + + Args: + rucaptcha_key: User API key + save_format: The format in which the image will be saved, or as a temporary file - 'temp', + or as a regular image to a folder created by the library - 'const'. + img_clearing: True - delete file after solution, False - don't delete file after solution + img_path: Folder to save captcha images + kwargs: Additional not required params for this captcha type + + Examples: + >>> CoordinatesCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122", + ... ).captcha_handler(captcha_link="https://rucaptcha.com/dist/web/99581b9d446a509a0a01954438a5e36a.jpg") + { + "errorId":0, + "status":"ready", + "solution":{ + "coordinates":[ + { + "x": 358, + "y": 268 + } + ], + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + >>> CoordinatesCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122", + ... ).captcha_handler(captcha_file="src/examples/088636.png") + { + "errorId":0, + "status":"ready", + "solution":{ + "coordinates":[ + { + "x": 358, + "y": 268 + } + ], + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + >>> with open("src/examples/088636.png", "rb") as f: + ... file_data = f.read() + >>> CoordinatesCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122" + ... ).captcha_handler(captcha_base64=file_data) + { + "errorId":0, + "status":"ready", + "solution":{ + "coordinates":[ + { + "x": 358, + "y": 268 + } + ], + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + >>> await CoordinatesCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122", + ... ).aio_captcha_handler(captcha_link="https://rucaptcha.com/dist/web/99581b9d446a509a0a01954438a5e36a.jpg") + { + "errorId":0, + "status":"ready", + "solution":{ + "coordinates":[ + { + "x": 358, + "y": 268 + } + ], + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + >>> await CoordinatesCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122", + ... ).aio_captcha_handler(captcha_file="src/examples/088636.png") + { + "errorId":0, + "status":"ready", + "solution":{ + "coordinates":[ + { + "x": 358, + "y": 268 + } + ], + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + Death Captcha + + >>> CoordinatesCaptcha(rucaptcha_key="some_username:some_password", + ... service_type="deathbycaptcha" + ... ).captcha_handler(captcha_file="src/examples/088636.jpg") + { + "errorId":0, + "status":"ready", + "solution":{ + "coordinates":[ + { + "x": 358, + "y": 268 + } + ], + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + >>> await CoordinatesCaptcha(rucaptcha_key="some_username:some_password", + ... service_type="deathbycaptcha" + ... ).aio_captcha_handler(captcha_link="https://rucaptcha.com/dist/web/99581b9d446a509a0a01954438a5e36a.jpg") + { + "errorId":0, + "status":"ready", + "solution":{ + "coordinates":[ + { + "x": 358, + "y": 268 + } + ], + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + >>> with open("src/examples/088636.png", "rb") as f: + ... file_data = f.read() + >>> await CoordinatesCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122" + ... ).aio_captcha_handler(captcha_base64=file_data) + { + "errorId":0, + "status":"ready", + "solution":{ + "coordinates":[ + { + "x": 358, + "y": 268 + } + ], + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + Returns: + Dict with full server response + + Notes: + https://2captcha.com/api-docs/coordinates + + https://rucaptcha.com/api-docs/coordinates + """ + super().__init__(method=CoordinatesCaptchaEnm.CoordinatesTask.value, *args, **kwargs) + + self.save_format = save_format + self.img_clearing = img_clearing + self.img_path = img_path + + self.create_task_payload["task"].update({"comment": comment}) + + def captcha_handler( + self, + captcha_link: Optional[str] = None, + captcha_file: Optional[str] = None, + captcha_base64: Optional[bytes] = None, + **kwargs, + ) -> dict: + """ + Sync solving method + + Args: + captcha_link: Captcha image URL + captcha_file: Captcha image file path + captcha_base64: Captcha image BASE64 info + kwargs: additional params for `requests` library + + Returns: + Dict with full server response + + Notes: + Check class docstirng for more info + """ + 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, + **kwargs, + ) + logging.warning(f"{self.result = }") + logging.warning(f"{self.create_task_payload = }") + if not self.result.errorId: + return self._processing_response(**kwargs) + return self.result.to_dict() + + async def aio_captcha_handler( + self, + captcha_link: Optional[str] = None, + captcha_file: Optional[str] = None, + captcha_base64: Optional[bytes] = None, + **kwargs, + ) -> dict: + """ + Async solving method + + Args: + captcha_link: Captcha image URL + captcha_file: Captcha image file path + captcha_base64: Captcha image BASE64 info + kwargs: additional params for `aiohttp` library + + Returns: + Dict with full server response + + Notes: + Check class docstirng for more info + """ + 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, + **kwargs, + ) + if not self.result.errorId: + return await self._aio_processing_response() + return self.result.to_dict() + + def __del__(self): + if self.save_format == SaveFormatsEnm.CONST.value and self.img_clearing: + shutil.rmtree(self.img_path) From 45043322540ccca6605f8fcd1241199223bb179e Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 13 Dec 2023 20:28:38 +0300 Subject: [PATCH 21/32] Update enums.py --- src/python_rucaptcha/core/enums.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/python_rucaptcha/core/enums.py b/src/python_rucaptcha/core/enums.py index dfbc11be..367689c8 100644 --- a/src/python_rucaptcha/core/enums.py +++ b/src/python_rucaptcha/core/enums.py @@ -136,3 +136,7 @@ class BoundingBoxCaptchaEnm(str, MyEnum): class DrawAroundCaptchaEnm(str, MyEnum): DrawAroundTask = "DrawAroundTask" + + +class CoordinatesCaptchaEnm(str, MyEnum): + CoordinatesTask = "CoordinatesTask" From 224198556adf693675c9fabc8e1dd17dd5195355 Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 13 Dec 2023 20:28:40 +0300 Subject: [PATCH 22/32] Update test_bounding_box.py --- tests/test_bounding_box.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/test_bounding_box.py b/tests/test_bounding_box.py index c9f18ab7..07a05a5a 100644 --- a/tests/test_bounding_box.py +++ b/tests/test_bounding_box.py @@ -6,11 +6,8 @@ from python_rucaptcha.bounding_box_captcha import BoundingBoxCaptcha -class BaseImageCaptcha(BaseTest): +class TestBoundingBoxCaptcha(BaseTest): captcha_file = "src/examples/bounding_box_start.png" - - -class TestImageCaptcha(BaseImageCaptcha): kwargs_params = { "comment": "None", "imgInstructions": "None", From 41cac8f7b5082bd1b1517427106936920f3ac25b Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 13 Dec 2023 20:42:59 +0300 Subject: [PATCH 23/32] added grid captcha --- src/examples/grid.png | Bin 0 -> 243060 bytes src/python_rucaptcha/core/enums.py | 4 + src/python_rucaptcha/grid_captcha.py | 271 +++++++++++++++++++++++++++ tests/test_coordinates.py | 5 +- tests/test_draw_around.py | 5 +- tests/test_grid.py | 159 ++++++++++++++++ tests/test_image.py | 4 +- 7 files changed, 437 insertions(+), 11 deletions(-) create mode 100644 src/examples/grid.png create mode 100644 src/python_rucaptcha/grid_captcha.py create mode 100644 tests/test_grid.py diff --git a/src/examples/grid.png b/src/examples/grid.png new file mode 100644 index 0000000000000000000000000000000000000000..dd2f7205db5a451d499fc4faec26d606a5ec0c9c GIT binary patch literal 243060 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 H&13&RqUSIW literal 0 HcmV?d00001 diff --git a/src/python_rucaptcha/core/enums.py b/src/python_rucaptcha/core/enums.py index 367689c8..1ca00112 100644 --- a/src/python_rucaptcha/core/enums.py +++ b/src/python_rucaptcha/core/enums.py @@ -140,3 +140,7 @@ class DrawAroundCaptchaEnm(str, MyEnum): class CoordinatesCaptchaEnm(str, MyEnum): CoordinatesTask = "CoordinatesTask" + + +class GridCaptchaEnm(str, MyEnum): + GridTask = "GridTask" diff --git a/src/python_rucaptcha/grid_captcha.py b/src/python_rucaptcha/grid_captcha.py new file mode 100644 index 00000000..e9ced02c --- /dev/null +++ b/src/python_rucaptcha/grid_captcha.py @@ -0,0 +1,271 @@ +import shutil +import logging +from typing import Union, Optional + +from .core.base import BaseCaptcha +from .core.enums import GridCaptchaEnm, SaveFormatsEnm + + +class GridCaptcha(BaseCaptcha): + def __init__( + self, + comment: str = "Check elements at Grid", + save_format: Union[str, SaveFormatsEnm] = SaveFormatsEnm.TEMP, + img_clearing: bool = True, + img_path: str = "PythonRuCaptchaBoundingBox", + *args, + **kwargs, + ): + """ + The class is used to work with Bounding Box. + + Args: + rucaptcha_key: User API key + save_format: The format in which the image will be saved, or as a temporary file - 'temp', + or as a regular image to a folder created by the library - 'const'. + img_clearing: True - delete file after solution, False - don't delete file after solution + img_path: Folder to save captcha images + kwargs: Additional not required params for this captcha type + + Examples: + >>> GridCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122", + ... ).captcha_handler(captcha_link="https://rucaptcha.com/dist/web/99581b9d446a509a0a01954438a5e36a.jpg") + { + "errorId":0, + "status":"ready", + "solution":{ + "click":[ + 1, 5, 9 + ], + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + >>> GridCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122", + ... ).captcha_handler(captcha_file="src/examples/grid.png") + { + "errorId":0, + "status":"ready", + "solution":{ + "click":[ + 1, 5, 9 + ], + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + >>> with open("src/examples/grid.png", "rb") as f: + ... file_data = f.read() + >>> GridCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122" + ... ).captcha_handler(captcha_base64=file_data) + { + "errorId":0, + "status":"ready", + "solution":{ + "click":[ + 1, 5, 9 + ], + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + >>> await GridCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122", + ... ).aio_captcha_handler(captcha_link="https://rucaptcha.com/dist/web/99581b9d446a509a0a01954438a5e36a.jpg") + { + "errorId":0, + "status":"ready", + "solution":{ + "click":[ + 1, 5, 9 + ], + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + >>> await GridCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122", + ... ).aio_captcha_handler(captcha_file="src/examples/grid.png") + { + "errorId":0, + "status":"ready", + "solution":{ + "click":[ + 1, 5, 9 + ], + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + Death Captcha + + >>> GridCaptcha(rucaptcha_key="some_username:some_password", + ... service_type="deathbycaptcha" + ... ).captcha_handler(captcha_file="src/examples/grid.jpg") + { + "errorId":0, + "status":"ready", + "solution":{ + "click":[ + 1, 5, 9 + ], + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + >>> await GridCaptcha(rucaptcha_key="some_username:some_password", + ... service_type="deathbycaptcha" + ... ).aio_captcha_handler(captcha_link="https://rucaptcha.com/dist/web/99581b9d446a509a0a01954438a5e36a.jpg") + { + "errorId":0, + "status":"ready", + "solution":{ + "click":[ + 1, 5, 9 + ], + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + >>> with open("src/examples/grid.png", "rb") as f: + ... file_data = f.read() + >>> await GridCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122" + ... ).aio_captcha_handler(captcha_base64=file_data) + { + "errorId":0, + "status":"ready", + "solution":{ + "click":[ + 1, 5, 9 + ], + }, + "cost":0.033, + "ip":"46.53.241.91", + "createTime":1696730723, + "endTime":1696730723, + "solveCount":1, + "taskId":74708110322 + } + + Returns: + Dict with full server response + + Notes: + https://2captcha.com/api-docs/grid + + https://rucaptcha.com/api-docs/grid + """ + super().__init__(method=GridCaptchaEnm.GridTask.value, *args, **kwargs) + + self.save_format = save_format + self.img_clearing = img_clearing + self.img_path = img_path + + self.create_task_payload["task"].update({"comment": comment}) + + def captcha_handler( + self, + captcha_link: Optional[str] = None, + captcha_file: Optional[str] = None, + captcha_base64: Optional[bytes] = None, + **kwargs, + ) -> dict: + """ + Sync solving method + + Args: + captcha_link: Captcha image URL + captcha_file: Captcha image file path + captcha_base64: Captcha image BASE64 info + kwargs: additional params for `requests` library + + Returns: + Dict with full server response + + Notes: + Check class docstirng for more info + """ + 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, + **kwargs, + ) + logging.warning(f"{self.result = }") + logging.warning(f"{self.create_task_payload = }") + if not self.result.errorId: + return self._processing_response(**kwargs) + return self.result.to_dict() + + async def aio_captcha_handler( + self, + captcha_link: Optional[str] = None, + captcha_file: Optional[str] = None, + captcha_base64: Optional[bytes] = None, + **kwargs, + ) -> dict: + """ + Async solving method + + Args: + captcha_link: Captcha image URL + captcha_file: Captcha image file path + captcha_base64: Captcha image BASE64 info + kwargs: additional params for `aiohttp` library + + Returns: + Dict with full server response + + Notes: + Check class docstirng for more info + """ + 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, + **kwargs, + ) + if not self.result.errorId: + return await self._aio_processing_response() + return self.result.to_dict() + + def __del__(self): + if self.save_format == SaveFormatsEnm.CONST.value and self.img_clearing: + shutil.rmtree(self.img_path) diff --git a/tests/test_coordinates.py b/tests/test_coordinates.py index 93daf013..3114bf40 100644 --- a/tests/test_coordinates.py +++ b/tests/test_coordinates.py @@ -6,11 +6,8 @@ from python_rucaptcha.coordinates_captcha import CoordinatesCaptcha -class BaseImageCaptcha(BaseTest): +class TestCoordinatesCaptcha(BaseTest): captcha_file = "src/examples/bounding_box_start.png" - - -class TestCoordinatesCaptcha(BaseImageCaptcha): kwargs_params = { "comment": "None", "imgInstructions": "None", diff --git a/tests/test_draw_around.py b/tests/test_draw_around.py index cfc51b05..3098b414 100644 --- a/tests/test_draw_around.py +++ b/tests/test_draw_around.py @@ -6,11 +6,8 @@ from python_rucaptcha.draw_around_captcha import DrawAroundCaptcha -class BaseImageCaptcha(BaseTest): +class TestDrawAroundCaptcha(BaseTest): captcha_file = "src/examples/bounding_box_start.png" - - -class TestDrawAroundCaptcha(BaseImageCaptcha): kwargs_params = { "comment": "None", "imgInstructions": "None", diff --git a/tests/test_grid.py b/tests/test_grid.py new file mode 100644 index 00000000..27feb6cb --- /dev/null +++ b/tests/test_grid.py @@ -0,0 +1,159 @@ +import pytest + +from tests.conftest import BaseTest +from python_rucaptcha.core.enums import GridCaptchaEnm, SaveFormatsEnm +from python_rucaptcha.grid_captcha import GridCaptcha +from python_rucaptcha.core.serializer import GetTaskResultResponseSer + + +class TestGridCaptcha(BaseTest): + captcha_file = "src/examples/grid.png" + kwargs_params = { + "comment": "None", + "rows": 3, + "columns": 3, + "imgInstructions": "None", + } + """ + Success tests + """ + + def test_methods_exists(self): + assert "captcha_handler" in GridCaptcha.__dict__.keys() + assert "aio_captcha_handler" in GridCaptcha.__dict__.keys() + + @pytest.mark.parametrize("img_clearing", (True, False)) + @pytest.mark.parametrize("save_format", SaveFormatsEnm.list_values()) + def test_args(self, save_format: str, img_clearing: bool): + instance = GridCaptcha( + rucaptcha_key=self.RUCAPTCHA_KEY, + img_clearing=img_clearing, + save_format=save_format, + ) + assert instance.create_task_payload["clientKey"] == self.RUCAPTCHA_KEY + assert instance.create_task_payload["task"]["type"] == GridCaptchaEnm.GridTask + assert instance.save_format == save_format + assert instance.img_clearing == img_clearing + + def test_kwargs(self): + instance = GridCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY, **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())) + + @pytest.mark.parametrize("save_format", SaveFormatsEnm.list_values()) + def test_basic_file(self, save_format): + instance = GridCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY, save_format=save_format) + result = instance.captcha_handler(captcha_file=self.captcha_file) + + assert isinstance(result, dict) is True + + if not result["errorId"]: + assert result["status"] == "ready" + assert isinstance(result["solution"], dict) is True + assert isinstance(result["taskId"], int) is True + else: + assert result["errorId"] in (1, 12) + assert result["errorCode"] == "ERROR_CAPTCHA_UNSOLVABLE" + + @pytest.mark.parametrize("save_format", SaveFormatsEnm.list_values()) + def test_basic_base64(self, save_format): + instance = GridCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY, save_format=save_format) + + with open(self.captcha_file, "rb") as f: + result = instance.captcha_handler(captcha_base64=f.read()) + + assert isinstance(result, dict) is True + + if not result["errorId"]: + assert result["status"] == "ready" + assert isinstance(result["solution"], dict) is True + 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() + + @pytest.mark.parametrize("save_format", SaveFormatsEnm.list_values()) + async def test_aio_basic_file(self, save_format): + instance = GridCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY, save_format=save_format) + + result = await instance.aio_captcha_handler(captcha_file=self.captcha_file) + assert isinstance(result, dict) is True + + if not result["errorId"]: + assert result["status"] == "ready" + assert isinstance(result["solution"], dict) is True + 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() + + @pytest.mark.parametrize("save_format", SaveFormatsEnm.list_values()) + async def test_aio_basic_base64(self, save_format): + instance = GridCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY, save_format=save_format) + + with open(self.captcha_file, "rb") as f: + result = await instance.aio_captcha_handler(captcha_base64=f.read()) + + assert isinstance(result, dict) is True + if not result["errorId"]: + assert result["status"] == "ready" + assert isinstance(result["solution"], dict) is True + 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() + + """ + Fail tests + """ + + def test_no_captcha(self): + instance = GridCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY) + result = instance.captcha_handler() + assert isinstance(result, dict) is True + assert result["errorId"] == 12 + assert isinstance(result["errorCode"], str) is True + assert result.keys() == GetTaskResultResponseSer().to_dict().keys() + + async def test_aio_no_captcha(self): + instance = GridCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY) + result = await instance.aio_captcha_handler() + assert isinstance(result, dict) is True + assert result["errorId"] == 12 + assert isinstance(result["errorCode"], str) is True + assert result.keys() == GetTaskResultResponseSer().to_dict().keys() + + def test_wrong_link(self): + instance = GridCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY) + result = instance.captcha_handler(captcha_link=self.get_random_string(length=50)) + assert isinstance(result, dict) is True + assert result["errorId"] == 12 + assert isinstance(result["errorCode"], str) is True + assert result.keys() == GetTaskResultResponseSer().to_dict().keys() + + def test_wrong_base64(self): + instance = GridCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY) + result = instance.captcha_handler(captcha_base64=self.get_random_string(length=50).encode(encoding="UTF-8")) + assert isinstance(result, dict) is True + assert result["errorId"] == 15 + assert result["taskId"] is None + + async def test_aio_wrong_link(self): + instance = GridCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY) + result = await instance.aio_captcha_handler(captcha_link=self.get_random_string(length=50)) + assert isinstance(result, dict) is True + assert result["errorId"] == 12 + assert isinstance(result["errorCode"], str) is True + assert result.keys() == GetTaskResultResponseSer().to_dict().keys() + + async def test_aio_wrong_base64(self): + instance = GridCaptcha(rucaptcha_key=self.RUCAPTCHA_KEY) + result = await instance.aio_captcha_handler( + captcha_base64=self.get_random_string(length=50).encode(encoding="UTF-8") + ) + assert isinstance(result, dict) is True + assert result["errorId"] == 15 + assert result["taskId"] is None diff --git a/tests/test_image.py b/tests/test_image.py index a9bbcc38..2e0b91f2 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -6,12 +6,10 @@ from python_rucaptcha.core.serializer import GetTaskResultResponseSer -class BaseImageCaptcha(BaseTest): +class TestImageCaptcha(BaseTest): captcha_file = "src/examples/088636.jpg" captcha_url = "https://rucaptcha.com/dist/web/99581b9d446a509a0a01954438a5e36a.jpg" - -class TestImageCaptcha(BaseImageCaptcha): kwargs_params = { "phrase": False, "case": True, From 35c4e9e9d4d5ed53ff653cc6185548c32b12d9ed Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 13 Dec 2023 20:44:51 +0300 Subject: [PATCH 24/32] 6.1 --- 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 58e57409..8833022e 100644 --- a/src/python_rucaptcha/__version__.py +++ b/src/python_rucaptcha/__version__.py @@ -1 +1 @@ -__version__ = "6.0.3" +__version__ = "6.1" From fe52bc652baf7b11227e9cc8395d611181069545 Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 13 Dec 2023 20:50:33 +0300 Subject: [PATCH 25/32] updated docs --- docs/index.rst | 5 +++++ docs/modules/bounding-box/example.rst | 12 ++++++++++++ docs/modules/coordinates/example.rst | 12 ++++++++++++ docs/modules/draw-around/example.rst | 12 ++++++++++++ docs/modules/enum/info.rst | 20 ++++++++++++++++++++ docs/modules/grid/example.rst | 12 ++++++++++++ docs/modules/mt-captcha/example.rst | 12 ++++++++++++ 7 files changed, 85 insertions(+) create mode 100644 docs/modules/bounding-box/example.rst create mode 100644 docs/modules/coordinates/example.rst create mode 100644 docs/modules/draw-around/example.rst create mode 100644 docs/modules/grid/example.rst create mode 100644 docs/modules/mt-captcha/example.rst diff --git a/docs/index.rst b/docs/index.rst index e767ca33..78b44b77 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -43,6 +43,11 @@ Check our other projects here - `RedPandaDev group Date: Wed, 13 Dec 2023 20:53:37 +0300 Subject: [PATCH 26/32] Update conf.py --- docs/conf.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/conf.py b/docs/conf.py index cb60cca5..cc076a29 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -10,16 +10,20 @@ hcaptcha, turnstile, amazon_waf, + mt_captcha, re_captcha, capy_puzzle, fun_captcha, key_captcha, + grid_captcha, text_captcha, image_captcha, lemin_captcha, rotate_captcha, datadome_captcha, cyber_siara_captcha, + draw_around_captcha, + bounding_box_captcha, ) from python_rucaptcha.__version__ import __version__ From f8d9e42efde931d2559d1c62c78bfd24d854f057 Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 13 Dec 2023 20:58:36 +0300 Subject: [PATCH 27/32] Update drawing.svg --- files/drawing.svg | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/files/drawing.svg b/files/drawing.svg index 33c08db8..92c885a2 100644 --- a/files/drawing.svg +++ b/files/drawing.svg @@ -28,8 +28,8 @@ inkscape:document-units="mm" showgrid="true" inkscape:zoom="2" - inkscape:cx="358.75" - inkscape:cy="200.5" + inkscape:cx="344" + inkscape:cy="234.5" inkscape:window-width="2560" inkscape:window-height="1380" inkscape:window-x="0" @@ -481,9 +481,9 @@ inkscape:label="TextBottom-2Captcha">2Captcha2CaptchaAntiCaptchaAntiCaptchaRuCaptchaCapsolverCapsolver Date: Wed, 13 Dec 2023 20:59:11 +0300 Subject: [PATCH 28/32] Update RuCaptchaHigh.png --- files/RuCaptchaHigh.png | Bin 1029790 -> 1029790 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/files/RuCaptchaHigh.png b/files/RuCaptchaHigh.png index d2f931d1de25be704e6c258b8731226b456a8a25..e0ae7ebd857557b46f1af5a2f851206d479163ae 100644 GIT binary patch delta 25450 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 Qa01l1{rfB{ubJ_`0CNMf7ytkO From 634c15c11314f6ed7a7f7ded543ea81b43c20e79 Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 13 Dec 2023 20:59:41 +0300 Subject: [PATCH 29/32] Update RuCaptchaHigh.png --- files/RuCaptchaHigh.png | Bin 1029790 -> 1029790 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/files/RuCaptchaHigh.png b/files/RuCaptchaHigh.png index e0ae7ebd857557b46f1af5a2f851206d479163ae..10d9b373d6d5b775b0f9e62ae16e5c9ef54ddf79 100644 GIT binary patch delta 21663 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 delta 4637 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 From aea1a3d893e78a2544211a715798b3e9c434c719 Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 13 Dec 2023 21:00:29 +0300 Subject: [PATCH 30/32] Update drawing.svg --- files/drawing.svg | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/files/drawing.svg b/files/drawing.svg index 92c885a2..67d22074 100644 --- a/files/drawing.svg +++ b/files/drawing.svg @@ -343,9 +343,9 @@ inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" - inkscape:export-filename="../../python3-anticaptcha/docs/_static/AntiCaptcha.png" - inkscape:export-xdpi="80" - inkscape:export-ydpi="80"> Date: Wed, 13 Dec 2023 21:12:35 +0300 Subject: [PATCH 31/32] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 63f6b97c..7e6fc938 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # python-rucaptcha -![Logo](https://red-panda-dev.xyz/media/images/RuCaptchaHigh.max-800x600.png) +![Logo](https://red-panda-dev.xyz/media/images/RuCaptchaHigh_zkkPoYF.original.png) Capsolver's Banner From ff786c5572e2f620cd5a0876641e639d4f5d9261 Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 13 Dec 2023 21:17:28 +0300 Subject: [PATCH 32/32] Update test.yml --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3df5408f..7f237e73 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,7 +29,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11"] + python-version: ["3.11"] steps: - uses: actions/checkout@v4