Compare commits
7 Commits
08361cbddc
...
5ea8f9b5cb
Author | SHA1 | Date | |
---|---|---|---|
|
5ea8f9b5cb | ||
|
4d05f84325 | ||
|
e0094e63c3 | ||
|
fd8242e3ef | ||
|
ad01fa6cca | ||
|
2eac0fa379 | ||
|
fad516e22e |
@ -577,9 +577,11 @@ class TestJSInterpreter(unittest.TestCase):
|
|||||||
def test_unary_operators(self):
|
def test_unary_operators(self):
|
||||||
jsi = JSInterpreter('function f(){return 2 - - - 2;}')
|
jsi = JSInterpreter('function f(){return 2 - - - 2;}')
|
||||||
self.assertEqual(jsi.call_function('f'), 0)
|
self.assertEqual(jsi.call_function('f'), 0)
|
||||||
# fails
|
jsi = JSInterpreter('function f(){return 2 + - + - - 2;}')
|
||||||
# jsi = JSInterpreter('function f(){return 2 + - + - - 2;}')
|
self.assertEqual(jsi.call_function('f'), 0)
|
||||||
# self.assertEqual(jsi.call_function('f'), 0)
|
# https://github.com/ytdl-org/youtube-dl/issues/32815
|
||||||
|
jsi = JSInterpreter('function f(){return 0 - 7 * - 6;}')
|
||||||
|
self.assertEqual(jsi.call_function('f'), 42)
|
||||||
|
|
||||||
""" # fails so far
|
""" # fails so far
|
||||||
def test_packed(self):
|
def test_packed(self):
|
||||||
|
@ -158,6 +158,10 @@ _NSIG_TESTS = [
|
|||||||
'https://www.youtube.com/s/player/b7910ca8/player_ias.vflset/en_US/base.js',
|
'https://www.youtube.com/s/player/b7910ca8/player_ias.vflset/en_US/base.js',
|
||||||
'_hXMCwMt9qE310D', 'LoZMgkkofRMCZQ',
|
'_hXMCwMt9qE310D', 'LoZMgkkofRMCZQ',
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
'https://www.youtube.com/s/player/590f65a6/player_ias.vflset/en_US/base.js',
|
||||||
|
'1tm7-g_A9zsI8_Lay_', 'xI4Vem4Put_rOg',
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -3033,7 +3033,6 @@ class InfoExtractor(object):
|
|||||||
transform_source=transform_source, default=None)
|
transform_source=transform_source, default=None)
|
||||||
|
|
||||||
def _extract_jwplayer_data(self, webpage, video_id, *args, **kwargs):
|
def _extract_jwplayer_data(self, webpage, video_id, *args, **kwargs):
|
||||||
|
|
||||||
# allow passing `transform_source` through to _find_jwplayer_data()
|
# allow passing `transform_source` through to _find_jwplayer_data()
|
||||||
transform_source = kwargs.pop('transform_source', None)
|
transform_source = kwargs.pop('transform_source', None)
|
||||||
kwfind = compat_kwargs({'transform_source': transform_source}) if transform_source else {}
|
kwfind = compat_kwargs({'transform_source': transform_source}) if transform_source else {}
|
||||||
|
@ -8,7 +8,7 @@ from ..compat import compat_str
|
|||||||
from ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
str_or_none,
|
str_or_none,
|
||||||
try_get,
|
traverse_obj,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -109,7 +109,7 @@ class PalcoMP3ArtistIE(PalcoMP3BaseIE):
|
|||||||
}
|
}
|
||||||
name'''
|
name'''
|
||||||
|
|
||||||
@ classmethod
|
@classmethod
|
||||||
def suitable(cls, url):
|
def suitable(cls, url):
|
||||||
return False if re.match(PalcoMP3IE._VALID_URL, url) else super(PalcoMP3ArtistIE, cls).suitable(url)
|
return False if re.match(PalcoMP3IE._VALID_URL, url) else super(PalcoMP3ArtistIE, cls).suitable(url)
|
||||||
|
|
||||||
@ -118,7 +118,8 @@ class PalcoMP3ArtistIE(PalcoMP3BaseIE):
|
|||||||
artist = self._call_api(artist_slug, self._ARTIST_FIELDS_TMPL)['artist']
|
artist = self._call_api(artist_slug, self._ARTIST_FIELDS_TMPL)['artist']
|
||||||
|
|
||||||
def entries():
|
def entries():
|
||||||
for music in (try_get(artist, lambda x: x['musics']['nodes'], list) or []):
|
for music in traverse_obj(artist, (
|
||||||
|
'musics', 'nodes', lambda _, m: m['musicID'])):
|
||||||
yield self._parse_music(music)
|
yield self._parse_music(music)
|
||||||
|
|
||||||
return self.playlist_result(
|
return self.playlist_result(
|
||||||
@ -137,7 +138,7 @@ class PalcoMP3VideoIE(PalcoMP3BaseIE):
|
|||||||
'title': 'Maiara e Maraisa - Você Faz Falta Aqui - DVD Ao Vivo Em Campo Grande',
|
'title': 'Maiara e Maraisa - Você Faz Falta Aqui - DVD Ao Vivo Em Campo Grande',
|
||||||
'description': 'md5:7043342c09a224598e93546e98e49282',
|
'description': 'md5:7043342c09a224598e93546e98e49282',
|
||||||
'upload_date': '20161107',
|
'upload_date': '20161107',
|
||||||
'uploader_id': 'maiaramaraisaoficial',
|
'uploader_id': '@maiaramaraisaoficial',
|
||||||
'uploader': 'Maiara e Maraisa',
|
'uploader': 'Maiara e Maraisa',
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
@ -4,58 +4,229 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import compat_str
|
from ..compat import (
|
||||||
from ..utils import (
|
compat_kwargs,
|
||||||
int_or_none,
|
compat_str,
|
||||||
remove_start,
|
|
||||||
smuggle_url,
|
|
||||||
try_get,
|
|
||||||
)
|
)
|
||||||
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
int_or_none,
|
||||||
|
smuggle_url,
|
||||||
|
str_or_none,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from ..utils import traverse_obj
|
||||||
|
except ImportError:
|
||||||
|
from ..compat import compat_collections_abc
|
||||||
|
|
||||||
|
def traverse_obj(obj, *path_list, **kw):
|
||||||
|
''' Traverse nested list/dict/tuple'''
|
||||||
|
|
||||||
|
# parameter defaults
|
||||||
|
default = kw.get('default')
|
||||||
|
expected_type = kw.get('expected_type')
|
||||||
|
get_all = kw.get('get_all', True)
|
||||||
|
casesense = kw.get('casesense', True)
|
||||||
|
is_user_input = kw.get('is_user_input', False)
|
||||||
|
traverse_string = kw.get('traverse_string', False)
|
||||||
|
|
||||||
|
def variadic(x, allowed_types=(compat_str, bytes)):
|
||||||
|
return x if isinstance(x, compat_collections_abc.Iterable) and not isinstance(x, allowed_types) else (x,)
|
||||||
|
|
||||||
|
def listish(l):
|
||||||
|
return isinstance(l, (list, tuple))
|
||||||
|
|
||||||
|
def from_iterable(iterables):
|
||||||
|
# chain.from_iterable(['ABC', 'DEF']) --> A B C D E F
|
||||||
|
for it in iterables:
|
||||||
|
for element in it:
|
||||||
|
yield element
|
||||||
|
|
||||||
|
class Nonlocal:
|
||||||
|
pass
|
||||||
|
nl = Nonlocal()
|
||||||
|
|
||||||
|
if not casesense:
|
||||||
|
_lower = lambda k: (k.lower() if isinstance(k, compat_str) else k)
|
||||||
|
path_list = (map(_lower, variadic(path)) for path in path_list)
|
||||||
|
|
||||||
|
def _traverse_obj(obj, path, _current_depth=0):
|
||||||
|
path = tuple(variadic(path))
|
||||||
|
for i, key in enumerate(path):
|
||||||
|
if obj is None:
|
||||||
|
return None
|
||||||
|
if listish(key):
|
||||||
|
obj = [_traverse_obj(obj, sub_key, _current_depth) for sub_key in key]
|
||||||
|
key = Ellipsis
|
||||||
|
if key is Ellipsis:
|
||||||
|
obj = (obj.values() if isinstance(obj, dict)
|
||||||
|
else obj if listish(obj)
|
||||||
|
else compat_str(obj) if traverse_string else [])
|
||||||
|
_current_depth += 1
|
||||||
|
nl.depth = max(nl.depth, _current_depth)
|
||||||
|
return [_traverse_obj(inner_obj, path[i + 1:], _current_depth) for inner_obj in obj]
|
||||||
|
elif callable(key):
|
||||||
|
if listish(obj):
|
||||||
|
obj = enumerate(obj)
|
||||||
|
elif isinstance(obj, dict):
|
||||||
|
obj = obj.items()
|
||||||
|
else:
|
||||||
|
if not traverse_string:
|
||||||
|
return None
|
||||||
|
obj = str(obj)
|
||||||
|
_current_depth += 1
|
||||||
|
nl.depth = max(nl.depth, _current_depth)
|
||||||
|
return [_traverse_obj(v, path[i + 1:], _current_depth) for k, v in obj if key(k)]
|
||||||
|
elif isinstance(obj, dict) and not (is_user_input and key == ':'):
|
||||||
|
obj = (obj.get(key) if casesense or (key in obj)
|
||||||
|
else next((v for k, v in obj.items() if _lower(k) == key), None))
|
||||||
|
else:
|
||||||
|
if is_user_input:
|
||||||
|
key = (int_or_none(key) if ':' not in key
|
||||||
|
else slice(*map(int_or_none, key.split(':'))))
|
||||||
|
if key == slice(None):
|
||||||
|
return _traverse_obj(obj, tuple([Ellipsis] + list(path[i + 1:])), _current_depth)
|
||||||
|
if not isinstance(key, (int, slice)):
|
||||||
|
return None
|
||||||
|
if not listish(obj):
|
||||||
|
if not traverse_string:
|
||||||
|
return None
|
||||||
|
obj = compat_str(obj)
|
||||||
|
try:
|
||||||
|
obj = obj[key]
|
||||||
|
except IndexError:
|
||||||
|
return None
|
||||||
|
return obj
|
||||||
|
|
||||||
|
if isinstance(expected_type, type):
|
||||||
|
type_test = lambda val: val if isinstance(val, expected_type) else None
|
||||||
|
elif expected_type is not None:
|
||||||
|
type_test = expected_type
|
||||||
|
else:
|
||||||
|
type_test = lambda val: val
|
||||||
|
|
||||||
|
for path in path_list:
|
||||||
|
nl.depth = 0
|
||||||
|
val = _traverse_obj(obj, path)
|
||||||
|
if val is not None:
|
||||||
|
if nl.depth:
|
||||||
|
for _ in range(nl.depth - 1):
|
||||||
|
val = from_iterable(v for v in val if v is not None)
|
||||||
|
val = [v for v in map(type_test, val) if v is not None]
|
||||||
|
if val:
|
||||||
|
return val if get_all else val[0]
|
||||||
|
else:
|
||||||
|
val = type_test(val)
|
||||||
|
if val is not None:
|
||||||
|
return val
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
class TVerIE(InfoExtractor):
|
class TVerIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?tver\.jp/(?P<path>(?:corner|episode|feature)/(?P<id>f?\d+))'
|
_VALID_URL = r'https?://(?:www\.)?tver\.jp/(?:(?P<type>lp|corner|series|episodes?|feature|tokyo2020/video)/)+(?P<id>[a-zA-Z0-9]+)'
|
||||||
# videos are only available for 7 days
|
# videos are only available for 7 days
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'https://tver.jp/corner/f0062178',
|
'skip': 'videos are only available for 7 days',
|
||||||
|
'url': 'https://tver.jp/episodes/ephss8yveb',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'ref:af03de03-21ac-4b98-a53b-9ffd9b102e92',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '#44 料理と値段と店主にびっくり オモてなしすぎウマい店 2時間SP',
|
||||||
|
'description': 'md5:66985373a66fed8ad3cd595a3cfebb13',
|
||||||
|
'upload_date': '20220329',
|
||||||
|
'uploader_id': '4394098882001',
|
||||||
|
'timestamp': 1648527032,
|
||||||
|
},
|
||||||
|
'add_ie': ['BrightcoveNew'],
|
||||||
|
}, {
|
||||||
|
'skip': 'videos are only available for 7 days',
|
||||||
|
'url': 'https://tver.jp/lp/episodes/ep6f16g26p',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '6302378806001',
|
||||||
|
'ext': 'mp4',
|
||||||
|
# "April 11 (Mon) 23:06-Broadcast scheduled": sorry but this is "correct"
|
||||||
|
'title': '4月11日(月)23時06分 ~ 放送予定',
|
||||||
|
'description': 'md5:4029cc5f4b1e8090dfc5b7bd2bc5cd0b',
|
||||||
|
'upload_date': '20220331',
|
||||||
|
'uploader_id': '3971130137001',
|
||||||
|
'timestamp': 1648696456,
|
||||||
|
},
|
||||||
|
'add_ie': ['BrightcoveNew'],
|
||||||
|
}, {
|
||||||
|
'url': 'https://tver.jp/corner/f0103888',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}, {
|
}, {
|
||||||
'url': 'https://tver.jp/feature/f0062413',
|
'url': 'https://tver.jp/lp/f0033031',
|
||||||
'only_matching': True,
|
|
||||||
}, {
|
|
||||||
'url': 'https://tver.jp/episode/79622438',
|
|
||||||
'only_matching': True,
|
|
||||||
}, {
|
|
||||||
# subtitle = ' '
|
|
||||||
'url': 'https://tver.jp/corner/f0068870',
|
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
_TOKEN = None
|
|
||||||
BRIGHTCOVE_URL_TEMPLATE = 'http://players.brightcove.net/%s/default_default/index.html?videoId=%s'
|
BRIGHTCOVE_URL_TEMPLATE = 'http://players.brightcove.net/%s/default_default/index.html?videoId=%s'
|
||||||
|
_PLATFORM_UID = None
|
||||||
|
_PLATFORM_TOKEN = None
|
||||||
|
|
||||||
|
def _download_json(self, url_or_request, video_id, **kwargs):
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'Origin': 'https://s.tver.jp',
|
||||||
|
'Referer': 'https://s.tver.jp/',
|
||||||
|
}
|
||||||
|
headers.update(kwargs.get('headers', {}))
|
||||||
|
kwargs.update(compat_kwargs({'headers': headers, }))
|
||||||
|
|
||||||
|
return super(TVerIE, self)._download_json(url_or_request, video_id, **kwargs)
|
||||||
|
|
||||||
def _real_initialize(self):
|
def _real_initialize(self):
|
||||||
self._TOKEN = self._download_json(
|
create_response = self._download_json(
|
||||||
'https://tver.jp/api/access_token.php', None)['token']
|
'https://platform-api.tver.jp/v2/api/platform_users/browser/create', None,
|
||||||
|
note='Creating session', data=b'device_type=pc', headers={
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
})
|
||||||
|
self._PLATFORM_UID = traverse_obj(create_response, ('result', 'platform_uid'), expected_type=compat_str)
|
||||||
|
self._PLATFORM_TOKEN = traverse_obj(create_response, ('result', 'platform_token'), expected_type=compat_str)
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
path, video_id = re.match(self._VALID_URL, url).groups()
|
video_id = self._match_id(url)
|
||||||
main = self._download_json(
|
|
||||||
'https://api.tver.jp/v4/' + path, video_id,
|
|
||||||
query={'token': self._TOKEN})['main']
|
|
||||||
p_id = main['publisher_id']
|
|
||||||
service = remove_start(main['service'], 'ts_')
|
|
||||||
|
|
||||||
r_id = main['reference_id']
|
video_id, video_type = re.match(self._VALID_URL, url).group('id', 'type')
|
||||||
if service not in ('tx', 'russia2018', 'sebare2018live', 'gorin'):
|
if video_type not in ('episode', 'episodes', 'series'):
|
||||||
|
webpage = self._download_webpage(url, video_id, note='Resolving to new URL')
|
||||||
|
video_id = self._match_id(self._search_regex(
|
||||||
|
(r'''canonical['"]\s+href\s*=\s*(?P<q>'|")(?P<url>https?://tver\.jp/(?!(?P=q)).+?)(?P=q)''',
|
||||||
|
r'&link=(?P<url>https?://tver\.jp/(?!(?P=q)).+?)[?&]'),
|
||||||
|
webpage, 'url regex', group='url'))
|
||||||
|
video_info = self._download_json(
|
||||||
|
'https://statics.tver.jp/content/episode/{0}.json'.format(video_id), video_id,
|
||||||
|
query={'v': '5'})
|
||||||
|
p_id = traverse_obj(video_info, ('video', 'accountID'), expected_type=compat_str)
|
||||||
|
r_id = traverse_obj(video_info, ('video', ('videoRefID', 'videoID')), get_all=False, expected_type=compat_str)
|
||||||
|
if None in (p_id, r_id):
|
||||||
|
raise ExtractorError(
|
||||||
|
'Failed to extract '
|
||||||
|
+ ', '.join(
|
||||||
|
(x[0] for x in (('accountID', p_id), ('videoRefID', r_id), )
|
||||||
|
if x[1] is None)),
|
||||||
|
expected=False)
|
||||||
|
if not r_id.isdigit():
|
||||||
r_id = 'ref:' + r_id
|
r_id = 'ref:' + r_id
|
||||||
bc_url = smuggle_url(
|
|
||||||
self.BRIGHTCOVE_URL_TEMPLATE % (p_id, r_id),
|
additional_info = self._download_json(
|
||||||
{'geo_countries': ['JP']})
|
'https://platform-api.tver.jp/service/api/v1/callEpisode/{0}?require_data=mylist,later[epefy106ur],good[epefy106ur],resume[epefy106ur]'.format(video_id),
|
||||||
|
video_id, fatal=False,
|
||||||
|
query={
|
||||||
|
'platform_uid': self._PLATFORM_UID,
|
||||||
|
'platform_token': self._PLATFORM_TOKEN,
|
||||||
|
}, headers={
|
||||||
|
'x-tver-platform-type': 'web'
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'_type': 'url_transparent',
|
'_type': 'url_transparent',
|
||||||
'description': try_get(main, lambda x: x['note'][0]['text'], compat_str),
|
'title': str_or_none(video_info.get('title')),
|
||||||
'episode_number': int_or_none(try_get(main, lambda x: x['ext']['episode_number'])),
|
'description': str_or_none(video_info.get('description')),
|
||||||
'url': bc_url,
|
'url': smuggle_url(
|
||||||
|
self.BRIGHTCOVE_URL_TEMPLATE % (p_id, r_id), {'geo_countries': ['JP']}),
|
||||||
|
'series': traverse_obj(
|
||||||
|
additional_info, ('result', ('episode', 'series'), 'content', ('seriesTitle', 'title')),
|
||||||
|
get_all=False, expected_type=compat_str),
|
||||||
'ie_key': 'BrightcoveNew',
|
'ie_key': 'BrightcoveNew',
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ from .utils import (
|
|||||||
remove_quotes,
|
remove_quotes,
|
||||||
unified_timestamp,
|
unified_timestamp,
|
||||||
variadic,
|
variadic,
|
||||||
|
write_string,
|
||||||
)
|
)
|
||||||
from .compat import (
|
from .compat import (
|
||||||
compat_basestring,
|
compat_basestring,
|
||||||
@ -53,15 +54,16 @@ def wraps_op(op):
|
|||||||
|
|
||||||
# NB In principle NaN cannot be checked by membership.
|
# NB In principle NaN cannot be checked by membership.
|
||||||
# Here all NaN values are actually this one, so _NaN is _NaN,
|
# Here all NaN values are actually this one, so _NaN is _NaN,
|
||||||
# although _NaN != _NaN.
|
# although _NaN != _NaN. Ditto Infinity.
|
||||||
|
|
||||||
_NaN = float('nan')
|
_NaN = float('nan')
|
||||||
|
_Infinity = float('inf')
|
||||||
|
|
||||||
|
|
||||||
def _js_bit_op(op):
|
def _js_bit_op(op):
|
||||||
|
|
||||||
def zeroise(x):
|
def zeroise(x):
|
||||||
return 0 if x in (None, JS_Undefined, _NaN) else x
|
return 0 if x in (None, JS_Undefined, _NaN, _Infinity) else x
|
||||||
|
|
||||||
@wraps_op(op)
|
@wraps_op(op)
|
||||||
def wrapped(a, b):
|
def wrapped(a, b):
|
||||||
@ -84,7 +86,7 @@ def _js_arith_op(op):
|
|||||||
def _js_div(a, b):
|
def _js_div(a, b):
|
||||||
if JS_Undefined in (a, b) or not (a or b):
|
if JS_Undefined in (a, b) or not (a or b):
|
||||||
return _NaN
|
return _NaN
|
||||||
return operator.truediv(a or 0, b) if b else float('inf')
|
return operator.truediv(a or 0, b) if b else _Infinity
|
||||||
|
|
||||||
|
|
||||||
def _js_mod(a, b):
|
def _js_mod(a, b):
|
||||||
@ -220,6 +222,42 @@ class LocalNameSpace(ChainMap):
|
|||||||
return 'LocalNameSpace%s' % (self.maps, )
|
return 'LocalNameSpace%s' % (self.maps, )
|
||||||
|
|
||||||
|
|
||||||
|
class Debugger(object):
|
||||||
|
ENABLED = False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def write(*args, **kwargs):
|
||||||
|
level = kwargs.get('level', 100)
|
||||||
|
|
||||||
|
def truncate_string(s, left, right=0):
|
||||||
|
if s is None or len(s) <= left + right:
|
||||||
|
return s
|
||||||
|
return '...'.join((s[:left - 3], s[-right:] if right else ''))
|
||||||
|
|
||||||
|
write_string('[debug] JS: {0}{1}\n'.format(
|
||||||
|
' ' * (100 - level),
|
||||||
|
' '.join(truncate_string(compat_str(x), 50, 50) for x in args)))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def wrap_interpreter(cls, f):
|
||||||
|
def interpret_statement(self, stmt, local_vars, allow_recursion, *args, **kwargs):
|
||||||
|
if cls.ENABLED and stmt.strip():
|
||||||
|
cls.write(stmt, level=allow_recursion)
|
||||||
|
try:
|
||||||
|
ret, should_ret = f(self, stmt, local_vars, allow_recursion, *args, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
if cls.ENABLED:
|
||||||
|
if isinstance(e, ExtractorError):
|
||||||
|
e = e.orig_msg
|
||||||
|
cls.write('=> Raises:', e, '<-|', stmt, level=allow_recursion)
|
||||||
|
raise
|
||||||
|
if cls.ENABLED and stmt.strip():
|
||||||
|
if should_ret or not repr(ret) == stmt:
|
||||||
|
cls.write(['->', '=>'][should_ret], repr(ret), '<-|', stmt, level=allow_recursion)
|
||||||
|
return ret, should_ret
|
||||||
|
return interpret_statement
|
||||||
|
|
||||||
|
|
||||||
class JSInterpreter(object):
|
class JSInterpreter(object):
|
||||||
__named_object_counter = 0
|
__named_object_counter = 0
|
||||||
|
|
||||||
@ -307,8 +345,7 @@ class JSInterpreter(object):
|
|||||||
def __op_chars(cls):
|
def __op_chars(cls):
|
||||||
op_chars = set(';,[')
|
op_chars = set(';,[')
|
||||||
for op in cls._all_operators():
|
for op in cls._all_operators():
|
||||||
for c in op[0]:
|
op_chars.update(op[0])
|
||||||
op_chars.add(c)
|
|
||||||
return op_chars
|
return op_chars
|
||||||
|
|
||||||
def _named_object(self, namespace, obj):
|
def _named_object(self, namespace, obj):
|
||||||
@ -326,9 +363,8 @@ class JSInterpreter(object):
|
|||||||
# collections.Counter() is ~10% slower in both 2.7 and 3.9
|
# collections.Counter() is ~10% slower in both 2.7 and 3.9
|
||||||
counters = dict((k, 0) for k in _MATCHING_PARENS.values())
|
counters = dict((k, 0) for k in _MATCHING_PARENS.values())
|
||||||
start, splits, pos, delim_len = 0, 0, 0, len(delim) - 1
|
start, splits, pos, delim_len = 0, 0, 0, len(delim) - 1
|
||||||
in_quote, escaping, skipping = None, False, 0
|
in_quote, escaping, after_op, in_regex_char_group = None, False, True, False
|
||||||
after_op, in_regex_char_group = True, False
|
skipping = 0
|
||||||
|
|
||||||
for idx, char in enumerate(expr):
|
for idx, char in enumerate(expr):
|
||||||
paren_delta = 0
|
paren_delta = 0
|
||||||
if not in_quote:
|
if not in_quote:
|
||||||
@ -382,10 +418,12 @@ class JSInterpreter(object):
|
|||||||
return separated[0][1:].strip(), separated[1].strip()
|
return separated[0][1:].strip(), separated[1].strip()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _all_operators():
|
def _all_operators(_cached=[]):
|
||||||
return itertools.chain(
|
if not _cached:
|
||||||
|
_cached.extend(itertools.chain(
|
||||||
# Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence
|
# Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence
|
||||||
_SC_OPERATORS, _LOG_OPERATORS, _COMP_OPERATORS, _OPERATORS)
|
_SC_OPERATORS, _LOG_OPERATORS, _COMP_OPERATORS, _OPERATORS))
|
||||||
|
return _cached
|
||||||
|
|
||||||
def _operator(self, op, left_val, right_expr, expr, local_vars, allow_recursion):
|
def _operator(self, op, left_val, right_expr, expr, local_vars, allow_recursion):
|
||||||
if op in ('||', '&&'):
|
if op in ('||', '&&'):
|
||||||
@ -416,7 +454,7 @@ class JSInterpreter(object):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
if allow_undefined:
|
if allow_undefined:
|
||||||
return JS_Undefined
|
return JS_Undefined
|
||||||
raise self.Exception('Cannot get index {idx:.100}'.format(**locals()), expr=repr(obj), cause=e)
|
raise self.Exception('Cannot get index {idx!r:.100}'.format(**locals()), expr=repr(obj), cause=e)
|
||||||
|
|
||||||
def _dump(self, obj, namespace):
|
def _dump(self, obj, namespace):
|
||||||
try:
|
try:
|
||||||
@ -438,6 +476,7 @@ class JSInterpreter(object):
|
|||||||
_FINALLY_RE = re.compile(r'finally\s*\{')
|
_FINALLY_RE = re.compile(r'finally\s*\{')
|
||||||
_SWITCH_RE = re.compile(r'switch\s*\(')
|
_SWITCH_RE = re.compile(r'switch\s*\(')
|
||||||
|
|
||||||
|
@Debugger.wrap_interpreter
|
||||||
def interpret_statement(self, stmt, local_vars, allow_recursion=100):
|
def interpret_statement(self, stmt, local_vars, allow_recursion=100):
|
||||||
if allow_recursion < 0:
|
if allow_recursion < 0:
|
||||||
raise self.Exception('Recursion limit reached')
|
raise self.Exception('Recursion limit reached')
|
||||||
@ -511,7 +550,6 @@ class JSInterpreter(object):
|
|||||||
expr = self._dump(inner, local_vars) + outer
|
expr = self._dump(inner, local_vars) + outer
|
||||||
|
|
||||||
if expr.startswith('('):
|
if expr.startswith('('):
|
||||||
|
|
||||||
m = re.match(r'\((?P<d>[a-z])%(?P<e>[a-z])\.length\+(?P=e)\.length\)%(?P=e)\.length', expr)
|
m = re.match(r'\((?P<d>[a-z])%(?P<e>[a-z])\.length\+(?P=e)\.length\)%(?P=e)\.length', expr)
|
||||||
if m:
|
if m:
|
||||||
# short-cut eval of frequently used `(d%e.length+e.length)%e.length`, worth ~6% on `pytest -k test_nsig`
|
# short-cut eval of frequently used `(d%e.length+e.length)%e.length`, worth ~6% on `pytest -k test_nsig`
|
||||||
@ -693,7 +731,7 @@ class JSInterpreter(object):
|
|||||||
(?P<op>{_OPERATOR_RE})?
|
(?P<op>{_OPERATOR_RE})?
|
||||||
=(?!=)(?P<expr>.*)$
|
=(?!=)(?P<expr>.*)$
|
||||||
)|(?P<return>
|
)|(?P<return>
|
||||||
(?!if|return|true|false|null|undefined)(?P<name>{_NAME_RE})$
|
(?!if|return|true|false|null|undefined|NaN|Infinity)(?P<name>{_NAME_RE})$
|
||||||
)|(?P<indexing>
|
)|(?P<indexing>
|
||||||
(?P<in>{_NAME_RE})\[(?P<idx>.+)\]$
|
(?P<in>{_NAME_RE})\[(?P<idx>.+)\]$
|
||||||
)|(?P<attribute>
|
)|(?P<attribute>
|
||||||
@ -727,11 +765,12 @@ class JSInterpreter(object):
|
|||||||
raise JS_Break()
|
raise JS_Break()
|
||||||
elif expr == 'continue':
|
elif expr == 'continue':
|
||||||
raise JS_Continue()
|
raise JS_Continue()
|
||||||
|
|
||||||
elif expr == 'undefined':
|
elif expr == 'undefined':
|
||||||
return JS_Undefined, should_return
|
return JS_Undefined, should_return
|
||||||
elif expr == 'NaN':
|
elif expr == 'NaN':
|
||||||
return _NaN, should_return
|
return _NaN, should_return
|
||||||
|
elif expr == 'Infinity':
|
||||||
|
return _Infinity, should_return
|
||||||
|
|
||||||
elif md.get('return'):
|
elif md.get('return'):
|
||||||
return local_vars[m.group('name')], should_return
|
return local_vars[m.group('name')], should_return
|
||||||
@ -760,17 +799,27 @@ class JSInterpreter(object):
|
|||||||
right_expr = separated.pop()
|
right_expr = separated.pop()
|
||||||
# handle operators that are both unary and binary, minimal BODMAS
|
# handle operators that are both unary and binary, minimal BODMAS
|
||||||
if op in ('+', '-'):
|
if op in ('+', '-'):
|
||||||
|
# simplify/adjust consecutive instances of these operators
|
||||||
undone = 0
|
undone = 0
|
||||||
while len(separated) > 1 and not separated[-1].strip():
|
while len(separated) > 1 and not separated[-1].strip():
|
||||||
undone += 1
|
undone += 1
|
||||||
separated.pop()
|
separated.pop()
|
||||||
if op == '-' and undone % 2 != 0:
|
if op == '-' and undone % 2 != 0:
|
||||||
right_expr = op + right_expr
|
right_expr = op + right_expr
|
||||||
|
elif op == '+':
|
||||||
|
while len(separated) > 1 and separated[-1].strip() in self.OP_CHARS:
|
||||||
|
right_expr = separated.pop() + right_expr
|
||||||
|
# hanging op at end of left => unary + (strip) or - (push right)
|
||||||
left_val = separated[-1]
|
left_val = separated[-1]
|
||||||
for dm_op in ('*', '%', '/', '**'):
|
for dm_op in ('*', '%', '/', '**'):
|
||||||
bodmas = tuple(self._separate(left_val, dm_op, skip_delims=skip_delim))
|
bodmas = tuple(self._separate(left_val, dm_op, skip_delims=skip_delim))
|
||||||
if len(bodmas) > 1 and not bodmas[-1].strip():
|
if len(bodmas) > 1 and not bodmas[-1].strip():
|
||||||
expr = op.join(separated) + op + right_expr
|
expr = op.join(separated) + op + right_expr
|
||||||
|
if len(separated) > 1:
|
||||||
|
separated.pop()
|
||||||
|
right_expr = op.join((left_val, right_expr))
|
||||||
|
else:
|
||||||
|
separated = [op.join((left_val, right_expr))]
|
||||||
right_expr = None
|
right_expr = None
|
||||||
break
|
break
|
||||||
if right_expr is None:
|
if right_expr is None:
|
||||||
@ -797,6 +846,8 @@ class JSInterpreter(object):
|
|||||||
|
|
||||||
def eval_method():
|
def eval_method():
|
||||||
if (variable, member) == ('console', 'debug'):
|
if (variable, member) == ('console', 'debug'):
|
||||||
|
if Debugger.ENABLED:
|
||||||
|
Debugger.write(self.interpret_expression('[{}]'.format(arg_str), local_vars, allow_recursion))
|
||||||
return
|
return
|
||||||
types = {
|
types = {
|
||||||
'String': compat_str,
|
'String': compat_str,
|
||||||
|
@ -2406,7 +2406,7 @@ class ExtractorError(YoutubeDLError):
|
|||||||
""" tb, if given, is the original traceback (so that it can be printed out).
|
""" tb, if given, is the original traceback (so that it can be printed out).
|
||||||
If expected is set, this is a normal error message and most likely not a bug in youtube-dl.
|
If expected is set, this is a normal error message and most likely not a bug in youtube-dl.
|
||||||
"""
|
"""
|
||||||
|
self.orig_msg = msg
|
||||||
if sys.exc_info()[0] in (compat_urllib_error.URLError, socket.timeout, UnavailableVideoError):
|
if sys.exc_info()[0] in (compat_urllib_error.URLError, socket.timeout, UnavailableVideoError):
|
||||||
expected = True
|
expected = True
|
||||||
if video_id is not None:
|
if video_id is not None:
|
||||||
|
Loading…
Reference in New Issue
Block a user