mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2026-06-28 13:45:55 +00:00
Compare commits
No commits in common. "8cb08028f5be2acb9835ce1670b196b9b077052f" and "26feac3dd142536ad08ad1ed731378cb88e63602" have entirely different histories.
8cb08028f5
...
26feac3dd1
@ -1221,10 +1221,20 @@ class TwitterIE(TwitterBaseIE):
|
|||||||
}]
|
}]
|
||||||
|
|
||||||
_MEDIA_ID_RE = re.compile(r'_video/(\d+)/')
|
_MEDIA_ID_RE = re.compile(r'_video/(\d+)/')
|
||||||
_GRAPHQL_ENDPOINT = '2ICDjqPd81tulZcYrtpTuQ/TweetResultByRestId'
|
|
||||||
|
@property
|
||||||
|
def _GRAPHQL_ENDPOINT(self):
|
||||||
|
if self.is_logged_in:
|
||||||
|
return 'zZXycP0V6H7m-2r0mOnFcA/TweetDetail'
|
||||||
|
return '2ICDjqPd81tulZcYrtpTuQ/TweetResultByRestId'
|
||||||
|
|
||||||
def _graphql_to_legacy(self, data, twid):
|
def _graphql_to_legacy(self, data, twid):
|
||||||
result = traverse_obj(data, ('tweetResult', 'result', {dict})) or {}
|
result = traverse_obj(data, (
|
||||||
|
'threaded_conversation_with_injections_v2', 'instructions', 0, 'entries',
|
||||||
|
lambda _, v: v['entryId'] == f'tweet-{twid}', 'content', 'itemContent',
|
||||||
|
'tweet_results', 'result', ('tweet', None), {dict},
|
||||||
|
), default={}, get_all=False) if self.is_logged_in else traverse_obj(
|
||||||
|
data, ('tweetResult', 'result', {dict}), default={})
|
||||||
|
|
||||||
typename = result.get('__typename')
|
typename = result.get('__typename')
|
||||||
if typename not in ('Tweet', 'TweetWithVisibilityResults', 'TweetTombstone', 'TweetUnavailable', None):
|
if typename not in ('Tweet', 'TweetWithVisibilityResults', 'TweetTombstone', 'TweetUnavailable', None):
|
||||||
@ -1268,6 +1278,37 @@ class TwitterIE(TwitterBaseIE):
|
|||||||
|
|
||||||
def _build_graphql_query(self, media_id):
|
def _build_graphql_query(self, media_id):
|
||||||
return {
|
return {
|
||||||
|
'variables': {
|
||||||
|
'focalTweetId': media_id,
|
||||||
|
'includePromotedContent': True,
|
||||||
|
'with_rux_injections': False,
|
||||||
|
'withBirdwatchNotes': True,
|
||||||
|
'withCommunity': True,
|
||||||
|
'withDownvotePerspective': False,
|
||||||
|
'withQuickPromoteEligibilityTweetFields': True,
|
||||||
|
'withReactionsMetadata': False,
|
||||||
|
'withReactionsPerspective': False,
|
||||||
|
'withSuperFollowsTweetFields': True,
|
||||||
|
'withSuperFollowsUserFields': True,
|
||||||
|
'withV2Timeline': True,
|
||||||
|
'withVoice': True,
|
||||||
|
},
|
||||||
|
'features': {
|
||||||
|
'graphql_is_translatable_rweb_tweet_is_translatable_enabled': False,
|
||||||
|
'interactive_text_enabled': True,
|
||||||
|
'responsive_web_edit_tweet_api_enabled': True,
|
||||||
|
'responsive_web_enhance_cards_enabled': True,
|
||||||
|
'responsive_web_graphql_timeline_navigation_enabled': False,
|
||||||
|
'responsive_web_text_conversations_enabled': False,
|
||||||
|
'responsive_web_uc_gql_enabled': True,
|
||||||
|
'standardized_nudges_misinfo': True,
|
||||||
|
'tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled': False,
|
||||||
|
'tweetypie_unmention_optimization_enabled': True,
|
||||||
|
'unified_cards_ad_metadata_container_dynamic_card_content_query_enabled': True,
|
||||||
|
'verified_phone_label_enabled': False,
|
||||||
|
'vibe_api_enabled': True,
|
||||||
|
},
|
||||||
|
} if self.is_logged_in else {
|
||||||
'variables': {
|
'variables': {
|
||||||
'tweetId': media_id,
|
'tweetId': media_id,
|
||||||
'withCommunity': False,
|
'withCommunity': False,
|
||||||
|
|||||||
@ -417,8 +417,6 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
|
|||||||
|
|
||||||
_NETRC_MACHINE = 'youtube'
|
_NETRC_MACHINE = 'youtube'
|
||||||
|
|
||||||
_COOKIE_HOWTO_WIKI_URL = 'https://github.com/yt-dlp/yt-dlp/wiki/Extractors#exporting-youtube-cookies'
|
|
||||||
|
|
||||||
def ucid_or_none(self, ucid):
|
def ucid_or_none(self, ucid):
|
||||||
return self._search_regex(rf'^({self._YT_CHANNEL_UCID_RE})$', ucid, 'UC-id', default=None)
|
return self._search_regex(rf'^({self._YT_CHANNEL_UCID_RE})$', ucid, 'UC-id', default=None)
|
||||||
|
|
||||||
@ -453,15 +451,17 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
|
|||||||
return preferred_lang
|
return preferred_lang
|
||||||
|
|
||||||
def _initialize_consent(self):
|
def _initialize_consent(self):
|
||||||
if self._has_auth_cookies:
|
cookies = self._get_cookies('https://www.youtube.com/')
|
||||||
|
if cookies.get('__Secure-3PSID'):
|
||||||
return
|
return
|
||||||
socs = self._youtube_cookies.get('SOCS')
|
socs = cookies.get('SOCS')
|
||||||
if socs and not socs.value.startswith('CAA'): # not consented
|
if socs and not socs.value.startswith('CAA'): # not consented
|
||||||
return
|
return
|
||||||
self._set_cookie('.youtube.com', 'SOCS', 'CAI', secure=True) # accept all (required for mixes)
|
self._set_cookie('.youtube.com', 'SOCS', 'CAI', secure=True) # accept all (required for mixes)
|
||||||
|
|
||||||
def _initialize_pref(self):
|
def _initialize_pref(self):
|
||||||
pref_cookie = self._youtube_cookies.get('PREF')
|
cookies = self._get_cookies('https://www.youtube.com/')
|
||||||
|
pref_cookie = cookies.get('PREF')
|
||||||
pref = {}
|
pref = {}
|
||||||
if pref_cookie:
|
if pref_cookie:
|
||||||
try:
|
try:
|
||||||
@ -472,9 +472,8 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
|
|||||||
self._set_cookie('.youtube.com', name='PREF', value=urllib.parse.urlencode(pref))
|
self._set_cookie('.youtube.com', name='PREF', value=urllib.parse.urlencode(pref))
|
||||||
|
|
||||||
def _initialize_cookie_auth(self):
|
def _initialize_cookie_auth(self):
|
||||||
self._passed_auth_cookies = False
|
yt_sapisid, yt_1psapisid, yt_3psapisid = self._get_sid_cookies()
|
||||||
if self._has_auth_cookies:
|
if yt_sapisid or yt_1psapisid or yt_3psapisid:
|
||||||
self._passed_auth_cookies = True
|
|
||||||
self.write_debug('Found YouTube account cookies')
|
self.write_debug('Found YouTube account cookies')
|
||||||
|
|
||||||
def _real_initialize(self):
|
def _real_initialize(self):
|
||||||
@ -493,7 +492,8 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def _youtube_login_hint(self):
|
def _youtube_login_hint(self):
|
||||||
return (f'{self._login_hint(method="cookies")}. Also see {self._COOKIE_HOWTO_WIKI_URL} '
|
return (f'{self._login_hint(method="cookies")}. Also see '
|
||||||
|
'https://github.com/yt-dlp/yt-dlp/wiki/Extractors#exporting-youtube-cookies '
|
||||||
'for tips on effectively exporting YouTube cookies')
|
'for tips on effectively exporting YouTube cookies')
|
||||||
|
|
||||||
def _check_login_required(self):
|
def _check_login_required(self):
|
||||||
@ -553,16 +553,12 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
|
|||||||
|
|
||||||
return f'{scheme} {"_".join(parts)}'
|
return f'{scheme} {"_".join(parts)}'
|
||||||
|
|
||||||
@property
|
|
||||||
def _youtube_cookies(self):
|
|
||||||
return self._get_cookies('https://www.youtube.com')
|
|
||||||
|
|
||||||
def _get_sid_cookies(self):
|
def _get_sid_cookies(self):
|
||||||
"""
|
"""
|
||||||
Get SAPISID, 1PSAPISID, 3PSAPISID cookie values
|
Get SAPISID, 1PSAPISID, 3PSAPISID cookie values
|
||||||
@returns sapisid, 1psapisid, 3psapisid
|
@returns sapisid, 1psapisid, 3psapisid
|
||||||
"""
|
"""
|
||||||
yt_cookies = self._youtube_cookies
|
yt_cookies = self._get_cookies('https://www.youtube.com')
|
||||||
yt_sapisid = try_call(lambda: yt_cookies['SAPISID'].value)
|
yt_sapisid = try_call(lambda: yt_cookies['SAPISID'].value)
|
||||||
yt_3papisid = try_call(lambda: yt_cookies['__Secure-3PAPISID'].value)
|
yt_3papisid = try_call(lambda: yt_cookies['__Secure-3PAPISID'].value)
|
||||||
yt_1papisid = try_call(lambda: yt_cookies['__Secure-1PAPISID'].value)
|
yt_1papisid = try_call(lambda: yt_cookies['__Secure-1PAPISID'].value)
|
||||||
@ -599,31 +595,6 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
|
|||||||
|
|
||||||
return ' '.join(authorizations)
|
return ' '.join(authorizations)
|
||||||
|
|
||||||
@property
|
|
||||||
def is_authenticated(self):
|
|
||||||
return self._has_auth_cookies
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _has_auth_cookies(self):
|
|
||||||
yt_sapisid, yt_1psapisid, yt_3psapisid = self._get_sid_cookies()
|
|
||||||
# YouTube doesn't appear to clear 3PSAPISID when rotating cookies (as of 2025-04-26)
|
|
||||||
# But LOGIN_INFO is cleared and should exist if logged in
|
|
||||||
has_login_info = 'LOGIN_INFO' in self._youtube_cookies
|
|
||||||
return bool(has_login_info and (yt_sapisid or yt_1psapisid or yt_3psapisid))
|
|
||||||
|
|
||||||
def _request_webpage(self, *args, **kwargs):
|
|
||||||
response = super()._request_webpage(*args, **kwargs)
|
|
||||||
|
|
||||||
# Check that we are still logged-in and cookies have not rotated after every request
|
|
||||||
if getattr(self, '_passed_auth_cookies', None) and not self._has_auth_cookies:
|
|
||||||
self.report_warning(
|
|
||||||
'The provided YouTube account cookies are no longer valid. '
|
|
||||||
'They have likely been rotated in the browser as a security measure. '
|
|
||||||
f'For tips on how to effectively export YouTube cookies, refer to {self._COOKIE_HOWTO_WIKI_URL} .',
|
|
||||||
only_once=False)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
def _call_api(self, ep, query, video_id, fatal=True, headers=None,
|
def _call_api(self, ep, query, video_id, fatal=True, headers=None,
|
||||||
note='Downloading API JSON', errnote='Unable to download API page',
|
note='Downloading API JSON', errnote='Unable to download API page',
|
||||||
context=None, api_key=None, api_hostname=None, default_client='web'):
|
context=None, api_key=None, api_hostname=None, default_client='web'):
|
||||||
@ -724,6 +695,10 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
|
|||||||
args, [('VISITOR_DATA', ('INNERTUBE_CONTEXT', 'client', 'visitorData'), ('responseContext', 'visitorData'))],
|
args, [('VISITOR_DATA', ('INNERTUBE_CONTEXT', 'client', 'visitorData'), ('responseContext', 'visitorData'))],
|
||||||
expected_type=str)
|
expected_type=str)
|
||||||
|
|
||||||
|
@functools.cached_property
|
||||||
|
def is_authenticated(self):
|
||||||
|
return bool(self._get_sid_authorization_header())
|
||||||
|
|
||||||
def extract_ytcfg(self, video_id, webpage):
|
def extract_ytcfg(self, video_id, webpage):
|
||||||
if not webpage:
|
if not webpage:
|
||||||
return {}
|
return {}
|
||||||
|
|||||||
@ -1982,9 +1982,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|||||||
def _player_js_cache_key(self, player_url):
|
def _player_js_cache_key(self, player_url):
|
||||||
player_id = self._extract_player_info(player_url)
|
player_id = self._extract_player_info(player_url)
|
||||||
player_path = remove_start(urllib.parse.urlparse(player_url).path, f'/s/player/{player_id}/')
|
player_path = remove_start(urllib.parse.urlparse(player_url).path, f'/s/player/{player_id}/')
|
||||||
variant = self._INVERSE_PLAYER_JS_VARIANT_MAP.get(player_path) or next((
|
variant = self._INVERSE_PLAYER_JS_VARIANT_MAP.get(player_path)
|
||||||
v for k, v in self._INVERSE_PLAYER_JS_VARIANT_MAP.items()
|
|
||||||
if re.fullmatch(re.escape(k).replace('en_US', r'[a-zA-Z0-9_]+'), player_path)), None)
|
|
||||||
if not variant:
|
if not variant:
|
||||||
self.write_debug(
|
self.write_debug(
|
||||||
f'Unable to determine player JS variant\n'
|
f'Unable to determine player JS variant\n'
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user