Upgrade yt_dlp and download script
This commit is contained in:
@@ -25,7 +25,7 @@ def _js_bit_op(op):
|
||||
with contextlib.suppress(TypeError):
|
||||
if math.isnan(x): # NB: NaN cannot be checked by membership
|
||||
return 0
|
||||
return x
|
||||
return int(float(x))
|
||||
|
||||
def wrapped(a, b):
|
||||
return op(zeroise(a), zeroise(b)) & 0xffffffff
|
||||
@@ -95,6 +95,61 @@ def _js_ternary(cndn, if_true=True, if_false=False):
|
||||
return if_true
|
||||
|
||||
|
||||
# Ref: https://es5.github.io/#x9.8.1
|
||||
def js_number_to_string(val: float, radix: int = 10):
|
||||
if radix in (JS_Undefined, None):
|
||||
radix = 10
|
||||
assert radix in range(2, 37), 'radix must be an integer at least 2 and no greater than 36'
|
||||
|
||||
if math.isnan(val):
|
||||
return 'NaN'
|
||||
if val == 0:
|
||||
return '0'
|
||||
if math.isinf(val):
|
||||
return '-Infinity' if val < 0 else 'Infinity'
|
||||
if radix == 10:
|
||||
# TODO: implement special cases
|
||||
...
|
||||
|
||||
ALPHABET = b'0123456789abcdefghijklmnopqrstuvwxyz.-'
|
||||
|
||||
result = collections.deque()
|
||||
sign = val < 0
|
||||
val = abs(val)
|
||||
fraction, integer = math.modf(val)
|
||||
delta = max(math.nextafter(.0, math.inf), math.ulp(val) / 2)
|
||||
|
||||
if fraction >= delta:
|
||||
result.append(-2) # `.`
|
||||
while fraction >= delta:
|
||||
delta *= radix
|
||||
fraction, digit = math.modf(fraction * radix)
|
||||
result.append(int(digit))
|
||||
# if we need to round, propagate potential carry through fractional part
|
||||
needs_rounding = fraction > 0.5 or (fraction == 0.5 and int(digit) & 1)
|
||||
if needs_rounding and fraction + delta > 1:
|
||||
for index in reversed(range(1, len(result))):
|
||||
if result[index] + 1 < radix:
|
||||
result[index] += 1
|
||||
break
|
||||
result.pop()
|
||||
|
||||
else:
|
||||
integer += 1
|
||||
break
|
||||
|
||||
integer, digit = divmod(int(integer), radix)
|
||||
result.appendleft(digit)
|
||||
while integer > 0:
|
||||
integer, digit = divmod(integer, radix)
|
||||
result.appendleft(digit)
|
||||
|
||||
if sign:
|
||||
result.appendleft(-1) # `-`
|
||||
|
||||
return bytes(ALPHABET[digit] for digit in result).decode('ascii')
|
||||
|
||||
|
||||
# Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence
|
||||
_OPERATORS = { # None => Defined in JSInterpreter._operator
|
||||
'?': None,
|
||||
@@ -133,6 +188,7 @@ _COMP_OPERATORS = {'===', '!==', '==', '!=', '<=', '>=', '<', '>'}
|
||||
_NAME_RE = r'[a-zA-Z_$][\w$]*'
|
||||
_MATCHING_PARENS = dict(zip(*zip('()', '{}', '[]')))
|
||||
_QUOTES = '\'"/'
|
||||
_NESTED_BRACKETS = r'[^[\]]+(?:\[[^[\]]+(?:\[[^\]]+\])?\])?'
|
||||
|
||||
|
||||
class JS_Undefined:
|
||||
@@ -190,7 +246,7 @@ class Debugger:
|
||||
cls.write('=> Raises:', e, '<-|', stmt, level=allow_recursion)
|
||||
raise
|
||||
if cls.ENABLED and stmt.strip():
|
||||
if should_ret or not repr(ret) == stmt:
|
||||
if should_ret or repr(ret) != stmt:
|
||||
cls.write(['->', '=>'][should_ret], repr(ret), '<-|', stmt, level=allow_recursion)
|
||||
return ret, should_ret
|
||||
return interpret_statement
|
||||
@@ -216,7 +272,7 @@ class JSInterpreter:
|
||||
self.code, self._functions = code, {}
|
||||
self._objects = {} if objects is None else objects
|
||||
|
||||
class Exception(ExtractorError):
|
||||
class Exception(ExtractorError): # noqa: A001
|
||||
def __init__(self, msg, expr=None, *args, **kwargs):
|
||||
if expr is not None:
|
||||
msg = f'{msg.rstrip()} in: {truncate_string(expr, 50, 50)}'
|
||||
@@ -235,7 +291,7 @@ class JSInterpreter:
|
||||
flags = 0
|
||||
if not expr:
|
||||
return flags, expr
|
||||
for idx, ch in enumerate(expr):
|
||||
for idx, ch in enumerate(expr): # noqa: B007
|
||||
if ch not in cls._RE_FLAGS:
|
||||
break
|
||||
flags |= cls._RE_FLAGS[ch]
|
||||
@@ -246,7 +302,7 @@ class JSInterpreter:
|
||||
OP_CHARS = '+-*/%&|^=<>!,;{}:['
|
||||
if not expr:
|
||||
return
|
||||
counters = {k: 0 for k in _MATCHING_PARENS.values()}
|
||||
counters = dict.fromkeys(_MATCHING_PARENS.values(), 0)
|
||||
start, splits, pos, delim_len = 0, 0, 0, len(delim) - 1
|
||||
in_quote, escaping, after_op, in_regex_char_group = None, False, True, False
|
||||
for idx, char in enumerate(expr):
|
||||
@@ -474,7 +530,7 @@ class JSInterpreter:
|
||||
if remaining.startswith('{'):
|
||||
body, expr = self._separate_at_paren(remaining)
|
||||
else:
|
||||
switch_m = re.match(r'switch\s*\(', remaining) # FIXME
|
||||
switch_m = re.match(r'switch\s*\(', remaining) # FIXME: ?
|
||||
if switch_m:
|
||||
switch_val, remaining = self._separate_at_paren(remaining[switch_m.end() - 1:])
|
||||
body, expr = self._separate_at_paren(remaining, '}')
|
||||
@@ -551,15 +607,18 @@ class JSInterpreter:
|
||||
|
||||
m = re.match(fr'''(?x)
|
||||
(?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<expr>.*)$
|
||||
)|(?P<return>
|
||||
(?!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<in>{_NAME_RE})\[(?P<idx>.+)\]$
|
||||
)|(?P<attribute>
|
||||
(?P<var>{_NAME_RE})(?:(?P<nullish>\?)?\.(?P<member>[^(]+)|\[(?P<member2>[^\]]+)\])\s*
|
||||
)|(?P<function>
|
||||
(?P<fname>{_NAME_RE})\((?P<args>.*)\)$
|
||||
)''', expr)
|
||||
@@ -585,9 +644,9 @@ class JSInterpreter:
|
||||
return int(expr), should_return
|
||||
|
||||
elif expr == 'break':
|
||||
raise JS_Break()
|
||||
raise JS_Break
|
||||
elif expr == 'continue':
|
||||
raise JS_Continue()
|
||||
raise JS_Continue
|
||||
elif expr == 'undefined':
|
||||
return JS_Undefined, should_return
|
||||
elif expr == 'NaN':
|
||||
@@ -636,6 +695,8 @@ class JSInterpreter:
|
||||
raise self.Exception(f'{member} {msg}', expr)
|
||||
|
||||
def eval_method():
|
||||
nonlocal member
|
||||
|
||||
if (variable, member) == ('console', 'debug'):
|
||||
if Debugger.ENABLED:
|
||||
Debugger.write(self.interpret_expression(f'[{arg_str}]', local_vars, allow_recursion))
|
||||
@@ -644,12 +705,13 @@ class JSInterpreter:
|
||||
types = {
|
||||
'String': str,
|
||||
'Math': float,
|
||||
'Array': list,
|
||||
}
|
||||
obj = local_vars.get(variable, types.get(variable, NO_DEFAULT))
|
||||
if obj is NO_DEFAULT:
|
||||
if variable not in self._objects:
|
||||
try:
|
||||
self._objects[variable] = self.extract_object(variable)
|
||||
self._objects[variable] = self.extract_object(variable, local_vars)
|
||||
except self.Exception:
|
||||
if not nullish:
|
||||
raise
|
||||
@@ -667,12 +729,27 @@ class JSInterpreter:
|
||||
self.interpret_expression(v, local_vars, allow_recursion)
|
||||
for v in self._separate(arg_str)]
|
||||
|
||||
if obj == str:
|
||||
# Fixup prototype call
|
||||
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 is str:
|
||||
if member == 'fromCharCode':
|
||||
assertion(argvals, 'takes one or more arguments')
|
||||
return ''.join(map(chr, argvals))
|
||||
raise self.Exception(f'Unsupported String method {member}', expr)
|
||||
elif obj == float:
|
||||
elif obj is float:
|
||||
if member == 'pow':
|
||||
assertion(len(argvals) == 2, 'takes two arguments')
|
||||
return argvals[0] ** argvals[1]
|
||||
@@ -691,18 +768,18 @@ class JSInterpreter:
|
||||
obj.reverse()
|
||||
return obj
|
||||
elif member == 'slice':
|
||||
assertion(isinstance(obj, list), 'must be applied on a list')
|
||||
assertion(len(argvals) == 1, 'takes exactly one argument')
|
||||
return obj[argvals[0]:]
|
||||
assertion(isinstance(obj, (list, str)), 'must be applied on a list or string')
|
||||
assertion(len(argvals) <= 2, 'takes between 0 and 2 arguments')
|
||||
return obj[slice(*argvals, None)]
|
||||
elif member == 'splice':
|
||||
assertion(isinstance(obj, list), 'must be applied on a list')
|
||||
assertion(argvals, 'takes one or more arguments')
|
||||
index, howMany = map(int, (argvals + [len(obj)])[:2])
|
||||
index, how_many = map(int, ([*argvals, len(obj)])[:2])
|
||||
if index < 0:
|
||||
index += len(obj)
|
||||
add_items = argvals[2:]
|
||||
res = []
|
||||
for i in range(index, min(index + howMany, len(obj))):
|
||||
for _ in range(index, min(index + how_many, len(obj))):
|
||||
res.append(obj.pop(index))
|
||||
for i, item in enumerate(add_items):
|
||||
obj.insert(index + i, item)
|
||||
@@ -726,12 +803,12 @@ class JSInterpreter:
|
||||
elif member == 'forEach':
|
||||
assertion(argvals, 'takes one or more arguments')
|
||||
assertion(len(argvals) <= 2, 'takes at-most 2 arguments')
|
||||
f, this = (argvals + [''])[:2]
|
||||
f, this = ([*argvals, ''])[:2]
|
||||
return [f((item, idx, obj), {'this': this}, allow_recursion) for idx, item in enumerate(obj)]
|
||||
elif member == 'indexOf':
|
||||
assertion(argvals, 'takes one or more arguments')
|
||||
assertion(len(argvals) <= 2, 'takes at-most 2 arguments')
|
||||
idx, start = (argvals + [0])[:2]
|
||||
idx, start = ([*argvals, 0])[:2]
|
||||
try:
|
||||
return obj.index(idx, start)
|
||||
except ValueError:
|
||||
@@ -774,7 +851,7 @@ class JSInterpreter:
|
||||
raise self.Exception('Cannot return from an expression', expr)
|
||||
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]+')'''
|
||||
obj = {}
|
||||
obj_m = re.search(
|
||||
@@ -796,7 +873,8 @@ class JSInterpreter:
|
||||
for f in fields_m:
|
||||
argnames = f.group('args').split(',')
|
||||
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
|
||||
|
||||
@@ -817,9 +895,9 @@ class JSInterpreter:
|
||||
code, _ = self._separate_at_paren(func_m.group('code'))
|
||||
return [x.strip() for x in func_m.group('args').split(',')], code
|
||||
|
||||
def extract_function(self, funcname):
|
||||
def extract_function(self, funcname, *global_stack):
|
||||
return function_with_repr(
|
||||
self.extract_function_from_code(*self.extract_function_code(funcname)),
|
||||
self.extract_function_from_code(*self.extract_function_code(funcname), *global_stack),
|
||||
f'F<{funcname}>')
|
||||
|
||||
def extract_function_from_code(self, argnames, code, *global_stack):
|
||||
|
Reference in New Issue
Block a user