From 0242090a863750e229eaf8114c1af7a12a3f53e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20De=20Boey?= Date: Thu, 6 Mar 2025 15:38:53 +0100 Subject: [PATCH 01/12] refactor: extract common `_is_jwt_token_expired` to `InfoExtractor` --- yt_dlp/extractor/cbc.py | 8 ++------ yt_dlp/extractor/common.py | 5 +++++ yt_dlp/extractor/digitalconcerthall.py | 6 +----- yt_dlp/extractor/iwara.py | 5 +---- yt_dlp/extractor/jiocinema.py | 12 ++++-------- yt_dlp/extractor/mlb.py | 6 +----- yt_dlp/extractor/qdance.py | 4 +--- yt_dlp/extractor/stacommu.py | 4 +--- yt_dlp/extractor/vrt.py | 5 ----- yt_dlp/extractor/wrestleuniverse.py | 12 ++---------- yt_dlp/extractor/zee5.py | 6 ++---- 11 files changed, 20 insertions(+), 53 deletions(-) diff --git a/yt_dlp/extractor/cbc.py b/yt_dlp/extractor/cbc.py index 319771655e..527bfe03dd 100644 --- a/yt_dlp/extractor/cbc.py +++ b/yt_dlp/extractor/cbc.py @@ -11,7 +11,6 @@ float_or_none, int_or_none, js_to_json, - jwt_decode_hs256, mimetype2ext, orderedSet, parse_age_limit, @@ -620,9 +619,6 @@ def _ropc_settings(self): 'https://services.radio-canada.ca/ott/catalog/v1/gem/settings', None, 'Downloading site settings', query={'device': 'web'})['identityManagement']['ropc'] - def _is_jwt_expired(self, token): - return jwt_decode_hs256(token)['exp'] - time.time() < 300 - def _call_oauth_api(self, oauth_data, note='Refreshing access token'): response = self._download_json( self._ropc_settings['url'], None, note, data=urlencode_postdata({ @@ -657,7 +653,7 @@ def _perform_login(self, username, password): raise def _fetch_access_token(self): - if self._is_jwt_expired(self._access_token): + if self._is_jwt_token_expired(self._access_token): try: self._call_oauth_api({ 'grant_type': 'refresh_token', @@ -675,7 +671,7 @@ def _fetch_claims_token(self): if not self._get_login_info()[0]: return None - if not self._claims_token or self._is_jwt_expired(self._claims_token): + if not self._claims_token or self._is_jwt_token_expired(self._claims_token): self._claims_token = self._download_json( 'https://services.radio-canada.ca/ott/subscription/v2/gem/Subscriber/profile', None, 'Downloading claims token', query={'device': 'web'}, diff --git a/yt_dlp/extractor/common.py b/yt_dlp/extractor/common.py index b816d788fa..3ae318c18a 100644 --- a/yt_dlp/extractor/common.py +++ b/yt_dlp/extractor/common.py @@ -69,6 +69,7 @@ int_or_none, join_nonempty, js_to_json, + jwt_decode_hs256, mimetype2ext, netrc_from_content, orderedSet, @@ -994,6 +995,10 @@ def _guess_encoding_from_content(content_type, webpage_bytes): return encoding + @staticmethod + def _is_jwt_token_expired(token): + return jwt_decode_hs256(token)['exp'] - time.time() < 300 + def __check_blocked(self, content): first_block = content[:512] if ('Access to this site is blocked' in content diff --git a/yt_dlp/extractor/digitalconcerthall.py b/yt_dlp/extractor/digitalconcerthall.py index 4c4fe470da..bc876ad5d1 100644 --- a/yt_dlp/extractor/digitalconcerthall.py +++ b/yt_dlp/extractor/digitalconcerthall.py @@ -1,5 +1,3 @@ -import time - from .common import InfoExtractor from ..networking.exceptions import HTTPError from ..utils import ( @@ -84,16 +82,14 @@ class DigitalConcertHallIE(InfoExtractor): 'User-Agent': _USER_AGENT, } _access_token = None - _access_token_expiry = 0 _refresh_token = None @property def _access_token_is_expired(self): - return self._access_token_expiry - 30 <= int(time.time()) + return self._is_jwt_token_expired(self._access_token) def _set_access_token(self, value): self._access_token = value - self._access_token_expiry = traverse_obj(value, ({jwt_decode_hs256}, 'exp', {int})) or 0 def _cache_tokens(self, /): self.cache.store(self._NETRC_MACHINE, 'tokens', { diff --git a/yt_dlp/extractor/iwara.py b/yt_dlp/extractor/iwara.py index 5b5c367ad8..882789cf4e 100644 --- a/yt_dlp/extractor/iwara.py +++ b/yt_dlp/extractor/iwara.py @@ -1,7 +1,6 @@ import functools import hashlib import json -import time import urllib.parse from .common import InfoExtractor @@ -9,11 +8,9 @@ ExtractorError, OnDemandPagedList, int_or_none, - jwt_decode_hs256, mimetype2ext, qualities, traverse_obj, - try_call, unified_timestamp, ) @@ -25,7 +22,7 @@ class IwaraBaseIE(InfoExtractor): def _is_token_expired(self, token, token_type): # User token TTL == ~3 weeks, Media token TTL == ~1 hour - if (try_call(lambda: jwt_decode_hs256(token)['exp']) or 0) <= int(time.time() - 120): + if self._is_jwt_token_expired(token): self.to_screen(f'{token_type} token has expired') return True diff --git a/yt_dlp/extractor/jiocinema.py b/yt_dlp/extractor/jiocinema.py index 94c85064ef..a4db9d47c9 100644 --- a/yt_dlp/extractor/jiocinema.py +++ b/yt_dlp/extractor/jiocinema.py @@ -4,7 +4,6 @@ import random import re import string -import time from .common import InfoExtractor from ..utils import ( @@ -106,11 +105,8 @@ def _call_login_api(self, endpoint, guest_token, data, note): 'os': ('os', {str}), })}, data=data) - def _is_token_expired(self, token): - return (try_call(lambda: jwt_decode_hs256(token)['exp']) or 0) <= int(time.time() - 180) - def _perform_login(self, username, password): - if self._ACCESS_TOKEN and not self._is_token_expired(self._ACCESS_TOKEN): + if self._ACCESS_TOKEN and not self._is_jwt_token_expired(self._ACCESS_TOKEN): return UUID_RE = r'[\da-f]{8}-(?:[\da-f]{4}-){3}[\da-f]{12}' @@ -185,7 +181,7 @@ def _perform_login(self, username, password): if JioCinemaBaseIE._REFRESH_TOKEN: self._cache_token('access') self.to_screen(f'Logging in as device ID "{JioCinemaBaseIE._DEVICE_ID}"') - if self._is_token_expired(JioCinemaBaseIE._ACCESS_TOKEN): + if self._is_jwt_token_expired(JioCinemaBaseIE._ACCESS_TOKEN): self._refresh_token() @@ -250,9 +246,9 @@ def _extract_formats_and_subtitles(self, playback, video_id): def _real_extract(self, url): video_id = self._match_id(url) - if not self._ACCESS_TOKEN and self._is_token_expired(self._GUEST_TOKEN): + if not self._ACCESS_TOKEN and self._is_jwt_token_expired(self._GUEST_TOKEN): self._fetch_guest_token() - elif self._ACCESS_TOKEN and self._is_token_expired(self._ACCESS_TOKEN): + elif self._ACCESS_TOKEN and self._is_jwt_token_expired(self._ACCESS_TOKEN): self._refresh_token() playback = self._call_api( diff --git a/yt_dlp/extractor/mlb.py b/yt_dlp/extractor/mlb.py index 935bf85615..61b56817ea 100644 --- a/yt_dlp/extractor/mlb.py +++ b/yt_dlp/extractor/mlb.py @@ -1,6 +1,5 @@ import json import re -import time import uuid from .common import InfoExtractor @@ -10,7 +9,6 @@ determine_ext, int_or_none, join_nonempty, - jwt_decode_hs256, parse_duration, parse_iso8601, try_get, @@ -350,11 +348,10 @@ class MLBTVIE(InfoExtractor): _device_id = None _session_id = None _access_token = None - _token_expiry = 0 @property def _api_headers(self): - if (self._token_expiry - 120) <= time.time(): + if self._is_jwt_token_expired(self._access_token): self.write_debug('Access token has expired; re-logging in') self._perform_login(*self._get_login_info()) return {'Authorization': f'Bearer {self._access_token}'} @@ -392,7 +389,6 @@ def _perform_login(self, username, password): raise ExtractorError('Invalid username or password', expected=True) raise - self._token_expiry = traverse_obj(self._access_token, ({jwt_decode_hs256}, 'exp', {int})) or 0 self._set_device_id(username) self._session_id = self._call_api({ diff --git a/yt_dlp/extractor/qdance.py b/yt_dlp/extractor/qdance.py index 4f71657c3f..680da2cf52 100644 --- a/yt_dlp/extractor/qdance.py +++ b/yt_dlp/extractor/qdance.py @@ -1,11 +1,9 @@ import json -import time from .common import InfoExtractor from ..utils import ( ExtractorError, int_or_none, - jwt_decode_hs256, str_or_none, traverse_obj, try_call, @@ -116,7 +114,7 @@ def _real_initialize(self): self.raise_login_required() def _get_auth(self): - if (try_call(lambda: jwt_decode_hs256(self._access_token)['exp']) or 0) <= int(time.time() - 120): + if self._is_jwt_token_expired(self._access_token): if not self._refresh_token: raise ExtractorError( 'Cannot refresh access token, login with yt-dlp or refresh cookies in browser') diff --git a/yt_dlp/extractor/stacommu.py b/yt_dlp/extractor/stacommu.py index 8300185183..26710a140b 100644 --- a/yt_dlp/extractor/stacommu.py +++ b/yt_dlp/extractor/stacommu.py @@ -1,5 +1,3 @@ -import time - from .wrestleuniverse import WrestleUniverseBaseIE from ..utils import ( int_or_none, @@ -22,7 +20,7 @@ class StacommuBaseIE(WrestleUniverseBaseIE): @WrestleUniverseBaseIE._TOKEN.getter def _TOKEN(self): - if self._REAL_TOKEN and self._TOKEN_EXPIRY <= int(time.time()): + if self._REAL_TOKEN and self._is_jwt_token_expired(self._REAL_TOKEN): self._refresh_token() return self._REAL_TOKEN diff --git a/yt_dlp/extractor/vrt.py b/yt_dlp/extractor/vrt.py index 5def2bacf4..5c945d2065 100644 --- a/yt_dlp/extractor/vrt.py +++ b/yt_dlp/extractor/vrt.py @@ -13,7 +13,6 @@ get_element_by_class, get_element_html_by_class, int_or_none, - jwt_decode_hs256, jwt_encode_hs256, make_archive_id, merge_dicts, @@ -316,10 +315,6 @@ def _get_vrt_cookie(self, cookie_name): # Refresh token cookie is scoped to /vrtmax/sso, others are scoped to / return try_call(lambda: self._get_cookies('https://www.vrt.be/vrtmax/sso')[cookie_name].value) - @staticmethod - def _is_jwt_token_expired(token): - return jwt_decode_hs256(token)['exp'] - time.time() < 300 - def _perform_login(self, username, password): refresh_token = self._get_vrt_cookie(self._REFRESH_TOKEN_COOKIE_NAME) if refresh_token and not self._is_jwt_token_expired(refresh_token): diff --git a/yt_dlp/extractor/wrestleuniverse.py b/yt_dlp/extractor/wrestleuniverse.py index d401d6d39d..3a9293f44f 100644 --- a/yt_dlp/extractor/wrestleuniverse.py +++ b/yt_dlp/extractor/wrestleuniverse.py @@ -1,7 +1,6 @@ import base64 import binascii import json -import time import uuid from .common import InfoExtractor @@ -9,7 +8,6 @@ from ..utils import ( ExtractorError, int_or_none, - jwt_decode_hs256, traverse_obj, try_call, url_basename, @@ -25,7 +23,6 @@ class WrestleUniverseBaseIE(InfoExtractor): _API_HOST = 'api.wrestle-universe.com' _API_PATH = None _REAL_TOKEN = None - _TOKEN_EXPIRY = None _REFRESH_TOKEN = None _DEVICE_ID = None _LOGIN_QUERY = {'key': 'AIzaSyCaRPBsDQYVDUWWBXjsTrHESi2r_F3RAdA'} @@ -40,13 +37,13 @@ class WrestleUniverseBaseIE(InfoExtractor): @property def _TOKEN(self): - if not self._REAL_TOKEN or not self._TOKEN_EXPIRY: + if not self._REAL_TOKEN: token = try_call(lambda: self._get_cookies('https://www.wrestle-universe.com/')['token'].value) if not token and not self._REFRESH_TOKEN: self.raise_login_required() self._TOKEN = token - if not self._REAL_TOKEN or self._TOKEN_EXPIRY <= int(time.time()): + if not self._REAL_TOKEN or self._is_jwt_token_expired(self._REAL_TOKEN): if not self._REFRESH_TOKEN: raise ExtractorError( 'Expired token. Refresh your cookies in browser and try again', expected=True) @@ -58,11 +55,6 @@ def _TOKEN(self): def _TOKEN(self, value): self._REAL_TOKEN = value - expiry = traverse_obj(value, ({jwt_decode_hs256}, 'exp', {int_or_none})) - if not expiry: - raise ExtractorError('There was a problem with the auth token') - self._TOKEN_EXPIRY = expiry - def _perform_login(self, username, password): login = self._download_json( 'https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword', None, diff --git a/yt_dlp/extractor/zee5.py b/yt_dlp/extractor/zee5.py index fb523de03b..dc975d05a4 100644 --- a/yt_dlp/extractor/zee5.py +++ b/yt_dlp/extractor/zee5.py @@ -1,5 +1,4 @@ import json -import time import uuid from .common import InfoExtractor @@ -124,10 +123,9 @@ def _perform_login(self, username, password): else: raise ExtractorError(self._LOGIN_HINT, expected=True) - token = jwt_decode_hs256(self._USER_TOKEN) - if token.get('exp', 0) <= int(time.time()): + if self._is_jwt_token_expired(self._USER_TOKEN): raise ExtractorError('User token has expired', expected=True) - self._USER_COUNTRY = token.get('current_country') + self._USER_COUNTRY = jwt_decode_hs256(self._USER_TOKEN).get('current_country') def _real_extract(self, url): video_id, display_id = self._match_valid_url(url).group('id', 'display_id') From 5ffedda32707a4255d2721a68e07b27dc99e9064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20De=20Boey?= Date: Sun, 16 Mar 2025 23:15:55 +0100 Subject: [PATCH 02/12] rename `_is_jwt_token_expired` to `_jwt_is_expired` --- yt_dlp/extractor/cbc.py | 4 ++-- yt_dlp/extractor/common.py | 2 +- yt_dlp/extractor/digitalconcerthall.py | 2 +- yt_dlp/extractor/iwara.py | 2 +- yt_dlp/extractor/jiocinema.py | 8 ++++---- yt_dlp/extractor/mlb.py | 2 +- yt_dlp/extractor/qdance.py | 2 +- yt_dlp/extractor/stacommu.py | 2 +- yt_dlp/extractor/vrt.py | 12 ++++++------ yt_dlp/extractor/wrestleuniverse.py | 2 +- yt_dlp/extractor/zee5.py | 2 +- 11 files changed, 20 insertions(+), 20 deletions(-) diff --git a/yt_dlp/extractor/cbc.py b/yt_dlp/extractor/cbc.py index 527bfe03dd..d7cb3ebfb6 100644 --- a/yt_dlp/extractor/cbc.py +++ b/yt_dlp/extractor/cbc.py @@ -653,7 +653,7 @@ def _perform_login(self, username, password): raise def _fetch_access_token(self): - if self._is_jwt_token_expired(self._access_token): + if self._jwt_is_expired(self._access_token): try: self._call_oauth_api({ 'grant_type': 'refresh_token', @@ -671,7 +671,7 @@ def _fetch_claims_token(self): if not self._get_login_info()[0]: return None - if not self._claims_token or self._is_jwt_token_expired(self._claims_token): + if not self._claims_token or self._jwt_is_expired(self._claims_token): self._claims_token = self._download_json( 'https://services.radio-canada.ca/ott/subscription/v2/gem/Subscriber/profile', None, 'Downloading claims token', query={'device': 'web'}, diff --git a/yt_dlp/extractor/common.py b/yt_dlp/extractor/common.py index 3ae318c18a..84bdea80bf 100644 --- a/yt_dlp/extractor/common.py +++ b/yt_dlp/extractor/common.py @@ -996,7 +996,7 @@ def _guess_encoding_from_content(content_type, webpage_bytes): return encoding @staticmethod - def _is_jwt_token_expired(token): + def _jwt_is_expired(token): return jwt_decode_hs256(token)['exp'] - time.time() < 300 def __check_blocked(self, content): diff --git a/yt_dlp/extractor/digitalconcerthall.py b/yt_dlp/extractor/digitalconcerthall.py index bc876ad5d1..a19b96a66d 100644 --- a/yt_dlp/extractor/digitalconcerthall.py +++ b/yt_dlp/extractor/digitalconcerthall.py @@ -86,7 +86,7 @@ class DigitalConcertHallIE(InfoExtractor): @property def _access_token_is_expired(self): - return self._is_jwt_token_expired(self._access_token) + return self._jwt_is_expired(self._access_token) def _set_access_token(self, value): self._access_token = value diff --git a/yt_dlp/extractor/iwara.py b/yt_dlp/extractor/iwara.py index 882789cf4e..32c5aea891 100644 --- a/yt_dlp/extractor/iwara.py +++ b/yt_dlp/extractor/iwara.py @@ -22,7 +22,7 @@ class IwaraBaseIE(InfoExtractor): def _is_token_expired(self, token, token_type): # User token TTL == ~3 weeks, Media token TTL == ~1 hour - if self._is_jwt_token_expired(token): + if self._jwt_is_expired(token): self.to_screen(f'{token_type} token has expired') return True diff --git a/yt_dlp/extractor/jiocinema.py b/yt_dlp/extractor/jiocinema.py index a4db9d47c9..3d9957f5f5 100644 --- a/yt_dlp/extractor/jiocinema.py +++ b/yt_dlp/extractor/jiocinema.py @@ -106,7 +106,7 @@ def _call_login_api(self, endpoint, guest_token, data, note): })}, data=data) def _perform_login(self, username, password): - if self._ACCESS_TOKEN and not self._is_jwt_token_expired(self._ACCESS_TOKEN): + if self._ACCESS_TOKEN and not self._jwt_is_expired(self._ACCESS_TOKEN): return UUID_RE = r'[\da-f]{8}-(?:[\da-f]{4}-){3}[\da-f]{12}' @@ -181,7 +181,7 @@ def _perform_login(self, username, password): if JioCinemaBaseIE._REFRESH_TOKEN: self._cache_token('access') self.to_screen(f'Logging in as device ID "{JioCinemaBaseIE._DEVICE_ID}"') - if self._is_jwt_token_expired(JioCinemaBaseIE._ACCESS_TOKEN): + if self._jwt_is_expired(JioCinemaBaseIE._ACCESS_TOKEN): self._refresh_token() @@ -246,9 +246,9 @@ def _extract_formats_and_subtitles(self, playback, video_id): def _real_extract(self, url): video_id = self._match_id(url) - if not self._ACCESS_TOKEN and self._is_jwt_token_expired(self._GUEST_TOKEN): + if not self._ACCESS_TOKEN and self._jwt_is_expired(self._GUEST_TOKEN): self._fetch_guest_token() - elif self._ACCESS_TOKEN and self._is_jwt_token_expired(self._ACCESS_TOKEN): + elif self._ACCESS_TOKEN and self._jwt_is_expired(self._ACCESS_TOKEN): self._refresh_token() playback = self._call_api( diff --git a/yt_dlp/extractor/mlb.py b/yt_dlp/extractor/mlb.py index 61b56817ea..c8f4fcf7e8 100644 --- a/yt_dlp/extractor/mlb.py +++ b/yt_dlp/extractor/mlb.py @@ -351,7 +351,7 @@ class MLBTVIE(InfoExtractor): @property def _api_headers(self): - if self._is_jwt_token_expired(self._access_token): + if self._jwt_is_expired(self._access_token): self.write_debug('Access token has expired; re-logging in') self._perform_login(*self._get_login_info()) return {'Authorization': f'Bearer {self._access_token}'} diff --git a/yt_dlp/extractor/qdance.py b/yt_dlp/extractor/qdance.py index 680da2cf52..3faab0465e 100644 --- a/yt_dlp/extractor/qdance.py +++ b/yt_dlp/extractor/qdance.py @@ -114,7 +114,7 @@ def _real_initialize(self): self.raise_login_required() def _get_auth(self): - if self._is_jwt_token_expired(self._access_token): + if self._jwt_is_expired(self._access_token): if not self._refresh_token: raise ExtractorError( 'Cannot refresh access token, login with yt-dlp or refresh cookies in browser') diff --git a/yt_dlp/extractor/stacommu.py b/yt_dlp/extractor/stacommu.py index 26710a140b..c9489763ea 100644 --- a/yt_dlp/extractor/stacommu.py +++ b/yt_dlp/extractor/stacommu.py @@ -20,7 +20,7 @@ class StacommuBaseIE(WrestleUniverseBaseIE): @WrestleUniverseBaseIE._TOKEN.getter def _TOKEN(self): - if self._REAL_TOKEN and self._is_jwt_token_expired(self._REAL_TOKEN): + if self._REAL_TOKEN and self._jwt_is_expired(self._REAL_TOKEN): self._refresh_token() return self._REAL_TOKEN diff --git a/yt_dlp/extractor/vrt.py b/yt_dlp/extractor/vrt.py index 5c945d2065..4b9fe39445 100644 --- a/yt_dlp/extractor/vrt.py +++ b/yt_dlp/extractor/vrt.py @@ -272,15 +272,15 @@ def _fetch_tokens(self): access_token = self._get_vrt_cookie(self._ACCESS_TOKEN_COOKIE_NAME) video_token = self._get_vrt_cookie(self._VIDEO_TOKEN_COOKIE_NAME) - if (access_token and not self._is_jwt_token_expired(access_token) - and video_token and not self._is_jwt_token_expired(video_token)): + if (access_token and not self._jwt_is_expired(access_token) + and video_token and not self._jwt_is_expired(video_token)): return access_token, video_token if has_credentials: access_token, video_token = self.cache.load(self._NETRC_MACHINE, 'token_data', default=(None, None)) - if (access_token and not self._is_jwt_token_expired(access_token) - and video_token and not self._is_jwt_token_expired(video_token)): + if (access_token and not self._jwt_is_expired(access_token) + and video_token and not self._jwt_is_expired(video_token)): self.write_debug('Restored tokens from cache') self._set_cookie(self._TOKEN_COOKIE_DOMAIN, self._ACCESS_TOKEN_COOKIE_NAME, access_token) self._set_cookie(self._TOKEN_COOKIE_DOMAIN, self._VIDEO_TOKEN_COOKIE_NAME, video_token) @@ -317,12 +317,12 @@ def _get_vrt_cookie(self, cookie_name): def _perform_login(self, username, password): refresh_token = self._get_vrt_cookie(self._REFRESH_TOKEN_COOKIE_NAME) - if refresh_token and not self._is_jwt_token_expired(refresh_token): + if refresh_token and not self._jwt_is_expired(refresh_token): self.write_debug('Using refresh token from logged-in cookies; skipping login with credentials') return refresh_token = self.cache.load(self._NETRC_MACHINE, 'refresh_token', default=None) - if refresh_token and not self._is_jwt_token_expired(refresh_token): + if refresh_token and not self._jwt_is_expired(refresh_token): self.write_debug('Restored refresh token from cache') self._set_cookie(self._TOKEN_COOKIE_DOMAIN, self._REFRESH_TOKEN_COOKIE_NAME, refresh_token, path='/vrtmax/sso') return diff --git a/yt_dlp/extractor/wrestleuniverse.py b/yt_dlp/extractor/wrestleuniverse.py index 3a9293f44f..47193a2e86 100644 --- a/yt_dlp/extractor/wrestleuniverse.py +++ b/yt_dlp/extractor/wrestleuniverse.py @@ -43,7 +43,7 @@ def _TOKEN(self): self.raise_login_required() self._TOKEN = token - if not self._REAL_TOKEN or self._is_jwt_token_expired(self._REAL_TOKEN): + if not self._REAL_TOKEN or self._jwt_is_expired(self._REAL_TOKEN): if not self._REFRESH_TOKEN: raise ExtractorError( 'Expired token. Refresh your cookies in browser and try again', expected=True) diff --git a/yt_dlp/extractor/zee5.py b/yt_dlp/extractor/zee5.py index dc975d05a4..02cd2a77b8 100644 --- a/yt_dlp/extractor/zee5.py +++ b/yt_dlp/extractor/zee5.py @@ -123,7 +123,7 @@ def _perform_login(self, username, password): else: raise ExtractorError(self._LOGIN_HINT, expected=True) - if self._is_jwt_token_expired(self._USER_TOKEN): + if self._jwt_is_expired(self._USER_TOKEN): raise ExtractorError('User token has expired', expected=True) self._USER_COUNTRY = jwt_decode_hs256(self._USER_TOKEN).get('current_country') From 98a68be9b8c79212ece7641c106f04d3d142f92f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20De=20Boey?= Date: Sun, 16 Mar 2025 23:26:14 +0100 Subject: [PATCH 03/12] add `buffer` argument to `_jwt_is_expired` --- yt_dlp/extractor/common.py | 4 ++-- yt_dlp/extractor/digitalconcerthall.py | 2 +- yt_dlp/extractor/iwara.py | 2 +- yt_dlp/extractor/jiocinema.py | 11 +++++++---- yt_dlp/extractor/mlb.py | 2 +- yt_dlp/extractor/qdance.py | 2 +- 6 files changed, 13 insertions(+), 10 deletions(-) diff --git a/yt_dlp/extractor/common.py b/yt_dlp/extractor/common.py index 84bdea80bf..306ad737f8 100644 --- a/yt_dlp/extractor/common.py +++ b/yt_dlp/extractor/common.py @@ -996,8 +996,8 @@ def _guess_encoding_from_content(content_type, webpage_bytes): return encoding @staticmethod - def _jwt_is_expired(token): - return jwt_decode_hs256(token)['exp'] - time.time() < 300 + def _jwt_is_expired(token, buffer=300): + return jwt_decode_hs256(token)['exp'] - time.time() < buffer def __check_blocked(self, content): first_block = content[:512] diff --git a/yt_dlp/extractor/digitalconcerthall.py b/yt_dlp/extractor/digitalconcerthall.py index a19b96a66d..1c0ff4a7d6 100644 --- a/yt_dlp/extractor/digitalconcerthall.py +++ b/yt_dlp/extractor/digitalconcerthall.py @@ -86,7 +86,7 @@ class DigitalConcertHallIE(InfoExtractor): @property def _access_token_is_expired(self): - return self._jwt_is_expired(self._access_token) + return self._jwt_is_expired(self._access_token, 30) def _set_access_token(self, value): self._access_token = value diff --git a/yt_dlp/extractor/iwara.py b/yt_dlp/extractor/iwara.py index 32c5aea891..f79f835767 100644 --- a/yt_dlp/extractor/iwara.py +++ b/yt_dlp/extractor/iwara.py @@ -22,7 +22,7 @@ class IwaraBaseIE(InfoExtractor): def _is_token_expired(self, token, token_type): # User token TTL == ~3 weeks, Media token TTL == ~1 hour - if self._jwt_is_expired(token): + if self._jwt_is_expired(token, 120): self.to_screen(f'{token_type} token has expired') return True diff --git a/yt_dlp/extractor/jiocinema.py b/yt_dlp/extractor/jiocinema.py index 3d9957f5f5..e8858695ea 100644 --- a/yt_dlp/extractor/jiocinema.py +++ b/yt_dlp/extractor/jiocinema.py @@ -105,8 +105,11 @@ def _call_login_api(self, endpoint, guest_token, data, note): 'os': ('os', {str}), })}, data=data) + def _is_token_expired(self, token): + return self._jwt_is_expired(token, 180) + def _perform_login(self, username, password): - if self._ACCESS_TOKEN and not self._jwt_is_expired(self._ACCESS_TOKEN): + if self._ACCESS_TOKEN and not self._is_token_expired(self._ACCESS_TOKEN): return UUID_RE = r'[\da-f]{8}-(?:[\da-f]{4}-){3}[\da-f]{12}' @@ -181,7 +184,7 @@ def _perform_login(self, username, password): if JioCinemaBaseIE._REFRESH_TOKEN: self._cache_token('access') self.to_screen(f'Logging in as device ID "{JioCinemaBaseIE._DEVICE_ID}"') - if self._jwt_is_expired(JioCinemaBaseIE._ACCESS_TOKEN): + if self._is_token_expired(JioCinemaBaseIE._ACCESS_TOKEN): self._refresh_token() @@ -246,9 +249,9 @@ def _extract_formats_and_subtitles(self, playback, video_id): def _real_extract(self, url): video_id = self._match_id(url) - if not self._ACCESS_TOKEN and self._jwt_is_expired(self._GUEST_TOKEN): + if not self._ACCESS_TOKEN and self._is_token_expired(self._GUEST_TOKEN): self._fetch_guest_token() - elif self._ACCESS_TOKEN and self._jwt_is_expired(self._ACCESS_TOKEN): + elif self._ACCESS_TOKEN and self._is_token_expired(self._ACCESS_TOKEN): self._refresh_token() playback = self._call_api( diff --git a/yt_dlp/extractor/mlb.py b/yt_dlp/extractor/mlb.py index c8f4fcf7e8..f6a8617d4e 100644 --- a/yt_dlp/extractor/mlb.py +++ b/yt_dlp/extractor/mlb.py @@ -351,7 +351,7 @@ class MLBTVIE(InfoExtractor): @property def _api_headers(self): - if self._jwt_is_expired(self._access_token): + if self._jwt_is_expired(self._access_token, 120): self.write_debug('Access token has expired; re-logging in') self._perform_login(*self._get_login_info()) return {'Authorization': f'Bearer {self._access_token}'} diff --git a/yt_dlp/extractor/qdance.py b/yt_dlp/extractor/qdance.py index 3faab0465e..46934cfd6b 100644 --- a/yt_dlp/extractor/qdance.py +++ b/yt_dlp/extractor/qdance.py @@ -114,7 +114,7 @@ def _real_initialize(self): self.raise_login_required() def _get_auth(self): - if self._jwt_is_expired(self._access_token): + if self._jwt_is_expired(self._access_token, 120): if not self._refresh_token: raise ExtractorError( 'Cannot refresh access token, login with yt-dlp or refresh cookies in browser') From 1b1117a9c512b549791f6513d1d1ad10471c9003 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20De=20Boey?= Date: Sun, 16 Mar 2025 23:37:39 +0100 Subject: [PATCH 04/12] move `_jwt_is_expired` to `utils` --- yt_dlp/extractor/cbc.py | 5 +++-- yt_dlp/extractor/common.py | 5 ----- yt_dlp/extractor/digitalconcerthall.py | 3 ++- yt_dlp/extractor/iwara.py | 3 ++- yt_dlp/extractor/jiocinema.py | 6 ++++-- yt_dlp/extractor/mlb.py | 3 ++- yt_dlp/extractor/qdance.py | 3 ++- yt_dlp/extractor/stacommu.py | 3 ++- yt_dlp/extractor/vrt.py | 13 +++++++------ yt_dlp/extractor/wrestleuniverse.py | 3 ++- yt_dlp/extractor/zee5.py | 3 ++- yt_dlp/utils/_utils.py | 4 ++++ 12 files changed, 32 insertions(+), 22 deletions(-) diff --git a/yt_dlp/extractor/cbc.py b/yt_dlp/extractor/cbc.py index d7cb3ebfb6..ac60252cc5 100644 --- a/yt_dlp/extractor/cbc.py +++ b/yt_dlp/extractor/cbc.py @@ -11,6 +11,7 @@ float_or_none, int_or_none, js_to_json, + jwt_is_expired, mimetype2ext, orderedSet, parse_age_limit, @@ -653,7 +654,7 @@ def _perform_login(self, username, password): raise def _fetch_access_token(self): - if self._jwt_is_expired(self._access_token): + if jwt_is_expired(self._access_token): try: self._call_oauth_api({ 'grant_type': 'refresh_token', @@ -671,7 +672,7 @@ def _fetch_claims_token(self): if not self._get_login_info()[0]: return None - if not self._claims_token or self._jwt_is_expired(self._claims_token): + if not self._claims_token or jwt_is_expired(self._claims_token): self._claims_token = self._download_json( 'https://services.radio-canada.ca/ott/subscription/v2/gem/Subscriber/profile', None, 'Downloading claims token', query={'device': 'web'}, diff --git a/yt_dlp/extractor/common.py b/yt_dlp/extractor/common.py index 306ad737f8..b816d788fa 100644 --- a/yt_dlp/extractor/common.py +++ b/yt_dlp/extractor/common.py @@ -69,7 +69,6 @@ int_or_none, join_nonempty, js_to_json, - jwt_decode_hs256, mimetype2ext, netrc_from_content, orderedSet, @@ -995,10 +994,6 @@ def _guess_encoding_from_content(content_type, webpage_bytes): return encoding - @staticmethod - def _jwt_is_expired(token, buffer=300): - return jwt_decode_hs256(token)['exp'] - time.time() < buffer - def __check_blocked(self, content): first_block = content[:512] if ('Access to this site is blocked' in content diff --git a/yt_dlp/extractor/digitalconcerthall.py b/yt_dlp/extractor/digitalconcerthall.py index 1c0ff4a7d6..adee35c218 100644 --- a/yt_dlp/extractor/digitalconcerthall.py +++ b/yt_dlp/extractor/digitalconcerthall.py @@ -3,6 +3,7 @@ from ..utils import ( ExtractorError, jwt_decode_hs256, + jwt_is_expired, parse_codecs, try_get, url_or_none, @@ -86,7 +87,7 @@ class DigitalConcertHallIE(InfoExtractor): @property def _access_token_is_expired(self): - return self._jwt_is_expired(self._access_token, 30) + return jwt_is_expired(self._access_token, 30) def _set_access_token(self, value): self._access_token = value diff --git a/yt_dlp/extractor/iwara.py b/yt_dlp/extractor/iwara.py index f79f835767..c6ffebf5fe 100644 --- a/yt_dlp/extractor/iwara.py +++ b/yt_dlp/extractor/iwara.py @@ -8,6 +8,7 @@ ExtractorError, OnDemandPagedList, int_or_none, + jwt_is_expired, mimetype2ext, qualities, traverse_obj, @@ -22,7 +23,7 @@ class IwaraBaseIE(InfoExtractor): def _is_token_expired(self, token, token_type): # User token TTL == ~3 weeks, Media token TTL == ~1 hour - if self._jwt_is_expired(token, 120): + if jwt_is_expired(token, 120): self.to_screen(f'{token_type} token has expired') return True diff --git a/yt_dlp/extractor/jiocinema.py b/yt_dlp/extractor/jiocinema.py index e8858695ea..f975e74436 100644 --- a/yt_dlp/extractor/jiocinema.py +++ b/yt_dlp/extractor/jiocinema.py @@ -11,6 +11,7 @@ float_or_none, int_or_none, jwt_decode_hs256, + jwt_is_expired, parse_age_limit, try_call, url_or_none, @@ -105,8 +106,9 @@ def _call_login_api(self, endpoint, guest_token, data, note): 'os': ('os', {str}), })}, data=data) - def _is_token_expired(self, token): - return self._jwt_is_expired(token, 180) + @staticmethod + def _is_token_expired(token): + return jwt_is_expired(token, 180) def _perform_login(self, username, password): if self._ACCESS_TOKEN and not self._is_token_expired(self._ACCESS_TOKEN): diff --git a/yt_dlp/extractor/mlb.py b/yt_dlp/extractor/mlb.py index f6a8617d4e..47ae64aa36 100644 --- a/yt_dlp/extractor/mlb.py +++ b/yt_dlp/extractor/mlb.py @@ -9,6 +9,7 @@ determine_ext, int_or_none, join_nonempty, + jwt_is_expired, parse_duration, parse_iso8601, try_get, @@ -351,7 +352,7 @@ class MLBTVIE(InfoExtractor): @property def _api_headers(self): - if self._jwt_is_expired(self._access_token, 120): + if jwt_is_expired(self._access_token, 120): self.write_debug('Access token has expired; re-logging in') self._perform_login(*self._get_login_info()) return {'Authorization': f'Bearer {self._access_token}'} diff --git a/yt_dlp/extractor/qdance.py b/yt_dlp/extractor/qdance.py index 46934cfd6b..777e2b60df 100644 --- a/yt_dlp/extractor/qdance.py +++ b/yt_dlp/extractor/qdance.py @@ -4,6 +4,7 @@ from ..utils import ( ExtractorError, int_or_none, + jwt_is_expired, str_or_none, traverse_obj, try_call, @@ -114,7 +115,7 @@ def _real_initialize(self): self.raise_login_required() def _get_auth(self): - if self._jwt_is_expired(self._access_token, 120): + if jwt_is_expired(self._access_token, 120): if not self._refresh_token: raise ExtractorError( 'Cannot refresh access token, login with yt-dlp or refresh cookies in browser') diff --git a/yt_dlp/extractor/stacommu.py b/yt_dlp/extractor/stacommu.py index c9489763ea..6b3959c353 100644 --- a/yt_dlp/extractor/stacommu.py +++ b/yt_dlp/extractor/stacommu.py @@ -1,6 +1,7 @@ from .wrestleuniverse import WrestleUniverseBaseIE from ..utils import ( int_or_none, + jwt_is_expired, traverse_obj, url_or_none, ) @@ -20,7 +21,7 @@ class StacommuBaseIE(WrestleUniverseBaseIE): @WrestleUniverseBaseIE._TOKEN.getter def _TOKEN(self): - if self._REAL_TOKEN and self._jwt_is_expired(self._REAL_TOKEN): + if self._REAL_TOKEN and jwt_is_expired(self._REAL_TOKEN): self._refresh_token() return self._REAL_TOKEN diff --git a/yt_dlp/extractor/vrt.py b/yt_dlp/extractor/vrt.py index 4b9fe39445..860a4d29c1 100644 --- a/yt_dlp/extractor/vrt.py +++ b/yt_dlp/extractor/vrt.py @@ -14,6 +14,7 @@ get_element_html_by_class, int_or_none, jwt_encode_hs256, + jwt_is_expired, make_archive_id, merge_dicts, parse_age_limit, @@ -272,15 +273,15 @@ def _fetch_tokens(self): access_token = self._get_vrt_cookie(self._ACCESS_TOKEN_COOKIE_NAME) video_token = self._get_vrt_cookie(self._VIDEO_TOKEN_COOKIE_NAME) - if (access_token and not self._jwt_is_expired(access_token) - and video_token and not self._jwt_is_expired(video_token)): + if (access_token and not jwt_is_expired(access_token) + and video_token and not jwt_is_expired(video_token)): return access_token, video_token if has_credentials: access_token, video_token = self.cache.load(self._NETRC_MACHINE, 'token_data', default=(None, None)) - if (access_token and not self._jwt_is_expired(access_token) - and video_token and not self._jwt_is_expired(video_token)): + if (access_token and not jwt_is_expired(access_token) + and video_token and not jwt_is_expired(video_token)): self.write_debug('Restored tokens from cache') self._set_cookie(self._TOKEN_COOKIE_DOMAIN, self._ACCESS_TOKEN_COOKIE_NAME, access_token) self._set_cookie(self._TOKEN_COOKIE_DOMAIN, self._VIDEO_TOKEN_COOKIE_NAME, video_token) @@ -317,12 +318,12 @@ def _get_vrt_cookie(self, cookie_name): def _perform_login(self, username, password): refresh_token = self._get_vrt_cookie(self._REFRESH_TOKEN_COOKIE_NAME) - if refresh_token and not self._jwt_is_expired(refresh_token): + if refresh_token and not jwt_is_expired(refresh_token): self.write_debug('Using refresh token from logged-in cookies; skipping login with credentials') return refresh_token = self.cache.load(self._NETRC_MACHINE, 'refresh_token', default=None) - if refresh_token and not self._jwt_is_expired(refresh_token): + if refresh_token and not jwt_is_expired(refresh_token): self.write_debug('Restored refresh token from cache') self._set_cookie(self._TOKEN_COOKIE_DOMAIN, self._REFRESH_TOKEN_COOKIE_NAME, refresh_token, path='/vrtmax/sso') return diff --git a/yt_dlp/extractor/wrestleuniverse.py b/yt_dlp/extractor/wrestleuniverse.py index 47193a2e86..2b18a27ccd 100644 --- a/yt_dlp/extractor/wrestleuniverse.py +++ b/yt_dlp/extractor/wrestleuniverse.py @@ -8,6 +8,7 @@ from ..utils import ( ExtractorError, int_or_none, + jwt_is_expired, traverse_obj, try_call, url_basename, @@ -43,7 +44,7 @@ def _TOKEN(self): self.raise_login_required() self._TOKEN = token - if not self._REAL_TOKEN or self._jwt_is_expired(self._REAL_TOKEN): + if not self._REAL_TOKEN or jwt_is_expired(self._REAL_TOKEN): if not self._REFRESH_TOKEN: raise ExtractorError( 'Expired token. Refresh your cookies in browser and try again', expected=True) diff --git a/yt_dlp/extractor/zee5.py b/yt_dlp/extractor/zee5.py index 02cd2a77b8..1657a563e6 100644 --- a/yt_dlp/extractor/zee5.py +++ b/yt_dlp/extractor/zee5.py @@ -6,6 +6,7 @@ ExtractorError, int_or_none, jwt_decode_hs256, + jwt_is_expired, parse_age_limit, str_or_none, try_call, @@ -123,7 +124,7 @@ def _perform_login(self, username, password): else: raise ExtractorError(self._LOGIN_HINT, expected=True) - if self._jwt_is_expired(self._USER_TOKEN): + if jwt_is_expired(self._USER_TOKEN): raise ExtractorError('User token has expired', expected=True) self._USER_COUNTRY = jwt_decode_hs256(self._USER_TOKEN).get('current_country') diff --git a/yt_dlp/utils/_utils.py b/yt_dlp/utils/_utils.py index 4093c238c2..2a5766d8a4 100644 --- a/yt_dlp/utils/_utils.py +++ b/yt_dlp/utils/_utils.py @@ -4763,6 +4763,10 @@ def jwt_decode_hs256(jwt): return json.loads(base64.urlsafe_b64decode(f'{payload_b64}===')) +def jwt_is_expired(token, buffer=300): + return jwt_decode_hs256(token)['exp'] - time.time() < buffer + + WINDOWS_VT_MODE = False if os.name == 'nt' else None From 711e3e0c45f4f47ad5957d89fbb8f75a691c49ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20De=20Boey?= Date: Sun, 16 Mar 2025 23:52:44 +0100 Subject: [PATCH 05/12] re-trigger tests From baba0a95edb19f02de2de2cd2b8a3abd82af5abe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20De=20Boey?= Date: Mon, 17 Mar 2025 00:01:48 +0100 Subject: [PATCH 06/12] use `jwt_is_expired` in `NFL` extractor --- yt_dlp/extractor/nfl.py | 6 ++---- yt_dlp/utils/_utils.py | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/yt_dlp/extractor/nfl.py b/yt_dlp/extractor/nfl.py index 59213a44be..29f5bd371c 100644 --- a/yt_dlp/extractor/nfl.py +++ b/yt_dlp/extractor/nfl.py @@ -1,7 +1,6 @@ import base64 import json import re -import time import uuid from .anvato import AnvatoIE @@ -12,6 +11,7 @@ determine_ext, get_element_by_class, int_or_none, + jwt_is_expired, make_archive_id, url_or_none, urlencode_postdata, @@ -84,7 +84,6 @@ class NFLBaseIE(InfoExtractor): _API_KEY = '3_Qa8TkWpIB8ESCBT8tY2TukbVKgO5F6BJVc7N1oComdwFzI7H2L9NOWdm11i_BY9f' _TOKEN = None - _TOKEN_EXPIRY = 0 def _get_account_info(self): cookies = self._get_cookies('https://auth-id.nfl.com/') @@ -123,7 +122,7 @@ def _get_account_info(self): raise ExtractorError('Failed to retrieve account info with provided cookies', expected=True) def _get_auth_token(self): - if self._TOKEN and self._TOKEN_EXPIRY > int(time.time() + 30): + if self._TOKEN and jwt_is_expired(self._TOKEN, 30, 'expiresIn'): return token = self._download_json( @@ -133,7 +132,6 @@ def _get_auth_token(self): data=json.dumps({**self._CLIENT_DATA, **self._ACCOUNT_INFO}, separators=(',', ':')).encode()) self._TOKEN = token['accessToken'] - self._TOKEN_EXPIRY = token['expiresIn'] self._ACCOUNT_INFO['refreshToken'] = token['refreshToken'] def _extract_video(self, mcp_id, is_live=False): diff --git a/yt_dlp/utils/_utils.py b/yt_dlp/utils/_utils.py index 2a5766d8a4..bc03389e07 100644 --- a/yt_dlp/utils/_utils.py +++ b/yt_dlp/utils/_utils.py @@ -4763,8 +4763,8 @@ def jwt_decode_hs256(jwt): return json.loads(base64.urlsafe_b64decode(f'{payload_b64}===')) -def jwt_is_expired(token, buffer=300): - return jwt_decode_hs256(token)['exp'] - time.time() < buffer +def jwt_is_expired(token, buffer=300, key='exp'): + return jwt_decode_hs256(token)[key] - time.time() < buffer WINDOWS_VT_MODE = False if os.name == 'nt' else None From ed9ec2c67e867111e0d9ccad0e14c1a389a71bc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20De=20Boey?= Date: Mon, 17 Mar 2025 00:13:34 +0100 Subject: [PATCH 07/12] use `jwt_is_expired` in `CDA` extractor --- yt_dlp/extractor/cda.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/yt_dlp/extractor/cda.py b/yt_dlp/extractor/cda.py index 96f25c22a8..0616d98781 100644 --- a/yt_dlp/extractor/cda.py +++ b/yt_dlp/extractor/cda.py @@ -17,11 +17,11 @@ int_or_none, merge_dicts, multipart_encode, - parse_duration, + parse_duration,jwt_is_expired, traverse_obj, try_call, try_get, - urljoin, + urljoin, jwt_encode_hs256, jwt_decode_hs256, ) @@ -151,8 +151,8 @@ def _perform_login(self, username, password): self._API_HEADERS['User-Agent'] = f'pl.cda 1.0 (version {app_version}; Android {android_version}; {phone_model})' cached_bearer = self.cache.load(self._BEARER_CACHE, username) or {} - if cached_bearer.get('valid_until', 0) > dt.datetime.now().timestamp() + 5: - self._API_HEADERS['Authorization'] = f'Bearer {cached_bearer["token"]}' + if not jwt_is_expired(cached_bearer, 5, 'valid_until'): + self._API_HEADERS['Authorization'] = f'Bearer {jwt_decode_hs256(cached_bearer)["token"]}' return password_hash = base64.urlsafe_b64encode(hmac.new( @@ -169,10 +169,10 @@ def _perform_login(self, username, password): 'login': username, 'password': password_hash, }) - self.cache.store(self._BEARER_CACHE, username, { + self.cache.store(self._BEARER_CACHE, username, jwt_encode_hs256({ 'token': token_res['access_token'], 'valid_until': token_res['expires_in'] + dt.datetime.now().timestamp(), - }) + }, 'cda.pl')) self._API_HEADERS['Authorization'] = f'Bearer {token_res["access_token"]}' def _real_extract(self, url): From 81c2fac721514ca6407cfd920ac8c93eae05dd33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20De=20Boey?= Date: Mon, 17 Mar 2025 00:14:32 +0100 Subject: [PATCH 08/12] fix formatting --- yt_dlp/extractor/cda.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/yt_dlp/extractor/cda.py b/yt_dlp/extractor/cda.py index 0616d98781..1c65c36345 100644 --- a/yt_dlp/extractor/cda.py +++ b/yt_dlp/extractor/cda.py @@ -15,13 +15,16 @@ OnDemandPagedList, float_or_none, int_or_none, + jwt_decode_hs256, + jwt_encode_hs256, + jwt_is_expired, merge_dicts, multipart_encode, - parse_duration,jwt_is_expired, + parse_duration, traverse_obj, try_call, try_get, - urljoin, jwt_encode_hs256, jwt_decode_hs256, + urljoin, ) From f6c24f54f3d5a2c969bf11b8e8cbebdb1a5d38a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20De=20Boey?= Date: Mon, 17 Mar 2025 00:38:27 +0100 Subject: [PATCH 09/12] make `jwt_is_expired` non-fatal --- yt_dlp/utils/_utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/yt_dlp/utils/_utils.py b/yt_dlp/utils/_utils.py index bc03389e07..04521651d1 100644 --- a/yt_dlp/utils/_utils.py +++ b/yt_dlp/utils/_utils.py @@ -4764,7 +4764,8 @@ def jwt_decode_hs256(jwt): def jwt_is_expired(token, buffer=300, key='exp'): - return jwt_decode_hs256(token)[key] - time.time() < buffer + exp = traversal.traverse_obj(token, ({jwt_decode_hs256}, key, {int, float})) or 0 + return exp - time.time() < buffer WINDOWS_VT_MODE = False if os.name == 'nt' else None From ff1786e158d1041e14f9b93b659f6f2f133984c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20De=20Boey?= Date: Mon, 17 Mar 2025 00:40:17 +0100 Subject: [PATCH 10/12] fix cache compat in CDA extractor --- yt_dlp/extractor/cda.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/yt_dlp/extractor/cda.py b/yt_dlp/extractor/cda.py index 1c65c36345..669b044be3 100644 --- a/yt_dlp/extractor/cda.py +++ b/yt_dlp/extractor/cda.py @@ -15,7 +15,6 @@ OnDemandPagedList, float_or_none, int_or_none, - jwt_decode_hs256, jwt_encode_hs256, jwt_is_expired, merge_dicts, @@ -154,8 +153,8 @@ def _perform_login(self, username, password): self._API_HEADERS['User-Agent'] = f'pl.cda 1.0 (version {app_version}; Android {android_version}; {phone_model})' cached_bearer = self.cache.load(self._BEARER_CACHE, username) or {} - if not jwt_is_expired(cached_bearer, 5, 'valid_until'): - self._API_HEADERS['Authorization'] = f'Bearer {jwt_decode_hs256(cached_bearer)["token"]}' + if not jwt_is_expired(jwt_encode_hs256(cached_bearer, 'cda.pl'), 5, 'valid_until'): + self._API_HEADERS['Authorization'] = f'Bearer {cached_bearer["token"]}' return password_hash = base64.urlsafe_b64encode(hmac.new( @@ -172,10 +171,10 @@ def _perform_login(self, username, password): 'login': username, 'password': password_hash, }) - self.cache.store(self._BEARER_CACHE, username, jwt_encode_hs256({ + self.cache.store(self._BEARER_CACHE, username, { 'token': token_res['access_token'], 'valid_until': token_res['expires_in'] + dt.datetime.now().timestamp(), - }, 'cda.pl')) + }) self._API_HEADERS['Authorization'] = f'Bearer {token_res["access_token"]}' def _real_extract(self, url): From 825d71efbccf3cbfcf19ae4c3d16bb45c33f6863 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20De=20Boey?= Date: Sun, 6 Apr 2025 09:10:34 +0200 Subject: [PATCH 11/12] use `jwt_is_expired` in `CanalSurmas` extractor --- yt_dlp/extractor/canalsurmas.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/yt_dlp/extractor/canalsurmas.py b/yt_dlp/extractor/canalsurmas.py index 210973a0b8..8af3be43c4 100644 --- a/yt_dlp/extractor/canalsurmas.py +++ b/yt_dlp/extractor/canalsurmas.py @@ -8,7 +8,7 @@ jwt_decode_hs256, parse_iso8601, url_or_none, - variadic, + variadic,jwt_is_expired, ) from ..utils.traversal import traverse_obj @@ -31,12 +31,8 @@ class CanalsurmasIE(InfoExtractor): _API_BASE = 'https://api-rtva.interactvty.com' _access_token = None - @staticmethod - def _is_jwt_expired(token): - return jwt_decode_hs256(token)['exp'] - time.time() < 300 - def _call_api(self, endpoint, video_id, fields=None): - if not self._access_token or self._is_jwt_expired(self._access_token): + if not self._access_token or jwt_is_expired(self._access_token): self._access_token = self._download_json( f'{self._API_BASE}/jwt/token/', None, 'Downloading access token', 'Failed to download access token', From 5b536e57560ed519fd7118a9379e66507470b12e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20De=20Boey?= Date: Sun, 6 Apr 2025 09:13:22 +0200 Subject: [PATCH 12/12] fix formatting --- yt_dlp/extractor/canalsurmas.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/yt_dlp/extractor/canalsurmas.py b/yt_dlp/extractor/canalsurmas.py index 8af3be43c4..4043418524 100644 --- a/yt_dlp/extractor/canalsurmas.py +++ b/yt_dlp/extractor/canalsurmas.py @@ -1,14 +1,13 @@ import json -import time from .common import InfoExtractor from ..utils import ( determine_ext, float_or_none, - jwt_decode_hs256, + jwt_is_expired, parse_iso8601, url_or_none, - variadic,jwt_is_expired, + variadic, ) from ..utils.traversal import traverse_obj