Skip to content

Commit

Permalink
cringe
Browse files Browse the repository at this point in the history
  • Loading branch information
bashonly committed Dec 7, 2024
1 parent 6fef824 commit ad6fc34
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 22 deletions.
1 change: 1 addition & 0 deletions yt_dlp/extractor/youtube.py
Original file line number Diff line number Diff line change
Expand Up @@ -3248,6 +3248,7 @@ def _extract_n_function_name(self, jscode, player_url=None):
f'Initial JS player n function list ({funcname}.{idx})')))[int(idx)]

def _fixup_n_function_code(self, argnames, code):
return argnames, code
return argnames, re.sub(
rf';\s*if\s*\(\s*typeof\s+[a-zA-Z0-9_$]+\s*===?\s*(["\'])undefined\1\s*\)\s*return\s+{argnames[0]};',
';', code)
Expand Down
105 changes: 83 additions & 22 deletions yt_dlp/jsinterp.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,51 @@ def _js_ternary(cndn, if_true=True, if_false=False):
return if_true


def _js_unary_op(op):

def wrapped(_, a):
return op(a)

return wrapped


# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof
def _js_typeof(expr):
try:
result = {
JS_Undefined: 'undefined',
True: 'boolean',
False: 'boolean',
None: 'object',
}[expr]
except (TypeError, KeyError):
result = None
if result is None:
for t, n in (
(str, 'string'),
((int, float), 'number'),
):
if isinstance(expr, t):
result = n
break
else:
if callable(expr):
result = 'function'
# TODO: Symbol, BigInt
return 'object' if result is None else result


def _strict_equals(a, b):
native_types = str, int, float
if not isinstance(a, native_types) or not isinstance(b, native_types):
return a is b
return a == b


def _strict_not_equals(a, b):
return not _strict_equals(a, b)


# Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence
_OPERATORS = { # None => Defined in JSInterpreter._operator
'?': None,
Expand All @@ -106,8 +151,8 @@ def _js_ternary(cndn, if_true=True, if_false=False):
'^': _js_bit_op(operator.xor),
'&': _js_bit_op(operator.and_),

'===': operator.is_,
'!==': operator.is_not,
'===': _strict_equals,
'!==': _strict_not_equals,
'==': _js_eq_op(operator.eq),
'!=': _js_eq_op(operator.ne),

Expand All @@ -126,10 +171,15 @@ def _js_ternary(cndn, if_true=True, if_false=False):
'%': _js_mod,
'/': _js_div,
'**': _js_exp,

'void': _js_unary_op(lambda _: JS_Undefined),
'typeof': _js_unary_op(_js_typeof),
}

_COMP_OPERATORS = {'===', '!==', '==', '!=', '<=', '>=', '<', '>'}

_UNARY_OPERATORS = {'void', 'typeof'}

_NAME_RE = r'[a-zA-Z_$][\w$]*'
_MATCHING_PARENS = dict(zip(*zip('()', '{}', '[]')))
_QUOTES = '\'"/'
Expand Down Expand Up @@ -169,7 +219,7 @@ def __delitem__(self, key):

class Debugger:
import sys
ENABLED = False and 'pytest' in sys.modules
ENABLED = True and 'pytest' in sys.modules

@staticmethod
def write(*args, level=100):
Expand Down Expand Up @@ -324,6 +374,23 @@ def _dump(self, obj, namespace):
except TypeError:
return self._named_object(namespace, obj)

def handle_operators(self, expr, local_vars, allow_recursion):
for op in _OPERATORS:
separated = list(self._separate(expr, op))
right_expr = separated.pop()
while True:
if op in '?<>*-' and len(separated) > 1 and not separated[-1].strip():
separated.pop()
elif not (separated and op == '?' and right_expr.startswith('.')):
break
right_expr = f'{op}{right_expr}'
if op != '-':
right_expr = f'{separated.pop()}{op}{right_expr}'
if not separated:
continue
left_val = self.interpret_expression(op.join(separated), local_vars, allow_recursion)
return self._operator(op, left_val, right_expr, expr, local_vars, allow_recursion), True

@Debugger.wrap_interpreter
def interpret_statement(self, stmt, local_vars, allow_recursion=100):
if allow_recursion < 0:
Expand Down Expand Up @@ -374,9 +441,15 @@ def interpret_statement(self, stmt, local_vars, allow_recursion=100):
else:
raise self.Exception(f'Unsupported object {obj}', expr)

if expr.startswith('void '):
left = self.interpret_expression(expr[5:], local_vars, allow_recursion)
return None, should_return
for op in _UNARY_OPERATORS:
if not expr.startswith(op):
continue
operand = expr[len(op):]
if not operand or operand[0] != ' ':
continue
op_result = self.handle_operators(expr, local_vars, allow_recursion)
if op_result:
return op_result[0], should_return

if expr.startswith('{'):
inner, outer = self._separate_at_paren(expr)
Expand Down Expand Up @@ -552,7 +625,7 @@ def dict_item(key, val):
m = re.match(fr'''(?x)
(?P<assign>
(?P<out>{_NAME_RE})(?:\[(?P<index>[^\]]+?)\])?\s*
(?P<op>{"|".join(map(re.escape, set(_OPERATORS) - _COMP_OPERATORS))})?
(?P<op>{"|".join(map(re.escape, set(_OPERATORS) - _COMP_OPERATORS - _UNARY_OPERATORS))})?
=(?!=)(?P<expr>.*)$
)|(?P<return>
(?!if|return|true|false|null|undefined|NaN)(?P<name>{_NAME_RE})$
Expand Down Expand Up @@ -604,21 +677,9 @@ def dict_item(key, val):
idx = self.interpret_expression(m.group('idx'), local_vars, allow_recursion)
return self._index(val, idx), should_return

for op in _OPERATORS:
separated = list(self._separate(expr, op))
right_expr = separated.pop()
while True:
if op in '?<>*-' and len(separated) > 1 and not separated[-1].strip():
separated.pop()
elif not (separated and op == '?' and right_expr.startswith('.')):
break
right_expr = f'{op}{right_expr}'
if op != '-':
right_expr = f'{separated.pop()}{op}{right_expr}'
if not separated:
continue
left_val = self.interpret_expression(op.join(separated), local_vars, allow_recursion)
return self._operator(op, left_val, right_expr, expr, local_vars, allow_recursion), should_return
op_result = self.handle_operators(expr, local_vars, allow_recursion)
if op_result:
return op_result[0], should_return

if m and m.group('attribute'):
variable, member, nullish = m.group('var', 'member', 'nullish')
Expand Down

0 comments on commit ad6fc34

Please sign in to comment.