Пример #1
0
 def prefetch_login(self):
     """Check if we have stored credentials.
     If so, do the login before the user requests it"""
     from requests import exceptions
     try:
         common.get_credentials()
         if not self.is_logged_in():
             self.login()
         return True
     except MissingCredentialsError:
         pass
     except exceptions.RequestException as exc:
         # It was not possible to connect to the web service, no connection, network problem, etc
         import traceback
         LOG.error('Login prefetch: request exception {}', exc)
         LOG.debug(G.py2_decode(traceback.format_exc(), 'latin-1'))
     except Exception as exc:  # pylint: disable=broad-except
         LOG.warn('Login prefetch: failed {}', exc)
     return False
Пример #2
0
def _migrate_strm_files(folder_path):
    # Change path in STRM files
    for filename in list_dir(folder_path)[1]:
        if not filename.endswith('.strm'):
            continue
        file_path = join_folders_paths(folder_path, filename)
        file_content = load_file(file_path)
        if not file_content:
            LOG.warn(
                'Migrate error: "{}" skipped, STRM file empty or corrupted',
                file_path)
            continue
        if 'action=play_video' in file_content:
            LOG.warn('Migrate error: "{}" skipped, STRM file type of v0.13.x',
                     file_path)
            continue
        file_content = file_content.strip('\t\n\r').replace(
            '/play/', '/play_strm/')
        save_file(file_path, file_content.encode('utf-8'))
Пример #3
0
    def _get_player_state(self, player_id=None, time_override=None):
        try:
            player_state = common.json_rpc('Player.GetProperties', {
                'playerid': self.active_player_id if player_id is None else player_id,
                'properties': [
                    'audiostreams',
                    'currentaudiostream',
                    'currentvideostream',
                    'subtitles',
                    'currentsubtitle',
                    'subtitleenabled',
                    'percentage',
                    'time']
            })
        except IOError as exc:
            LOG.warn('_get_player_state: {}', exc)
            return {}

        # convert time dict to elapsed seconds
        player_state['elapsed_seconds'] = (player_state['time']['hours'] * 3600 +
                                           player_state['time']['minutes'] * 60 +
                                           player_state['time']['seconds'])

        if time_override:
            player_state['time'] = time_override
            elapsed_seconds = (time_override['hours'] * 3600 +
                               time_override['minutes'] * 60 +
                               time_override['seconds'])
            player_state['percentage'] = player_state['percentage'] / player_state['elapsed_seconds'] * elapsed_seconds
            player_state['elapsed_seconds'] = elapsed_seconds

        # Sometimes may happen that when you stop playback the player status is partial,
        # this is because the Kodi player stop immediately but the stop notification (from the Monitor)
        # arrives late, meanwhile in this interval of time a service tick may occur.
        if ((player_state['audiostreams'] and player_state['elapsed_seconds']) or
                (player_state['audiostreams'] and not player_state['elapsed_seconds'] and not self._last_player_state)):
            # save player state
            self._last_player_state = player_state
        else:
            # use saved player state
            player_state = self._last_player_state

        return player_state
Пример #4
0
 def _update_library(self, videoids_tasks, exp_tvshows_videoids_values, show_prg_dialog, show_nfo_dialog,
                     clear_on_cancel):
     # If set ask to user if want to export NFO files (override user custom NFO settings for videoids)
     nfo_settings_override = None
     if show_nfo_dialog:
         nfo_settings_override = nfo.NFOSettings()
         nfo_settings_override.show_export_dialog()
     # Get the exported tvshows, but to be excluded from the updates
     excluded_videoids_values = G.SHARED_DB.get_tvshows_id_list(VidLibProp['exclude_update'], True)
     # Start the update operations
     with ui.ProgressDialog(show_prg_dialog, max_value=len(videoids_tasks)) as progress_bar:
         for videoid, task_handler in iteritems(videoids_tasks):
             # Check if current videoid is excluded from updates
             if int(videoid.value) in excluded_videoids_values:
                 continue
             # Get the NFO settings for the current videoid
             if not nfo_settings_override and int(videoid.value) in exp_tvshows_videoids_values:
                 # User custom NFO setting
                 # it is possible that the user has chosen not to export NFO files for a specific tv show
                 nfo_export = G.SHARED_DB.get_tvshow_property(videoid.value,
                                                              VidLibProp['nfo_export'], False)
                 nfo_settings = nfo.NFOSettings(nfo_export)
             else:
                 nfo_settings = nfo_settings_override or nfo.NFOSettings()
             # Execute the task
             for index, total_tasks, title in self.execute_library_task(videoid,
                                                                        task_handler,
                                                                        nfo_settings=nfo_settings,
                                                                        notify_errors=show_prg_dialog):
                 label_partial_op = ' ({}/{})'.format(index + 1, total_tasks) if total_tasks > 1 else ''
                 progress_bar.set_message(title + label_partial_op)
             if progress_bar.is_cancelled():
                 LOG.warn('Auto update of the Kodi library interrupted by User')
                 if clear_on_cancel:
                     self.clear_library(True)
                 return False
             if self.monitor.abortRequested():
                 LOG.warn('Auto update of the Kodi library interrupted by Kodi')
                 return False
             progress_bar.perform_step()
             progress_bar.set_wait_message()
             delay_anti_ban()
     return True
