from __future__ import absolute_import import requests try: from urlparse import urlparse except ImportError: from urllib.parse import urlparse from ..exceptions import ( CaptchaServiceUnavailable, CaptchaAPIError, CaptchaTimeout, CaptchaParameter, CaptchaBadJobID, CaptchaReportError ) try: import polling2 except ImportError: raise ImportError("Please install the python module 'polling2' via pip") from . import Captcha class captchaSolver(Captcha): def __init__(self): super(captchaSolver, self).__init__('2captcha') self.host = 'https://2captcha.com' self.session = requests.Session() # ------------------------------------------------------------------------------- # @staticmethod def checkErrorStatus(response, request_type): if response.status_code in [500, 502]: raise CaptchaServiceUnavailable(f'2Captcha: Server Side Error {response.status_code}') errors = { 'in.php': { "ERROR_WRONG_USER_KEY": "You've provided api_key parameter value is in incorrect format, it should contain 32 symbols.", "ERROR_KEY_DOES_NOT_EXIST": "The api_key you've provided does not exists.", "ERROR_ZERO_BALANCE": "You don't have sufficient funds on your account.", "ERROR_PAGEURL": "pageurl parameter is missing in your request.", "ERROR_NO_SLOT_AVAILABLE": "No Slots Available.\nYou can receive this error in two cases:\n" "1. If you solve ReCaptcha: the queue of your captchas that are not distributed to workers is too long. " "Queue limit changes dynamically and depends on total amount of captchas awaiting solution and usually it's between 50 and 100 captchas.\n" "2. If you solve Normal Captcha: your maximum rate for normal captchas is lower than current rate on the server." "You can change your maximum rate in your account's settings.", "ERROR_IP_NOT_ALLOWED": "The request is sent from the IP that is not on the list of your allowed IPs.", "IP_BANNED": "Your IP address is banned due to many frequent attempts to access the server using wrong authorization keys.", "ERROR_BAD_TOKEN_OR_PAGEURL": "You can get this error code when sending ReCaptcha V2. " "That happens if your request contains invalid pair of googlekey and pageurl. " "The common reason for that is that ReCaptcha is loaded inside an iframe hosted on another domain/subdomain.", "ERROR_GOOGLEKEY": "You can get this error code when sending ReCaptcha V2. " "That means that sitekey value provided in your request is incorrect: it's blank or malformed.", "MAX_USER_TURN": "You made more than 60 requests within 3 seconds.Your account is banned for 10 seconds. Ban will be lifted automatically." }, 'res.php': { "ERROR_CAPTCHA_UNSOLVABLE": "We are unable to solve your captcha - three of our workers were unable solve it " "or we didn't get an answer within 90 seconds (300 seconds for ReCaptcha V2). " "We will not charge you for that request.", "ERROR_WRONG_USER_KEY": "You've provided api_key parameter value in incorrect format, it should contain 32 symbols.", "ERROR_KEY_DOES_NOT_EXIST": "The api_key you've provided does not exists.", "ERROR_WRONG_ID_FORMAT": "You've provided captcha ID in wrong format. The ID can contain numbers only.", "ERROR_WRONG_CAPTCHA_ID": "You've provided incorrect captcha ID.", "ERROR_BAD_DUPLICATES": "Error is returned when 100% accuracy feature is enabled. " "The error means that max numbers of tries is reached but min number of matches not found.", "REPORT_NOT_RECORDED": "Error is returned to your complain request if you already complained lots of correctly solved captchas.", "ERROR_IP_ADDRES": "You can receive this error code when registering a pingback (callback) IP or domain." "That happes if your request is coming from an IP address that doesn't match the IP address of your pingback IP or domain.", "ERROR_TOKEN_EXPIRED": "You can receive this error code when sending GeeTest. That error means that challenge value you provided is expired.", "ERROR_EMPTY_ACTION": "Action parameter is missing or no value is provided for action parameter." } } rPayload = response.json() if rPayload.get('status') == 0 and rPayload.get('request') in errors.get(request_type): raise CaptchaAPIError( f"{rPayload['request']} {errors.get(request_type).get(rPayload['request'])}" ) # ------------------------------------------------------------------------------- # def reportJob(self, jobID): if not jobID: raise CaptchaBadJobID( "2Captcha: Error bad job id to request Captcha." ) def _checkRequest(response): self.checkErrorStatus(response, 'res.php') if response.ok and response.json().get('status') == 1: return response return None response = polling2.poll( lambda: self.session.get( f'{self.host}/res.php', params={ 'key': self.api_key, 'action': 'reportbad', 'id': jobID, 'json': '1' }, timeout=30 ), check_success=_checkRequest, step=5, timeout=180 ) if response: return True else: raise CaptchaReportError( "2Captcha: Error - Failed to report bad Captcha solve." ) # ------------------------------------------------------------------------------- # def requestJob(self, jobID): if not jobID: raise CaptchaBadJobID("2Captcha: Error bad job id to request Captcha.") def _checkRequest(response): self.checkErrorStatus(response, 'res.php') if response.ok and response.json().get('status') == 1: return response return None response = polling2.poll( lambda: self.session.get( f'{self.host}/res.php', params={ 'key': self.api_key, 'action': 'get', 'id': jobID, 'json': '1' }, timeout=30 ), check_success=_checkRequest, step=5, timeout=180 ) if response: return response.json().get('request') else: raise CaptchaTimeout( "2Captcha: Error failed to solve Captcha." ) # ------------------------------------------------------------------------------- # def requestSolve(self, captchaType, url, siteKey): def _checkRequest(response): self.checkErrorStatus(response, 'in.php') if response.ok and response.json().get("status") == 1 and response.json().get('request'): return response return None data = { 'key': self.api_key, 'pageurl': url, 'json': 1, 'soft_id': 2905 } data.update( { 'method': 'userrcaptcha', 'googlekey': siteKey } if captchaType == 'reCaptcha' else { 'method': 'hcaptcha', 'sitekey': siteKey } ) if self.proxy: data.update( { 'proxy': self.proxy, 'proxytype': self.proxyType } ) response = polling2.poll( lambda: self.session.post( f'{self.host}/in.php', data=data, allow_redirects=False, timeout=30 ), check_success=_checkRequest, step=5, timeout=180 ) if response: return response.json().get('request') else: raise CaptchaBadJobID( '2Captcha: Error no job id was returned.' ) # ------------------------------------------------------------------------------- # def getCaptchaAnswer(self, captchaType, url, siteKey, captchaParams): jobID = None if not captchaParams.get('api_key'): raise CaptchaParameter( "2Captcha: Missing api_key parameter." ) self.api_key = captchaParams.get('api_key') if captchaParams.get('proxy') and not captchaParams.get('no_proxy'): hostParsed = urlparse(captchaParams.get('proxy', {}).get('https')) if not hostParsed.scheme: raise CaptchaParameter('Cannot parse proxy correctly, bad scheme') if not hostParsed.netloc: raise CaptchaParameter('Cannot parse proxy correctly, bad netloc') self.proxyType = hostParsed.scheme self.proxy = hostParsed.netloc else: self.proxy = None try: jobID = self.requestSolve(captchaType, url, siteKey) return self.requestJob(jobID) except polling2.TimeoutException: try: if jobID: self.reportJob(jobID) except polling2.TimeoutException: raise CaptchaTimeout( f"2Captcha: Captcha solve took to long and also failed reporting the job the job id {jobID}." ) raise CaptchaTimeout( f"2Captcha: Captcha solve took to long to execute job id {jobID}, aborting." ) # ------------------------------------------------------------------------------- # captchaSolver()