def logout(self): """Logout of the current account and reset the session""" common.debug('Logging out of current account') # Perform the website logout self.get('logout') G.settings_monitor_suspend(True) # Disable and reset auto-update / auto-sync features G.ADDON.setSettingInt('lib_auto_upd_mode', 1) G.ADDON.setSettingBool('lib_sync_mylist', False) G.SHARED_DB.delete_key('sync_mylist_profile_guid') # Disable and reset the auto-select profile G.LOCAL_DB.set_value('autoselect_profile_guid', '') G.ADDON.setSetting('autoselect_profile_name', '') G.ADDON.setSettingBool('autoselect_profile_enabled', False) # Reset of selected profile guid for library playback G.LOCAL_DB.set_value('library_playback_profile_guid', '') G.ADDON.setSetting('library_playback_profile', '') G.settings_monitor_suspend(False) # Delete cookie and credentials self.session.cookies.clear() cookies.delete(self.account_hash) common.purge_credentials() # Reset the ESN obtained from website/generated G.LOCAL_DB.set_value('esn', '', TABLE_SESSION) # Reinitialize the MSL handler (delete msl data file, then reset everything) common.send_signal(signal=common.Signals.REINITIALIZE_MSL_HANDLER, data=True) G.CACHE.clear(clear_database=True) common.info('Logout successful') ui.show_notification(common.get_local_string(30113)) self._init_session() common.container_update('path', True) # Go to a fake page to clear screen # Open root page common.container_update(G.BASE_URL, True)
def change_watched_status_locally(videoid): """Change the watched status locally""" # Todo: how get resumetime/playcount of selected item for calculate current watched status? profile_guid = G.LOCAL_DB.get_active_profile_guid() current_value = G.SHARED_DB.get_watched_status(profile_guid, videoid.value, None, bool) if current_value: txt_index = 1 G.SHARED_DB.set_watched_status(profile_guid, videoid.value, False) elif current_value is not None and not current_value: txt_index = 2 G.SHARED_DB.delete_watched_status(profile_guid, videoid.value) else: txt_index = 0 G.SHARED_DB.set_watched_status(profile_guid, videoid.value, True) ui.show_notification(common.get_local_string(30237).split('|')[txt_index]) common.container_refresh()
def _create_dictitem_from_row(row): row_id = str(row['ID']) search_desc = common.get_local_string( 30401) + ': ' + SEARCH_TYPES_DESC.get(row['Type'], 'Unknown') return { 'url': common.build_url(['search', 'search', row_id], mode=g.MODE_DIRECTORY), 'label': row['Value'], 'info': { 'plot': search_desc }, # The description 'menu_items': generate_context_menu_searchitem(row_id, row['Type']), 'is_folder': True }
def _display_search_results(pathitems, perpetual_range_start, dir_update_listing): menu_data = g.MAIN_MENU_ITEMS['search'] call_args = { 'menu_data': menu_data, 'search_term': pathitems[2], 'pathitems': pathitems, 'perpetual_range_start': perpetual_range_start } list_data, extra_data = common.make_call('get_video_list_search', call_args) if list_data: _search_results_directory(pathitems, menu_data, list_data, extra_data, dir_update_listing) else: ui.show_notification(common.get_local_string(30013)) xbmcplugin.endOfDirectory(g.PLUGIN_HANDLE, succeeded=False)
def update_videoid_bookmark(self, video_id): """Update the videoid bookmark position""" # You can check if this function works through the official android app # by checking if the red status bar of watched time position appears and will be updated, or also # if continueWatching list will be updated (e.g. try to play a new tvshow not contained in the "my list") call_paths = [['refreshVideoCurrentPositions']] params = [f'[{video_id}]', '[]'] try: response = self.callpath_request(call_paths, params) LOG.debug('refreshVideoCurrentPositions response: {}', response) except Exception as exc: # pylint: disable=broad-except LOG.warn('refreshVideoCurrentPositions failed: {}', exc) ui.show_notification( title=common.get_local_string(30105), msg= 'An error prevented the update the status watched on Netflix', time=10000)
def play(videoid): """Play an episode or movie as specified by the path""" common.info('Playing {}', videoid) metadata = api.metadata(videoid) common.debug('Metadata is {}', metadata) # Parental control PIN pin_result = _verify_pin(metadata[0].get('requiresPin', False)) if not pin_result: if pin_result is not None: ui.show_notification(common.get_local_string(30106), time=10000) xbmcplugin.endOfDirectory(g.PLUGIN_HANDLE, succeeded=False) return list_item = get_inputstream_listitem(videoid) infos, art = infolabels.add_info_for_playback(videoid, list_item) # Workaround for resuming strm files from library resume_position = infos.get('resume', {}).get('position') \ if xbmc.getInfoLabel('Container.PluginName') != g.ADDON.getAddonInfo('id') \ and g.ADDON.getSettingBool('ResumeManager_enabled') else None if resume_position: index_selected = ui.ask_for_resume( resume_position) if g.ADDON.getSettingBool( 'ResumeManager_dialog') else None if index_selected == -1: xbmcplugin.setResolvedUrl(handle=g.PLUGIN_HANDLE, succeeded=False, listitem=list_item) return if index_selected == 1: resume_position = None common.debug('Sending initialization signal') common.send_signal( common.Signals.PLAYBACK_INITIATED, { 'videoid': videoid.to_dict(), 'infos': infos, 'art': art, 'timeline_markers': get_timeline_markers(metadata[0]), 'upnext_info': get_upnext_info(videoid, (infos, art), metadata), 'resume_position': resume_position }) xbmcplugin.setResolvedUrl(handle=g.PLUGIN_HANDLE, succeeded=True, listitem=list_item)
def update_videoid_bookmark(video_id): """Update the videoid bookmark position""" # You can check if this function works through the official android app # by checking if the status bar watched of the video will be updated callargs = { 'callpaths': [['refreshVideoCurrentPositions']], 'params': ['[' + video_id + ']', '[]'], } try: response = common.make_http_call('callpath_request', callargs) common.debug('refreshVideoCurrentPositions response: {}', response) except Exception: # pylint: disable=broad-except # I do not know the reason yet, but sometimes continues to return error 401, # making it impossible to update the bookmark position ui.show_notification(title=common.get_local_string(30105), msg='An error prevented the update the status watched on netflix', time=10000)
def update_loco_context(self, context_name): """Update a loco list by context""" # Call this api seem no more needed to update the continueWatching loco list # Get current loco root data loco_data = self.path_request( [['loco', [context_name], ['context', 'id', 'index']]]) loco_root = loco_data['loco'][1] if 'continueWatching' in loco_data['locos'][loco_root]: context_index = loco_data['locos'][loco_root]['continueWatching'][ 2] context_id = loco_data['locos'][loco_root][context_index][1] else: # In the new profiles, there is no 'continueWatching' list and no list is returned LOG.warn( 'update_loco_context: Update skipped due to missing context {}', context_name) return path = [['locos', loco_root, 'refreshListByContext']] # After the introduction of LoCo, the following notes are to be reviewed (refers to old LoLoMo): # The fourth parameter is like a request-id, but it does not seem to match to # serverDefs/date/requestId of reactContext nor to request_id of the video event request, # seem to have some kind of relationship with renoMessageId suspect with the logblob but i am not sure. # I noticed also that this request can also be made with the fourth parameter empty. params = [ common.enclose_quotes(context_id), context_index, common.enclose_quotes(context_name), '' ] # path_suffixs = [ # [{'from': 0, 'to': 100}, 'itemSummary'], # [['componentSummary']] # ] try: response = self.callpath_request(path, params) LOG.debug('refreshListByContext response: {}', response) # The call response return the new context id of the previous invalidated loco context_id # and if path_suffixs is added return also the new video list data except Exception as exc: # pylint: disable=broad-except LOG.warn('refreshListByContext failed: {}', exc) if not LOG.level == LOG.LEVEL_VERBOSE: return ui.show_notification( title=common.get_local_string(30105), msg='An error prevented the update the loco context on Netflix', time=10000)
def get_inputstream_listitem(videoid): """Return a listitem that has all inputstream relevant properties set for playback of the given video_id""" service_url = SERVICE_URL_FORMAT.format( port=G.LOCAL_DB.get_value('msl_service_port', 8000)) manifest_path = MANIFEST_PATH_FORMAT.format(videoid=videoid.value) list_item = xbmcgui.ListItem(path=service_url + manifest_path, offscreen=True) list_item.setContentLookup(False) list_item.setMimeType('application/xml+dash') list_item.setProperty('isFolder', 'false') list_item.setProperty('IsPlayable', 'true') try: import inputstreamhelper is_helper = inputstreamhelper.Helper('mpd', drm='widevine') inputstream_ready = is_helper.check_inputstream() if not inputstream_ready: raise Exception(common.get_local_string(30046)) list_item.setProperty(key=is_helper.inputstream_addon + '.stream_headers', value='user-agent=' + common.get_user_agent()) list_item.setProperty(key=is_helper.inputstream_addon + '.license_type', value='com.widevine.alpha') list_item.setProperty(key=is_helper.inputstream_addon + '.manifest_type', value='mpd') list_item.setProperty( key=is_helper.inputstream_addon + '.license_key', value=service_url + LICENSE_PATH_FORMAT.format(videoid=videoid.value) + '||b{SSM}!b{SID}|') list_item.setProperty(key=is_helper.inputstream_addon + '.server_certificate', value=INPUTSTREAM_SERVER_CERTIFICATE) list_item.setProperty( key='inputstreamaddon' if G.KODI_VERSION.is_major_ver('18') else 'inputstream', value=is_helper.inputstream_addon) return list_item except Exception as exc: # pylint: disable=broad-except # Captures all types of ISH internal errors import traceback LOG.error(G.py2_decode(traceback.format_exc(), 'latin-1')) raise_from(InputStreamHelperError(str(exc)), exc)
def _login(self): """Perform account login""" try: auth_url = website.extract_userdata( self._get('profiles'))['authURL'] common.debug('Logging in...') login_response = self._post( 'login', data=_login_payload(common.get_credentials(), auth_url)) session_data = website.extract_session_data(login_response) except Exception: common.debug(traceback.format_exc()) self.session.cookies.clear() raise LoginFailedError common.info('Login successful') ui.show_notification(common.get_local_string(30109)) self.session_data = session_data
def home(self, pathitems=None): # pylint: disable=unused-argument """Show home listing""" if 'switch_profile_guid' in self.params: if G.IS_ADDON_EXTERNAL_CALL: # Profile switch/ask PIN only once ret = not self.params['switch_profile_guid'] == G.LOCAL_DB.get_active_profile_guid() else: # Profile switch/ask PIN every time you come from ... ret = common.WndHomeProps[common.WndHomeProps.CURRENT_DIRECTORY] in ['', 'root', 'profiles'] if ret and not activate_profile(self.params['switch_profile_guid']): xbmcplugin.endOfDirectory(G.PLUGIN_HANDLE, succeeded=False) return LOG.debug('Showing home listing') dir_items, extra_data = common.make_call('get_mainmenu') # pylint: disable=unused-variable finalize_directory(dir_items, G.CONTENT_FOLDER, title=(G.LOCAL_DB.get_profile_config('profileName', '???') + ' - ' + common.get_local_string(30097))) end_of_directory(True)
def export_to_library_new_episodes(self, videoid, show_prg_dialog=True): """ Export new episodes for a tv show by it's videoid :param videoid: The videoid of the tv show to process :param show_prg_dialog: if True show progress dialog, otherwise, a background progress bar """ LOG.info('Start exporting new episodes for {}', videoid) if videoid.mediatype != common.VideoId.SHOW: LOG.warn('{} is not a tv show, the operation is cancelled', videoid) return nfo_settings = nfo.NFOSettings() nfo_settings.show_export_dialog(videoid.mediatype) self.execute_library_task_gui(videoid, self.export_new_item, title=common.get_local_string(30198), nfo_settings=nfo_settings, show_prg_dialog=show_prg_dialog)
def _get_dictitem_clear(): """The "clear" menu item""" return { 'url': common.build_url(['search', 'search', 'clear'], mode=G.MODE_DIRECTORY), 'label': common.get_local_string(30404), 'art': { 'icon': 'icons\\infodialogs\\uninstall.png' }, 'is_folder': False, # Set folder to false to run as command so that clear is not in directory history 'media_type': None, # Set media type none to avoid setting isplayable flag for non-folder item 'properties': { 'specialsort': 'bottom' } # Force an item to stay on bottom (not documented in Kodi) }
def home(self, pathitems=None, cache_to_disc=True, is_autoselect_profile=False): # pylint: disable=unused-argument """Show home listing""" if not is_autoselect_profile and 'switch_profile_guid' in self.params: # This is executed only when you have selected a profile from the profile list if not activate_profile(self.params['switch_profile_guid']): xbmcplugin.endOfDirectory(g.PLUGIN_HANDLE, succeeded=False) return common.debug('Showing home listing') list_data, extra_data = common.make_call('get_mainmenu') # pylint: disable=unused-variable finalize_directory( convert_list_to_dir_items(list_data), g.CONTENT_FOLDER, title=(g.LOCAL_DB.get_profile_config('profileName', '???') + ' - ' + common.get_local_string(30097))) end_of_directory(False, cache_to_disc)
def __init__(self, path_response): self.data = path_response have_data = 'search' in path_response if have_data: self.title = common.get_local_string(30100).format( self.data['search']['byTerm'].keys()[0][1:]) self.videos = OrderedDict( resolve_refs(self.data['search']['byReference'].values()[0], self.data)) self.videoids = _get_videoids(self.videos) self.artitem = next(self.videos.itervalues(), None) self.contained_titles = _get_titles(self.videos) else: common.debug('SearchVideoList - No data in path_response') self.videos = {} self.videoids = None self.artitem = None self.contained_titles = None
def on_playback_started(self, player_state): is_enabled = G.ADDON.getSettingBool('StreamContinuityManager_enabled') if is_enabled: # Get user saved preferences self.sc_settings = G.SHARED_DB.get_stream_continuity(G.LOCAL_DB.get_active_profile_guid(), self.videoid_parent.value, {}) else: # Disable on_tick activity to check changes of settings self.enabled = False if (player_state.get(STREAMS['subtitle']['current']) is None and player_state.get('currentvideostream') is None): # Kodi 19 BUG JSON RPC: "Player.GetProperties" is broken: https://github.com/xbmc/xbmc/issues/17915 # The first call return wrong data the following calls return OSError, and then _notify_all will be blocked self.enabled = False LOG.error('Due of Kodi 19 bug has been disabled: ' 'Ask to skip dialog, remember audio/subtitles preferences and other features') ui.show_notification(title=common.get_local_string(30105), msg='Due to Kodi bug has been disabled all Netflix features') return xbmc.sleep(500) # Wait for slower systems self.player_state = player_state # If the user has not changed the subtitle settings if self.sc_settings.get('subtitleenabled') is None: # Copy player state to restore it after, or the changes will affect the _restore_stream() _player_state_copy = copy.deepcopy(player_state) # Force selection of the audio/subtitles language with country code if G.ADDON.getSettingBool('prefer_alternative_lang'): self._select_lang_with_country_code() # Ensures the display of forced subtitles only with the audio language set if G.ADDON.getSettingBool('show_forced_subtitles_only'): self._ensure_forced_subtitle_only() # Ensure in any case to show the regular subtitles when the preferred audio language is not available if G.ADDON.getSettingBool('show_subtitles_miss_audio'): self._ensure_subtitles_no_audio_available() player_state = _player_state_copy for stype in sorted(STREAMS): # Save current stream setting from the Kodi player to the local dict self._set_current_stream(stype, player_state) # Apply the chosen stream setting to Kodi player and update the local dict self._restore_stream(stype) if is_enabled: # It is mandatory to wait at least 1 second to allow the Kodi system to update the values # changed by restore, otherwise when on_tick is executed it will save twice unnecessarily xbmc.sleep(1000)
def logout(self, url): """Logout of the current account and reset the session""" common.debug('Logging out of current account') # Perform the website logout self._get('logout') g.settings_monitor_suspend(True) # Disable and reset auto-update / auto-sync features g.ADDON.setSettingInt('lib_auto_upd_mode', 0) g.ADDON.setSettingBool('lib_sync_mylist', False) g.SHARED_DB.delete_key('sync_mylist_profile_guid') # Disable and reset the auto-select profile g.LOCAL_DB.set_value('autoselect_profile_guid', '') g.ADDON.setSetting('autoselect_profile_name', '') g.ADDON.setSettingBool('autoselect_profile_enabled', False) g.settings_monitor_suspend(False) # Delete cookie and credentials self.session.cookies.clear() cookies.delete(self.account_hash) common.purge_credentials() # Reset the ESN obtained from website/generated g.LOCAL_DB.set_value('esn', '', TABLE_SESSION) # Reinitialize the MSL handler (delete msl data file, then reset everything) common.send_signal(signal=common.Signals.REINITIALIZE_MSL_HANDLER, data=True) g.CACHE.clear(clear_database=True) common.info('Logout successful') ui.show_notification(common.get_local_string(30113)) self._init_session() xbmc.executebuiltin('Container.Update(path,replace)' ) # Go to a fake page to clear screen # Open root page xbmc.executebuiltin('Container.Update({},replace)'.format( url)) # replace=reset history
def rate_thumb(videoid, rating, track_id_jaw): """Rate a video on Netflix""" common.debug('Thumb rating {} as {}', videoid.value, rating) event_uuid = common.get_random_uuid() response = common.make_call( 'post', {'component': 'set_thumb_rating', 'data': { 'eventUuid': event_uuid, 'titleId': int(videoid.value), 'trackId': track_id_jaw, 'rating': rating, }}) if response.get('status', '') == 'success': ui.show_notification(common.get_local_string(30045).split('|')[rating]) else: common.error('Rating thumb error, response detail: {}', response) ui.show_error_info('Rating error', 'Error type: {}' + response.get('status', '--'), True, True)
def export_new_episodes(videoid, silent=False, nfo_settings=None): """ Export new episodes for a tv show by it's video id :param videoid: The videoid of the tv show to process :param silent: don't display user interface while exporting :param nfo_settings: the nfo settings :return: None """ method = execute_library_tasks_silently if silent else execute_library_tasks if videoid.mediatype == common.VideoId.SHOW: common.debug('Exporting new episodes for {}', videoid) method(videoid, [export_new_item], title=common.get_local_string(30198), nfo_settings=nfo_settings) else: common.debug('{} is not a tv show, no new episodes will be exported', videoid)
def update_loco_context(context_name): """Update a loco list by context""" # This api seem no more needed to update the continueWatching loco list loco_root = g.LOCAL_DB.get_value('loco_root_id', '', TABLE_SESSION) context_index = g.LOCAL_DB.get_value('loco_{}_index'.format(context_name.lower()), '', TABLE_SESSION) context_id = g.LOCAL_DB.get_value('loco_{}_id'.format(context_name.lower()), '', TABLE_SESSION) if not context_index: common.warn('Update loco context {} skipped due to missing loco index', context_name) return path = [['locos', loco_root, 'refreshListByContext']] # After the introduction of LoCo, the following notes are to be reviewed (refers to old LoLoMo): # The fourth parameter is like a request-id, but it doesn't seem to match to # serverDefs/date/requestId of reactContext (g.LOCAL_DB.get_value('request_id', table=TABLE_SESSION)) # nor to request_id of the video event request, # has a kind of relationship with renoMessageId suspect with the logblob but i'm not sure because my debug crashed # and i am no longer able to trace the source. # I noticed also that this request can also be made with the fourth parameter empty. params = [common.enclose_quotes(context_id), context_index, common.enclose_quotes(context_name), ''] # path_suffixs = [ # [{'from': 0, 'to': 100}, 'itemSummary'], # [['componentSummary']] # ] callargs = { 'callpaths': path, 'params': params, # 'path_suffixs': path_suffixs } try: response = common.make_http_call('callpath_request', callargs) common.debug('refreshListByContext response: {}', response) # The call response return the new context id of the previous invalidated loco context_id # and if path_suffixs is added return also the new video list data except Exception: # pylint: disable=broad-except if not common.is_debug_verbose(): return ui.show_notification(title=common.get_local_string(30105), msg='An error prevented the update the loco context on netflix', time=10000)
def clear_library(self, show_prg_dialog=True): """ Delete all exported items to the library :param show_prg_dialog: if True, will be show a progress dialog window """ LOG.info('Start deleting exported library items') # This will clear all the add-on library data, to prevents possible inconsistencies when for some reason # such as improper use of the add-on, unexpected error or other has broken the library database data or files with ui.ProgressDialog(show_prg_dialog, common.get_local_string(30245), max_value=3) as progress_dlg: progress_dlg.perform_step() progress_dlg.set_wait_message() G.SHARED_DB.purge_library() for folder_name in [FOLDER_NAME_MOVIES, FOLDER_NAME_SHOWS]: progress_dlg.perform_step() progress_dlg.set_wait_message() section_root_dir = common.join_folders_paths(get_library_path(), folder_name) common.delete_folder_contents(section_root_dir, delete_subfolders=True) # Clean the Kodi library database common.clean_library(show_prg_dialog)
def _create_profile_item(profile_guid, profile, html_parser): """Create a tuple that can be added to a Kodi directory that represents a profile as listed in the profiles listing""" profile_name = profile.get('profileName', '') unescaped_profile_name = html_parser.unescape(profile_name) enc_profile_name = profile_name.encode('utf-8') list_item = list_item_skeleton(label=unescaped_profile_name, icon=profile.get('avatar')) list_item.select(profile.get('isActive', False)) autologin_url = common.build_url( pathitems=['save_autologin', profile_guid], params={'autologin_user': enc_profile_name}, mode=g.MODE_ACTION) list_item.addContextMenuItems([(common.get_local_string(30053), 'RunPlugin({})'.format(autologin_url))]) url = common.build_url(pathitems=['home'], params={'profile_id': profile_guid}, mode=g.MODE_DIRECTORY) return (url, list_item, True)
def update_lolomo_context(context_name): """Update the lolomo list by context""" lolomo_root = g.LOCAL_DB.get_value('lolomo_root_id', '', TABLE_SESSION) context_index = g.LOCAL_DB.get_value('lolomo_continue_watching_index', '', TABLE_SESSION) context_id = g.LOCAL_DB.get_value('lolomo_continue_watching_id', '', TABLE_SESSION) path = [['lolomos', lolomo_root, 'refreshListByContext']] # The fourth parameter is like a request-id, but it doesn't seem to match to # serverDefs/date/requestId of reactContext (g.LOCAL_DB.get_value('request_id', table=TABLE_SESSION)) # nor to request_id of the video event request # has a kind of relationship with renoMessageId suspect with the logblob but i'm not sure because my debug crashed, # and i am no longer able to trace the source. # I noticed also that this request can also be made with the fourth parameter empty, # but it still doesn't update the continueWatching list of lolomo, that is strange because of no error params = [common.enclose_quotes(context_id), context_index, common.enclose_quotes(context_name), ''] # path_suffixs = [ # [['trackIds', 'context', 'length', 'genreId', 'videoId', 'displayName', 'isTallRow', 'isShowAsARow', # 'impressionToken', 'showAsARow', 'id', 'requestId']], # [{'from': 0, 'to': 100}, 'reference', 'summary'], # [{'from': 0, 'to': 100}, 'reference', 'title'], # [{'from': 0, 'to': 100}, 'reference', 'titleMaturity'], # [{'from': 0, 'to': 100}, 'reference', 'userRating'], # [{'from': 0, 'to': 100}, 'reference', 'userRatingRequestId'], # [{'from': 0, 'to': 100}, 'reference', 'boxarts', '_342x192', 'jpg'], # [{'from': 0, 'to': 100}, 'reference', 'promoVideo'] # ] callargs = { 'callpaths': path, 'params': params, # 'path_suffixs': path_suffixs } try: response = common.make_http_call('callpath_request', callargs) common.debug('refreshListByContext response: {}', response) except Exception: # pylint: disable=broad-except # I do not know the reason yet, but sometimes continues to return error 401, # making it impossible to update the bookmark position ui.show_notification(title=common.get_local_string(30105), msg='An error prevented the update the lolomo context on netflix', time=10000)
def search_add(): """Perform actions to add and execute a new research""" # Ask to user the type of research search_types_desc = [ SEARCH_TYPES_DESC.get(stype, 'Unknown') for stype in SEARCH_TYPES ] type_index = ui.show_dlg_select(common.get_local_string(30401), search_types_desc) if type_index == -1: # Cancelled return False # If needed ask to user other info, then save the research to the database search_type = SEARCH_TYPES[type_index] row_id = None if search_type == 'text': search_term = ui.ask_for_search_term() if search_term and search_term.strip(): row_id = G.LOCAL_DB.insert_search_item(SEARCH_TYPES[type_index], search_term.strip()) elif search_type == 'audio_lang': row_id = _search_add_bylang(SEARCH_TYPES[type_index], api.get_available_audio_languages()) elif search_type == 'subtitles_lang': row_id = _search_add_bylang(SEARCH_TYPES[type_index], api.get_available_subtitles_languages()) elif search_type == 'genre_id': genre_id = ui.show_dlg_input_numeric(search_types_desc[type_index], mask_input=False) if genre_id: row_id = _search_add_bygenreid(SEARCH_TYPES[type_index], genre_id) else: raise NotImplementedError( 'Search type index {} not implemented'.format(type_index)) # Redirect to "search" endpoint (otherwise no results in JSON-RPC) # Rewrite path history using dir_update_listing + container_update # (otherwise will retrigger input dialog on Back or Container.Refresh) if row_id is not None and search_query(row_id, 0, False): url = common.build_url(['search', 'search', row_id], mode=G.MODE_DIRECTORY, params={'dir_update_listing': True}) common.container_update(url, False) return True return False
def try_refresh_session_data(self, raise_exception=False): """Refresh session data from the Netflix website""" from requests import exceptions try: self.auth_url = website.extract_session_data( self.get('browse'))['auth_url'] cookies.save(self.session.cookies) LOG.debug('Successfully refreshed session data') return True except MbrStatusError: raise except (WebsiteParsingError, MbrStatusAnonymousError) as exc: import traceback LOG.warn( 'Failed to refresh session data, login can be expired or the password has been changed ({})', type(exc).__name__) LOG.debug(G.py2_decode(traceback.format_exc(), 'latin-1')) self.session.cookies.clear() if isinstance(exc, MbrStatusAnonymousError): # This prevent the MSL error: No entity association record found for the user common.send_signal(signal=common.Signals.CLEAR_USER_ID_TOKENS) # Needed to do a new login common.purge_credentials() ui.show_notification(common.get_local_string(30008)) raise_from(NotLoggedInError, exc) except exceptions.RequestException: import traceback LOG.warn( 'Failed to refresh session data, request error (RequestException)' ) LOG.warn(G.py2_decode(traceback.format_exc(), 'latin-1')) if raise_exception: raise except Exception: # pylint: disable=broad-except import traceback LOG.warn( 'Failed to refresh session data, login expired (Exception)') LOG.debug(G.py2_decode(traceback.format_exc(), 'latin-1')) self.session.cookies.clear() if raise_exception: raise return False
def start_services(self): """ Start the background services """ from resources.lib.services.playback.action_controller import ActionController from resources.lib.services.library_updater import LibraryUpdateService from resources.lib.services.settings_monitor import SettingsMonitor for server in self.SERVERS: server['instance'].server_activate() server['instance'].timeout = 1 server['thread'].start() info('[{}] Thread started'.format(server['name'])) self.controller = ActionController() self.library_updater = LibraryUpdateService() self.settings_monitor = SettingsMonitor() # Mark the service as active self._set_service_status('running') if not g.ADDON.getSettingBool('disable_startup_notification'): from resources.lib.kodi.ui import show_notification show_notification(get_local_string(30110))
def search(self, pathitems): """Ask for a search term if none is given via path, query API and display search results""" if len(pathitems) == 2: # Show 'search term' window search_term = ui.ask_for_search_term() pathitems.append(search_term) else: # Do a research search_term = pathitems[2] if search_term: search_results = api.search(search_term, self.perpetual_range_start) if search_results.videos: listings.build_video_listing(search_results, g.MAIN_MENU_ITEMS['search'], pathitems) _handle_endofdirectory(self.dir_update_listing) else: ui.show_notification(common.get_local_string(30013)) xbmcplugin.endOfDirectory(g.PLUGIN_HANDLE, succeeded=False) else: xbmcplugin.endOfDirectory(g.PLUGIN_HANDLE, succeeded=False)
def _remove_from_kodi_library(videoid): """Remove an item from the Kodi library.""" common.debug('Removing {} videoid from Kodi library'.format(videoid)) try: kodi_library_item = get_item(videoid) rpc_params = { 'movie': ['VideoLibrary.RemoveMovie', 'movieid'], 'show': ['VideoLibrary.RemoveTVShow', 'tvshowid'], 'episode': ['VideoLibrary.RemoveEpisode', 'episodeid'] }[videoid.mediatype] common.json_rpc(rpc_params[0], {rpc_params[1]: kodi_library_item[rpc_params[1]]}) except ItemNotFound: common.debug( 'Cannot remove {} from Kodi library, item not present'.format( videoid)) except KeyError as exc: ui.show_notification(common.get_local_string(30120), time=7500) common.warn('Cannot remove {} from Kodi library, ' 'Kodi does not support this (yet)'.format(exc))
def __init__(self, path_response): self.perpetual_range_selector = path_response.get( '_perpetual_range_selector') self.data = path_response has_data = 'search' in path_response self.videos = OrderedDict() self.videoids = None self.artitem = None self.contained_titles = None if has_data: self.title = common.get_local_string(30100).format( list(self.data['search']['byTerm'])[0][1:]) self.videos = OrderedDict( resolve_refs( list(self.data['search']['byReference'].values())[0], self.data)) self.videoids = _get_videoids(self.videos) # self.artitem = next(itervalues(self.videos), None) self.artitem = listvalues(self.videos)[0] if self.videos else None self.contained_titles = _get_titles(self.videos)
def export_new_episodes(videoid, silent=False): """ Export new episodes for a tv show by it's video id :param videoid: The videoid of the tv show to process :param scan: Whether or not to scan the library after exporting, useful for a single show :param silent: don't display user interface while exporting :return: None """ method = execute_library_tasks_silently if silent else execute_library_tasks if videoid.mediatype == common.VideoId.SHOW: common.debug('Exporting new episodes for {}'.format(videoid)) method(videoid, [export_new_item], title=common.get_local_string(30198), sync_mylist=False) else: common.debug( '{} is not a tv show, no new episodes will be exported'.format( videoid))