Пример #5
0
def route(pathitems):
    """Route to the appropriate handler"""
    LOG.debug('Routing navigation request')
    root_handler = pathitems[0] if pathitems else G.MODE_DIRECTORY
    if root_handler == G.MODE_PLAY:
        from resources.lib.navigation.player import play
        play(videoid=pathitems[1:])
    elif root_handler == G.MODE_PLAY_STRM:
        from resources.lib.navigation.player import play_strm
        play_strm(videoid=pathitems[1:])
    elif root_handler == 'extrafanart':
        LOG.warn('Route: ignoring extrafanart invocation')
        return False
    else:
        nav_handler = _get_nav_handler(root_handler)
        if not nav_handler:
            raise InvalidPathError('No root handler for path {}'.format('/'.join(pathitems)))
        _execute(nav_handler, pathitems[1:], G.REQUEST_PARAMS)
    return True
 def _process_event_request(self, event_type, event_data, player_state):
     """Build and make the event post request"""
     if event_type == EVENT_START:
         # We get at every new video playback a fresh LoCo data
         self.loco_data = self.nfsession.get_loco_data()
     url = event_data['manifest']['links']['events']['href']
     from resources.lib.services.nfsession.msl.msl_request_builder import MSLRequestBuilder
     request_data = MSLRequestBuilder.build_request_data(
         url,
         self._build_event_params(event_type, event_data, player_state,
                                  event_data['manifest'], self.loco_data))
     # Request attempts can be made up to a maximum of 3 times per event
     LOG.info('EVENT [{}] - Executing request', event_type)
     endpoint_url = ENDPOINTS['events'] + create_req_params(
         20 if event_type == EVENT_START else 0, f'events/{event_type}')
     try:
         response = self.chunked_request(endpoint_url,
                                         request_data,
                                         get_esn(),
                                         disable_msl_switch=False)
         # Malformed/wrong content in requests are ignored without returning any error in the response or exception
         LOG.debug('EVENT [{}] - Request response: {}', event_type,
                   response)
         if event_type == EVENT_STOP:
             if event_data['allow_request_update_loco']:
                 if 'list_context_name' in self.loco_data:
                     self.nfsession.update_loco_context(
                         self.loco_data['root_id'],
                         self.loco_data['list_context_name'],
                         self.loco_data['list_id'],
                         self.loco_data['list_index'])
                 else:
                     LOG.warn(
                         'EventsHandler: LoCo list not updated due to missing list context data'
                     )
                 video_id = request_data['params']['sessionParams'][
                     'uiplaycontext']['video_id']
                 self.nfsession.update_videoid_bookmark(video_id)
             self.loco_data = None
     except Exception as exc:  # pylint: disable=broad-except
         LOG.error('EVENT [{}] - The request has failed: {}', event_type,
                   exc)
    def compile_jobs_data(self, videoid, task_type, nfo_settings=None):
        """Compile a list of jobs data based on the videoid"""
        LOG.debug(
            'Compiling list of jobs data for task handler "{}" and videoid "{}"',
            task_type.__name__, videoid)
        jobs_data = None
        try:
            if task_type == self.export_item:
                metadata = self.ext_func_get_metadata(videoid)  # pylint: disable=not-callable
                if videoid.mediatype == common.VideoId.MOVIE:
                    jobs_data = [
                        self._create_export_movie_job(videoid, metadata[0],
                                                      nfo_settings)
                    ]
                if videoid.mediatype in common.VideoId.TV_TYPES:
                    jobs_data = self._create_export_tvshow_jobs(
                        videoid, metadata, nfo_settings)

            if task_type == self.export_new_item:
                metadata = self.ext_func_get_metadata(videoid, True)  # pylint: disable=not-callable
                jobs_data = self._create_export_new_episodes_jobs(
                    videoid, metadata, nfo_settings)

            if task_type == self.remove_item:
                if videoid.mediatype == common.VideoId.MOVIE:
                    jobs_data = [self._create_remove_movie_job(videoid)]
                if videoid.mediatype == common.VideoId.SHOW:
                    jobs_data = self._create_remove_tvshow_jobs(videoid)
                if videoid.mediatype == common.VideoId.SEASON:
                    jobs_data = self._create_remove_season_jobs(videoid)
                if videoid.mediatype == common.VideoId.EPISODE:
                    jobs_data = [self._create_remove_episode_job(videoid)]
        except MetadataNotAvailable:
            LOG.warn(
                'Unavailable metadata for videoid "{}", list of jobs not compiled',
                videoid)
            return None
        if jobs_data is None:
            LOG.error(
                'Unexpected job compile case for task type "{}" and videoid "{}", list of jobs not compiled',
                task_type.__name__, videoid)
        return jobs_data
