From 54415df8ae446e2b6ab8450881e8a4f8bfc31132 Mon Sep 17 00:00:00 2001 From: "pavel.grinkevich" Date: Wed, 14 Jan 2026 14:28:01 +0000 Subject: [PATCH 1/4] altcha, rc3e, imperva, wroom, client_proxies --- .../CapMonsterCloudClient.py | 28 ++++++- capmonstercloud_client/GetResultTimeouts.py | 6 ++ capmonstercloud_client/clientOptions.py | 5 +- capmonstercloud_client/exceptions.py | 2 + .../requests/AltchaCustomTaskRequest.py | 36 +++++++++ .../requests/CustomTaskRequestBase.py | 4 +- .../requests/FuncaptchaRequest.py | 3 + .../requests/ImpervaCustomTaskRequest.py | 20 +++-- .../requests/RecaptchaV2EnterpiseRequest.py | 3 + .../requests/RecaptchaV3EnterpriseRequest.py | 22 ++++++ .../requests/TurnstileRequest.py | 12 +-- capmonstercloud_client/requests/__init__.py | 5 +- capmonstercloud_client/requests/proxy_info.py | 20 +++++ examples/altcha.py | 49 ++++++++++++ examples/cf_waitroom.py | 37 +++++++++ examples/client_proxy.py | 50 ++++++++++++ examples/imperva.py | 11 ++- examples/recaptchaV3Enterprise.py | 41 ++++++++++ examples/turnstile_cf.py | 30 +++++++ test/altcha_test.py | 79 +++++++++++++++++++ test/imperva_request_test.py | 20 ++++- test/recaptchaV3Enterprise_test.py | 62 +++++++++++++++ 22 files changed, 519 insertions(+), 26 deletions(-) create mode 100644 capmonstercloud_client/requests/AltchaCustomTaskRequest.py create mode 100644 capmonstercloud_client/requests/RecaptchaV3EnterpriseRequest.py create mode 100644 examples/altcha.py create mode 100644 examples/cf_waitroom.py create mode 100644 examples/client_proxy.py create mode 100644 examples/recaptchaV3Enterprise.py create mode 100644 examples/turnstile_cf.py create mode 100644 test/altcha_test.py create mode 100644 test/recaptchaV3Enterprise_test.py diff --git a/capmonstercloud_client/CapMonsterCloudClient.py b/capmonstercloud_client/CapMonsterCloudClient.py index 21de793..34c8983 100644 --- a/capmonstercloud_client/CapMonsterCloudClient.py +++ b/capmonstercloud_client/CapMonsterCloudClient.py @@ -17,6 +17,7 @@ ((RecaptchaV2Request,), getRecaptchaV2Timeouts), ((RecaptchaV2EnterpriseRequest,), getRecaptchaV2EnterpriseTimeouts), ((RecaptchaV3ProxylessRequest), getRecaptchaV3Timeouts), + ((RecaptchaV3EnterpriseRequest), getRecaptchaV3Timeouts), ((ImageToTextRequest), getImage2TextTimeouts), ((FuncaptchaRequest,), getFuncaptchaTimeouts), ((HcaptchaRequest,), getHcaptchaTimeouts), @@ -35,6 +36,8 @@ ((YidunRequest), getYidunTimeouts), ((TemuCustomTaskRequest), getTemuTimeouts), ((ProsopoTaskRequest), getProsopoTimeouts), + ((YidunRequest), getImage2TextTimeouts), + ((AltchaCustomTaskRequest), getAltchaTimeouts), ) @@ -44,6 +47,14 @@ def __init__(self, self.options = options self._headers = {'User-Agent': f'Zennolab.CapMonsterCloud.Client.Python/{parseVersion()}'} + if self.options.client_proxy: + if self.options.client_proxy.proxyType not in ['http', 'https']: + raise UnsupportedProxyTypeError(f'Supported client proxy types are: [HTTP, HTTPS]') + if self.options.client_proxy.proxyLogin and self.options.client_proxy.proxyPassword: + auth = f"{self.options.client_proxy.proxyLogin}:{self.options.client_proxy.proxyPassword}@" + self.client_proxy_string = f"{self.options.client_proxy.proxyType}://{auth}{self.options.client_proxy.proxyAddress}:{self.options.client_proxy.proxyPort}" + else: + self.client_proxy_string = None @property def headers(self): @@ -56,7 +67,8 @@ async def get_balance(self) -> Dict[str, Union[int, float, str]]: async with aiohttp.ClientSession() as session: async with session.post(url=self.options.service_url + '/getBalance', json=body, - timeout=aiohttp.ClientTimeout(total=self.options.client_timeout)) as resp: + timeout=aiohttp.ClientTimeout(total=self.options.client_timeout), + proxy=self.client_proxy_string) as resp: if resp.status != 200: raise HTTPException(f'Cannot create task. Status code: {resp.status}.') result = await resp.json(content_type=None) @@ -69,6 +81,7 @@ async def solve_captcha(self, request: Union[ RecaptchaV2EnterpriseRequest, RecaptchaV2Request, RecaptchaV3ProxylessRequest, + RecaptchaV3EnterpriseRequest, RecaptchaComplexImageTaskRequest, ImageToTextRequest, FuncaptchaRequest, @@ -86,8 +99,10 @@ async def solve_captcha(self, request: Union[ RecognitionComplexImageTaskRequest, MTCaptchaRequest, YidunRequest, + AltchaCustomTaskRequest, TemuCustomTaskRequest, ProsopoTaskRequest], + AltchaCustomTaskRequest], ) -> Dict[str, str]: ''' Non-blocking method for captcha solving. @@ -106,6 +121,7 @@ async def _solve(self, request: Union[ RecaptchaV2EnterpriseRequest, RecaptchaV2Request, RecaptchaV3ProxylessRequest, + RecaptchaV3EnterpriseRequest, RecaptchaComplexImageTaskRequest, ImageToTextRequest, FuncaptchaRequest, @@ -124,7 +140,8 @@ async def _solve(self, request: Union[ MTCaptchaRequest, YidunRequest, TemuCustomTaskRequest, - ProsopoTaskRequest], + ProsopoTaskRequest, + AltchaCustomTaskRequest], timeouts: GetResultTimeouts, ) -> Dict[str, str]: @@ -168,7 +185,8 @@ async def _getTaskResult(self, task_id: str) -> Dict[str, Union[int, str, None]] async with session.post(url=self.options.service_url + '/getTaskResult', json=body, timeout=aiohttp.ClientTimeout(total=self.options.client_timeout), - headers=self.headers) as resp: + headers=self.headers, + proxy=self.client_proxy_string) as resp: if resp.status != 200: if resp.status == 500: return {'errorId': 0, 'status': 'processing'} @@ -184,11 +202,13 @@ async def _createTask(self, request: BaseRequest) -> Dict[str, Union[str, int]]: "task": task, "softId": self.options.default_soft_id } + async with aiohttp.ClientSession() as session: async with session.post(url=self.options.service_url + '/createTask', json=body, timeout=aiohttp.ClientTimeout(total=self.options.client_timeout), - headers=self.headers) as resp: + headers=self.headers, + proxy=self.client_proxy_string) as resp: if resp.status != 200: raise HTTPException(f'Cannot create task. Status code: {resp.status}.') else: diff --git a/capmonstercloud_client/GetResultTimeouts.py b/capmonstercloud_client/GetResultTimeouts.py index bd86b14..202262b 100644 --- a/capmonstercloud_client/GetResultTimeouts.py +++ b/capmonstercloud_client/GetResultTimeouts.py @@ -17,6 +17,9 @@ def getRecaptchaV2EnterpriseTimeouts() -> GetResultTimeouts: def getRecaptchaV3Timeouts() -> GetResultTimeouts: return GetResultTimeouts(1, 10, 3, 180) +def getRecaptchaV3EnterpriseTimeouts() -> GetResultTimeouts: + return GetResultTimeouts(1, 10, 3, 150) + def getImage2TextTimeouts() -> GetResultTimeouts: return GetResultTimeouts(0.35, 0, 0.2, 10) @@ -50,6 +53,9 @@ def getBinanceTimeouts() -> GetResultTimeouts: def getImpervaTimeouts() -> GetResultTimeouts: return GetResultTimeouts(1, 0, 1, 20) +def getAltchaTimeouts() -> GetResultTimeouts: + return GetResultTimeouts(1, 0, 1, 50) + def getCITTimeouts() -> GetResultTimeouts: return GetResultTimeouts(0.35, 0, 0.2, 10) diff --git a/capmonstercloud_client/clientOptions.py b/capmonstercloud_client/clientOptions.py index 5e58501..5d81077 100644 --- a/capmonstercloud_client/clientOptions.py +++ b/capmonstercloud_client/clientOptions.py @@ -1,11 +1,14 @@ from pydantic import BaseModel, validator, Field - +from .requests import ClientProxyInfo +from typing import Optional class ClientOptions(BaseModel): api_key: str + client_proxy: Optional[ClientProxyInfo] = None service_url: str = Field(default="https://api.capmonster.cloud") default_soft_id: int = Field(default=55) client_timeout: float = Field(default=20.0) + @validator('api_key') def validate_api_key(cls, value): diff --git a/capmonstercloud_client/exceptions.py b/capmonstercloud_client/exceptions.py index f523ff8..a6d828d 100644 --- a/capmonstercloud_client/exceptions.py +++ b/capmonstercloud_client/exceptions.py @@ -24,6 +24,8 @@ class TaskNotDefinedError(BaseError): class ExtraParamsError(BaseError): pass +class UnsupportedProxyTypeError(BaseError): + pass class UserAgentNotDefinedError(BaseError): diff --git a/capmonstercloud_client/requests/AltchaCustomTaskRequest.py b/capmonstercloud_client/requests/AltchaCustomTaskRequest.py new file mode 100644 index 0000000..d7bb9c4 --- /dev/null +++ b/capmonstercloud_client/requests/AltchaCustomTaskRequest.py @@ -0,0 +1,36 @@ +from typing import Dict, Union +from pydantic import Field, validator + +from .CustomTaskRequestBase import CustomTaskRequestBase + +class AltchaCustomTaskRequest(CustomTaskRequestBase): + captchaClass: str = Field(default='altcha') + websiteKey: str = Field() + metadata : Dict[str, str] + + @validator('metadata') + def validate_metadata(cls, value): + for key in ['challenge', 'iterations', 'salt', 'signature']: + if value.get(key) is None: + raise TypeError(f'Expect that {key} will be defined.') + else: + if not isinstance(value.get(key), str): + raise TypeError(f'Expect that {key} will be str.') + 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/CustomTaskRequestBase.py b/capmonstercloud_client/requests/CustomTaskRequestBase.py index 4508e20..a9a16f2 100644 --- a/capmonstercloud_client/requests/CustomTaskRequestBase.py +++ b/capmonstercloud_client/requests/CustomTaskRequestBase.py @@ -6,5 +6,5 @@ class CustomTaskRequestBase(BaseRequestWithProxy): captchaClass: str # Class(subtype) of ComplexImageTask type: str = "CustomTask" # Recognition task type websiteUrl: str # Address of a webpage with captcha - userAgent: Optional[str] = None # It is required that you use a signature of a modern browser - domains: Optional[List[str]] = None # Collection with base64 encoded images. Must be populated if not. + userAgent: Optional[str] = None + domains: Optional[List[str]] = None diff --git a/capmonstercloud_client/requests/FuncaptchaRequest.py b/capmonstercloud_client/requests/FuncaptchaRequest.py index 9e44156..9646c8e 100644 --- a/capmonstercloud_client/requests/FuncaptchaRequest.py +++ b/capmonstercloud_client/requests/FuncaptchaRequest.py @@ -9,6 +9,7 @@ class FuncaptchaRequest(BaseRequestWithProxy): websitePublicKey: str funcaptchaApiJSSubdomain: Optional[str] = Field(default=None) data: Optional[str] = Field(default=None) + cookies: Optional[str] = Field(default=None) def getTaskDict(self) -> Dict[str, Union[str, int, bool]]: task = {} @@ -26,4 +27,6 @@ def getTaskDict(self) -> Dict[str, Union[str, int, bool]]: task['funcaptchaApiJSSubdomain'] = self.funcaptchaApiJSSubdomain if self.data is not None: task['data'] = self.data + if self.cookies is not None: + task['cookies'] = self.cookies return task \ No newline at end of file diff --git a/capmonstercloud_client/requests/ImpervaCustomTaskRequest.py b/capmonstercloud_client/requests/ImpervaCustomTaskRequest.py index f103868..2b7ed8b 100644 --- a/capmonstercloud_client/requests/ImpervaCustomTaskRequest.py +++ b/capmonstercloud_client/requests/ImpervaCustomTaskRequest.py @@ -1,5 +1,5 @@ from typing import Dict, Union -from pydantic import Field, validator +from pydantic import Field, validator, model_validator from .CustomTaskRequestBase import CustomTaskRequestBase @@ -23,18 +23,24 @@ def validate_metadata(cls, value): raise TypeError(f'Expect that reese84UrlEndpoint will be str.') return value + @model_validator(mode='before') + def validate_imperva_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 - 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['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/RecaptchaV2EnterpiseRequest.py b/capmonstercloud_client/requests/RecaptchaV2EnterpiseRequest.py index e740983..db7dfed 100644 --- a/capmonstercloud_client/requests/RecaptchaV2EnterpiseRequest.py +++ b/capmonstercloud_client/requests/RecaptchaV2EnterpiseRequest.py @@ -9,6 +9,7 @@ class RecaptchaV2EnterpriseRequest(BaseRequestWithProxy): websiteKey: str enterprisePayload: Optional[str] = Field(default=None) apiDomain: Optional[str] = Field(default=None) + pageAction: Optional[str] = Field(default=None) def getTaskDict(self) -> Dict[str, Union[str, int]]: task = {} @@ -25,4 +26,6 @@ def getTaskDict(self) -> Dict[str, Union[str, int]]: task['enterprisePayload'] = {'s': self.enterprisePayload} if self.apiDomain is not None: task['apiDomain'] = self.apiDomain + if self.pageAction is not None: + task['pageAction'] = self.pageAction return task \ No newline at end of file diff --git a/capmonstercloud_client/requests/RecaptchaV3EnterpriseRequest.py b/capmonstercloud_client/requests/RecaptchaV3EnterpriseRequest.py new file mode 100644 index 0000000..32625c5 --- /dev/null +++ b/capmonstercloud_client/requests/RecaptchaV3EnterpriseRequest.py @@ -0,0 +1,22 @@ +from typing import Dict, Union, Optional +from pydantic import Field + +from .baseRequest import BaseRequest + +class RecaptchaV3EnterpriseRequest(BaseRequest): + type: str = Field(default='RecaptchaV3EnterpriseTask') + websiteUrl: str + websiteKey: str + minScore: Optional[float] = Field(default=None) + pageAction: Optional[str] = Field(default=None) + + def getTaskDict(self) -> Dict[str, Union[str, int, float]]: + task = {} + task['type'] = self.type + task['websiteURL'] = self.websiteUrl + task['websiteKey'] = self.websiteKey + 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/TurnstileRequest.py b/capmonstercloud_client/requests/TurnstileRequest.py index 6a525c2..4bc0776 100644 --- a/capmonstercloud_client/requests/TurnstileRequest.py +++ b/capmonstercloud_client/requests/TurnstileRequest.py @@ -19,7 +19,7 @@ class TurnstileRequest(BaseRequestWithProxy): @validator('cloudflareTaskType') def validate_cloudflare_task(cls, value): if value is not None: - if value not in ['cf_clearance', 'token']: + if value not in ['cf_clearance', 'token', 'wait_room']: raise ValueError(f'cloudflareTaskType could be "cf_clearance" if you need cookie or ' \ f'"token" if required token from Turnstile.') return value @@ -28,13 +28,13 @@ def validate_cloudflare_task(cls, value): def validate_cloudflare_type_token(self): if self.get('htmlPageBase64') is None: - if self.get('cloudflareTaskType') == 'cf_clearance': + if self.get('cloudflareTaskType') in ['cf_clearance', 'wait_room']: raise RuntimeError(f'Expect that "htmlPageBase64" will be filled ' \ - f'when cloudflareTaskType is "cf_clearance"') + f'when cloudflareTaskType is "cf_clearance" or "wait_room') if self.get('proxy') is None: - if self.get('cloudflareTaskType') == 'cf_clearance': - raise RuntimeError(f'You are working using queries, and you need cf_clearance cookies ' \ + if self.get('cloudflareTaskType') in ['cf_clearance', 'wait_room']: + raise RuntimeError(f'You are working using queries, and you need cf_clearance cookies or wait_room ' \ f'it is required that you need your proxies.') if self.get('cloudflareTaskType') == 'token': @@ -44,7 +44,7 @@ def validate_cloudflare_type_token(self): f'when "cloudflareTaskType" = "token".') if self.get('cloudflareTaskType') is not None: - if self.get('cloudflareTaskType') in ['cf_clearance', 'token']: + if self.get('cloudflareTaskType') in ['cf_clearance', 'token', 'wait_room']: if self.get('userAgent') is None: raise RuntimeError(f'Expect that userAgent will be filled ' \ f'when cloudflareTaskType specified.') diff --git a/capmonstercloud_client/requests/__init__.py b/capmonstercloud_client/requests/__init__.py index 2932039..29b8df3 100644 --- a/capmonstercloud_client/requests/__init__.py +++ b/capmonstercloud_client/requests/__init__.py @@ -2,6 +2,8 @@ from .RecaptchaV2Request import RecaptchaV2Request from .RecaptchaV2EnterpiseRequest import RecaptchaV2EnterpriseRequest from .RecaptchaV3ProxylessRequest import RecaptchaV3ProxylessRequest +from .RecaptchaV3EnterpriseRequest import RecaptchaV3EnterpriseRequest +from .RecaptchaV3EnterpriseRequest import RecaptchaV3EnterpriseRequest from .RecaptchaComplexImageTask import RecaptchaComplexImageTaskRequest from .HcaptchaRequest import HcaptchaRequest from .FuncaptchaRequest import FuncaptchaRequest @@ -22,7 +24,8 @@ from .YidunRequest import YidunRequest from .ProsopoTaskRequest import ProsopoTaskRequest from .TemuCustomTaskRequest import TemuCustomTaskRequest -from .proxy_info import ProxyInfo +from .AltchaCustomTaskRequest import AltchaCustomTaskRequest +from .proxy_info import ProxyInfo, ClientProxyInfo REQUESTS = ['RecaptchaV2EnterpiseRequest', 'RecaptchaV2Request', 'RecaptchaV3ProxylessRequest', 'RecaptchaComplexImageTaskRequest' diff --git a/capmonstercloud_client/requests/proxy_info.py b/capmonstercloud_client/requests/proxy_info.py index d7edaf7..3811d34 100644 --- a/capmonstercloud_client/requests/proxy_info.py +++ b/capmonstercloud_client/requests/proxy_info.py @@ -1,5 +1,6 @@ from pydantic import BaseModel, validator from .enums import ProxyTypes +from typing import Optional class ProxyInfo(BaseModel): proxyType: str @@ -19,3 +20,22 @@ def validate_port(cls, value): if not isinstance(value, int): raise TypeError(f'Expect that port value will be type, got {type(value)}') return value + +class ClientProxyInfo(BaseModel): + proxyType: str + proxyAddress: str + proxyPort: int + proxyLogin: Optional[str] = None + proxyPassword: Optional[str] = None + + @validator('proxyType') + def validate_proxy_type(cls, value): + if value not in ProxyTypes.list_values(): + raise ValueError(f'Expected that proxy type will be in {ProxyTypes.list_values()}, got "{value}"') + return value + + @validator('proxyPort') + def validate_port(cls, value): + if not isinstance(value, int): + raise TypeError(f'Expect that port value will be type, got {type(value)}') + return value \ No newline at end of file diff --git a/examples/altcha.py b/examples/altcha.py new file mode 100644 index 0000000..1b04a46 --- /dev/null +++ b/examples/altcha.py @@ -0,0 +1,49 @@ +import os +import time +import asyncio + +from capmonstercloudclient.requests import AltchaCustomTaskRequest +from capmonstercloudclient import ClientOptions, CapMonsterClient + + +async def solve_captcha_sync(num_requests): + return [await cap_monster_client.solve_captcha(altcha_request) for _ in range(num_requests)] + + +async def solve_captcha_async(num_requests): + tasks = [asyncio.create_task(cap_monster_client.solve_captcha(altcha_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 = { + "challenge": "challenge_string_here", + "iterations": "1000", + "salt": "salt_string_here", + "signature": "signature_string_here" + } + altcha_request = AltchaCustomTaskRequest( + websiteUrl='https://example.com/login', + websiteKey='altcha-public-key-123', + 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/cf_waitroom.py b/examples/cf_waitroom.py new file mode 100644 index 0000000..b92ee7a --- /dev/null +++ b/examples/cf_waitroom.py @@ -0,0 +1,37 @@ +import asyncio +from capmonstercloudclient import CapMonsterClient, ClientOptions +from capmonstercloudclient.requests import TurnstileRequest +from capmonstercloudclient.requests import ProxyInfo, ClientProxyInfo + +async def main(): + client_proxy = ClientProxyInfo( + proxyType='http', + proxyAddress='gw-us.zengw.io', # get at https://docs.zennolab.com/zennoproxy/introduction or your own proxy + proxyPort=8080, # get at https://docs.zennolab.com/zennoproxy/introduction or your own proxy + proxyLogin='login', # get at https://docs.zennolab.com/zennoproxy/introduction or your own proxy + proxyPassword='password' # get at https://docs.zennolab.com/zennoproxy/introduction or your own proxy + ) + client_options = ClientOptions(api_key="dac3599143bdfd88dfc41758c6cb8729", client_proxy=client_proxy) + cap_monster_client = CapMonsterClient(options=client_options) + + proxy = ProxyInfo( + proxyType='http', + proxyAddress='gw-us.zengw.io', # get at https://docs.zennolab.com/zennoproxy/introduction or your own proxy + proxyPort=8080, # get at https://docs.zennolab.com/zennoproxy/introduction or your own proxy + proxyLogin='login', # get at https://docs.zennolab.com/zennoproxy/introduction or your own proxy + proxyPassword='password' # get at https://docs.zennolab.com/zennoproxy/introduction or your own proxy + ) + + turnstile_request = TurnstileRequest( + websiteURL="https://resa.notredamedeparis.fr/", + websiteKey="xxxxx", + cloudflareTaskType="wait_room", + userAgent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36", + htmlPageBase64="PGh0bWwgbGFuZz0iZW4tVVMiIGRpcj0ibHRyIj48aGVhZD48dGl0bGU+SnVzdCBhIG1vbWVudC4uLjwvdGl0bGU+PG1ldGEgaHR0cC1lcXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7IGNoYXJzZXQ9VVRGLTgiPjxtZXRhIGh0dHAtZXF1aXY9IlgtVUEtQ29tcGF0aWJsZSIgY29udGVudD0iSUU9RWRnZSI+PG1ldGEgbmFtZT0icm9ib3RzIiBjb250ZW50PSJub2luZGV4LG5vZm9sbG93Ij48bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLGluaXRpYWwtc2NhbGU9MSI+PHN0eWxlPip7Ym94LXNpemluZzpib3JkZXItYm94O21hcmdpbjowO3BhZGRpbmc6MH1odG1se2xpbmUtaGVpZ2h0OjEuMTU7LXdlYmtpdC10ZXh0LXNpemUtYWRqdXN0OjEwMCU7Y29sb3I6IzMxMzEzMTtmb250LWZhbWlseTpzeXN0ZW0tdWksLWFwcGxlLXN5c3RlbSxCbGlua01hY1N5c3RlbUZvbnQsIlNlZ29lIFVJIixSb2JvdG8sIkhlbHZldGljYSBOZXVlIixBcmlhbCwiTm90byBTYW5zIixzYW5zLXNlcmlmLCJBcHBsZSBDb2xvciBFbW9qaSIsIlNlZ29lIFVJIEVtb2ppIiwiU2Vnb2UgVUkgU3ltYm9sIiwiTm90byBDb2xvciBFbW9qaSJ9Ym9keXtkaXNwbGF5OmZsZXg7ZmxleC1kaXJlY3Rpb246Y29sdW1uO2hlaWdodDoxMDB2aDttaW4taGVpZ2h0OjEwMHZofS5tYWluLWNvbnRlbnR7bWFyZ2luOjhyZW0gYXV0bztwYWRkaW5nLWxlZnQ6MS41cmVtO21heC13aWR0aDo2MHJlbX1AbWVkaWEgKHdpZHRoIDw9IDcyMHB4KXsubWFpbi1jb250ZW50e21hcmdpbi10b3A6NHJlbX19Lmgye2xpbmUtaGVpZ2h0OjIuMjVyZW07Zm9udC1zaXplOjEuNXJlbTtmb250LXdlaWdodDo1MDB9QG1lZGlhICh3aWR0aCA8PSA3MjBweCl7Lmgye2xpbmUtaGVpZ2h0OjEuNXJlbTtmb250LXNpemU6MS4yNXJlbX19I2NoYWxsZW5nZS1lcnJvci10ZXh0e2JhY2tncm91bmQtaW1hZ2U6dXJsKCJkYXRhOmltYWdlL3N2Zyt4bWw7YmFzZTY0LFBITjJaeUI0Yld4dWN6MGlhSFIwY0RvdkwzZDNkeTUzTXk1dmNtY3ZNakF3TUM5emRtY2lJSGRwWkhSb1BTSXpNaUlnYUdWcFoyaDBQU0l6TWlJZ1ptbHNiRDBpYm05dVpTSStQSEJoZEdnZ1ptbHNiRDBpSTBJeU1FWXdNeUlnWkQwaVRURTJJRE5oTVRNZ01UTWdNQ0F4SURBZ01UTWdNVE5CTVRNdU1ERTFJREV6TGpBeE5TQXdJREFnTUNBeE5pQXpiVEFnTWpSaE1URWdNVEVnTUNBeElERWdNVEV0TVRFZ01URXVNREVnTVRFdU1ERWdNQ0F3SURFdE1URWdNVEVpTHo0OGNHRjBhQ0JtYVd4c1BTSWpRakl3UmpBeklpQmtQU0pOTVRjdU1ETTRJREU0TGpZeE5VZ3hOQzQ0TjB3eE5DNDFOak1nT1M0MWFESXVOemd6ZW0wdE1TNHdPRFFnTVM0ME1qZHhMalkySURBZ01TNHdOVGN1TXpnNExqUXdOeTR6T0RrdU5EQTNMams1TkNBd0lDNDFPVFl0TGpRd055NDVPRFF0TGpNNU55NHpPUzB4TGpBMU55NHpPRGt0TGpZMUlEQXRNUzR3TlRZdExqTTRPUzB1TXprNExTNHpPRGt0TGpNNU9DMHVPVGcwSURBdExqVTVOeTR6T1RndExqazROUzQwTURZdExqTTVOeUF4TGpBMU5pMHVNemszSWk4K1BDOXpkbWMrIik7YmFja2dyb3VuZC1yZXBlYXQ6bm8tcmVwZWF0O2JhY2tncm91bmQtc2l6ZTpjb250YWluO3BhZGRpbmctbGVmdDozNHB4fUBtZWRpYSAocHJlZmVycy1jb2xvci1zY2hlbWU6IGRhcmspe2JvZHl7YmFja2dyb3VuZC1jb2xvcjojMjIyO2NvbG9yOiNkOWQ5ZDl9fTwvc3R5bGU+PG1ldGEgaHR0cC1lcXVpdj0icmVmcmVzaCIgY29udGVudD0iMzYwIj48c2NyaXB0IHNyYz0iL2Nkbi1jZ2kvY2hhbGxlbmdlLXBsYXRmb3JtL2gvYi9vcmNoZXN0cmF0ZS9jaGxfcGFnZS92MT9yYXk9OTk1MWIxNTVmODE5ZTk0YiI+PC9zY3JpcHQ+PHN0eWxlPip7Ym94LXNpemluZzpib3JkZXItYm94O21hcmdpbjowO3BhZGRpbmc6MH1odG1se2xpbmUtaGVpZ2h0OjEuMTU7LXdlYmtpdC10ZXh0LXNpemUtYWRqdXN0OjEwMCU7Y29sb3I6IzMxMzEzMTtmb250LWZhbWlseTpzeXN0ZW0tdWksLWFwcGxlLXN5c3RlbSxCbGlua01hY1N5c3RlbUZvbnQsIlNlZ29lIFVJIixSb2JvdG8sIkhlbHZldGljYSBOZXVlIixBcmlhbCwiTm90byBTYW5zIixzYW5zLXNlcmlmLCJBcHBsZSBDb2xvciBFbW9qaSIsIlNlZ29lIFVJIEVtb2ppIiwiU2Vnb2UgVUkgU3ltYm9sIiwiTm90byBDb2xvciBFbW9qaSJ9YnV0dG9ue2ZvbnQtZmFtaWx5OnN5c3RlbS11aSwtYXBwbGUtc3lzdGVtLEJsaW5rTWFjU3lzdGVtRm9udCwiU2Vnb2UgVUkiLFJvYm90bywiSGVsdmV0aWNhIE5ldWUiLEFyaWFsLCJOb3RvIFNhbnMiLHNhbnMtc2VyaWYsIkFwcGxlIENvbG9yIEVtb2ppIiwiU2Vnb2UgVUkgRW1vamkiLCJTZWdvZSBVSSBTeW1ib2wiLCJOb3RvIENvbG9yIEVtb2ppIn1ib2R5e2Rpc3BsYXk6ZmxleDtmbGV4LWRpcmVjdGlvbjpjb2x1bW47aGVpZ2h0OjEwMHZoO21pbi1oZWlnaHQ6MTAwdmh9Ym9keS50aGVtZS1kYXJre2JhY2tncm91bmQtY29sb3I6IzIyMjtjb2xvcjojZDlkOWQ5fWJvZHkudGhlbWUtZGFyayBhe2NvbG9yOiNmZmZ9Ym9keS50aGVtZS1kYXJrIGE6aG92ZXJ7dGV4dC1kZWNvcmF0aW9uOnVuZGVybGluZTtjb2xvcjojZWU3MzBhfWJvZHkudGhlbWUtZGFyayAubGRzLXJpbmcgZGl2e2JvcmRlci1jb2xvcjojOTk5IHJnYmEoMCwwLDAsMCkgcmdiYSgwLDAsMCwwKX1ib2R5LnRoZW1lLWRhcmsgLmZvbnQtcmVke2NvbG9yOiNiMjBmMDN9Ym9keS50aGVtZS1kYXJrIC5jdHAtYnV0dG9ue2JhY2tncm91bmQtY29sb3I6IzQ2OTNmZjtjb2xvcjojMWQxZDFkfWJvZHkudGhlbWUtZGFyayAjY2hhbGxlbmdlLXN1Y2Nlc3MtdGV4dHtiYWNrZ3JvdW5kLWltYWdlOnVybCgiZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlCNGJXeHVjejBpYUhSMGNEb3ZMM2QzZHk1M015NXZjbWN2TWpBd01DOXpkbWNpSUhkcFpIUm9QU0l6TWlJZ2FHVnBaMmgwUFNJek1pSWdabWxzYkQwaWJtOXVaU0lnZG1sbGQwSnZlRDBpTUNBd0lESTJJREkySWo0OGNHRjBhQ0JtYVd4c1BTSWpaRGxrT1dRNUlpQmtQU0pOTVRNZ01HRXhNeUF4TXlBd0lERWdNQ0F3SURJMklERXpJREV6SURBZ01DQXdJREF0TWpadE1DQXlOR0V4TVNBeE1TQXdJREVnTVNBd0xUSXlJREV4SURFeElEQWdNQ0F4SURBZ01qSWlMejQ4Y0dGMGFDQm1hV3hzUFNJalpEbGtPV1E1SWlCa1BTSnRNVEF1T1RVMUlERTJMakExTlMwekxqazFMVFF1TVRJMUxURXVORFExSURFdU16ZzFJRFV1TXpjZ05TNDJNU0E1TGpRNU5TMDVMall0TVM0ME1pMHhMalF3TlhvaUx6NDhMM04yWno0Iil9Ym9keS50aGVtZS1kYXJrICNjaGFsbGVuZ2UtZXJyb3ItdGV4dHtiYWNrZ3JvdW5kLWltYWdlOnVybCgiZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlCNGJXeHVjejBpYUhSMGNEb3ZMM2QzZHk1M015NXZjbWN2TWpBd01DOXpkbWNpSUhkcFpIUm9QU0l6TWlJZ2FHVnBaMmgwUFNJek1pSWdabWxzYkQwaWJtOXVaU0krUEhCaGRHZ2dabWxzYkQwaUkwSXlNRVl3TXlJZ1pEMGlUVEUySUROaE1UTWdNVE1nTUNBeElEQWdNVE1nTVROQk1UTXVNREUxSURFekxqQXhOU0F3SURBZ01DQXhOaUF6YlRBZ01qUmhNVEVnTVRFZ01DQXhJREVnTVRFdE1URWdNVEV1TURFZ01URXVNREVnTUNBd0lERXRNVEVnTVRFaUx6NDhjR0YwYUNCbWFXeHNQU0lqUWpJd1JqQXpJaUJrUFNKTk1UY3VNRE00SURFNExqWXhOVWd4TkM0NE4wd3hOQzQxTmpNZ09TNDFhREl1TnpnemVtMHRNUzR3T0RRZ01TNDBNamR4TGpZMklEQWdNUzR3TlRjdU16ZzRMalF3Tnk0ek9Ea3VOREEzTGprNU5DQXdJQzQxT1RZdExqUXdOeTQ1T0RRdExqTTVOeTR6T1MweExqQTFOeTR6T0RrdExqWTFJREF0TVM0d05UWXRMak00T1MwdU16azRMUzR6T0RrdExqTTVPQzB1T1RnMElEQXRMalU1Tnk0ek9UZ3RMams0TlM0ME1EWXRMak01TnlBeExqQTFOaTB1TXprM0lpOCtQQzl6ZG1jKyIpfWJvZHkudGhlbWUtbGlnaHR7YmFja2dyb3VuZC1jb2xvcjojZmZmO2NvbG9yOiMzMTMxMzF9Ym9keS50aGVtZS1saWdodCBhe2NvbG9yOiMwMDUxYzN9Ym9keS50aGVtZS1saWdodCBhOmhvdmVye3RleHQtZGVjb3JhdGlvbjp1bmRlcmxpbmU7Y29sb3I6I2VlNzMwYX1ib2R5LnRoZW1lLWxpZ2h0IC5sZHMtcmluZyBkaXZ7Ym9yZGVyLWNvbG9yOiM1OTU5NTkgcmdiYSgwLDAsMCwwKSByZ2JhKDAsMCwwLDApfWJvZHkudGhlbWUtbGlnaHQgLmZvbnQtcmVke2NvbG9yOiNmYzU3NGF9Ym9keS50aGVtZS1saWdodCAuY3RwLWJ1dHRvbntib3JkZXItY29sb3I6IzAwMzY4MTtiYWNrZ3JvdW5kLWNvbG9yOiMwMDM2ODE7Y29sb3I6I2ZmZn1ib2R5LnRoZW1lLWxpZ2h0ICNjaGFsbGVuZ2Utc3VjY2Vzcy10ZXh0e2JhY2tncm91bmQtaW1hZ2U6dXJsKCJkYXRhOmltYWdlL3N2Zyt4bWw7YmFzZTY0LFBITjJaeUI0Yld4dWN6MGlhSFIwY0RvdkwzZDNkeTUzTXk1dmNtY3ZNakF3TUM5emRtY2lJSGRwWkhSb1BTSXpNaUlnYUdWcFoyaDBQU0l6TWlJZ1ptbHNiRDBpYm05dVpTSWdkbWxsZDBKdmVEMGlNQ0F3SURJMklESTJJajQ4Y0dGMGFDQm1hV3hzUFNJak16RXpNVE14SWlCa1BTSk5NVE1nTUdFeE15QXhNeUF3SURFZ01DQXdJREkySURFeklERXpJREFnTUNBd0lEQXRNalp0TUNBeU5HRXhNU0F4TVNBd0lERWdNU0F3TFRJeUlERXhJREV4SURBZ01DQXhJREFnTWpJaUx6NDhjR0YwYUNCbWFXeHNQU0lqTXpFek1UTXhJaUJrUFNKdE1UQXVPVFUxSURFMkxqQTFOUzB6TGprMUxUUXVNVEkxTFRFdU5EUTFJREV1TXpnMUlEVXVNemNnTlM0Mk1TQTVMalE1TlMwNUxqWXRNUzQwTWkweExqUXdOWG9pTHo0OEwzTjJaejQ9Iil9Ym9keS50aGVtZS1saWdodCAjY2hhbGxlbmdlLWVycm9yLXRleHR7YmFja2dyb3VuZC1pbWFnZTp1cmwoImRhdGE6aW1hZ2Uvc3ZnK3htbDtiYXNlNjQsUEhOMlp5QjRiV3h1Y3owaWFIUjBjRG92TDNkM2R5NTNNeTV2Y21jdk1qQXdNQzl6ZG1jaUlIZHBaSFJvUFNJek1pSWdhR1ZwWjJoMFBTSXpNaUlnWm1sc2JEMGlibTl1WlNJK1BIQmhkR2dnWm1sc2JEMGlJMlpqTlRjMFlTSWdaRDBpVFRFMklETmhNVE1nTVRNZ01DQXhJREFnTVRNZ01UTkJNVE11TURFMUlERXpMakF4TlNBd0lEQWdNQ0F4TmlBemJUQWdNalJoTVRFZ01URWdNQ0F4SURFZ01URXRNVEVnTVRFdU1ERWdNVEV1TURFZ01DQXdJREV0TVRFZ01URWlMejQ4Y0dGMGFDQm1hV3hzUFNJalptTTFOelJoSWlCa1BTSk5NVGN1TURNNElERTRMall4TlVneE5DNDROMHd4TkM0MU5qTWdPUzQxYURJdU56Z3plbTB0TVM0d09EUWdNUzQwTWpkeExqWTJJREFnTVM0d05UY3VNemc0TGpRd055NHpPRGt1TkRBM0xqazVOQ0F3SUM0MU9UWXRMalF3Tnk0NU9EUXRMak01Tnk0ek9TMHhMakExTnk0ek9Ea3RMalkxSURBdE1TNHdOVFl0TGpNNE9TMHVNems0TFM0ek9Ea3RMak01T0MwdU9UZzBJREF0TGpVNU55NHpPVGd0TGprNE5TNDBNRFl0TGpNNU55QXhMakExTmkwdU16azNJaTgrUEM5emRtYysiKX1he3RyYW5zaXRpb246Y29sb3IgMTUwbXMgZWFzZTtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsMCk7dGV4dC1kZWNvcmF0aW9uOm5vbmU7Y29sb3I6IzAwNTFjM31hOmhvdmVye3RleHQtZGVjb3JhdGlvbjp1bmRlcmxpbmU7Y29sb3I6I2VlNzMwYX0ubWFpbi1jb250ZW50e21hcmdpbjo4cmVtIGF1dG87cGFkZGluZy1yaWdodDoxLjVyZW07cGFkZGluZy1sZWZ0OjEuNXJlbTt3aWR0aDoxMDAlO21heC13aWR0aDo2MHJlbX0ubWFpbi1jb250ZW50IC5sb2FkaW5nLXZlcmlmeWluZ3toZWlnaHQ6NzYuMzkxcHh9LnNwYWNlcnttYXJnaW46MnJlbSAwfS5zcGFjZXItdG9we21hcmdpbi10b3A6NHJlbX0uc3BhY2VyLWJvdHRvbXttYXJnaW4tYm90dG9tOjJyZW19LmhlYWRpbmctZmF2aWNvbnttYXJnaW4tcmlnaHQ6LjVyZW07d2lkdGg6MnJlbTtoZWlnaHQ6MnJlbX1AbWVkaWEgKHdpZHRoIDw9IDcyMHB4KXsubWFpbi1jb250ZW50e21hcmdpbi10b3A6NHJlbX0uaGVhZGluZy1mYXZpY29ue3dpZHRoOjEuNXJlbTtoZWlnaHQ6MS41cmVtfX0ubWFpbi13cmFwcGVye2Rpc3BsYXk6ZmxleDtmbGV4OjE7ZmxleC1kaXJlY3Rpb246Y29sdW1uO2FsaWduLWl0ZW1zOmNlbnRlcn0uZm9udC1yZWR7Y29sb3I6I2IyMGYwM30uaDF7bGluZS1oZWlnaHQ6My43NXJlbTtmb250LXNpemU6Mi41cmVtO2ZvbnQtd2VpZ2h0OjUwMH0uaDJ7bGluZS1oZWlnaHQ6Mi4yNXJlbTtmb250LXNpemU6MS41cmVtO2ZvbnQtd2VpZ2h0OjUwMH0uY29yZS1tc2d7bGluZS1oZWlnaHQ6Mi4yNXJlbTtmb250LXNpemU6MS41cmVtO2ZvbnQtd2VpZ2h0OjQwMH0uYm9keS10ZXh0e2xpbmUtaGVpZ2h0OjEuMjVyZW07Zm9udC1zaXplOjFyZW07Zm9udC13ZWlnaHQ6NDAwfUBtZWRpYSAod2lkdGggPD0gNzIwcHgpey5oMXtsaW5lLWhlaWdodDoxLjc1cmVtO2ZvbnQtc2l6ZToxLjVyZW19Lmgye2xpbmUtaGVpZ2h0OjEuNXJlbTtmb250LXNpemU6MS4yNXJlbX0uY29yZS1tc2d7bGluZS1oZWlnaHQ6MS41cmVtO2ZvbnQtc2l6ZToxcmVtfX0jY2hhbGxlbmdlLWVycm9yLXRleHR7YmFja2dyb3VuZC1pbWFnZTp1cmwoImRhdGE6aW1hZ2Uvc3ZnK3htbDtiYXNlNjQsUEhOMlp5QjRiV3h1Y3owaWFIUjBjRG92TDNkM2R5NTNNeTV2Y21jdk1qQXdNQzl6ZG1jaUlIZHBaSFJvUFNJek1pSWdhR1ZwWjJoMFBTSXpNaUlnWm1sc2JEMGlibTl1WlNJK1BIQmhkR2dnWm1sc2JEMGlJMlpqTlRjMFlTSWdaRDBpVFRFMklETmhNVE1nTVRNZ01DQXhJREFnTVRNZ01UTkJNVE11TURFMUlERXpMakF4TlNBd0lEQWdNQ0F4TmlBemJUQWdNalJoTVRFZ01URWdNQ0F4SURFZ01URXRNVEVnTVRFdU1ERWdNVEV1TURFZ01DQXdJREV0TVRFZ01URWlMejQ4Y0dGMGFDQm1hV3hzUFNJalptTTFOelJoSWlCa1BTSk5NVGN1TURNNElERTRMall4TlVneE5DNDROMHd4TkM0MU5qTWdPUzQxYURJdU56Z3plbTB0TVM0d09EUWdNUzQwTWpkeExqWTJJREFnTVM0d05UY3VNemc0TGpRd055NHpPRGt1TkRBM0xqazVOQ0F3SUM0MU9UWXRMalF3Tnk0NU9EUXRMak01Tnk0ek9TMHhMakExTnk0ek9Ea3RMalkxSURBdE1TNHdOVFl0TGpNNE9TMHVNems0TFM0ek9Ea3RMak01T0MwdU9UZzBJREF0TGpVNU55NHpPVGd0TGprNE5TNDBNRFl0TGpNNU55QXhMakExTmkwdU16azNJaTgrUEM5emRtYysiKTtiYWNrZ3JvdW5kLXJlcGVhdDpuby1yZXBlYXQ7YmFja2dyb3VuZC1zaXplOmNvbnRhaW47cGFkZGluZy1sZWZ0OjM0cHh9I2NoYWxsZW5nZS1zdWNjZXNzLXRleHR7YmFja2dyb3VuZC1pbWFnZTp1cmwoImRhdGE6aW1hZ2Uvc3ZnK3htbDtiYXNlNjQsUEhOMlp5QjRiV3h1Y3owaWFIUjBjRG92TDNkM2R5NTNNeTV2Y21jdk1qQXdNQzl6ZG1jaUlIZHBaSFJvUFNJek1pSWdhR1ZwWjJoMFBTSXpNaUlnWm1sc2JEMGlibTl1WlNJZ2RtbGxkMEp2ZUQwaU1DQXdJREkySURJMklqNDhjR0YwYUNCbWFXeHNQU0lqTXpFek1UTXhJaUJrUFNKTk1UTWdNR0V4TXlBeE15QXdJREVnTUNBd0lESTJJREV6SURFeklEQWdNQ0F3SURBdE1qWnRNQ0F5TkdFeE1TQXhNU0F3SURFZ01TQXdMVEl5SURFeElERXhJREFnTUNBeElEQWdNaklpTHo0OGNHRjBhQ0JtYVd4c1BTSWpNekV6TVRNeElpQmtQU0p0TVRBdU9UVTFJREUyTGpBMU5TMHpMamsxTFRRdU1USTFMVEV1TkRRMUlERXVNemcxSURVdU16Y2dOUzQyTVNBNUxqUTVOUzA1TGpZdE1TNDBNaTB4TGpRd05Yb2lMejQ4TDNOMlp6ND0iKTtiYWNrZ3JvdW5kLXJlcGVhdDpuby1yZXBlYXQ7YmFja2dyb3VuZC1zaXplOmNvbnRhaW47cGFkZGluZy1sZWZ0OjQycHh9LnRleHQtY2VudGVye3RleHQtYWxpZ246Y2VudGVyfS5jdHAtYnV0dG9ue3RyYW5zaXRpb24tZHVyYXRpb246MjAwbXM7dHJhbnNpdGlvbi1wcm9wZXJ0eTpiYWNrZ3JvdW5kLWNvbG9yLGJvcmRlci1jb2xvcixjb2xvcjt0cmFuc2l0aW9uLXRpbWluZy1mdW5jdGlvbjplYXNlO21hcmdpbjoycmVtIDA7Ym9yZGVyOi4wNjNyZW0gc29saWQgIzAwNTFjMztib3JkZXItcmFkaXVzOi4zMTNyZW07YmFja2dyb3VuZC1jb2xvcjojMDA1MWMzO2N1cnNvcjpwb2ludGVyO3BhZGRpbmc6LjM3NXJlbSAxcmVtO2xpbmUtaGVpZ2h0OjEuMzEzcmVtO2NvbG9yOiNmZmY7Zm9udC1zaXplOi44NzVyZW19LmN0cC1idXR0b246aG92ZXJ7Ym9yZGVyLWNvbG9yOiMwMDM2ODE7YmFja2dyb3VuZC1jb2xvcjojMDAzNjgxO2N1cnNvcjpwb2ludGVyO2NvbG9yOiNmZmZ9LmZvb3RlcnttYXJnaW46MCBhdXRvO3BhZGRpbmctcmlnaHQ6MS41cmVtO3BhZGRpbmctbGVmdDoxLjVyZW07d2lkdGg6MTAwJTttYXgtd2lkdGg6NjByZW07bGluZS1oZWlnaHQ6MS4xMjVyZW07Zm9udC1zaXplOi43NXJlbX0uZm9vdGVyLWlubmVye2JvcmRlci10b3A6MXB4IHNvbGlkICNkOWQ5ZDk7cGFkZGluZy10b3A6MXJlbTtwYWRkaW5nLWJvdHRvbToxcmVtfS5jbGVhcmZpeDo6YWZ0ZXJ7ZGlzcGxheTp0YWJsZTtjbGVhcjpib3RoO2NvbnRlbnQ6IiJ9LmNsZWFyZml4IC5jb2x1bW57ZmxvYXQ6bGVmdDtwYWRkaW5nLXJpZ2h0OjEuNXJlbTt3aWR0aDo1MCV9LmRpYWdub3N0aWMtd3JhcHBlcnttYXJnaW4tYm90dG9tOi41cmVtfS5mb290ZXIgLnJheS1pZHt0ZXh0LWFsaWduOmNlbnRlcn0uZm9vdGVyIC5yYXktaWQgY29kZXtmb250LWZhbWlseTptb25hY28sY291cmllcixtb25vc3BhY2V9LmNvcmUtbXNnLC56b25lLW5hbWUtdGl0bGV7b3ZlcmZsb3ctd3JhcDpicmVhay13b3JkfUBtZWRpYSAod2lkdGggPD0gNzIwcHgpey5kaWFnbm9zdGljLXdyYXBwZXJ7ZGlzcGxheTpmbGV4O2ZsZXgtd3JhcDp3cmFwO2p1c3RpZnktY29udGVudDpjZW50ZXJ9LmNsZWFyZml4OjphZnRlcntkaXNwbGF5OmluaXRpYWw7Y2xlYXI6bm9uZTt0ZXh0LWFsaWduOmNlbnRlcjtjb250ZW50Om5vbmV9LmNvbHVtbntwYWRkaW5nLWJvdHRvbToycmVtfS5jbGVhcmZpeCAuY29sdW1ue2Zsb2F0Om5vbmU7cGFkZGluZzowO3dpZHRoOmF1dG87d29yZC1icmVhazprZWVwLWFsbH0uem9uZS1uYW1lLXRpdGxle21hcmdpbi1ib3R0b206MXJlbX19LmxvYWRpbmctdmVyaWZ5aW5ne2hlaWdodDo3Ni4zOTFweH0ubGRzLXJpbmd7ZGlzcGxheTppbmxpbmUtYmxvY2s7cG9zaXRpb246cmVsYXRpdmU7d2lkdGg6MS44NzVyZW07aGVpZ2h0OjEuODc1cmVtfS5sZHMtcmluZyBkaXZ7Ym94LXNpemluZzpib3JkZXItYm94O2Rpc3BsYXk6YmxvY2s7cG9zaXRpb246YWJzb2x1dGU7Ym9yZGVyOi4zcmVtIHNvbGlkICM1OTU5NTk7Ym9yZGVyLXJhZGl1czo1MCU7Ym9yZGVyLWNvbG9yOiMzMTMxMzEgcmdiYSgwLDAsMCwwKSByZ2JhKDAsMCwwLDApO3dpZHRoOjEuODc1cmVtO2hlaWdodDoxLjg3NXJlbTthbmltYXRpb246bGRzLXJpbmcgMS4ycyBjdWJpYy1iZXppZXIoLjUsIDAsIC41LCAxKSBpbmZpbml0ZX0ubGRzLXJpbmcgZGl2Om50aC1jaGlsZCgxKXthbmltYXRpb24tZGVsYXk6LS40NXN9Lmxkcy1yaW5nIGRpdjpudGgtY2hpbGQoMil7YW5pbWF0aW9uLWRlbGF5Oi0uM3N9Lmxkcy1yaW5nIGRpdjpudGgtY2hpbGQoMyl7YW5pbWF0aW9uLWRlbGF5Oi0uMTVzfUBrZXlmcmFtZXMgbGRzLXJpbmd7MCV7dHJhbnNmb3JtOnJvdGF0ZSgwZGVnKX0xMDAle3RyYW5zZm9ybTpyb3RhdGUoMzYwZGVnKX19LnJ0bCAuaGVhZGluZy1mYXZpY29ue21hcmdpbi1yaWdodDowO21hcmdpbi1sZWZ0Oi41cmVtfS5ydGwgI2NoYWxsZW5nZS1zdWNjZXNzLXRleHR7YmFja2dyb3VuZC1wb3NpdGlvbjpyaWdodDtwYWRkaW5nLXJpZ2h0OjQycHg7cGFkZGluZy1sZWZ0OjB9LnJ0bCAjY2hhbGxlbmdlLWVycm9yLXRleHR7YmFja2dyb3VuZC1wb3NpdGlvbjpyaWdodDtwYWRkaW5nLXJpZ2h0OjM0cHg7cGFkZGluZy1sZWZ0OjB9LmNoYWxsZW5nZS1jb250ZW50IC5sb2FkaW5nLXZlcmlmeWluZ3toZWlnaHQ6NzYuMzkxcHh9QG1lZGlhIChwcmVmZXJzLWNvbG9yLXNjaGVtZTogZGFyayl7Ym9keXtiYWNrZ3JvdW5kLWNvbG9yOiMyMjI7Y29sb3I6I2Q5ZDlkOX1ib2R5IGF7Y29sb3I6I2ZmZn1ib2R5IGE6aG92ZXJ7dGV4dC1kZWNvcmF0aW9uOnVuZGVybGluZTtjb2xvcjojZWU3MzBhfWJvZHkgLmxkcy1yaW5nIGRpdntib3JkZXItY29sb3I6Izk5OSByZ2JhKDAsMCwwLDApIHJnYmEoMCwwLDAsMCl9Ym9keSAuZm9udC1yZWR7Y29sb3I6I2IyMGYwM31ib2R5IC5jdHAtYnV0dG9ue2JhY2tncm91bmQtY29sb3I6IzQ2OTNmZjtjb2xvcjojMWQxZDFkfWJvZHkgI2NoYWxsZW5nZS1zdWNjZXNzLXRleHR7YmFja2dyb3VuZC1pbWFnZTp1cmwoImRhdGE6aW1hZ2Uvc3ZnK3htbDtiYXNlNjQsUEhOMlp5QjRiV3h1Y3owaWFIUjBjRG92TDNkM2R5NTNNeTV2Y21jdk1qQXdNQzl6ZG1jaUlIZHBaSFJvUFNJek1pSWdhR1ZwWjJoMFBTSXpNaUlnWm1sc2JEMGlibTl1WlNJZ2RtbGxkMEp2ZUQwaU1DQXdJREkySURJMklqNDhjR0YwYUNCbWFXeHNQU0lqWkRsa09XUTVJaUJrUFNKTk1UTWdNR0V4TXlBeE15QXdJREVnTUNBd0lESTJJREV6SURFeklEQWdNQ0F3SURBdE1qWnRNQ0F5TkdFeE1TQXhNU0F3SURFZ01TQXdMVEl5SURFeElERXhJREFnTUNBeElEQWdNaklpTHo0OGNHRjBhQ0JtYVd4c1BTSWpaRGxrT1dRNUlpQmtQU0p0TVRBdU9UVTFJREUyTGpBMU5TMHpMamsxTFRRdU1USTFMVEV1TkRRMUlERXVNemcxSURVdU16Y2dOUzQyTVNBNUxqUTVOUzA1TGpZdE1TNDBNaTB4TGpRd05Yb2lMejQ4TDNOMlp6NCIpfWJvZHkgI2NoYWxsZW5nZS1lcnJvci10ZXh0e2JhY2tncm91bmQtaW1hZ2U6dXJsKCJkYXRhOmltYWdlL3N2Zyt4bWw7YmFzZTY0LFBITjJaeUI0Yld4dWN6MGlhSFIwY0RvdkwzZDNkeTUzTXk1dmNtY3ZNakF3TUM5emRtY2lJSGRwWkhSb1BTSXpNaUlnYUdWcFoyaDBQU0l6TWlJZ1ptbHNiRDBpYm05dVpTSStQSEJoZEdnZ1ptbHNiRDBpSTBJeU1FWXdNeUlnWkQwaVRURTJJRE5oTVRNZ01UTWdNQ0F4SURBZ01UTWdNVE5CTVRNdU1ERTFJREV6TGpBeE5TQXdJREFnTUNBeE5pQXpiVEFnTWpSaE1URWdNVEVnTUNBeElERWdNVEV0TVRFZ01URXVNREVnTVRFdU1ERWdNQ0F3SURFdE1URWdNVEVpTHo0OGNHRjBhQ0JtYVd4c1BTSWpRakl3UmpBeklpQmtQU0pOTVRjdU1ETTRJREU0TGpZeE5VZ3hOQzQ0TjB3eE5DNDFOak1nT1M0MWFESXVOemd6ZW0wdE1TNHdPRFFnTVM0ME1qZHhMalkySURBZ01TNHdOVGN1TXpnNExqUXdOeTR6T0RrdU5EQTNMams1TkNBd0lDNDFPVFl0TGpRd055NDVPRFF0TGpNNU55NHpPUzB4TGpBMU55NHpPRGt0TGpZMUlEQXRNUzR3TlRZdExqTTRPUzB1TXprNExTNHpPRGt0TGpNNU9DMHVPVGcwSURBdExqVTVOeTR6T1RndExqazROUzQwTURZdExqTTVOeUF4TGpBMU5pMHVNemszSWk4K1BDOXpkbWMrIil9fTwvc3R5bGU+PHNjcmlwdCBzcmM9Imh0dHBzOi8vY2hhbGxlbmdlcy5jbG91ZGZsYXJlLmNvbS90dXJuc3RpbGUvdjAvYi9jODg3NTViMGNkZGMvYXBpLmpzP29ubG9hZD10Sk5jNiZhbXA7cmVuZGVyPWV4cGxpY2l0IiBhc3luYz0iIiBkZWZlcj0iIiBjcm9zc29yaWdpbj0iYW5vbnltb3VzIj48L3NjcmlwdD48L2hlYWQ+PGJvZHk+PGRpdiBjbGFzcz0ibWFpbi13cmFwcGVyIiByb2xlPSJtYWluIj48ZGl2IGNsYXNzPSJtYWluLWNvbnRlbnQiPjxoMSBjbGFzcz0iem9uZS1uYW1lLXRpdGxlIGgxIj5yZXNhLm5vdHJlZGFtZWRlcGFyaXMuZnI8L2gxPjxwIGlkPSJHRnB3azUiIGNsYXNzPSJoMiBzcGFjZXItYm90dG9tIj5WZXJpZnkgeW91IGFyZSBodW1hbiBieSBjb21wbGV0aW5nIHRoZSBhY3Rpb24gYmVsb3cuPC9wPjxkaXYgaWQ9IlRrdFJZMSIgc3R5bGU9ImRpc3BsYXk6IGdyaWQ7Ij48ZGl2PjxkaXY+PGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iY2YtdHVybnN0aWxlLXJlc3BvbnNlIiBpZD0iY2YtY2hsLXdpZGdldC1lMnM4c19yZXNwb25zZSI+PC9kaXY+PC9kaXY+PC9kaXY+PGRpdiBpZD0iZkd0Y1M3IiBjbGFzcz0ic3BhY2VyIGxvYWRpbmctdmVyaWZ5aW5nIiBzdHlsZT0iZGlzcGxheTogbm9uZTsgdmlzaWJpbGl0eTogaGlkZGVuOyI+PGRpdiBjbGFzcz0ibGRzLXJpbmciPjxkaXY+PC9kaXY+PGRpdj48L2Rpdj48ZGl2PjwvZGl2PjxkaXY+PC9kaXY+PC9kaXY+PC9kaXY+PGRpdiBpZD0iRGNIc08yIiBjbGFzcz0iY29yZS1tc2cgc3BhY2VyIHNwYWNlci10b3AiPnJlc2Eubm90cmVkYW1lZGVwYXJpcy5mciBuZWVkcyB0byByZXZpZXcgdGhlIHNlY3VyaXR5IG9mIHlvdXIgY29ubmVjdGlvbiBiZWZvcmUgcHJvY2VlZGluZy48L2Rpdj48ZGl2IGlkPSJOdU10MyIgc3R5bGU9ImRpc3BsYXk6IG5vbmU7Ij48ZGl2IGlkPSJjaGFsbGVuZ2Utc3VjY2Vzcy10ZXh0IiBjbGFzcz0iaDIiPlZlcmlmaWNhdGlvbiBzdWNjZXNzZnVsPC9kaXY+PGRpdiBjbGFzcz0iY29yZS1tc2cgc3BhY2VyIj5XYWl0aW5nIGZvciByZXNhLm5vdHJlZGFtZWRlcGFyaXMuZnIgdG8gcmVzcG9uZC4uLjwvZGl2PjwvZGl2Pjxub3NjcmlwdD48ZGl2IGNsYXNzPSJoMiI+PHNwYW4gaWQ9ImNoYWxsZW5nZS1lcnJvci10ZXh0Ij5FbmFibGUgSmF2YVNjcmlwdCBhbmQgY29va2llcyB0byBjb250aW51ZTwvc3Bhbj48L2Rpdj48L25vc2NyaXB0PjwvZGl2PjwvZGl2PjxzY3JpcHQ+KGZ1bmN0aW9uKCl7d2luZG93Ll9jZl9jaGxfb3B0ID0ge2N2SWQ6ICczJyxjWm9uZTogJ3Jlc2Eubm90cmVkYW1lZGVwYXJpcy5mcicsY1R5cGU6ICdtYW5hZ2VkJyxjUmF5OiAnOTk1MWIxNTVmODE5ZTk0YicsY0g6ICdZTVBjcU5HVm1oSGptNmk4YXdqLllxWmRiZmJ5dDZfRWlIaXR2aHZMcnlRLTE3NjE1NjI4NTctMS4yLjEuMS1WOE90alI2el80QmFCdjlkaUQ4eWowMkVhUkpQYWNjVDZ3WGE4cVpaRjlyUno4a2d4eXp1WG9tVmxDbk0xWFpyJyxjVVBNRFRrOiJcLz9fX2NmX2NobF90az0xaERpMjR5VWxpN0FZSDFPWVA3b3hnWngycHF2Wlp5a0RjRXIxOU9JUHJrLTE3NjE1NjI4NTctMS4wLjEuMS1wYUZTd25ZYkxkVmVqSXhUbk02RUlwTTJmSGZVVEJ4cmw5ZWR4RERnUnh3IixjRlBXdjogJ2InLGNJVGltZVM6ICcxNzYxNTYyODU3JyxjVHBsQzowLGNUcGxWOjUsY1RwbEI6ICcwJyxmYToiXC8/X19jZl9jaGxfZl90az0xaERpMjR5VWxpN0FZSDFPWVA3b3hnWngycHF2Wlp5a0RjRXIxOU9JUHJrLTE3NjE1NjI4NTctMS4wLjEuMS1wYUZTd25ZYkxkVmVqSXhUbk02RUlwTTJmSGZVVEJ4cmw5ZWR4RERnUnh3IixtZDogJ1ZIemtCN0xxS0dENEhaNUVRYV9hQUpueVQuX3h2V2g0WGtyUUc1WGhkaEEtMTc2MTU2Mjg1Ny0xLjIuMS4xLXJmRWRJOXV2ckY3cFI3NEd1QmRwM2VRalBkM2pWY0NtRkNGS3RfWVh3blExTlVxa2VJbkouclBJS0VTSmNhaTd5ZTJQcUdZeDRLa1ZMYVhpMHltWHBLS01xT3k0VGxQaEd1V1h1a1BmUjVYZG5pdkdnRFE4WUhzNloweGRBTHlhcHduNjdNYWtRSE9iRXFQd05udEhFck9uT1RneGdIZzBrS3dqeTZ5d1hhS25QdGwuNGl4Snl4Z3M5R2pxMnEzN3JMUUpkeU90ejBkTWVVcWd0V3Zpa0dLZWNqZ3Z2eTRiR3JGanNIbTdSNHRqbGI2UnIuT3psV3VzUjdZVG9fcXZ0NUZyTkFJNUxaSzdoMnlZUEFhVFFZRGhvN2xkRG0zdHp6UEkxTHdHUDZQWmRsRmtIYURyUW9KTVVCZThQLk82b1VsVUV0UXoxQ3c5YktOYXJJSXN5Mk0zRkI0b3YzQWZfZDZVdTVlMDVaSHhUajdQQW53a201cFRIOVh4NlhacW5HZ2JWSHBIN2FxYnhQd1FQblllNHFVVDZVY0JDMHVPOW5ibzJyMldndTdJOG0zSHVjRTAwbnBRNXhBZE9GMlJPVlp0LlVqcktrdDRFN21lUmJkWE5jcVpPR3JfVXcxUkxxNzEwQnVJSDFONWllc3pzMDFiVUliVVhrV1ZyV3RhLi5lMHY4N0t0U0JEUmU4eWpndHFTQjV0YW1rdENtdldvalpVNDBuSTlfbF9jLk5INnFPUTdla19jT0xhNHBFbFdFUWtuZ0MueG9EdnNuTnF5RFJiZ1VHdUtUUlNET0xxOHlRUG9SdmpXUDh0ckFmVm9NTUdNSG14UVFSYzlQdEZPXzBtekIuVG1XQWJqeVBrQXZtOWs5OTg5S0RKU2Z0WFNIdXRJOER3cHhFalZhWmt1Tl85SXpFakxERkNXQkRCYmFCdC5tRy44aDk3RHVvVEh2aDM2bS5DSUlvZjR5MEd6TDhmQWx4MHdQQ3h0U0hVZjdVZ0U2XzhkSjl3S0FQTU1xeEhWVnJHTEJHblZMOHl2NFJHY3VWTklQd1ByOTV4RFd4UjlWaS5QbnNwdnVxSFFvbmVoSlZSMURNUmVlNWxCU1RvNHMzNy5MLnl3QnFTVHM3OVpoeVZ0el81NlhhZjEzbExGZFFhWDF0WFoyclcuSURKdnNnWnhOYVU4MFlJOEdrbjNCZ21VUmhXWFl1Q1ppMk4ueU1paHFSZXBrVnh5Z3dXWWxtdWVZMm1udW9JU21qVXhKMG0xZzQxZU5FUHQ5dXJWVjlsbVVuYU1vQlI1a0wzMW5pUFh6QWt3QmlpeTc5aW9wYWpnU2R0eVZXdGtUamtscjh1MFRsUnV2OUJVOGRMNTBvWjVTUmVVdE5hdXZvajJYTS5zcGZvYlpiLjBLYWFMakwuM3g0JyxtZHJkOiAnbkd3eHl2Y2lIQU1MTnFWYU1sdjQ1X1NKN3hZRlpYOGhPcnFWazlWZU1ZRS0xNzYxNTYyODU3LTEuMi4xLjEtdWVzYzZHbkJlMHJJZ2oxTlk4TlhpRm01a0R2OW9HX0ExTFR2RWsuSHgxWV9LbkJ6dkZhdDhHWjI3d0xOOWt4ZWxESnNPMklOdWt4Y2Q3V1RYN1JiRzkyTU1WRFZmTG5HRDBQaXdtcE1GREVsNzNBMUdhU2tUX1JNNjIyaWVMMkpCUmdYeEtTamtQckhNWmZXZVNBbnkubEMubGtDM0NwaXBoQ3N6Wk5VV0pGTUVNcW9RZVJLbXkwWmtPUE4yaTcxZ0I3eE81REZteHBTWGlsckREdGpTcG1jZW8wWFpMMTVkLlFzMFkySlpncDlicC5Oc3hSZUJOallPR0s4dnhyU1pncmZ0YlhBUHNDejNXR3ZVbVFyczRwNWczajJ0X0ROVU14cUVxdm55cUVlRDhod2dDZG9OUXMxZHBRZTh0dWpSdGpLcnZadDFXRFFGcHBtcHZYRU1nMzZYVHFHNWgzWnFXdmJ6QVBxbVYyMElsOGdoc3ZZdEhUWl94aUZPY3RSMlh0VEFPWnJJeDc3SEpiQUU3UDRXcjBCSkFwNU5BVTRTZ2IwbmdsU05wVXd4LmNDbndZZGVlXzJZMzZneWRGT3F2SEJkRDRNSWpOeU8yVnB2RzczdkRndUdMdVNuUU5aSE9NbndTSlZoVFFQQk9aUVFfV3EwZ1lBMU1NTFYzSkUzLnh6Zmd6cUpwNDVNQ3BfYU5SRmFYUWo5NGZkb3hwdlJkWVdseVVZSzlwdlM4N3dDc3kucFpsX25ENllncXdBLjlFRGVsSFJfTHNjaVh4S19WbWZnUl9kWmJ6OGYwQnp3UEJITEJZTEk4VUFIbjdoaDFWUlBucUlOUW43X290aXBTaHVOSlAwd25BOHBmQUpxMWxXZGNEczFoUE82bWg3bFNmUkl3dml4dXguWmk1Y2FOUkR1cS5pc2JfNkc5eV83aldSbzJfbFB1akgxWlVBNDBFck5wdjRmOGhjRG1uTFR3R0RHVGw1cHhBc1d5M1pBSU5qeThiTG5CbmJrNFJPUEU3WEQ1MUM4ZkVPNHFfVGdaVzJWUGlyQnNtQnFabEdPWnhyMS5fWTNOdzZHWEd0d01WN0p3N3k4bk5ja0lGdk1POW5PRnBrZkRzNWFsZUlpWkNSOGp5RTk3WjBaX3NiajdJU2lLVXBLM3pRTzlpdDhydXdST2EyelpUZ3ZZWDcyWUMxdzZ0M1VFWXhoeTFha2pVbHc1QVFPQkZhODhMVlV6X0VZRE9vMzViMzVFYl8yYjJheFVwV3EwQ3hwRnlPQlN5VlFXNnhvNVV4TklsSmdDZHZCQzBaWGVBUmpkaDQ0V240dURjRmt2eGIzaFFMTkVkUm92R0t3QlBmR2JRdk0wUHlLbUw0SzY2RUNIT2pPM2NCWFNDNmYxRGdCS0hzV2ZKUFFHVGV3VHhMMEF6cUFsOUNjUXcxOVU3LmFBM2VhMnA4eDhqWUFpU3g3ZGNKbEVRMUswS1hyaDl3NVcuS041SFB3RzY2YmYyS0dyUm1Mc2RZd2hpYlBoNlhvcDVJRThhZVluUnFvYmpsZEl2a1o0RGNfSWRXT3ppQUtYM01YSGlWMkJUWFpSeERLbWl2YjF2N2RCZEpDM19jSE5LcUZVWDI3ejBKOFVXRlQ0T01pbVFhQ3hRd3JQV0tCdE9LMmR4TncyZjFHSlBUQmhxVlVSOF8xaGtQTElwdzFvaUFOczcxaGpWUU5EZG9tWlZ1bVAuelFXQjN0N2U0TU9icUJIeThsLnFxeXo0WWV3SWVlSk1Ja1lCZlFMdVpwUV9MZmY4OWZ1cUVpMnA0MHJ4NEEycnVvc1lMeGZRWGVHM1FTSWxrdk96Qm4wemNmMU55TG4uR2tfZVFvYzlYN1lDR2JxTUFMS2hDR3pXM1l0VlZaeU1MSmUxYlN1d0NMc0RvVUpxcnZkUWh4dHhvVU9HZDF1dnBoUmRmOTdYbTRFdmFkWmFDVkI4bnRYb181MmpYUnltYTk3cGZEajU4U25kR21ld3BReWthYUdrem5tRncxajlnRk4ySG9qZnFtZFBVRW44eDM2VFZJRm1sNzJjVXhZWG5mU1EwNTNHYXhuTVR2T3NrZzkuMjJOWXp5cWRuNnNPaHdnMnVvU3lpanAuYnNoYklPYlFzUmxOY05IY1prNHZCa2w2RFVjaEc4WklJRTNObWRsTi45czZSeEk0UGlfUEtKaXJnR0duTUxXWUZrd2lESEc3TGtFdUQ5TkdoYU5WNVNLZVFXY0M3MnlZcHp0MU04VTduVDZ3S1ZKWmJMd2dGQTFXeXFNNXYzajJhYl9ZdEgySFNINGNkeDFRYWxqUkpTcWgzNXRFU1ViLi5vVmppZDh1UFJQdUxVODg3TG45MEx1WENWUnpCN0xWaEpoUG00ZENaWDgyejBnbmluUlkwcFZNOVhaYU90VXdkTlBIN0JKanoyUlo1N2xDN3VEbVQ4M1VoY1R1TjZCNFBlTlJhek9ZZGVOaG1FN0cxZU9sVnROaEoucUlRdHdWc3Zick9lWTBoTjM1ZklkUGU4TEhhQkxyUzVwdUg2ZzRHbWZLVUF4Q29NeVRIcnNqTnlYbGV3cG9HOEdNX0JzazRyMUhpWmFMSk9pYkVPM3hqMGRlV0lIZ3BaRnFoaHNZX2RZLndwTWxaR04zeHF6clFKekdFa191UkdPVFNaa0NpM1oxd21HZmFDVmFpVnhTd2NoRk9oOUVWN25EYUJHRFBCYVd6cGhhNXZoSnRVU0toRWxpNVpBQXE2ZndQUDRHY0xFR2xCSU52VGlMZ2FnZTRUbGpPbGU0azJpUDRzWHJqUy5NcnJLUHQxTjZ6RW85R0xfSGlEY0Q3WjBWMnlDeU5uMjlRUUhzNnJBeWdxVmlQVGNjR3Fscl9UdF80eWljc3ZDYW5nVFM0X3VrWWtlclhhdkZudVQyeklCeTNOVmt4SkpoSllQeEtpbElJb1hBLm1RSGZWRHVCcUNXRTR0MC5oLkpDakdXMXpyOHd2ek5xR1dZX283aGthbUpac0NENlZ5VU8ua3RPSVRqQll6blZjUTdWYndjWDVZRjh3YTNvMktLTF83TGtVT3FwSmZQelZ1VkFhYXFiekcxQ3pzaG9yZDFxdVJHbXA1eWVqelRDYmREaU9XZnpVTjdxOFlSbm9JenlKZ1dNOHF5OGVhdGVWUkhaT2VKWHpLQUo0VmYuWEZkQ0hENDNGX1FVcnVjYjRQQ1J4YUtfemhwemFjZVFIYTd4UlZ3d3RTTkVWS01xWEJEV3FSdnhqMk94WGM0YVJtZ3pIYUE2dUY0SDBYaFhSODdQYXZsTnBnQ29lV0Q3R2F5V0FNOW9JSXpNdFBabnZYMGlOaDBvbTlSeUlGV1N5cW1zd1dtMC5tdDV0Lk9SQ3VKYm1QRVBGbHppcnN5Qjc1dDROZlRtUEJ6UlZCY2tkSEtXMVdEaUp1ZGRISTJMcm5QSXdjOWZ2Z2hybU5xN0Uxc3ZIM3ZBXzg4U3JoQzEnLH07dmFyIGEgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzY3JpcHQnKTthLnNyYyA9ICcvY2RuLWNnaS9jaGFsbGVuZ2UtcGxhdGZvcm0vaC9iL29yY2hlc3RyYXRlL2NobF9wYWdlL3YxP3JheT05OTUxYjE1NWY4MTllOTRiJzt3aW5kb3cuX2NmX2NobF9vcHQuY09nVUhhc2ggPSBsb2NhdGlvbi5oYXNoID09PSAnJyAmJiBsb2NhdGlvbi5ocmVmLmluZGV4T2YoJyMnKSAhPT0gLTEgPyAnIycgOiBsb2NhdGlvbi5oYXNoO3dpbmRvdy5fY2ZfY2hsX29wdC5jT2dVUXVlcnkgPSBsb2NhdGlvbi5zZWFyY2ggPT09ICcnICYmIGxvY2F0aW9uLmhyZWYuc2xpY2UoMCwgbG9jYXRpb24uaHJlZi5sZW5ndGggLSB3aW5kb3cuX2NmX2NobF9vcHQuY09nVUhhc2gubGVuZ3RoKS5pbmRleE9mKCc/JykgIT09IC0xID8gJz8nIDogbG9jYXRpb24uc2VhcmNoO2lmICh3aW5kb3cuaGlzdG9yeSAmJiB3aW5kb3cuaGlzdG9yeS5yZXBsYWNlU3RhdGUpIHt2YXIgb2dVID0gbG9jYXRpb24ucGF0aG5hbWUgKyB3aW5kb3cuX2NmX2NobF9vcHQuY09nVVF1ZXJ5ICsgd2luZG93Ll9jZl9jaGxfb3B0LmNPZ1VIYXNoO2hpc3RvcnkucmVwbGFjZVN0YXRlKG51bGwsIG51bGwsIlwvP19fY2ZfY2hsX3J0X3RrPTFoRGkyNHlVbGk3QVlIMU9ZUDdveGdaeDJwcXZaWnlrRGNFcjE5T0lQcmstMTc2MTU2Mjg1Ny0xLjAuMS4xLXBhRlN3blliTGRWZWpJeFRuTTZFSXBNMmZIZlVUQnhybDllZHhERGdSeHciKyB3aW5kb3cuX2NmX2NobF9vcHQuY09nVUhhc2gpO2Eub25sb2FkID0gZnVuY3Rpb24oKSB7aGlzdG9yeS5yZXBsYWNlU3RhdGUobnVsbCwgbnVsbCwgb2dVKTt9fWRvY3VtZW50LmdldEVsZW1lbnRzQnlUYWdOYW1lKCdoZWFkJylbMF0uYXBwZW5kQ2hpbGQoYSk7fSgpKTs8L3NjcmlwdD48c2NyaXB0IGRlZmVyPSIiIHNyYz0iaHR0cHM6Ly9zdGF0aWMuY2xvdWRmbGFyZWluc2lnaHRzLmNvbS9iZWFjb24ubWluLmpzL3ZjZDE1Y2JlNzc3MmY0OWMzOTljNmE1YmFiZjIyYzEyNDE3MTc2ODkxNzYwMTUiIGludGVncml0eT0ic2hhNTEyLVpwc09tbFJRVjZ5OTA3VEkwZEtCSHE5TWQyOW5uYUVJUGxrZjg0cm5hRVJucTZ6dld2UFVxcjJmdDhNMWFTMjhvTjcyUGRyQ3pTalk0VTZWYUF3MUVRPT0iIGRhdGEtY2YtYmVhY29uPSJ7JnF1b3Q7cmF5SWQmcXVvdDs6JnF1b3Q7OTk1MWIxNTVmODE5ZTk0YiZxdW90OywmcXVvdDtzZXJ2ZXJUaW1pbmcmcXVvdDs6eyZxdW90O25hbWUmcXVvdDs6eyZxdW90O2NmRXh0UHJpJnF1b3Q7OnRydWUsJnF1b3Q7Y2ZFZGdlJnF1b3Q7OnRydWUsJnF1b3Q7Y2ZPcmlnaW4mcXVvdDs6dHJ1ZSwmcXVvdDtjZkw0JnF1b3Q7OnRydWUsJnF1b3Q7Y2ZTcGVlZEJyYWluJnF1b3Q7OnRydWUsJnF1b3Q7Y2ZDYWNoZVN0YXR1cyZxdW90Ozp0cnVlfX0sJnF1b3Q7dmVyc2lvbiZxdW90OzomcXVvdDsyMDI1LjkuMSZxdW90OywmcXVvdDt0b2tlbiZxdW90OzomcXVvdDtjMzgxZTcwZWFlNDQ0MzE4YmQzMjk2Y2M4ODlmNjdiNiZxdW90O30iIGNyb3Nzb3JpZ2luPSJhbm9ueW1vdXMiPjwvc2NyaXB0Pgo8ZGl2IGNsYXNzPSJmb290ZXIiIHJvbGU9ImNvbnRlbnRpbmZvIj48ZGl2IGNsYXNzPSJmb290ZXItaW5uZXIiPjxkaXYgY2xhc3M9ImNsZWFyZml4IGRpYWdub3N0aWMtd3JhcHBlciI+PGRpdiBjbGFzcz0icmF5LWlkIj5SYXkgSUQ6IDxjb2RlPjk5NTFiMTU1ZjgxOWU5NGI8L2NvZGU+PC9kaXY+PC9kaXY+PGRpdiBjbGFzcz0idGV4dC1jZW50ZXIiIGlkPSJmb290ZXItdGV4dCI+UGVyZm9ybWFuY2UgJmFtcDsgc2VjdXJpdHkgYnkgPGEgcmVsPSJub29wZW5lciBub3JlZmVycmVyIiBocmVmPSJodHRwczovL3d3dy5jbG91ZGZsYXJlLmNvbT91dG1fc291cmNlPWNoYWxsZW5nZSZhbXA7dXRtX2NhbXBhaWduPW0iIHRhcmdldD0iX2JsYW5rIj5DbG91ZGZsYXJlPC9hPjwvZGl2PjwvZGl2PjwvZGl2PjwvYm9keT48L2h0bWw+", + proxy=proxy + ) + + solution = await cap_monster_client.solve_captcha(turnstile_request) + print("Solution:", solution) + +asyncio.run(main()) diff --git a/examples/client_proxy.py b/examples/client_proxy.py new file mode 100644 index 0000000..320fca2 --- /dev/null +++ b/examples/client_proxy.py @@ -0,0 +1,50 @@ +import asyncio +import os +import urllib +import base64 +import time + +from capmonstercloudclient import CapMonsterClient, ClientOptions +from capmonstercloudclient.requests import RecaptchaComplexImageTaskRequest, ClientProxyInfo + +async def solve_captcha_sync(num_requests, + client, + request): + return [await client.solve_captcha(request) for _ in range(num_requests)] + + +if __name__ == '__main__': + + api_key = os.getenv('API_KEY') + userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36." + metadata = { + "Task": "Click on traffic lights", + "Grid": "3x3", + "TaskDefinition": "/m/015qff" + } + imagesUrls = ["https://i.postimg.cc/yYjg75Kv/payloadtraffic.jpg"] + client_proxy = ClientProxyInfo( + proxyType='http', + proxyAddress='gw-us.zengw.io', # get at https://docs.zennolab.com/zennoproxy/introduction or your own proxy + proxyPort=8080, # get at https://docs.zennolab.com/zennoproxy/introduction or your own proxy + proxyLogin='login', # get at https://docs.zennolab.com/zennoproxy/introduction or your own proxy + proxyPassword='password' # get at https://docs.zennolab.com/zennoproxy/introduction or your own proxy + ) + + options = ClientOptions(api_key=api_key, client_proxy=client_proxy) + client = CapMonsterClient(options) + + # urls example + request_with_urls = RecaptchaComplexImageTaskRequest(metadata=metadata, + imagesUrls=imagesUrls) + + nums = 3 + + print(' Test with urls '.center(120, '#')) + + # Sync test + sync_start = time.time() + sync_responses = asyncio.run(solve_captcha_sync(nums, client, request_with_urls)) + print(f'average execution time sync {1/((time.time()-sync_start)/nums):0.2f} ' \ + f'resp/sec\nsolution: {sync_responses[0]}') + diff --git a/examples/imperva.py b/examples/imperva.py index 6eba08b..cf87af4 100644 --- a/examples/imperva.py +++ b/examples/imperva.py @@ -18,12 +18,21 @@ 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) + from capmonstercloudclient.requests import ProxyInfo + proxy = ProxyInfo( + proxyType="http", + proxyAddress="8.8.8.8", + proxyPort=8000, + proxyLogin="proxyLoginHere", + proxyPassword="proxyPasswordHere" + ) metadata = {"incapsulaScriptUrl": "_Incapsula_Resource?SWJIYLWA=719d34d31c8e3a6e6fffd425f7e032f3", "incapsulaCookie": "incap_ses_1166_2930313=br7iX33ZNCtf3HlpEXcuEDzz72cAAAAA0suDnBGrq/iA0J4oERYzjQ==; visid_incap_2930313=P3hgPVm9S8Oond1L0sXhZqfK72cAAAAAQUIPAAAAAABoMSY9xZ34RvRseJRiY6s+;", "reese84UrlEndpoint": "Built-with-the-For-hopence-Hurleysurfecting-the-"} imperva_request = ImpervaCustomTaskRequest( websiteUrl='https://example.com/login', - metadata=metadata + metadata=metadata, + proxy=proxy ) nums = 3 diff --git a/examples/recaptchaV3Enterprise.py b/examples/recaptchaV3Enterprise.py new file mode 100644 index 0000000..6135b19 --- /dev/null +++ b/examples/recaptchaV3Enterprise.py @@ -0,0 +1,41 @@ +import os +import time +import asyncio + +from capmonstercloudclient import CapMonsterClient, ClientOptions +from capmonstercloudclient.requests import RecaptchaV3EnterpriseRequest + +async def solve_captcha_sync(num_requests): + return [await cap_monster_client.solve_captcha(recaptcha3enterprise_request) for _ in range(num_requests)] + +async def solve_captcha_async(num_requests): + tasks = [asyncio.create_task(cap_monster_client.solve_captcha(recaptcha3enterprise_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) + + recaptcha3enterprise_request = RecaptchaV3EnterpriseRequest( + websiteUrl="https://lessons.zennolab.com/captchas/recaptcha/v3.php?level=beta", + websiteKey="6Le0xVgUAAAAAIt20XEB4rVhYOODgTl00d8juDob", + minScore=0.9, + pageAction="submit" + ) + + 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/turnstile_cf.py b/examples/turnstile_cf.py new file mode 100644 index 0000000..d24df7c --- /dev/null +++ b/examples/turnstile_cf.py @@ -0,0 +1,30 @@ +import asyncio +from capmonstercloudclient import CapMonsterClient, ClientOptions +from capmonstercloudclient.requests import TurnstileRequest +from capmonstercloudclient.requests.baseRequestWithProxy import ProxyInfo + +async def main(): + client_options = ClientOptions(api_key="dac3599143bdfd88dfc41758c6cb8729") + cap_monster_client = CapMonsterClient(options=client_options) + + proxy = ProxyInfo( + proxyType="https", + proxyAddress="proxyAddress", + proxyPort=8080, + proxyLogin="login", + proxyPassword="password" + ) + + turnstile_request = TurnstileRequest( + websiteURL="https://resa.notredamedeparis.fr/", + websiteKey="xxxxx", + cloudflareTaskType="cf_clearance", + userAgent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36", + htmlPageBase64="PGh0bWwgbGFuZz0iZW4tVVMiIGRpcj0ibHRyIj48aGVhZD48dGl0bGU+SnVzdCBhIG1vbWVudC4uLjwvdGl0bGU+PG1ldGEgaHR0cC1lcXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7IGNoYXJzZXQ9VVRGLTgiPjxtZXRhIGh0dHAtZXF1aXY9IlgtVUEtQ29tcGF0aWJsZSIgY29udGVudD0iSUU9RWRnZSI+PG1ldGEgbmFtZT0icm9ib3RzIiBjb250ZW50PSJub2luZGV4LG5vZm9sbG93Ij48bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLGluaXRpYWwtc2NhbGU9MSI+PHN0eWxlPip7Ym94LXNpemluZzpib3JkZXItYm94O21hcmdpbjowO3BhZGRpbmc6MH1odG1se2xpbmUtaGVpZ2h0OjEuMTU7LXdlYmtpdC10ZXh0LXNpemUtYWRqdXN0OjEwMCU7Y29sb3I6IzMxMzEzMTtmb250LWZhbWlseTpzeXN0ZW0tdWksLWFwcGxlLXN5c3RlbSxCbGlua01hY1N5c3RlbUZvbnQsIlNlZ29lIFVJIixSb2JvdG8sIkhlbHZldGljYSBOZXVlIixBcmlhbCwiTm90byBTYW5zIixzYW5zLXNlcmlmLCJBcHBsZSBDb2xvciBFbW9qaSIsIlNlZ29lIFVJIEVtb2ppIiwiU2Vnb2UgVUkgU3ltYm9sIiwiTm90byBDb2xvciBFbW9qaSJ9Ym9keXtkaXNwbGF5OmZsZXg7ZmxleC1kaXJlY3Rpb246Y29sdW1uO2hlaWdodDoxMDB2aDttaW4taGVpZ2h0OjEwMHZofS5tYWluLWNvbnRlbnR7bWFyZ2luOjhyZW0gYXV0bztwYWRkaW5nLWxlZnQ6MS41cmVtO21heC13aWR0aDo2MHJlbX1AbWVkaWEgKHdpZHRoIDw9IDcyMHB4KXsubWFpbi1jb250ZW50e21hcmdpbi10b3A6NHJlbX19Lmgye2xpbmUtaGVpZ2h0OjIuMjVyZW07Zm9udC1zaXplOjEuNXJlbTtmb250LXdlaWdodDo1MDB9QG1lZGlhICh3aWR0aCA8PSA3MjBweCl7Lmgye2xpbmUtaGVpZ2h0OjEuNXJlbTtmb250LXNpemU6MS4yNXJlbX19I2NoYWxsZW5nZS1lcnJvci10ZXh0e2JhY2tncm91bmQtaW1hZ2U6dXJsKCJkYXRhOmltYWdlL3N2Zyt4bWw7YmFzZTY0LFBITjJaeUI0Yld4dWN6MGlhSFIwY0RvdkwzZDNkeTUzTXk1dmNtY3ZNakF3TUM5emRtY2lJSGRwWkhSb1BTSXpNaUlnYUdWcFoyaDBQU0l6TWlJZ1ptbHNiRDBpYm05dVpTSStQSEJoZEdnZ1ptbHNiRDBpSTBJeU1FWXdNeUlnWkQwaVRURTJJRE5oTVRNZ01UTWdNQ0F4SURBZ01UTWdNVE5CTVRNdU1ERTFJREV6TGpBeE5TQXdJREFnTUNBeE5pQXpiVEFnTWpSaE1URWdNVEVnTUNBeElERWdNVEV0TVRFZ01URXVNREVnTVRFdU1ERWdNQ0F3SURFdE1URWdNVEVpTHo0OGNHRjBhQ0JtYVd4c1BTSWpRakl3UmpBeklpQmtQU0pOTVRjdU1ETTRJREU0TGpZeE5VZ3hOQzQ0TjB3eE5DNDFOak1nT1M0MWFESXVOemd6ZW0wdE1TNHdPRFFnTVM0ME1qZHhMalkySURBZ01TNHdOVGN1TXpnNExqUXdOeTR6T0RrdU5EQTNMams1TkNBd0lDNDFPVFl0TGpRd055NDVPRFF0TGpNNU55NHpPUzB4TGpBMU55NHpPRGt0TGpZMUlEQXRNUzR3TlRZdExqTTRPUzB1TXprNExTNHpPRGt0TGpNNU9DMHVPVGcwSURBdExqVTVOeTR6T1RndExqazROUzQwTURZdExqTTVOeUF4TGpBMU5pMHVNemszSWk4K1BDOXpkbWMrIik7YmFja2dyb3VuZC1yZXBlYXQ6bm8tcmVwZWF0O2JhY2tncm91bmQtc2l6ZTpjb250YWluO3BhZGRpbmctbGVmdDozNHB4fUBtZWRpYSAocHJlZmVycy1jb2xvci1zY2hlbWU6IGRhcmspe2JvZHl7YmFja2dyb3VuZC1jb2xvcjojMjIyO2NvbG9yOiNkOWQ5ZDl9fTwvc3R5bGU+PG1ldGEgaHR0cC1lcXVpdj0icmVmcmVzaCIgY29udGVudD0iMzYwIj48c2NyaXB0IHNyYz0iL2Nkbi1jZ2kvY2hhbGxlbmdlLXBsYXRmb3JtL2gvYi9vcmNoZXN0cmF0ZS9jaGxfcGFnZS92MT9yYXk9OTk1MWIxNTVmODE5ZTk0YiI+PC9zY3JpcHQ+PHN0eWxlPip7Ym94LXNpemluZzpib3JkZXItYm94O21hcmdpbjowO3BhZGRpbmc6MH1odG1se2xpbmUtaGVpZ2h0OjEuMTU7LXdlYmtpdC10ZXh0LXNpemUtYWRqdXN0OjEwMCU7Y29sb3I6IzMxMzEzMTtmb250LWZhbWlseTpzeXN0ZW0tdWksLWFwcGxlLXN5c3RlbSxCbGlua01hY1N5c3RlbUZvbnQsIlNlZ29lIFVJIixSb2JvdG8sIkhlbHZldGljYSBOZXVlIixBcmlhbCwiTm90byBTYW5zIixzYW5zLXNlcmlmLCJBcHBsZSBDb2xvciBFbW9qaSIsIlNlZ29lIFVJIEVtb2ppIiwiU2Vnb2UgVUkgU3ltYm9sIiwiTm90byBDb2xvciBFbW9qaSJ9YnV0dG9ue2ZvbnQtZmFtaWx5OnN5c3RlbS11aSwtYXBwbGUtc3lzdGVtLEJsaW5rTWFjU3lzdGVtRm9udCwiU2Vnb2UgVUkiLFJvYm90bywiSGVsdmV0aWNhIE5ldWUiLEFyaWFsLCJOb3RvIFNhbnMiLHNhbnMtc2VyaWYsIkFwcGxlIENvbG9yIEVtb2ppIiwiU2Vnb2UgVUkgRW1vamkiLCJTZWdvZSBVSSBTeW1ib2wiLCJOb3RvIENvbG9yIEVtb2ppIn1ib2R5e2Rpc3BsYXk6ZmxleDtmbGV4LWRpcmVjdGlvbjpjb2x1bW47aGVpZ2h0OjEwMHZoO21pbi1oZWlnaHQ6MTAwdmh9Ym9keS50aGVtZS1kYXJre2JhY2tncm91bmQtY29sb3I6IzIyMjtjb2xvcjojZDlkOWQ5fWJvZHkudGhlbWUtZGFyayBhe2NvbG9yOiNmZmZ9Ym9keS50aGVtZS1kYXJrIGE6aG92ZXJ7dGV4dC1kZWNvcmF0aW9uOnVuZGVybGluZTtjb2xvcjojZWU3MzBhfWJvZHkudGhlbWUtZGFyayAubGRzLXJpbmcgZGl2e2JvcmRlci1jb2xvcjojOTk5IHJnYmEoMCwwLDAsMCkgcmdiYSgwLDAsMCwwKX1ib2R5LnRoZW1lLWRhcmsgLmZvbnQtcmVke2NvbG9yOiNiMjBmMDN9Ym9keS50aGVtZS1kYXJrIC5jdHAtYnV0dG9ue2JhY2tncm91bmQtY29sb3I6IzQ2OTNmZjtjb2xvcjojMWQxZDFkfWJvZHkudGhlbWUtZGFyayAjY2hhbGxlbmdlLXN1Y2Nlc3MtdGV4dHtiYWNrZ3JvdW5kLWltYWdlOnVybCgiZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlCNGJXeHVjejBpYUhSMGNEb3ZMM2QzZHk1M015NXZjbWN2TWpBd01DOXpkbWNpSUhkcFpIUm9QU0l6TWlJZ2FHVnBaMmgwUFNJek1pSWdabWxzYkQwaWJtOXVaU0lnZG1sbGQwSnZlRDBpTUNBd0lESTJJREkySWo0OGNHRjBhQ0JtYVd4c1BTSWpaRGxrT1dRNUlpQmtQU0pOTVRNZ01HRXhNeUF4TXlBd0lERWdNQ0F3SURJMklERXpJREV6SURBZ01DQXdJREF0TWpadE1DQXlOR0V4TVNBeE1TQXdJREVnTVNBd0xUSXlJREV4SURFeElEQWdNQ0F4SURBZ01qSWlMejQ4Y0dGMGFDQm1hV3hzUFNJalpEbGtPV1E1SWlCa1BTSnRNVEF1T1RVMUlERTJMakExTlMwekxqazFMVFF1TVRJMUxURXVORFExSURFdU16ZzFJRFV1TXpjZ05TNDJNU0E1TGpRNU5TMDVMall0TVM0ME1pMHhMalF3TlhvaUx6NDhMM04yWno0Iil9Ym9keS50aGVtZS1kYXJrICNjaGFsbGVuZ2UtZXJyb3ItdGV4dHtiYWNrZ3JvdW5kLWltYWdlOnVybCgiZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlCNGJXeHVjejBpYUhSMGNEb3ZMM2QzZHk1M015NXZjbWN2TWpBd01DOXpkbWNpSUhkcFpIUm9QU0l6TWlJZ2FHVnBaMmgwUFNJek1pSWdabWxzYkQwaWJtOXVaU0krUEhCaGRHZ2dabWxzYkQwaUkwSXlNRVl3TXlJZ1pEMGlUVEUySUROaE1UTWdNVE1nTUNBeElEQWdNVE1nTVROQk1UTXVNREUxSURFekxqQXhOU0F3SURBZ01DQXhOaUF6YlRBZ01qUmhNVEVnTVRFZ01DQXhJREVnTVRFdE1URWdNVEV1TURFZ01URXVNREVnTUNBd0lERXRNVEVnTVRFaUx6NDhjR0YwYUNCbWFXeHNQU0lqUWpJd1JqQXpJaUJrUFNKTk1UY3VNRE00SURFNExqWXhOVWd4TkM0NE4wd3hOQzQxTmpNZ09TNDFhREl1TnpnemVtMHRNUzR3T0RRZ01TNDBNamR4TGpZMklEQWdNUzR3TlRjdU16ZzRMalF3Tnk0ek9Ea3VOREEzTGprNU5DQXdJQzQxT1RZdExqUXdOeTQ1T0RRdExqTTVOeTR6T1MweExqQTFOeTR6T0RrdExqWTFJREF0TVM0d05UWXRMak00T1MwdU16azRMUzR6T0RrdExqTTVPQzB1T1RnMElEQXRMalU1Tnk0ek9UZ3RMams0TlM0ME1EWXRMak01TnlBeExqQTFOaTB1TXprM0lpOCtQQzl6ZG1jKyIpfWJvZHkudGhlbWUtbGlnaHR7YmFja2dyb3VuZC1jb2xvcjojZmZmO2NvbG9yOiMzMTMxMzF9Ym9keS50aGVtZS1saWdodCBhe2NvbG9yOiMwMDUxYzN9Ym9keS50aGVtZS1saWdodCBhOmhvdmVye3RleHQtZGVjb3JhdGlvbjp1bmRlcmxpbmU7Y29sb3I6I2VlNzMwYX1ib2R5LnRoZW1lLWxpZ2h0IC5sZHMtcmluZyBkaXZ7Ym9yZGVyLWNvbG9yOiM1OTU5NTkgcmdiYSgwLDAsMCwwKSByZ2JhKDAsMCwwLDApfWJvZHkudGhlbWUtbGlnaHQgLmZvbnQtcmVke2NvbG9yOiNmYzU3NGF9Ym9keS50aGVtZS1saWdodCAuY3RwLWJ1dHRvbntib3JkZXItY29sb3I6IzAwMzY4MTtiYWNrZ3JvdW5kLWNvbG9yOiMwMDM2ODE7Y29sb3I6I2ZmZn1ib2R5LnRoZW1lLWxpZ2h0ICNjaGFsbGVuZ2Utc3VjY2Vzcy10ZXh0e2JhY2tncm91bmQtaW1hZ2U6dXJsKCJkYXRhOmltYWdlL3N2Zyt4bWw7YmFzZTY0LFBITjJaeUI0Yld4dWN6MGlhSFIwY0RvdkwzZDNkeTUzTXk1dmNtY3ZNakF3TUM5emRtY2lJSGRwWkhSb1BTSXpNaUlnYUdWcFoyaDBQU0l6TWlJZ1ptbHNiRDBpYm05dVpTSWdkbWxsZDBKdmVEMGlNQ0F3SURJMklESTJJajQ4Y0dGMGFDQm1hV3hzUFNJak16RXpNVE14SWlCa1BTSk5NVE1nTUdFeE15QXhNeUF3SURFZ01DQXdJREkySURFeklERXpJREFnTUNBd0lEQXRNalp0TUNBeU5HRXhNU0F4TVNBd0lERWdNU0F3TFRJeUlERXhJREV4SURBZ01DQXhJREFnTWpJaUx6NDhjR0YwYUNCbWFXeHNQU0lqTXpFek1UTXhJaUJrUFNKdE1UQXVPVFUxSURFMkxqQTFOUzB6TGprMUxUUXVNVEkxTFRFdU5EUTFJREV1TXpnMUlEVXVNemNnTlM0Mk1TQTVMalE1TlMwNUxqWXRNUzQwTWkweExqUXdOWG9pTHo0OEwzTjJaejQ9Iil9Ym9keS50aGVtZS1saWdodCAjY2hhbGxlbmdlLWVycm9yLXRleHR7YmFja2dyb3VuZC1pbWFnZTp1cmwoImRhdGE6aW1hZ2Uvc3ZnK3htbDtiYXNlNjQsUEhOMlp5QjRiV3h1Y3owaWFIUjBjRG92TDNkM2R5NTNNeTV2Y21jdk1qQXdNQzl6ZG1jaUlIZHBaSFJvUFNJek1pSWdhR1ZwWjJoMFBTSXpNaUlnWm1sc2JEMGlibTl1WlNJK1BIQmhkR2dnWm1sc2JEMGlJMlpqTlRjMFlTSWdaRDBpVFRFMklETmhNVE1nTVRNZ01DQXhJREFnTVRNZ01UTkJNVE11TURFMUlERXpMakF4TlNBd0lEQWdNQ0F4TmlBemJUQWdNalJoTVRFZ01URWdNQ0F4SURFZ01URXRNVEVnTVRFdU1ERWdNVEV1TURFZ01DQXdJREV0TVRFZ01URWlMejQ4Y0dGMGFDQm1hV3hzUFNJalptTTFOelJoSWlCa1BTSk5NVGN1TURNNElERTRMall4TlVneE5DNDROMHd4TkM0MU5qTWdPUzQxYURJdU56Z3plbTB0TVM0d09EUWdNUzQwTWpkeExqWTJJREFnTVM0d05UY3VNemc0TGpRd055NHpPRGt1TkRBM0xqazVOQ0F3SUM0MU9UWXRMalF3Tnk0NU9EUXRMak01Tnk0ek9TMHhMakExTnk0ek9Ea3RMalkxSURBdE1TNHdOVFl0TGpNNE9TMHVNems0TFM0ek9Ea3RMak01T0MwdU9UZzBJREF0TGpVNU55NHpPVGd0TGprNE5TNDBNRFl0TGpNNU55QXhMakExTmkwdU16azNJaTgrUEM5emRtYysiKX1he3RyYW5zaXRpb246Y29sb3IgMTUwbXMgZWFzZTtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsMCk7dGV4dC1kZWNvcmF0aW9uOm5vbmU7Y29sb3I6IzAwNTFjM31hOmhvdmVye3RleHQtZGVjb3JhdGlvbjp1bmRlcmxpbmU7Y29sb3I6I2VlNzMwYX0ubWFpbi1jb250ZW50e21hcmdpbjo4cmVtIGF1dG87cGFkZGluZy1yaWdodDoxLjVyZW07cGFkZGluZy1sZWZ0OjEuNXJlbTt3aWR0aDoxMDAlO21heC13aWR0aDo2MHJlbX0ubWFpbi1jb250ZW50IC5sb2FkaW5nLXZlcmlmeWluZ3toZWlnaHQ6NzYuMzkxcHh9LnNwYWNlcnttYXJnaW46MnJlbSAwfS5zcGFjZXItdG9we21hcmdpbi10b3A6NHJlbX0uc3BhY2VyLWJvdHRvbXttYXJnaW4tYm90dG9tOjJyZW19LmhlYWRpbmctZmF2aWNvbnttYXJnaW4tcmlnaHQ6LjVyZW07d2lkdGg6MnJlbTtoZWlnaHQ6MnJlbX1AbWVkaWEgKHdpZHRoIDw9IDcyMHB4KXsubWFpbi1jb250ZW50e21hcmdpbi10b3A6NHJlbX0uaGVhZGluZy1mYXZpY29ue3dpZHRoOjEuNXJlbTtoZWlnaHQ6MS41cmVtfX0ubWFpbi13cmFwcGVye2Rpc3BsYXk6ZmxleDtmbGV4OjE7ZmxleC1kaXJlY3Rpb246Y29sdW1uO2FsaWduLWl0ZW1zOmNlbnRlcn0uZm9udC1yZWR7Y29sb3I6I2IyMGYwM30uaDF7bGluZS1oZWlnaHQ6My43NXJlbTtmb250LXNpemU6Mi41cmVtO2ZvbnQtd2VpZ2h0OjUwMH0uaDJ7bGluZS1oZWlnaHQ6Mi4yNXJlbTtmb250LXNpemU6MS41cmVtO2ZvbnQtd2VpZ2h0OjUwMH0uY29yZS1tc2d7bGluZS1oZWlnaHQ6Mi4yNXJlbTtmb250LXNpemU6MS41cmVtO2ZvbnQtd2VpZ2h0OjQwMH0uYm9keS10ZXh0e2xpbmUtaGVpZ2h0OjEuMjVyZW07Zm9udC1zaXplOjFyZW07Zm9udC13ZWlnaHQ6NDAwfUBtZWRpYSAod2lkdGggPD0gNzIwcHgpey5oMXtsaW5lLWhlaWdodDoxLjc1cmVtO2ZvbnQtc2l6ZToxLjVyZW19Lmgye2xpbmUtaGVpZ2h0OjEuNXJlbTtmb250LXNpemU6MS4yNXJlbX0uY29yZS1tc2d7bGluZS1oZWlnaHQ6MS41cmVtO2ZvbnQtc2l6ZToxcmVtfX0jY2hhbGxlbmdlLWVycm9yLXRleHR7YmFja2dyb3VuZC1pbWFnZTp1cmwoImRhdGE6aW1hZ2Uvc3ZnK3htbDtiYXNlNjQsUEhOMlp5QjRiV3h1Y3owaWFIUjBjRG92TDNkM2R5NTNNeTV2Y21jdk1qQXdNQzl6ZG1jaUlIZHBaSFJvUFNJek1pSWdhR1ZwWjJoMFBTSXpNaUlnWm1sc2JEMGlibTl1WlNJK1BIQmhkR2dnWm1sc2JEMGlJMlpqTlRjMFlTSWdaRDBpVFRFMklETmhNVE1nTVRNZ01DQXhJREFnTVRNZ01UTkJNVE11TURFMUlERXpMakF4TlNBd0lEQWdNQ0F4TmlBemJUQWdNalJoTVRFZ01URWdNQ0F4SURFZ01URXRNVEVnTVRFdU1ERWdNVEV1TURFZ01DQXdJREV0TVRFZ01URWlMejQ4Y0dGMGFDQm1hV3hzUFNJalptTTFOelJoSWlCa1BTSk5NVGN1TURNNElERTRMall4TlVneE5DNDROMHd4TkM0MU5qTWdPUzQxYURJdU56Z3plbTB0TVM0d09EUWdNUzQwTWpkeExqWTJJREFnTVM0d05UY3VNemc0TGpRd055NHpPRGt1TkRBM0xqazVOQ0F3SUM0MU9UWXRMalF3Tnk0NU9EUXRMak01Tnk0ek9TMHhMakExTnk0ek9Ea3RMalkxSURBdE1TNHdOVFl0TGpNNE9TMHVNems0TFM0ek9Ea3RMak01T0MwdU9UZzBJREF0TGpVNU55NHpPVGd0TGprNE5TNDBNRFl0TGpNNU55QXhMakExTmkwdU16azNJaTgrUEM5emRtYysiKTtiYWNrZ3JvdW5kLXJlcGVhdDpuby1yZXBlYXQ7YmFja2dyb3VuZC1zaXplOmNvbnRhaW47cGFkZGluZy1sZWZ0OjM0cHh9I2NoYWxsZW5nZS1zdWNjZXNzLXRleHR7YmFja2dyb3VuZC1pbWFnZTp1cmwoImRhdGE6aW1hZ2Uvc3ZnK3htbDtiYXNlNjQsUEhOMlp5QjRiV3h1Y3owaWFIUjBjRG92TDNkM2R5NTNNeTV2Y21jdk1qQXdNQzl6ZG1jaUlIZHBaSFJvUFNJek1pSWdhR1ZwWjJoMFBTSXpNaUlnWm1sc2JEMGlibTl1WlNJZ2RtbGxkMEp2ZUQwaU1DQXdJREkySURJMklqNDhjR0YwYUNCbWFXeHNQU0lqTXpFek1UTXhJaUJrUFNKTk1UTWdNR0V4TXlBeE15QXdJREVnTUNBd0lESTJJREV6SURFeklEQWdNQ0F3SURBdE1qWnRNQ0F5TkdFeE1TQXhNU0F3SURFZ01TQXdMVEl5SURFeElERXhJREFnTUNBeElEQWdNaklpTHo0OGNHRjBhQ0JtYVd4c1BTSWpNekV6TVRNeElpQmtQU0p0TVRBdU9UVTFJREUyTGpBMU5TMHpMamsxTFRRdU1USTFMVEV1TkRRMUlERXVNemcxSURVdU16Y2dOUzQyTVNBNUxqUTVOUzA1TGpZdE1TNDBNaTB4TGpRd05Yb2lMejQ4TDNOMlp6ND0iKTtiYWNrZ3JvdW5kLXJlcGVhdDpuby1yZXBlYXQ7YmFja2dyb3VuZC1zaXplOmNvbnRhaW47cGFkZGluZy1sZWZ0OjQycHh9LnRleHQtY2VudGVye3RleHQtYWxpZ246Y2VudGVyfS5jdHAtYnV0dG9ue3RyYW5zaXRpb24tZHVyYXRpb246MjAwbXM7dHJhbnNpdGlvbi1wcm9wZXJ0eTpiYWNrZ3JvdW5kLWNvbG9yLGJvcmRlci1jb2xvcixjb2xvcjt0cmFuc2l0aW9uLXRpbWluZy1mdW5jdGlvbjplYXNlO21hcmdpbjoycmVtIDA7Ym9yZGVyOi4wNjNyZW0gc29saWQgIzAwNTFjMztib3JkZXItcmFkaXVzOi4zMTNyZW07YmFja2dyb3VuZC1jb2xvcjojMDA1MWMzO2N1cnNvcjpwb2ludGVyO3BhZGRpbmc6LjM3NXJlbSAxcmVtO2xpbmUtaGVpZ2h0OjEuMzEzcmVtO2NvbG9yOiNmZmY7Zm9udC1zaXplOi44NzVyZW19LmN0cC1idXR0b246aG92ZXJ7Ym9yZGVyLWNvbG9yOiMwMDM2ODE7YmFja2dyb3VuZC1jb2xvcjojMDAzNjgxO2N1cnNvcjpwb2ludGVyO2NvbG9yOiNmZmZ9LmZvb3RlcnttYXJnaW46MCBhdXRvO3BhZGRpbmctcmlnaHQ6MS41cmVtO3BhZGRpbmctbGVmdDoxLjVyZW07d2lkdGg6MTAwJTttYXgtd2lkdGg6NjByZW07bGluZS1oZWlnaHQ6MS4xMjVyZW07Zm9udC1zaXplOi43NXJlbX0uZm9vdGVyLWlubmVye2JvcmRlci10b3A6MXB4IHNvbGlkICNkOWQ5ZDk7cGFkZGluZy10b3A6MXJlbTtwYWRkaW5nLWJvdHRvbToxcmVtfS5jbGVhcmZpeDo6YWZ0ZXJ7ZGlzcGxheTp0YWJsZTtjbGVhcjpib3RoO2NvbnRlbnQ6IiJ9LmNsZWFyZml4IC5jb2x1bW57ZmxvYXQ6bGVmdDtwYWRkaW5nLXJpZ2h0OjEuNXJlbTt3aWR0aDo1MCV9LmRpYWdub3N0aWMtd3JhcHBlcnttYXJnaW4tYm90dG9tOi41cmVtfS5mb290ZXIgLnJheS1pZHt0ZXh0LWFsaWduOmNlbnRlcn0uZm9vdGVyIC5yYXktaWQgY29kZXtmb250LWZhbWlseTptb25hY28sY291cmllcixtb25vc3BhY2V9LmNvcmUtbXNnLC56b25lLW5hbWUtdGl0bGV7b3ZlcmZsb3ctd3JhcDpicmVhay13b3JkfUBtZWRpYSAod2lkdGggPD0gNzIwcHgpey5kaWFnbm9zdGljLXdyYXBwZXJ7ZGlzcGxheTpmbGV4O2ZsZXgtd3JhcDp3cmFwO2p1c3RpZnktY29udGVudDpjZW50ZXJ9LmNsZWFyZml4OjphZnRlcntkaXNwbGF5OmluaXRpYWw7Y2xlYXI6bm9uZTt0ZXh0LWFsaWduOmNlbnRlcjtjb250ZW50Om5vbmV9LmNvbHVtbntwYWRkaW5nLWJvdHRvbToycmVtfS5jbGVhcmZpeCAuY29sdW1ue2Zsb2F0Om5vbmU7cGFkZGluZzowO3dpZHRoOmF1dG87d29yZC1icmVhazprZWVwLWFsbH0uem9uZS1uYW1lLXRpdGxle21hcmdpbi1ib3R0b206MXJlbX19LmxvYWRpbmctdmVyaWZ5aW5ne2hlaWdodDo3Ni4zOTFweH0ubGRzLXJpbmd7ZGlzcGxheTppbmxpbmUtYmxvY2s7cG9zaXRpb246cmVsYXRpdmU7d2lkdGg6MS44NzVyZW07aGVpZ2h0OjEuODc1cmVtfS5sZHMtcmluZyBkaXZ7Ym94LXNpemluZzpib3JkZXItYm94O2Rpc3BsYXk6YmxvY2s7cG9zaXRpb246YWJzb2x1dGU7Ym9yZGVyOi4zcmVtIHNvbGlkICM1OTU5NTk7Ym9yZGVyLXJhZGl1czo1MCU7Ym9yZGVyLWNvbG9yOiMzMTMxMzEgcmdiYSgwLDAsMCwwKSByZ2JhKDAsMCwwLDApO3dpZHRoOjEuODc1cmVtO2hlaWdodDoxLjg3NXJlbTthbmltYXRpb246bGRzLXJpbmcgMS4ycyBjdWJpYy1iZXppZXIoLjUsIDAsIC41LCAxKSBpbmZpbml0ZX0ubGRzLXJpbmcgZGl2Om50aC1jaGlsZCgxKXthbmltYXRpb24tZGVsYXk6LS40NXN9Lmxkcy1yaW5nIGRpdjpudGgtY2hpbGQoMil7YW5pbWF0aW9uLWRlbGF5Oi0uM3N9Lmxkcy1yaW5nIGRpdjpudGgtY2hpbGQoMyl7YW5pbWF0aW9uLWRlbGF5Oi0uMTVzfUBrZXlmcmFtZXMgbGRzLXJpbmd7MCV7dHJhbnNmb3JtOnJvdGF0ZSgwZGVnKX0xMDAle3RyYW5zZm9ybTpyb3RhdGUoMzYwZGVnKX19LnJ0bCAuaGVhZGluZy1mYXZpY29ue21hcmdpbi1yaWdodDowO21hcmdpbi1sZWZ0Oi41cmVtfS5ydGwgI2NoYWxsZW5nZS1zdWNjZXNzLXRleHR7YmFja2dyb3VuZC1wb3NpdGlvbjpyaWdodDtwYWRkaW5nLXJpZ2h0OjQycHg7cGFkZGluZy1sZWZ0OjB9LnJ0bCAjY2hhbGxlbmdlLWVycm9yLXRleHR7YmFja2dyb3VuZC1wb3NpdGlvbjpyaWdodDtwYWRkaW5nLXJpZ2h0OjM0cHg7cGFkZGluZy1sZWZ0OjB9LmNoYWxsZW5nZS1jb250ZW50IC5sb2FkaW5nLXZlcmlmeWluZ3toZWlnaHQ6NzYuMzkxcHh9QG1lZGlhIChwcmVmZXJzLWNvbG9yLXNjaGVtZTogZGFyayl7Ym9keXtiYWNrZ3JvdW5kLWNvbG9yOiMyMjI7Y29sb3I6I2Q5ZDlkOX1ib2R5IGF7Y29sb3I6I2ZmZn1ib2R5IGE6aG92ZXJ7dGV4dC1kZWNvcmF0aW9uOnVuZGVybGluZTtjb2xvcjojZWU3MzBhfWJvZHkgLmxkcy1yaW5nIGRpdntib3JkZXItY29sb3I6Izk5OSByZ2JhKDAsMCwwLDApIHJnYmEoMCwwLDAsMCl9Ym9keSAuZm9udC1yZWR7Y29sb3I6I2IyMGYwM31ib2R5IC5jdHAtYnV0dG9ue2JhY2tncm91bmQtY29sb3I6IzQ2OTNmZjtjb2xvcjojMWQxZDFkfWJvZHkgI2NoYWxsZW5nZS1zdWNjZXNzLXRleHR7YmFja2dyb3VuZC1pbWFnZTp1cmwoImRhdGE6aW1hZ2Uvc3ZnK3htbDtiYXNlNjQsUEhOMlp5QjRiV3h1Y3owaWFIUjBjRG92TDNkM2R5NTNNeTV2Y21jdk1qQXdNQzl6ZG1jaUlIZHBaSFJvUFNJek1pSWdhR1ZwWjJoMFBTSXpNaUlnWm1sc2JEMGlibTl1WlNJZ2RtbGxkMEp2ZUQwaU1DQXdJREkySURJMklqNDhjR0YwYUNCbWFXeHNQU0lqWkRsa09XUTVJaUJrUFNKTk1UTWdNR0V4TXlBeE15QXdJREVnTUNBd0lESTJJREV6SURFeklEQWdNQ0F3SURBdE1qWnRNQ0F5TkdFeE1TQXhNU0F3SURFZ01TQXdMVEl5SURFeElERXhJREFnTUNBeElEQWdNaklpTHo0OGNHRjBhQ0JtYVd4c1BTSWpaRGxrT1dRNUlpQmtQU0p0TVRBdU9UVTFJREUyTGpBMU5TMHpMamsxTFRRdU1USTFMVEV1TkRRMUlERXVNemcxSURVdU16Y2dOUzQyTVNBNUxqUTVOUzA1TGpZdE1TNDBNaTB4TGpRd05Yb2lMejQ4TDNOMlp6NCIpfWJvZHkgI2NoYWxsZW5nZS1lcnJvci10ZXh0e2JhY2tncm91bmQtaW1hZ2U6dXJsKCJkYXRhOmltYWdlL3N2Zyt4bWw7YmFzZTY0LFBITjJaeUI0Yld4dWN6MGlhSFIwY0RvdkwzZDNkeTUzTXk1dmNtY3ZNakF3TUM5emRtY2lJSGRwWkhSb1BTSXpNaUlnYUdWcFoyaDBQU0l6TWlJZ1ptbHNiRDBpYm05dVpTSStQSEJoZEdnZ1ptbHNiRDBpSTBJeU1FWXdNeUlnWkQwaVRURTJJRE5oTVRNZ01UTWdNQ0F4SURBZ01UTWdNVE5CTVRNdU1ERTFJREV6TGpBeE5TQXdJREFnTUNBeE5pQXpiVEFnTWpSaE1URWdNVEVnTUNBeElERWdNVEV0TVRFZ01URXVNREVnTVRFdU1ERWdNQ0F3SURFdE1URWdNVEVpTHo0OGNHRjBhQ0JtYVd4c1BTSWpRakl3UmpBeklpQmtQU0pOTVRjdU1ETTRJREU0TGpZeE5VZ3hOQzQ0TjB3eE5DNDFOak1nT1M0MWFESXVOemd6ZW0wdE1TNHdPRFFnTVM0ME1qZHhMalkySURBZ01TNHdOVGN1TXpnNExqUXdOeTR6T0RrdU5EQTNMams1TkNBd0lDNDFPVFl0TGpRd055NDVPRFF0TGpNNU55NHpPUzB4TGpBMU55NHpPRGt0TGpZMUlEQXRNUzR3TlRZdExqTTRPUzB1TXprNExTNHpPRGt0TGpNNU9DMHVPVGcwSURBdExqVTVOeTR6T1RndExqazROUzQwTURZdExqTTVOeUF4TGpBMU5pMHVNemszSWk4K1BDOXpkbWMrIil9fTwvc3R5bGU+PHNjcmlwdCBzcmM9Imh0dHBzOi8vY2hhbGxlbmdlcy5jbG91ZGZsYXJlLmNvbS90dXJuc3RpbGUvdjAvYi9jODg3NTViMGNkZGMvYXBpLmpzP29ubG9hZD10Sk5jNiZhbXA7cmVuZGVyPWV4cGxpY2l0IiBhc3luYz0iIiBkZWZlcj0iIiBjcm9zc29yaWdpbj0iYW5vbnltb3VzIj48L3NjcmlwdD48L2hlYWQ+PGJvZHk+PGRpdiBjbGFzcz0ibWFpbi13cmFwcGVyIiByb2xlPSJtYWluIj48ZGl2IGNsYXNzPSJtYWluLWNvbnRlbnQiPjxoMSBjbGFzcz0iem9uZS1uYW1lLXRpdGxlIGgxIj5yZXNhLm5vdHJlZGFtZWRlcGFyaXMuZnI8L2gxPjxwIGlkPSJHRnB3azUiIGNsYXNzPSJoMiBzcGFjZXItYm90dG9tIj5WZXJpZnkgeW91IGFyZSBodW1hbiBieSBjb21wbGV0aW5nIHRoZSBhY3Rpb24gYmVsb3cuPC9wPjxkaXYgaWQ9IlRrdFJZMSIgc3R5bGU9ImRpc3BsYXk6IGdyaWQ7Ij48ZGl2PjxkaXY+PGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iY2YtdHVybnN0aWxlLXJlc3BvbnNlIiBpZD0iY2YtY2hsLXdpZGdldC1lMnM4c19yZXNwb25zZSI+PC9kaXY+PC9kaXY+PC9kaXY+PGRpdiBpZD0iZkd0Y1M3IiBjbGFzcz0ic3BhY2VyIGxvYWRpbmctdmVyaWZ5aW5nIiBzdHlsZT0iZGlzcGxheTogbm9uZTsgdmlzaWJpbGl0eTogaGlkZGVuOyI+PGRpdiBjbGFzcz0ibGRzLXJpbmciPjxkaXY+PC9kaXY+PGRpdj48L2Rpdj48ZGl2PjwvZGl2PjxkaXY+PC9kaXY+PC9kaXY+PC9kaXY+PGRpdiBpZD0iRGNIc08yIiBjbGFzcz0iY29yZS1tc2cgc3BhY2VyIHNwYWNlci10b3AiPnJlc2Eubm90cmVkYW1lZGVwYXJpcy5mciBuZWVkcyB0byByZXZpZXcgdGhlIHNlY3VyaXR5IG9mIHlvdXIgY29ubmVjdGlvbiBiZWZvcmUgcHJvY2VlZGluZy48L2Rpdj48ZGl2IGlkPSJOdU10MyIgc3R5bGU9ImRpc3BsYXk6IG5vbmU7Ij48ZGl2IGlkPSJjaGFsbGVuZ2Utc3VjY2Vzcy10ZXh0IiBjbGFzcz0iaDIiPlZlcmlmaWNhdGlvbiBzdWNjZXNzZnVsPC9kaXY+PGRpdiBjbGFzcz0iY29yZS1tc2cgc3BhY2VyIj5XYWl0aW5nIGZvciByZXNhLm5vdHJlZGFtZWRlcGFyaXMuZnIgdG8gcmVzcG9uZC4uLjwvZGl2PjwvZGl2Pjxub3NjcmlwdD48ZGl2IGNsYXNzPSJoMiI+PHNwYW4gaWQ9ImNoYWxsZW5nZS1lcnJvci10ZXh0Ij5FbmFibGUgSmF2YVNjcmlwdCBhbmQgY29va2llcyB0byBjb250aW51ZTwvc3Bhbj48L2Rpdj48L25vc2NyaXB0PjwvZGl2PjwvZGl2PjxzY3JpcHQ+KGZ1bmN0aW9uKCl7d2luZG93Ll9jZl9jaGxfb3B0ID0ge2N2SWQ6ICczJyxjWm9uZTogJ3Jlc2Eubm90cmVkYW1lZGVwYXJpcy5mcicsY1R5cGU6ICdtYW5hZ2VkJyxjUmF5OiAnOTk1MWIxNTVmODE5ZTk0YicsY0g6ICdZTVBjcU5HVm1oSGptNmk4YXdqLllxWmRiZmJ5dDZfRWlIaXR2aHZMcnlRLTE3NjE1NjI4NTctMS4yLjEuMS1WOE90alI2el80QmFCdjlkaUQ4eWowMkVhUkpQYWNjVDZ3WGE4cVpaRjlyUno4a2d4eXp1WG9tVmxDbk0xWFpyJyxjVVBNRFRrOiJcLz9fX2NmX2NobF90az0xaERpMjR5VWxpN0FZSDFPWVA3b3hnWngycHF2Wlp5a0RjRXIxOU9JUHJrLTE3NjE1NjI4NTctMS4wLjEuMS1wYUZTd25ZYkxkVmVqSXhUbk02RUlwTTJmSGZVVEJ4cmw5ZWR4RERnUnh3IixjRlBXdjogJ2InLGNJVGltZVM6ICcxNzYxNTYyODU3JyxjVHBsQzowLGNUcGxWOjUsY1RwbEI6ICcwJyxmYToiXC8/X19jZl9jaGxfZl90az0xaERpMjR5VWxpN0FZSDFPWVA3b3hnWngycHF2Wlp5a0RjRXIxOU9JUHJrLTE3NjE1NjI4NTctMS4wLjEuMS1wYUZTd25ZYkxkVmVqSXhUbk02RUlwTTJmSGZVVEJ4cmw5ZWR4RERnUnh3IixtZDogJ1ZIemtCN0xxS0dENEhaNUVRYV9hQUpueVQuX3h2V2g0WGtyUUc1WGhkaEEtMTc2MTU2Mjg1Ny0xLjIuMS4xLXJmRWRJOXV2ckY3cFI3NEd1QmRwM2VRalBkM2pWY0NtRkNGS3RfWVh3blExTlVxa2VJbkouclBJS0VTSmNhaTd5ZTJQcUdZeDRLa1ZMYVhpMHltWHBLS01xT3k0VGxQaEd1V1h1a1BmUjVYZG5pdkdnRFE4WUhzNloweGRBTHlhcHduNjdNYWtRSE9iRXFQd05udEhFck9uT1RneGdIZzBrS3dqeTZ5d1hhS25QdGwuNGl4Snl4Z3M5R2pxMnEzN3JMUUpkeU90ejBkTWVVcWd0V3Zpa0dLZWNqZ3Z2eTRiR3JGanNIbTdSNHRqbGI2UnIuT3psV3VzUjdZVG9fcXZ0NUZyTkFJNUxaSzdoMnlZUEFhVFFZRGhvN2xkRG0zdHp6UEkxTHdHUDZQWmRsRmtIYURyUW9KTVVCZThQLk82b1VsVUV0UXoxQ3c5YktOYXJJSXN5Mk0zRkI0b3YzQWZfZDZVdTVlMDVaSHhUajdQQW53a201cFRIOVh4NlhacW5HZ2JWSHBIN2FxYnhQd1FQblllNHFVVDZVY0JDMHVPOW5ibzJyMldndTdJOG0zSHVjRTAwbnBRNXhBZE9GMlJPVlp0LlVqcktrdDRFN21lUmJkWE5jcVpPR3JfVXcxUkxxNzEwQnVJSDFONWllc3pzMDFiVUliVVhrV1ZyV3RhLi5lMHY4N0t0U0JEUmU4eWpndHFTQjV0YW1rdENtdldvalpVNDBuSTlfbF9jLk5INnFPUTdla19jT0xhNHBFbFdFUWtuZ0MueG9EdnNuTnF5RFJiZ1VHdUtUUlNET0xxOHlRUG9SdmpXUDh0ckFmVm9NTUdNSG14UVFSYzlQdEZPXzBtekIuVG1XQWJqeVBrQXZtOWs5OTg5S0RKU2Z0WFNIdXRJOER3cHhFalZhWmt1Tl85SXpFakxERkNXQkRCYmFCdC5tRy44aDk3RHVvVEh2aDM2bS5DSUlvZjR5MEd6TDhmQWx4MHdQQ3h0U0hVZjdVZ0U2XzhkSjl3S0FQTU1xeEhWVnJHTEJHblZMOHl2NFJHY3VWTklQd1ByOTV4RFd4UjlWaS5QbnNwdnVxSFFvbmVoSlZSMURNUmVlNWxCU1RvNHMzNy5MLnl3QnFTVHM3OVpoeVZ0el81NlhhZjEzbExGZFFhWDF0WFoyclcuSURKdnNnWnhOYVU4MFlJOEdrbjNCZ21VUmhXWFl1Q1ppMk4ueU1paHFSZXBrVnh5Z3dXWWxtdWVZMm1udW9JU21qVXhKMG0xZzQxZU5FUHQ5dXJWVjlsbVVuYU1vQlI1a0wzMW5pUFh6QWt3QmlpeTc5aW9wYWpnU2R0eVZXdGtUamtscjh1MFRsUnV2OUJVOGRMNTBvWjVTUmVVdE5hdXZvajJYTS5zcGZvYlpiLjBLYWFMakwuM3g0JyxtZHJkOiAnbkd3eHl2Y2lIQU1MTnFWYU1sdjQ1X1NKN3hZRlpYOGhPcnFWazlWZU1ZRS0xNzYxNTYyODU3LTEuMi4xLjEtdWVzYzZHbkJlMHJJZ2oxTlk4TlhpRm01a0R2OW9HX0ExTFR2RWsuSHgxWV9LbkJ6dkZhdDhHWjI3d0xOOWt4ZWxESnNPMklOdWt4Y2Q3V1RYN1JiRzkyTU1WRFZmTG5HRDBQaXdtcE1GREVsNzNBMUdhU2tUX1JNNjIyaWVMMkpCUmdYeEtTamtQckhNWmZXZVNBbnkubEMubGtDM0NwaXBoQ3N6Wk5VV0pGTUVNcW9RZVJLbXkwWmtPUE4yaTcxZ0I3eE81REZteHBTWGlsckREdGpTcG1jZW8wWFpMMTVkLlFzMFkySlpncDlicC5Oc3hSZUJOallPR0s4dnhyU1pncmZ0YlhBUHNDejNXR3ZVbVFyczRwNWczajJ0X0ROVU14cUVxdm55cUVlRDhod2dDZG9OUXMxZHBRZTh0dWpSdGpLcnZadDFXRFFGcHBtcHZYRU1nMzZYVHFHNWgzWnFXdmJ6QVBxbVYyMElsOGdoc3ZZdEhUWl94aUZPY3RSMlh0VEFPWnJJeDc3SEpiQUU3UDRXcjBCSkFwNU5BVTRTZ2IwbmdsU05wVXd4LmNDbndZZGVlXzJZMzZneWRGT3F2SEJkRDRNSWpOeU8yVnB2RzczdkRndUdMdVNuUU5aSE9NbndTSlZoVFFQQk9aUVFfV3EwZ1lBMU1NTFYzSkUzLnh6Zmd6cUpwNDVNQ3BfYU5SRmFYUWo5NGZkb3hwdlJkWVdseVVZSzlwdlM4N3dDc3kucFpsX25ENllncXdBLjlFRGVsSFJfTHNjaVh4S19WbWZnUl9kWmJ6OGYwQnp3UEJITEJZTEk4VUFIbjdoaDFWUlBucUlOUW43X290aXBTaHVOSlAwd25BOHBmQUpxMWxXZGNEczFoUE82bWg3bFNmUkl3dml4dXguWmk1Y2FOUkR1cS5pc2JfNkc5eV83aldSbzJfbFB1akgxWlVBNDBFck5wdjRmOGhjRG1uTFR3R0RHVGw1cHhBc1d5M1pBSU5qeThiTG5CbmJrNFJPUEU3WEQ1MUM4ZkVPNHFfVGdaVzJWUGlyQnNtQnFabEdPWnhyMS5fWTNOdzZHWEd0d01WN0p3N3k4bk5ja0lGdk1POW5PRnBrZkRzNWFsZUlpWkNSOGp5RTk3WjBaX3NiajdJU2lLVXBLM3pRTzlpdDhydXdST2EyelpUZ3ZZWDcyWUMxdzZ0M1VFWXhoeTFha2pVbHc1QVFPQkZhODhMVlV6X0VZRE9vMzViMzVFYl8yYjJheFVwV3EwQ3hwRnlPQlN5VlFXNnhvNVV4TklsSmdDZHZCQzBaWGVBUmpkaDQ0V240dURjRmt2eGIzaFFMTkVkUm92R0t3QlBmR2JRdk0wUHlLbUw0SzY2RUNIT2pPM2NCWFNDNmYxRGdCS0hzV2ZKUFFHVGV3VHhMMEF6cUFsOUNjUXcxOVU3LmFBM2VhMnA4eDhqWUFpU3g3ZGNKbEVRMUswS1hyaDl3NVcuS041SFB3RzY2YmYyS0dyUm1Mc2RZd2hpYlBoNlhvcDVJRThhZVluUnFvYmpsZEl2a1o0RGNfSWRXT3ppQUtYM01YSGlWMkJUWFpSeERLbWl2YjF2N2RCZEpDM19jSE5LcUZVWDI3ejBKOFVXRlQ0T01pbVFhQ3hRd3JQV0tCdE9LMmR4TncyZjFHSlBUQmhxVlVSOF8xaGtQTElwdzFvaUFOczcxaGpWUU5EZG9tWlZ1bVAuelFXQjN0N2U0TU9icUJIeThsLnFxeXo0WWV3SWVlSk1Ja1lCZlFMdVpwUV9MZmY4OWZ1cUVpMnA0MHJ4NEEycnVvc1lMeGZRWGVHM1FTSWxrdk96Qm4wemNmMU55TG4uR2tfZVFvYzlYN1lDR2JxTUFMS2hDR3pXM1l0VlZaeU1MSmUxYlN1d0NMc0RvVUpxcnZkUWh4dHhvVU9HZDF1dnBoUmRmOTdYbTRFdmFkWmFDVkI4bnRYb181MmpYUnltYTk3cGZEajU4U25kR21ld3BReWthYUdrem5tRncxajlnRk4ySG9qZnFtZFBVRW44eDM2VFZJRm1sNzJjVXhZWG5mU1EwNTNHYXhuTVR2T3NrZzkuMjJOWXp5cWRuNnNPaHdnMnVvU3lpanAuYnNoYklPYlFzUmxOY05IY1prNHZCa2w2RFVjaEc4WklJRTNObWRsTi45czZSeEk0UGlfUEtKaXJnR0duTUxXWUZrd2lESEc3TGtFdUQ5TkdoYU5WNVNLZVFXY0M3MnlZcHp0MU04VTduVDZ3S1ZKWmJMd2dGQTFXeXFNNXYzajJhYl9ZdEgySFNINGNkeDFRYWxqUkpTcWgzNXRFU1ViLi5vVmppZDh1UFJQdUxVODg3TG45MEx1WENWUnpCN0xWaEpoUG00ZENaWDgyejBnbmluUlkwcFZNOVhaYU90VXdkTlBIN0JKanoyUlo1N2xDN3VEbVQ4M1VoY1R1TjZCNFBlTlJhek9ZZGVOaG1FN0cxZU9sVnROaEoucUlRdHdWc3Zick9lWTBoTjM1ZklkUGU4TEhhQkxyUzVwdUg2ZzRHbWZLVUF4Q29NeVRIcnNqTnlYbGV3cG9HOEdNX0JzazRyMUhpWmFMSk9pYkVPM3hqMGRlV0lIZ3BaRnFoaHNZX2RZLndwTWxaR04zeHF6clFKekdFa191UkdPVFNaa0NpM1oxd21HZmFDVmFpVnhTd2NoRk9oOUVWN25EYUJHRFBCYVd6cGhhNXZoSnRVU0toRWxpNVpBQXE2ZndQUDRHY0xFR2xCSU52VGlMZ2FnZTRUbGpPbGU0azJpUDRzWHJqUy5NcnJLUHQxTjZ6RW85R0xfSGlEY0Q3WjBWMnlDeU5uMjlRUUhzNnJBeWdxVmlQVGNjR3Fscl9UdF80eWljc3ZDYW5nVFM0X3VrWWtlclhhdkZudVQyeklCeTNOVmt4SkpoSllQeEtpbElJb1hBLm1RSGZWRHVCcUNXRTR0MC5oLkpDakdXMXpyOHd2ek5xR1dZX283aGthbUpac0NENlZ5VU8ua3RPSVRqQll6blZjUTdWYndjWDVZRjh3YTNvMktLTF83TGtVT3FwSmZQelZ1VkFhYXFiekcxQ3pzaG9yZDFxdVJHbXA1eWVqelRDYmREaU9XZnpVTjdxOFlSbm9JenlKZ1dNOHF5OGVhdGVWUkhaT2VKWHpLQUo0VmYuWEZkQ0hENDNGX1FVcnVjYjRQQ1J4YUtfemhwemFjZVFIYTd4UlZ3d3RTTkVWS01xWEJEV3FSdnhqMk94WGM0YVJtZ3pIYUE2dUY0SDBYaFhSODdQYXZsTnBnQ29lV0Q3R2F5V0FNOW9JSXpNdFBabnZYMGlOaDBvbTlSeUlGV1N5cW1zd1dtMC5tdDV0Lk9SQ3VKYm1QRVBGbHppcnN5Qjc1dDROZlRtUEJ6UlZCY2tkSEtXMVdEaUp1ZGRISTJMcm5QSXdjOWZ2Z2hybU5xN0Uxc3ZIM3ZBXzg4U3JoQzEnLH07dmFyIGEgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzY3JpcHQnKTthLnNyYyA9ICcvY2RuLWNnaS9jaGFsbGVuZ2UtcGxhdGZvcm0vaC9iL29yY2hlc3RyYXRlL2NobF9wYWdlL3YxP3JheT05OTUxYjE1NWY4MTllOTRiJzt3aW5kb3cuX2NmX2NobF9vcHQuY09nVUhhc2ggPSBsb2NhdGlvbi5oYXNoID09PSAnJyAmJiBsb2NhdGlvbi5ocmVmLmluZGV4T2YoJyMnKSAhPT0gLTEgPyAnIycgOiBsb2NhdGlvbi5oYXNoO3dpbmRvdy5fY2ZfY2hsX29wdC5jT2dVUXVlcnkgPSBsb2NhdGlvbi5zZWFyY2ggPT09ICcnICYmIGxvY2F0aW9uLmhyZWYuc2xpY2UoMCwgbG9jYXRpb24uaHJlZi5sZW5ndGggLSB3aW5kb3cuX2NmX2NobF9vcHQuY09nVUhhc2gubGVuZ3RoKS5pbmRleE9mKCc/JykgIT09IC0xID8gJz8nIDogbG9jYXRpb24uc2VhcmNoO2lmICh3aW5kb3cuaGlzdG9yeSAmJiB3aW5kb3cuaGlzdG9yeS5yZXBsYWNlU3RhdGUpIHt2YXIgb2dVID0gbG9jYXRpb24ucGF0aG5hbWUgKyB3aW5kb3cuX2NmX2NobF9vcHQuY09nVVF1ZXJ5ICsgd2luZG93Ll9jZl9jaGxfb3B0LmNPZ1VIYXNoO2hpc3RvcnkucmVwbGFjZVN0YXRlKG51bGwsIG51bGwsIlwvP19fY2ZfY2hsX3J0X3RrPTFoRGkyNHlVbGk3QVlIMU9ZUDdveGdaeDJwcXZaWnlrRGNFcjE5T0lQcmstMTc2MTU2Mjg1Ny0xLjAuMS4xLXBhRlN3blliTGRWZWpJeFRuTTZFSXBNMmZIZlVUQnhybDllZHhERGdSeHciKyB3aW5kb3cuX2NmX2NobF9vcHQuY09nVUhhc2gpO2Eub25sb2FkID0gZnVuY3Rpb24oKSB7aGlzdG9yeS5yZXBsYWNlU3RhdGUobnVsbCwgbnVsbCwgb2dVKTt9fWRvY3VtZW50LmdldEVsZW1lbnRzQnlUYWdOYW1lKCdoZWFkJylbMF0uYXBwZW5kQ2hpbGQoYSk7fSgpKTs8L3NjcmlwdD48c2NyaXB0IGRlZmVyPSIiIHNyYz0iaHR0cHM6Ly9zdGF0aWMuY2xvdWRmbGFyZWluc2lnaHRzLmNvbS9iZWFjb24ubWluLmpzL3ZjZDE1Y2JlNzc3MmY0OWMzOTljNmE1YmFiZjIyYzEyNDE3MTc2ODkxNzYwMTUiIGludGVncml0eT0ic2hhNTEyLVpwc09tbFJRVjZ5OTA3VEkwZEtCSHE5TWQyOW5uYUVJUGxrZjg0cm5hRVJucTZ6dld2UFVxcjJmdDhNMWFTMjhvTjcyUGRyQ3pTalk0VTZWYUF3MUVRPT0iIGRhdGEtY2YtYmVhY29uPSJ7JnF1b3Q7cmF5SWQmcXVvdDs6JnF1b3Q7OTk1MWIxNTVmODE5ZTk0YiZxdW90OywmcXVvdDtzZXJ2ZXJUaW1pbmcmcXVvdDs6eyZxdW90O25hbWUmcXVvdDs6eyZxdW90O2NmRXh0UHJpJnF1b3Q7OnRydWUsJnF1b3Q7Y2ZFZGdlJnF1b3Q7OnRydWUsJnF1b3Q7Y2ZPcmlnaW4mcXVvdDs6dHJ1ZSwmcXVvdDtjZkw0JnF1b3Q7OnRydWUsJnF1b3Q7Y2ZTcGVlZEJyYWluJnF1b3Q7OnRydWUsJnF1b3Q7Y2ZDYWNoZVN0YXR1cyZxdW90Ozp0cnVlfX0sJnF1b3Q7dmVyc2lvbiZxdW90OzomcXVvdDsyMDI1LjkuMSZxdW90OywmcXVvdDt0b2tlbiZxdW90OzomcXVvdDtjMzgxZTcwZWFlNDQ0MzE4YmQzMjk2Y2M4ODlmNjdiNiZxdW90O30iIGNyb3Nzb3JpZ2luPSJhbm9ueW1vdXMiPjwvc2NyaXB0Pgo8ZGl2IGNsYXNzPSJmb290ZXIiIHJvbGU9ImNvbnRlbnRpbmZvIj48ZGl2IGNsYXNzPSJmb290ZXItaW5uZXIiPjxkaXYgY2xhc3M9ImNsZWFyZml4IGRpYWdub3N0aWMtd3JhcHBlciI+PGRpdiBjbGFzcz0icmF5LWlkIj5SYXkgSUQ6IDxjb2RlPjk5NTFiMTU1ZjgxOWU5NGI8L2NvZGU+PC9kaXY+PC9kaXY+PGRpdiBjbGFzcz0idGV4dC1jZW50ZXIiIGlkPSJmb290ZXItdGV4dCI+UGVyZm9ybWFuY2UgJmFtcDsgc2VjdXJpdHkgYnkgPGEgcmVsPSJub29wZW5lciBub3JlZmVycmVyIiBocmVmPSJodHRwczovL3d3dy5jbG91ZGZsYXJlLmNvbT91dG1fc291cmNlPWNoYWxsZW5nZSZhbXA7dXRtX2NhbXBhaWduPW0iIHRhcmdldD0iX2JsYW5rIj5DbG91ZGZsYXJlPC9hPjwvZGl2PjwvZGl2PjwvZGl2PjwvYm9keT48L2h0bWw+", + proxy=proxy + ) + + solution = await cap_monster_client.solve_captcha(turnstile_request) + print("Solution:", solution) + +asyncio.run(main()) diff --git a/test/altcha_test.py b/test/altcha_test.py new file mode 100644 index 0000000..728b89a --- /dev/null +++ b/test/altcha_test.py @@ -0,0 +1,79 @@ +import unittest + +from pydantic.error_wrappers import ValidationError +from capmonstercloudclient.requests import AltchaCustomTaskRequest + + +class AltchaCustomTaskRequestTest(unittest.TestCase): + websiteUrlExample = "https://example.com" + websiteKeyExample = "altcha-public-key-123" + userAgentExample = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36" + challengeExample = "challenge_string_here" + iterationsExample = "1000" + saltExample = "salt_string_here" + signatureExample = "signature_string_here" + + def test_altcha_request_required_fields(self): + required_fields = ["type", "class", "websiteURL", "websiteKey", "metadata"] + metadata_required_fields = ["challenge", "iterations", "salt", "signature"] + metadata_example = { + "challenge": self.challengeExample, + "iterations": self.iterationsExample, + "salt": self.saltExample, + "signature": self.signatureExample, + } + request = AltchaCustomTaskRequest( + 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"], "altcha") + self.assertEqual(task_dictionary["type"], "CustomTask") + + def test_altcha_metadata_validation(self): + base_kwargs = { + "websiteUrl": self.websiteUrlExample, + "websiteKey": self.websiteKeyExample, + "metadata": {} + } + self.assertRaises(TypeError, AltchaCustomTaskRequest, **base_kwargs) + base_kwargs["metadata"]["challenge"] = self.challengeExample + self.assertRaises(TypeError, AltchaCustomTaskRequest, **base_kwargs) + base_kwargs["metadata"]["iterations"] = self.iterationsExample + self.assertRaises(TypeError, AltchaCustomTaskRequest, **base_kwargs) + base_kwargs["metadata"]["salt"] = self.saltExample + self.assertRaises(TypeError, AltchaCustomTaskRequest, **base_kwargs) + base_kwargs["metadata"]["signature"] = self.signatureExample + AltchaCustomTaskRequest(**base_kwargs) + + def test_altcha_missing_fields(self): + base_kwargs = {} + self.assertRaises(ValidationError, AltchaCustomTaskRequest, **base_kwargs) + base_kwargs.update({"websiteUrl": self.websiteUrlExample}) + self.assertRaises(ValidationError, AltchaCustomTaskRequest, **base_kwargs) + base_kwargs.update({"websiteKey": self.websiteKeyExample}) + self.assertRaises(ValidationError, AltchaCustomTaskRequest, **base_kwargs) + metadata_example = { + "challenge": self.challengeExample, + "iterations": self.iterationsExample, + "salt": self.saltExample, + "signature": self.signatureExample, + } + base_kwargs.update({"metadata": metadata_example}) + AltchaCustomTaskRequest(**base_kwargs) + + +if __name__ == "__main__": + unittest.main() + diff --git a/test/imperva_request_test.py b/test/imperva_request_test.py index 16ac86b..0edf1ea 100644 --- a/test/imperva_request_test.py +++ b/test/imperva_request_test.py @@ -2,7 +2,7 @@ from copy import deepcopy from pydantic import ValidationError -from capmonstercloudclient.requests import ImpervaCustomTaskRequest +from capmonstercloudclient.requests import ImpervaCustomTaskRequest, ProxyInfo class ImpervaRequestTest(unittest.TestCase): @@ -14,17 +14,26 @@ class ImpervaRequestTest(unittest.TestCase): incapsulaCookieExample = "l/LsGnrvyB9lNhXI8borDKa2IGcAAAAAX0qAEHheCWuNDquzwb44cw=" reese84UrlEndpointExample = "Built-with-the-For-hopence-Hurleysurfecting-the-" + def setUp(self): + self.proxy = ProxyInfo( + proxyType="http", + proxyAddress="8.8.8.8", + proxyPort=8000, + proxyLogin="proxyLoginHere", + proxyPassword="proxyPasswordHere" + ) + def test_imperva( self, ): - required_fields = ["type", "websiteURL", "metadata"] + required_fields = ["type", "websiteURL", "metadata", "proxyType", "proxyAddress", "proxyPort", "proxyLogin", "proxyPassword"] metadata_required_fields = ["incapsulaScriptUrl", "incapsulaCookie"] metadata_example = { "incapsulaScriptUrl": self.incapsulaScriptUrlExample, "incapsulaCookie": self.incapsulaCookieExample, } request = ImpervaCustomTaskRequest( - websiteUrl=self.websiteUrlExample, metadata=metadata_example + websiteUrl=self.websiteUrlExample, metadata=metadata_example, proxy=self.proxy ) task_dictionary = request.getTaskDict() for f in required_fields: @@ -41,7 +50,7 @@ def test_imperva( def test_imperva_metadata( self, ): - base_kwargs = {"websiteUrl": self.websiteUrlExample, "metadata": {}} + base_kwargs = {"websiteUrl": self.websiteUrlExample, "metadata": {}, "proxy": self.proxy} self.assertRaises(TypeError, ImpervaCustomTaskRequest, **base_kwargs) base_kwargs["metadata"]["incapsulaScriptUrl"] = self.incapsulaScriptUrlExample self.assertRaises(TypeError, ImpervaCustomTaskRequest, **base_kwargs) @@ -59,10 +68,13 @@ def test_imperva_missing( "incapsulaScriptUrl": self.incapsulaScriptUrlExample, "incapsulaCookie": self.incapsulaCookieExample, } + self.assertRaises(RuntimeError, ImpervaCustomTaskRequest, **base_kwargs) + base_kwargs.update({"proxy": self.proxy}) self.assertRaises(ValidationError, ImpervaCustomTaskRequest, **base_kwargs) base_kwargs.update({"websiteUrl": self.websiteUrlExample}) self.assertRaises(ValidationError, ImpervaCustomTaskRequest, **base_kwargs) base_kwargs.update({"metadata": metadata_example}) + ImpervaCustomTaskRequest(**base_kwargs) diff --git a/test/recaptchaV3Enterprise_test.py b/test/recaptchaV3Enterprise_test.py new file mode 100644 index 0000000..2c7ce32 --- /dev/null +++ b/test/recaptchaV3Enterprise_test.py @@ -0,0 +1,62 @@ +import unittest + +from pydantic.error_wrappers import ValidationError +from capmonstercloudclient.requests import RecaptchaV3EnterpriseRequest + + +class RecaptchaV3EnterpriseRequestTest(unittest.TestCase): + websiteUrlExample = "https://example.com" + websiteKeyExample = "6Le0xVgUAAAAAIt20XEB4rVhYOODgTl00d8juDob" + minScoreExample = 0.9 + pageActionExample = "submit" + + def test_captcha_input_types(self): + with self.assertRaises(ValidationError): + request = RecaptchaV3EnterpriseRequest( + websiteUrl=RecaptchaV3EnterpriseRequestTest.websiteUrlExample + ) + + with self.assertRaises(ValidationError): + request = RecaptchaV3EnterpriseRequest( + websiteKey=RecaptchaV3EnterpriseRequestTest.websiteKeyExample, + ) + + request = RecaptchaV3EnterpriseRequest( + websiteUrl=RecaptchaV3EnterpriseRequestTest.websiteUrlExample, + websiteKey=RecaptchaV3EnterpriseRequestTest.websiteKeyExample, + minScore=RecaptchaV3EnterpriseRequestTest.minScoreExample, + pageAction=RecaptchaV3EnterpriseRequestTest.pageActionExample, + ) + + def test_all_required_fields_filling(self): + required_fields = ["type", "websiteURL", "websiteKey"] + request = RecaptchaV3EnterpriseRequest( + websiteUrl=RecaptchaV3EnterpriseRequestTest.websiteUrlExample, + websiteKey=RecaptchaV3EnterpriseRequestTest.websiteKeyExample, + minScore=RecaptchaV3EnterpriseRequestTest.minScoreExample, + pageAction=RecaptchaV3EnterpriseRequestTest.pageActionExample, + ) + request_dict = request.getTaskDict() + for i in required_fields: + self.assertTrue( + i in list(request_dict.keys()), + msg=f"Required field {i} not in {request_dict}", + ) + + self.assertEqual(request_dict["type"], "RecaptchaV3EnterpriseTask") + self.assertEqual(request_dict["minScore"], self.minScoreExample) + self.assertEqual(request_dict["pageAction"], self.pageActionExample) + + def test_optional_fields(self): + request = RecaptchaV3EnterpriseRequest( + websiteUrl=RecaptchaV3EnterpriseRequestTest.websiteUrlExample, + websiteKey=RecaptchaV3EnterpriseRequestTest.websiteKeyExample, + ) + request_dict = request.getTaskDict() + self.assertNotIn("minScore", request_dict) + self.assertNotIn("pageAction", request_dict) + + +if __name__ == "__main__": + unittest.main() + From b34e9b25faa900422205e41193e9e64a59aea043 Mon Sep 17 00:00:00 2001 From: "pavel.grinkevich" Date: Wed, 14 Jan 2026 14:31:02 +0000 Subject: [PATCH 2/4] fix yidun, altcha --- capmonstercloud_client/CapMonsterCloudClient.py | 4 +--- capmonstercloud_client/requests/__init__.py | 5 +++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/capmonstercloud_client/CapMonsterCloudClient.py b/capmonstercloud_client/CapMonsterCloudClient.py index 34c8983..8ffa4c1 100644 --- a/capmonstercloud_client/CapMonsterCloudClient.py +++ b/capmonstercloud_client/CapMonsterCloudClient.py @@ -36,7 +36,6 @@ ((YidunRequest), getYidunTimeouts), ((TemuCustomTaskRequest), getTemuTimeouts), ((ProsopoTaskRequest), getProsopoTimeouts), - ((YidunRequest), getImage2TextTimeouts), ((AltchaCustomTaskRequest), getAltchaTimeouts), ) @@ -99,9 +98,8 @@ async def solve_captcha(self, request: Union[ RecognitionComplexImageTaskRequest, MTCaptchaRequest, YidunRequest, - AltchaCustomTaskRequest, TemuCustomTaskRequest, - ProsopoTaskRequest], + ProsopoTaskRequest, AltchaCustomTaskRequest], ) -> Dict[str, str]: ''' diff --git a/capmonstercloud_client/requests/__init__.py b/capmonstercloud_client/requests/__init__.py index 29b8df3..235c29d 100644 --- a/capmonstercloud_client/requests/__init__.py +++ b/capmonstercloud_client/requests/__init__.py @@ -28,7 +28,7 @@ from .proxy_info import ProxyInfo, ClientProxyInfo -REQUESTS = ['RecaptchaV2EnterpiseRequest', 'RecaptchaV2Request', 'RecaptchaV3ProxylessRequest', 'RecaptchaComplexImageTaskRequest' +REQUESTS = ['RecaptchaV2EnterpiseRequest', 'RecaptchaV2Request', 'RecaptchaV3ProxylessRequest', 'RecaptchaComplexImageTaskRequest', 'RecaptchaV3EnterpriseRequest' 'ImageToTextRequest', 'FuncaptchaRequest', 'FunCaptchaComplexImageTaskRequest', 'HcaptchaRequest', 'HcaptchaComplexImageTaskRequest' @@ -44,5 +44,6 @@ 'MTCaptchaRequest', 'YidunRequest', 'ProsopoTaskRequest', - 'TemuCustomTaskRequest' + 'TemuCustomTaskRequest', + 'AltchaCustomTaskRequest' ] From 15bec266760ca5a884b4d74c8532560a1d4b8bb3 Mon Sep 17 00:00:00 2001 From: "pavel.grinkevich" Date: Wed, 14 Jan 2026 14:40:19 +0000 Subject: [PATCH 3/4] fix double import --- capmonstercloud_client/requests/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/capmonstercloud_client/requests/__init__.py b/capmonstercloud_client/requests/__init__.py index 235c29d..9c224d3 100644 --- a/capmonstercloud_client/requests/__init__.py +++ b/capmonstercloud_client/requests/__init__.py @@ -3,7 +3,6 @@ from .RecaptchaV2EnterpiseRequest import RecaptchaV2EnterpriseRequest from .RecaptchaV3ProxylessRequest import RecaptchaV3ProxylessRequest from .RecaptchaV3EnterpriseRequest import RecaptchaV3EnterpriseRequest -from .RecaptchaV3EnterpriseRequest import RecaptchaV3EnterpriseRequest from .RecaptchaComplexImageTask import RecaptchaComplexImageTaskRequest from .HcaptchaRequest import HcaptchaRequest from .FuncaptchaRequest import FuncaptchaRequest From b90ce204c650e7341de1b4e9ee56bd142296386f Mon Sep 17 00:00:00 2001 From: "pavel.grinkevich" Date: Wed, 14 Jan 2026 14:54:38 +0000 Subject: [PATCH 4/4] version upd --- capmonstercloud_client/version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/capmonstercloud_client/version.txt b/capmonstercloud_client/version.txt index 0fa4ae4..fbcbf73 100644 --- a/capmonstercloud_client/version.txt +++ b/capmonstercloud_client/version.txt @@ -1 +1 @@ -3.3.0 \ No newline at end of file +3.4.0 \ No newline at end of file