diff --git a/channels/seriehd.py b/channels/seriehd.py index 15b3b382..c7a6139b 100644 --- a/channels/seriehd.py +++ b/channels/seriehd.py @@ -10,9 +10,6 @@ host = support.config.get_channel_url() headers = [['Referer', host]] - - - @support.menu def mainlist(item): diff --git a/lib/cloudscraper/__init__.py b/lib/cloudscraper/__init__.py index d10d2f73..ce20c8cb 100644 --- a/lib/cloudscraper/__init__.py +++ b/lib/cloudscraper/__init__.py @@ -46,18 +46,19 @@ from .exceptions import ( CloudflareLoopProtection, CloudflareCode1020, CloudflareIUAMError, + CloudflareSolveError, CloudflareChallengeError, - CloudflareReCaptchaError, - CloudflareReCaptchaProvider + CloudflareCaptchaError, + CloudflareCaptchaProvider ) from .interpreters import JavaScriptInterpreter -from .reCaptcha import reCaptcha +from .captcha import Captcha from .user_agent import User_Agent # ------------------------------------------------------------------------------- # -__version__ = '1.2.40' +__version__ = '1.2.46' # ------------------------------------------------------------------------------- # @@ -70,12 +71,23 @@ class CipherSuiteAdapter(HTTPAdapter): 'config', '_pool_connections', '_pool_maxsize', - '_pool_block' + '_pool_block', + 'source_address' ] def __init__(self, *args, **kwargs): self.ssl_context = kwargs.pop('ssl_context', None) self.cipherSuite = kwargs.pop('cipherSuite', None) + self.source_address = kwargs.pop('source_address', None) + + if self.source_address: + if isinstance(self.source_address, str): + self.source_address = (self.source_address, 0) + + if not isinstance(self.source_address, tuple): + raise TypeError( + "source_address must be IP address string or (ip, port) tuple" + ) if not self.ssl_context: self.ssl_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH) @@ -89,12 +101,14 @@ class CipherSuiteAdapter(HTTPAdapter): def init_poolmanager(self, *args, **kwargs): kwargs['ssl_context'] = self.ssl_context + kwargs['source_address'] = self.source_address return super(CipherSuiteAdapter, self).init_poolmanager(*args, **kwargs) # ------------------------------------------------------------------------------- # def proxy_manager_for(self, *args, **kwargs): kwargs['ssl_context'] = self.ssl_context + kwargs['source_address'] = self.source_address return super(CipherSuiteAdapter, self).proxy_manager_for(*args, **kwargs) # ------------------------------------------------------------------------------- # @@ -108,9 +122,11 @@ class CloudScraper(Session): self.cipherSuite = kwargs.pop('cipherSuite', None) self.ssl_context = kwargs.pop('ssl_context', None) self.interpreter = kwargs.pop('interpreter', 'native') - self.recaptcha = kwargs.pop('recaptcha', {}) + self.captcha = kwargs.pop('captcha', {}) self.requestPreHook = kwargs.pop('requestPreHook', None) self.requestPostHook = kwargs.pop('requestPostHook', None) + self.source_address = kwargs.pop('source_address', None) + self.doubleDown = kwargs.pop('doubleDown', True) self.allow_brotli = kwargs.pop( 'allow_brotli', @@ -143,7 +159,8 @@ class CloudScraper(Session): 'https://', CipherSuiteAdapter( cipherSuite=self.cipherSuite, - ssl_context=self.ssl_context + ssl_context=self.ssl_context, + source_address=self.source_address ) ) @@ -157,6 +174,13 @@ class CloudScraper(Session): def __getstate__(self): return self.__dict__ + # ------------------------------------------------------------------------------- # + # Allow replacing actual web request call via subclassing + # ------------------------------------------------------------------------------- # + + def perform_request(self, method, url, *args, **kwargs): + return super(CloudScraper, self).request(method, url, *args, **kwargs) + # ------------------------------------------------------------------------------- # # Raise an Exception with no stacktrace and reset depth counter. # ------------------------------------------------------------------------------- # @@ -236,7 +260,7 @@ class CloudScraper(Session): # ------------------------------------------------------------------------------- # response = self.decodeBrotli( - super(CloudScraper, self).request(method, url, *args, **kwargs) + self.perform_request(method, url, *args, **kwargs) ) # ------------------------------------------------------------------------------- # @@ -314,6 +338,7 @@ class CloudScraper(Session): resp.text, re.M | re.S ) + and re.search(r'window._cf_chl_enter\(', resp.text, re.M | re.S) ) except AttributeError: pass @@ -321,17 +346,38 @@ class CloudScraper(Session): return False # ------------------------------------------------------------------------------- # - # check if the response contains a valid Cloudflare reCaptcha challenge + # check if the response contains a v2 hCaptcha Cloudflare challenge # ------------------------------------------------------------------------------- # @staticmethod - def is_reCaptcha_Challenge(resp): + def is_New_Captcha_Challenge(resp): + try: + return ( + CloudScraper.is_Captcha_Challenge(resp) + and re.search( + r'cpo.src\s*=\s*"/cdn-cgi/challenge-platform/orchestrate/captcha/v1"', + resp.text, + re.M | re.S + ) + and re.search(r'window._cf_chl_enter\(', resp.text, re.M | re.S) + ) + except AttributeError: + pass + + return False + + # ------------------------------------------------------------------------------- # + # check if the response contains a Cloudflare hCaptcha challenge + # ------------------------------------------------------------------------------- # + + @staticmethod + def is_Captcha_Challenge(resp): try: return ( resp.headers.get('Server', '').startswith('cloudflare') and resp.status_code == 403 and re.search( - r'action="/.*?__cf_chl_captcha_tk__=\S+".*?data\-sitekey=.*?', + r'action="/\S+__cf_chl_captcha_tk__=\S+', resp.text, re.M | re.DOTALL ) @@ -363,7 +409,7 @@ class CloudScraper(Session): return False # ------------------------------------------------------------------------------- # - # Wrapper for is_reCaptcha_Challenge, is_IUAM_Challenge, is_Firewall_Blocked + # Wrapper for is_Captcha_Challenge, is_IUAM_Challenge, is_Firewall_Blocked # ------------------------------------------------------------------------------- # def is_Challenge_Request(self, resp): @@ -373,15 +419,21 @@ class CloudScraper(Session): 'Cloudflare has blocked this request (Code 1020 Detected).' ) + if self.is_New_Captcha_Challenge(resp): + self.simpleException( + CloudflareChallengeError, + 'Detected a Cloudflare version 2 challenge, This feature is not available in the opensource (free) version.' + ) + if self.is_New_IUAM_Challenge(resp): self.simpleException( CloudflareChallengeError, - 'Detected the new Cloudflare challenge.' + 'Detected a Cloudflare version 2 Captcha challenge, This feature is not available in the opensource (free) version.' ) - if self.is_reCaptcha_Challenge(resp) or self.is_IUAM_Challenge(resp): + if self.is_Captcha_Challenge(resp) or self.is_IUAM_Challenge(resp): if self.debug: - print('Detected Challenge.') + print('Detected a Cloudflare version 1 challenge.') return True return False @@ -442,10 +494,10 @@ class CloudScraper(Session): } # ------------------------------------------------------------------------------- # - # Try to solve the reCaptcha challenge via 3rd party. + # Try to solve the Captcha challenge via 3rd party. # ------------------------------------------------------------------------------- # - def reCaptcha_Challenge_Response(self, provider, provider_params, body, url): + def captcha_Challenge_Response(self, provider, provider_params, body, url): try: formPayload = re.search( r'