Пример #8
0
def _get_authentication_key_data(file_path, pin):
    """Open the auth key file"""
    from resources.lib.kodi import ui
    try:
        file_content = load_file(file_path)
        iv = '\x00' * 16
        cipher = AES.new((pin + pin + pin + pin).encode("utf-8"), AES.MODE_CBC,
                         iv.encode("utf-8"))
        decoded = Padding.unpad(padded_data=cipher.decrypt(
            base64.b64decode(file_content)),
                                block_size=16)
        return json.loads(decoded.decode('utf-8'))
    except ValueError:
        # ValueError should always means wrong decryption due to wrong key
        ui.show_ok_dialog(get_local_string(30342), get_local_string(30106))
        return ''
    except Exception as exc:  # pylint: disable=broad-except
        LOG.warn('Exception raised: {}', exc)
        ui.show_ok_dialog(get_local_string(30342), get_local_string(30343))
    return None
Пример #9
0
def route(pathitems):
    """Route to the appropriate handler"""
    LOG.debug('Routing navigation request')
    if pathitems:
        if 'extrafanart' in pathitems:
            LOG.warn('Route: ignoring extrafanart invocation')
            return False
        root_handler = pathitems[0]
    else:
        root_handler = G.MODE_DIRECTORY
    if root_handler == G.MODE_PLAY:
        from resources.lib.navigation.player import play
        play(videoid=pathitems[1:])
    elif root_handler == G.MODE_PLAY_STRM:
        from resources.lib.navigation.player import play_strm
        play_strm(videoid=pathitems[1:])
    else:
        nav_handler = _get_nav_handler(root_handler, pathitems)
        _execute(nav_handler, pathitems[1:], G.REQUEST_PARAMS, root_handler)
    return True
 def initialize(self, data):
     if not xbmc.getCondVisibility('System.AddonIsEnabled(service.upnext)'):
         return
     videoid_next_ep = _upnext_get_next_episode_videoid(
         data['videoid'], data['metadata'])
     if not videoid_next_ep:
         return
     info_next_ep = self.nfsession.get_videoid_info(videoid_next_ep)
     try:
         self.upnext_info = self._get_upnext_info(
             videoid_next_ep, info_next_ep, data['metadata'],
             data['is_played_from_strm'])
     except DBRecordNotExistError:
         # The videoid record of the STRM episode is missing in add-on database when:
         # - The metadata have a new episode, but the STRM is not exported yet
         # - User try to play STRM files copied from another/previous add-on installation (without import them)
         # - User try to play STRM files from a shared path (like SMB) of another device (without use shared db)
         LOG.warn(
             'Up Next add-on signal skipped, the videoid for the next episode does not exist in the database'
         )
Пример #11
0
 def rate_thumb(self, videoid):
     """Rate an item on Netflix. Ask for a thumb rating"""
     # Get updated user rating info for this videoid
     raw_data = api.get_video_raw_data([videoid], VIDEO_LIST_RATING_THUMB_PATHS)
     if raw_data.get('videos', {}).get(videoid.value):
         video_data = raw_data['videos'][videoid.value]
         title = video_data.get('title')
         track_id_jaw = video_data.get('trackIds', {})['trackId_jaw']
         is_thumb_rating = video_data.get('userRating', {}).get('type', '') == 'thumb'
         user_rating = video_data.get('userRating', {}).get('userRating') if is_thumb_rating else None
         ui.show_modal_dialog(False,
                              ui.xmldialogs.RatingThumb,
                              'plugin-video-netflix-RatingThumb.xml',
                              G.ADDON.getAddonInfo('path'),
                              videoid=videoid,
                              title=title,
                              track_id_jaw=track_id_jaw,
                              user_rating=user_rating)
     else:
         LOG.warn('Rating thumb video list api request no got results for {}', videoid)
Пример #12
0
 def import_videoid_from_existing_strm(self, folder_path, folder_name):
     """
     Get a VideoId from an existing STRM file that was exported
     """
     for filename in common.list_dir(folder_path)[1]:
         filename = G.py2_decode(filename)
         if not filename.endswith('.strm'):
             continue
         file_path = common.join_folders_paths(folder_path, filename)
         # Only get a VideoId from the first file in each folder.
         # For tv shows all episodes will result in the same VideoId, the movies only contain one file.
         file_content = common.load_file(file_path)
         if not file_content:
             LOG.warn('Import error: folder "{}" skipped, STRM file empty or corrupted', folder_name)
             return None
         if 'action=play_video' in file_content:
             LOG.debug('Trying to import (v0.13.x): {}', file_path)
             return self._import_videoid_old(file_content, folder_name)
         LOG.debug('Trying to import: {}', file_path)
         return self._import_videoid(file_content, folder_name)
