def get_url(self, url): """ Perform a GET request to the Premiumize API :param url: URI to perform request again :type url: str :return: JSON response :rtype: dict """ if self.headers["Authorization"] == "Bearer ": g.log("User is not authorised to make PM requests", "warning") return None url = "https://www.premiumize.me/api{}".format(url) req = self.session.get(url, timeout=10, headers=self.headers) req = self._error_handler(req) return req.json()
def get_json(self, url, **params): response = self.get(url, **params) if response is None: return None try: return self._handle_response(params.get("language"), self._flatten(response.json())) except (ValueError, AttributeError): traceback.print_exc() g.log( "Failed to receive JSON from Tvdb response - response: {}". format(response), "error", ) return None
def getPlayingFile(self): """ Fetches the path to the playing file else returns None :return: Path to file :rtype: str/None """ g.log("123") if self.isPlaying(): try: return xbmc.Player().getPlayingFile() except RuntimeError: # seems that we have a racing condition between isPlaying() and getPlayingFile() return None else: return None
def _get_adaptive_sources(self, info, provider): provider_name = provider[1].upper() try: self.sources_information["remainingProviders"].append(provider_name) provider_module = importlib.import_module('{}.{}'.format(provider[0], provider[1])) if not hasattr(provider_module, "sources"): g.log('Invalid provider, Source Class missing') return provider_source = provider_module.sources() if not hasattr(provider_source, self.media_type): g.log('Skipping provider: {} - Does not support {} types'.format(provider_name, self.media_type), 'warning') return self.running_providers.append(provider_source) if self.media_type == 'episode': simple_info = self._build_simple_show_info(info) results = provider_source.episode(simple_info, info) else: try: results = provider_source.movie(info['info']['title'], g.UNICODE(info['info']['year']), info['info'].get('imdb_id')) except TypeError: results = provider_source.movie(info['info']['title'], g.UNICODE(info['info']['year'])) if results is None: self.sources_information["remainingProviders"].remove(provider_name) return if self.canceled: return if len(results) > 0: # Begin filling in optional dictionary returns for result in results: self._process_adaptive_source(result, provider_name, provider) self.sources_information['adaptiveSources'] += results self.running_providers.remove(provider_source) return finally: self.sources_information["remainingProviders"].remove(provider_name)
def get_url(self, url, fail_check=False): original_url = url url = self.base_url + url if not self.token: g.log("No Real Debrid Token Found") return None response = self.session.get(url, timeout=5) if not self._is_response_ok(response) and not fail_check: self.try_refresh_token() response = self.get_url(original_url, fail_check=True) try: return response.json() except (ValueError, AttributeError): return response
def onNotification(self, sender, method, data): if method == "System.OnWake": g.log("System.OnWake notification received" "info") xbmc.executebuiltin( 'RunPlugin("plugin://plugin.video.seren/?action=runMaintenance")' ) xbmc.executebuiltin( 'RunPlugin("plugin://plugin.video.seren/?action=torrentCacheCleanup")' ) if not g.wait_for_abort( 15 ): # Sleep to make sure tokens refreshed during maintenance xbmc.executebuiltin( 'RunPlugin("plugin://plugin.video.seren/?action=syncTraktActivities")' ) return
def rebuild_database(self): g.log("Rebuilding database: {}".format(self._db_file)) with SQLiteConnection(self._db_file) as sqlite: with sqlite.transaction(): database_schema = sqlite._connection.execute( "SELECT m.name from sqlite_master m where type = 'table'" ).fetchall() sqlite._connection.execute("PRAGMA foreign_keys = OFF") for q in ["DROP TABLE IF EXISTS [{}]".format(t["name"]) for t in database_schema]: sqlite._connection.execute(q) sqlite._connection.execute("PRAGMA foreign_keys = ON") sqlite._connection.execute("VACUUM") with sqlite.transaction(): self._create_tables(sqlite._connection)
def _handle_response(self, response, art_type, season=None): try: if response: result = {} result.update( {'art': self._handle_art(response, art_type, season)}) result.update({ 'info': self._normalize_info(self.meta_objects[art_type], response) }) return result except (ValueError, AttributeError): g.log( 'Failed to receive JSON from FanartTv response - response: {}'. format(response), 'error') return None
def mark_show_collected(self, show_id, collected): """ Sets collected status for all items of a given show :param show_id: ID of show to update :type show_id: int :param collected: Status of collection (1=True, 0=False) :type collected: int :return: None :rtype: None """ g.log("Marking show {} as collected in sync database".format(show_id), "debug") self._mill_if_needed([{"trakt_id": show_id}]) self.execute_sql( "UPDATE episodes SET collected=?, collected_at=? WHERE trakt_show_id=?", (collected, self._get_datetime_now(), show_id), )
def _try_detect_type(self, item): item_types = [ ("list", lambda x: "item_count" in x and "sort_by" in x), ("mixedepisode", lambda x: "show" in x and "episode" in x), ("mixedseason", lambda x: "show" in x and "season" in x), ( "movie", lambda x: "title" in x and "year" in x and "network" not in x, ), ("show", lambda x: "title" in x and "year" in x and "network" in x), ( "episode", lambda x: "number" in x and ("season" in x or ("last_watched_at" in x and "plays" in x) or ("collected_at" in x)), ), ("season", lambda x: "number" in x), ("playback", lambda x: "paused_at" in x), ("playbackhistory", lambda x: "action" in x), ("user_rating", lambda x: "rated_at" in x), ("calendar", lambda x: "first_aired" in x), ("cast", lambda x: "cast" in x), ("genre", lambda x: "name" in x and "slug" in x), ("network", lambda x: "name" in x), ("alias", lambda x: "title" in x and "country" in x), ("translation", lambda x: "title" in x and "language" in x), ("people", lambda x: "character" in x and "characters" in x), ("anticipated", lambda x: "list_count" in x), ("box_office", lambda x: "revenue" in x), ("collected", lambda x: "watcher_count" in x and "collected_count" in x and "play_count" in x), ("lists", lambda x: "like_count" in x and "comment_count" in x), ("updated", lambda x: "updated_at" in x and ("movie" in x or "show" in x)), ("trending", lambda x: "watchers" in x), ("sync_activities", lambda x: "all" in x), ("sync_watched", lambda x: "last_watched_at" in x), ("sync_collected", lambda x: "last_collected_at" in x) ] for item_type in item_types: if item_type[1](item): item.update({"type": item_type[0]}) break if "type" not in item: g.log("Error detecting trakt item type for: {}".format(item), "error") return item
def account_premium_status_checks(): """ Updates premium status settings to reflect current state and advises users of expiries if enabled :return: None :rtype: None """ def set_settings_status(debrid_provider, status): """ Ease of use method to set premium status setting :param debrid_provider: setting prefix for debrid provider :type debrid_provider: str :param is_premium: Status of premium status :type is_premium: bool :return: None :rtype: None """ g.set_setting("{}.premiumstatus".format(debrid_provider), status.title()) def display_expiry_notification(display_debrid_name): """ Ease of use method to notify user of expiry of debrid premium status :param display_debrid_name: Debrid providers full display name :type display_debrid_name: str :return: None :rtype: None """ if g.get_bool_setting("general.accountNotifications"): g.notification( "{}".format(g.ADDON_NAME), g.get_language_string(30036).format(display_debrid_name), ) valid_debrid_providers = [ ("Real Debrid", real_debrid.RealDebrid, "rd"), ("Premiumize", premiumize.Premiumize, "premiumize"), ("All Debrid", all_debrid.AllDebrid, "alldebrid"), ] for service in valid_debrid_providers: service_module = service[1]() if service_module.is_service_enabled(): status = service_module.get_account_status() if status == "expired": display_expiry_notification(service[0]) g.log("{}: {}".format(service[0], status)) set_settings_status(service[2], status)
def execute_sql(self, query, data=None): retries = 0 self._register_pickler_adapters() monitor = xbmc.Monitor() with self._get_connection() as connection: while not retries == 50 and not monitor.abortRequested( ) and not self._exit: try: if isinstance(query, list) or isinstance( query, types.GeneratorType): if g.PLATFORM == 'xbox': results = [] for i in query: results.append( self._execute_query( data, connection.cursor(), i)) connection.commit() return results else: return [ self._execute_query(data, connection.cursor(), i) for i in query ] return self._execute_query(data, connection.cursor(), query) except sqlite3.OperationalError as error: if "database is locked" in str(error): g.log( "database is locked waiting: {}".format( self._db_file), "warning", ) monitor.waitForAbort(0.1) else: self._log_error(query, data) raise except (RuntimeError, InterfaceError): if retries >= 2: self._log_error(query, data) raise monitor.waitForAbort(0.1) except: self._log_error(query, data) raise retries += 1 connection.commit() return None
def resolve_single_source(self, source, item_information, pack_select=False, silent=False): """ Resolves source to a streamable object :param source: Item to attempt to resolve :param item_information: Metadata on item intended to be played :param pack_select: Set to True to force manual file selection :return: streamable URL or dictionary of adaptive source information """ stream_link = None try: if source["type"] == "Adaptive": stream_link = source elif source["type"] == "torrent": stream_link = self._resolve_debrid_source( self.resolvers[source["debrid_provider"]], source, item_information, pack_select, ) if not stream_link and self.torrent_resolve_failure_style == 1 and not pack_select and not silent: if xbmcgui.Dialog().yesno(g.ADDON_NAME, g.get_language_string(30519)): stream_link = self._resolve_debrid_source( self.resolvers[source["debrid_provider"]], source, item_information, True, ) elif source["type"] == "hoster" or source["type"] == "cloud": stream_link = self._resolve_hoster_or_cloud( source, item_information) if stream_link: return stream_link else: g.log("Failed to resolve source: {}".format(source), "error") except ResolverFailure as e: g.log('Failed to resolve source: {}'.format(e))
def mark_show_watched(self, show_id, watched): """ Mark watched status for all items of a show :param show_id: Trakt ID of the show to update :type show_id: int :param watched: 1 for watched 0 for unwatched :type watched: int :return: None :rtype: None """ g.log("Marking show {} as watched in sync database".format(show_id), "debug") self._mill_if_needed([{"trakt_id": show_id}]) self.execute_sql( "UPDATE episodes SET watched=?, last_watched_at=? WHERE trakt_show_id=?", (watched, self._get_datetime_now(), show_id), ) self._update_shows_statistics_from_show_id(show_id)
def get_episode_list( self, trakt_show_id, trakt_season_id=None, trakt_id=None, minimum_episode=None, **params ): """ Retrieves a list of episodes or a given season with full meta :param trakt_show_id: Trakt ID of show :type trakt_show_id: int :param trakt_season_id: Trakt ID of season :type trakt_season_id: int :param trakt_id: Optional Trakt ID of single episode to pull :type trakt_id: int :param hide_unaired: Optional hiding of un-aired items :type hide_unaired: bool :param minimum_episode: Optional minimum episode to set as a floor :type minimum_episode: int :return: List of episode objects with full meta :rtype: list """ g.log("Fetching Episode list from sync database", "debug") self._try_update_episodes(trakt_show_id, trakt_season_id, trakt_id) g.log("Updated required episodes", "debug") statement = """SELECT e.trakt_id, e.info, e.cast, e.art, e.args, e.watched as play_count, b.resume_time as resume_time, b.percent_played as percent_played FROM episodes as e LEFT JOIN bookmarks as b on e.trakt_id = b.trakt_id WHERE """ if trakt_season_id is not None: statement += "e.trakt_season_id = {} ".format(trakt_season_id) elif trakt_id is not None: statement += "e.trakt_id = {} ".format(trakt_id) else: statement += "e.trakt_show_id = {} ".format(trakt_show_id) if params.pop("hide_unaired", self.hide_unaired): statement += " AND Datetime(e.air_date) < Datetime('now') " if params.pop("self.hide_specials", self.hide_specials): statement += " AND e.season != 0" if params.pop("hide_watched", self.hide_watched): statement += " AND e.watched = 0" if minimum_episode: statement += " AND e.number >= {}".format(int(minimum_episode)) statement += " order by e.season, e.number " return self.execute_sql(statement).fetchall()
def _update_status(self, chunk_size): """ Updates feedback information :return: """ self._bytes_consumed += chunk_size self.progress = int( (float(self._bytes_consumed) / self.file_size) * 100) self.speed = self._bytes_consumed / (time.time() - self._start_time) self.remaining_seconds = ( float(self.file_size - self._bytes_consumed) / self.speed) g.log("Speed: {} | Remaining Time: {} | Progress: {}".format( self.get_display_speed(), self.get_remainging_time_display(), self.progress, ))
def post_url(self, url, data): """ Perform a POST request to the Premiumize API :param url: URI to perform request again :type url: str :param data: POST data to send with request :type data: dict :return: JSON response :rtype: dict """ if self.headers["Authorization"] == "Bearer ": g.log("User is not authorised to make PM requests", "warning") return None url = "https://www.premiumize.me/api{}".format(url) req = self.session.post(url, headers=self.headers, data=data, timeout=10) req = self._error_handler(req) return req.json()
def _select_files(self): if not self.file_keys: raise GeneralCachingFailure( "Unable to select any relevent files for torrent" ) g.log( "Selecting files: {} - Transfer ID: {}".format( self.file_keys, self.transfer_id ) ) response = self.debrid.torrent_select( self.transfer_id, ",".join(self.file_keys) ) if "error" in response: raise FailureAtRemoteParty( "Unable to select torrent files - {}".format(response) )
def get_json(self, **params): response = self.get(**params) if response is None: return None try: if not response.content: return None return self._handle_response( xml_to_dict.parse(response.text).get("root", {}).get("movie")) except (ValueError, AttributeError): g.log_stacktrace() g.log( "Failed to receive JSON from OMDb response - response: {}". format(response), "error", ) return None
def getControlList(self, control_id): """Get and check the control for the ControlList type. :param control_id: Control id to get nd check for ControlList :type control_id: int :return: The checked control :rtype: xbmcgui.ControlList """ try: control = self.getControl(control_id) except RuntimeError as e: g.log('Control does not exist {}'.format(control_id), 'error') g.log(e) if not isinstance(control, xbmcgui.ControlList): raise AttributeError("Control with Id {} should be of type ControlList".format(control_id)) return control
def _keep_alive(self): for i in range(0, 480): g.log("waiting") self._running_path = self.getPlayingFile() if self._is_file_playing() or self._playback_has_stopped(): break xbmc.sleep(250) self.total_time = self.getTotalTime() if self.offset and not self.resumed: self.seekTime(self.offset) self.resumed = True self._log_debug_information() self._add_subtitle_if_needed() xbmc.sleep(5000) while self._is_file_playing() and not g.abort_requested(): self._update_progress() if not self.scrobble_started: self._trakt_start_watching() time_left = int(self.total_time) - int(self.current_time) if self.min_time_before_scrape > time_left and not self.pre_scrape_initiated: self._handle_pre_scrape() if (self.watched_percentage >= self.playCountMinimumPercent) and not self.scrobbled: self._trakt_stop_watching() self._handle_bookmark() if self.dialogs_enabled and not self.dialogs_triggered: if time_left <= self.playing_next_time: xbmc.executebuiltin( 'RunPlugin("plugin://plugin.video.seren/?action=runPlayerDialogs")' ) self.dialogs_triggered = True xbmc.sleep(100) self._end_playback()
def download(self, request, **extra): """ Downloads requested subtitle :param request: Selected subtitle from search results :type request: dict :param extra: Kwargs, set settings to settings to request to use :type extra: dict :return: Path to subtitle :rtype: str """ try: settings = extra.pop("settings", None) return self.service.download(request, settings) except (OSError, IOError): g.log("Unable to download subtitle, file already exists", "error") except Exception as e: g.log("Unknown error acquiring subtitle: {}".format(e), "error") g.log_stacktrace()
def token_request(self): if not self.client_secret: return url = self.oauth_url + self.token_url response = self.session.post( url, data={ "client_id": self.client_id, "client_secret": self.client_secret, "code": self.device_code, "grant_type": "http://oauth.net/grant_type/device/1.0", }, ).json() self._save_settings(response) self._save_user_status() xbmcgui.Dialog().ok(g.ADDON_NAME, "Real Debrid " + g.get_language_string(30020)) g.log("Authorised Real Debrid successfully", "info")
def mark_episode_unwatched(self, show_id, season, number): """ Mark an individual episode item as unwatched :param show_id: ID of show to update :type show_id: int :param season: Season number of episode :type season: int :param number: Episode number to update :type number: int :return: None :rtype: None """ g.log("Marking episode {} S{}E{} as unwatched in sync database".format(show_id, season, number), "debug") self.execute_sql( "UPDATE episodes SET watched=0 WHERE trakt_show_id=? and season=? and number=?", (show_id, season, number), ) self._update_shows_statistics_from_show_id(show_id)
def wrapper(*args, **kwarg): try: response = func(*args, **kwarg) if response.status_code in [200, 201]: return response g.log('FanartTv returned a {} ({}): while requesting {}'.format(response.status_code, FanartTv.http_codes[response.status_code], response.url), 'error') return None except requests.exceptions.ConnectionError: return None except: xbmcgui.Dialog().notification(g.ADDON_NAME, g.get_language_string(30025).format('Fanart')) if g.get_global_setting("run.mode") == "test": raise else: g.log_stacktrace() return None
def mark_season_watched(self, show_id, season, watched): """ Mark watched status for all items of a season :param show_id: Trakt ID of the show to update :type show_id: int :param season: Season number to mark :type season: int :param watched: 1 for watched 0 for unwatched :type watched: int :return: None :rtype: None """ g.log("Marking season {} as watched in sync database".format(season), "debug") self.execute_sql( "UPDATE episodes SET watched=?, last_watched_at=?" " WHERE trakt_show_id=? AND season=?", (watched, self._get_datetime_now(), show_id, season), ) self._update_shows_statistics_from_show_id(show_id)
def do_version_change(): if g.get_setting("seren.version") == g.CLEAN_VERSION: return g.log("Clearing cache on Seren version change", "info") g.clear_cache(silent=True) g.set_setting("seren.version", g.CLEAN_VERSION) # Reuselanguageinvoker update. This should be last to execute as it can do a profile reload. # Disable the restoration of reuselanguageinvoker addon.xml based on settings value on upgrade. # It can still be toggled in settings, although initially it will be the release default value. # This is due to the fact that we still don't recommend having this enabled due to Kodi hard crashes. # maintenance.toggle_reuselanguageinvoker( # True if g.get_setting("reuselanguageinvoker") == "Enabled" else False) g.set_setting( "reuselanguageinvoker.status", "Disabled" ) # This ensures setting is reflected as disabled on version change
def pre_scrape(): """ Checks whether a item exists in the current playlist after current item and then pre-fetches results :return: :rtype: """ next_position = g.PLAYLIST.getposition() + 1 if next_position >= g.PLAYLIST.size(): return url = g.PLAYLIST[ # pylint: disable=unsubscriptable-object next_position].getPath() if not url: return url = url.replace("getSources", "preScrape") g.set_runtime_setting("tempSilent", True) g.log("Running Pre-Scrape: {}".format(url)) xbmc.executebuiltin('RunPlugin("{}")'.format(url))
def _failure_cleanup(meta_location, package_name, folders): # In the event of a failure to install package this function will revert changes made g.log("Reverting changes") try: if xbmcvfs.exists("{}.temp".format(meta_location)): os.remove(meta_location) os.rename("{}.temp".format(meta_location), meta_location) except Exception: pass for folder in folders: folder_path = os.path.join(g.ADDON_USERDATA_PATH, folder.strip("/"), package_name) if xbmcvfs.exists("{}.temp".format(folder_path)): try: shutil.rmtree(folder_path) except Exception: pass os.rename("{}.temp".format(folder_path), folder_path)
def _extract_zip(self, skin_meta): try: file_path = [i for i in self._file_list if i.endswith("resources/skins/")][ 0 ] file_path = file_path.split("resources/")[0] except IndexError: file_path = "" if "{}resources/".format(file_path) not in self._file_list: g.log('Theme Folder Structure Invalid: Missing folder "Resources"') xbmcgui.Dialog().ok(g.ADDON_NAME, g.get_language_string(30231)) raise Exception skin_path = os.path.join(g.SKINS_PATH, skin_meta["skin_name"]) self._extract_zip_members( [i for i in self._file_list if i.startswith(file_path) and i != file_path], skin_path, ) self._destroy_created_temp_items()