From baf4f4f440976009f1dbc68f061003eb8a994d97 Mon Sep 17 00:00:00 2001 From: MoojMidge <56883549+MoojMidge@users.noreply.github.com> Date: Sat, 21 Mar 2026 10:47:08 +0900 Subject: [PATCH 01/18] Update XbmcContext.is_plugin_folder() to match partial paths by default --- .../youtube_plugin/kodion/context/xbmc/xbmc_context.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py index 7e13d584..e1437060 100644 --- a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py +++ b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py @@ -1091,18 +1091,17 @@ class XbmcContext(AbstractContext): ) return value - def is_plugin_folder(self, folder_path='', name=False): + def is_plugin_folder(self, folder_path='', name=False, partial=True): if name: return XbmcContextUI.get_container_info( FOLDER_NAME, - container_id=None, ) == self._plugin_name return self.is_plugin_path( - XbmcContextUI.get_container_info( + uri=XbmcContextUI.get_container_info( FOLDER_URI, - container_id=None, ), - folder_path, + uri_path=folder_path, + partial=partial, ) def refresh_requested(self, force=False, on=False, off=False, params=None): From b0345b142f6372a76b8384388c5a6814918f91e4 Mon Sep 17 00:00:00 2001 From: MoojMidge <56883549+MoojMidge@users.noreply.github.com> Date: Mon, 30 Mar 2026 13:47:01 +0900 Subject: [PATCH 02/18] Invalidate expired access tokens only if already stored --- resources/lib/youtube_plugin/youtube/provider.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py index a8114ade..3fbf9abe 100644 --- a/resources/lib/youtube_plugin/youtube/provider.py +++ b/resources/lib/youtube_plugin/youtube/provider.py @@ -259,7 +259,8 @@ class Provider(AbstractProvider): ) = access_manager.get_refresh_tokens(dev_id) if not num_access_tokens and not num_refresh_tokens: - access_manager.update_access_token(dev_id, access_token='') + if any(access_tokens): + access_manager.update_access_token(dev_id, access_token='') return client if num_access_tokens == num_refresh_tokens and client.logged_in: return client From 0dc2d76a20ca393be2e2e59803eedb4b1a0608ce Mon Sep 17 00:00:00 2001 From: MoojMidge <56883549+MoojMidge@users.noreply.github.com> Date: Mon, 30 Mar 2026 11:46:33 +0900 Subject: [PATCH 03/18] Use consistent language for signing in/out --- resources/language/resource.language.en_gb/strings.po | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index 5c7a6ed2..f1b71433 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -490,11 +490,11 @@ msgid "No videos found." msgstr "" msgctxt "#30546" -msgid "Please complete all login prompts" +msgid "Please sign in and complete all access authorisation prompts" msgstr "" msgctxt "#30547" -msgid "You may be prompted to login and enable access to multiple applications so that this addon can function properly." +msgid "You may be prompted to sign in and enable access to multiple applications so that this addon can function properly." msgstr "" msgctxt "#30548" @@ -1166,7 +1166,7 @@ msgid "Prefer automatically translated dubbed audio over original audio" msgstr "" msgctxt "#30715" -msgid "Use YouTube internal list for Watch History?[CR][CR]Requires signing-in via the addon and activating history tracking on YouTube." +msgid "Use YouTube internal list for Watch History?[CR][CR]Requires signing in via the addon and activating history tracking on YouTube." msgstr "" msgctxt "#30716" @@ -1178,7 +1178,7 @@ msgid "Disliked video" msgstr "" msgctxt "#30718" -msgid "Use YouTube internal list for Watch Later?[CR][CR]Requires signing-in via the addon." +msgid "Use YouTube internal list for Watch Later?[CR][CR]Requires signing in via the addon." msgstr "" msgctxt "#30719" From 34f763953a854fc8d0c20c58bbf2e3bc0ec260f4 Mon Sep 17 00:00:00 2001 From: MoojMidge <56883549+MoojMidge@users.noreply.github.com> Date: Sat, 28 Mar 2026 23:06:06 +0900 Subject: [PATCH 04/18] Detect and notify when API requests cannot be completed - Provide specific user notifications for the various different causes - Force cache use when requests can't be made --- .../youtube/client/data_client.py | 55 +++++++++---- .../youtube/helper/resource_manager.py | 82 +++++++++++-------- 2 files changed, 88 insertions(+), 49 deletions(-) diff --git a/resources/lib/youtube_plugin/youtube/client/data_client.py b/resources/lib/youtube_plugin/youtube/client/data_client.py index 01e0e742..5fe96d59 100644 --- a/resources/lib/youtube_plugin/youtube/client/data_client.py +++ b/resources/lib/youtube_plugin/youtube/client/data_client.py @@ -2976,6 +2976,13 @@ class YouTubeDataClient(YouTubeLoginClient): v3_response['_item_filter'] = item_filter return v3_response + @classmethod + def v3_api_available(cls): + user_config = cls._configs.get('user') + if user_config: + return bool(user_config.get('key')) + return False + def _auth_required(self, params): if params: if params.get('mine') or params.get('forMine'): @@ -3105,15 +3112,30 @@ class YouTubeDataClient(YouTubeLoginClient): do_auth=None, cache=None, **kwargs): + context = self._context + + if client == 'v3' and not self.v3_api_available(): + abort = True + abort_msg = 'Request skipped: API key not provided' + abort_prompt = 'key.requirement' + else: + abort = False + abort_msg = None + abort_prompt = None + if not client_data: client_data = {} - client_data.setdefault('method', method) + if path: client_data['_endpoint'] = path.strip('/') + if url: client_data['url'] = url + if headers: client_data['headers'] = headers + + client_data.setdefault('method', method) if method in {'POST', 'PUT'}: if post_data: client_data['json'] = post_data @@ -3124,16 +3146,18 @@ class YouTubeDataClient(YouTubeLoginClient): if do_auth is None and method == 'DELETE': do_auth = True clear_data = True + if params: client_data['params'] = params if do_auth is None: do_auth = self._auth_required(params) if do_auth: - abort = not self.logged_in + if not self.logged_in: + abort = True + abort_msg = 'Request skipped: Authorisation required' + abort_prompt = 'sign.multi.title' client_data.setdefault('_auth_required', do_auth) - else: - abort = False client_data['_access_tokens'] = access_tokens = {} client_data['_api_keys'] = api_keys = {} @@ -3161,17 +3185,18 @@ class YouTubeDataClient(YouTubeLoginClient): params = client.get('params') if params and 'key' in params: key = params['key'] - if key: - abort = False - elif not client['_has_auth']: + if not key and not client['_has_auth']: abort = True + abort_msg = 'Request skipped: API key not provided' + abort_prompt = 'key.requirement' else: client_data.setdefault('_name', client) client = client_data params = client.get('params') abort = True + abort_msg = 'Request skipped: Invalid or disabled client' + abort_prompt = None - context = self._context self.log.debug(('{request_name} API request', 'method: {method!r}', 'path: {path!u}', @@ -3185,18 +3210,18 @@ class YouTubeDataClient(YouTubeLoginClient): data=client.get('json'), headers=client.get('headers'), stacklevel=2) + if abort: - if kwargs.get('notify', True): - context.get_ui().on_ok( - context.get_name(), - context.localize('key.requirement'), - ) - self.log.warning('Aborted', stacklevel=2) + if abort_prompt and kwargs.get('notify', True): + context.get_ui().on_ok(context.get_name(), abort_prompt) + self.log.warning(abort_msg, stacklevel=2) return {} + if cache is None and 'no_content' in kwargs: cache = False - elif cache is not False and self._context.refresh_requested(): + elif cache is not False and context.refresh_requested(): cache = 'refresh' + return self.request(response_hook=self._request_response_hook, event_hook_kwargs=kwargs, error_hook=self._request_error_hook, diff --git a/resources/lib/youtube_plugin/youtube/helper/resource_manager.py b/resources/lib/youtube_plugin/youtube/helper/resource_manager.py index 9bb034c5..10267365 100644 --- a/resources/lib/youtube_plugin/youtube/helper/resource_manager.py +++ b/resources/lib/youtube_plugin/youtube/helper/resource_manager.py @@ -61,15 +61,18 @@ class ResourceManager(object): def get_channels(self, ids, suppress_errors=False, defer_cache=False): context = self._context client = self._client - data_cache = context.get_data_cache() + function_cache = context.get_function_cache() refresh = context.refresh_requested() - forced_cache = not function_cache.run( - client.internet_available, - function_cache.ONE_MINUTE * 5, - _refresh=refresh, - ) + if not client.v3_api_available(): + forced_cache = True + else: + forced_cache = not function_cache.run( + client.internet_available, + function_cache.ONE_MINUTE * 5, + _refresh=refresh, + ) refresh = not forced_cache and refresh updated = [] @@ -97,6 +100,7 @@ class ResourceManager(object): if refresh or not ids: result = {} else: + data_cache = context.get_data_cache() result = data_cache.get_items( ids, None if forced_cache else data_cache.ONE_DAY, @@ -168,14 +172,17 @@ class ResourceManager(object): defer_cache=False): context = self._context client = self._client - function_cache = context.get_function_cache() refresh = context.refresh_requested() - forced_cache = not function_cache.run( - client.internet_available, - function_cache.ONE_MINUTE * 5, - _refresh=refresh, - ) + if not client.v3_api_available(): + forced_cache = True + else: + function_cache = context.get_function_cache() + forced_cache = not function_cache.run( + client.internet_available, + function_cache.ONE_MINUTE * 5, + _refresh=refresh, + ) refresh = not forced_cache and refresh if not refresh and channel_data: @@ -289,14 +296,17 @@ class ResourceManager(object): context = self._context client = self._client - function_cache = context.get_function_cache() refresh = context.refresh_requested() - forced_cache = not function_cache.run( - client.internet_available, - function_cache.ONE_MINUTE * 5, - _refresh=refresh, - ) + if not client.v3_api_available(): + forced_cache = True + else: + function_cache = context.get_function_cache() + forced_cache = not function_cache.run( + client.internet_available, + function_cache.ONE_MINUTE * 5, + _refresh=refresh, + ) refresh = not forced_cache and refresh if refresh or not ids: @@ -379,18 +389,19 @@ class ResourceManager(object): context = self._context client = self._client - function_cache = context.get_function_cache() refresh = context.refresh_requested() - forced_cache = ( - not function_cache.run( - client.internet_available, - function_cache.ONE_MINUTE * 5, - _refresh=refresh, - ) - or (context.get_param(CHANNEL_ID) == 'mine' - and not client.logged_in) - ) + if not client.v3_api_available(): + forced_cache = True + elif not client.logged_in and context.get_param(CHANNEL_ID) == 'mine': + forced_cache = True + else: + function_cache = context.get_function_cache() + forced_cache = not function_cache.run( + client.internet_available, + function_cache.ONE_MINUTE * 5, + _refresh=refresh, + ) refresh = not forced_cache and refresh if batch_id: @@ -564,14 +575,17 @@ class ResourceManager(object): context = self._context client = self._client - function_cache = context.get_function_cache() refresh = context.refresh_requested() - forced_cache = not function_cache.run( - client.internet_available, - function_cache.ONE_MINUTE * 5, - _refresh=refresh, - ) + if not client.v3_api_available(): + forced_cache = True + else: + function_cache = context.get_function_cache() + forced_cache = not function_cache.run( + client.internet_available, + function_cache.ONE_MINUTE * 5, + _refresh=refresh, + ) refresh = not forced_cache and refresh if refresh or not ids: From 9cca37c97324a87cee3d675e79d73b1d47d1dba0 Mon Sep 17 00:00:00 2001 From: MoojMidge <56883549+MoojMidge@users.noreply.github.com> Date: Mon, 30 Mar 2026 11:54:03 +0900 Subject: [PATCH 05/18] Add fallback method to load playlists when v3 API request cannot be made --- .../youtube/helper/resource_manager.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/resources/lib/youtube_plugin/youtube/helper/resource_manager.py b/resources/lib/youtube_plugin/youtube/helper/resource_manager.py index 10267365..ee28c7e4 100644 --- a/resources/lib/youtube_plugin/youtube/helper/resource_manager.py +++ b/resources/lib/youtube_plugin/youtube/helper/resource_manager.py @@ -391,9 +391,8 @@ class ResourceManager(object): client = self._client refresh = context.refresh_requested() - if not client.v3_api_available(): - forced_cache = True - elif not client.logged_in and context.get_param(CHANNEL_ID) == 'mine': + v3_api_available = client.v3_api_available() + if not client.logged_in and context.get_param(CHANNEL_ID) == 'mine': forced_cache = True else: function_cache = context.get_function_cache() @@ -478,7 +477,18 @@ class ResourceManager(object): batch_id = (playlist_id, page_token) if batch_id in result: break - batch = client.get_playlist_items(*batch_id, **kwargs) + batch = ( + client.get_playlist_items(*batch_id, **kwargs) + if v3_api_available else + client.get_browse_items( + browse_id='VL' + playlist_id, + playlist_id=playlist_id, + page_token=page_token, + response_type='playlistItems', + client='tv', + json_path=client.JSON_PATHS['tv_playlist'], + ) + ) if not batch: break new_batch_ids.append(batch_id) From b051092f9ab54b9ae04c14d0c052d8f50aad8006 Mon Sep 17 00:00:00 2001 From: MoojMidge <56883549+MoojMidge@users.noreply.github.com> Date: Wed, 25 Mar 2026 12:02:01 +0900 Subject: [PATCH 06/18] Improve handling of API key settings changes - Allow settings to sync back to api_keys.json when actually changed --- .../kodion/constants/__init__.py | 2 + .../kodion/json_store/api_keys.py | 106 ++++++++++-------- .../kodion/monitors/service_monitor.py | 18 ++- .../kodion/network/http_server.py | 2 + .../kodion/plugin/xbmc/xbmc_plugin.py | 4 + 5 files changed, 87 insertions(+), 45 deletions(-) diff --git a/resources/lib/youtube_plugin/kodion/constants/__init__.py b/resources/lib/youtube_plugin/kodion/constants/__init__.py index e3cc93cd..7acb8698 100644 --- a/resources/lib/youtube_plugin/kodion/constants/__init__.py +++ b/resources/lib/youtube_plugin/kodion/constants/__init__.py @@ -115,6 +115,7 @@ PLAYBACK_STOPPED = 'playback_stopped' REFRESH_CONTAINER = 'refresh_container' RELOAD_ACCESS_MANAGER = 'reload_access_manager' SERVICE_IPC = 'service_ipc' +SYNC_API_KEYS = 'sync_api_keys' SYNC_LISTITEM = 'sync_listitem' # Sleep/wakeup states @@ -282,6 +283,7 @@ __all__ = ( 'REFRESH_CONTAINER', 'RELOAD_ACCESS_MANAGER', 'SERVICE_IPC', + 'SYNC_API_KEYS', 'SYNC_LISTITEM', # Sleep/wakeup states diff --git a/resources/lib/youtube_plugin/kodion/json_store/api_keys.py b/resources/lib/youtube_plugin/kodion/json_store/api_keys.py index 2e99fc38..ac816db3 100644 --- a/resources/lib/youtube_plugin/kodion/json_store/api_keys.py +++ b/resources/lib/youtube_plugin/kodion/json_store/api_keys.py @@ -262,65 +262,83 @@ class APIKeyStore(JSONStore): data['keys']['developer'][developer_id] = new_config return self.save(data) - def sync(self): + def sync(self, update_store=False, update_settings=False): api_data = self.get_data() settings = self._context.get_settings() - update_saved_values = False - update_settings_values = False - - saved_details = ( + forced = update_store + stored_values = ( api_data['keys']['user'].get('api_key', ''), api_data['keys']['user'].get('client_id', ''), api_data['keys']['user'].get('client_secret', ''), ) - if all(saved_details): - update_settings_values = True - # users are now pasting keys into api_keys.json - # try stripping whitespace and domain suffix from API details - # and save the results if they differ - stripped_details = self.strip_details(*saved_details) - if all(stripped_details) and saved_details != stripped_details: - saved_details = stripped_details - api_data['keys']['user'] = { - 'api_key': saved_details[0], - 'client_id': saved_details[1], - 'client_secret': saved_details[2], - } - update_saved_values = True + _stored_values = self.strip_details(*stored_values) + all_stored = all(stored_values) - setting_details = ( + settings_values = ( settings.api_key(), settings.api_id(), settings.api_secret(), ) - if all(setting_details): - update_settings_values = False - stripped_details = self.strip_details(*setting_details) - if all(stripped_details) and setting_details != stripped_details: - setting_details = ( - settings.api_key(stripped_details[0]), - settings.api_id(stripped_details[1]), - settings.api_secret(stripped_details[2]), - ) + _settings_values = self.strip_details(*settings_values) + all_settings = all(settings_values) - if saved_details != setting_details: - api_data['keys']['user'] = { - 'api_key': setting_details[0], - 'client_id': setting_details[1], - 'client_secret': setting_details[2], - } - update_saved_values = True + if all_stored: + sync_to_settings = ( + not update_store + and _settings_values != _stored_values + ) + else: + sync_to_settings = update_settings - if update_settings_values: - settings.api_key(saved_details[0]) - settings.api_id(saved_details[1]) - settings.api_secret(saved_details[2]) + if all_settings: + sync_to_store = ( + not update_settings + and _stored_values != _settings_values + ) + else: + sync_to_store = update_store - if update_saved_values: - self.save(api_data) - return True - return False + update_settings = all_settings and settings_values != _settings_values + update_store = all_stored and stored_values != _stored_values + + if sync_to_settings: + settings.api_key(_stored_values[0]) + settings.api_id(_stored_values[1]) + settings.api_secret(_stored_values[2]) + + elif update_settings: + settings.api_key(_settings_values[0]) + settings.api_id(_settings_values[1]) + settings.api_secret(_settings_values[2]) + + elif sync_to_store: + api_data = { + 'keys': { + 'user': { + 'api_key': _settings_values[0], + 'client_id': _settings_values[1], + 'client_secret': _settings_values[2], + }, + }, + } + self.save(api_data, update=True, ipc=not forced) + + elif update_store: + api_data = { + 'keys': { + 'user': { + 'api_key': _stored_values[0], + 'client_id': _stored_values[1], + 'client_secret': _stored_values[2], + }, + }, + } + self.save(api_data, update=True, ipc=not forced) + + else: + return False + return True def update(self): context = self._context diff --git a/resources/lib/youtube_plugin/kodion/monitors/service_monitor.py b/resources/lib/youtube_plugin/kodion/monitors/service_monitor.py index b309ce53..e74556ec 100644 --- a/resources/lib/youtube_plugin/kodion/monitors/service_monitor.py +++ b/resources/lib/youtube_plugin/kodion/monitors/service_monitor.py @@ -37,6 +37,7 @@ from ..constants import ( RESUMABLE, SERVER_WAKEUP, SERVICE_IPC, + SYNC_API_KEYS, SYNC_LISTITEM, VIDEO_ID, ) @@ -54,6 +55,7 @@ class ServiceMonitor(xbmc.Monitor): def __init__(self, context): self._context = context + self._api_values = ('', '', '') self._httpd_address = None self._httpd_port = None self._whitelist = None @@ -260,6 +262,9 @@ class ServiceMonitor(xbmc.Monitor): self._context.reload_access_manager() self.refresh_container() + elif event == SYNC_API_KEYS: + self.onSettingsChanged(force=True) + elif event == PLAYBACK_STOPPED: if data: data = json.loads(data) @@ -315,6 +320,7 @@ class ServiceMonitor(xbmc.Monitor): def onSettingsChanged(self, force=False): context = self._context + ui = context.get_ui() if force: self._settings_collect = False @@ -352,7 +358,17 @@ class ServiceMonitor(xbmc.Monitor): self.log.stack_info = False self.log.verbose_logging = False - context.get_ui().set_property(CHECK_SETTINGS) + api_values = ( + settings.api_key(), + settings.api_id(), + settings.api_secret(), + ) + if api_values != self._api_values: + context.get_api_store().sync(update_store=True) + self._api_values = api_values + ui.set_property(SYNC_API_KEYS) + + ui.set_property(CHECK_SETTINGS) self.refresh_container() httpd_started = bool(self.httpd) diff --git a/resources/lib/youtube_plugin/kodion/network/http_server.py b/resources/lib/youtube_plugin/kodion/network/http_server.py index 09ce1058..73bf9bd0 100644 --- a/resources/lib/youtube_plugin/kodion/network/http_server.py +++ b/resources/lib/youtube_plugin/kodion/network/http_server.py @@ -38,6 +38,7 @@ from ..constants import ( LICENSE_TOKEN, LICENSE_URL, PATHS, + SYNC_API_KEYS, TEMP_PATH, ) from ..utils.convert_format import fix_subtitle_stream @@ -359,6 +360,7 @@ class RequestHandler(BaseHTTPRequestHandler, object): enabled = localize('api.personal.disabled') if updated: + context.send_notification(SYNC_API_KEYS) # Successfully updated updated = localize('api.config.updated', ', '.join(updated)) else: diff --git a/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_plugin.py b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_plugin.py index 48fc23ac..78eac75e 100644 --- a/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_plugin.py +++ b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_plugin.py @@ -33,6 +33,7 @@ from ...constants import ( REFRESH_CONTAINER, RELOAD_ACCESS_MANAGER, REROUTE_PATH, + SYNC_API_KEYS, SYNC_LISTITEM, TRAKT_PAUSE_FLAG, VIDEO_ID, @@ -195,6 +196,9 @@ class XbmcPlugin(AbstractPlugin): if ui.get_property(PLUGIN_SLEEPING): context.ipc_exec(PLUGIN_WAKEUP) + if ui.pop_property(SYNC_API_KEYS): + context.get_api_store().sync(update_store=True) + if ui.pop_property(RELOAD_ACCESS_MANAGER): context.reload_access_manager() From b4d625130287d7146c31f3f10a820188dad61f44 Mon Sep 17 00:00:00 2001 From: MoojMidge <56883549+MoojMidge@users.noreply.github.com> Date: Wed, 25 Mar 2026 12:03:46 +0900 Subject: [PATCH 07/18] Allow trailing slashes in url of html pages served by internal http server --- resources/lib/youtube_plugin/kodion/network/http_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/youtube_plugin/kodion/network/http_server.py b/resources/lib/youtube_plugin/kodion/network/http_server.py index 73bf9bd0..512f7439 100644 --- a/resources/lib/youtube_plugin/kodion/network/http_server.py +++ b/resources/lib/youtube_plugin/kodion/network/http_server.py @@ -232,7 +232,7 @@ class RequestHandler(BaseHTTPRequestHandler, object): parts, params, log_uri, log_params, log_path = parse_and_redact_uri(uri) path = { 'uri': uri, - 'path': parts.path, + 'path': parts.path.rstrip('/'), 'query': parts.query, 'params': params, 'log_uri': log_uri, From e749719efd36e5fe7ad79cd8a384b4f864d602b6 Mon Sep 17 00:00:00 2001 From: MoojMidge <56883549+MoojMidge@users.noreply.github.com> Date: Wed, 25 Mar 2026 12:22:52 +0900 Subject: [PATCH 08/18] Update API config page html - Move note to bookmark page from /youtube/api/submit to /youtube/api - Use POST method for form submission to prevent OAuth2 details potentially being logged or recorded in browser history - Disable form input autocomplete on /youtube/api - Improve display when input labels wrap --- .../kodion/network/http_server.py | 202 +++++++++--------- 1 file changed, 106 insertions(+), 96 deletions(-) diff --git a/resources/lib/youtube_plugin/kodion/network/http_server.py b/resources/lib/youtube_plugin/kodion/network/http_server.py index 512f7439..ee91755b 100644 --- a/resources/lib/youtube_plugin/kodion/network/http_server.py +++ b/resources/lib/youtube_plugin/kodion/network/http_server.py @@ -28,6 +28,7 @@ from ..compatibility import ( BaseHTTPRequestHandler, TCPServer, ThreadingMixIn, + parse_qs, urlencode, urlsplit, urlunsplit, @@ -266,10 +267,7 @@ class RequestHandler(BaseHTTPRequestHandler, object): return context = self._context - localize = context.localize - settings = context.get_settings() - api_config_enabled = settings.api_config_page() empty = [None] @@ -307,7 +305,7 @@ class RequestHandler(BaseHTTPRequestHandler, object): .format(uri=path['log_uri'], file_path=file_path)) self.send_error(404, response) - elif api_config_enabled and path['path'] == PATHS.API: + elif path['path'] == PATHS.API and settings.api_config_page(): html = self.api_config_page() html = html.encode('utf-8') @@ -319,65 +317,6 @@ class RequestHandler(BaseHTTPRequestHandler, object): for chunk in self._get_chunks(html): self.wfile.write(chunk) - elif api_config_enabled and path['path'].startswith(PATHS.API_SUBMIT): - xbmc.executebuiltin('Dialog.Close(addonsettings,true)') - - query = path['query'] - params = path['params'] - updated = [] - - api_key = params.get('api_key', empty)[0] - api_id = params.get('api_id', empty)[0] - api_secret = params.get('api_secret', empty)[0] - # Bookmark this page - if api_key and api_id and api_secret: - footer = localize('api.config.bookmark') - else: - footer = '' - - if re.search(r'api_key=(?:&|$)', query): - api_key = '' - if re.search(r'api_id=(?:&|$)', query): - api_id = '' - if re.search(r'api_secret=(?:&|$)', query): - api_secret = '' - - if api_key is not None and api_key != settings.api_key(): - settings.api_key(new_key=api_key) - updated.append(localize('api.key')) - - if api_id is not None and api_id != settings.api_id(): - settings.api_id(new_id=api_id) - updated.append(localize('api.id')) - - if api_secret is not None and api_secret != settings.api_secret(): - settings.api_secret(new_secret=api_secret) - updated.append(localize('api.secret')) - - if api_key and api_id and api_secret: - enabled = localize('api.personal.enabled') - else: - enabled = localize('api.personal.disabled') - - if updated: - context.send_notification(SYNC_API_KEYS) - # Successfully updated - updated = localize('api.config.updated', ', '.join(updated)) - else: - # No changes, not updated - updated = localize('api.config.not_updated') - - html = self.api_submit_page(updated, enabled, footer) - html = html.encode('utf-8') - - self.send_response(200) - self.send_header('Content-Type', 'text/html; charset=utf-8') - self.send_header('Content-Length', str(len(html))) - self.end_headers() - - for chunk in self._get_chunks(html): - self.wfile.write(chunk) - elif path['path'] == PATHS.PING: self.send_error(204) @@ -694,7 +633,70 @@ class RequestHandler(BaseHTTPRequestHandler, object): self.send_error(403) return - if path['path'].startswith(PATHS.DRM): + context = self._context + settings = context.get_settings() + localize = context.localize + + empty = [None] + + if path['path'] == PATHS.API_SUBMIT and settings.api_config_page(): + xbmc.executebuiltin('Dialog.Close(addonsettings,true)') + + length = int(self.headers['Content-Length']) + post_data = self.rfile.read(length) + + query = post_data.decode('utf-8', 'ignore') + params = parse_qs(query, keep_blank_values=True) + updated = [] + + api_key = params.get('api_key', empty)[0] + api_id = params.get('api_id', empty)[0] + api_secret = params.get('api_secret', empty)[0] + + if re.search(r'api_key=(?:&|$)', query): + api_key = '' + if re.search(r'api_id=(?:&|$)', query): + api_id = '' + if re.search(r'api_secret=(?:&|$)', query): + api_secret = '' + + if api_key is not None and api_key != settings.api_key(): + settings.api_key(new_key=api_key) + updated.append(localize('api.key')) + + if api_id is not None and api_id != settings.api_id(): + settings.api_id(new_id=api_id) + updated.append(localize('api.id')) + + if api_secret is not None and api_secret != settings.api_secret(): + settings.api_secret(new_secret=api_secret) + updated.append(localize('api.secret')) + + if api_key and api_id and api_secret: + enabled = localize('api.personal.enabled') + else: + enabled = localize('api.personal.disabled') + + if updated: + context.send_notification(SYNC_API_KEYS) + # Successfully updated + updated = localize('api.config.updated', ', '.join(updated)) + else: + # No changes, not updated + updated = localize('api.config.not_updated') + + html = self.api_submit_page(updated, enabled) + html = html.encode('utf-8') + + self.send_response(200) + self.send_header('Content-Type', 'text/html; charset=utf-8') + self.send_header('Content-Length', str(len(html))) + self.end_headers() + + for chunk in self._get_chunks(html): + self.wfile.write(chunk) + + elif path['path'].startswith(PATHS.DRM): ui = self._context.get_ui() lic_url = ui.get_property(LICENSE_URL) @@ -816,12 +818,14 @@ class RequestHandler(BaseHTTPRequestHandler, object): api_key_value=api_key, api_secret_value=api_secret, submit=localize('api.config.save'), + action_url=PATHS.API_SUBMIT, header=localize('api.config'), + footer=localize('api.config.bookmark'), ) return html @classmethod - def api_submit_page(cls, updated_keys, enabled, footer): + def api_submit_page(cls, updated_keys, enabled): localize = cls._context.localize html = Pages.api_submit.get('html') css = Pages.api_submit.get('css') @@ -830,7 +834,6 @@ class RequestHandler(BaseHTTPRequestHandler, object): title=localize('api.config'), updated=updated_keys, enabled=enabled, - footer=footer, header=localize('api.config'), ) return html @@ -844,31 +847,34 @@ class Pages(object):
-+ {footer} +