def add(self, bucket, identifier, data, ttl=None, expires=None, delayed_db_op=False): """ Add or update an item to a cache bucket :param bucket: bucket where save the data :param identifier: key identifier of the data :param data: the content :param ttl: override default expiration (in seconds) :param expires: override default expiration (in timestamp) if specified override also the 'ttl' value :param delayed_db_op: if True, queues the adding operation for the db, then is mandatory to call 'execute_pending_db_add' at end of all operations to apply the changes to the db (only for persistent buckets) """ call_args = { 'bucket': bucket, 'identifier': identifier, 'data': data, 'ttl': ttl, 'expires': expires, 'delayed_db_op': delayed_db_op } # make_call('add', call_args, IPC_ENDPOINT_CACHE) make_http_call(IPC_ENDPOINT_CACHE, 'add', call_args)
def _process_event_request(self, event): """Do the event post request""" event.status = Event.STATUS_REQUESTED # Request attempts can be made up to a maximum of 3 times per event LOG.info('EVENT [{}] - Executing request', event) endpoint_url = ENDPOINTS['events'] + create_req_params( 20 if event.event_type == EVENT_START else 0, 'events/{}'.format(event)) try: response = self.chunked_request(endpoint_url, event.request_data, get_esn(), disable_msl_switch=False) # Malformed/wrong content in requests are ignored without returning error feedback in the response event.set_response(response) except Exception as exc: # pylint: disable=broad-except LOG.error('EVENT [{}] - The request has failed: {}', event, exc) event.set_response('RequestError') if event.event_type == EVENT_STOP and event.status == Event.STATUS_SUCCESS: self.clear_queue() if event.event_data['allow_request_update_loco']: # Calls to nfsession common.make_http_call('update_loco_context', {'context_name': 'continueWatching'}) common.make_http_call('update_videoid_bookmark', {'video_id': event.get_video_id()}) return True
def clear(self, buckets=None, clear_database=True): """ Clear the cache :param buckets: list of buckets to clear, if not specified clear all the cache :param clear_database: if True clear also the database data """ call_args = {'buckets': buckets, 'clear_database': clear_database} # make_call('clear', call_args, IPC_ENDPOINT_CACHE) make_http_call(IPC_ENDPOINT_CACHE, 'clear', call_args)
def delete(self, bucket, identifier, including_suffixes=False): """ Delete an item from cache bucket :param including_suffixes: if true will delete all items with the identifier that start with it """ call_args = { 'bucket': bucket, 'identifier': identifier, 'including_suffixes': including_suffixes } # make_call('delete', call_args, IPC_ENDPOINT_CACHE) make_http_call(IPC_ENDPOINT_CACHE, 'delete', call_args)
def remove_watched_status(self, videoid): """Remove the watched status from the Netflix service""" if not ui.ask_for_confirmation( common.get_local_string(30168), common.get_local_string(30300).format( xbmc.getInfoLabel('ListItem.Label'))): return if not api.remove_watched_status(videoid): ui.show_notification( 'The operation was cancelled due to an unexpected error') return # Check if item is in the cache videoid_exists, list_id = common.make_http_call( 'get_continuewatching_videoid_exists', {'video_id': str(videoid.value)}) if videoid_exists: # Try to remove the videoid from the list in the cache try: video_list_sorted_data = G.CACHE.get(cache_utils.CACHE_COMMON, list_id) del video_list_sorted_data.videos[videoid.value] G.CACHE.add(cache_utils.CACHE_COMMON, list_id, video_list_sorted_data) common.json_rpc( 'Input.Down') # Avoids selection back to the top except CacheMiss: pass common.container_refresh()
def update_lolomo_context(context_name): """Update the lolomo list by context""" # 01/06/2020: refreshListByContext often return HTTP error 500, currently i have seen that in the website is # performed only when the video played is not added to "my list", but with a strange mixed data: # context_id: the id of continueWatching # context_index: that seem to point to "My list" id context index # This api is no more needed to update the continueWatching lolomo list lolomo_root = g.LOCAL_DB.get_value('lolomo_root_id', '', TABLE_SESSION) context_index = g.LOCAL_DB.get_value( 'lolomo_{}_index'.format(context_name.lower()), '', TABLE_SESSION) context_id = g.LOCAL_DB.get_value( 'lolomo_{}_id'.format(context_name.lower()), '', TABLE_SESSION) if not context_index: common.warn( 'Update lolomo context {} skipped due to missing lolomo index', context_name) return 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) # The call response return the new context id of the previous invalidated lolomo 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 lolomo context on netflix', time=10000)
def update_lolomo_context(context_name, video_id=None): """Update the lolomo list by context""" # Should update the context list but it doesn't, what is missing? # The remaining requests made on the website that are missing here are of logging type, # it seems strange that they use log data to finish the operations are almost impossible to reproduce here: # pbo_logblobs /logblob # personalization/cl2 lolomo_data = common.make_call('path_request', [["lolomo", [context_name], ['context', 'id', 'index']]]) # Note: lolomo root seem differs according to the profile in use lolomo_root = lolomo_data['lolomo'][1] context_index = lolomo_data['lolomos'][lolomo_root][context_name][2] context_id = lolomo_data['lolomos'][lolomo_root][context_index][1] path = [['lolomos', lolomo_root, 'refreshListByContext']] params = [common.enclose_quotes(context_id), context_index, common.enclose_quotes(context_name), common.enclose_quotes(g.LOCAL_DB.get_value('request_id', table=TABLE_SESSION))] # 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 } response = common.make_http_call('callpath_request', callargs) common.debug('refreshListByContext response: {}', response) callargs = { 'callpaths': [['refreshVideoCurrentPositions']], 'params': ['[' + video_id + ']', ''], } response = common.make_http_call('callpath_request', callargs) common.debug('refreshVideoCurrentPositions response: {}', response)
def get(self, bucket, identifier): """Get a item from cache bucket""" call_args = { 'bucket': bucket, 'identifier': identifier, 'deserialize_data': False } # Todo: currently AddonSignals does not support serialization of objects # return make_call('get', call_args, IPC_ENDPOINT_CACHE) return make_http_call(IPC_ENDPOINT_CACHE, 'get', call_args)
def on_playback_started(self, player_state): # Clear continue watching list data on the cache, to force loading of new data # but only when the videoid not exists in the continue watching list videoid_exists, list_id = common.make_http_call('get_continuewatching_videoid_exists', {'video_id': str(self.videoid_parent.value)}) if not videoid_exists: # Delete the cache of continueWatching list G.CACHE.delete(CACHE_COMMON, list_id, including_suffixes=True) # When the continueWatching context is invalidated from a refreshListByContext call # the LoCo need to be updated to obtain the new list id, so we delete the cache to get new data G.CACHE.delete(CACHE_COMMON, 'loco_list')
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_{}_index'.format(context_name.lower()), '', TABLE_SESSION) context_id = g.LOCAL_DB.get_value( 'lolomo_{}_id'.format(context_name.lower()), '', TABLE_SESSION) if not context_index: return 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 if not common.is_debug_verbose(): return ui.show_notification( title=common.get_local_string(30105), msg='An error prevented the update the lolomo context on netflix', time=10000)
def _process_event_request(self, event): """Do the event post request""" event.status = Event.STATUS_REQUESTED # Request attempts can be made up to a maximum of 3 times per event while event.is_attempts_granted(): common.info('EVENT [{}] - Executing request (attempt {})', event, event.req_attempt) params = { 'reqAttempt': event.req_attempt, 'reqPriority': 20 if event.event_type == EVENT_START else 0, 'reqName': 'events/{}'.format(event) } url = ENDPOINTS['events'] + '?' + urlencode(params).replace( '%2F', '/') try: response = self.chunked_request(url, event.request_data, G.get_esn(), disable_msl_switch=False) event.set_response(response) break except Exception as exc: # pylint: disable=broad-except common.error('EVENT [{}] - The request has failed: {}', event, exc) if event.event_type == EVENT_STOP: self.clear_queue() if event.event_data['allow_request_update_loco']: # Calls to nfsession common.make_http_call('update_loco_context', {'context_name': 'continueWatching'}) common.make_http_call('update_videoid_bookmark', {'video_id': event.get_video_id()}) # Below commented lines: let future requests continue to be sent, unstable connections like wi-fi cause problems # if not event.is_response_success(): # The event request is unsuccessful then there is some problem, # no longer make any future requests from this event id # return False return True
def on_playback_started(self, player_state): # Clear continue watching list data on the cache, to force loading of new data # but only when the videoid not exists in the continue watching list try: videoid_exists, list_id = common.make_http_call('get_continuewatching_videoid_exists', {'video_id': str(self.videoid_parent.value)}) if not videoid_exists: # Delete the cache of continueWatching list G.CACHE.delete(CACHE_COMMON, list_id, including_suffixes=True) # When the continueWatching context is invalidated from a refreshListByContext call # the LoCo need to be updated to obtain the new list id, so we delete the cache to get new data G.CACHE.delete(CACHE_COMMON, 'loco_list') except InvalidVideoListTypeError: # Ignore possible "No lists with context xxx available" exception due to a new profile without data pass
def on_playback_started(self, player_state): # Clear continue watching list data on the cache, to force loading of new data # but only when the videoid not exists in the continue watching list current_videoid = (self.videoid if self.videoid.mediatype == common.VideoId.MOVIE else self.videoid.derive_parent(0)) videoid_exists, list_id = common.make_http_call( 'get_continuewatching_videoid_exists', {'video_id': str(current_videoid.value)}) if not videoid_exists: # Delete the cache of continueWatching list g.CACHE.delete(CACHE_COMMON, list_id, including_suffixes=True) # When the continueWatching context is invalidated from a refreshListByContext call # the lolomo need to be updated to obtain the new list id, so we delete the cache to get new data g.CACHE.delete(CACHE_COMMON, 'lolomo_list')
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 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(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 auto_update_library(sync_with_mylist, silent): """ Perform an auto update of the exported items to Kodi library, so check if there is new seasons/episodes. If sync_with_mylist is enabled the Kodi library will be also synchronized with the Netflix "My List". :param sync_with_mylist: True to enable sync with My List :param silent: don't display user interface while performing an operation :return: None """ if _is_auto_update_library_running(): return execute_lib_tasks_method = execute_library_tasks_silently if silent else execute_library_tasks common.info( 'Starting auto update library - check updates for tv shows (sync with My List is {})', 'ENABLED' if sync_with_mylist else 'DISABLED') g.SHARED_DB.set_value('library_auto_update_is_running', True) g.SHARED_DB.set_value('library_auto_update_start_time', datetime.now()) try: videoids_to_update = [] # Get the list of the exported items to Kodi library exported_tvshows_videoids_values = g.SHARED_DB.get_tvshows_id_list() exported_movies_videoids_values = g.SHARED_DB.get_movies_id_list() if sync_with_mylist: # Get My List videoids of the chosen profile # Use make_http_call instead make_http because call AddonSignals on same instance makes problems mylist_video_id_list, mylist_video_id_list_type = common.make_http_call( 'get_mylist_videoids_profile_switch', None) # Check if tv shows have been removed from the My List for videoid_value in exported_tvshows_videoids_values: if unicode(videoid_value) in mylist_video_id_list: continue # The tv show no more exist in My List so remove it from library videoid = common.VideoId.from_path( [common.VideoId.SHOW, videoid_value]) execute_lib_tasks_method(videoid, [remove_item]) # Check if movies have been removed from the My List for videoid_value in exported_movies_videoids_values: if unicode(videoid_value) in mylist_video_id_list: continue # The movie no more exist in My List so remove it from library videoid = common.VideoId.from_path( [common.VideoId.MOVIE, videoid_value]) execute_lib_tasks_method(videoid, [remove_item]) # Add missing tv shows / movies of My List to library for index, video_id in enumerate(mylist_video_id_list): if (int(video_id) not in exported_tvshows_videoids_values and int(video_id) not in exported_movies_videoids_values): videoids_to_update.append( common.VideoId( **{ ('movieid' if (mylist_video_id_list_type[index] == 'movie') else 'tvshowid'): video_id })) # Add the exported tv shows to be updated to the list.. tvshows_videoids_to_upd = [ common.VideoId.from_path([common.VideoId.SHOW, videoid_value]) for videoid_value in g.SHARED_DB.get_tvshows_id_list( VidLibProp['exclude_update'], False) ] # ..and avoids any duplication caused by possible unexpected errors videoids_to_update.extend( list(set(tvshows_videoids_to_upd) - set(videoids_to_update))) # Add missing tv shows/movies or update existing tv shows _update_library(videoids_to_update, exported_tvshows_videoids_values, silent) common.debug('Auto update of the library completed') g.SHARED_DB.set_value('library_auto_update_is_running', False) if not g.ADDON.getSettingBool('lib_auto_upd_disable_notification'): ui.show_notification(common.get_local_string(30220), time=5000) common.debug( 'Notify service to communicate to Kodi of update the library') common.send_signal(common.Signals.LIBRARY_UPDATE_REQUESTED) except Exception: # pylint: disable=broad-except import traceback common.error('An error has occurred in the library auto update') common.error(traceback.format_exc()) g.SHARED_DB.set_value('library_auto_update_is_running', False)