mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2025-05-01 00:08:47 +02:00
Compare commits
3 Commits
26feac3dd1
...
8cb08028f5
Author | SHA1 | Date | |
---|---|---|---|
|
8cb08028f5 | ||
|
1cf39ddf3d | ||
|
c2d6659d10 |
@ -1221,20 +1221,10 @@ 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, (
|
result = traverse_obj(data, ('tweetResult', 'result', {dict})) or {}
|
||||||
'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):
|
||||||
@ -1278,37 +1268,6 @@ 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,6 +417,8 @@ 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)
|
||||||
|
|
||||||
@ -451,17 +453,15 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
|
|||||||
return preferred_lang
|
return preferred_lang
|
||||||
|
|
||||||
def _initialize_consent(self):
|
def _initialize_consent(self):
|
||||||
cookies = self._get_cookies('https://www.youtube.com/')
|
if self._has_auth_cookies:
|
||||||
if cookies.get('__Secure-3PSID'):
|
|
||||||
return
|
return
|
||||||
socs = cookies.get('SOCS')
|
socs = self._youtube_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):
|
||||||
cookies = self._get_cookies('https://www.youtube.com/')
|
pref_cookie = self._youtube_cookies.get('PREF')
|
||||||
pref_cookie = cookies.get('PREF')
|
|
||||||
pref = {}
|
pref = {}
|
||||||
if pref_cookie:
|
if pref_cookie:
|
||||||
try:
|
try:
|
||||||
@ -472,8 +472,9 @@ 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):
|
||||||
yt_sapisid, yt_1psapisid, yt_3psapisid = self._get_sid_cookies()
|
self._passed_auth_cookies = False
|
||||||
if yt_sapisid or yt_1psapisid or yt_3psapisid:
|
if self._has_auth_cookies:
|
||||||
|
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):
|
||||||
@ -492,8 +493,7 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def _youtube_login_hint(self):
|
def _youtube_login_hint(self):
|
||||||
return (f'{self._login_hint(method="cookies")}. Also see '
|
return (f'{self._login_hint(method="cookies")}. Also see {self._COOKIE_HOWTO_WIKI_URL} '
|
||||||
'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,12 +553,16 @@ 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._get_cookies('https://www.youtube.com')
|
yt_cookies = self._youtube_cookies
|
||||||
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)
|
||||||
@ -595,6 +599,31 @@ 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'):
|
||||||
@ -695,10 +724,6 @@ 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,7 +1982,9 @@ 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)
|
variant = self._INVERSE_PLAYER_JS_VARIANT_MAP.get(player_path) or next((
|
||||||
|
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