Пример #13
0
    def login(self, credentials=None):
        """Perform account login with credentials"""
        try:
            # First we get the authentication url without logging in, required for login API call
            self.session.cookies.clear()
            react_context = website.extract_json(self.get('login'),
                                                 'reactContext')
            auth_url = website.extract_api_data(react_context)['auth_url']
            LOG.debug('Logging in with credentials')
            login_response = self.post(
                'login',
                headers={
                    'Accept-Language':
                    _get_accept_language_string(react_context)
                },
                data=_login_payload(credentials or common.get_credentials(),
                                    auth_url, react_context))

            website.extract_session_data(login_response,
                                         validate=True,
                                         update_profiles=True)
            if credentials:
                # Save credentials only when login has succeeded
                common.set_credentials(credentials)
            LOG.info('Login successful')
            ui.show_notification(common.get_local_string(30109))
            cookies.save(self.session.cookies)
            return True
        except LoginValidateError as exc:
            self.session.cookies.clear()
            common.purge_credentials()
            raise LoginError(str(exc)) from exc
        except (MbrStatusNeverMemberError, MbrStatusFormerMemberError) as exc:
            self.session.cookies.clear()
            LOG.warn('Membership status {} not valid for login', exc)
            raise LoginError(common.get_local_string(30180)) from exc
        except Exception:  # pylint: disable=broad-except
            self.session.cookies.clear()
            import traceback
            LOG.error(traceback.format_exc())
            raise
 def _request(self, method, endpoint, session_refreshed, **kwargs):
     from requests import exceptions
     endpoint_conf = ENDPOINTS[endpoint]
     url = (_api_url(endpoint_conf['address'])
            if endpoint_conf['is_api_call']
            else _document_url(endpoint_conf['address'], kwargs))
     LOG.debug('Executing {verb} request to {url}',
               verb='GET' if method == self.session.get else 'POST', url=url)
     data, headers, params = self._prepare_request_properties(endpoint_conf, kwargs)
     start = time.perf_counter()
     try:
         response = method(
             url=url,
             verify=self.verify_ssl,
             headers=headers,
             params=params,
             data=data,
             timeout=8)
     except exceptions.ReadTimeout as exc:
         LOG.error('HTTP Request ReadTimeout error: {}', exc)
         raise HttpErrorTimeout from exc
     LOG.debug('Request took {}s', time.perf_counter() - start)
     LOG.debug('Request returned status code {}', response.status_code)
     # for redirect in response.history:
     #     LOG.warn('Redirected to: [{}] {}', redirect.status_code, redirect.url)
     if not session_refreshed:
         # We refresh the session when happen:
         # Error 404: It happen when Netflix update the build_identifier version and causes the api address to change
         # Error 401: This is a generic error, can happen when the http request for some reason has failed,
         #   we allow the refresh only for shakti endpoint, sometimes for unknown reasons it is necessary to update
         #   the session for the request to be successful
         if response.status_code == 404 or (response.status_code == 401 and endpoint == 'shakti'):
             LOG.warn('Attempt to refresh the session due to HTTP error {}', response.status_code)
             if self.try_refresh_session_data():
                 return self._request(method, endpoint, True, **kwargs)
     if response.status_code == 401:
         raise HttpError401
     response.raise_for_status()
     return (_raise_api_error(response.json() if response.content else {})
             if endpoint_conf['is_api_call']
             else response.content)
    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)
Пример #16
0
 def _import_videoid(self, file_content, folder_name):
     file_content = file_content.strip('\t\n\r')
     if G.BASE_URL not in file_content:
         LOG.warn(
             'Import error: folder "{}" skipped, unrecognized plugin name in STRM file',
             folder_name)
         raise ImportWarning
     file_content = file_content.replace(G.BASE_URL, '')
     # file_content should result as, example:
     # - Old STRM path: '/play/show/xxxxxxxx/season/xxxxxxxx/episode/xxxxxxxx/' (used before ver 1.7.0)
     # - New STRM path: '/play_strm/show/xxxxxxxx/season/xxxxxxxx/episode/xxxxxxxx/' (used from ver 1.7.0)
     pathitems = file_content.strip('/').split('/')
     if G.MODE_PLAY not in pathitems and G.MODE_PLAY_STRM not in pathitems:
         LOG.warn(
             'Import error: folder "{}" skipped, unsupported play path in STRM file',
             folder_name)
         raise ImportWarning
     pathitems = pathitems[1:]
     try:
         if pathitems[0] == common.VideoId.SHOW:
             # Get always VideoId of tvshow type (not season or episode)
             videoid = common.VideoId.from_path(pathitems[:2])
         else:
             videoid = common.VideoId.from_path(pathitems)
         # Try to get the videoid metadata, to know if the videoid still exists on netflix
         self.ext_func_get_metadata(videoid)  # pylint: disable=not-callable
         return videoid
     except MetadataNotAvailable:
         LOG.warn(
             'Import error: folder {} skipped, metadata not available for videoid {}',
             folder_name, pathitems[1])
         return None
