support jwt arg

This commit is contained in:
c-basalt 2024-09-19 20:09:48 -04:00
parent 02f4b25227
commit dcd2e93fa0
2 changed files with 25 additions and 5 deletions

View file

@ -1864,6 +1864,10 @@ #### bilibili
#### digitalconcerthall #### digitalconcerthall
* `prefer_combined_hls`: Prefer extracting combined/pre-merged video and audio HLS formats. This will exclude 4K/HEVC video and lossless/FLAC audio formats, which are only available as split video/audio HLS formats * `prefer_combined_hls`: Prefer extracting combined/pre-merged video and audio HLS formats. This will exclude 4K/HEVC video and lossless/FLAC audio formats, which are only available as split video/audio HLS formats
#### rplaylive
* `jwt_token`: JWT token that can be found as value of `_AUTHORIZATION_` entry from the browser local storage. This can be used as an alternative login method.
**Note**: These options may be changed/removed in the future without concern for backward compatibility **Note**: These options may be changed/removed in the future without concern for backward compatibility
<!-- MANPAGE: MOVE "INSTALLATION" SECTION HERE --> <!-- MANPAGE: MOVE "INSTALLATION" SECTION HERE -->

View file

@ -27,17 +27,27 @@ class RPlayBaseIE(InfoExtractor):
_user_id = None _user_id = None
_login_type = None _login_type = None
_jwt_token = None _jwt_token = None
_tested_jwt = False
def _check_jwt_args(self):
jwt_arg = self._configuration_arg('jwt_token', ie_key='rplaylive', casesense=True)
if self._jwt_token is None and jwt_arg and not self._tested_jwt:
self._login_by_token(jwt_arg[0], raw_token_hint=True)
self._tested_jwt = True
@property @property
def user_id(self): def user_id(self):
self._check_jwt_args()
return self._user_id return self._user_id
@property @property
def login_type(self): def login_type(self):
self._check_jwt_args()
return self._login_type return self._login_type
@property @property
def jwt_token(self): def jwt_token(self):
self._check_jwt_args()
return self._jwt_token return self._jwt_token
@property @property
@ -54,6 +64,10 @@ def jwt_header(self):
'Authorization': self.jwt_token or 'null', 'Authorization': self.jwt_token or 'null',
} }
def _login_hint(self, **kwargs):
return (f'Use --username and --password, --netrc-cmd, --netrc ({self._NETRC_MACHINE}) '
'or --extractor-args "rplaylive:jwt_token=xxx" to provide account credentials')
def _jwt_encode_hs256(self, payload: dict, key: str): def _jwt_encode_hs256(self, payload: dict, key: str):
# yt_dlp.utils.jwt_encode_hs256() uses slightly different details that would fails # yt_dlp.utils.jwt_encode_hs256() uses slightly different details that would fails
# and we need to re-implement it with minor changes # and we need to re-implement it with minor changes
@ -75,9 +89,9 @@ def _perform_login(self, username, password):
key = hashlib.sha256(password.encode()).hexdigest() key = hashlib.sha256(password.encode()).hexdigest()
self._login_by_token(self._jwt_encode_hs256(payload, key).decode()) self._login_by_token(self._jwt_encode_hs256(payload, key).decode())
def _login_by_token(self, jwt_token): def _login_by_token(self, jwt_token, raw_token_hint=False):
user_info = self._download_json( user_info = self._download_json(
'https://api.rplay.live/account/login', 'login', note='performing login', errnote='Failed to login', 'https://api.rplay.live/account/login', 'login', note='performing login', errnote='login failed',
data=f'{{"token":"{jwt_token}","loginType":null,"checkAdmin":null}}'.encode(), data=f'{{"token":"{jwt_token}","loginType":null,"checkAdmin":null}}'.encode(),
headers={'Content-Type': 'application/json', 'Authorization': 'null'}, fatal=False) headers={'Content-Type': 'application/json', 'Authorization': 'null'}, fatal=False)
@ -86,7 +100,10 @@ def _login_by_token(self, jwt_token):
self._login_type = traverse_obj(user_info, 'accountType') self._login_type = traverse_obj(user_info, 'accountType')
self._jwt_token = jwt_token if self._user_id else None self._jwt_token = jwt_token if self._user_id else None
if not self._user_id: if not self._user_id:
self.report_warning('Failed to login, possibly due to wrong password or website change') if raw_token_hint:
self.report_warning('Login failed, possibly due to wrong or expired JWT token')
else:
self.report_warning('Login failed, possibly due to wrong password or website change')
def get_butter_token(self): def get_butter_token(self):
salt = 'QWI@(!WAS)Dj1AA(!@*DJ#@$@~1)P' salt = 'QWI@(!WAS)Dj1AA(!@*DJ#@$@~1)P'
@ -181,8 +198,7 @@ def _real_extract(self, url):
if traverse_obj(video_info, ('viewableTiers', 'free')): if traverse_obj(video_info, ('viewableTiers', 'free')):
msg = 'This video requires a free subscription to access' msg = 'This video requires a free subscription to access'
if not self.user_id: if not self.user_id:
# credential is in browser localStorage only, no cookies to use msg += f'. {self._login_hint()}'
msg += f'. {self._login_hint(method="password")}'
raise ExtractorError(msg, expected=True) raise ExtractorError(msg, expected=True)
formats = self._extract_m3u8_formats(m3u8_url, video_id, headers={ formats = self._extract_m3u8_formats(m3u8_url, video_id, headers={