def show_root_menu(): """ Show the plugin root menu """ li_style = xbmcgui.ListItem('[B]' + G.LANGUAGE(32001) + '[/B]', offscreen=True) li_style.setArt({ 'thumb': os.path.join(G.THUMB_PATH, 'camera.jpg'), 'fanart': G.FANART_PATH }) add_directory_item({"mode": "camera"}, li_style) li_style = xbmcgui.ListItem('[B]' + G.LANGUAGE(32002) + '[/B]', offscreen=True) li_style.setArt({ 'thumb': os.path.join(G.THUMB_PATH, 'senato.png'), 'fanart': G.FANART_PATH }) add_directory_item({"mode": "senato"}, li_style) li_style = xbmcgui.ListItem('[B]' + G.LANGUAGE(32003) + '[/B]', offscreen=True) li_style.setArt({ 'thumb': os.path.join(G.THUMB_PATH, 'tv.png'), 'fanart': G.FANART_PATH }) add_directory_item({"mode": "tv"}, li_style) li_style = xbmcgui.ListItem('[B]' + G.LANGUAGE(32004) + '[/B]', offscreen=True) li_style.setArt({ 'thumb': os.path.join(G.THUMB_PATH, 'radio.png'), 'fanart': G.FANART_PATH }) add_directory_item({"mode": "radio"}, li_style) xbmcplugin.endOfDirectory(handle=G.PLUGIN_HANDLE, succeeded=True)
def migrate_library(): # Migrate the Kodi library to the new format of STRM path # - Old STRM: '/play/show/xxxxxxxx/season/xxxxxxxx/episode/xxxxxxxx/' (used before ver 1.7.0) # - New STRM: '/play_strm/show/xxxxxxxx/season/xxxxxxxx/episode/xxxxxxxx/' (used from ver 1.7.0) folders = get_library_subfolders( FOLDER_NAME_MOVIES) + get_library_subfolders(FOLDER_NAME_SHOWS) if not folders: return LOG.debug('Start migrating STRM files') try: with ui.ProgressDialog(True, title='Migrating library to new format', max_value=len(folders)) as progress_bar: for folder_path in folders: folder_name = os.path.basename( G.py2_decode(xbmc.translatePath(folder_path))) progress_bar.set_message('PLEASE WAIT - Migrating: ' + folder_name) _migrate_strm_files(folder_path) except Exception as exc: # pylint: disable=broad-except LOG.error('Migrating failed: {}', exc) import traceback LOG.error(G.py2_decode(traceback.format_exc(), 'latin-1')) ui.show_ok_dialog('Migrating library to new format', ( 'Library migration has failed.[CR]' 'Before try play a Netflix video from library, you must run manually the library migration, ' 'otherwise you will have add-on malfunctions.[CR][CR]' 'Open add-on settings on "Library" section, and select "Import existing library".' ))
def load(account_hash): """Load cookies for a given account and check them for validity""" filename = cookie_filename(account_hash) if not xbmcvfs.exists(filename): common.debug('Cookies file does not exist') raise MissingCookiesError() common.debug('Loading cookies from {}', G.py2_decode(filename)) cookie_file = xbmcvfs.File(filename, 'rb') try: if G.PY_IS_VER2: # pickle.loads on py2 wants string cookie_jar = pickle.loads(cookie_file.read()) else: cookie_jar = pickle.loads(cookie_file.readBytes()) except Exception as exc: # pylint: disable=broad-except import traceback common.error('Failed to load cookies from file: {exc}', exc=exc) common.error(G.py2_decode(traceback.format_exc(), 'latin-1')) raise MissingCookiesError() finally: cookie_file.close() # Clear flwssn cookie if present, as it is trouble with early expiration if 'flwssn' in cookie_jar: cookie_jar.clear(domain='.netflix.com', path='/', name='flwssn') log_cookie(cookie_jar) return cookie_jar
def parse_profiles(data): """Parse profile information from Netflix response""" profiles_list = jgraph_get_list('profilesList', data) try: if not profiles_list: raise InvalidProfilesError('It has not been possible to obtain the list of profiles.') sort_order = 0 current_guids = [] for index, profile_data in iteritems(profiles_list): # pylint: disable=unused-variable summary = jgraph_get('summary', profile_data) guid = summary['guid'] current_guids.append(guid) LOG.debug('Parsing profile {}', summary['guid']) avatar_url = _get_avatar(profile_data, data, guid) is_active = summary.pop('isActive') G.LOCAL_DB.set_profile(guid, is_active, sort_order) G.SHARED_DB.set_profile(guid, sort_order) # Add profile language description translated from locale summary['language_desc'] = G.py2_decode(xbmc.convertLanguage(summary['language'][:2], xbmc.ENGLISH_NAME)) if LOG.level == LOG.LEVEL_VERBOSE: for key, value in iteritems(summary): if key in PROFILE_DEBUG_INFO: LOG.debug('Profile info {}', {key: value}) # Translate the profile name, is coded as HTML summary['profileName'] = parse_html(summary['profileName']) summary['avatar'] = avatar_url G.LOCAL_DB.insert_profile_configs(summary, guid) sort_order += 1 _delete_non_existing_profiles(current_guids) except Exception as exc: # pylint: disable=broad-except import traceback LOG.error(G.py2_decode(traceback.format_exc(), 'latin-1')) LOG.error('Profile list data: {}', profiles_list) raise_from(InvalidProfilesError, exc)
def make_http_call_cache(callname, params, data): """Make an IPC call via HTTP and wait for it to return. The contents of data will be expanded to kwargs and passed into the target function.""" try: # Python 3 from urllib.request import build_opener, install_opener, ProxyHandler, HTTPError, URLError, Request, urlopen except ImportError: # Python 2 from urllib2 import build_opener, install_opener, ProxyHandler, HTTPError, URLError, Request, urlopen import json # debug('Handling HTTP IPC call to {}'.format(callname)) # Note: On python 3, using 'localhost' slowdown the call (Windows OS is affected) not sure if it is an urllib issue url = 'http://127.0.0.1:{}/{}'.format(G.LOCAL_DB.get_value('cache_service_port', 8002), callname) install_opener(build_opener(ProxyHandler({}))) # don't use proxy for localhost r = Request(url=url, data=data, headers={'Params': json.dumps(params)}) try: result = urlopen(r, timeout=IPC_TIMEOUT_SECS).read() except HTTPError as exc: if exc.reason in exceptions.__dict__: raise_from(exceptions.__dict__[exc.reason], exc) raise_from(Exception('The service has returned: {}'.format(exc.reason)), exc) except URLError as exc: # On PY2 the exception message have to be decoded with latin-1 for system with symbolic characters err_msg = G.py2_decode(str(exc), 'latin-1') if '10049' in err_msg: err_msg += '\r\nPossible cause is wrong localhost settings in your operative system.' LOG.error(err_msg) raise_from(exceptions.BackendNotReady(G.py2_encode(err_msg, encoding='latin-1')), exc) return result
def make_http_call(callname, data): """Make an IPC call via HTTP and wait for it to return. The contents of data will be expanded to kwargs and passed into the target function.""" from collections import OrderedDict try: # Python 3 from urllib.request import build_opener, install_opener, ProxyHandler, HTTPError, URLError, urlopen except ImportError: # Python 2 from urllib2 import build_opener, install_opener, ProxyHandler, HTTPError, URLError, urlopen import json LOG.debug('Handling HTTP IPC call to {}'.format(callname)) # Note: On python 3, using 'localhost' slowdown the call (Windows OS is affected) not sure if it is an urllib issue url = 'http://127.0.0.1:{}/{}'.format(G.LOCAL_DB.get_value('ns_service_port', 8001), callname) install_opener(build_opener(ProxyHandler({}))) # don't use proxy for localhost try: result = json.loads( urlopen(url=url, data=json.dumps(data).encode('utf-8'), timeout=IPC_TIMEOUT_SECS).read(), object_pairs_hook=OrderedDict) except HTTPError as exc: result = json.loads(exc.reason) except URLError as exc: # On PY2 the exception message have to be decoded with latin-1 for system with symbolic characters err_msg = G.py2_decode(str(exc), 'latin-1') if '10049' in err_msg: err_msg += '\r\nPossible cause is wrong localhost settings in your operative system.' LOG.error(err_msg) raise_from(exceptions.BackendNotReady(G.py2_encode(err_msg, encoding='latin-1')), exc) _raise_for_error(result) return result
def autoselect_profile_remove(self, pathitems): # pylint: disable=unused-argument """Remove the auto-selection set""" G.LOCAL_DB.set_value('autoselect_profile_guid', '') G.settings_monitor_suspend(True) G.ADDON.setSetting('autoselect_profile_name', '') G.ADDON.setSettingBool('autoselect_profile_enabled', False) G.settings_monitor_suspend(False)
def _delete_non_existing_profiles(current_guids): list_guid = G.LOCAL_DB.get_guid_profiles() for guid in list_guid: if guid not in current_guids: LOG.debug('Deleting non-existing profile {}', guid) G.LOCAL_DB.delete_profile(guid) G.SHARED_DB.delete_profile(guid) # Ensures at least one active profile try: G.LOCAL_DB.get_active_profile_guid() except DBProfilesMissing: G.LOCAL_DB.switch_active_profile(G.LOCAL_DB.get_guid_owner_profile()) G.settings_monitor_suspend(True) # Verify if auto select profile exists autoselect_profile_guid = G.LOCAL_DB.get_value('autoselect_profile_guid', '') if autoselect_profile_guid and autoselect_profile_guid not in current_guids: LOG.warn('Auto-selection disabled, the GUID {} not more exists', autoselect_profile_guid) G.LOCAL_DB.set_value('autoselect_profile_guid', '') # Verify if profile for library auto-sync exists sync_mylist_profile_guid = G.SHARED_DB.get_value('sync_mylist_profile_guid') if sync_mylist_profile_guid and sync_mylist_profile_guid not in current_guids: LOG.warn('Library auto-sync disabled, the GUID {} not more exists', sync_mylist_profile_guid) G.ADDON.setSettingBool('lib_sync_mylist', False) G.SHARED_DB.delete_key('sync_mylist_profile_guid') # Verify if profile for library playback exists library_playback_profile_guid = G.LOCAL_DB.get_value('library_playback_profile_guid') if library_playback_profile_guid and library_playback_profile_guid not in current_guids: LOG.warn('Profile set for playback from library cleared, the GUID {} not more exists', library_playback_profile_guid) G.LOCAL_DB.set_value('library_playback_profile_guid', '') G.settings_monitor_suspend(False)
def run_addon_configuration(show_end_msg=False): """ Add-on configuration wizard, automatically configures profiles and add-ons dependencies, based on user-supplied data and device characteristics """ system = get_system_platform() debug('Running add-on configuration wizard ({})', system) G.settings_monitor_suspend(True, False) is_4k_capable = is_device_4k_capable() _set_profiles(system, is_4k_capable) _set_kodi_settings(system) _set_isa_addon_settings(is_4k_capable, system == 'android') # This settings for now used only with android devices and it should remain disabled (keep it for test), # in the future it may be useful for other platforms or it may be removed G.ADDON.setSettingBool('enable_force_hdcp', False) # Enable UpNext if it is installed and enabled G.ADDON.setSettingBool( 'UpNextNotifier_enabled', getCondVisibility('System.AddonIsEnabled(service.upnext)')) G.settings_monitor_suspend(False) if show_end_msg: show_ok_dialog(get_local_string(30154), get_local_string(30157))
def autoselect_profile_set(self, pathitems): # pylint: disable=unused-argument """Save the GUID for profile auto-selection""" G.LOCAL_DB.set_value('autoselect_profile_guid', self.params['profile_guid']) G.settings_monitor_suspend(True) G.ADDON.setSetting('autoselect_profile_name', self.params['profile_name']) G.ADDON.setSettingBool('autoselect_profile_enabled', True) G.settings_monitor_suspend(False) ui.show_notification(common.get_local_string(30058).format(G.py2_decode(self.params['profile_name'])))
def _subtitle_profiles(): from xbmcaddon import Addon isa_version = G.remove_ver_suffix( G.py2_decode(Addon('inputstream.adaptive').getAddonInfo('version'))) subtitle_profile = ['webvtt-lssdh-ios8'] if G.ADDON.getSettingBool('disable_webvtt_subtitle') \ or not common.is_minimum_version(isa_version, '2.3.8'): subtitle_profile = ['simplesdh'] return subtitle_profile
def rename_cookie_file(): # The file "COOKIE_xxxxxx..." will be renamed to "COOKIES" list_files = list_dir(G.DATA_PATH)[1] for filename in list_files: if 'COOKIE_' in G.py2_decode(filename): copy_file(join_folders_paths(G.DATA_PATH, G.py2_decode(filename)), join_folders_paths(G.DATA_PATH, 'COOKIES')) xbmc.sleep(80) delete_file(G.py2_decode(filename))
def run(argv): # Initialize globals right away to avoid stale values from the last addon invocation. # Otherwise Kodi's reuseLanguageInvoker will cause some really quirky behavior! # PR: https://github.com/xbmc/xbmc/pull/13814 G.init_globals(argv) check_service_upgrade() netflix_service = NetflixService() if netflix_service.init_servers(): netflix_service.run()
def onSettingsChanged(self): status = G.settings_monitor_suspend_status() if status == 'First': LOG.warn('SettingsMonitor: triggered but in suspend status (at first change)') G.settings_monitor_suspend(False) return if status == 'True': LOG.warn('SettingsMonitor: triggered but in suspend status (permanent)') return self._on_change()
def run(argv): # Initialize globals right away to avoid stale values from the last addon invocation. # Otherwise Kodi's reuseLanguageInvoker will cause some really quirky behavior! # PR: https://github.com/xbmc/xbmc/pull/13814 G.init_globals(argv) LOG.info('Started (Version {})'.format(G.VERSION_RAW)) LOG.info('URL is {}'.format(G.URL)) success = True window_cls = Window(10000) # Kodi home window # If you use multiple Kodi profiles you need to distinguish the property of current profile prop_nf_service_status = G.py2_encode('nf_service_status_' + get_current_kodi_profile_name()) is_external_call = _check_addon_external_call(window_cls, prop_nf_service_status) service_status = _get_service_status(window_cls, prop_nf_service_status) if service_status.get('status') != 'running': if not is_external_call: if service_status.get('status') == 'error': # The services are not started due to an error exception from resources.lib.kodi.ui import show_error_info show_error_info( get_local_string(30105), get_local_string(30240).format( service_status.get('message')), False, False) else: # The services are not started yet from resources.lib.kodi.ui import show_backend_not_ready show_backend_not_ready() success = False if success: cancel_playback = False pathitems = [part for part in G.REQUEST_PATH.split('/') if part] if G.IS_ADDON_FIRSTRUN: is_first_run_install, cancel_playback = check_addon_upgrade() if is_first_run_install: from resources.lib.config_wizard import run_addon_configuration run_addon_configuration() if cancel_playback and G.MODE_PLAY in pathitems[:1]: # Temporary for migration library STRM to new format. todo: to be removed in future releases # When a user do the add-on upgrade, the first time that the add-on will be opened will be executed # the library migration. But if a user instead to open the add-on, try to play a video from Kodi # library, Kodi will open the old STRM file because the migration is executed after. success = False else: success = route(pathitems) if not success: from xbmcplugin import endOfDirectory endOfDirectory(handle=G.PLUGIN_HANDLE, succeeded=False) LOG.log_time_trace()
def convert_to_dash(manifest): """Convert a Netflix style manifest to MPEG-DASH manifest""" from xbmcaddon import Addon isa_version = G.remove_ver_suffix( G.py2_decode(Addon('inputstream.adaptive').getAddonInfo('version'))) # If a CDN server has stability problems it may cause errors with streaming, # we allow users to select a different CDN server # (should be managed by ISA but is currently is not implemented) cdn_index = int(G.ADDON.getSettingString('cdn_server')[-1]) - 1 seconds = manifest['duration'] / 1000 init_length = int(seconds / 2 * 12 + 20 * 1000) duration = "PT" + str(int(seconds)) + ".00S" root = _mpd_manifest_root(duration) period = ET.SubElement(root, 'Period', start='PT0S', duration=duration) has_video_drm_streams = manifest['video_tracks'][0].get( 'hasDrmStreams', False) video_protection_info = _get_protection_info( manifest['video_tracks'][0]) if has_video_drm_streams else None for video_track in manifest['video_tracks']: _convert_video_track(video_track, period, init_length, video_protection_info, has_video_drm_streams, cdn_index) common.fix_locale_languages(manifest['audio_tracks']) common.fix_locale_languages(manifest['timedtexttracks']) has_audio_drm_streams = manifest['audio_tracks'][0].get( 'hasDrmStreams', False) default_audio_language_index = _get_default_audio_language(manifest) for index, audio_track in enumerate(manifest['audio_tracks']): _convert_audio_track(audio_track, period, init_length, (index == default_audio_language_index), has_audio_drm_streams, cdn_index) default_subtitle_language_index = _get_default_subtitle_language(manifest) for index, text_track in enumerate(manifest['timedtexttracks']): if text_track['isNoneTrack']: continue _convert_text_track(text_track, period, (index == default_subtitle_language_index), cdn_index, isa_version) xml = ET.tostring(root, encoding='utf-8', method='xml') if LOG.level == LOG.LEVEL_VERBOSE: common.save_file_def('manifest.mpd', xml) return xml.decode('utf-8').replace('\n', '').replace('\r', '').encode('utf-8')
def delete_cache_folder(): # Delete cache folder in the add-on userdata (no more needed with the new cache management) cache_path = os.path.join(G.DATA_PATH, 'cache') if not os.path.exists(G.py2_decode(xbmc.translatePath(cache_path))): return LOG.debug('Deleting the cache folder from add-on userdata folder') try: delete_folder_contents(cache_path, True) xbmc.sleep(80) xbmcvfs.rmdir(cache_path) except Exception: # pylint: disable=broad-except import traceback LOG.error(G.py2_decode(traceback.format_exc(), 'latin-1'))
def log_time_trace(): """Write the time tracing info to the debug log""" if not is_debug_verbose() and not G.TIME_TRACE_ENABLED: return time_trace = ['Execution time info for this run:\n'] G.TIME_TRACE.reverse() for trace in G.TIME_TRACE: time_trace.append(' ' * trace[2]) time_trace.append(format(trace[0], '<30')) time_trace.append('{:>5} ms\n'.format(trace[1])) debug(''.join(time_trace)) G.reset_time_trace()
def show_root_menu(): ''' Show the plugin root menu ''' liStyle = xbmcgui.ListItem('[B]' + G.LANGUAGE(32002) + '[/B]', offscreen=True) liStyle.setArt({ 'thumb': os.path.join(G.THUMB_PATH, 'direttalivela7.jpg'), 'fanart': G.FANART_PATH }) addDirectoryItem_nodup({"mode": "diretta_la7"}, liStyle, folder=False) liStyle = xbmcgui.ListItem('[B]' + G.LANGUAGE(32009) + '[/B]', offscreen=True) liStyle.setArt({ 'thumb': os.path.join(G.THUMB_PATH, 'direttalivela7d.jpg'), 'fanart': G.FANART_PATH }) addDirectoryItem_nodup({"mode": "diretta_la7d"}, liStyle, folder=False) liStyle = xbmcgui.ListItem('[B]' + G.LANGUAGE(32001) + '[/B]', offscreen=True) liStyle.setArt({ 'thumb': os.path.join(G.THUMB_PATH, 'rivedila7.jpg'), 'fanart': G.FANART_PATH }) addDirectoryItem_nodup({"mode": "rivedi_la7"}, liStyle) liStyle = xbmcgui.ListItem('[B]' + G.LANGUAGE(32004) + '[/B]', offscreen=True) liStyle.setArt({ 'thumb': os.path.join(G.THUMB_PATH, 'rivedila7d.jpg'), 'fanart': G.FANART_PATH }) addDirectoryItem_nodup({"mode": "rivedi_la7d"}, liStyle) liStyle = xbmcgui.ListItem('[B]' + G.LANGUAGE(32010) + '[/B]', offscreen=True) liStyle.setArt({ 'thumb': os.path.join(G.THUMB_PATH, 'la7prime.jpg'), 'fanart': G.FANART_PATH }) addDirectoryItem_nodup({"mode": "la7_prime"}, liStyle) liStyle = xbmcgui.ListItem('[B]' + G.LANGUAGE(32006) + '[/B]', offscreen=True) liStyle.setArt({ 'thumb': os.path.join(G.THUMB_PATH, 'programmila7la7d.jpg'), 'fanart': G.FANART_PATH }) addDirectoryItem_nodup({"mode": "tutti_programmi"}, liStyle) liStyle = xbmcgui.ListItem('[B]' + G.LANGUAGE(32007) + '[/B]', offscreen=True) liStyle.setArt({ 'thumb': os.path.join(G.THUMB_PATH, 'tgmeteo.jpg'), 'fanart': G.FANART_PATH }) addDirectoryItem_nodup({"mode": "tg_meteo"}, liStyle) liStyle = xbmcgui.ListItem('[B]' + G.LANGUAGE(32008) + '[/B]', offscreen=True) liStyle.setArt({ 'thumb': os.path.join(G.THUMB_PATH, 'techela7.jpg'), 'fanart': G.FANART_PATH }) addDirectoryItem_nodup({"mode": "teche_la7"}, liStyle) xbmcplugin.endOfDirectory(handle=G.PLUGIN_HANDLE, succeeded=True)
def delete_folder_contents(path, delete_subfolders=False): """ Delete all files in a folder :param path: Path to perform delete contents :param delete_subfolders: If True delete also all subfolders """ directories, files = list_dir(xbmc.translatePath(path)) for filename in files: xbmcvfs.delete(os.path.join(path, G.py2_decode(filename))) if not delete_subfolders: return for directory in directories: delete_folder_contents(os.path.join(path, G.py2_decode(directory)), True) # Give time because the system performs previous op. otherwise it can't delete the folder xbmc.sleep(80) xbmcvfs.rmdir(os.path.join(path, G.py2_decode(directory)))
def _load_msl_data(self, msl_data): try: self.crypto.load_msl_data(msl_data) self.crypto.load_crypto_session(msl_data) except Exception: # pylint: disable=broad-except import traceback LOG.error(G.py2_decode(traceback.format_exc(), 'latin-1'))
def _create_videolist_item(list_id, video_list, menu_data, common_data, static_lists=False): if static_lists and G.is_known_menu_context(video_list['context']): pathitems = list(menu_data['path']) # Make a copy pathitems.append(video_list['context']) else: # It is a dynamic video list / menu context if menu_data.get('force_use_videolist_id', False): path = 'video_list' else: path = 'video_list_sorted' pathitems = [path, menu_data['path'][1], list_id] list_item = ListItemW(label=video_list['displayName']) add_info_list_item(list_item, video_list.videoid, video_list, video_list.data, False, common_data, art_item=video_list.artitem) # Add possibility to browse the sub-genres (see build_video_listing) sub_genre_id = video_list.get('genreId') params = {'sub_genre_id': str(sub_genre_id)} if sub_genre_id else None return common.build_url(pathitems, params=params, mode=G.MODE_DIRECTORY), list_item, True
def get_library_subfolders(folder_name): """Returns all the subfolders contained in a folder of library path""" section_path = common.join_folders_paths(get_library_path(), folder_name) return [ common.join_folders_paths(section_path, G.py2_decode(folder)) for folder in common.list_dir(section_path)[0] ]
def show_browse_dialog(title, browse_type=0, default_path=None, multi_selection=False, extensions=None): """ Show a browse dialog to select files or folders :param title: The window title :param browse_type: Type of dialog as int value (0 = ShowAndGetDirectory, 1 = ShowAndGetFile, ..see doc) :param default_path: The initial path :param multi_selection: Allow multi selection :param extensions: extensions allowed e.g. '.jpg|.png' :return: The selected path as string (or tuple of selected items) if user pressed 'Ok', else None """ ret = G.py2_decode(xbmcgui.Dialog().browse(browse_type, title, shares='local', useThumbs=False, treatAsFolder=False, defaultt=default_path, enableMultiple=multi_selection, mask=extensions)) # Note: when defaultt is set and the user cancel the action (when enableMultiple is False), # will be returned the defaultt value again, so we avoid this strange behavior... return None if not ret or ret == default_path else ret
def login(self, credentials=None): """Perform account login""" try: # First we get the authentication url without logging in, required for login API call react_context = website.extract_json(self.get('login'), 'reactContext') auth_url = website.extract_api_data(react_context)['auth_url'] LOG.debug('Logging in...') 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.account_hash, self.session.cookies) return True except LoginValidateError as exc: self.session.cookies.clear() common.purge_credentials() raise_from(LoginError(unicode(exc)), exc) except (MbrStatusNeverMemberError, MbrStatusFormerMemberError) as exc: self.session.cookies.clear() LOG.warn('Membership status {} not valid for login', exc) raise_from(LoginError(common.get_local_string(30180)), exc) except Exception: # pylint: disable=broad-except self.session.cookies.clear() import traceback LOG.error(G.py2_decode(traceback.format_exc(), 'latin-1')) raise
def video_list_sorted(self, pathitems): """Show a video list sorted of a 'context' name""" menu_data = G.MAIN_MENU_ITEMS.get(pathitems[1]) if not menu_data: # Dynamic menus menu_data = G.LOCAL_DB.get_value(pathitems[1], table=TABLE_MENU_DATA, data_type=dict) call_args = { 'pathitems': pathitems, 'menu_data': menu_data, 'sub_genre_id': self.params.get( 'sub_genre_id' ), # Used to show the sub-genre folder when sub-genres exists 'perpetual_range_start': self.perpetual_range_start, 'is_dynamic_id': not G.is_known_menu_context(pathitems[2]) } list_data, extra_data = common.make_call('get_video_list_sorted', call_args) sort_type = 'sort_nothing' if menu_data['path'][1] == 'myList' and int( G.ADDON.getSettingInt('menu_sortorder_mylist')) == 0: # At the moment it is not possible to make a query with results sorted for the 'mylist', # so we adding the sort order of kodi sort_type = 'sort_label_ignore_folders' finalize_directory(convert_list_to_dir_items(list_data), menu_data.get('content_type', G.CONTENT_SHOW), title=get_title(menu_data, extra_data), sort_type=sort_type) end_of_directory(self.dir_update_listing) return menu_data.get('view')
def _log(msg, level, *args, **kwargs): """Log a message to the Kodi logfile.""" if args or kwargs: msg = msg.format(*args, **kwargs) xbmc.log( G.py2_encode('[{identifier} ({handle})] {msg}'.format( identifier=G.ADDON_ID, handle=G.PLUGIN_HANDLE, msg=msg)), level)
def validate_login(react_context): path_code_list = PAGE_ITEM_ERROR_CODE_LIST.split('\\') path_error_code = PAGE_ITEM_ERROR_CODE.split('/') if common.check_path_exists(path_error_code, react_context): # If the path exists, a login error occurs try: error_code_list = common.get_path(path_code_list, react_context) error_code = common.get_path(path_error_code, react_context) LOG.error('Login not valid, error code {}', error_code) error_description = common.get_local_string(30102) + error_code if error_code in error_code_list: error_description = error_code_list[error_code] if 'email_' + error_code in error_code_list: error_description = error_code_list['email_' + error_code] if 'login_' + error_code in error_code_list: error_description = error_code_list['login_' + error_code] raise LoginValidateError(common.remove_html_tags(error_description)) except (AttributeError, KeyError) as exc: import traceback LOG.error(G.py2_decode(traceback.format_exc(), 'latin-1')) error_msg = ( 'Something is wrong in PAGE_ITEM_ERROR_CODE or PAGE_ITEM_ERROR_CODE_LIST paths.' 'react_context data may have changed.') LOG.error(error_msg) raise_from(WebsiteParsingError(error_msg), exc)
def release_license(self, data=None): # pylint: disable=unused-argument """Release the server license""" try: # When you try to play a video while another one is currently in playing, # a new license to be released will be queued, so the oldest license must be released url = self.licenses_release_url.pop() sid = self.licenses_session_id.pop() xid = self.licenses_xid.pop() common.debug('Requesting releasing license') params = [{ 'url': url, 'params': { 'drmSessionId': sid, 'xid': xid }, 'echo': 'drmSessionId' }] response = self.msl_requests.chunked_request( ENDPOINTS['license'], self.msl_requests.build_request_data('/bundle', params), G.get_esn()) common.debug('License release response: {}', response) except IndexError: # Example the supplemental media type have no license common.debug('No license to release')
def extract_json(content, name): """Extract json from netflix content page""" LOG.debug('Extracting {} JSON', name) json_str = None try: json_array = recompile(JSON_REGEX.format(name), DOTALL).findall(content.decode('utf-8')) json_str = json_array[0] json_str_replace = json_str.replace('\\"', '\\\\"') # Escape double-quotes json_str_replace = json_str_replace.replace('\\s', '\\\\s') # Escape \s json_str_replace = json_str_replace.replace( '\\n', '\\\\n') # Escape line feed json_str_replace = json_str_replace.replace('\\t', '\\\\t') # Escape tab json_str_replace = json_str_replace.encode().decode( 'unicode_escape') # Decode the string as unicode json_str_replace = sub( r'\\(?!["])', r'\\\\', json_str_replace ) # Escape backslash (only when is not followed by double quotation marks \") return json.loads(json_str_replace) except Exception as exc: # pylint: disable=broad-except if json_str: LOG.error('JSON string trying to load: {}', json_str) import traceback LOG.error(G.py2_decode(traceback.format_exc(), 'latin-1')) raise_from(WebsiteParsingError('Unable to extract {}'.format(name)), exc)