Пример #17
0
    def remove_item(self, job_data, library_home=None):  # pylint: disable=unused-argument
        """Remove an item from the Kodi library, delete it from disk, remove add-on database references"""
        videoid = job_data['videoid']
        LOG.debug('Removing {} ({}) from add-on library', videoid, job_data['title'])
        try:
            # Remove the STRM file exported
            exported_file_path = G.py2_decode(translatePath(job_data['file_path']))
            common.delete_file_safe(exported_file_path)

            parent_folder = G.py2_decode(translatePath(os.path.dirname(exported_file_path)))

            # Remove the NFO file of the related STRM file
            nfo_file = os.path.splitext(exported_file_path)[0] + '.nfo'
            common.delete_file_safe(nfo_file)

            dirs, files = common.list_dir(parent_folder)

            # Remove the tvshow NFO file (only when it is the last file in the folder)
            tvshow_nfo_file = common.join_folders_paths(parent_folder, 'tvshow.nfo')

            # (users have the option of removing even single seasons)
            if xbmcvfs.exists(tvshow_nfo_file) and not dirs and len(files) == 1:
                xbmcvfs.delete(tvshow_nfo_file)
                # Delete parent folder
                xbmcvfs.rmdir(parent_folder)

            # Delete parent folder when empty
            if not dirs and not files:
                xbmcvfs.rmdir(parent_folder)

            # Remove videoid records from add-on database
            remove_videoid_from_db(videoid)
        except ItemNotFound:
            LOG.warn('The videoid {} not exists in the add-on library database', videoid)
        except Exception as exc:  # pylint: disable=broad-except
            import traceback
            LOG.error(G.py2_decode(traceback.format_exc(), 'latin-1'))
            ui.show_addon_error_info(exc)
Пример #18
0
    def add_event_to_queue(self, event_type, event_data, player_state):
        """Adds an event in the queue of events to be processed"""
        videoid = common.VideoId.from_dict(event_data['videoid'])
        # pylint: disable=unused-variable
        previous_data, previous_player_state = self.cache_data_events.get(videoid.value, ({}, None))
        manifest = get_manifest(videoid)
        url = manifest['links']['events']['href']

        if previous_data.get('xid') in self.banned_events_ids:
            LOG.warn('EVENT [{}] - Not added to the queue. The xid {} is banned due to a previous failed request',
                     event_type, previous_data.get('xid'))
            return

        from resources.lib.services.msl.msl_request_builder import MSLRequestBuilder
        request_data = MSLRequestBuilder.build_request_data(url,
                                                            self._build_event_params(event_type,
                                                                                     event_data,
                                                                                     player_state,
                                                                                     manifest))
        try:
            self.queue_events.put_nowait(Event(request_data, event_data))
        except queue.Full:
            LOG.warn('EVENT [{}] - Not added to the queue. The event queue is full.', event_type)
Пример #19
0
 def _import_videoid_old(self, file_content, folder_name):
     try:
         # The STRM file in add-on v13.x is different and can contains two lines, example:
         #   #EXTINF:-1,Tv show title - "meta data ..."
         #   plugin://plugin.video.netflix/?video_id=12345678&action=play_video
         # Get last line and extract the videoid value
         match = re.search(r'video_id=(\d+)', file_content.split('\n')[-1])
         # Create a videoid of UNSPECIFIED type (we do not know the real type of videoid)
         videoid = common.VideoId(videoid=match.groups()[0])
         # Try to get the videoid metadata:
         # - To know if the videoid still exists on netflix
         # - To get the videoid type
         # - To get the Tv show videoid, in the case of STRM of an episode
         metadata = self.ext_func_get_metadata(videoid)[0]  # pylint: disable=not-callable
         # Generate the a good videoid
         if metadata['type'] == 'show':
             return common.VideoId(tvshowid=metadata['id'])
         return common.VideoId(movieid=metadata['id'])
     except MetadataNotAvailable:
         LOG.warn('Import error: folder {} skipped, metadata not available', folder_name)
         return None
     except (AttributeError, IndexError):
         LOG.warn('Import error: folder {} skipped, STRM not conform to v0.13.x format', folder_name)
         return None
Пример #20
0
 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
