diff --git a/capmonstercloud_client/CapMonsterCloudClient.py b/capmonstercloud_client/CapMonsterCloudClient.py index 942bbef..21de793 100644 --- a/capmonstercloud_client/CapMonsterCloudClient.py +++ b/capmonstercloud_client/CapMonsterCloudClient.py @@ -32,7 +32,9 @@ ((ImpervaCustomTaskRequest,), getImpervaTimeouts), ((RecognitionComplexImageTaskRequest), getCITTimeouts), ((MTCaptchaRequest), getImage2TextTimeouts), - ((YidunRequest), getImage2TextTimeouts), + ((YidunRequest), getYidunTimeouts), + ((TemuCustomTaskRequest), getTemuTimeouts), + ((ProsopoTaskRequest), getProsopoTimeouts), ) @@ -83,7 +85,9 @@ async def solve_captcha(self, request: Union[ TurnstileRequest, RecognitionComplexImageTaskRequest, MTCaptchaRequest, - YidunRequest], + YidunRequest, + TemuCustomTaskRequest, + ProsopoTaskRequest], ) -> Dict[str, str]: ''' Non-blocking method for captcha solving. @@ -116,7 +120,11 @@ async def _solve(self, request: Union[ BinanceTaskRequest, ImpervaCustomTaskRequest, TurnstileRequest, - RecognitionComplexImageTaskRequest], + RecognitionComplexImageTaskRequest, + MTCaptchaRequest, + YidunRequest, + TemuCustomTaskRequest, + ProsopoTaskRequest], timeouts: GetResultTimeouts, ) -> Dict[str, str]: diff --git a/capmonstercloud_client/GetResultTimeouts.py b/capmonstercloud_client/GetResultTimeouts.py index 8791a93..bd86b14 100644 --- a/capmonstercloud_client/GetResultTimeouts.py +++ b/capmonstercloud_client/GetResultTimeouts.py @@ -52,3 +52,12 @@ def getImpervaTimeouts() -> GetResultTimeouts: def getCITTimeouts() -> GetResultTimeouts: return GetResultTimeouts(0.35, 0, 0.2, 10) + +def getYidunTimeouts() -> GetResultTimeouts: + return GetResultTimeouts(1, 10, 3, 180) + +def getProsopoTimeouts() -> GetResultTimeouts: + return GetResultTimeouts(1, 10, 3, 180) + +def getTemuTimeouts() -> GetResultTimeouts: + return GetResultTimeouts(1, 10, 3, 180) diff --git a/capmonstercloud_client/requests/ProsopoTaskRequest.py b/capmonstercloud_client/requests/ProsopoTaskRequest.py new file mode 100644 index 0000000..943a96f --- /dev/null +++ b/capmonstercloud_client/requests/ProsopoTaskRequest.py @@ -0,0 +1,23 @@ +from typing import Dict, Union +from pydantic import Field +from .baseRequestWithProxy import BaseRequestWithProxy + + +class ProsopoTaskRequest(BaseRequestWithProxy): + type: str = Field(default="ProsopoTask") + websiteUrl: str + websiteKey: str + + def getTaskDict(self) -> Dict[str, Union[str, int, bool]]: + task = {} + task["type"] = self.type + task["websiteURL"] = self.websiteUrl + task["websiteKey"] = self.websiteKey + if self.proxy: + task["proxyType"] = self.proxy.proxyType + task["proxyAddress"] = self.proxy.proxyAddress + task["proxyPort"] = self.proxy.proxyPort + task["proxyLogin"] = self.proxy.proxyLogin + task["proxyPassword"] = self.proxy.proxyPassword + + return task diff --git a/capmonstercloud_client/requests/TemuCustomTaskRequest.py b/capmonstercloud_client/requests/TemuCustomTaskRequest.py new file mode 100644 index 0000000..2a9820f --- /dev/null +++ b/capmonstercloud_client/requests/TemuCustomTaskRequest.py @@ -0,0 +1,35 @@ +from typing import Dict, Union +from pydantic import Field, validator + +from .CustomTaskRequestBase import CustomTaskRequestBase + +class TemuCustomTaskRequest(CustomTaskRequestBase): + captchaClass: str = Field(default='Temu') + metadata: Dict[str, str] + + @validator('metadata') + def validate_metadata(cls, value): + if value.get('cookie') is None: + raise TypeError(f'Expect that cookie will be defined.') + else: + if not isinstance(value.get('cookie'), str): + raise TypeError(f'Expect that cookie will be str.') + if not set(value.keys()).issubset(set(["cookie"])): + raise TypeError(f'Allowed keys for metadata are "cookie"') + return value + + def getTaskDict(self) -> Dict[str, Union[str, int, bool]]: + task = {} + task['type'] = self.type + task['class'] = self.captchaClass + task['websiteURL'] = self.websiteUrl + task['metadata'] = self.metadata + if self.proxy: + task['proxyType'] = self.proxy.proxyType + task['proxyAddress'] = self.proxy.proxyAddress + task['proxyPort'] = self.proxy.proxyPort + task['proxyLogin'] = self.proxy.proxyLogin + task['proxyPassword'] = self.proxy.proxyPassword + if self.userAgent is not None: + task['userAgent'] = self.userAgent + return task \ No newline at end of file diff --git a/capmonstercloud_client/requests/__init__.py b/capmonstercloud_client/requests/__init__.py index f4953fd..2932039 100644 --- a/capmonstercloud_client/requests/__init__.py +++ b/capmonstercloud_client/requests/__init__.py @@ -20,6 +20,8 @@ from .RecognitionComplexImageTaskRequest import RecognitionComplexImageTaskRequest from .MTCaptchaRequest import MTCaptchaRequest from .YidunRequest import YidunRequest +from .ProsopoTaskRequest import ProsopoTaskRequest +from .TemuCustomTaskRequest import TemuCustomTaskRequest from .proxy_info import ProxyInfo @@ -35,4 +37,9 @@ 'BinanceTaskRequest', 'ImpervaCustomTaskRequest', 'TurnstileRequest', - 'RecognitionComplexImageTaskRequest'] + 'RecognitionComplexImageTaskRequest', + 'MTCaptchaRequest', + 'YidunRequest', + 'ProsopoTaskRequest', + 'TemuCustomTaskRequest' + ] diff --git a/capmonstercloud_client/version.txt b/capmonstercloud_client/version.txt index a4f52a5..0fa4ae4 100644 --- a/capmonstercloud_client/version.txt +++ b/capmonstercloud_client/version.txt @@ -1 +1 @@ -3.2.0 \ No newline at end of file +3.3.0 \ No newline at end of file diff --git a/examples/prosopo.py b/examples/prosopo.py new file mode 100644 index 0000000..b9947e4 --- /dev/null +++ b/examples/prosopo.py @@ -0,0 +1,38 @@ +import os +import time +import asyncio + +from capmonstercloudclient.requests import ProsopoTaskRequest +from capmonstercloudclient import ClientOptions, CapMonsterClient + + +async def solve_captcha_sync(num_requests): + return [await cap_monster_client.solve_captcha(prosopo_request) for _ in range(num_requests)] + + +async def solve_captcha_async(num_requests): + tasks = [asyncio.create_task(cap_monster_client.solve_captcha(prosopo_request)) for _ in range(num_requests)] + return await asyncio.gather(*tasks, return_exceptions=True) + + +if __name__ == '__main__': + key = os.getenv('API_KEY') + client_options = ClientOptions(api_key=key) + cap_monster_client = CapMonsterClient(options=client_options) + + prosopo_request = ProsopoTaskRequest( + websiteUrl='https://example.com/login', + websiteKey='5EZU3LG31uzq1Mwi8inwqxmfvFDpj7VzwDnZwj4Q3syyxBwV' + ) + print(prosopo_request.getTaskDict()) + nums = 3 + + # Sync test + sync_start = time.time() + sync_responses = asyncio.run(solve_captcha_sync(nums)) + print(f'average execution time sync {1/((time.time()-sync_start)/nums):0.2f} resp/sec\nsolution: {sync_responses[0]}') + + # Async test + async_start = time.time() + async_responses = asyncio.run(solve_captcha_async(nums)) + print(f'average execution time async {1/((time.time()-async_start)/nums):0.2f} resp/sec\nsolution: {async_responses[0]}') diff --git a/examples/temu.py b/examples/temu.py new file mode 100644 index 0000000..33cc392 --- /dev/null +++ b/examples/temu.py @@ -0,0 +1,42 @@ +import os +import time +import asyncio + +from capmonstercloudclient.requests import TemuCustomTaskRequest +from capmonstercloudclient import ClientOptions, CapMonsterClient + + +async def solve_captcha_sync(num_requests): + return [await cap_monster_client.solve_captcha(temu_request) for _ in range(num_requests)] + + +async def solve_captcha_async(num_requests): + tasks = [asyncio.create_task(cap_monster_client.solve_captcha(temu_request)) for _ in range(num_requests)] + return await asyncio.gather(*tasks, return_exceptions=True) + + +if __name__ == '__main__': + key = os.getenv('API_KEY') + client_options = ClientOptions(api_key=key) + cap_monster_client = CapMonsterClient(options=client_options) + + metadata = { + "cookie": "region=141; language=en; currency=EUR; api_uid=CnBpI2fwFW2BogBITHVYAg==; timezone=Europe%2FMoscow; _nano_fp=XpmYXqmJnqX8npXblT_T6~7rkpA2LDnz2BPFuT5m; privacy_setting_detail=%7B%22firstPAds%22%3A0%2C%22adj%22%3A0%2C%22fbsAnlys%22%3A0%2C%22fbEvt%22%3A0%2C%22ggAds%22%3A0%2C%22fbAds%22%3A0%2C%22ttAds%22%3A0%2C%22scAds%22%3A0%2C%22ptAds%22%3A0%2C%22bgAds%22%3A0%2C%22tblAds%22%3A0%2C%22obAds%22%3A0%2C%22vgAds%22%3A0%2C%22idAds%22%3A0%2C%22opAds%22%3A0%2C%22stAds%22%3A0%2C%22pmAds%22%3A0%7D; webp=1; _bee=pgoBlKp038lBhEyoQ4yXnuNrw1X5va2U; verifyAuthToken=QkZmx2TJFbSuuRVD_MKJmA0b84fe3df183da8ab" + } + temu_request = TemuCustomTaskRequest( + websiteUrl='https://www.temu.com/bgn_verification.html?verifyCode=7PRQIzDznoFE67ecZYtRTw394f6185143a4af80&from=https%3A%2F%2Fwww.temu.com%2F&refer_page_name=home&refer_page_id=10005_1743074140645_cwb6un82rq&refer_page_sn=10005&_x_sessn_id=xmp1zuyv7y', + metadata=metadata, + userAgent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36', + ) + + nums = 3 + print(temu_request.getTaskDict()) + # Sync test + sync_start = time.time() + sync_responses = asyncio.run(solve_captcha_sync(nums)) + print(f'average execution time sync {1/((time.time()-sync_start)/nums):0.2f} resp/sec\nsolution: {sync_responses[0]}') + + # Async test + async_start = time.time() + async_responses = asyncio.run(solve_captcha_async(nums)) + print(f'average execution time async {1/((time.time()-async_start)/nums):0.2f} resp/sec\nsolution: {async_responses[0]}') diff --git a/test/prosopo_test.py b/test/prosopo_test.py new file mode 100644 index 0000000..38e8290 --- /dev/null +++ b/test/prosopo_test.py @@ -0,0 +1,35 @@ +import unittest + +from pydantic.error_wrappers import ValidationError +from capmonstercloudclient.requests import ProsopoTaskRequest + + +class ProsopoTaskRequestTest(unittest.TestCase): + websiteUrlExample = "https://example.com" + websiteKeyExample = "prosopo-public-key-123" + + def test_prosopo_request_required_fields(self): + required_fields = ["type", "websiteURL", "websiteKey"] + request = ProsopoTaskRequest( + websiteUrl=self.websiteUrlExample, + websiteKey=self.websiteKeyExample + ) + task_dictionary = request.getTaskDict() + for f in required_fields: + self.assertTrue( + f in list(task_dictionary.keys()), + msg=f'Required captcha input key "{f}" does not include to request.', + ) + self.assertEqual(task_dictionary["type"], "ProsopoTask") + + def test_prosopo_missing_fields(self): + base_kwargs = {} + self.assertRaises(ValidationError, ProsopoTaskRequest, **base_kwargs) + base_kwargs.update({"websiteUrl": self.websiteUrlExample}) + self.assertRaises(ValidationError, ProsopoTaskRequest, **base_kwargs) + base_kwargs.update({"websiteKey": self.websiteKeyExample}) + ProsopoTaskRequest(**base_kwargs) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/temu_test.py b/test/temu_test.py new file mode 100644 index 0000000..c3f415f --- /dev/null +++ b/test/temu_test.py @@ -0,0 +1,49 @@ +import unittest + +from pydantic.error_wrappers import ValidationError +from capmonstercloudclient.requests import TemuCustomTaskRequest + + +class TemuCustomTaskRequestTest(unittest.TestCase): + websiteUrlExample = "https://example.com" + userAgentExample = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36" + cookieExample = "sessionid=abc123; path=/; domain=.example.com" + + def test_temu_request_required_fields(self): + required_fields = ["type", "class", "websiteURL", "metadata"] + metadata_required_fields = ["cookie"] + request = TemuCustomTaskRequest( + websiteUrl=self.websiteUrlExample, metadata={"cookie": self.cookieExample} + ) + task_dictionary = request.getTaskDict() + for f in required_fields: + self.assertTrue( + f in list(task_dictionary.keys()), + msg=f'Required captcha input key "{f}" does not include to request.', + ) + for f in metadata_required_fields: + self.assertTrue( + f in list(task_dictionary["metadata"].keys()), + msg=f'Required captcha input key "{f}" does not include to request.', + ) + + def test_temu_metadata_validation(self): + base_kwargs = {"websiteUrl": self.websiteUrlExample, "metadata": {}} + self.assertRaises(TypeError, TemuCustomTaskRequest, **base_kwargs) + base_kwargs["metadata"]["cookie"] = self.cookieExample + TemuCustomTaskRequest(**base_kwargs) + # Unsupported keys should raise error + base_kwargs["metadata"]["extra"] = "not-allowed" + self.assertRaises(TypeError, TemuCustomTaskRequest, **base_kwargs) + + def test_temu_missing_fields(self): + base_kwargs = {} + self.assertRaises(ValidationError, TemuCustomTaskRequest, **base_kwargs) + base_kwargs.update({"websiteUrl": self.websiteUrlExample}) + self.assertRaises(ValidationError, TemuCustomTaskRequest, **base_kwargs) + base_kwargs.update({"metadata": {"cookie": self.cookieExample}}) + TemuCustomTaskRequest(**base_kwargs) + + +if __name__ == "__main__": + unittest.main()