Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 deletions capmonstercloud_client/CapMonsterCloudClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
((RecaptchaV2Request,), getRecaptchaV2Timeouts),
((RecaptchaV2EnterpriseRequest,), getRecaptchaV2EnterpriseTimeouts),
((RecaptchaV3ProxylessRequest), getRecaptchaV3Timeouts),
((RecaptchaV3EnterpriseRequest), getRecaptchaV3Timeouts),
((ImageToTextRequest), getImage2TextTimeouts),
((FuncaptchaRequest,), getFuncaptchaTimeouts),
((HcaptchaRequest,), getHcaptchaTimeouts),
Expand All @@ -35,6 +36,7 @@
((YidunRequest), getYidunTimeouts),
((TemuCustomTaskRequest), getTemuTimeouts),
((ProsopoTaskRequest), getProsopoTimeouts),
((AltchaCustomTaskRequest), getAltchaTimeouts),
)


Expand All @@ -44,6 +46,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):
Expand All @@ -56,7 +66,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)
Expand All @@ -69,6 +80,7 @@ async def solve_captcha(self, request: Union[
RecaptchaV2EnterpriseRequest,
RecaptchaV2Request,
RecaptchaV3ProxylessRequest,
RecaptchaV3EnterpriseRequest,
RecaptchaComplexImageTaskRequest,
ImageToTextRequest,
FuncaptchaRequest,
Expand All @@ -87,7 +99,8 @@ async def solve_captcha(self, request: Union[
MTCaptchaRequest,
YidunRequest,
TemuCustomTaskRequest,
ProsopoTaskRequest],
ProsopoTaskRequest,
AltchaCustomTaskRequest],
) -> Dict[str, str]:
'''
Non-blocking method for captcha solving.
Expand All @@ -106,6 +119,7 @@ async def _solve(self, request: Union[
RecaptchaV2EnterpriseRequest,
RecaptchaV2Request,
RecaptchaV3ProxylessRequest,
RecaptchaV3EnterpriseRequest,
RecaptchaComplexImageTaskRequest,
ImageToTextRequest,
FuncaptchaRequest,
Expand All @@ -124,7 +138,8 @@ async def _solve(self, request: Union[
MTCaptchaRequest,
YidunRequest,
TemuCustomTaskRequest,
ProsopoTaskRequest],
ProsopoTaskRequest,
AltchaCustomTaskRequest],
timeouts: GetResultTimeouts,
) -> Dict[str, str]:

Expand Down Expand Up @@ -168,7 +183,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'}
Expand All @@ -184,11 +200,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:
Expand Down
6 changes: 6 additions & 0 deletions capmonstercloud_client/GetResultTimeouts.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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)

Expand Down
5 changes: 4 additions & 1 deletion capmonstercloud_client/clientOptions.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down
2 changes: 2 additions & 0 deletions capmonstercloud_client/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ class TaskNotDefinedError(BaseError):
class ExtraParamsError(BaseError):
pass

class UnsupportedProxyTypeError(BaseError):
pass


class UserAgentNotDefinedError(BaseError):
Expand Down
36 changes: 36 additions & 0 deletions capmonstercloud_client/requests/AltchaCustomTaskRequest.py
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions capmonstercloud_client/requests/CustomTaskRequestBase.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 <see cref="ImageUrls"/> not.
userAgent: Optional[str] = None
domains: Optional[List[str]] = None
3 changes: 3 additions & 0 deletions capmonstercloud_client/requests/FuncaptchaRequest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}
Expand All @@ -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
20 changes: 13 additions & 7 deletions capmonstercloud_client/requests/ImpervaCustomTaskRequest.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}
Expand All @@ -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
22 changes: 22 additions & 0 deletions capmonstercloud_client/requests/RecaptchaV3EnterpriseRequest.py
Original file line number Diff line number Diff line change
@@ -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
12 changes: 6 additions & 6 deletions capmonstercloud_client/requests/TurnstileRequest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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':
Expand All @@ -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.')
Expand Down
9 changes: 6 additions & 3 deletions capmonstercloud_client/requests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from .RecaptchaV2Request import RecaptchaV2Request
from .RecaptchaV2EnterpiseRequest import RecaptchaV2EnterpriseRequest
from .RecaptchaV3ProxylessRequest import RecaptchaV3ProxylessRequest
from .RecaptchaV3EnterpriseRequest import RecaptchaV3EnterpriseRequest
from .RecaptchaComplexImageTask import RecaptchaComplexImageTaskRequest
from .HcaptchaRequest import HcaptchaRequest
from .FuncaptchaRequest import FuncaptchaRequest
Expand All @@ -22,10 +23,11 @@
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'
REQUESTS = ['RecaptchaV2EnterpiseRequest', 'RecaptchaV2Request', 'RecaptchaV3ProxylessRequest', 'RecaptchaComplexImageTaskRequest', 'RecaptchaV3EnterpriseRequest'
'ImageToTextRequest',
'FuncaptchaRequest', 'FunCaptchaComplexImageTaskRequest',
'HcaptchaRequest', 'HcaptchaComplexImageTaskRequest'
Expand All @@ -41,5 +43,6 @@
'MTCaptchaRequest',
'YidunRequest',
'ProsopoTaskRequest',
'TemuCustomTaskRequest'
'TemuCustomTaskRequest',
'AltchaCustomTaskRequest'
]
20 changes: 20 additions & 0 deletions capmonstercloud_client/requests/proxy_info.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from pydantic import BaseModel, validator
from .enums import ProxyTypes
from typing import Optional

class ProxyInfo(BaseModel):
proxyType: str
Expand All @@ -19,3 +20,22 @@ def validate_port(cls, value):
if not isinstance(value, int):
raise TypeError(f'Expect that port value will be <int> 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 <int> type, got {type(value)}')
return value
2 changes: 1 addition & 1 deletion capmonstercloud_client/version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.3.0
3.4.0
Loading