diff --git a/.gitignore b/.gitignore index 5806f4f..62d48de 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ __pycache__/ dist/ build/ env/ +venv/ capmonstercloudclient.egg-info/ \ No newline at end of file diff --git a/capmonstercloud_client/CapMonsterCloudClient.py b/capmonstercloud_client/CapMonsterCloudClient.py index 8ffa4c1..82344cc 100644 --- a/capmonstercloud_client/CapMonsterCloudClient.py +++ b/capmonstercloud_client/CapMonsterCloudClient.py @@ -16,9 +16,9 @@ _instance_config = ( ((RecaptchaV2Request,), getRecaptchaV2Timeouts), ((RecaptchaV2EnterpriseRequest,), getRecaptchaV2EnterpriseTimeouts), - ((RecaptchaV3ProxylessRequest), getRecaptchaV3Timeouts), - ((RecaptchaV3EnterpriseRequest), getRecaptchaV3Timeouts), - ((ImageToTextRequest), getImage2TextTimeouts), + ((RecaptchaV3ProxylessRequest,), getRecaptchaV3Timeouts), + ((RecaptchaV3EnterpriseRequest,), getRecaptchaV3Timeouts), + ((ImageToTextRequest,), getImage2TextTimeouts), ((FuncaptchaRequest,), getFuncaptchaTimeouts), ((HcaptchaRequest,), getHcaptchaTimeouts), ((GeetestRequest,), getGeetestTimeouts), @@ -31,12 +31,15 @@ ((AmazonWafRequest,), getAmazonWafTimeouts), ((BinanceTaskRequest,), getBinanceTimeouts), ((ImpervaCustomTaskRequest,), getImpervaTimeouts), - ((RecognitionComplexImageTaskRequest), getCITTimeouts), - ((MTCaptchaRequest), getImage2TextTimeouts), - ((YidunRequest), getYidunTimeouts), - ((TemuCustomTaskRequest), getTemuTimeouts), - ((ProsopoTaskRequest), getProsopoTimeouts), - ((AltchaCustomTaskRequest), getAltchaTimeouts), + ((RecognitionComplexImageTaskRequest,), getCITTimeouts), + ((MTCaptchaRequest,), getImage2TextTimeouts), + ((YidunRequest,), getYidunTimeouts), + ((TemuCustomTaskRequest,), getTemuTimeouts), + ((ProsopoTaskRequest,), getProsopoTimeouts), + ((AltchaCustomTaskRequest,), getAltchaTimeouts), + ((CastleCustomTaskRequest,), getCastleTimeouts), + ((TspdCustomTaskRequest,), getTspdTimeouts), + ((HuntCustomTaskRequest,), getHuntTimeouts), ) @@ -100,7 +103,10 @@ async def solve_captcha(self, request: Union[ YidunRequest, TemuCustomTaskRequest, ProsopoTaskRequest, - AltchaCustomTaskRequest], + AltchaCustomTaskRequest, + CastleCustomTaskRequest, + TspdCustomTaskRequest, + HuntCustomTaskRequest], ) -> Dict[str, str]: ''' Non-blocking method for captcha solving. @@ -148,6 +154,7 @@ async def _solve(self, request: Union[ raise GetTaskError(f'[{getTaskResponse.get("errorCode")}] ' \ f'{getTaskResponse.get("errorDescription")}.') timer = RequestController(timeout=timeouts.timeout) + timer.run() await asyncio.sleep(timeouts.firstRequestDelay) result = CaptchaResult() while not timer.cancel: @@ -165,13 +172,10 @@ async def _solve(self, request: Union[ elif getResultResponse.get('status') == 'ready': timer.stop() result.solution = getResultResponse.get('solution') - break - - if result != None: - return result.solution - else: - raise TimeoutError('Failed to get a solution within the maximum ' \ - f'response waiting interval: {timeouts.timeout:0.1f} sec.') + return result.solution + + raise TimeoutError('Failed to get a solution within the maximum ' \ + f'response waiting interval: {timeouts.timeout:0.1f} sec.') async def _getTaskResult(self, task_id: str) -> Dict[str, Union[int, str, None]]: diff --git a/capmonstercloud_client/GetResultTimeouts.py b/capmonstercloud_client/GetResultTimeouts.py index 202262b..0b62fef 100644 --- a/capmonstercloud_client/GetResultTimeouts.py +++ b/capmonstercloud_client/GetResultTimeouts.py @@ -56,6 +56,15 @@ def getImpervaTimeouts() -> GetResultTimeouts: def getAltchaTimeouts() -> GetResultTimeouts: return GetResultTimeouts(1, 0, 1, 50) +def getCastleTimeouts() -> GetResultTimeouts: + return GetResultTimeouts(1, 0, 1, 60) + +def getTspdTimeouts() -> GetResultTimeouts: + return GetResultTimeouts(1, 0, 1, 60) + +def getHuntTimeouts() -> GetResultTimeouts: + return GetResultTimeouts(1, 0, 1, 60) + def getCITTimeouts() -> GetResultTimeouts: return GetResultTimeouts(0.35, 0, 0.2, 10) diff --git a/capmonstercloud_client/requests/CastleCustomTaskRequest.py b/capmonstercloud_client/requests/CastleCustomTaskRequest.py new file mode 100644 index 0000000..2515600 --- /dev/null +++ b/capmonstercloud_client/requests/CastleCustomTaskRequest.py @@ -0,0 +1,42 @@ +from typing import Dict, Union +from pydantic import Field, validator + +from .CustomTaskRequestBase import CustomTaskRequestBase + +class CastleCustomTaskRequest(CustomTaskRequestBase): + captchaClass: str = Field(default='Castle') + websiteKey: str = Field() + metadata: Dict[str, Union[str, int]] + + @validator('metadata') + def validate_metadata(cls, value): + if value.get('wUrl') is None: + raise TypeError(f'Expected that wUrl is defined.') + else: + if not isinstance(value.get('wUrl'), str): + raise TypeError(f'Expected that wUrl is str.') + if value.get('swUrl') is None: + raise TypeError(f'Expected that swUrl is defined.') + else: + if not isinstance(value.get('swUrl'), str): + raise TypeError(f'Expected that swUrl is str.') + if value.get('count') is not None and not isinstance(value.get('count'), int): + raise TypeError(f'Expected that count is int.') + 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['websiteKey'] = self.websiteKey + 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 diff --git a/capmonstercloud_client/requests/DataDomeCustomTaskRequest.py b/capmonstercloud_client/requests/DataDomeCustomTaskRequest.py index 278a616..179cd67 100644 --- a/capmonstercloud_client/requests/DataDomeCustomTaskRequest.py +++ b/capmonstercloud_client/requests/DataDomeCustomTaskRequest.py @@ -1,15 +1,17 @@ from typing import Dict, Union -from pydantic import Field, validator +from pydantic import Field, validator, model_validator from .CustomTaskRequestBase import CustomTaskRequestBase class DataDomeCustomTaskRequest(CustomTaskRequestBase): captchaClass: str = Field(default='DataDome') metadata : Dict[str, str] - + @validator('metadata') def validate_metadata(cls, value): if value.get('datadomeCookie') is None: raise TypeError(f'Expect that datadomeCookie will be defined.') + if value.get('datadomeVersion') is not None and not isinstance(value.get('datadomeVersion'), str): + raise TypeError(f'Expected datadomeVersion to be str') if value.get('captchaUrl') and value.get('htmlPageBase64'): raise TypeError(f'Expected only one of [captchaUrl, htmlPageBase64]') elif value.get('captchaUrl'): @@ -18,22 +20,24 @@ def validate_metadata(cls, value): return {i: value[i] for i in value if i != 'captchaUrl'} else: raise TypeError(f'Expected one of [captchaUrl, htmlPageBase64]') - if value.get('datadomeVersion') and not isinstance(value.get('datadomeVersion'), str): - raise TypeError(f'Expected datadomeVersion to be str') - + @model_validator(mode='before') + def validate_datadome_proxy(cls, values): + proxy = values.get('proxy') + if proxy is None: + raise RuntimeError(f'You are required to use your own proxies.') + return values + def getTaskDict(self) -> Dict[str, Union[str, int, bool]]: task = {} task['type'] = self.type task['class'] = self.captchaClass task['websiteURL'] = self.websiteUrl - 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 - task['domains'] = self.domains + 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 task['metadata'] = self.metadata if self.userAgent is not None: task['userAgent'] = self.userAgent diff --git a/capmonstercloud_client/requests/HuntCustomTaskRequest.py b/capmonstercloud_client/requests/HuntCustomTaskRequest.py new file mode 100644 index 0000000..0dd1302 --- /dev/null +++ b/capmonstercloud_client/requests/HuntCustomTaskRequest.py @@ -0,0 +1,41 @@ +from typing import Dict, Union +from pydantic import Field, validator, model_validator + +from .CustomTaskRequestBase import CustomTaskRequestBase + +class HuntCustomTaskRequest(CustomTaskRequestBase): + captchaClass: str = Field(default='HUNT') + metadata: Dict[str, str] + + @validator('metadata') + def validate_metadata(cls, value): + if value.get('apiGetLib') is None: + raise TypeError(f'Expect that apiGetLib will be defined.') + else: + if not isinstance(value.get('apiGetLib'), str): + raise TypeError(f'Expect that apiGetLib will be str.') + if value.get('data') is not None and not isinstance(value.get('data'), str): + raise TypeError(f'Expect that data will be str.') + return value + + @model_validator(mode='before') + def validate_hunt_proxy(cls, values): + proxy = values.get('proxy') + if proxy is None: + raise RuntimeError(f'You are required to use your own proxies.') + return values + + 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 + 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 diff --git a/capmonstercloud_client/requests/RecaptchaV2EnterpiseRequest.py b/capmonstercloud_client/requests/RecaptchaV2EnterpiseRequest.py index db7dfed..845297a 100644 --- a/capmonstercloud_client/requests/RecaptchaV2EnterpiseRequest.py +++ b/capmonstercloud_client/requests/RecaptchaV2EnterpiseRequest.py @@ -10,7 +10,9 @@ class RecaptchaV2EnterpriseRequest(BaseRequestWithProxy): enterprisePayload: Optional[str] = Field(default=None) apiDomain: Optional[str] = Field(default=None) pageAction: Optional[str] = Field(default=None) - + userAgent: Optional[str] = Field(default=None) + cookies: Optional[str] = Field(default=None) + def getTaskDict(self) -> Dict[str, Union[str, int]]: task = {} task['type'] = self.type @@ -28,4 +30,8 @@ def getTaskDict(self) -> Dict[str, Union[str, int]]: task['apiDomain'] = self.apiDomain if self.pageAction is not None: task['pageAction'] = self.pageAction + if self.userAgent is not None: + task['userAgent'] = self.userAgent + if self.cookies is not None: + task['cookies'] = self.cookies return task \ No newline at end of file diff --git a/capmonstercloud_client/requests/RecaptchaV2Request.py b/capmonstercloud_client/requests/RecaptchaV2Request.py index 725e8e5..34bfb91 100644 --- a/capmonstercloud_client/requests/RecaptchaV2Request.py +++ b/capmonstercloud_client/requests/RecaptchaV2Request.py @@ -11,8 +11,9 @@ class RecaptchaV2Request(BaseRequestWithProxy): dataSValue: Optional[str] = Field(default=None) userAgent: Optional[str] = Field(default=None) cookies: Optional[str] = Field(default=None) + isInvisible: Optional[bool] = Field(default=None) - def getTaskDict(self) -> Dict[str, Union[str, int]]: + def getTaskDict(self) -> Dict[str, Union[str, int, bool]]: task = {} task['type'] = self.type task['websiteURL'] = self.websiteUrl @@ -26,12 +27,15 @@ def getTaskDict(self) -> Dict[str, Union[str, int]]: if self.dataSValue is not None: task['recaptchaDataSValue'] = self.dataSValue - + if self.userAgent is not None: task['userAgent'] = self.userAgent if self.cookies is not None: task['cookies'] = self.cookies + if self.isInvisible is not None: + task['isInvisible'] = self.isInvisible + return task \ No newline at end of file diff --git a/capmonstercloud_client/requests/RecaptchaV3ProxylessRequest.py b/capmonstercloud_client/requests/RecaptchaV3ProxylessRequest.py index 1176b42..3865f09 100644 --- a/capmonstercloud_client/requests/RecaptchaV3ProxylessRequest.py +++ b/capmonstercloud_client/requests/RecaptchaV3ProxylessRequest.py @@ -7,10 +7,10 @@ class RecaptchaV3ProxylessRequest(BaseRequest): websiteUrl: str websiteKey: str type: str = Field(default='RecaptchaV3TaskProxyless') - min_score: Optional[float] = Field(default=None) + minScore: Optional[float] = Field(default=None) pageAction: Optional[str] = Field(default=None) - @validator('min_score') + @validator('minScore') def validate_min_score(cls, value): if value is not None: if not 0.1 <= value <= 0.9: @@ -23,8 +23,8 @@ def getTaskDict(self) -> Dict[str, Union[str, float]]: task['type'] = self.type task['websiteURL'] = self.websiteUrl task['websiteKey'] = self.websiteKey - if self.min_score is not None: - task['minScore'] = self.min_score + if self.minScore is not None: + task['minScore'] = self.minScore if self.pageAction is not None: task['pageAction'] = self.pageAction return task \ No newline at end of file diff --git a/capmonstercloud_client/requests/TenDiCustomTaskRequest.py b/capmonstercloud_client/requests/TenDiCustomTaskRequest.py index 9ee01de..0b6dc63 100644 --- a/capmonstercloud_client/requests/TenDiCustomTaskRequest.py +++ b/capmonstercloud_client/requests/TenDiCustomTaskRequest.py @@ -1,11 +1,21 @@ -from typing import Dict, Union -from pydantic import Field +from typing import Dict, Optional, Union +from pydantic import Field, validator from .CustomTaskRequestBase import CustomTaskRequestBase class TenDiCustomTaskRequest(CustomTaskRequestBase): captchaClass: str = Field(default='TenDI') websiteKey: str = Field() + metadata: Optional[Dict[str, str]] = Field(default=None) + + @validator('metadata') + def validate_metadata(cls, value): + if value is not None: + if not set(value.keys()).issubset(set(["captchaUrl"])): + raise TypeError(f'Allowed keys for metadata are "captchaUrl"') + if value.get('captchaUrl') is not None and not isinstance(value.get('captchaUrl'), str): + raise TypeError(f'Expect that captchaUrl will be str.') + return value def getTaskDict(self) -> Dict[str, Union[str, int, bool]]: task = {} @@ -21,4 +31,6 @@ def getTaskDict(self) -> Dict[str, Union[str, int, bool]]: task['proxyPassword'] = self.proxy.proxyPassword if self.userAgent is not None: task['userAgent'] = self.userAgent + if self.metadata is not None: + task['metadata'] = self.metadata return task \ No newline at end of file diff --git a/capmonstercloud_client/requests/TspdCustomTaskRequest.py b/capmonstercloud_client/requests/TspdCustomTaskRequest.py new file mode 100644 index 0000000..c5781d9 --- /dev/null +++ b/capmonstercloud_client/requests/TspdCustomTaskRequest.py @@ -0,0 +1,44 @@ +from typing import Dict, Union +from pydantic import Field, validator, model_validator + +from .CustomTaskRequestBase import CustomTaskRequestBase + +class TspdCustomTaskRequest(CustomTaskRequestBase): + captchaClass: str = Field(default='tspd') + userAgent: str = Field() + metadata: Dict[str, str] + + @validator('metadata') + def validate_metadata(cls, value): + if value.get('tspdCookie') is None: + raise TypeError(f'Expect that tspdCookie will be defined.') + else: + if not isinstance(value.get('tspdCookie'), str): + raise TypeError(f'Expect that tspdCookie will be str.') + if value.get('htmlPageBase64') is None: + raise TypeError(f'Expect that htmlPageBase64 will be defined.') + else: + if not isinstance(value.get('htmlPageBase64'), str): + raise TypeError(f'Expect that htmlPageBase64 will be str.') + return value + + @model_validator(mode='before') + def validate_tspd_proxy(cls, values): + proxy = values.get('proxy') + if proxy is None: + raise RuntimeError(f'You are required to use your own proxies.') + return values + + 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 + task['userAgent'] = self.userAgent + 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/YidunRequest.py b/capmonstercloud_client/requests/YidunRequest.py index 46352ec..dcc05c9 100644 --- a/capmonstercloud_client/requests/YidunRequest.py +++ b/capmonstercloud_client/requests/YidunRequest.py @@ -8,6 +8,11 @@ class YidunRequest(BaseRequestWithProxy): websiteUrl: str websiteKey: str userAgent: Optional[str] = Field(default=None) + yidunGetLib: Optional[str] = Field(default=None) + yidunApiServerSubdomain: Optional[str] = Field(default=None) + challenge: Optional[str] = Field(default=None) + hcg: Optional[str] = Field(default=None) + hct: Optional[int] = Field(default=None) def getTaskDict(self) -> Dict[str, Union[str, int, bool]]: task = {} @@ -16,6 +21,16 @@ def getTaskDict(self) -> Dict[str, Union[str, int, bool]]: task["websiteKey"] = self.websiteKey if self.userAgent is not None: task["userAgent"] = self.userAgent + if self.yidunGetLib is not None: + task["yidunGetLib"] = self.yidunGetLib + if self.yidunApiServerSubdomain is not None: + task["yidunApiServerSubdomain"] = self.yidunApiServerSubdomain + if self.challenge is not None: + task["challenge"] = self.challenge + if self.hcg is not None: + task["hcg"] = self.hcg + if self.hct is not None: + task["hct"] = self.hct if self.proxy: task["proxyType"] = self.proxy.proxyType task["proxyAddress"] = self.proxy.proxyAddress diff --git a/capmonstercloud_client/requests/__init__.py b/capmonstercloud_client/requests/__init__.py index 9c224d3..4328baa 100644 --- a/capmonstercloud_client/requests/__init__.py +++ b/capmonstercloud_client/requests/__init__.py @@ -24,13 +24,16 @@ from .ProsopoTaskRequest import ProsopoTaskRequest from .TemuCustomTaskRequest import TemuCustomTaskRequest from .AltchaCustomTaskRequest import AltchaCustomTaskRequest +from .CastleCustomTaskRequest import CastleCustomTaskRequest +from .TspdCustomTaskRequest import TspdCustomTaskRequest +from .HuntCustomTaskRequest import HuntCustomTaskRequest from .proxy_info import ProxyInfo, ClientProxyInfo -REQUESTS = ['RecaptchaV2EnterpiseRequest', 'RecaptchaV2Request', 'RecaptchaV3ProxylessRequest', 'RecaptchaComplexImageTaskRequest', 'RecaptchaV3EnterpriseRequest' +REQUESTS = ['RecaptchaV2EnterpiseRequest', 'RecaptchaV2Request', 'RecaptchaV3ProxylessRequest', 'RecaptchaComplexImageTaskRequest', 'RecaptchaV3EnterpriseRequest', 'ImageToTextRequest', 'FuncaptchaRequest', 'FunCaptchaComplexImageTaskRequest', - 'HcaptchaRequest', 'HcaptchaComplexImageTaskRequest' + 'HcaptchaRequest', 'HcaptchaComplexImageTaskRequest', 'GeetestRequest', 'DataDomeCustomTaskRequest', 'TenDiCustomTaskRequest', @@ -44,5 +47,8 @@ 'YidunRequest', 'ProsopoTaskRequest', 'TemuCustomTaskRequest', - 'AltchaCustomTaskRequest' + 'AltchaCustomTaskRequest', + 'CastleCustomTaskRequest', + 'TspdCustomTaskRequest', + 'HuntCustomTaskRequest' ] diff --git a/capmonstercloud_client/version.txt b/capmonstercloud_client/version.txt index fbcbf73..0c89fc9 100644 --- a/capmonstercloud_client/version.txt +++ b/capmonstercloud_client/version.txt @@ -1 +1 @@ -3.4.0 \ No newline at end of file +4.0.0 \ No newline at end of file diff --git a/examples/castle.py b/examples/castle.py new file mode 100644 index 0000000..4cbd656 --- /dev/null +++ b/examples/castle.py @@ -0,0 +1,47 @@ +import os +import time +import asyncio + +from capmonstercloudclient.requests import CastleCustomTaskRequest +from capmonstercloudclient import ClientOptions, CapMonsterClient + + +async def solve_captcha_sync(num_requests): + return [await cap_monster_client.solve_captcha(castle_request) for _ in range(num_requests)] + + +async def solve_captcha_async(num_requests): + tasks = [asyncio.create_task(cap_monster_client.solve_captcha(castle_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 = { + "wUrl": "https://s.rsg.sc/auth/js/20251234bgef/build/cw.js", + "swUrl": "https://s.rsg.sc/auth/js/20251234bgef/build/csw.js", + "count": 1, + } + castle_request = CastleCustomTaskRequest( + websiteUrl='https://www.example.com', + websiteKey='pk_1Tk5Yzr1WFzxrJCh7WzMZzY1rHpaOtdK', + metadata=metadata, + userAgent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36', + ) + + 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} ' \ + f'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} ' \ + f'resp/sec\nsolution: {async_responses[0]}') diff --git a/examples/datadome.py b/examples/datadome.py index 3cd5d8f..fa709a7 100644 --- a/examples/datadome.py +++ b/examples/datadome.py @@ -2,14 +2,14 @@ import time import asyncio -from capmonstercloudclient.requests import DataDomeCustomTaskRequest +from capmonstercloudclient.requests import DataDomeCustomTaskRequest, ProxyInfo from capmonstercloudclient import ClientOptions, CapMonsterClient async def solve_captcha_sync(num_requests): return [await cap_monster_client.solve_captcha(datadome_request) for _ in range(num_requests)] async def solve_captcha_async(num_requests): - tasks = [asyncio.create_task(cap_monster_client.solve_captcha(datadome_request)) + tasks = [asyncio.create_task(cap_monster_client.solve_captcha(datadome_request)) for _ in range(num_requests)] return await asyncio.gather(*tasks, return_exceptions=True) @@ -17,10 +17,18 @@ async def solve_captcha_async(num_requests): key = os.getenv('API_KEY') client_options = ClientOptions(api_key=key) cap_monster_client = CapMonsterClient(options=client_options) + proxy = ProxyInfo( + proxyType="http", + proxyAddress="8.8.8.8", + proxyPort=8080, + proxyLogin="proxyLoginHere", + proxyPassword="proxyPasswordHere" + ) metadata = {'captchaUrl': 'https://geo.captcha-delivery.com/captcha/?initialCid=AHrlqAAAAAMAJxx4dfgwjzwAQW0ctQ%3D%3D&hash=D66B23AC3F48A302A7654416846381&cid=d3k5rbDsu8cq0kmPHISS3hsC3f4qeL_K12~G33PrE4fbkmDYSul6l0Ze_aG5sUHLKG0676UpTv6GFvUgIActglZF33GTodOoRhEDkMMsuWTodlYa3YYQ9xKy9J89PAWh&t=fe&referer=https%3A%2F%2Fantoinevastel.com%2Fbots%2Fdatadome&s=21705&e=04fc682817ba89bf8fa4b18031fa53294fa0fb7449d95c036a1986413e6dfc7d', 'datadomeCookie': 'datadome=d3k5rbDsu8cq0kmPHISS3hsC3f4qeL_K12~G33PrE4fbkmDYSul6l0Ze_aG5sUHLKG0676UpTv6GFvUgIActglZF33GTodOoRhEDkMMsuWTodlYa3YYQ9xKy9J89PAWh'} datadome_request = DataDomeCustomTaskRequest(websiteUrl='https://antoinevastel.com/bots/datadome', - metadata=metadata + metadata=metadata, + proxy=proxy ) nums = 3 diff --git a/examples/hunt.py b/examples/hunt.py new file mode 100644 index 0000000..317abd4 --- /dev/null +++ b/examples/hunt.py @@ -0,0 +1,52 @@ +import os +import time +import asyncio + +from capmonstercloudclient.requests import HuntCustomTaskRequest, ProxyInfo +from capmonstercloudclient import ClientOptions, CapMonsterClient + + +async def solve_captcha_sync(num_requests): + return [await cap_monster_client.solve_captcha(hunt_request) for _ in range(num_requests)] + + +async def solve_captcha_async(num_requests): + tasks = [asyncio.create_task(cap_monster_client.solve_captcha(hunt_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) + + proxy = ProxyInfo( + proxyType="http", + proxyAddress="8.8.8.8", + proxyPort=8080, + proxyLogin="proxyLoginHere", + proxyPassword="proxyPasswordHere" + ) + metadata = { + "apiGetLib": "https://example.com/hd-api/external/apps/a2157wab1045d68672a63557e0n2a77edbfd15ea/api.js", + } + hunt_request = HuntCustomTaskRequest( + websiteUrl='https://example.com', + metadata=metadata, + proxy=proxy, + userAgent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36', + ) + + 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} ' \ + f'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} ' \ + f'resp/sec\nsolution: {async_responses[0]}') diff --git a/examples/recaptchaV3.py b/examples/recaptchaV3.py index 8f95798..7e236ab 100644 --- a/examples/recaptchaV3.py +++ b/examples/recaptchaV3.py @@ -21,7 +21,7 @@ async def solve_captcha_async(num_requests): recaptcha3request = RecaptchaV3Request(websiteUrl="https://lessons.zennolab.com/captchas/recaptcha/v3.php?level=beta", websiteKey="6Le0xVgUAAAAAIt20XEB4rVhYOODgTl00d8juDob", - min_score=0.9) + minScore=0.9) nums = 3 diff --git a/examples/tspd.py b/examples/tspd.py new file mode 100644 index 0000000..3afb8e7 --- /dev/null +++ b/examples/tspd.py @@ -0,0 +1,53 @@ +import os +import time +import asyncio + +from capmonstercloudclient.requests import TspdCustomTaskRequest, ProxyInfo +from capmonstercloudclient import ClientOptions, CapMonsterClient + + +async def solve_captcha_sync(num_requests): + return [await cap_monster_client.solve_captcha(tspd_request) for _ in range(num_requests)] + + +async def solve_captcha_async(num_requests): + tasks = [asyncio.create_task(cap_monster_client.solve_captcha(tspd_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) + + proxy = ProxyInfo( + proxyType="http", + proxyAddress="8.8.8.8", + proxyPort=8080, + proxyLogin="proxyLoginHere", + proxyPassword="proxyPasswordHere" + ) + metadata = { + "tspdCookie": "TS386a400d029=08267010245; TS386a400d078=08267dbb3b0c", + "htmlPageBase64": "PCFET0NUWVBFIGh0bWw+PC9odG1sPg==" + } + tspd_request = TspdCustomTaskRequest( + websiteUrl='https://example.com', + userAgent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36', + metadata=metadata, + proxy=proxy, + ) + + 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} ' \ + f'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} ' \ + f'resp/sec\nsolution: {async_responses[0]}') diff --git a/pyproject.toml b/pyproject.toml index 7fd26b9..6a0bd46 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,3 @@ [build-system] -requires = ["setuptools"] +requires = ["setuptools>=61", "wheel"] build-backend = "setuptools.build_meta" \ No newline at end of file diff --git a/setup.py b/setup.py index d04f630..ba5151d 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ sys.exit(-1) from pathlib import Path -from pkg_resources import parse_requirements +#from pkg_resources import parse_requirements from setuptools import setup @@ -15,8 +15,8 @@ AUTHOR = 'Andrey Ilyin' with open('capmonstercloud_client/version.txt', 'r') as f: VERSION = f.read() -with open("requirements.txt", "rt") as requirements_txt: - REQUIRED = [str(requirement) for requirement in parse_requirements(requirements_txt)] +with open("requirements.txt") as f: + REQUIRED = [line.strip() for line in f if line.strip() and not line.startswith("#")] URL='https://github.com/ZennoLab/capmonstercloud-client-python' this_directory = Path(__file__).parent diff --git a/test/castle_test.py b/test/castle_test.py new file mode 100644 index 0000000..b8a6063 --- /dev/null +++ b/test/castle_test.py @@ -0,0 +1,96 @@ +import unittest + +from pydantic.error_wrappers import ValidationError +from capmonstercloudclient.requests import CastleCustomTaskRequest + + +class CastleCustomTaskRequestTest(unittest.TestCase): + websiteUrlExample = "https://www.example.com" + websiteKeyExample = "pk_1Tk5Yzr1WFzxrJCh7WzMZzY1rHpaOtdK" + userAgentExample = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36" + wUrlExample = "https://s.rsg.sc/auth/js/20251234bgef/build/cw.js" + swUrlExample = "https://s.rsg.sc/auth/js/20251234bgef/build/csw.js" + + def test_castle_request_required_fields(self): + required_fields = ["type", "class", "websiteURL", "websiteKey", "metadata"] + metadata_required_fields = ["wUrl", "swUrl"] + metadata_example = { + "wUrl": self.wUrlExample, + "swUrl": self.swUrlExample, + } + request = CastleCustomTaskRequest( + websiteUrl=self.websiteUrlExample, + websiteKey=self.websiteKeyExample, + metadata=metadata_example, + ) + 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.', + ) + self.assertEqual(task_dictionary["class"], "Castle") + self.assertEqual(task_dictionary["type"], "CustomTask") + + def test_castle_metadata_validation(self): + base_kwargs = { + "websiteUrl": self.websiteUrlExample, + "websiteKey": self.websiteKeyExample, + "metadata": {} + } + self.assertRaises(TypeError, CastleCustomTaskRequest, **base_kwargs) + base_kwargs["metadata"]["wUrl"] = self.wUrlExample + self.assertRaises(TypeError, CastleCustomTaskRequest, **base_kwargs) + base_kwargs["metadata"]["swUrl"] = self.swUrlExample + CastleCustomTaskRequest(**base_kwargs) + + def test_castle_metadata_with_count(self): + metadata_example = { + "wUrl": self.wUrlExample, + "swUrl": self.swUrlExample, + "count": 5, + } + request = CastleCustomTaskRequest( + websiteUrl=self.websiteUrlExample, + websiteKey=self.websiteKeyExample, + metadata=metadata_example, + ) + task_dictionary = request.getTaskDict() + self.assertEqual(task_dictionary["metadata"]["count"], 5) + + def test_castle_missing_fields(self): + base_kwargs = {} + self.assertRaises(ValidationError, CastleCustomTaskRequest, **base_kwargs) + base_kwargs.update({"websiteUrl": self.websiteUrlExample}) + self.assertRaises(ValidationError, CastleCustomTaskRequest, **base_kwargs) + base_kwargs.update({"websiteKey": self.websiteKeyExample}) + self.assertRaises(ValidationError, CastleCustomTaskRequest, **base_kwargs) + metadata_example = { + "wUrl": self.wUrlExample, + "swUrl": self.swUrlExample, + } + base_kwargs.update({"metadata": metadata_example}) + CastleCustomTaskRequest(**base_kwargs) + + def test_castle_optional_fields(self): + metadata_example = { + "wUrl": self.wUrlExample, + "swUrl": self.swUrlExample, + } + request = CastleCustomTaskRequest( + websiteUrl=self.websiteUrlExample, + websiteKey=self.websiteKeyExample, + metadata=metadata_example, + userAgent=self.userAgentExample, + ) + task_dictionary = request.getTaskDict() + self.assertEqual(task_dictionary["userAgent"], self.userAgentExample) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/datadome_test.py b/test/datadome_test.py index daea79e..37e502c 100644 --- a/test/datadome_test.py +++ b/test/datadome_test.py @@ -1,7 +1,7 @@ import unittest from pydantic.error_wrappers import ValidationError -from capmonstercloudclient.requests import DataDomeCustomTaskRequest +from capmonstercloudclient.requests import DataDomeCustomTaskRequest, ProxyInfo class DataDomeCustomTaskRequestTest(unittest.TestCase): @@ -11,6 +11,23 @@ class DataDomeCustomTaskRequestTest(unittest.TestCase): "datadomeCookie": "datadome=d3k5rbDsu8cq0kmPHISS3hsC3f4qeL_K12~G33PrE4fbkmDYSul6l0Ze_aG5sUHLKG0676UpTv6GFvUgIActglZF33GTodOoRhEDkMMsuWTodlYa3YYQ9xKy9J89PAWh", } + def setUp(self): + self.proxy = ProxyInfo( + proxyType="http", + proxyAddress="8.8.8.8", + proxyPort=8080, + proxyLogin="proxyLoginHere", + proxyPassword="proxyPasswordHere" + ) + + def testRequiresProxy(self): + self.assertRaises( + RuntimeError, + DataDomeCustomTaskRequest, + websiteUrl=DataDomeCustomTaskRequestTest.websiteUrlExample, + metadata=DataDomeCustomTaskRequestTest.metadataExample, + ) + def testCaptchaInputTypes(self): metadataListUrl = DataDomeCustomTaskRequestTest.metadataExample.copy() metadataListUrl["captchaUrl"] = list(metadataListUrl["captchaUrl"]) @@ -25,27 +42,32 @@ def testCaptchaInputTypes(self): request = DataDomeCustomTaskRequest( websiteUrl=DataDomeCustomTaskRequestTest.websiteUrlExample, metadata=metadataListUrl, + proxy=self.proxy, ) with self.assertRaises(ValidationError): request = DataDomeCustomTaskRequest( websiteUrl=DataDomeCustomTaskRequestTest.websiteUrlExample, metadata=metadataListImage, + proxy=self.proxy, ) with self.assertRaises(TypeError): request = DataDomeCustomTaskRequest( websiteUrl=DataDomeCustomTaskRequestTest.websiteUrlExample, metadata=metadataListExtra, + proxy=self.proxy, ) def testAllRequiredFieldsFilling(self): - required_fields = ["class", "type", "websiteURL", "metadata"] + required_fields = ["class", "type", "websiteURL", "metadata", + "proxyType", "proxyAddress", "proxyPort", "proxyLogin", "proxyPassword"] metadata_fields = ["datadomeCookie"] one_of_fields = [["captchaUrl", "htmlPageBase64"]] request = DataDomeCustomTaskRequest( websiteUrl=DataDomeCustomTaskRequestTest.websiteUrlExample, metadata=DataDomeCustomTaskRequestTest.metadataExample, + proxy=self.proxy, ) request_dict = request.getTaskDict() for i in required_fields: @@ -67,6 +89,15 @@ def testAllRequiredFieldsFilling(self): self.assertEqual(request_dict["class"], "DataDome") self.assertEqual(request_dict["type"], "CustomTask") + def testDomainsNotSentWhenNone(self): + request = DataDomeCustomTaskRequest( + websiteUrl=DataDomeCustomTaskRequestTest.websiteUrlExample, + metadata=DataDomeCustomTaskRequestTest.metadataExample, + proxy=self.proxy, + ) + request_dict = request.getTaskDict() + self.assertNotIn("domains", request_dict) + if __name__ == "__main__": unittest.main() diff --git a/test/hunt_test.py b/test/hunt_test.py new file mode 100644 index 0000000..ecaa60e --- /dev/null +++ b/test/hunt_test.py @@ -0,0 +1,112 @@ +import unittest + +from pydantic import ValidationError +from capmonstercloudclient.requests import HuntCustomTaskRequest, ProxyInfo + + +class HuntCustomTaskRequestTest(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" + apiGetLibExample = "https://example.com/hd-api/external/apps/a2157wab1045d68672a63557e0n2a77edbfd15ea/api.js" + dataExample = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" + + def setUp(self): + self.proxy = ProxyInfo( + proxyType="http", + proxyAddress="8.8.8.8", + proxyPort=8080, + proxyLogin="proxyLoginHere", + proxyPassword="proxyPasswordHere" + ) + + def test_hunt_request_required_fields(self): + required_fields = ["type", "class", "websiteURL", "metadata", + "proxyType", "proxyAddress", "proxyPort", "proxyLogin", "proxyPassword"] + metadata_required_fields = ["apiGetLib"] + metadata_example = { + "apiGetLib": self.apiGetLibExample, + } + request = HuntCustomTaskRequest( + websiteUrl=self.websiteUrlExample, + metadata=metadata_example, + proxy=self.proxy, + ) + 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.', + ) + self.assertEqual(task_dictionary["class"], "HUNT") + self.assertEqual(task_dictionary["type"], "CustomTask") + + def test_hunt_metadata_validation(self): + base_kwargs = { + "websiteUrl": self.websiteUrlExample, + "metadata": {}, + "proxy": self.proxy, + } + self.assertRaises(TypeError, HuntCustomTaskRequest, **base_kwargs) + base_kwargs["metadata"]["apiGetLib"] = self.apiGetLibExample + HuntCustomTaskRequest(**base_kwargs) + + def test_hunt_metadata_with_data(self): + metadata_example = { + "apiGetLib": self.apiGetLibExample, + "data": self.dataExample, + } + request = HuntCustomTaskRequest( + websiteUrl=self.websiteUrlExample, + metadata=metadata_example, + proxy=self.proxy, + ) + task_dictionary = request.getTaskDict() + self.assertEqual(task_dictionary["metadata"]["data"], self.dataExample) + + def test_hunt_missing_fields(self): + metadata_example = { + "apiGetLib": self.apiGetLibExample, + } + # No proxy -> RuntimeError + base_kwargs = {} + self.assertRaises(RuntimeError, HuntCustomTaskRequest, **base_kwargs) + # With proxy but missing other required fields + base_kwargs.update({"proxy": self.proxy}) + self.assertRaises(ValidationError, HuntCustomTaskRequest, **base_kwargs) + base_kwargs.update({"websiteUrl": self.websiteUrlExample}) + self.assertRaises(ValidationError, HuntCustomTaskRequest, **base_kwargs) + base_kwargs.update({"metadata": metadata_example}) + HuntCustomTaskRequest(**base_kwargs) + + def test_hunt_requires_proxy(self): + metadata_example = { + "apiGetLib": self.apiGetLibExample, + } + self.assertRaises( + RuntimeError, + HuntCustomTaskRequest, + websiteUrl=self.websiteUrlExample, + metadata=metadata_example, + ) + + def test_hunt_optional_useragent(self): + metadata_example = { + "apiGetLib": self.apiGetLibExample, + } + request = HuntCustomTaskRequest( + websiteUrl=self.websiteUrlExample, + metadata=metadata_example, + proxy=self.proxy, + userAgent=self.userAgentExample, + ) + task_dictionary = request.getTaskDict() + self.assertEqual(task_dictionary["userAgent"], self.userAgentExample) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/requests_generation_test.py b/test/requests_generation_test.py index 3a7eb55..1874431 100644 --- a/test/requests_generation_test.py +++ b/test/requests_generation_test.py @@ -15,6 +15,7 @@ def test_rcv2(self): "recaptchaDataSValue", "userAgent", "cookies", + "isInvisible", ] rc2_no_proxy_type = "NoCaptchaTask" request = requests.RecaptchaV2Request( @@ -23,6 +24,7 @@ def test_rcv2(self): dataSValue="sdfa", userAgent="fasdf", cookies="asdfsdf", + isInvisible=True, ) task = request.getTaskDict() for key in default_keys: @@ -49,6 +51,7 @@ def test_rcv2(self): dataSValue="data s value", userAgent="user agent", cookies="cookies", + isInvisible=True, proxy=proxy, ) proxy_task = proxy_request.getTaskDict() @@ -67,7 +70,7 @@ def test_rcv3(self): request = requests.RecaptchaV3ProxylessRequest( websiteUrl="some_url", websiteKey="some_key", - min_score=0.2, + minScore=0.2, pageAction="asdfsfd", ) task = request.getTaskDict() @@ -83,6 +86,34 @@ def test_rcv3(self): msg=f"Task type of ReCaptchaV3 not equal to {rc3_type}", ) + # validate_min_score: valid boundary values + for valid_score in [0.1, 0.5, 0.9]: + req = requests.RecaptchaV3ProxylessRequest( + websiteUrl="some_url", + websiteKey="some_key", + minScore=valid_score, + ) + self.assertEqual(req.minScore, valid_score) + + # validate_min_score: invalid values should raise ValueError + for invalid_score in [0.0, 0.09, 0.91, 1.0, -0.1, 2.0]: + with self.assertRaises( + ValueError, + msg=f"minScore={invalid_score} should raise ValueError", + ): + requests.RecaptchaV3ProxylessRequest( + websiteUrl="some_url", + websiteKey="some_key", + minScore=invalid_score, + ) + + # validate_min_score: None should be accepted + req_none = requests.RecaptchaV3ProxylessRequest( + websiteUrl="some_url", + websiteKey="some_key", + ) + self.assertIsNone(req_none.minScore) + def test_rcv2_enterprise(self): rcv2e_type = "RecaptchaV2EnterpriseTask" default_keys = [ @@ -91,12 +122,16 @@ def test_rcv2_enterprise(self): "websiteKey", "enterprisePayload", "apiDomain", + "userAgent", + "cookies", ] request = requests.RecaptchaV2EnterpriseRequest( websiteUrl="some_url", websiteKey="some_key", enterprisePayload="payload", apiDomain="asdfasdf", + userAgent="user agent", + cookies="cookies", ) task = request.getTaskDict() for key in default_keys: @@ -122,6 +157,8 @@ def test_rcv2_enterprise(self): websiteKey="some_key", enterprisePayload="payload", apiDomain="asdfasdf", + userAgent="user agent", + cookies="cookies", proxy=proxy, ) diff --git a/test/tendi_test.py b/test/tendi_test.py index a55dbaf..77f145e 100644 --- a/test/tendi_test.py +++ b/test/tendi_test.py @@ -42,6 +42,32 @@ def testAllRequiredFieldsFilling(self): self.assertEqual(request_dict["class"], "TenDI") self.assertEqual(request_dict["type"], "CustomTask") + def testMetadataOptional(self): + request = TenDiCustomTaskRequest( + websiteUrl=TenDiCustomTaskRequestTest.websiteUrlExample, + websiteKey=TenDiCustomTaskRequestTest.websiteKeyExample, + ) + request_dict = request.getTaskDict() + self.assertNotIn("metadata", request_dict) + + def testMetadataWithCaptchaUrl(self): + request = TenDiCustomTaskRequest( + websiteUrl=TenDiCustomTaskRequestTest.websiteUrlExample, + websiteKey=TenDiCustomTaskRequestTest.websiteKeyExample, + metadata={"captchaUrl": "https://example.com/TCaptcha.js"}, + ) + request_dict = request.getTaskDict() + self.assertEqual(request_dict["metadata"]["captchaUrl"], "https://example.com/TCaptcha.js") + + def testMetadataInvalidKey(self): + self.assertRaises( + TypeError, + TenDiCustomTaskRequest, + websiteUrl=TenDiCustomTaskRequestTest.websiteUrlExample, + websiteKey=TenDiCustomTaskRequestTest.websiteKeyExample, + metadata={"invalidKey": "value"}, + ) + if __name__ == "__main__": unittest.main() diff --git a/test/tspd_test.py b/test/tspd_test.py new file mode 100644 index 0000000..00079ca --- /dev/null +++ b/test/tspd_test.py @@ -0,0 +1,96 @@ +import unittest + +from pydantic import ValidationError +from capmonstercloudclient.requests import TspdCustomTaskRequest, ProxyInfo + + +class TspdCustomTaskRequestTest(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" + tspdCookieExample = "TS386a400d029=08267010245; TS386a400d078=08267dbb3b0c" + htmlPageBase64Example = "PCFET0NUWVBFIGh0bWw+PC9odG1sPg==" + + def setUp(self): + self.proxy = ProxyInfo( + proxyType="http", + proxyAddress="8.8.8.8", + proxyPort=8080, + proxyLogin="proxyLoginHere", + proxyPassword="proxyPasswordHere" + ) + + def test_tspd_request_required_fields(self): + required_fields = ["type", "class", "websiteURL", "metadata", "userAgent", + "proxyType", "proxyAddress", "proxyPort", "proxyLogin", "proxyPassword"] + metadata_required_fields = ["tspdCookie", "htmlPageBase64"] + metadata_example = { + "tspdCookie": self.tspdCookieExample, + "htmlPageBase64": self.htmlPageBase64Example, + } + request = TspdCustomTaskRequest( + websiteUrl=self.websiteUrlExample, + userAgent=self.userAgentExample, + metadata=metadata_example, + proxy=self.proxy, + ) + 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.', + ) + self.assertEqual(task_dictionary["class"], "tspd") + self.assertEqual(task_dictionary["type"], "CustomTask") + + def test_tspd_metadata_validation(self): + base_kwargs = { + "websiteUrl": self.websiteUrlExample, + "userAgent": self.userAgentExample, + "metadata": {}, + "proxy": self.proxy, + } + self.assertRaises(TypeError, TspdCustomTaskRequest, **base_kwargs) + base_kwargs["metadata"]["tspdCookie"] = self.tspdCookieExample + self.assertRaises(TypeError, TspdCustomTaskRequest, **base_kwargs) + base_kwargs["metadata"]["htmlPageBase64"] = self.htmlPageBase64Example + TspdCustomTaskRequest(**base_kwargs) + + def test_tspd_missing_fields(self): + metadata_example = { + "tspdCookie": self.tspdCookieExample, + "htmlPageBase64": self.htmlPageBase64Example, + } + # No proxy -> RuntimeError + base_kwargs = {} + self.assertRaises(RuntimeError, TspdCustomTaskRequest, **base_kwargs) + # With proxy but missing other required fields + base_kwargs.update({"proxy": self.proxy}) + self.assertRaises(ValidationError, TspdCustomTaskRequest, **base_kwargs) + base_kwargs.update({"websiteUrl": self.websiteUrlExample}) + self.assertRaises(ValidationError, TspdCustomTaskRequest, **base_kwargs) + base_kwargs.update({"userAgent": self.userAgentExample}) + self.assertRaises(ValidationError, TspdCustomTaskRequest, **base_kwargs) + base_kwargs.update({"metadata": metadata_example}) + TspdCustomTaskRequest(**base_kwargs) + + def test_tspd_requires_proxy(self): + metadata_example = { + "tspdCookie": self.tspdCookieExample, + "htmlPageBase64": self.htmlPageBase64Example, + } + self.assertRaises( + RuntimeError, + TspdCustomTaskRequest, + websiteUrl=self.websiteUrlExample, + userAgent=self.userAgentExample, + metadata=metadata_example, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/yidun_test.py b/test/yidun_test.py index 371b5f2..fff8316 100644 --- a/test/yidun_test.py +++ b/test/yidun_test.py @@ -41,6 +41,33 @@ def testAllRequiredFieldsFilling(self): self.assertEqual(request_dict["type"], "YidunTask") + def testOptionalFields(self): + request = YidunRequest( + websiteUrl=self.websiteUrlExample, + websiteKey=self.websiteKeyExample, + userAgent=self.userAgentExample, + yidunGetLib="https://example.com/yidun.js", + yidunApiServerSubdomain="api.example.com", + challenge="challenge_value", + hcg="hash_value", + hct=12345, + ) + request_dict = request.getTaskDict() + self.assertEqual(request_dict["yidunGetLib"], "https://example.com/yidun.js") + self.assertEqual(request_dict["yidunApiServerSubdomain"], "api.example.com") + self.assertEqual(request_dict["challenge"], "challenge_value") + self.assertEqual(request_dict["hcg"], "hash_value") + self.assertEqual(request_dict["hct"], 12345) + + def testOptionalFieldsNotSentWhenNone(self): + request = YidunRequest( + websiteUrl=self.websiteUrlExample, + websiteKey=self.websiteKeyExample, + ) + request_dict = request.getTaskDict() + for field in ["userAgent", "yidunGetLib", "yidunApiServerSubdomain", "challenge", "hcg", "hct"]: + self.assertNotIn(field, request_dict) + if __name__ == "__main__": unittest.main()