mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2025-07-11 15:58:31 +00:00
Merge branch 'master' of https://github.com/yt-dlp/yt-dlp into fix/ie/europa
This commit is contained in:
commit
4a0b1079f8
@ -4,6 +4,14 @@ # Changelog
|
|||||||
# To create a release, dispatch the https://github.com/yt-dlp/yt-dlp/actions/workflows/release.yml workflow on master
|
# To create a release, dispatch the https://github.com/yt-dlp/yt-dlp/actions/workflows/release.yml workflow on master
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
### 2025.03.27
|
||||||
|
|
||||||
|
#### Core changes
|
||||||
|
- **jsinterp**: [Fix nested attributes and object extraction](https://github.com/yt-dlp/yt-dlp/commit/a8b9ff3c2a0ae25735e580173becc78545b92572) ([#12760](https://github.com/yt-dlp/yt-dlp/issues/12760)) by [bashonly](https://github.com/bashonly), [seproDev](https://github.com/seproDev)
|
||||||
|
|
||||||
|
#### Extractor changes
|
||||||
|
- **youtube**: [Make signature and nsig extraction more robust](https://github.com/yt-dlp/yt-dlp/commit/48be862b32648bff5b3e553e40fca4dcc6e88b28) ([#12761](https://github.com/yt-dlp/yt-dlp/issues/12761)) by [bashonly](https://github.com/bashonly), [seproDev](https://github.com/seproDev)
|
||||||
|
|
||||||
### 2025.03.26
|
### 2025.03.26
|
||||||
|
|
||||||
#### Extractor changes
|
#### Extractor changes
|
||||||
|
@ -118,6 +118,7 @@ def test_assignments(self):
|
|||||||
self._test('function f(){var x = 20; x = 30 + 1; return x;}', 31)
|
self._test('function f(){var x = 20; x = 30 + 1; return x;}', 31)
|
||||||
self._test('function f(){var x = 20; x += 30 + 1; return x;}', 51)
|
self._test('function f(){var x = 20; x += 30 + 1; return x;}', 51)
|
||||||
self._test('function f(){var x = 20; x -= 30 + 1; return x;}', -11)
|
self._test('function f(){var x = 20; x -= 30 + 1; return x;}', -11)
|
||||||
|
self._test('function f(){var x = 2; var y = ["a", "b"]; y[x%y["length"]]="z"; return y}', ['z', 'b'])
|
||||||
|
|
||||||
@unittest.skip('Not implemented')
|
@unittest.skip('Not implemented')
|
||||||
def test_comments(self):
|
def test_comments(self):
|
||||||
@ -403,6 +404,8 @@ def test_split(self):
|
|||||||
test_result = list('test')
|
test_result = list('test')
|
||||||
tests = [
|
tests = [
|
||||||
'function f(a, b){return a.split(b)}',
|
'function f(a, b){return a.split(b)}',
|
||||||
|
'function f(a, b){return a["split"](b)}',
|
||||||
|
'function f(a, b){let x = ["split"]; return a[x[0]](b)}',
|
||||||
'function f(a, b){return String.prototype.split.call(a, b)}',
|
'function f(a, b){return String.prototype.split.call(a, b)}',
|
||||||
'function f(a, b){return String.prototype.split.apply(a, [b])}',
|
'function f(a, b){return String.prototype.split.apply(a, [b])}',
|
||||||
]
|
]
|
||||||
@ -441,6 +444,9 @@ def test_slice(self):
|
|||||||
self._test('function f(){return "012345678".slice(-1, 1)}', '')
|
self._test('function f(){return "012345678".slice(-1, 1)}', '')
|
||||||
self._test('function f(){return "012345678".slice(-3, -1)}', '67')
|
self._test('function f(){return "012345678".slice(-3, -1)}', '67')
|
||||||
|
|
||||||
|
def test_splice(self):
|
||||||
|
self._test('function f(){var T = ["0", "1", "2"]; T["splice"](2, 1, "0")[0]; return T }', ['0', '1', '0'])
|
||||||
|
|
||||||
def test_js_number_to_string(self):
|
def test_js_number_to_string(self):
|
||||||
for test, radix, expected in [
|
for test, radix, expected in [
|
||||||
(0, None, '0'),
|
(0, None, '0'),
|
||||||
|
@ -88,11 +88,51 @@
|
|||||||
'2aq0aqSyOoJXtK73m-uME_jv7-pT15gOFC02RFkGMqWpzEICs69VdbwQ0LDp1v7j8xx92efCJlYFYb1sUkkBSPOlPmXgIARw8JQ0qOAOAA',
|
'2aq0aqSyOoJXtK73m-uME_jv7-pT15gOFC02RFkGMqWpzEICs69VdbwQ0LDp1v7j8xx92efCJlYFYb1sUkkBSPOlPmXgIARw8JQ0qOAOAA',
|
||||||
'0aqSyOoJXtK73m-uME_jv7-pT15gOFC02RFkGMqWpz2ICs6EVdbwQ0LDp1v7j8xx92efCJlYFYb1sUkkBSPOlPmXgIARw8JQ0qOAOAA',
|
'0aqSyOoJXtK73m-uME_jv7-pT15gOFC02RFkGMqWpz2ICs6EVdbwQ0LDp1v7j8xx92efCJlYFYb1sUkkBSPOlPmXgIARw8JQ0qOAOAA',
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
'https://www.youtube.com/s/player/363db69b/player_ias_tce.vflset/en_US/base.js',
|
||||||
|
'2aq0aqSyOoJXtK73m-uME_jv7-pT15gOFC02RFkGMqWpzEICs69VdbwQ0LDp1v7j8xx92efCJlYFYb1sUkkBSPOlPmXgIARw8JQ0qOAOAA',
|
||||||
|
'0aqSyOoJXtK73m-uME_jv7-pT15gOFC02RFkGMqWpz2ICs6EVdbwQ0LDp1v7j8xx92efCJlYFYb1sUkkBSPOlPmXgIARw8JQ0qOAOAA',
|
||||||
|
),
|
||||||
(
|
(
|
||||||
'https://www.youtube.com/s/player/4fcd6e4a/player_ias.vflset/en_US/base.js',
|
'https://www.youtube.com/s/player/4fcd6e4a/player_ias.vflset/en_US/base.js',
|
||||||
'2aq0aqSyOoJXtK73m-uME_jv7-pT15gOFC02RFkGMqWpzEICs69VdbwQ0LDp1v7j8xx92efCJlYFYb1sUkkBSPOlPmXgIARw8JQ0qOAOAA',
|
'2aq0aqSyOoJXtK73m-uME_jv7-pT15gOFC02RFkGMqWpzEICs69VdbwQ0LDp1v7j8xx92efCJlYFYb1sUkkBSPOlPmXgIARw8JQ0qOAOAA',
|
||||||
'wAOAOq0QJ8ARAIgXmPlOPSBkkUs1bYFYlJCfe29xx8q7v1pDL0QwbdV96sCIEzpWqMGkFR20CFOg51Tp-7vj_EMu-m37KtXJoOySqa0',
|
'wAOAOq0QJ8ARAIgXmPlOPSBkkUs1bYFYlJCfe29xx8q7v1pDL0QwbdV96sCIEzpWqMGkFR20CFOg51Tp-7vj_EMu-m37KtXJoOySqa0',
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
'https://www.youtube.com/s/player/4fcd6e4a/player_ias_tce.vflset/en_US/base.js',
|
||||||
|
'2aq0aqSyOoJXtK73m-uME_jv7-pT15gOFC02RFkGMqWpzEICs69VdbwQ0LDp1v7j8xx92efCJlYFYb1sUkkBSPOlPmXgIARw8JQ0qOAOAA',
|
||||||
|
'wAOAOq0QJ8ARAIgXmPlOPSBkkUs1bYFYlJCfe29xx8q7v1pDL0QwbdV96sCIEzpWqMGkFR20CFOg51Tp-7vj_EMu-m37KtXJoOySqa0',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'https://www.youtube.com/s/player/20830619/player_ias.vflset/en_US/base.js',
|
||||||
|
'2aq0aqSyOoJXtK73m-uME_jv7-pT15gOFC02RFkGMqWpzEICs69VdbwQ0LDp1v7j8xx92efCJlYFYb1sUkkBSPOlPmXgIARw8JQ0qOAOAA',
|
||||||
|
'7AOq0QJ8wRAIgXmPlOPSBkkAs1bYFYlJCfe29xx8jOv1pDL0Q2bdV96sCIEzpWqMGkFR20CFOg51Tp-7vj_EMu-m37KtXJoOySqa0qaw',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'https://www.youtube.com/s/player/20830619/player_ias_tce.vflset/en_US/base.js',
|
||||||
|
'2aq0aqSyOoJXtK73m-uME_jv7-pT15gOFC02RFkGMqWpzEICs69VdbwQ0LDp1v7j8xx92efCJlYFYb1sUkkBSPOlPmXgIARw8JQ0qOAOAA',
|
||||||
|
'7AOq0QJ8wRAIgXmPlOPSBkkAs1bYFYlJCfe29xx8jOv1pDL0Q2bdV96sCIEzpWqMGkFR20CFOg51Tp-7vj_EMu-m37KtXJoOySqa0qaw',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'https://www.youtube.com/s/player/20830619/player-plasma-ias-phone-en_US.vflset/base.js',
|
||||||
|
'2aq0aqSyOoJXtK73m-uME_jv7-pT15gOFC02RFkGMqWpzEICs69VdbwQ0LDp1v7j8xx92efCJlYFYb1sUkkBSPOlPmXgIARw8JQ0qOAOAA',
|
||||||
|
'7AOq0QJ8wRAIgXmPlOPSBkkAs1bYFYlJCfe29xx8jOv1pDL0Q2bdV96sCIEzpWqMGkFR20CFOg51Tp-7vj_EMu-m37KtXJoOySqa0qaw',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'https://www.youtube.com/s/player/20830619/player-plasma-ias-tablet-en_US.vflset/base.js',
|
||||||
|
'2aq0aqSyOoJXtK73m-uME_jv7-pT15gOFC02RFkGMqWpzEICs69VdbwQ0LDp1v7j8xx92efCJlYFYb1sUkkBSPOlPmXgIARw8JQ0qOAOAA',
|
||||||
|
'7AOq0QJ8wRAIgXmPlOPSBkkAs1bYFYlJCfe29xx8jOv1pDL0Q2bdV96sCIEzpWqMGkFR20CFOg51Tp-7vj_EMu-m37KtXJoOySqa0qaw',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'https://www.youtube.com/s/player/8a8ac953/player_ias_tce.vflset/en_US/base.js',
|
||||||
|
'2aq0aqSyOoJXtK73m-uME_jv7-pT15gOFC02RFkGMqWpzEICs69VdbwQ0LDp1v7j8xx92efCJlYFYb1sUkkBSPOlPmXgIARw8JQ0qOAOAA',
|
||||||
|
'IAOAOq0QJ8wRAAgXmPlOPSBkkUs1bYFYlJCfe29xx8j7v1pDL0QwbdV96sCIEzpWqMGkFR20CFOg51Tp-7vj_E2u-m37KtXJoOySqa0',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'https://www.youtube.com/s/player/8a8ac953/tv-player-es6.vflset/tv-player-es6.js',
|
||||||
|
'2aq0aqSyOoJXtK73m-uME_jv7-pT15gOFC02RFkGMqWpzEICs69VdbwQ0LDp1v7j8xx92efCJlYFYb1sUkkBSPOlPmXgIARw8JQ0qOAOAA',
|
||||||
|
'IAOAOq0QJ8wRAAgXmPlOPSBkkUs1bYFYlJCfe29xx8j7v1pDL0QwbdV96sCIEzpWqMGkFR20CFOg51Tp-7vj_E2u-m37KtXJoOySqa0',
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
_NSIG_TESTS = [
|
_NSIG_TESTS = [
|
||||||
@ -252,6 +292,30 @@
|
|||||||
'https://www.youtube.com/s/player/4fcd6e4a/player_ias.vflset/en_US/base.js',
|
'https://www.youtube.com/s/player/4fcd6e4a/player_ias.vflset/en_US/base.js',
|
||||||
'o_L251jm8yhZkWtBW', 'lXoxI3XvToqn6A',
|
'o_L251jm8yhZkWtBW', 'lXoxI3XvToqn6A',
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
'https://www.youtube.com/s/player/4fcd6e4a/player_ias_tce.vflset/en_US/base.js',
|
||||||
|
'o_L251jm8yhZkWtBW', 'lXoxI3XvToqn6A',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'https://www.youtube.com/s/player/20830619/tv-player-ias.vflset/tv-player-ias.js',
|
||||||
|
'ir9-V6cdbCiyKxhr', '9YE85kNjZiS4',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'https://www.youtube.com/s/player/20830619/player-plasma-ias-phone-en_US.vflset/base.js',
|
||||||
|
'ir9-V6cdbCiyKxhr', '9YE85kNjZiS4',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'https://www.youtube.com/s/player/20830619/player-plasma-ias-tablet-en_US.vflset/base.js',
|
||||||
|
'ir9-V6cdbCiyKxhr', '9YE85kNjZiS4',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'https://www.youtube.com/s/player/8a8ac953/player_ias_tce.vflset/en_US/base.js',
|
||||||
|
'MiBYeXx_vRREbiCCmh', 'RtZYMVvmkE0JE',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'https://www.youtube.com/s/player/8a8ac953/tv-player-es6.vflset/tv-player-es6.js',
|
||||||
|
'MiBYeXx_vRREbiCCmh', 'RtZYMVvmkE0JE',
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -302,33 +366,33 @@ def make_tfunc(url, sig_input, expected_sig):
|
|||||||
test_id = re.sub(r'[/.-]', '_', m.group('id') or m.group('compat_id'))
|
test_id = re.sub(r'[/.-]', '_', m.group('id') or m.group('compat_id'))
|
||||||
|
|
||||||
def test_func(self):
|
def test_func(self):
|
||||||
basename = f'player-{name}-{test_id}.js'
|
basename = f'player-{test_id}.js'
|
||||||
fn = os.path.join(self.TESTDATA_DIR, basename)
|
fn = os.path.join(self.TESTDATA_DIR, basename)
|
||||||
|
|
||||||
if not os.path.exists(fn):
|
if not os.path.exists(fn):
|
||||||
urllib.request.urlretrieve(url, fn)
|
urllib.request.urlretrieve(url, fn)
|
||||||
with open(fn, encoding='utf-8') as testf:
|
with open(fn, encoding='utf-8') as testf:
|
||||||
jscode = testf.read()
|
jscode = testf.read()
|
||||||
self.assertEqual(sig_func(jscode, sig_input), expected_sig)
|
self.assertEqual(sig_func(jscode, sig_input, url), expected_sig)
|
||||||
|
|
||||||
test_func.__name__ = f'test_{name}_js_{test_id}'
|
test_func.__name__ = f'test_{name}_js_{test_id}'
|
||||||
setattr(TestSignature, test_func.__name__, test_func)
|
setattr(TestSignature, test_func.__name__, test_func)
|
||||||
return make_tfunc
|
return make_tfunc
|
||||||
|
|
||||||
|
|
||||||
def signature(jscode, sig_input):
|
def signature(jscode, sig_input, player_url):
|
||||||
func = YoutubeIE(FakeYDL())._parse_sig_js(jscode)
|
func = YoutubeIE(FakeYDL())._parse_sig_js(jscode, player_url)
|
||||||
src_sig = (
|
src_sig = (
|
||||||
str(string.printable[:sig_input])
|
str(string.printable[:sig_input])
|
||||||
if isinstance(sig_input, int) else sig_input)
|
if isinstance(sig_input, int) else sig_input)
|
||||||
return func(src_sig)
|
return func(src_sig)
|
||||||
|
|
||||||
|
|
||||||
def n_sig(jscode, sig_input):
|
def n_sig(jscode, sig_input, player_url):
|
||||||
ie = YoutubeIE(FakeYDL())
|
ie = YoutubeIE(FakeYDL())
|
||||||
funcname = ie._extract_n_function_name(jscode)
|
funcname = ie._extract_n_function_name(jscode, player_url=player_url)
|
||||||
jsi = JSInterpreter(jscode)
|
jsi = JSInterpreter(jscode)
|
||||||
func = jsi.extract_function_from_code(*ie._fixup_n_function_code(*jsi.extract_function_code(funcname), jscode))
|
func = jsi.extract_function_from_code(*ie._fixup_n_function_code(*jsi.extract_function_code(funcname), jscode, player_url))
|
||||||
return func([sig_input])
|
return func([sig_input])
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
clean_html,
|
clean_html,
|
||||||
datetime_from_str,
|
datetime_from_str,
|
||||||
filesize_from_tbr,
|
filesize_from_tbr,
|
||||||
|
filter_dict,
|
||||||
float_or_none,
|
float_or_none,
|
||||||
format_field,
|
format_field,
|
||||||
get_first,
|
get_first,
|
||||||
@ -1986,12 +1987,12 @@ def _extract_signature_function(self, video_id, player_url, example_sig):
|
|||||||
assert os.path.basename(func_id) == func_id
|
assert os.path.basename(func_id) == func_id
|
||||||
|
|
||||||
self.write_debug(f'Extracting signature function {func_id}')
|
self.write_debug(f'Extracting signature function {func_id}')
|
||||||
cache_spec, code = self.cache.load('youtube-sigfuncs', func_id), None
|
cache_spec, code = self.cache.load('youtube-sigfuncs', func_id, min_ver='2025.03.27'), None
|
||||||
|
|
||||||
if not cache_spec:
|
if not cache_spec:
|
||||||
code = self._load_player(video_id, player_url)
|
code = self._load_player(video_id, player_url)
|
||||||
if code:
|
if code:
|
||||||
res = self._parse_sig_js(code)
|
res = self._parse_sig_js(code, player_url)
|
||||||
test_string = ''.join(map(chr, range(len(example_sig))))
|
test_string = ''.join(map(chr, range(len(example_sig))))
|
||||||
cache_spec = [ord(c) for c in res(test_string)]
|
cache_spec = [ord(c) for c in res(test_string)]
|
||||||
self.cache.store('youtube-sigfuncs', func_id, cache_spec)
|
self.cache.store('youtube-sigfuncs', func_id, cache_spec)
|
||||||
@ -2039,7 +2040,7 @@ def _genslice(start, end, step):
|
|||||||
f' return {expr_code}\n')
|
f' return {expr_code}\n')
|
||||||
self.to_screen('Extracted signature function:\n' + code)
|
self.to_screen('Extracted signature function:\n' + code)
|
||||||
|
|
||||||
def _parse_sig_js(self, jscode):
|
def _parse_sig_js(self, jscode, player_url):
|
||||||
# Examples where `sig` is funcname:
|
# Examples where `sig` is funcname:
|
||||||
# sig=function(a){a=a.split(""); ... ;return a.join("")};
|
# sig=function(a){a=a.split(""); ... ;return a.join("")};
|
||||||
# ;c&&(c=sig(decodeURIComponent(c)),a.set(b,encodeURIComponent(c)));return a};
|
# ;c&&(c=sig(decodeURIComponent(c)),a.set(b,encodeURIComponent(c)));return a};
|
||||||
@ -2063,12 +2064,9 @@ def _parse_sig_js(self, jscode):
|
|||||||
r'\bc\s*&&\s*[a-zA-Z0-9]+\.set\([^,]+\s*,\s*\([^)]*\)\s*\(\s*(?P<sig>[a-zA-Z0-9$]+)\('),
|
r'\bc\s*&&\s*[a-zA-Z0-9]+\.set\([^,]+\s*,\s*\([^)]*\)\s*\(\s*(?P<sig>[a-zA-Z0-9$]+)\('),
|
||||||
jscode, 'Initial JS player signature function name', group='sig')
|
jscode, 'Initial JS player signature function name', group='sig')
|
||||||
|
|
||||||
|
varname, global_list = self._interpret_player_js_global_var(jscode, player_url)
|
||||||
jsi = JSInterpreter(jscode)
|
jsi = JSInterpreter(jscode)
|
||||||
global_var_map = {}
|
initial_function = jsi.extract_function(funcname, filter_dict({varname: global_list}))
|
||||||
_, varname, value = self._extract_player_js_global_var(jscode)
|
|
||||||
if varname:
|
|
||||||
global_var_map[varname] = jsi.interpret_expression(value, {}, allow_recursion=100)
|
|
||||||
initial_function = jsi.extract_function(funcname, global_var_map)
|
|
||||||
return lambda s: initial_function([s])
|
return lambda s: initial_function([s])
|
||||||
|
|
||||||
def _cached(self, func, *cache_id):
|
def _cached(self, func, *cache_id):
|
||||||
@ -2093,7 +2091,7 @@ def _load_nsig_code_from_cache(self, player_id):
|
|||||||
if func_code := self._player_cache.get(cache_id):
|
if func_code := self._player_cache.get(cache_id):
|
||||||
return func_code
|
return func_code
|
||||||
|
|
||||||
func_code = self.cache.load('youtube-nsig', player_id, min_ver='2025.03.26')
|
func_code = self.cache.load('youtube-nsig', player_id, min_ver='2025.03.27')
|
||||||
if func_code:
|
if func_code:
|
||||||
self._player_cache[cache_id] = func_code
|
self._player_cache[cache_id] = func_code
|
||||||
|
|
||||||
@ -2150,6 +2148,26 @@ def _decrypt_nsig(self, s, video_id, player_url):
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
def _extract_n_function_name(self, jscode, player_url=None):
|
def _extract_n_function_name(self, jscode, player_url=None):
|
||||||
|
varname, global_list = self._interpret_player_js_global_var(jscode, player_url)
|
||||||
|
if debug_str := traverse_obj(global_list, (lambda _, v: v.endswith('_w8_'), any)):
|
||||||
|
funcname = self._search_regex(
|
||||||
|
r'''(?xs)
|
||||||
|
[;\n](?:
|
||||||
|
(?P<f>function\s+)|
|
||||||
|
(?:var\s+)?
|
||||||
|
)(?P<funcname>[a-zA-Z0-9_$]+)\s*(?(f)|=\s*function\s*)
|
||||||
|
\((?P<argname>[a-zA-Z0-9_$]+)\)\s*\{
|
||||||
|
(?:(?!\}[;\n]).)+
|
||||||
|
\}\s*catch\(\s*[a-zA-Z0-9_$]+\s*\)\s*
|
||||||
|
\{\s*return\s+%s\[%d\]\s*\+\s*(?P=argname)\s*\}\s*return\s+[^}]+\}[;\n]
|
||||||
|
''' % (re.escape(varname), global_list.index(debug_str)),
|
||||||
|
jscode, 'nsig function name', group='funcname', default=None)
|
||||||
|
if funcname:
|
||||||
|
return funcname
|
||||||
|
self.write_debug(join_nonempty(
|
||||||
|
'Initial search was unable to find nsig function name',
|
||||||
|
player_url and f' player = {player_url}', delim='\n'), only_once=True)
|
||||||
|
|
||||||
# Examples (with placeholders nfunc, narray, idx):
|
# Examples (with placeholders nfunc, narray, idx):
|
||||||
# * .get("n"))&&(b=nfunc(b)
|
# * .get("n"))&&(b=nfunc(b)
|
||||||
# * .get("n"))&&(b=narray[idx](b)
|
# * .get("n"))&&(b=narray[idx](b)
|
||||||
@ -2179,7 +2197,7 @@ def _extract_n_function_name(self, jscode, player_url=None):
|
|||||||
if not funcname:
|
if not funcname:
|
||||||
self.report_warning(join_nonempty(
|
self.report_warning(join_nonempty(
|
||||||
'Falling back to generic n function search',
|
'Falling back to generic n function search',
|
||||||
player_url and f' player = {player_url}', delim='\n'))
|
player_url and f' player = {player_url}', delim='\n'), only_once=True)
|
||||||
return self._search_regex(
|
return self._search_regex(
|
||||||
r'''(?xs)
|
r'''(?xs)
|
||||||
;\s*(?P<name>[a-zA-Z0-9_$]+)\s*=\s*function\([a-zA-Z0-9_$]+\)
|
;\s*(?P<name>[a-zA-Z0-9_$]+)\s*=\s*function\([a-zA-Z0-9_$]+\)
|
||||||
@ -2192,9 +2210,10 @@ def _extract_n_function_name(self, jscode, player_url=None):
|
|||||||
rf'var {re.escape(funcname)}\s*=\s*(\[.+?\])\s*[,;]', jscode,
|
rf'var {re.escape(funcname)}\s*=\s*(\[.+?\])\s*[,;]', jscode,
|
||||||
f'Initial JS player n function list ({funcname}.{idx})')))[int(idx)]
|
f'Initial JS player n function list ({funcname}.{idx})')))[int(idx)]
|
||||||
|
|
||||||
def _extract_player_js_global_var(self, jscode):
|
def _extract_player_js_global_var(self, jscode, player_url):
|
||||||
"""Returns tuple of strings: variable assignment code, variable name, variable value code"""
|
"""Returns tuple of strings: variable assignment code, variable name, variable value code"""
|
||||||
return self._search_regex(
|
extract_global_var = self._cached(self._search_regex, 'js global array', player_url)
|
||||||
|
varcode, varname, varvalue = extract_global_var(
|
||||||
r'''(?x)
|
r'''(?x)
|
||||||
(?P<q1>["\'])use\s+strict(?P=q1);\s*
|
(?P<q1>["\'])use\s+strict(?P=q1);\s*
|
||||||
(?P<code>
|
(?P<code>
|
||||||
@ -2206,17 +2225,41 @@ def _extract_player_js_global_var(self, jscode):
|
|||||||
)
|
)
|
||||||
)[;,]
|
)[;,]
|
||||||
''', jscode, 'global variable', group=('code', 'name', 'value'), default=(None, None, None))
|
''', jscode, 'global variable', group=('code', 'name', 'value'), default=(None, None, None))
|
||||||
|
if not varcode:
|
||||||
|
self.write_debug(join_nonempty(
|
||||||
|
'No global array variable found in player JS',
|
||||||
|
player_url and f' player = {player_url}', delim='\n'), only_once=True)
|
||||||
|
return varcode, varname, varvalue
|
||||||
|
|
||||||
def _fixup_n_function_code(self, argnames, code, full_code):
|
def _interpret_player_js_global_var(self, jscode, player_url):
|
||||||
global_var, varname, _ = self._extract_player_js_global_var(full_code)
|
"""Returns tuple of: variable name string, variable value list"""
|
||||||
if global_var:
|
_, varname, array_code = self._extract_player_js_global_var(jscode, player_url)
|
||||||
self.write_debug(f'Prepending n function code with global array variable "{varname}"')
|
jsi = JSInterpreter(array_code)
|
||||||
code = global_var + '; ' + code
|
interpret_global_var = self._cached(jsi.interpret_expression, 'js global list', player_url)
|
||||||
|
return varname, interpret_global_var(array_code, {}, allow_recursion=10)
|
||||||
|
|
||||||
|
def _fixup_n_function_code(self, argnames, nsig_code, jscode, player_url):
|
||||||
|
varcode, varname, _ = self._extract_player_js_global_var(jscode, player_url)
|
||||||
|
if varcode and varname:
|
||||||
|
nsig_code = varcode + '; ' + nsig_code
|
||||||
|
_, global_list = self._interpret_player_js_global_var(jscode, player_url)
|
||||||
else:
|
else:
|
||||||
self.write_debug('No global array variable found in player JS')
|
varname = 'dlp_wins'
|
||||||
return argnames, re.sub(
|
global_list = []
|
||||||
rf';\s*if\s*\(\s*typeof\s+[a-zA-Z0-9_$]+\s*===?\s*(?:(["\'])undefined\1|{varname}\[\d+\])\s*\)\s*return\s+{argnames[0]};',
|
|
||||||
';', code)
|
undefined_idx = global_list.index('undefined') if 'undefined' in global_list else r'\d+'
|
||||||
|
fixed_code = re.sub(
|
||||||
|
rf'''(?x)
|
||||||
|
;\s*if\s*\(\s*typeof\s+[a-zA-Z0-9_$]+\s*===?\s*(?:
|
||||||
|
(["\'])undefined\1|
|
||||||
|
{re.escape(varname)}\[{undefined_idx}\]
|
||||||
|
)\s*\)\s*return\s+{re.escape(argnames[0])};
|
||||||
|
''', ';', nsig_code)
|
||||||
|
if fixed_code == nsig_code:
|
||||||
|
self.write_debug(join_nonempty(
|
||||||
|
'No typeof statement found in nsig function code',
|
||||||
|
player_url and f' player = {player_url}', delim='\n'), only_once=True)
|
||||||
|
return argnames, fixed_code
|
||||||
|
|
||||||
def _extract_n_function_code(self, video_id, player_url):
|
def _extract_n_function_code(self, video_id, player_url):
|
||||||
player_id = self._extract_player_info(player_url)
|
player_id = self._extract_player_info(player_url)
|
||||||
@ -2230,7 +2273,7 @@ def _extract_n_function_code(self, video_id, player_url):
|
|||||||
func_name = self._extract_n_function_name(jscode, player_url=player_url)
|
func_name = self._extract_n_function_name(jscode, player_url=player_url)
|
||||||
|
|
||||||
# XXX: Workaround for the global array variable and lack of `typeof` implementation
|
# XXX: Workaround for the global array variable and lack of `typeof` implementation
|
||||||
func_code = self._fixup_n_function_code(*jsi.extract_function_code(func_name), jscode)
|
func_code = self._fixup_n_function_code(*jsi.extract_function_code(func_name), jscode, player_url)
|
||||||
|
|
||||||
return jsi, player_id, func_code
|
return jsi, player_id, func_code
|
||||||
|
|
||||||
|
@ -188,6 +188,7 @@ def js_number_to_string(val: float, radix: int = 10):
|
|||||||
_NAME_RE = r'[a-zA-Z_$][\w$]*'
|
_NAME_RE = r'[a-zA-Z_$][\w$]*'
|
||||||
_MATCHING_PARENS = dict(zip(*zip('()', '{}', '[]')))
|
_MATCHING_PARENS = dict(zip(*zip('()', '{}', '[]')))
|
||||||
_QUOTES = '\'"/'
|
_QUOTES = '\'"/'
|
||||||
|
_NESTED_BRACKETS = r'[^[\]]+(?:\[[^[\]]+(?:\[[^\]]+\])?\])?'
|
||||||
|
|
||||||
|
|
||||||
class JS_Undefined:
|
class JS_Undefined:
|
||||||
@ -606,15 +607,18 @@ def dict_item(key, val):
|
|||||||
|
|
||||||
m = re.match(fr'''(?x)
|
m = re.match(fr'''(?x)
|
||||||
(?P<assign>
|
(?P<assign>
|
||||||
(?P<out>{_NAME_RE})(?:\[(?P<index>[^\]]+?)\])?\s*
|
(?P<out>{_NAME_RE})(?:\[(?P<index>{_NESTED_BRACKETS})\])?\s*
|
||||||
(?P<op>{"|".join(map(re.escape, set(_OPERATORS) - _COMP_OPERATORS))})?
|
(?P<op>{"|".join(map(re.escape, set(_OPERATORS) - _COMP_OPERATORS))})?
|
||||||
=(?!=)(?P<expr>.*)$
|
=(?!=)(?P<expr>.*)$
|
||||||
)|(?P<return>
|
)|(?P<return>
|
||||||
(?!if|return|true|false|null|undefined|NaN)(?P<name>{_NAME_RE})$
|
(?!if|return|true|false|null|undefined|NaN)(?P<name>{_NAME_RE})$
|
||||||
|
)|(?P<attribute>
|
||||||
|
(?P<var>{_NAME_RE})(?:
|
||||||
|
(?P<nullish>\?)?\.(?P<member>[^(]+)|
|
||||||
|
\[(?P<member2>{_NESTED_BRACKETS})\]
|
||||||
|
)\s*
|
||||||
)|(?P<indexing>
|
)|(?P<indexing>
|
||||||
(?P<in>{_NAME_RE})\[(?P<idx>.+)\]$
|
(?P<in>{_NAME_RE})\[(?P<idx>.+)\]$
|
||||||
)|(?P<attribute>
|
|
||||||
(?P<var>{_NAME_RE})(?:(?P<nullish>\?)?\.(?P<member>[^(]+)|\[(?P<member2>[^\]]+)\])\s*
|
|
||||||
)|(?P<function>
|
)|(?P<function>
|
||||||
(?P<fname>{_NAME_RE})\((?P<args>.*)\)$
|
(?P<fname>{_NAME_RE})\((?P<args>.*)\)$
|
||||||
)''', expr)
|
)''', expr)
|
||||||
@ -707,7 +711,7 @@ def eval_method():
|
|||||||
if obj is NO_DEFAULT:
|
if obj is NO_DEFAULT:
|
||||||
if variable not in self._objects:
|
if variable not in self._objects:
|
||||||
try:
|
try:
|
||||||
self._objects[variable] = self.extract_object(variable)
|
self._objects[variable] = self.extract_object(variable, local_vars)
|
||||||
except self.Exception:
|
except self.Exception:
|
||||||
if not nullish:
|
if not nullish:
|
||||||
raise
|
raise
|
||||||
@ -847,7 +851,7 @@ def interpret_expression(self, expr, local_vars, allow_recursion):
|
|||||||
raise self.Exception('Cannot return from an expression', expr)
|
raise self.Exception('Cannot return from an expression', expr)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def extract_object(self, objname):
|
def extract_object(self, objname, *global_stack):
|
||||||
_FUNC_NAME_RE = r'''(?:[a-zA-Z$0-9]+|"[a-zA-Z$0-9]+"|'[a-zA-Z$0-9]+')'''
|
_FUNC_NAME_RE = r'''(?:[a-zA-Z$0-9]+|"[a-zA-Z$0-9]+"|'[a-zA-Z$0-9]+')'''
|
||||||
obj = {}
|
obj = {}
|
||||||
obj_m = re.search(
|
obj_m = re.search(
|
||||||
@ -869,7 +873,8 @@ def extract_object(self, objname):
|
|||||||
for f in fields_m:
|
for f in fields_m:
|
||||||
argnames = f.group('args').split(',')
|
argnames = f.group('args').split(',')
|
||||||
name = remove_quotes(f.group('key'))
|
name = remove_quotes(f.group('key'))
|
||||||
obj[name] = function_with_repr(self.build_function(argnames, f.group('code')), f'F<{name}>')
|
obj[name] = function_with_repr(
|
||||||
|
self.build_function(argnames, f.group('code'), *global_stack), f'F<{name}>')
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# Autogenerated by devscripts/update-version.py
|
# Autogenerated by devscripts/update-version.py
|
||||||
|
|
||||||
__version__ = '2025.03.26'
|
__version__ = '2025.03.27'
|
||||||
|
|
||||||
RELEASE_GIT_HEAD = 'ecee97b4fa90d51c48f9154c3a6d5a8ffe46cd5c'
|
RELEASE_GIT_HEAD = '48be862b32648bff5b3e553e40fca4dcc6e88b28'
|
||||||
|
|
||||||
VARIANT = None
|
VARIANT = None
|
||||||
|
|
||||||
@ -12,4 +12,4 @@
|
|||||||
|
|
||||||
ORIGIN = 'yt-dlp/yt-dlp'
|
ORIGIN = 'yt-dlp/yt-dlp'
|
||||||
|
|
||||||
_pkg_version = '2025.03.26'
|
_pkg_version = '2025.03.27'
|
||||||
|
Loading…
Reference in New Issue
Block a user