Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v7.1.0 #917

Merged
merged 9 commits into from
Oct 2, 2024
2 changes: 1 addition & 1 deletion addon.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.youtube" name="YouTube" version="7.1.0+beta.5" provider-name="anxdpanic, bromix, MoojMidge">
<addon id="plugin.video.youtube" name="YouTube" version="7.1.0" provider-name="anxdpanic, bromix, MoojMidge">
<requires>
<import addon="xbmc.python" version="3.0.0"/>
<import addon="script.module.requests" version="2.27.1"/>
Expand Down
60 changes: 58 additions & 2 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,66 @@
## v7.1.0
### Fixed
- Fix logging/retry of sqlite3.OperationalError
- Fix trying to use ISA for progressive live streams
- Retain list position when refreshing listings
- Add workarounds for trying to play videos using RunPlugin rather PlayMedia
- Fix possible regression causing 6s delay on first play
- Fix regression in building client details causing wrong referer to be used
- Only reroute to new window if container was not filled by the plugin #896
- Only reroute to new window if modal dialog is not open #896
- Fix various timing and sync issues with script, service, plugin and Kodi settings
- Fix playback history related context menu items not being shown #904
- Fix new resume details not being saved in plugin local playback history #904
- Fix default thumbnails not being updated if available
- Fix login to personal project only #910

### Changed
- Update multiple busy dialog crash workaround #891
- Use all playable codecs but deprioritise if not selected in stream features
- Change default live stream type to suit ISA and Youtube stream availability
- Use a CommandItem that opens the Info dialog for comments to avoid log spam
- Revert old workaround for Kodi treating a non-folder listitem as playable
- Improve parsing of plugin url query parameters to allow empty values
- Move IP location lookup to script and add to settings dialog
- Move language and region selection to script and add to settings dialog
- Allow default thumbnail selection fallbacks for all results
- Simplify handling of local history
- Incognito will no longer prevent existing local history from being shown
- Incognito will continue to prevent any update to local history
- Remove revoked API credentials #905
- Improve handling of "forbidden - The caller does not have permission" errors #905
- Allow retries on POST requests #913

### New
- Allow ask for quality from context menu to override audio only setting
- Add items_per_page query parameter to allow number of items in widgets to be customised #896
- Add item_filter query parameter to override "Hide videos from listings" setting #896
- Comma seperated string of item types to be filtered out of listing
- "?item_filter=shorts" will remove shorts from listing
- "?item_filter=shorts,live" will remove shorts and live streams from listing
- "?item_filter" will show all item types
- Allowable item types:
- shorts
- upcoming
- upcoming_live
- live
- premieres
- completed
- vod
- Add hide_next_page query parameter to hide plugin Next page item #896
- Add new input_prompt command for search endpoint to bypass Kodi window caching
- plugin://plugin.video.youtube/kodion/search/input_prompt
- Add support for proxy settings #884
- Preliminary support for /watch_videos YouTube urls
- Player client updates

## v7.1.0+beta.5
### Fixed
- Fix default thumbnails not being updated if available

### Changed
- Remove revoked API credentials
- Improve handling of "forbidden - The caller does not have permission" errors
- Remove revoked API credentials #905
- Improve handling of "forbidden - The caller does not have permission" errors #905

### New
- Preliminary support for /watch_videos YouTube urls
Expand Down
4 changes: 2 additions & 2 deletions resources/language/resource.language.en_gb/strings.po
Original file line number Diff line number Diff line change
Expand Up @@ -490,11 +490,11 @@ msgid "No further links found."
msgstr ""

msgctxt "#30546"
msgid "Please log in twice!"
msgid "Please complete all login prompts"
msgstr ""

msgctxt "#30547"
msgid "You must enable two applications so that YouTube is functioning properly."
msgid "You may be prompted to enable two applications so that YouTube is functioning properly."
msgstr ""