Пример #21
0
 def try_refresh_session_data(self, raise_exception=False):
     """Refresh session data from the Netflix website"""
     try:
         self.auth_url = website.extract_session_data(
             self.get('browse'))['auth_url']
         cookies.save(self.session.cookies.jar)
         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(traceback.format_exc())
         self.session.cookies.clear()
         if isinstance(exc, MbrStatusAnonymousError):
             # This prevent the MSL error: No entity association record found for the user
             self.msl_handler.clear_user_id_tokens()
         # Needed to do a new login
         common.purge_credentials()
         ui.show_notification(common.get_local_string(30008))
         raise NotLoggedInError from exc
     except httpx.RequestError:
         import traceback
         LOG.warn(
             'Failed to refresh session data, request error (RequestError)')
         LOG.warn(traceback.format_exc())
         if raise_exception:
             raise
     except Exception:  # pylint: disable=broad-except
         import traceback
         LOG.warn(
             'Failed to refresh session data, login expired (Exception)')
         LOG.debug(traceback.format_exc())
         self.session.cookies.clear()
         if raise_exception:
             raise
     return False
Пример #22
0
def _play(videoid, is_played_from_strm=False):
    """Play an episode or movie as specified by the path"""
    is_upnext_enabled = G.ADDON.getSettingBool('UpNextNotifier_enabled')
    LOG.info('Playing {}{}{}', videoid,
             ' [STRM file]' if is_played_from_strm else '',
             ' [external call]' if G.IS_ADDON_EXTERNAL_CALL else '')

    # Profile switch when playing from a STRM file (library)
    if is_played_from_strm:
        if not _profile_switch():
            xbmcplugin.endOfDirectory(G.PLUGIN_HANDLE, succeeded=False)
            return

    # Get metadata of videoid
    try:
        metadata = api.get_metadata(videoid)
        LOG.debug('Metadata is {}', json.dumps(metadata))
    except MetadataNotAvailable:
        LOG.warn('Metadata not available for {}', videoid)
        metadata = [{}, {}]

    # Check 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=8000)
        xbmcplugin.endOfDirectory(G.PLUGIN_HANDLE, succeeded=False)
        return

    # Generate the xbmcgui.ListItem to be played
    list_item = get_inputstream_listitem(videoid)

    # STRM file resume workaround (Kodi library)
    resume_position = _strm_resume_workaroud(is_played_from_strm, videoid)
    if resume_position == '':
        xbmcplugin.setResolvedUrl(handle=G.PLUGIN_HANDLE,
                                  succeeded=False,
                                  listitem=list_item)
        return

    info_data = None
    event_data = {}
    videoid_next_episode = None

    # Get Infolabels and Arts for the videoid to be played, and for the next video if it is an episode (for UpNext)
    if is_played_from_strm or is_upnext_enabled or G.IS_ADDON_EXTERNAL_CALL:
        if is_upnext_enabled and videoid.mediatype == common.VideoId.EPISODE:
            # When UpNext is enabled, get the next episode to play
            videoid_next_episode = _upnext_get_next_episode_videoid(
                videoid, metadata)
        info_data = infolabels.get_info_from_netflix(
            [videoid, videoid_next_episode]
            if videoid_next_episode else [videoid])
        info, arts = info_data[videoid.value]
        # When a item is played from Kodi library or Up Next add-on is needed set info and art to list_item
        list_item.setInfo('video', info)
        list_item.setArt(arts)

    # Get event data for videoid to be played (needed for sync of watched status with Netflix)
    if (G.ADDON.getSettingBool('ProgressManager_enabled') and videoid.mediatype
            in [common.VideoId.MOVIE, common.VideoId.EPISODE]):
        if not is_played_from_strm or is_played_from_strm and G.ADDON.getSettingBool(
                'sync_watched_status_library'):
            event_data = _get_event_data(videoid)
            event_data['videoid'] = videoid.to_dict()
            event_data['is_played_by_library'] = is_played_from_strm

    # Start and initialize the action controller (see action_controller.py)
    LOG.debug('Sending initialization signal')
    common.send_signal(common.Signals.PLAYBACK_INITIATED, {
        'videoid':
        videoid.to_dict(),
        'videoid_next_episode':
        videoid_next_episode.to_dict() if videoid_next_episode else None,
        'metadata':
        metadata,
        'info_data':
        info_data,
        'is_played_from_strm':
        is_played_from_strm,
        'resume_position':
        resume_position,
        'event_data':
        event_data
    },
                       non_blocking=True)
    xbmcplugin.setResolvedUrl(handle=G.PLUGIN_HANDLE,
                              succeeded=True,
                              listitem=list_item)
 def onNotification(self, sender, method, data):  # pylint: disable=unused-argument
     """
     Callback for Kodi notifications that handles and dispatches playback events
     """
     # WARNING: Do not get playerid from 'data',
     # Because when Up Next add-on play a video while we are inside Netflix add-on and
     # not externally like Kodi library, the playerid become -1 this id does not exist
     if not self.tracking or not method.startswith('Player.'):
         return
     try:
         if method == 'Player.OnPlay':
             if self.init_count > 0:
                 # In this case the user has chosen to play another video while another one is in playing,
                 # then we send the missing Stop event for the current video
                 self._on_playback_stopped()
             self._initialize_am()
         elif method == 'Player.OnAVStart':
             self._on_playback_started()
             self.tracking_tick = True
         elif method == 'Player.OnSeek':
             self._on_playback_seek(json.loads(data)['player']['time'])
         elif method == 'Player.OnPause':
             self._is_pause_called = True
             self._on_playback_pause()
         elif method == 'Player.OnResume':
             # Kodi call this event instead the "Player.OnStop" event when you try to play a video
             # while another one is in playing (also if the current video is in pause)
             # Can be one of following cases:
             # - When you use ctx menu "Play From Here", this happen when click to next button
             # - When you use UpNext add-on
             # - When you play a non-Netflix video when a Netflix video is in playback in background
             # - When you play a video over another in playback (back in menus)
             if not self._is_pause_called:
                 return
             if self.init_count == 0:
                 # This should never happen, we have to avoid this event when you try to play a video
                 # while another non-netflix video is in playing
                 return
             self._is_pause_called = False
             self._on_playback_resume()
         elif method == 'Player.OnStop':
             self.tracking = False
             if self.active_player_id is None:
                 # if playback does not start due to an error in streams initialization
                 # OnAVStart notification will not be called, then active_player_id will be None
                 LOG.debug(
                     'ActionController: Player.OnStop event has been ignored'
                 )
                 LOG.warn(
                     'ActionController: Action managers disabled due to a playback initialization error'
                 )
                 self.action_managers = None
                 self.init_count -= 1
                 return
             self._on_playback_stopped()
     except Exception:  # pylint: disable=broad-except
         import traceback
         LOG.error(traceback.format_exc())
         self.tracking = False
         self.tracking_tick = False
         self.init_count = 0
