1
0
Fork 0
mirror of https://github.com/yt-dlp/yt-dlp.git synced 2025-03-09 12:50:23 -05:00

login and misc fixes

This commit is contained in:
c-basalt 2024-07-26 03:09:55 -04:00
parent cddca4e4fc
commit ef048b6b01

View file

@ -1,4 +1,9 @@
import asyncio import asyncio
import base64
import datetime as dt
import hashlib
import hmac
import json
import random import random
import re import re
import time import time
@ -18,6 +23,7 @@
class RPlayBaseIE(InfoExtractor): class RPlayBaseIE(InfoExtractor):
_NETRC_MACHINE = 'rplaylive'
_TOKEN_CACHE = {} _TOKEN_CACHE = {}
_user_id = None _user_id = None
_login_type = None _login_type = None
@ -35,22 +41,40 @@ def login_type(self):
def jwt_token(self): def jwt_token(self):
return self._jwt_token return self._jwt_token
def _perform_login(self, username, password): def _jwt_encode_hs256(self, payload: dict, key: str):
_ = { # ..utils.jwt_encode_hs256() uses slightly different details that would fails
'alg': 'HS256', # and we need to re-implement it with minor changes
'typ': 'JWT', b64encode = lambda x: base64.urlsafe_b64encode(
} json.dumps(x, separators=(',', ':')).encode()).strip(b'=')
raise NotImplementedError
def _login_by_token(self, jwt_token, video_id): header_b64 = b64encode({'alg': 'HS256', 'typ': 'JWT'})
payload_b64 = b64encode(payload)
h = hmac.new(key.encode(), header_b64 + b'.' + payload_b64, hashlib.sha256)
signature_b64 = base64.urlsafe_b64encode(h.digest()).strip(b'=')
return header_b64 + b'.' + payload_b64 + b'.' + signature_b64
def _perform_login(self, username, password):
payload = {
'eml': username,
'dat': dt.datetime.now(dt.timezone.utc).isoformat(timespec='milliseconds').replace('+00:00', 'Z'),
'iat': int(time.time()),
}
key = hashlib.sha256(password.encode()).hexdigest()
self._login_by_token(self._jwt_encode_hs256(payload, key).decode())
def _login_by_token(self, jwt_token):
user_info = self._download_json( user_info = self._download_json(
'https://api.rplay.live/account/login', video_id, note='performing login', errnote='Failed to login', 'https://api.rplay.live/account/login', 'login', note='performing login', errnote='Failed to login',
data=f'{{"token":"{jwt_token}","lang":"en","loginType":null,"checkAdmin":null}}'.encode(), data=f'{{"token":"{jwt_token}","lang":"en","loginType":null,"checkAdmin":null}}'.encode(),
headers={'Content-Type': 'application/json', 'Authorization': 'null'}, fatal=False) headers={'Content-Type': 'application/json', 'Authorization': 'null'}, fatal=False)
if user_info: if user_info:
self._user_id = traverse_obj(user_info, 'oid') self._user_id = traverse_obj(user_info, 'oid')
self._login_type = traverse_obj(user_info, 'accountType') self._login_type = traverse_obj(user_info, 'accountType')
self._jwt_token = jwt_token self._jwt_token = jwt_token if self._user_id else None
if not self._user_id:
self.report_warning('Failed to login, possibly due to wrong password or website change')
def _get_butter_files(self): def _get_butter_files(self):
cache = self.cache.load('rplay', 'butter-code') or {} cache = self.cache.load('rplay', 'butter-code') or {}
@ -172,11 +196,8 @@ class RPlayVideoIE(RPlayBaseIE):
def _real_extract(self, url): def _real_extract(self, url):
video_id = self._match_id(url) video_id = self._match_id(url)
if self._configuration_arg('jwt_token') and not self.user_id:
self._login_by_token(self._configuration_arg('jwt_token', casesense=True)[0], video_id)
headers = {'Origin': 'https://rplay.live', 'Referer': 'https://rplay.live/'} headers = {'Origin': 'https://rplay.live', 'Referer': 'https://rplay.live/'}
content = self._download_json('https://api.rplay.live/content', video_id, query={ video_info = self._download_json('https://api.rplay.live/content', video_id, query={
'contentOid': video_id, 'contentOid': video_id,
'status': 'published', 'status': 'published',
'withComments': True, 'withComments': True,
@ -186,12 +207,10 @@ def _real_extract(self, url):
'loginType': self.login_type, 'loginType': self.login_type,
} if self.user_id else {}), } if self.user_id else {}),
}, headers={**headers, 'Authorization': self.jwt_token or 'null'}) }, headers={**headers, 'Authorization': self.jwt_token or 'null'})
if content.get('drm'): if video_info.get('drm'):
raise ExtractorError('This video is DRM-protected') raise ExtractorError('This video is DRM-protected')
content.pop('daily_views', None)
content.get('creatorInfo', {}).pop('subscriptionTiers', None)
metainfo = traverse_obj(content, { metainfo = traverse_obj(video_info, {
'title': ('title', {str}), 'title': ('title', {str}),
'description': ('introText', {str}), 'description': ('introText', {str}),
'release_timestamp': ('publishedAt', {parse_iso8601}), 'release_timestamp': ('publishedAt', {parse_iso8601}),
@ -203,12 +222,16 @@ def _real_extract(self, url):
'age_limit': (('hideContent', 'isAdultContent'), {lambda x: 18 if x else None}, any), 'age_limit': (('hideContent', 'isAdultContent'), {lambda x: 18 if x else None}, any),
}) })
m3u8_url = traverse_obj(content, ('canView', 'url')) m3u8_url = traverse_obj(video_info, ('canView', 'url'))
if not m3u8_url: if not m3u8_url:
raise ExtractorError('You do not have access to this video. ' msg = 'You do not have access to this video'
'Passing JWT token using --extractor-args RPlayVideo:jwt_token=xxx.xxxxx.xxx to login') if traverse_obj(video_info, ('viewableTiers', 'free')):
msg += '. This video requires a free subscription'
if not self.user_id:
msg += f'. {self._login_hint(method="password")}'
raise ExtractorError(msg)
thumbnail_key = traverse_obj(content, ('streamables', lambda _, v: v['type'].startswith('image/'), 's3key', any)) thumbnail_key = traverse_obj(video_info, ('streamables', lambda _, v: v['type'].startswith('image/'), 's3key', any))
if thumbnail_key: if thumbnail_key:
metainfo['thumbnail'] = url_or_none(self._download_webpage( metainfo['thumbnail'] = url_or_none(self._download_webpage(
'https://api.rplay.live/upload/privateasset', video_id, 'getting cover url', query={ 'https://api.rplay.live/upload/privateasset', video_id, 'getting cover url', query={
@ -231,7 +254,7 @@ def _real_extract(self, url):
urlh = self._request_webpage(match[1], video_id, 'getting hls key', headers={ urlh = self._request_webpage(match[1], video_id, 'getting hls key', headers={
**headers, **headers,
'rplay-private-content-requestor': self.user_id or 'not-logged-in', 'rplay-private-content-requestor': self.user_id or 'not-logged-in',
'age': random.randint(100, 10000), 'age': random.randint(1, 4999),
}) })
fmt['hls_aes'] = {'key': urlh.read().hex()} fmt['hls_aes'] = {'key': urlh.read().hex()}
@ -261,14 +284,26 @@ class RPlayUserIE(RPlayBaseIE):
'playlist_mincount': 77, 'playlist_mincount': 77,
}] }]
def _perform_login(self, username, password):
# This playlist extractor does not require login
return
def _real_extract(self, url): def _real_extract(self, url):
user_id, short = self._match_valid_url(url).group('id', 'short') user_id, short = self._match_valid_url(url).group('id', 'short')
key = 'customUrl' if short == 'c' else 'userOid' key = 'customUrl' if short == 'c' else 'userOid'
user_info = self._download_json( user_info = self._download_json(
f'https://api.rplay.live/account/getuser?{key}={user_id}&filter[]=nickname&filter[]=published', user_id) f'https://api.rplay.live/account/getuser?{key}={user_id}&filter[]=nickname&filter[]=published', user_id)
replays = self._download_json(
'https://api.rplay.live/live/replays?=667e4cd99aa7f739a2c91852', user_id, query={
'creatorOid': user_info.get('_id')})
entries = traverse_obj(user_info, ('published', ..., { entries = traverse_obj(user_info, ('published', ..., {
lambda x: self.url_result(f'https://rplay.live/play/{x}/', ie=RPlayVideoIE, video_id=x)})) lambda x: self.url_result(f'https://rplay.live/play/{x}/', ie=RPlayVideoIE, video_id=x)}))
for entry_id in traverse_obj(replays, (..., '_id', {str})):
if entry_id in user_info.get('published', []):
continue
entries.append(self.url_result(f'https://rplay.live/play/{entry_id}/', ie=RPlayVideoIE, video_id=entry_id))
return self.playlist_result(entries, user_info.get('_id', user_id), user_info.get('nickname')) return self.playlist_result(entries, user_info.get('_id', user_id), user_info.get('nickname'))
@ -314,10 +349,8 @@ def _real_extract(self, url):
if stream_state == 'youtube': if stream_state == 'youtube':
return self.url_result(f'https://www.youtube.com/watch?v={live_info["liveStreamId"]}') return self.url_result(f'https://www.youtube.com/watch?v={live_info["liveStreamId"]}')
elif stream_state == 'live': elif stream_state == 'live':
if self._configuration_arg('jwt_token') and not self.user_id: if not self.user_id:
self._login_by_token(self._configuration_arg('jwt_token', casesense=True)[0], user_id) self.raise_login_required(method='password')
if not live_info.get('allowAnonymous') and not self.user_id:
self.raise_login_required()
key2 = self._download_webpage( key2 = self._download_webpage(
'https://api.rplay.live/live/key2', user_id, 'getting live key', 'https://api.rplay.live/live/key2', user_id, 'getting live key',
headers={'Authorization': self.jwt_token}, headers={'Authorization': self.jwt_token},