def onNotification(self, sender, method, data): if sender.lower() not in ('plugin.video.jellyfin', 'xbmc', 'upnextprovider.signal'): return if sender == 'plugin.video.jellyfin': method = method.split('.')[1] if method not in ('GetItem', 'ReportProgressRequested', 'LoadServer', 'RandomItems', 'Recommended', 'GetServerAddress', 'GetPlaybackInfo', 'Browse', 'GetImages', 'GetToken', 'PlayPlaylist', 'Play', 'GetIntros', 'GetAdditionalParts', 'RefreshItem', 'Genres', 'FavoriteItem', 'DeleteItem', 'AddUser', 'GetSession', 'GetUsers', 'GetThemes', 'GetTheme', 'Playstate', 'GeneralCommand', 'GetTranscodeOptions', 'RecentlyAdded', 'BrowseSeason', 'LiveTV', 'GetLiveStream'): return data = json.loads(data)[0] elif sender.startswith('upnextprovider'): LOG.info('Attempting to play the next episode via upnext') method = method.split('.', 1)[1] if method not in ('plugin.video.jellyfin_play_action', ): LOG.info('Received invalid upnext method: %s', method) return data = json.loads(data) method = "Play" if data: data = json.loads(binascii.unhexlify(data[0])) else: if method not in ('Player.OnPlay', 'VideoLibrary.OnUpdate', 'Player.OnAVChange'): ''' We have to clear the playlist if it was stopped before it has been played completely. Otherwise the next played item will be added the previous queue. ''' if method == "Player.OnStop": xbmc.sleep( 3000 ) # let's wait for the player so we don't clear the canceled playlist by mistake. if xbmc.getCondVisibility( "!Player.HasMedia + !Window.IsVisible(busydialog)" ): xbmc.executebuiltin("Playlist.Clear") LOG.info("[ playlist ] cleared") return data = json.loads(data) LOG.debug("[ %s: %s ] %s", sender, method, JsonDebugPrinter(data)) if self.sleep: LOG.info("System.OnSleep detected, ignore monitor request.") return try: if not data.get('ServerId'): server = Jellyfin() else: if method != 'LoadServer' and data[ 'ServerId'] not in self.servers: try: connect.Connect().register(data['ServerId']) self.server_instance(data['ServerId']) except Exception as error: LOG.exception(error) dialog("ok", heading="{jellyfin}", line1=translate(33142)) return server = Jellyfin(data['ServerId']) except Exception as error: LOG.exception(error) server = Jellyfin() if method == 'GetItem': item = server.jellyfin.get_item(data['Id']) self.void_responder(data, item) elif method == 'GetAdditionalParts': item = server.jellyfin.get_additional_parts(data['Id']) self.void_responder(data, item) elif method == 'GetIntros': item = server.jellyfin.get_intros(data['Id']) self.void_responder(data, item) elif method == 'GetImages': item = server.jellyfin.get_images(data['Id']) self.void_responder(data, item) elif method == 'GetServerAddress': server_address = server.auth.get_server_info( server.auth.server_id)['address'] self.void_responder(data, server_address) elif method == 'GetPlaybackInfo': sources = server.jellyfin.get_play_info(data['Id'], data['Profile']) self.void_responder(data, sources) elif method == 'GetLiveStream': sources = server.jellyfin.get_live_stream(data['Id'], data['PlaySessionId'], data['Token'], data['Profile']) self.void_responder(data, sources) elif method == 'GetToken': token = server.auth.jellyfin_token() self.void_responder(data, token) elif method == 'GetSession': session = server.jellyfin.get_device(self.device_id) self.void_responder(data, session) elif method == 'GetUsers': users = server.jellyfin.get_users() self.void_responder(data, users) elif method == 'GetTranscodeOptions': result = server.jellyfin.get_transcode_settings() self.void_responder(data, result) elif method == 'GetThemes': if data['Type'] == 'Video': theme = server.jellyfin.get_items_theme_video(data['Id']) else: theme = server.jellyfin.get_items_theme_song(data['Id']) self.void_responder(data, theme) elif method == 'GetTheme': theme = server.jellyfin.get_themes(data['Id']) self.void_responder(data, theme) elif method == 'Browse': result = downloader.get_filtered_section(data.get('Id'), data.get('Media'), data.get('Limit'), data.get('Recursive'), data.get('Sort'), data.get('SortOrder'), data.get('Filters'), data.get('Params'), data.get('ServerId')) self.void_responder(data, result) elif method == 'BrowseSeason': result = server.jellyfin.get_seasons(data['Id']) self.void_responder(data, result) elif method == 'LiveTV': result = server.jellyfin.get_channels() self.void_responder(data, result) elif method == 'RecentlyAdded': result = server.jellyfin.get_recently_added( data.get('Media'), data.get('Id'), data.get('Limit')) self.void_responder(data, result) elif method == 'Genres': result = server.jellyfin.get_genres(data.get('Id')) self.void_responder(data, result) elif method == 'Recommended': result = server.jellyfin.get_recommendation( data.get('Id'), data.get('Limit')) self.void_responder(data, result) elif method == 'RefreshItem': server.jellyfin.refresh_item(data['Id']) elif method == 'FavoriteItem': server.jellyfin.favorite(data['Id'], data['Favorite']) elif method == 'DeleteItem': server.jellyfin.delete_item(data['Id']) elif method == 'PlayPlaylist': server.jellyfin.post_session( server.config.data['app.session'], "Playing", { 'PlayCommand': "PlayNow", 'ItemIds': data['Id'], 'StartPositionTicks': 0 }) elif method == 'Play': items = server.jellyfin.get_items(data['ItemIds']) PlaylistWorker(data.get('ServerId'), items, data['PlayCommand'] == 'PlayNow', data.get('StartPositionTicks', 0), data.get('AudioStreamIndex'), data.get('SubtitleStreamIndex')).start() elif method in ('ReportProgressRequested', 'Player.OnAVChange'): self.player.report_playback(data.get('Report', True)) elif method == 'Playstate': self.playstate(data) elif method == 'GeneralCommand': self.general_commands(data) elif method == 'LoadServer': self.server_instance(data['ServerId']) elif method == 'AddUser': server.jellyfin.session_add_user(server.config.data['app.session'], data['Id'], data['Add']) self.additional_users(server) elif method == 'Player.OnPlay': on_play(data, server) elif method == 'VideoLibrary.OnUpdate': on_update(data, server)
def __init__(self): ''' Parse the parameters. Reroute to our service.py where user is fully identified already. ''' base_url = ADDON_BASE_URL path = QUERY_STRING try: params = dict(parse_qsl(path[1:])) except Exception: params = {} mode = params.get('mode') server = params.get('server') if server == 'None': server = None jellyfin_client = Jellyfin(server).get_client() api_client = jellyfin_client.jellyfin addon_data = xbmc.translatePath( "special://profile/addon_data/plugin.video.jellyfin/data.json") try: with open(addon_data, 'rb') as infile: data = json.load(infile) server_data = data['Servers'][0] api_client.config.data['auth.server'] = server_data.get('address') api_client.config.data['auth.server-name'] = server_data.get( 'Name') api_client.config.data['auth.user_id'] = server_data.get('UserId') api_client.config.data['auth.token'] = server_data.get( 'AccessToken') except Exception as e: LOG.warning('Addon appears to not be configured yet: {}'.format(e)) LOG.info("path: %s params: %s", path, JsonDebugPrinter(params)) if '/extrafanart' in base_url: jellyfin_path = path[1:] jellyfin_id = params.get('id') get_fanart(jellyfin_id, jellyfin_path, server, api_client) elif '/Extras' in base_url or '/VideoFiles' in base_url: jellyfin_path = path[1:] jellyfin_id = params.get('id') get_video_extras(jellyfin_id, jellyfin_path, server, api_client) elif mode == 'play': item = api_client.get_item(params['id']) item["resumePlayback"] = sys.argv[3].split(":")[1] == "true" Actions(server, api_client).play(item, params.get('dbid'), params.get('transcode') == 'true', playlist=params.get('playlist') == 'true') elif mode == 'playlist': api_client.post_session( api_client.config.data['app.session'], "Playing", { 'PlayCommand': "PlayNow", 'ItemIds': params['id'], 'StartPositionTicks': 0 }) elif mode == 'deviceid': client.reset_device_id() elif mode == 'reset': reset() elif mode == 'delete': delete_item() elif mode == 'refreshboxsets': event('SyncLibrary', {'Id': "Boxsets:Refresh"}) elif mode == 'nextepisodes': get_next_episodes(params['id'], params['limit']) elif mode == 'browse': browse(params.get('type'), params.get('id'), params.get('folder'), server, api_client) elif mode == 'synclib': event('SyncLibrary', {'Id': params.get('id')}) elif mode == 'updatelib': event('SyncLibrary', {'Id': params.get('id'), 'Update': True}) elif mode == 'repairlib': event('RepairLibrary', {'Id': params.get('id')}) elif mode == 'removelib': event('RemoveLibrary', {'Id': params.get('id')}) elif mode == 'repairlibs': event('RepairLibrarySelection') elif mode == 'updatelibs': event('SyncLibrarySelection') elif mode == 'removelibs': event('RemoveLibrarySelection') elif mode == 'addlibs': event('AddLibrarySelection') elif mode == 'addserver': event('AddServer') elif mode == 'login': event('ServerConnect', {'Id': server}) elif mode == 'removeserver': event('RemoveServer', {'Id': server}) elif mode == 'settings': xbmc.executebuiltin('Addon.OpenSettings(plugin.video.jellyfin)') elif mode == 'adduser': add_user(api_client) elif mode == 'updatepassword': event('UpdatePassword') elif mode == 'thememedia': get_themes(api_client) elif mode == 'managelibs': manage_libraries() elif mode == 'backup': backup() elif mode == 'restartservice': window('jellyfin.restart.bool', True) else: listing()
def request(self, data, session=None): ''' Give a chance to retry the connection. Jellyfin sometimes can be slow to answer back data dictionary can contain: type: GET, POST, etc. url: (optional) handler: not considered when url is provided (optional) params: request parameters (optional) json: request body (optional) headers: (optional), verify: ssl certificate, True (verify using device built-in library) or False ''' if not data: raise AttributeError("Request cannot be empty") data = self._request(data) LOG.debug("--->[ http ] %s", JsonDebugPrinter(data)) retry = data.pop('retry', 5) while True: try: r = self._requests(session or self.session or requests, data.pop('type', "GET"), **data) r.content # release the connection if not self.keep_alive and self.session is not None: self.stop_session() r.raise_for_status() except requests.exceptions.ConnectionError as error: if retry: retry -= 1 time.sleep(1) continue LOG.error(error) self.client.callback( "ServerUnreachable", {'ServerId': self.config.data['auth.server-id']}) raise HTTPException("ServerUnreachable", error) except requests.exceptions.ReadTimeout as error: if retry: retry -= 1 time.sleep(1) continue LOG.error(error) raise HTTPException("ReadTimeout", error) except requests.exceptions.HTTPError as error: LOG.error(error) if r.status_code == 401: if 'X-Application-Error-Code' in r.headers: self.client.callback( "AccessRestricted", {'ServerId': self.config.data['auth.server-id']}) raise HTTPException("AccessRestricted", error) else: self.client.callback( "Unauthorized", {'ServerId': self.config.data['auth.server-id']}) self.client.auth.revoke_token() raise HTTPException("Unauthorized", error) elif r.status_code == 400: LOG.warning(error) LOG.warning(data) try: LOG.warning(r.json()) except Exception: LOG.warning(r.text) elif r.status_code == 500: # log and ignore. LOG.error("--[ 500 response ] %s", error) return elif r.status_code == 502: if retry: retry -= 1 time.sleep(1) continue raise HTTPException(r.status_code, error) except requests.exceptions.MissingSchema as error: LOG.error("Request missing Schema. " + str(error)) raise HTTPException( "MissingSchema", {'Id': self.config.data.get('auth.server', "None")}) else: try: self.config.data['server-time'] = r.headers['Date'] elapsed = int(r.elapsed.total_seconds() * 1000) response = r.json() LOG.debug("---<[ http ][%s ms]", elapsed) LOG.debug(JsonDebugPrinter(response)) return clean_none_dict_values(response) except ValueError: return except TypeError: # Empty json return
def onNotification(self, sender, method, data): ''' All notifications are sent via NotifyAll built-in or Kodi. Central hub. ''' if sender.lower() not in ('plugin.video.jellyfin', 'xbmc'): return if sender == 'plugin.video.jellyfin': method = method.split('.')[1] if method not in ('ServerUnreachable', 'ServerShuttingDown', 'UserDataChanged', 'ServerConnect', 'LibraryChanged', 'ServerOnline', 'SyncLibrary', 'RepairLibrary', 'RemoveLibrary', 'SyncLibrarySelection', 'RepairLibrarySelection', 'AddServer', 'Unauthorized', 'UserConfigurationUpdated', 'ServerRestarting', 'RemoveServer', 'UpdatePassword', 'AddLibrarySelection', 'RemoveLibrarySelection'): return data = json.loads(data)[0] else: if method not in ('System.OnQuit', 'System.OnSleep', 'System.OnWake'): return data = json.loads(data) LOG.debug("[ %s: %s ] %s", sender, method, JsonDebugPrinter(data)) if method == 'ServerOnline': if data.get('ServerId') is None: window('jellyfin_online.bool', True) self.settings['auth_check'] = True self.warn = True if settings('connectMsg.bool'): users = [ user for user in ( settings('additionalUsers') or "").split(',') if user ] users.insert(0, settings('username')) dialog("notification", heading="{jellyfin}", message="%s %s" % (translate(33000), ", ".join(users)), icon="{jellyfin}", time=1500, sound=False) if self.library_thread is None: self.library_thread = library.Library(self) self.library_thread.start() elif method in ('ServerUnreachable', 'ServerShuttingDown'): if self.warn or data.get('ServerId'): self.warn = data.get('ServerId') is not None dialog("notification", heading="{jellyfin}", message=translate(33146) if data.get('ServerId') is None else translate(33149), icon=xbmcgui.NOTIFICATION_ERROR) if data.get('ServerId') is None: self.stop_default() if self.waitForAbort(120): return self.start_default() elif method == 'Unauthorized': dialog("notification", heading="{jellyfin}", message=translate(33147) if data['ServerId'] is None else translate(33148), icon=xbmcgui.NOTIFICATION_ERROR) if data.get('ServerId') is None and self.settings['auth_check']: self.settings['auth_check'] = False self.stop_default() if self.waitForAbort(5): return self.start_default() elif method == 'ServerRestarting': if data.get('ServerId'): return if settings('restartMsg.bool'): dialog("notification", heading="{jellyfin}", message=translate(33006), icon="{jellyfin}") self.stop_default() if self.waitForAbort(15): return self.start_default() elif method == 'ServerConnect': self.connect.register(data['Id']) xbmc.executebuiltin("Container.Refresh") elif method == 'AddServer': self.connect.setup_manual_server() xbmc.executebuiltin("Container.Refresh") elif method == 'RemoveServer': self.connect.remove_server(data['Id']) xbmc.executebuiltin("Container.Refresh") elif method == 'UpdatePassword': self.connect.setup_login_manual() elif method == 'UserDataChanged' and self.library_thread: if data.get('ServerId') or not window('jellyfin_startup.bool'): return LOG.info("[ UserDataChanged ] %s", data) self.library_thread.userdata(data['UserDataList']) elif method == 'LibraryChanged' and self.library_thread: if data.get('ServerId') or not window('jellyfin_startup.bool'): return LOG.info("[ LibraryChanged ] %s", data) self.library_thread.updated(data['ItemsUpdated'] + data['ItemsAdded']) self.library_thread.removed(data['ItemsRemoved']) elif method == 'System.OnQuit': window('jellyfin_should_stop.bool', True) self.running = False elif method in ('SyncLibrarySelection', 'RepairLibrarySelection', 'AddLibrarySelection', 'RemoveLibrarySelection'): self.library_thread.select_libraries(method) elif method == 'SyncLibrary': if not data.get('Id'): return self.library_thread.add_library(data['Id'], data.get('Update', False)) xbmc.executebuiltin("Container.Refresh") elif method == 'RepairLibrary': if not data.get('Id'): return libraries = data['Id'].split(',') for lib in libraries: if not self.library_thread.remove_library(lib): return self.library_thread.add_library(data['Id']) xbmc.executebuiltin("Container.Refresh") elif method == 'RemoveLibrary': libraries = data['Id'].split(',') for lib in libraries: if not self.library_thread.remove_library(lib): return xbmc.executebuiltin("Container.Refresh") elif method == 'System.OnSleep': LOG.info("-->[ sleep ]") window('jellyfin_should_stop.bool', True) if self.library_thread is not None: self.library_thread.stop_client() self.library_thread = None Jellyfin.close_all() self.monitor.server = [] self.monitor.sleep = True elif method == 'System.OnWake': if not self.monitor.sleep: LOG.warning( "System.OnSleep was never called, skip System.OnWake") return LOG.info("--<[ sleep ]") xbmc.sleep(10000) # Allow network to wake up self.monitor.sleep = False window('jellyfin_should_stop', clear=True) try: self.connect.register() except Exception as error: LOG.exception(error) elif method == 'GUI.OnScreensaverDeactivated': LOG.info("--<[ screensaver ]") xbmc.sleep(5000) if self.library_thread is not None: self.library_thread.fast_sync() elif method == 'UserConfigurationUpdated' and data.get( 'ServerId') is None: Views().get_views()
def __init__(self): ''' Parse the parameters. Reroute to our service.py where user is fully identified already. ''' base_url = sys.argv[0] path = sys.argv[2] try: params = dict(parse_qsl(path[1:])) except Exception: params = {} mode = params.get('mode') server = params.get('server') if server == 'None': server = None LOG.info("path: %s params: %s", path, JsonDebugPrinter(params)) if '/extrafanart' in base_url: jellyfin_path = path[1:] jellyfin_id = params.get('id') get_fanart(jellyfin_id, jellyfin_path, server) elif '/Extras' in base_url or '/VideoFiles' in base_url: jellyfin_path = path[1:] jellyfin_id = params.get('id') get_video_extras(jellyfin_id, jellyfin_path, server) elif mode == 'play': item = TheVoid('GetItem', { 'Id': params['id'], 'ServerId': server }).get() Actions(server).play(item, params.get('dbid'), params.get('transcode') == 'true', playlist=params.get('playlist') == 'true') elif mode == 'playlist': event('PlayPlaylist', {'Id': params['id'], 'ServerId': server}) elif mode == 'deviceid': client.reset_device_id() elif mode == 'reset': reset() elif mode == 'delete': delete_item() elif mode == 'refreshboxsets': event('SyncLibrary', {'Id': "Boxsets:Refresh"}) elif mode == 'nextepisodes': get_next_episodes(params['id'], params['limit']) elif mode == 'browse': browse(params.get('type'), params.get('id'), params.get('folder'), server) elif mode == 'synclib': event('SyncLibrary', {'Id': params.get('id')}) elif mode == 'updatelib': event('SyncLibrary', {'Id': params.get('id'), 'Update': True}) elif mode == 'repairlib': event('RepairLibrary', {'Id': params.get('id')}) elif mode == 'removelib': event('RemoveLibrary', {'Id': params.get('id')}) elif mode == 'repairlibs': event('RepairLibrarySelection') elif mode == 'updatelibs': event('SyncLibrarySelection') elif mode == 'removelibs': event('RemoveLibrarySelection') elif mode == 'addlibs': event('AddLibrarySelection') elif mode == 'addserver': event('AddServer') elif mode == 'login': event('ServerConnect', {'Id': server}) elif mode == 'removeserver': event('RemoveServer', {'Id': server}) elif mode == 'settings': xbmc.executebuiltin('Addon.OpenSettings(plugin.video.jellyfin)') elif mode == 'adduser': add_user() elif mode == 'updateserver': event('UpdateServer') elif mode == 'thememedia': get_themes() elif mode == 'managelibs': manage_libraries() elif mode == 'backup': backup() elif mode == 'restartservice': window('jellyfin.restart.bool', True) else: listing()