Skip to content

Commit

Permalink
Only use OAuth token if necessary #994 #996
Browse files Browse the repository at this point in the history
- Workaround for issues with family/linked accounts
- Workaround for potential future restrictions on use of OAuth
  • Loading branch information
MoojMidge committed Dec 7, 2024
1 parent 5be63c2 commit bf50321
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 83 deletions.
10 changes: 6 additions & 4 deletions resources/lib/youtube_plugin/youtube/client/request_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ class YouTubeRequestClient(BaseRequestsClass):
'android_youtube_tv': {
'_id': 29,
'_disabled': True,
'_auth_required': 'tv',
'_auth_type': 'tv',
'_query_subtitles': True,
'json': {
'params': _PLAYER_PARAMS['android'],
Expand Down Expand Up @@ -173,6 +173,7 @@ class YouTubeRequestClient(BaseRequestsClass):
},
'ios': {
'_id': 5,
'_auth_type': False,
'_os': {
'major': '18',
'minor': '1',
Expand Down Expand Up @@ -392,14 +393,15 @@ def build_client(cls, client_name=None, data=None):
try:
params = client['params']
auth_required = client.get('_auth_required')
if auth_required == 'tv':
auth_type = client.get('_auth_type')
if auth_type == 'tv':
auth_token = client.get('_access_token_tv')
elif auth_required is not False:
elif auth_type is not False:
auth_token = client.get('_access_token')
else:
auth_token = None

if auth_token:
if auth_required and auth_token:
headers = client['headers']
if 'Authorization' in headers:
headers = headers.copy()
Expand Down
1 change: 1 addition & 0 deletions resources/lib/youtube_plugin/youtube/client/youtube.py
Original file line number Diff line number Diff line change
Expand Up @@ -2255,6 +2255,7 @@ def api_request(self,

abort = False
if not no_login:
client_data.setdefault('_auth_required', True)
# a config can decide if a token is allowed
if self._access_token and self._config.get('token-allowed', True):
client_data['_access_token'] = self._access_token
Expand Down
178 changes: 99 additions & 79 deletions resources/lib/youtube_plugin/youtube/helper/stream_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,7 @@ class StreamInfo(YouTubeRequestClient):
def __init__(self,
context,
access_token='',
access_token_tv='',
clients=None,
ask_for_quality=False,
audio_only=False,
Expand All @@ -766,6 +767,7 @@ def __init__(self,
self._context = context

self._access_token = access_token
self._access_token_tv = access_token_tv
self._ask_for_quality = ask_for_quality
self._audio_only = audio_only
self._use_mpd = use_mpd
Expand Down Expand Up @@ -1445,9 +1447,11 @@ def load_stream_info(self, video_id):
'country',
'not available',
}
skip_reasons = {
reauth_reasons = {
'age',
'inappropriate',
}
skip_reasons = {
'latest version',
}
retry_reasons = {
Expand All @@ -1457,12 +1461,15 @@ def load_stream_info(self, video_id):
}
abort = False

client_data = {'json': {'videoId': video_id}}
if self._access_token:
auth = True
client_data['_access_token'] = self._access_token
else:
auth = False
has_access_token = bool(self._access_token or self._access_token_tv)
client_data = {
'json': {
'videoId': video_id,
},
'_auth_required': False,
'_access_token': self._access_token,
'_access_token_tv': self._access_token_tv,
}

for name, clients in self._client_groups.items():
if not clients:
Expand All @@ -1472,81 +1479,94 @@ def load_stream_info(self, video_id):
if name == 'ask' and use_mpd and not ask_for_quality:
continue

for client_name in clients:
_client = self.build_client(client_name, client_data)
if not _client:
continue

_result = self.request(
video_info_url,
'POST',
response_hook=self._response_hook_json,
error_title='Player request failed',
error_hook=self._error_hook,
error_hook_kwargs={
'video_id': video_id,
'client': client_name,
'auth': _client.get('_has_auth', False),
},
**_client
) or {}

_video_details = _result.get('videoDetails', {})
_playability = _result.get('playabilityStatus', {})
_status = _playability.get('status', 'ERROR').upper()
_reason = _playability.get('reason', 'UNKNOWN')

if _video_details and video_id != _video_details.get('videoId'):
_status = 'CONTENT_NOT_AVAILABLE_IN_THIS_APP'
_reason = 'Watch on the latest version of YouTube'

if (age_gate_enabled
and _playability.get('desktopLegacyAgeGateReason')):
abort = True
break
elif _status == 'LIVE_STREAM_OFFLINE':
abort = True
break
elif _status == 'OK':
break
elif _status in {
'AGE_CHECK_REQUIRED',
'AGE_VERIFICATION_REQUIRED',
'CONTENT_CHECK_REQUIRED',
'LOGIN_REQUIRED',
'CONTENT_NOT_AVAILABLE_IN_THIS_APP',
'ERROR',
'UNPLAYABLE',
}:
log_warning(
'Failed to retrieve video info'
'\n\tStatus: {status}'
'\n\tReason: {reason}'
'\n\tvideo_id: |{video_id}|'
'\n\tClient: |{client}|'
'\n\tAuth: |{auth}|'
.format(
status=_status,
reason=_reason or 'UNKNOWN',
video_id=video_id,
client=_client['_name'],
auth=_client.get('_has_auth', False),
)
)
compare_reason = _reason.lower()
if any(why in compare_reason for why in retry_reasons):
restart = False
while 1:
for client_name in clients:
_client = self.build_client(client_name, client_data)
if not _client:
continue
if any(why in compare_reason for why in skip_reasons):

_result = self.request(
video_info_url,
'POST',
response_hook=self._response_hook_json,
error_title='Player request failed',
error_hook=self._error_hook,
error_hook_kwargs={
'video_id': video_id,
'client': client_name,
'auth': _client.get('_has_auth', False),
},
**_client
) or {}

_video_details = _result.get('videoDetails', {})
_playability = _result.get('playabilityStatus', {})
_status = _playability.get('status', 'ERROR').upper()
_reason = _playability.get('reason', 'UNKNOWN')

if (_video_details
and video_id != _video_details.get('videoId')):
_status = 'CONTENT_NOT_AVAILABLE_IN_THIS_APP'
_reason = 'Watch on the latest version of YouTube'

if (age_gate_enabled
and _playability.get('desktopLegacyAgeGateReason')):
abort = True
break
if any(why in compare_reason for why in abort_reasons):
elif _status == 'LIVE_STREAM_OFFLINE':
abort = True
break
elif _status == 'OK':
break
elif _status in {
'AGE_CHECK_REQUIRED',
'AGE_VERIFICATION_REQUIRED',
'CONTENT_CHECK_REQUIRED',
'LOGIN_REQUIRED',
'CONTENT_NOT_AVAILABLE_IN_THIS_APP',
'ERROR',
'UNPLAYABLE',
}:
log_warning(
'Failed to retrieve video info'
'\n\tStatus: {status}'
'\n\tReason: {reason}'
'\n\tvideo_id: |{video_id}|'
'\n\tClient: |{client}|'
'\n\tAuth: |{auth}|'
.format(
status=_status,
reason=_reason or 'UNKNOWN',
video_id=video_id,
client=_client['_name'],
auth=_client.get('_has_auth', False),
)
)
compare_reason = _reason.lower()
if any(why in compare_reason for why in reauth_reasons):
if has_access_token:
client_data['_auth_required'] = True
restart = True
break
if any(why in compare_reason for why in retry_reasons):
continue
if any(why in compare_reason for why in skip_reasons):
break
if any(why in compare_reason for why in abort_reasons):
abort = True
break
else:
log_debug(
'Unknown playabilityStatus in player response'
'\n\tplayabilityStatus: {0}'
.format(_playability)
)
else:
log_debug(
'Unknown playabilityStatus in player response'
'\n\tplayabilityStatus: {0}'
.format(_playability)
)
break
if not restart:
break
restart = False

if abort:
break
Expand All @@ -1560,7 +1580,7 @@ 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:
Expand Down Expand Up @@ -1748,7 +1768,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
)
Expand Down

0 comments on commit bf50321

Please sign in to comment.