Пример #24
0
    def __init__(self):
        super(AndroidMSLCrypto, self).__init__()
        self.crypto_session = None
        self.keyset_id = None
        self.key_id = None
        self.hmac_key_id = None
        try:
            self.crypto_session = xbmcdrm.CryptoSession(
                'edef8ba9-79d6-4ace-a3c8-27dcd51d21ed', 'AES/CBC/NoPadding',
                'HmacSHA256')
            LOG.debug('Widevine CryptoSession successful constructed')
        except Exception:  # pylint: disable=broad-except
            import traceback
            LOG.error(G.py2_decode(traceback.format_exc(), 'latin-1'))
            raise MSLError('Failed to construct Widevine CryptoSession')

        drm_info = {
            'version':
            self.crypto_session.GetPropertyString('version'),
            'system_id':
            self.crypto_session.GetPropertyString('systemId'),
            #  'device_unique_id': self.crypto_session.GetPropertyByteArray('deviceUniqueId')
            'hdcp_level':
            self.crypto_session.GetPropertyString('hdcpLevel'),
            'hdcp_level_max':
            self.crypto_session.GetPropertyString('maxHdcpLevel'),
            'security_level':
            self.crypto_session.GetPropertyString('securityLevel')
        }

        if not drm_info['version']:
            # Possible cases where no data is obtained:
            # - Device with custom ROM or without Widevine support
            # - Using Kodi debug build with a InputStream Adaptive release build (yes users do it)
            raise MSLError(
                'It was not possible to get the data from Widevine CryptoSession.\r\n'
                'Your system is not Widevine certified or you have a wrong Kodi version installed.'
            )

        G.LOCAL_DB.set_value('drm_system_id', drm_info['system_id'],
                             TABLE_SESSION)
        G.LOCAL_DB.set_value('drm_security_level', drm_info['security_level'],
                             TABLE_SESSION)
        G.LOCAL_DB.set_value('drm_hdcp_level', drm_info['hdcp_level'],
                             TABLE_SESSION)

        LOG.debug('Widevine version: {}', drm_info['version'])
        if drm_info['system_id']:
            LOG.debug('Widevine CryptoSession system id: {}',
                      drm_info['system_id'])
        else:
            LOG.warn('Widevine CryptoSession system id not obtained!')
        LOG.debug('Widevine CryptoSession security level: {}',
                  drm_info['security_level'])
        if G.ADDON.getSettingBool('force_widevine_l3'):
            LOG.warn(
                'Widevine security level is forced to L3 by user settings!')
        LOG.debug('Widevine CryptoSession current hdcp level: {}',
                  drm_info['hdcp_level'])
        LOG.debug('Widevine CryptoSession max hdcp level supported: {}',
                  drm_info['hdcp_level_max'])
        LOG.debug('Widevine CryptoSession algorithms: {}',
                  self.crypto_session.GetPropertyString('algorithms'))