msgctxt "#30548"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,8 @@ class XbmcContext(AbstractContext):
'sign.go_to': 30502,
'sign.in': 30111,
'sign.out': 30112,
'sign.twice.text': 30547,
'sign.twice.title': 30546,
'sign.multi.text': 30547,
'sign.multi.title': 30546,
'stats.commentCount': 30732,
# 'stats.favoriteCount': 1036,
'stats.likeCount': 30733,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ def update_access_token(self,

if unix_timestamp is not None:
details['token_expires'] = (
min(map(int, unix_timestamp))
min(map(int, [val for val in unix_timestamp if val]))
if isinstance(unix_timestamp, (list, tuple)) else
int(unix_timestamp)
)
Expand Down
1 change: 1 addition & 0 deletions resources/lib/youtube_plugin/kodion/network/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class BaseRequestsClass(object):
total=3,
backoff_factor=0.1,
status_forcelist={500, 502, 503, 504},
allowed_methods=None,
)
))
atexit.register(_session.close)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
CONTAINER_FOCUS,
CONTAINER_ID,
CONTAINER_POSITION,
CONTENT_TYPE,
PLAYLIST_PATH,
PLAYLIST_POSITION,
PLUGIN_SLEEPING,
Expand Down Expand Up @@ -258,6 +259,7 @@ def run(self, provider, context, focused=None):
cache_to_disc = options.get(provider.RESULT_CACHE_TO_DISC, True)
update_listing = options.get(provider.RESULT_UPDATE_LISTING, False)
else:
ui.clear_property(CONTENT_TYPE)
succeeded = bool(result)
cache_to_disc = False
update_listing = True
Expand Down
3 changes: 2 additions & 1 deletion resources/lib/youtube_plugin/youtube/client/__config__.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ def get_api_keys(self, switch):
key = key.partition('_')[-1]
if key and key in key_set:
key_set[key] = value
if not key_set['id'].endswith('.apps.googleusercontent.com'):
if (key_set['id']
and not key_set['id'].endswith('.apps.googleusercontent.com')):
key_set['id'] += '.apps.googleusercontent.com'
return key_set

Expand Down
51 changes: 32 additions & 19 deletions resources/lib/youtube_plugin/youtube/client/login_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,11 @@ def _error_hook(**kwargs):
return None, None, None, json_data, False, InvalidGrant(json_data)
return None, None, None, json_data, False, LoginException(json_data)

def set_access_token(self, access_token=''):
self._access_token = access_token

def set_access_token_tv(self, access_token_tv=''):
self._access_token_tv = access_token_tv
def set_access_token(self, personal=None, tv=None):
if personal is not None:
self._access_token = personal
if tv is not None:
self._access_token_tv = tv

def revoke(self, refresh_token):
# https://developers.google.com/youtube/v3/guides/auth/devices
Expand All @@ -107,8 +107,10 @@ def revoke(self, refresh_token):
raise_exc=True)

def refresh_token_tv(self, refresh_token):
client_id = self._config_tv.get('id', '')
client_secret = self._config_tv.get('secret', '')
client_id = self._config_tv.get('id')
client_secret = self._config_tv.get('secret')
if not client_id or not client_secret:
return '', 0
return self.refresh_token(refresh_token,
client_id=client_id,
client_secret=client_secret)
Expand Down Expand Up @@ -151,13 +153,15 @@ def refresh_token(self, refresh_token, client_id='', client_secret=''):

if json_data:
access_token = json_data['access_token']
expires_in = time.time() + int(json_data.get('expires_in', 3600))
return access_token, expires_in
return '', ''
expiry = time.time() + int(json_data.get('expires_in', 3600))
return access_token, expiry
return '', 0

def request_access_token_tv(self, code, client_id='', client_secret=''):
client_id = client_id or self._config_tv.get('id', '')
client_secret = client_secret or self._config_tv.get('secret', '')
client_id = client_id or self._config_tv.get('id')
client_secret = client_secret or self._config_tv.get('secret')
if not client_id or not client_secret:
return '', ''
return self.request_access_token(code,
client_id=client_id,
client_secret=client_secret)
Expand Down Expand Up @@ -200,7 +204,9 @@ def request_access_token(self, code, client_id='', client_secret=''):
return json_data

def request_device_and_user_code_tv(self):
client_id = self._config_tv.get('id', '')
client_id = self._config_tv.get('id')
if not client_id:
return None
return self.request_device_and_user_code(client_id=client_id)

def request_device_and_user_code(self, client_id=''):
Expand Down Expand Up @@ -282,17 +288,24 @@ def authenticate(self, username, password):
def _get_config_type(self, client_id, client_secret=None):
"""used for logging"""
if client_secret is None:
using_conf_tv = client_id == self._config_tv.get('id', '')
using_conf_main = client_id == self._config.get('id', '')
config_id = self._config_tv.get('id')
using_conf_tv = config_id and client_id == config_id
config_id = self._config.get('id')
using_conf_main = config_id and client_id == config_id
else:
config_secret = self._config_tv.get('secret')
config_id = self._config_tv.get('id')
using_conf_tv = (
client_secret == self._config_tv.get('secret', '')
and client_id == self._config_tv.get('id', '')
config_secret and client_secret == config_secret
and config_id and client_id == config_id
)
config_secret = self._config.get('secret')
config_id = self._config.get('id')
using_conf_main = (
client_secret == self._config.get('secret', '')
and client_id == self._config.get('id', '')
config_secret and client_secret == config_secret
and config_id and client_id == config_id
)

