diff --git a/resources/lib/youtube_plugin/youtube/client/request_client.py b/resources/lib/youtube_plugin/youtube/client/request_client.py index 1f535f47e..d6012445a 100644 --- a/resources/lib/youtube_plugin/youtube/client/request_client.py +++ b/resources/lib/youtube_plugin/youtube/client/request_client.py @@ -172,6 +172,7 @@ class YouTubeRequestClient(BaseRequestsClass): }, 'ios': { '_id': 5, + '_auth_type': False, '_os': { 'major': '17', 'minor': '5', @@ -261,6 +262,7 @@ class YouTubeRequestClient(BaseRequestsClass): }, '_common': { '_access_token': None, + '_access_token_tv': None, 'json': { 'contentCheckOk': True, 'context': { @@ -286,13 +288,11 @@ class YouTubeRequestClient(BaseRequestsClass): 'videoId': None, }, 'headers': { - 'Origin': 'https://www.youtube.com', - 'Referer': 'https://www.youtube.com/watch?v={json[videoId]}', 'Accept-Encoding': 'gzip, deflate', 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', 'Accept': '*/*', 'Accept-Language': 'en-US,en;q=0.5', - 'Authorization': 'Bearer {_access_token}', + 'Authorization': None, }, 'params': { 'key': ValueError, @@ -369,31 +369,53 @@ def json_traverse(cls, json_data, path, default=None): def build_client(cls, client_name=None, data=None): templates = {} - client = None + base_client = None if client_name: - client = cls.CLIENTS.get(client_name) - if client and client.get('_disabled'): + base_client = cls.CLIENTS.get(client_name) + if base_client and base_client.get('_disabled'): return None - if not client: - client = YouTubeRequestClient.CLIENTS['web'] - client = client.copy() + if not base_client: + base_client = YouTubeRequestClient.CLIENTS['web'] + base_client = base_client.copy() if data: - client = merge_dicts(client, data) + client = merge_dicts(base_client, data) client = merge_dicts(cls.CLIENTS['_common'], client, templates) client['_name'] = client_name + if base_client.get('_auth_required'): + client['_auth_required'] = True for values, template_id, template in templates.values(): if template_id in values: values[template_id] = template.format(**client) + has_auth = False try: params = client['params'] - if client.get('_access_token'): + auth_required = client.get('_auth_required') + auth_requested = client.get('_auth_requested') + auth_type = client.get('_auth_type') + if auth_type == 'tv' and auth_requested != 'personal': + auth_token = client.get('_access_token_tv') + elif auth_type is not False: + auth_token = client.get('_access_token') + else: + auth_token = None + + if auth_token and (auth_required or auth_requested): + headers = client['headers'] + if 'Authorization' in headers: + headers = headers.copy() + headers['Authorization'] = 'Bearer {0}'.format(auth_token) + client['headers'] = headers + has_auth = True + if 'key' in params: params = params.copy() del params['key'] client['params'] = params + elif auth_required: + return None else: headers = client['headers'] if 'Authorization' in headers: @@ -407,5 +429,6 @@ def build_client(cls, client_name=None, data=None): client['params'] = params except KeyError: pass + client['_has_auth'] = has_auth return client diff --git a/resources/lib/youtube_plugin/youtube/client/youtube.py b/resources/lib/youtube_plugin/youtube/client/youtube.py index 332f6f54e..c68cdd160 100644 --- a/resources/lib/youtube_plugin/youtube/client/youtube.py +++ b/resources/lib/youtube_plugin/youtube/client/youtube.py @@ -85,6 +85,24 @@ class YouTube(LoginClient): 'Host': 'www.youtube.com', }, }, + 'watch_history': { + '_auth_required': True, + '_auth_type': 'personal', + '_video_id': None, + 'headers': { + 'Host': 's.youtube.com', + 'Referer': 'https://www.youtube.com/watch?v={_video_id}', + }, + 'params': { + 'referrer': 'https://accounts.google.com/', + 'ns': 'yt', + 'el': 'detailpage', + 'ver': '2', + 'fs': '0', + 'volume': '100', + 'muted': '0', + }, + }, '_common': { '_access_token': None, '_access_token_tv': None, @@ -155,29 +173,13 @@ def update_watch_history(self, context, video_id, url, status=None): et=et, state=state)) - headers = { - 'Host': 's.youtube.com', - 'Connection': 'keep-alive', - 'Accept-Encoding': 'gzip, deflate', - 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', - 'Accept': '*/*', - 'Accept-Language': 'en-US,en;q=0.5', - 'DNT': '1', - 'Referer': 'https://www.youtube.com/watch?v=' + video_id, - 'User-Agent': ('Mozilla/5.0 (Linux; Android 10; SM-G981B)' - ' AppleWebKit/537.36 (KHTML, like Gecko)' - ' Chrome/80.0.3987.162 Mobile Safari/537.36'), - } - params = { - 'docid': video_id, - 'referrer': 'https://accounts.google.com/', - 'ns': 'yt', - 'el': 'detailpage', - 'ver': '2', - 'fs': '0', - 'volume': '100', - 'muted': '0', + client_data = { + '_video_id': video_id, + 'url': url, + 'error_title': 'Failed to update watch history', } + + params = {} if cmt is not None: params['cmt'] = format(cmt, '.3f') if st is not None: @@ -186,11 +188,11 @@ def update_watch_history(self, context, video_id, url, status=None): params['et'] = format(et, '.3f') if state is not None: params['state'] = state - if self._access_token: - params['access_token'] = self._access_token - self.request(url, params=params, headers=headers, - error_msg='Failed to update watch history') + self.api_request(client='watch_history', + client_data=client_data, + params=params, + no_content=True) def get_streams(self, context, diff --git a/resources/lib/youtube_plugin/youtube/helper/stream_info.py b/resources/lib/youtube_plugin/youtube/helper/stream_info.py index e44833366..27c902f8c 100644 --- a/resources/lib/youtube_plugin/youtube/helper/stream_info.py +++ b/resources/lib/youtube_plugin/youtube/helper/stream_info.py @@ -703,7 +703,8 @@ def __init__(self, self._calculate_n = True self._cipher = None - self._selected_client = None + self._auth_client = {} + self._selected_client = {} self._client_groups = { 'custom': clients if clients else (), # Access "premium" streams, HLS and DASH @@ -1357,6 +1358,7 @@ def load_stream_info(self, video_id): audio_only = self._audio_only ask_for_quality = self._ask_for_quality use_mpd = self._use_mpd + use_remote_history = settings.use_remote_history() client_name = None _client = None @@ -1394,20 +1396,24 @@ def load_stream_info(self, video_id): } abort = False - client_data = {'json': {'videoId': video_id}} - access_token = self._access_token - auth = False + has_access_token = bool(self._access_token) + client_data = { + 'json': { + 'videoId': video_id, + }, + '_auth_required': False, + '_auth_requested': 'personal' if use_remote_history else False, + '_access_token': self._access_token, + } for name, clients in self._client_groups.items(): if not clients: continue - if name == 'mpd' and not use_mpd: + if name == 'mpd' and not (use_mpd or use_remote_history): continue if name == 'ask' and use_mpd and not ask_for_quality: continue - status = None - restart = False while 1: for client_name in clients: @@ -1424,7 +1430,7 @@ def load_stream_info(self, video_id): error_hook_kwargs={ 'video_id': video_id, 'client': client_name, - 'auth': bool(_client.get('_access_token')), + 'auth': _client.get('_has_auth', False), }, **_client ) or {} @@ -1469,14 +1475,13 @@ def load_stream_info(self, video_id): reason=reason or 'UNKNOWN', video_id=video_id, client=_client['_name'], - auth=auth, + auth=_client.get('_has_auth', False), ) ) compare_reason = reason.lower() if any(why in compare_reason for why in reauth_reasons): - if access_token and not auth: - auth = True - client_data['_access_token'] = access_token + if has_access_token: + client_data['_auth_required'] = True restart = True break if any(why in compare_reason for why in retry_reasons): @@ -1510,14 +1515,19 @@ def load_stream_info(self, video_id): .format( video_id=video_id, client=client_name, - auth=bool(_client.get('_access_token')), + auth=_client.get('_has_auth', False), ) ) if not self._selected_client: - client = self._selected_client = _client.copy() - result = _result - video_details = result.get('videoDetails', {}) - playability = result.get('playabilityStatus', {}) + self._selected_client = { + 'client': _client.copy(), + 'result': _result, + } + if not self._auth_client and _client.get('_has_auth'): + self._auth_client = { + 'client': _client.copy(), + 'result': _result, + } _streaming_data = _result.get('streamingData', {}) if audio_only or ask_for_quality or not use_mpd: @@ -1549,6 +1559,9 @@ def load_stream_info(self, video_id): reason = self._get_error_details(playability) raise YouTubeException(reason or 'UNKNOWN') + client = self._selected_client['client'] + result = self._selected_client['result'] + if 'Authorization' in client['headers']: del client['headers']['Authorization'] # Make a set of URL-quoted headers to be sent to Kodi when requesting @@ -1557,8 +1570,7 @@ def load_stream_info(self, video_id): # curl_headers = self._make_curl_headers(headers, cookies) curl_headers = self._prepare_headers(client['headers']) - microformat = (result.get('microformat', {}) - .get('playerMicroformatRenderer', {})) + video_details = result.get('videoDetails', {}) is_live = video_details.get('isLiveContent', False) if is_live: is_live = video_details.get('isLive', False) @@ -1568,6 +1580,8 @@ def load_stream_info(self, video_id): live_dvr = False thumb_suffix = '' + microformat = (result.get('microformat', {}) + .get('playerMicroformatRenderer', {})) meta_info = { 'id': video_id, 'title': unescape(video_details.get('title', '') @@ -1597,12 +1611,14 @@ def load_stream_info(self, video_id): 'subtitles': None, } - if settings.use_remote_history(): + if use_remote_history and self._auth_client: playback_stats = { 'playback_url': 'videostatsPlaybackUrl', 'watchtime_url': 'videostatsWatchtimeUrl', } - playback_tracking = result.get('playbackTracking', {}) + playback_tracking = (self._auth_client + .get('result', {}) + .get('playbackTracking', {})) cpn = self._generate_cpn() for key, url_key in playback_stats.items(): @@ -1637,7 +1653,7 @@ def load_stream_info(self, video_id): '', '', )) + '||R{{SSM}}|R', - 'token': access_token, + 'token': self._access_token, } break else: @@ -1699,7 +1715,7 @@ def load_stream_info(self, video_id): error_hook_kwargs={ 'video_id': video_id, 'client': client_name, - 'auth': bool(caption_client.get('_access_token')), + 'auth': _client.get('_has_auth', False), }, **caption_client )