From 1c20e86fbe164cc2ce459acb15052b187061805a Mon Sep 17 00:00:00 2001 From: Felipe Ucelli <78266261+felipeucelli@users.noreply.github.com> Date: Tue, 9 Jul 2024 22:40:11 -0400 Subject: [PATCH] Fixed "get_throttling_function_name: could not find match for multiple" and other improvements --- .gitignore | 2 + pytubefix/__main__.py | 25 +- pytubefix/cipher.py | 11 +- pytubefix/contrib/channel.py | 12 +- pytubefix/contrib/playlist.py | 12 +- pytubefix/innertube.py | 511 +++++++++++++++++++--------------- pytubefix/jsinterp.py | 18 ++ 7 files changed, 342 insertions(+), 249 deletions(-) diff --git a/.gitignore b/.gitignore index 2adb00e..0f571a0 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,8 @@ coverage.xml .hypothesis/ .pytest_cache/ *.mp4 +*.mp3 +*.webm # Performance profiling prof/ diff --git a/pytubefix/__main__.py b/pytubefix/__main__.py index 11a1ce3..53f684c 100644 --- a/pytubefix/__main__.py +++ b/pytubefix/__main__.py @@ -204,18 +204,6 @@ def streaming_data(self): video_id = self.vid_info['videoDetails']['videoId'] if video_id in invalid_id_list: - logger.warning( - f'The {self.client} client did not get a valid response, trying to use the WEB client.' - ) - logger.warning( - f'Video ID: {video_id}' - ) - logger.warning( - 'Please open an issue at ' - 'https://github.com/JuanBindez/pytubefix/issues ' - 'and provide this log output.' - ) - self.try_another_client() else: self.try_another_client() @@ -338,6 +326,19 @@ def try_another_client(self): but it is no longer effective. """ + + logger.warning( + f'The {self.client} client did not get a valid response, trying to use the WEB client.' + ) + logger.warning( + f'Video ID: {self.video_id}' + ) + logger.warning( + 'Please open an issue at ' + 'https://github.com/JuanBindez/pytubefix/issues ' + 'and provide this log output.' + ) + innertube = InnerTube( client='WEB', use_oauth=self.use_oauth, diff --git a/pytubefix/cipher.py b/pytubefix/cipher.py index e79115b..d3384c9 100644 --- a/pytubefix/cipher.py +++ b/pytubefix/cipher.py @@ -103,8 +103,15 @@ def get_throttling_function_name(js: str) -> str: # a.C && (b = a.get("n")) && (b = Bpa[0](b), a.set("n", b), # Bpa.length || iha("")) }}; # In the above case, `iha` is the relevant function name - r'a\.[a-zA-Z]\s*&&\s*\([a-z]\s*=\s*a\.get\("n"\)\)\s*&&\s*' - r'\([a-z]\s*=\s*([a-zA-Z0-9$]+)(\[\d+\])?\([a-z]\)', + # r'a\.[a-zA-Z]\s*&&\s*\([a-z]\s*=\s*a\.get\("n"\)\)\s*&&\s*' + # r'\([a-z]\s*=\s*([a-zA-Z0-9$]+)(\[\d+\])?\([a-z]\)', + + # New pattern added on July 9, 2024 + # https://github.com/yt-dlp/yt-dlp/pull/10390/files + # In this example we can find the name of the function at index "0" of "IRa" + # a.D && (b = String.fromCharCode(110), c = a.get(b)) && (c = IRa[0](c), a.set(b,c), IRa.length || Ima("")) + r'(?:\.get\(\"n\"\)\)&&\(b=|b=String\.fromCharCode\(\d+\),c=a\.get\(b\)\)&&\(c=)([a-zA-Z0-9$]+)(?:\[(' + r'\d+)\])?\([a-zA-Z0-9]\)' ] logger.debug('Finding throttling function name') for pattern in function_patterns: diff --git a/pytubefix/contrib/channel.py b/pytubefix/contrib/channel.py index de2bb51..db257a1 100644 --- a/pytubefix/contrib/channel.py +++ b/pytubefix/contrib/channel.py @@ -186,13 +186,12 @@ def _build_continuation_url(self, continuation: str) -> Tuple[str, dict, dict]: return ( ( # was changed to this format (and post requests) - # between 2022.11.06 and 2022.11.20 - "https://www.youtube.com/youtubei/v1/browse?key=" - f"{self.yt_api_key}" + # around the day 2024.04.16 + "https://www.youtube.com/youtubei/v1/browse?prettyPrint=false" ), { "X-YouTube-Client-Name": "1", - "X-YouTube-Client-Version": "2.20200720.00.02", + "X-YouTube-Client-Version": "2.20240530.02.00", }, # extra data required for post request { @@ -201,7 +200,10 @@ def _build_continuation_url(self, continuation: str) -> Tuple[str, dict, dict]: "client": { "clientName": "WEB", "visitorData": self._visitor_data, - "clientVersion": "2.20200720.00.02" + "osName": "Windows", + "osVersion": "10.0", + "clientVersion": "2.20240530.02.00", + "platform": "DESKTOP" } } } diff --git a/pytubefix/contrib/playlist.py b/pytubefix/contrib/playlist.py index 7f8a68d..04fc822 100644 --- a/pytubefix/contrib/playlist.py +++ b/pytubefix/contrib/playlist.py @@ -175,13 +175,12 @@ def _build_continuation_url(self, continuation: str) -> Tuple[str, dict, dict]: return ( ( # was changed to this format (and post requests) - # between 2021.03.02 and 2021.03.03 - "https://www.youtube.com/youtubei/v1/browse?key=" - f"{self.yt_api_key}" + # around the day 2024.04.16 + "https://www.youtube.com/youtubei/v1/browse?prettyPrint=false" ), { "X-YouTube-Client-Name": "1", - "X-YouTube-Client-Version": "2.20200720.00.02", + "X-YouTube-Client-Version": "2.20240530.02.00", }, # extra data required for post request { @@ -189,7 +188,10 @@ def _build_continuation_url(self, continuation: str) -> Tuple[str, dict, dict]: "context": { "client": { "clientName": "WEB", - "clientVersion": "2.20200720.00.02" + "osName": "Windows", + "osVersion": "10.0", + "clientVersion": "2.20240530.02.00", + "platform": "DESKTOP" } } } diff --git a/pytubefix/innertube.py b/pytubefix/innertube.py index 62e6ce0..4fe6cf7 100644 --- a/pytubefix/innertube.py +++ b/pytubefix/innertube.py @@ -19,6 +19,7 @@ _client_secret = 'SboVhoG9s0rNafixCSGGKXAT' # Extracted API keys -- unclear what these are linked to. +# API keys are not required, see: https://github.com/TeamNewPipe/NewPipeExtractor/pull/1168 _api_keys = [ 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', 'AIzaSyCtkvNIR1HCEwzsqK6JuE6KqpyjusIRI30', @@ -29,247 +30,312 @@ ] _default_clients = { - 'WEB': { - 'innertube_context': { - 'context': { - 'client': { - 'clientName': 'WEB', - 'clientVersion': '2.20200720.00.02' - } - } - }, - 'header': { - 'User-Agent': 'Mozilla/5.0' - }, - 'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', - 'require_js_player': True - }, - 'ANDROID': { - 'innertube_context': { - 'context': { - 'client': { - 'clientName': 'ANDROID', - 'clientVersion': '19.08.35', - 'androidSdkVersion': 30 + 'WEB': { + 'innertube_context': { + 'context': { + 'client': { + 'clientName': 'WEB', + 'osName': 'Windows', + 'osVersion': '10.0', + 'clientVersion': '2.20240709.01.00', + 'platform': 'DESKTOP' + } } }, - "params": "CgIIAdgDAQ%3D%3D" - }, - 'header': { - 'User-Agent': 'com.google.android.youtube/', + 'header': { + 'User-Agent': 'Mozilla/5.0', + 'X-Youtube-Client-Name': '1', + 'X-Youtube-Client-Version': '2.20240709.01.00' + }, + 'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', + 'require_js_player': 'True' }, - 'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', - 'require_js_player': False - }, - 'IOS': { - 'innertube_context': { - 'context': { - 'client': { - 'clientName': 'IOS', - 'clientVersion': '19.08.35', - 'deviceModel': 'iPhone14,3' + + 'WEB_EMBED': { + 'innertube_context': { + 'context': { + 'client': { + 'clientName': 'WEB_EMBEDDED_PLAYER', + 'osName': 'Windows', + 'osVersion': '10.0', + 'clientVersion': '2.20240530.02.00', + 'clientScreen': 'EMBED' + } } - } - }, - 'header': { - 'User-Agent': 'com.google.ios.youtube/' + }, + 'header': { + 'User-Agent': 'Mozilla/5.0', + 'X-Youtube-Client-Name': '56', + 'X-Youtube-Client-Version': '2.20240530.02.00' + }, + 'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', + 'require_js_player': 'True' }, - 'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', - 'require_js_player': False - }, - - 'WEB_EMBED': { - 'innertube_context': { - 'context': { - 'client': { - 'clientName': 'WEB_EMBEDDED_PLAYER', - 'clientVersion': '2.20210721.00.00', - 'clientScreen': 'EMBED' + + 'WEB_MUSIC': { + 'innertube_context': { + 'context': { + 'client': { + 'clientName': 'WEB_REMIX', + 'clientVersion': '1.20240403.01.00' + } } - } - }, - 'header': { - 'User-Agent': 'Mozilla/5.0' + }, + 'header': { + 'User-Agent': 'Mozilla/5.0', + 'X-Youtube-Client-Name': '67', + 'X-Youtube-Client-Version': '1.20240403.01.00' + }, + 'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', + 'require_js_player': 'True' }, - 'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', - 'require_js_player': True - }, - 'ANDROID_EMBED': { - 'innertube_context': { - 'context': { - 'client': { - 'clientName': 'ANDROID_EMBEDDED_PLAYER', - 'clientVersion': '19.08.35', - 'clientScreen': 'EMBED', - 'androidSdkVersion': 30, + + 'WEB_CREATOR': { + 'innertube_context': { + 'context': { + 'client': { + 'clientName': 'WEB_CREATOR', + 'clientVersion': '1.20220726.00.00' + } } - } - }, - 'header': { - 'User-Agent': 'com.google.android.youtube/' + }, + 'header': { + 'User-Agent': 'Mozilla/5.0', + 'X-Youtube-Client-Name': '62', + 'X-Youtube-Client-Version': '1.20220726.00.00' + }, + 'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', + 'require_js_player': 'True' }, - 'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', - 'require_js_player': False - }, - 'IOS_EMBED': { - 'innertube_context': { - 'context': { - 'client': { - 'clientName': 'IOS_MESSAGES_EXTENSION', - 'clientVersion': '19.08.35', - 'deviceModel': 'iPhone14,3' + + 'MWEB': { + 'innertube_context': { + 'context': { + 'client': { + 'clientName': 'MWEB', + 'clientVersion': '2.20240304.08.00' + } } - } + }, + 'header': { + 'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36', + 'X-Youtube-Client-Name': '2', + 'X-Youtube-Client-Version': '2.20240304.08.00' + }, + 'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', + 'require_js_player': 'True' }, - 'header': { - 'User-Agent': 'com.google.ios.youtube/' + + 'ANDROID': { + 'innertube_context': { + 'context': { + 'client': { + 'clientName': 'ANDROID', + 'clientVersion': '19.21.37', + 'platform': 'MOBILE', + 'osName': 'Android', + 'osVersion': '14', + 'androidSdkVersion': '34' + } + }, + 'params': 'CgIIAdgDAQ%3D%3D' + }, + 'header': { + 'User-Agent': 'com.google.android.youtube/', + 'X-Youtube-Client-Name': '3', + 'X-Youtube-Client-Version': '19.21.37' + }, + 'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', + 'require_js_player': 'False' }, - 'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', - 'require_js_player': False - }, - - 'WEB_MUSIC': { - 'innertube_context': { - 'context': { - 'client': { - 'clientName': 'WEB_REMIX', - 'clientVersion': '1.20220727.01.00', + + 'ANDROID_EMBED': { + 'innertube_context': { + 'context': { + 'client': { + 'clientName': 'ANDROID_EMBEDDED_PLAYER', + 'clientVersion': '19.13.36', + 'clientScreen': 'EMBED', + 'androidSdkVersion': '30' + } } - } - }, - 'header': { - 'User-Agent': 'Mozilla/5.0' + }, + 'header': { + 'User-Agent': 'com.google.android.youtube/', + 'X-Youtube-Client-Name': '55', + 'X-Youtube-Client-Version': '19.13.36' + }, + 'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', + 'require_js_player': 'False' }, - 'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', - 'require_js_player': True - }, - 'ANDROID_MUSIC': { - 'innertube_context': { - 'context': { - 'client': { - 'clientName': 'ANDROID_MUSIC', - 'clientVersion': '6.40.52', - 'androidSdkVersion': 30 + + 'ANDROID_MUSIC': { + 'innertube_context': { + 'context': { + 'client': { + 'clientName': 'ANDROID_MUSIC', + 'clientVersion': '6.40.52', + 'androidSdkVersion': '30' + } } - } - }, - 'header': { - 'User-Agent': 'com.google.android.apps.youtube.music/' + }, + 'header': { + 'User-Agent': 'com.google.android.apps.youtube.music/', + 'X-Youtube-Client-Name': '21', + 'X-Youtube-Client-Version': '6.40.52' + }, + 'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', + 'require_js_player': 'False' }, - 'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', - 'require_js_player': False - }, - 'IOS_MUSIC': { - 'innertube_context': { - 'context': { - 'client': { - 'clientName': 'IOS_MUSIC', - 'clientVersion': '6.41', - 'deviceModel': 'iPhone14,3' + + 'ANDROID_CREATOR': { + 'innertube_context': { + 'context': { + 'client': { + 'clientName': 'ANDROID_CREATOR', + 'clientVersion': '22.30.100', + 'androidSdkVersion': '30' + } } - } - }, - 'header': { - 'User-Agent': 'com.google.ios.youtubemusic/' + }, + 'header': { + 'User-Agent': 'com.google.android.apps.youtube.creator/', + 'X-Youtube-Client-Name': '14', + 'X-Youtube-Client-Version': '22.30.100' + }, + 'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', + 'require_js_player': 'False' }, - 'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', - 'require_js_player': False - }, - - 'WEB_CREATOR': { - 'innertube_context': { - 'context': { - 'client': { - 'clientName': 'WEB_CREATOR', - 'clientVersion': '1.20220726.00.00', + + 'ANDROID_TESTSUITE': { + 'innertube_context': { + 'context': { + 'client': { + 'clientName': 'ANDROID_TESTSUITE', + 'clientVersion': '1.9', + 'platform': 'MOBILE', + 'osName': 'Android', + 'osVersion': '14', + 'androidSdkVersion': '34' + } } - } - }, - 'header': { - 'User-Agent': 'Mozilla/5.0' + }, + 'header': { + 'User-Agent': 'com.google.android.youtube/', + 'X-Youtube-Client-Name': '30', + 'X-Youtube-Client-Version': '1.9' + }, + 'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', + 'require_js_player': 'False' }, - 'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', - 'require_js_player': True - }, - 'ANDROID_CREATOR': { - 'innertube_context': { - 'context': { - 'client': { - 'clientName': 'ANDROID_CREATOR', - 'clientVersion': '22.30.100', - 'androidSdkVersion': 30, + + 'IOS': { + 'innertube_context': { + 'context': { + 'client': { + 'clientName': 'IOS', + 'clientVersion': '19.14.3', + 'deviceMake': 'Apple', + 'platform': 'MOBILE', + 'osName': 'iOS', + 'osVersion': '17.4.1.21E237', + 'deviceModel': 'iPhone15,5' + } } - } - }, - 'header': { - 'User-Agent': 'com.google.android.apps.youtube.creator/', + }, + 'header': { + 'User-Agent': 'com.google.ios.youtube/', + 'X-Youtube-Client-Name': '5', + 'X-Youtube-Client-Version': '19.14.3' + }, + 'api_key': 'AIzaSyB-63vPrdThhKuerbB2N_l7Kwwcxj6yUAc', + 'require_js_player': 'False' }, - 'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', - 'require_js_player': False - }, - 'IOS_CREATOR': { - 'innertube_context': { - 'context': { - 'client': { - 'clientName': 'IOS_CREATOR', - 'clientVersion': '22.33.101', - 'deviceModel': 'iPhone14,3', + + 'IOS_EMBED': { + 'innertube_context': { + 'context': { + 'client': { + 'clientName': 'IOS_MESSAGES_EXTENSION', + 'clientVersion': '19.16.3', + 'deviceMake': 'Apple', + 'platform': 'MOBILE', + 'osName': 'iOS', + 'osVersion': '17.4.1.21E237', + 'deviceModel': 'iPhone15,5' + } } - } - }, - 'header': { - 'User-Agent': 'com.google.ios.ytcreator/' + }, + 'header': { + 'User-Agent': 'com.google.ios.youtube/', + 'X-Youtube-Client-Name': '66', + 'X-Youtube-Client-Version': '19.16.3' + }, + 'api_key': 'AIzaSyB-63vPrdThhKuerbB2N_l7Kwwcxj6yUAc', + 'require_js_player': 'False' }, - 'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', - 'require_js_player': False - }, - 'ANDROID_TESTSUITE': { - 'innertube_context': { - 'context': { - 'client': { - 'clientName': 'ANDROID_TESTSUITE', - 'clientVersion': '1.9', - 'androidSdkVersion': 30 + + 'IOS_MUSIC': { + 'innertube_context': { + 'context': { + 'client': { + 'clientName': 'IOS_MUSIC', + 'clientVersion': '6.42', + 'deviceMake': 'Apple', + 'platform': 'MOBILE', + 'osName': 'iOS', + 'osVersion': '17.4.1.21E237', + 'deviceModel': 'iPhone14,3' + } } - } - }, - 'header': { - 'User-Agent': 'com.google.android.youtube/', + }, + 'header': { + 'User-Agent': 'com.google.ios.youtubemusic/', + 'X-Youtube-Client-Name': '26', + 'X-Youtube-Client-Version': '6.42' + }, + 'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', + 'require_js_player': 'False' }, - 'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', - 'require_js_player': False - }, - 'MWEB': { - 'innertube_context': { - 'context': { - 'client': { - 'clientName': 'MWEB', - 'clientVersion': '2.20220801.00.00', + + 'IOS_CREATOR': { + 'innertube_context': { + 'context': { + 'client': { + 'clientName': 'IOS_CREATOR', + 'clientVersion': '22.33.101', + 'deviceModel': 'iPhone14,3' + } } - } - }, - 'header': { - 'User-Agent': 'Mozilla/5.0' + }, + 'header': { + 'User-Agent': 'com.google.ios.ytcreator/', + 'X-Youtube-Client-Name': '15', + 'X-Youtube-Client-Version': '22.33.101' + }, + 'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', + 'require_js_player': 'False' }, - 'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', - 'require_js_player': True - }, - - 'TV_EMBED': { - 'innertube_context': { - 'context': { - 'client': { - 'clientName': 'TVHTML5_SIMPLY_EMBEDDED_PLAYER', - 'clientVersion': '2.0', + + 'TV_EMBED': { + 'innertube_context': { + 'context': { + 'client': { + 'clientName': 'TVHTML5_SIMPLY_EMBEDDED_PLAYER', + 'clientVersion': '2.0', + 'clientScreen': 'EMBED', + 'platform': 'TV' + } } - } - }, - 'header': { - 'User-Agent': 'Mozilla/5.0' - }, - 'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', - 'require_js_player': True - }, + }, + 'header': { + 'User-Agent': 'Mozilla/5.0', + 'X-Youtube-Client-Name': '85', + 'X-Youtube-Client-Version': '2.0' + }, + 'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', + 'require_js_player': 'True' + } } _token_timeout = 1800 _cache_dir = pathlib.Path(__file__).parent.resolve() / '__cache__' @@ -278,12 +344,12 @@ class InnerTube: """Object for interacting with the innertube API.""" - def __init__(self, client='ANDROID', use_oauth=False, allow_cache=True): + def __init__(self, client='ANDROID_TESTSUITE', use_oauth=False, allow_cache=True): """Initialize an InnerTube object. :param str client: Client to use for the object. - The default is ANDROID because there is no need to decrypt the + The default is ANDROID_TESTSUITE because there is no need to decrypt the signature cipher and throttling parameter. :param bool use_oauth: Whether or not to authenticate to YouTube. @@ -417,9 +483,7 @@ def base_data(self) -> dict: def base_params(self): """Return the base query parameters to transmit to the innertube API.""" return { - 'key': self.api_key, - 'contentCheckOk': True, - 'racyCheckOk': True + 'prettyPrint': "false" } def _call_api(self, endpoint, query, data): @@ -497,10 +561,9 @@ def player(self, video_id): Raw player info results. """ endpoint = f'{self.base_url}/player' - query = { - 'videoId': video_id, - } - query.update(self.base_params) + query = self.base_params + + self.base_data.update({'videoId': video_id, 'contentCheckOk': "true"}) return self._call_api(endpoint, query, self.base_data) def search(self, search_query, continuation=None): @@ -513,11 +576,9 @@ def search(self, search_query, continuation=None): Raw search query results. """ endpoint = f'{self.base_url}/search' - query = { - 'query': search_query - } - query.update(self.base_params) + query = self.base_params data = {} + self.base_data.update({'query': search_query}) if continuation: data['continuation'] = continuation data.update(self.base_data) diff --git a/pytubefix/jsinterp.py b/pytubefix/jsinterp.py index d53e0e5..77ea691 100644 --- a/pytubefix/jsinterp.py +++ b/pytubefix/jsinterp.py @@ -874,9 +874,11 @@ def assertion(cndn, msg): raise self.Exception(f'{member} {msg}', expr) def eval_method(): + nonlocal member types = { 'String': str, 'Math': float, + 'Array': list, } obj = local_vars.get(variable, types.get(variable, NO_DEFAULT)) if obj is NO_DEFAULT: @@ -900,6 +902,22 @@ def eval_method(): self.interpret_expression(v, local_vars, allow_recursion) for v in self._separate(arg_str)] + # Fixup prototype call + # https://github.com/yt-dlp/yt-dlp/commit/6c056ea7aeb03660281653a9668547f2548f194f + if isinstance(obj, type) and member.startswith('prototype.'): + new_member, _, func_prototype = member.partition('.')[2].partition('.') + assertion(argvals, 'takes one or more arguments') + assertion(isinstance(argvals[0], obj), f'needs binding to type {obj}') + if func_prototype == 'call': + obj, *argvals = argvals + elif func_prototype == 'apply': + assertion(len(argvals) == 2, 'takes two arguments') + obj, argvals = argvals + assertion(isinstance(argvals, list), 'second argument needs to be a list') + else: + raise self.Exception(f'Unsupported Function method {func_prototype}', expr) + member = new_member + if obj == str: if member == 'fromCharCode': assertion(argvals, 'takes one or more arguments')