def remove_videoid_from_kodi_library(videoid):
    """Remove an item from the Kodi library database (not related files)"""
    try:
        # Get a single file result by searching by videoid
        kodi_library_items = [get_library_item_by_videoid(videoid)]
        LOG.debug(
            'Removing {} ({}) from Kodi library', videoid,
            kodi_library_items[0].get('showtitle',
                                      kodi_library_items[0]['title']))
        media_type = videoid.mediatype
        if videoid.mediatype in [VideoId.SHOW, VideoId.SEASON]:
            # Retrieve the all episodes in the export folder
            tvshow_path = os.path.dirname(kodi_library_items[0]['file'])
            filters = {
                'and': [{
                    'field': 'path',
                    'operator': 'startswith',
                    'value': tvshow_path
                }, {
                    'field': 'filename',
                    'operator': 'endswith',
                    'value': '.strm'
                }]
            }
            if videoid.mediatype == VideoId.SEASON:
                # Use the single file result to figure out what the season is,
                # then add a season filter to get only the episodes of the specified season
                filters['and'].append({
                    'field':
                    'season',
                    'operator':
                    'is',
                    'value':
                    str(kodi_library_items[0]['season'])
                })
            kodi_library_items = get_library_items(VideoId.EPISODE, filters)
            media_type = VideoId.EPISODE
        rpc_params = {
            'movie': ['VideoLibrary.RemoveMovie', 'movieid'],
            # We should never remove an entire show
            # 'show': ['VideoLibrary.RemoveTVShow', 'tvshowid'],
            # Instead we delete all episodes listed in the JSON query above
            'show': ['VideoLibrary.RemoveEpisode', 'episodeid'],
            'season': ['VideoLibrary.RemoveEpisode', 'episodeid'],
            'episode': ['VideoLibrary.RemoveEpisode', 'episodeid']
        }
        list_rpc_params = []
        # Collect multiple json-rpc commands
        for item in kodi_library_items:
            params = rpc_params[media_type]
            list_rpc_params.append({params[1]: item[params[1]]})
        rpc_method = rpc_params[media_type][0]
        # Execute all the json-rpc commands in one call
        json_rpc_multi(rpc_method, list_rpc_params)
    except ItemNotFound:
        LOG.warn('Cannot remove {} from Kodi library, item not present',
                 videoid)
    except KeyError as exc:
        from resources.lib.kodi import ui
        ui.show_notification(get_local_string(30120), time=7500)
        LOG.error(
            'Cannot remove {} from Kodi library, mediatype not supported', exc)
Пример #26
0
 def _request(self, method, endpoint, session_refreshed, **kwargs):
     endpoint_conf = ENDPOINTS[endpoint]
     url = (_api_url(endpoint_conf['address'])
            if endpoint_conf['is_api_call'] else _document_url(
                endpoint_conf['address'], kwargs))
     data, headers, params = self._prepare_request_properties(
         endpoint_conf, kwargs)
     retry = 1
     while True:
         try:
             LOG.debug('Executing {verb} request to {url}',
                       verb=method,
                       url=url)
             start = time.perf_counter()
             if method == 'GET':
                 response = self.session.get(url=url,
                                             headers=headers,
                                             params=params,
                                             timeout=8)
             else:
                 response = self.session.post(url=url,
                                              headers=headers,
                                              params=params,
                                              data=data,
                                              timeout=8)
             LOG.debug('Request took {}s', time.perf_counter() - start)
             LOG.debug('Request returned status code {}',
                       response.status_code)
             break
         except httpx.RemoteProtocolError as exc:
             if 'Server disconnected' in str(exc):
                 # Known reasons:
                 # - The server has revoked cookies validity
                 # - The user has executed "Sign out of all devices" from account settings
                 # Clear the user ID tokens are tied to the credentials
                 self.msl_handler.clear_user_id_tokens()
                 raise NotLoggedInError from exc
             raise
         except httpx.ConnectError as exc:
             LOG.error('HTTP request error: {}', exc)
             if retry == 3:
                 raise
             retry += 1
             LOG.warn('Another attempt will be performed ({})', retry)
     # for redirect in response.history:
     #     LOG.warn('Redirected to: [{}] {}', redirect.status_code, redirect.url)
     if not session_refreshed:
         # We refresh the session when happen:
         # Error 404: It happen when Netflix update the build_identifier version and causes the api address to change
         # Error 401: This is a generic error, can happen when the http request for some reason has failed,
         #   we allow the refresh only for shakti endpoint, sometimes for unknown reasons it is necessary to update
         #   the session for the request to be successful
         if response.status_code == 404 or (response.status_code == 401
                                            and endpoint == 'shakti'):
             LOG.warn('Attempt to refresh the session due to HTTP error {}',
                      response.status_code)
             if self.try_refresh_session_data():
                 return self._request(method, endpoint, True, **kwargs)
     if response.status_code == 401:
         raise HttpError401
     response.raise_for_status()
     return (_raise_api_error(response.json() if response.content else {})
             if endpoint_conf['is_api_call'] else response.content)
 def initialize(self, data):
     if not data['event_data']:
         LOG.warn('AMVideoEvents: disabled due to no event data')
         self.enabled = False
         return
     self.event_data = data['event_data']