if not using_conf_main and not using_conf_tv:
return 'None'
if using_conf_tv:
Expand Down
4 changes: 2 additions & 2 deletions resources/lib/youtube_plugin/youtube/client/request_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,11 +154,11 @@ class YouTubeRequestClient(BaseRequestsClass):
'deviceModel': 'Quest 3',
'osName': 'Android',
'osVersion': '12L',
'androidSdkVersion': '32'
'androidSdkVersion': '32',
}
}
},
'header': {
'headers': {
'User-Agent': ('com.google.android.apps.youtube.vr.oculus/'
'{json[context][client][clientVersion]}'
' (Linux; U; {json[context][client][osName]}'
Expand Down
72 changes: 44 additions & 28 deletions resources/lib/youtube_plugin/youtube/client/youtube.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class YouTube(LoginClient):
'headers': {
'Host': 'www.googleapis.com',
},
'auth_required': True,
},
'tv': {
'url': 'https://www.youtube.com/youtubei/v1/{_endpoint}',
Expand Down Expand Up @@ -194,7 +195,8 @@ def get_streams(self,
audio_only=False,
use_mpd=True):
return StreamInfo(context,
access_token=self._access_token_tv,
access_token=(self._access_token
or self._access_token_tv),
ask_for_quality=ask_for_quality,
audio_only=audio_only,
use_mpd=use_mpd).load_stream_info(video_id)
Expand Down Expand Up @@ -2032,22 +2034,31 @@ def api_request(self,
if params:
client_data['params'] = params

abort = False
if no_login:
pass
# a config can decide if a token is allowed
if (not no_login and self._access_token
and self._config.get('token-allowed', True)):
elif self._access_token and self._config.get('token-allowed', True):
client_data['_access_token'] = self._access_token
# abort if authentication is required but not available for request
elif self.CLIENTS.get(version, {}).get('auth_required'):
abort = True

client = self.build_client(version, client_data)

params = client.get('params')
if 'key' in params and not params['key']:
params = params.copy()
key = self._config.get('key') or self._config_tv.get('key')
if key:
params['key'] = key
if 'key' in params:
if params['key']:
abort = False
else:
del params['key']
client['params'] = params
params = params.copy()
key = self._config.get('key') or self._config_tv.get('key')
if key:
abort = False
params['key'] = key
else:
del params['key']
client['params'] = params

if clear_data and 'json' in client:
del client['json']
Expand All @@ -2070,21 +2081,26 @@ def api_request(self,
else:
log_headers = None

self._context.log_debug('API request:\n'
'version: |{version}|\n'
'method: |{method}|\n'
'path: |{path}|\n'
'params: |{params}|\n'
'post_data: |{data}|\n'
'headers: |{headers}|'
.format(version=version,
method=method,
path=path,
params=log_params,
data=client.get('json'),
headers=log_headers))
response = self.request(response_hook=self._response_hook,
response_hook_kwargs=kwargs,
error_hook=self._error_hook,
**client)
return response
context = self._context
context.log_debug('API request:\n'
'version: |{version}|\n'
'method: |{method}|\n'
'path: |{path}|\n'
'params: |{params}|\n'
'post_data: |{data}|\n'
'headers: |{headers}|'
.format(version=version,
method=method,
path=path,
params=log_params,
data=client.get('json'),
headers=log_headers))
if abort:
if kwargs.get('notify', True):
context.get_ui().on_ok(context.get_name(), context.localize('key.requirement'))
context.log_warning('API request: aborted')
return {}
return self.request(response_hook=self._response_hook,
response_hook_kwargs=kwargs,
error_hook=self._error_hook,
**client)
2 changes: 1 addition & 1 deletion resources/lib/youtube_plugin/youtube/helper/stream_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -1408,7 +1408,7 @@ def load_stream_info(self, video_id):
'auth': bool(_client.get('_access_token')),
},
**_client
)
) or {}

video_details = _result.get('videoDetails', {})
playability = _result.get('playabilityStatus', {})
Expand Down
Loading
Loading