def __init__(self, ): super(TraktSyncDatabase, self).__init__(g.TRAKT_SYNC_DB_PATH, schema, migrate_db_lock) self.metadataHandler = MetadataHandler() self.trakt_api = TraktAPI() self.activities = {} self.item_list = [] self.base_date = "1970-01-01T00:00:00" self.task_queue = ThreadPool() self.mill_task_queue = ThreadPool() self.parent_task_queue = ThreadPool() self.refresh_activities() # If you make changes to the required meta in any indexer that is cached in this database # You will need to update the below version number to match the new addon version # This will ensure that the metadata required for operations is available self.last_meta_update = "2.0.0" if self.activities is None: self.clear_all_meta(False) self.set_base_activities() if self.activities is not None: self._check_database_version() self.notification_prefix = "{}: Trakt".format(g.ADDON_NAME) self.hide_unaired = g.get_bool_setting("general.hideUnAired") self.hide_specials = g.get_bool_setting("general.hideSpecials") self.hide_watched = g.get_bool_setting("general.hideWatched") self.date_delay = g.get_bool_setting("general.datedelay") self.page_limit = g.get_int_setting("item.limit")
def shufflePlay(self): import random self.window = windows.smart_play_background() self.window.setBackground(self.poster) self.window.setProgress(0) self.window.show() self.window.setText(tools.lang(32096).encode('utf-8')) tools.playList.clear() showInfo = {} showInfo['showInfo'] = self.info_dictionary season_list = TraktAPI().json_response( 'shows/%s/seasons?extended=episodes' % self.show_trakt_id) if season_list[0]['number'] == 0: season_list.pop(0) self.window.setProgress(50) self.window.setText(tools.lang(32097).encode('utf-8')) episode_list = [ episode for season in season_list for episode in season['episodes'] ] random.shuffle(episode_list) episode_list = episode_list[:40] # mill the episodes playlist = tvshowMenus.Menus().episodeListBuilder(episode_list, showInfo, smartPlay=True) self.window.setProgress(100) self.window.setText(tools.lang(32098).encode('utf-8')) for episode in playlist: if episode is not None: tools.playList.add(url=episode[0], listitem=episode[1]) self.window.setProgress(100) self.window.close() tools.playList.shuffle() tools.player().play(tools.playList)
def __init__(self): self.trakt = TraktAPI() self.movies_database = movies.TraktSyncDatabase() self.list_builder = ListBuilder() self.page_limit = g.get_int_setting("item.limit") self.page_start = (g.PAGE-1)*self.page_limit self.page_end = g.PAGE*self.page_limit
def __init__(self): self.trakt = TraktAPI() self.language_code = g.get_language_code() self.trakt_database = TraktSyncDatabase() self.hidden_database = hidden.TraktSyncDatabase() self.bookmark_database = bookmark.TraktSyncDatabase() self.shows_database = shows.TraktSyncDatabase() self.list_builder = ListBuilder() self.page_limit = g.get_int_setting("item.limit")
def shufflePlay(self): import random if self.display_style == '0': self.window = PersistentBackground( *SkinManager().confirm_skin_path('persistent_background.xml'), actionArgs=self.actionArgs) self.window.show() self.window.setText(tools.lang(32096)) tools.playList.clear() season_list = TraktAPI().json_response( 'shows/%s/seasons?extended=episodes' % self.show_trakt_id) if season_list[0]['number'] == 0: season_list.pop(0) self.window.setText(tools.lang(32097)) episode_list = [ episode for season in season_list for episode in season['episodes'] ] random.shuffle(episode_list) episode_list = episode_list[:40] shuffle_list = [] for episode in episode_list: shuffle_list.append({ 'episode': episode, 'show': { 'ids': { 'trakt': self.show_trakt_id } } }) # mill the episodes playlist = tvshowMenus.Menus().mixedEpisodeBuilder(shuffle_list, sort=False, smartPlay=True) self.window.setText(tools.lang(32098)) for episode in playlist: if episode is not None: tools.playList.add(url=episode[0], listitem=episode[1]) self.window.close() tools.playList.shuffle() tools.player().play(tools.playList)
def resume_playback(self): tools.playList.clear() if self.display_style == '0': self.window = PersistentBackground( *SkinManager().confirm_skin_path('persistent_background.xml'), actionArgs=self.actionArgs) self.window.show() self.window.setText(tools.lang(32095).encode('utf-8')) if tools.getSetting('trakt.auth') == '': tools.showDialog.ok(tools.addonName, tools.lang(32093).encode('utf-8')) self.window.close() return playback_history = TraktAPI().json_response('sync/history/shows/%s' % self.show_trakt_id) self.window.setText(tools.lang(32096).encode('utf-8')) season, episode = self.get_resume_episode(playback_history) self.build_playlist(season, episode) self.window.close() tools.player().play(tools.playList)
def traktProgressWorker(self, trakt_object): progress = database.get( TraktAPI().json_response, .5, 'shows/%s/progress/watched?extended=full' % trakt_object['show']['ids']['trakt']) trakt_object['progress'] = progress self.itemList.append(trakt_object)
def __init__(self, item_information): self.list_builder = ListBuilder() if "info" not in item_information: item_information = tools.get_item_information(item_information) self.item_information = item_information if not isinstance(self.item_information, dict): raise TypeError("Item Information is not a dictionary") self.show_trakt_id = self.item_information.get("trakt_show_id") if not self.show_trakt_id and "action_args" in self.item_information: self.show_trakt_id = self._extract_show_id_from_args( self.item_information["action_args"]) self.display_style = g.get_int_setting("smartplay.displaystyle") self.trakt_api = TraktAPI()
def final_episode_check(self, season, episode): last_aired = TraktAPI().json_response( 'shows/%s/last_episode?extended=full' % self.show_trakt_id) if str(season) == str(last_aired['season']): if str(episode) == str(last_aired['number']): return True return False
def refresh_apis(): """ Refresh common API tokens :return: None :rtype: None """ TraktAPI().try_refresh_token() real_debrid.RealDebrid().try_refresh_token() TVDBAPI().try_refresh_token()
def shufflePlay(self): import random self.window = windows.smart_play_background() self.window.setBackground(self.poster) self.window.setProgress(0) self.window.show() self.window.setText(tools.lang(32096)) tools.playList.clear() season_list = TraktAPI().json_response( 'shows/%s/seasons?extended=episodes' % self.show_trakt_id) if season_list[0]['number'] == 0: season_list.pop(0) self.window.setProgress(50) self.window.setText(tools.lang(32097)) episode_list = [ episode for season in season_list for episode in season['episodes'] ] random.shuffle(episode_list) episode_list = episode_list[:40] shuffle_list = [] for episode in episode_list: shuffle_list.append({ 'episode': episode, 'show': { 'ids': { 'trakt': self.show_trakt_id } } }) # mill the episodes playlist = tvshowMenus.Menus().mixedEpisodeBuilder(shuffle_list, sort=False, smartPlay=True) self.window.setProgress(100) self.window.setText(tools.lang(32098)) for episode in playlist: if episode is not None: tools.playList.add(url=episode[0], listitem=episode[1]) self.window.setProgress(100) self.window.close() tools.playList.shuffle() tools.player().play(tools.playList)
def __init__(self, info_dictionary): try: self.info_dictionary = json.loads(tools.unquote(info_dictionary)) except: self.info_dictionary = info_dictionary if 'episodeInfo' not in info_dictionary: self.show_trakt_id = self.info_dictionary['ids']['trakt'] if tools.getSetting('trakt.auth') != '': self.user_history = TraktAPI().json_response( 'sync/history/shows/%s' % self.show_trakt_id) self.poster = self.info_dictionary['art']['fanart'] else: self.poster = self.info_dictionary['showInfo']['art']['fanart'] self.show_trakt_id = self.info_dictionary['showInfo']['ids'][ 'trakt'] self.show_season_info = database.get( TraktAPI().json_response, 12, 'shows/%s/seasons?extended=full' % self.show_trakt_id) self.window = None
def torrent_file_picker(self): tools.playList.clear() info = self.info_dictionary episode = info['episodeInfo']['info']['episode'] season = info['episodeInfo']['info']['season'] show_id = info['showInfo']['ids']['trakt'] trakt_object = TraktAPI().json_response( 'shows/%s/seasons/%s/episodes/%s?extended=full' % (show_id, season, episode)) list_item = tvshowMenus.Menus().episodeListBuilder([trakt_object], info, smartPlay=True)[0] url = list_item[0] + "&packSelect=true" tools.playList.add(url=url, listitem=list_item[1]) tools.player().play(tools.playList)
def final_episode_check(self, season, episode): season = int(season) episode = int(episode) last_aired = TraktAPI().json_response('shows/%s/last_episode' % self.show_trakt_id) if season > last_aired['season']: return True if season == last_aired['season']: if episode == last_aired['number']: return True return False
def get_season_info(self): return database.get(TraktAPI().json_response, 12, 'shows/%s/seasons' % self.show_trakt_id)
def smart_play_show(self, append_playlist=False): self.window = windows.smart_play_background() self.window.setBackground(self.poster) self.window.setText(tools.lang(32094).encode('utf-8')) if not append_playlist: self.window.show() self.window.setProgress(0) self.window.setProgress(40) self.window.setText(tools.lang(32095).encode('utf-8')) if not append_playlist: tools.playList.clear() if 'episodeInfo' not in self.info_dictionary: if tools.getSetting('trakt.auth') == '': tools.showDialog.ok(tools.addonName, tools.lang(32093).encode('utf-8')) return season, episode = self.get_resume_episode() if self.final_episode_check(season, episode) is True: season = 1 episode = 1 season_object = TraktAPI().json_response( 'shows/%s/seasons?extended=full' % self.info_dictionary['ids']['trakt']) season_object = [x for x in season_object if x['number'] == season] self.info_dictionary = tvshowMenus.Menus().seasonListBuilder( season_object, self.info_dictionary, smartPlay=True) self.info_dictionary = json.loads( tools.unquote(self.info_dictionary)) else: season = self.info_dictionary['episodeInfo']['info']['season'] episode = self.info_dictionary['episodeInfo']['info']['episode'] self.window.setText(tools.lang(32096).encode('utf-8')) self.window.setProgress(60) episode_list = database.get( TraktAPI().json_response, 12, 'shows/%s/seasons/%s?extended=full' % (self.show_trakt_id, str(season))) playlist = [] for i in episode_list: if i['number'] < episode: continue playlist.append(i) if append_playlist: tools.log("STARTING PLAYLIST GENERATION") playlist = tvshowMenus.Menus().episodeListBuilder( playlist, self.info_dictionary, smartPlay=True) for i in playlist: # Confirm that the episode meta we have received from TVDB are for the correct episodes # If trakt provides the incorrect TVDB ID it's possible to begin play from the incorrect episode params = dict(tools.parse_qsl(i[0].replace('?', ''))) actionArgs = json.loads(params.get('actionArgs')) if actionArgs['episodeInfo']['info']['episode'] < episode: continue # If the episode is confirmed ok, add it to our playlist. tools.log("ADDING ITEM TO PLAYLIST") tools.playList.add(url=i[0], listitem=i[1]) return self.window.setText(tools.lang(32097).encode('utf-8')) self.window.setProgress(80) actionArgs = {} actionArgs['playlist'] = playlist actionArgs['info_dictionary'] = self.info_dictionary actionArgs = tools.quote(json.dumps(actionArgs)) # Begin nasty Kodi 18 Skin workaround tools.execute( 'RunPlugin(plugin://plugin.video.%s?action=buildPlaylist&actionArgs=%s)' % (tools.addonName.lower(), actionArgs)) self.window.close()
def __init__( self, tmdb_api=None, tvdb_api=None, fanarttv_api=None, trakt_api=None, omdb_api=None, ): self.tmdb_api = tmdb_api if tmdb_api else TMDBAPI() self.tvdb_api = tvdb_api if tvdb_api else TVDBAPI() self.fanarttv_api = fanarttv_api if fanarttv_api else FanartTv() self.trakt_api = trakt_api if trakt_api else TraktAPI() self.omdb_api = omdb_api if omdb_api else OmdbApi() self.lang_code = g.get_language_code() self.lang_full_code = g.get_language_code(True) self.lang_region_code = self.lang_full_code.split("-")[:1] self.allowed_artwork_languages = {None, "en", self.lang_code} self.movies_poster_limit = g.get_int_setting("movies.poster_limit", 1) self.movies_fanart_limit = g.get_int_setting("movies.fanart_limit", 1) self.movies_keyart_limit = g.get_int_setting("movies.keyart_limit", 1) self.movies_characterart_limit = g.get_int_setting( "movies.characterart_limit", 1) self.movies_banner = g.get_bool_setting("movies.banner", "true") self.movies_clearlogo = g.get_bool_setting("movies.clearlogo", "true") self.movies_landscape = g.get_bool_setting("movies.landscape", "true") self.movies_clearart = g.get_bool_setting("movies.clearart", "true") self.movies_discart = g.get_bool_setting("movies.discart", "true") self.tvshows_poster_limit = g.get_int_setting("tvshows.poster_limit", 1) self.tvshows_fanart_limit = g.get_int_setting("tvshows.fanart_limit", 1) self.tvshows_keyart_limit = g.get_int_setting("tvshows.keyart_limit", 1) self.tvshows_characterart_limit = g.get_int_setting( "tvshows.characterart_limit", 1) self.tvshows_banner = g.get_bool_setting("tvshows.banner", "true") self.tvshows_clearlogo = g.get_bool_setting("tvshows.clearlogo", "true") self.tvshows_landscape = g.get_bool_setting("tvshows.landscape", "true") self.tvshows_clearart = g.get_bool_setting("tvshows.clearart", "true") self.season_poster = g.get_bool_setting("season.poster", "true") self.season_banner = g.get_bool_setting("season.banner", "true") self.season_landscape = g.get_bool_setting("season.landscape", "true") self.season_fanart = g.get_bool_setting("season.fanart", "true") self.episode_fanart = g.get_bool_setting("episode.fanart", "true") self.tvshows_preferred_art_source = g.get_int_setting( "tvshows.preferedsource", 1) self.movies_preferred_art_source = g.get_int_setting( "movies.preferedsource", 1) self.metadata_location = g.get_int_setting("general.metalocation", 1) self.preferred_artwork_size = g.get_int_setting( "artwork.preferredsize", 1) self.show_original_title = g.get_bool_setting( "general.meta.showoriginaltitle", False) self.genres = { "action": g.get_language_string(30534), "adventure": g.get_language_string(30535), "animation": g.get_language_string(30536), "anime": g.get_language_string(30537), "biography": g.get_language_string(30538), "children": g.get_language_string(30539), "comedy": g.get_language_string(30540), "crime": g.get_language_string(30541), "documentary": g.get_language_string(30542), "drama": g.get_language_string(30543), "family": g.get_language_string(30544), "fantasy": g.get_language_string(30545), "game-show": g.get_language_string(30546), "history": g.get_language_string(30547), "holiday": g.get_language_string(30548), "home-and-garden": g.get_language_string(30549), "horror": g.get_language_string(30550), "mini-series": g.get_language_string(30551), "music": g.get_language_string(30552), "musical": g.get_language_string(30553), "mystery": g.get_language_string(30554), "news": g.get_language_string(30555), "none": g.get_language_string(30556), "reality": g.get_language_string(30557), "romance": g.get_language_string(30558), "science-fiction": g.get_language_string(30559), "sci-fi": g.get_language_string(30559), "short": g.get_language_string(30560), "soap": g.get_language_string(30561), "special-interest": g.get_language_string(30562), "sporting-event": g.get_language_string(30563), "superhero": g.get_language_string(30564), "suspense": g.get_language_string(30565), "talk-show": g.get_language_string(30566), "talkshow": g.get_language_string(30566), "thriller": g.get_language_string(30567), "tv-movie": g.get_language_string(30568), "war": g.get_language_string(30569), "western": g.get_language_string(30570), } self.meta_hash = tools.md5_hash([ self.lang_code, self.movies_poster_limit, self.movies_fanart_limit, self.movies_keyart_limit, self.movies_characterart_limit, self.movies_banner, self.movies_clearlogo, self.movies_landscape, self.movies_clearart, self.movies_discart, self.tvshows_poster_limit, self.tvshows_fanart_limit, self.tvshows_keyart_limit, self.tvshows_characterart_limit, self.tvshows_banner, self.tvshows_clearlogo, self.tvshows_landscape, self.tvshows_clearart, self.season_poster, self.season_banner, self.season_landscape, self.season_fanart, self.episode_fanart, self.tvshows_preferred_art_source, self.tvshows_preferred_art_source, self.metadata_location, self.fanarttv_api.fanart_support, self.preferred_artwork_size, self.show_original_title, ])
class Menus: def __init__(self): self.trakt = TraktAPI() self.movies_database = movies.TraktSyncDatabase() self.list_builder = ListBuilder() self.page_limit = g.get_int_setting("item.limit") self.page_start = (g.PAGE - 1) * self.page_limit self.page_end = g.PAGE * self.page_limit ###################################################### # MENUS ###################################################### @trakt_auth_guard def on_deck_movies(self): hidden_movies = HiddenDatabase().get_hidden_items( "progress_watched", "movies") bookmark_sync = BookmarkDatabase() bookmarked_items = [ i for i in bookmark_sync.get_all_bookmark_items("movie") if i["trakt_id"] not in hidden_movies ][self.page_start:self.page_end] self.list_builder.movie_menu_builder(bookmarked_items) @staticmethod def discover_movies(): g.add_directory_item( g.get_language_string(30004), action="genericEndpoint", mediatype="movies", endpoint="popular", description=g.get_language_string(30417), ) g.add_directory_item( g.get_language_string(30369), action="moviePopularRecent", description=g.get_language_string(30418), ) if g.get_setting("trakt.auth"): g.add_directory_item( g.get_language_string(30005), action="moviesRecommended", description=g.get_language_string(30419), ) g.add_directory_item( g.get_language_string(30006), action="genericEndpoint", mediatype="movies", endpoint="trending", description=g.get_language_string(30420), ) g.add_directory_item( g.get_language_string(30370), action="movieTrendingRecent", description=g.get_language_string(30421), ) g.add_directory_item( g.get_language_string(30007), action="genericEndpoint", mediatype="movies", endpoint="played", description=g.get_language_string(30422), ) g.add_directory_item( g.get_language_string(30008), action="genericEndpoint", mediatype="movies", endpoint="watched", description=g.get_language_string(30423), ) g.add_directory_item( g.get_language_string(30009), action="genericEndpoint", mediatype="movies", endpoint="collected", description=g.get_language_string(30424), ) g.add_directory_item( g.get_language_string(30375), action="TrendingLists", mediatype="movies", description=g.get_language_string(30425), ) g.add_directory_item( g.get_language_string(30377), action="PopularLists", mediatype="movies", description=g.get_language_string(30426), ) if not g.get_bool_setting("general.hideUnAired"): g.add_directory_item( g.get_language_string(30010), action="genericEndpoint", mediatype="movies", endpoint="anticipated", description=g.get_language_string(30427), ) g.add_directory_item( g.get_language_string(30012), action="genericEndpoint", mediatype="movies", endpoint="boxoffice", description=g.get_language_string(30428), ) g.add_directory_item( g.get_language_string(30011), action="moviesUpdated", description=g.get_language_string(30429), ) g.add_directory_item( g.get_language_string(30042), action="movieGenres", description=g.get_language_string(30430), ) g.add_directory_item( g.get_language_string(30184), action="movieYears", description=g.get_language_string(30431), ) g.add_directory_item( g.get_language_string(30203), action="movieByActor", description=g.get_language_string(30397), ) if not g.get_bool_setting("searchHistory"): g.add_directory_item( g.get_language_string(30013), action="moviesSearch", description=g.get_language_string(30393), ) else: g.add_directory_item( g.get_language_string(30013), action="moviesSearchHistory", description=g.get_language_string(30395), ) g.close_directory(g.CONTENT_MENU) @staticmethod @trakt_auth_guard def my_movies(): g.add_directory_item( g.get_language_string(30043), action="onDeckMovies", description=g.get_language_string(30432), ) g.add_directory_item( g.get_language_string(30014), action="moviesMyCollection", description=g.get_language_string(30433), ) g.add_directory_item( g.get_language_string(30015), action="moviesMyWatchlist", description=g.get_language_string(30434), ) g.add_directory_item( g.get_language_string(30044), action="myTraktLists", mediatype="movies", description=g.get_language_string(30435), ) g.add_directory_item( g.get_language_string(30373), action="myLikedLists", mediatype="movies", description=g.get_language_string(30436), ) g.add_directory_item( g.get_language_string(30347), action="myWatchedMovies", description=g.get_language_string(30437), ) g.close_directory(g.CONTENT_MENU) def generic_endpoint(self, endpoint): trakt_list = self.movies_database.extract_trakt_page( "movies/{}".format(endpoint), extended="full", page=g.PAGE) self.list_builder.movie_menu_builder(trakt_list) def movie_popular_recent(self): year_range = "{}-{}".format(datetime.datetime.now().year - 1, datetime.datetime.now().year) trakt_list = self.movies_database.extract_trakt_page("movies/popular", years=year_range, page=g.PAGE, extended="full") self.list_builder.movie_menu_builder(trakt_list) def movie_trending_recent(self): year_range = "{}-{}".format(datetime.datetime.now().year - 1, datetime.datetime.now().year) trakt_list = self.movies_database.extract_trakt_page("movies/trending", years=year_range, page=g.PAGE, extended="full") self.list_builder.movie_menu_builder(trakt_list) @trakt_auth_guard def my_movie_collection(self): paginate = not g.get_bool_setting("general.paginatecollection") sort = "title" if paginate else False self.list_builder.movie_menu_builder( movies.TraktSyncDatabase().get_collected_movies(g.PAGE), no_paging=paginate, sort=sort, ) @trakt_auth_guard def my_movie_watchlist(self): paginate = not g.get_bool_setting("general.paginatetraktlists") trakt_list = self.movies_database.extract_trakt_page( "users/me/watchlist/movies", extended="full", page=g.PAGE, ignore_cache=True, no_paging=paginate, pull_all=True, ) self.list_builder.movie_menu_builder(trakt_list, no_paging=paginate) @trakt_auth_guard def movies_recommended(self): trakt_list = self.movies_database.extract_trakt_page( "recommendations/movies", ignore_collected=True, extended="full", page=g.PAGE, ) self.list_builder.movie_menu_builder(trakt_list) def movies_updated(self): import datetime date = datetime.date.today() - datetime.timedelta(days=29) date = date.strftime(g.DATE_FORMAT) trakt_list = self.movies_database.extract_trakt_page( "movies/updates/{}".format(date), page=g.PAGE, extended="full") self.list_builder.movie_menu_builder(trakt_list) @staticmethod def movies_search_history(): history = SearchHistory().get_search_history("movie") g.add_directory_item( g.get_language_string(30194), action="moviesSearch", description=g.get_language_string(30393), ) g.add_directory_item( g.get_language_string(30193), action="clearSearchHistory", mediatype="movie", is_folder=False, description=g.get_language_string(30403), ) for i in history: g.add_directory_item(i, action="moviesSearchResults", action_args=i) g.close_directory(g.CONTENT_MENU) def movies_search(self, query=None): if query is None: query = g.get_keyboard_input(heading=g.get_language_string(30013)) if not query: g.cancel_directory() return if g.get_bool_setting("searchHistory"): SearchHistory().add_search_history("movie", query) self.movies_search_results(query) def movies_search_results(self, query): trakt_list = self.movies_database.extract_trakt_page( "search/movie", query=query, extended="full", page=g.PAGE, hide_watched=False, hide_unaired=False, ) if not trakt_list: g.cancel_directory() return self.list_builder.movie_menu_builder( [ movie for movie in trakt_list if float(movie["trakt_object"]["info"]["score"]) > 0 ], hide_watched=False, hide_unaired=False, ) def movies_related(self, args): trakt_list = self.movies_database.extract_trakt_page( "movies/{}/related".format(args), page=g.PAGE, extended="full") self.list_builder.movie_menu_builder(trakt_list) @staticmethod def movies_years(): from datetime import datetime year = int(datetime.today().year) years = [] for i in range(year - 100, year + 1): years.append(i) years = sorted(years, reverse=True) [ g.add_directory_item(str(i), action="movieYearsMovies", action_args=i) for i in years ] g.close_directory(g.CONTENT_MENU) def movie_years_results(self, year): trakt_list = self.movies_database.extract_trakt_page("movies/popular", years=year, page=g.PAGE, extended="full") self.list_builder.movie_menu_builder(trakt_list) def movies_by_actor(self, query): if query is None: query = g.get_keyboard_input(g.get_language_string(30013)) if not query: g.cancel_directory() return if g.get_bool_setting("searchHistory"): SearchHistory().add_search_history("movieActor", query) query = g.transliterate_string(query) # Try to deal with transliterated chinese actor names as some character -> word transliterations can be joined # I have no idea of the rules and it could well be arbitrary # This approach will only work if only one pair of adjoining transliterated chars are joined name_parts = query.split() for i in range(len(name_parts), 0, -1): query = "-".join(name_parts[:i]) + "-".join(name_parts[i:i + 1]) query = tools.quote_plus(query) trakt_list = self.movies_database.extract_trakt_page( "people/{}/movies".format(query), extended="full", page=g.PAGE, hide_watched=False, hide_unaired=False) if not trakt_list: continue else: break try: if not trakt_list or 'trakt_id' not in trakt_list[0]: raise KeyError except KeyError: g.cancel_directory() return self.list_builder.movie_menu_builder(trakt_list, hide_watched=False, hide_unaired=False) def movies_genres(self): g.add_directory_item( g.get_language_string(30045), action="movieGenresGet", menu_item={ "art": dict.fromkeys(['icon', 'poster', 'thumb', 'fanart'], g.GENRES_PATH + "list.png") }) genres = self.trakt.get_json_cached("genres/movies") if genres is None: g.cancel_directory() return for i in genres: g.add_directory_item( i["name"], action="movieGenresGet", action_args=i["slug"], menu_item={ "art": dict.fromkeys(['icon', 'poster', 'thumb', 'fanart'], "{}{}.png".format(g.GENRES_PATH, i["slug"])) }) g.close_directory(g.CONTENT_GENRES) def movies_genre_list(self, args): trakt_endpoint = ("trending" if g.get_int_setting("general.genres.endpoint") == 0 else "popular") if args is None: genre_display_list = [] genres = self.trakt.get_json_cached("genres/movies") for genre in genres: gi = xbmcgui.ListItem(genre["name"]) gi.setArt( {"thumb": "{}{}.png".format(g.GENRES_PATH, genre["slug"])}) genre_display_list.append(gi) genre_multiselect = xbmcgui.Dialog().multiselect( "{}: {}".format(g.ADDON_NAME, g.get_language_string(30320)), genre_display_list, useDetails=True) if genre_multiselect is None: return genre_string = ",".join( [genres[i]["slug"] for i in genre_multiselect]) else: genre_string = tools.unquote(args) trakt_list = self.movies_database.extract_trakt_page( "movies/{}".format(trakt_endpoint), genres=genre_string, page=g.PAGE, extended="full") if trakt_list is None: g.cancel_directory() return self.list_builder.movie_menu_builder(trakt_list, next_args=genre_string) @trakt_auth_guard def my_watched_movies(self): watched_movies = movies.TraktSyncDatabase().get_watched_movies(g.PAGE) self.list_builder.movie_menu_builder(watched_movies)
class Menus: def __init__(self): self.trakt = TraktAPI() self.movies_database = movies.TraktSyncDatabase() self.list_builder = ListBuilder() self.page_limit = g.get_int_setting("item.limit") self.page_start = (g.PAGE-1)*self.page_limit self.page_end = g.PAGE*self.page_limit ###################################################### # MENUS ###################################################### @trakt_auth_guard def on_deck_movies(self): hidden_movies = HiddenDatabase().get_hidden_items("progress_watched", "movies") bookmark_sync = BookmarkDatabase() bookmarked_items = [ i for i in bookmark_sync.get_all_bookmark_items("movie") if i["trakt_id"] not in hidden_movies ][self.page_start:self.page_end] self.list_builder.movie_menu_builder(bookmarked_items) @staticmethod def discover_movies(): g.add_directory_item( g.get_language_string(30004), action="genericEndpoint", mediatype="movies", endpoint="popular", description=g.get_language_string(30429), ) g.add_directory_item( g.get_language_string(30380), action="moviePopularRecent", description=g.get_language_string(30430), ) if g.get_setting("trakt.auth"): g.add_directory_item( g.get_language_string(30005), action="moviesRecommended", description=g.get_language_string(30431), ) g.add_directory_item( g.get_language_string(30006), action="genericEndpoint", mediatype="movies", endpoint="trending", description=g.get_language_string(30432), ) g.add_directory_item( g.get_language_string(30381), action="movieTrendingRecent", description=g.get_language_string(30433), ) g.add_directory_item( g.get_language_string(30007), action="genericEndpoint", mediatype="movies", endpoint="played", description=g.get_language_string(30434), ) g.add_directory_item( g.get_language_string(30008), action="genericEndpoint", mediatype="movies", endpoint="watched", description=g.get_language_string(30435), ) g.add_directory_item( g.get_language_string(30009), action="genericEndpoint", mediatype="movies", endpoint="collected", description=g.get_language_string(30436), ) g.add_directory_item( g.get_language_string(30386), action="TrendingLists", mediatype="movies", description=g.get_language_string(30437), ) g.add_directory_item( g.get_language_string(30388), action="PopularLists", mediatype="movies", description=g.get_language_string(30438), ) if not g.get_bool_setting("general.hideUnAired"): g.add_directory_item( g.get_language_string(30010), action="genericEndpoint", mediatype="movies", endpoint="anticipated", description=g.get_language_string(30439), ) g.add_directory_item( g.get_language_string(30012), action="genericEndpoint", mediatype="movies", endpoint="boxoffice", description=g.get_language_string(30440), ) g.add_directory_item( g.get_language_string(30011), action="moviesUpdated", description=g.get_language_string(30441), ) g.add_directory_item( g.get_language_string(30043), action="movieGenres", description=g.get_language_string(30442), ) g.add_directory_item( g.get_language_string(30188), action="movieYears", description=g.get_language_string(30443), ) g.add_directory_item( g.get_language_string(30212), action="movieByActor", description=g.get_language_string(30408), ) if not g.get_bool_setting("searchHistory"): g.add_directory_item( g.get_language_string(30013), action="moviesSearch", description=g.get_language_string(30404), ) else: g.add_directory_item( g.get_language_string(30013), action="moviesSearchHistory", description=g.get_language_string(30406), ) g.close_directory(g.CONTENT_FOLDER) @staticmethod @trakt_auth_guard def my_movies(): g.add_directory_item( g.get_language_string(30044), action="onDeckMovies", description=g.get_language_string(30444), ) g.add_directory_item( g.get_language_string(30014), action="moviesMyCollection", description=g.get_language_string(30445), ) g.add_directory_item( g.get_language_string(30015), action="moviesMyWatchlist", description=g.get_language_string(30446), ) g.add_directory_item( g.get_language_string(30045), action="myTraktLists", mediatype="movies", description=g.get_language_string(30447), ) g.add_directory_item( g.get_language_string(30384), action="myLikedLists", mediatype="movies", description=g.get_language_string(30448), ) g.add_directory_item( g.get_language_string(30357), action="myWatchedMovies", description=g.get_language_string(30449), ) g.close_directory(g.CONTENT_FOLDER) def generic_endpoint(self, endpoint): trakt_list = self.movies_database.extract_trakt_page( "movies/{}".format(endpoint), extended="full", page=g.PAGE ) self.list_builder.movie_menu_builder(trakt_list) def movie_popular_recent(self): year_range = "{}-{}".format( datetime.datetime.now().year - 1, datetime.datetime.now().year ) trakt_list = self.movies_database.extract_trakt_page( "movies/popular", years=year_range, page=g.PAGE, extended="full" ) self.list_builder.movie_menu_builder(trakt_list) def movie_trending_recent(self): year_range = "{}-{}".format( datetime.datetime.now().year - 1, datetime.datetime.now().year ) trakt_list = self.movies_database.extract_trakt_page( "movies/trending", years=year_range, page=g.PAGE, extended="full" ) self.list_builder.movie_menu_builder(trakt_list) @trakt_auth_guard def my_movie_collection(self): paginate = not g.get_bool_setting("general.paginatecollection") sort = "title" if paginate else False self.list_builder.movie_menu_builder( movies.TraktSyncDatabase().get_collected_movies(g.PAGE), no_paging=paginate, sort=sort, ) @trakt_auth_guard def my_movie_watchlist(self): paginate = not g.get_bool_setting("general.paginatetraktlists") trakt_list = self.movies_database.extract_trakt_page( "users/me/watchlist/movies", extended="full", page=g.PAGE, ignore_cache=True, no_paging=paginate, pull_all=True, ) self.list_builder.movie_menu_builder(trakt_list, no_paging=paginate) @trakt_auth_guard def movies_recommended(self): trakt_list = self.movies_database.extract_trakt_page( "recommendations/movies", ignore_collected=True, extended="full", page=g.PAGE, ) self.list_builder.movie_menu_builder(trakt_list) def movies_updated(self): import datetime date = datetime.date.today() - datetime.timedelta(days=31) date = date.strftime(g.DATE_FORMAT) trakt_list = self.movies_database.extract_trakt_page( "movies/updates/{}".format(date), page=g.PAGE, extended="full" ) self.list_builder.movie_menu_builder(trakt_list) @staticmethod def movies_search_history(): history = SearchHistory().get_search_history("movie") g.add_directory_item( g.get_language_string(30203), action="moviesSearch", description=g.get_language_string(30404), ) g.add_directory_item( g.get_language_string(30202), action="clearSearchHistory", mediatype="movie", is_folder=False, description=g.get_language_string(30414), ) for i in history: g.add_directory_item(i, action="moviesSearchResults", action_args=i) g.close_directory(g.CONTENT_FOLDER) def movies_search(self, query=None): if query is None: k = xbmc.Keyboard("", g.get_language_string(30013)) k.doModal() query = k.getText() if k.isConfirmed() else None del k if not query: g.cancel_directory() return query = g.decode_py2(query) if g.get_bool_setting("searchHistory"): SearchHistory().add_search_history("movie", query) query = g.deaccent_string(g.display_string(query)) query = tools.quote(query) self.movies_search_results(query) def movies_search_results(self, query): trakt_list = self.trakt.get_json_paged( "search/movie", query=tools.unquote(query), extended="full", page=g.PAGE ) if not trakt_list: g.cancel_directory() return self.list_builder.movie_menu_builder( [ movie for movie in trakt_list if float(movie["trakt_object"]["info"]["score"]) > 0 ], hide_watched=False, hide_unaired=False, ) def movies_related(self, args): trakt_list = self.movies_database.extract_trakt_page( "movies/{}/related".format(args), page=g.PAGE, extended="full" ) self.list_builder.movie_menu_builder(trakt_list) @staticmethod def movies_years(): from datetime import datetime year = int(datetime.today().year) years = [] for i in range(year - 100, year + 1): years.append(i) years = sorted(years, reverse=True) [ g.add_directory_item(str(i), action="movieYearsMovies", action_args=i) for i in years ] g.close_directory(g.CONTENT_FOLDER) def movie_years_results(self, year): trakt_list = self.movies_database.extract_trakt_page( "movies/popular", years=year, page=g.PAGE, extended="full" ) self.list_builder.movie_menu_builder(trakt_list) def movies_by_actor(self, actor): if actor is None: k = xbmc.Keyboard("", g.get_language_string(30013)) k.doModal() query = k.getText() if k.isConfirmed() else None if not query: g.cancel_directory() return else: query = tools.unquote(actor) if g.get_bool_setting("searchHistory"): SearchHistory().add_search_history("movieActor", query) query = g.deaccent_string(query) query = query.replace(" ", "-") query = tools.quote_plus(query) self.list_builder.movie_menu_builder( self.trakt.get_json_paged( "people/{}/movies".format(query), extended="full", page=g.PAGE ), hide_watched=False, hide_unaired=False, ) def movies_genres(self): g.add_directory_item(g.get_language_string(30046), action="movieGenresGet") genres = self.trakt.get_json("genres/movies") if genres is None: g.cancel_directory() return for i in genres: g.add_directory_item( i["name"], action="movieGenresGet", action_args=i["slug"] ) g.close_directory(g.CONTENT_GENRES) def movies_genre_list(self, args): trakt_endpoint = ( "trending" if g.get_int_setting("general.genres.endpoint") == 0 else "popular" ) if args is None: genre_display_list = [] genres = self.trakt.get_json("genres/movies") for genre in genres: genre_display_list.append(genre["name"]) genre_multiselect = xbmcgui.Dialog().multiselect( "{}: {}".format(g.ADDON_NAME, g.get_language_string(30330)), genre_display_list, ) if genre_multiselect is None: return genre_string = ",".join([genres[i]["slug"] for i in genre_multiselect]) else: genre_string = tools.unquote(args) trakt_list = self.trakt.get_json_cached( "movies/{}".format(trakt_endpoint), page=g.PAGE, extended="full" ) if trakt_list is None: g.cancel_directory() return self.list_builder.movie_menu_builder(trakt_list, next_args=genre_string) @trakt_auth_guard def my_watched_movies(self): watched_movies = movies.TraktSyncDatabase().get_watched_movies(g.PAGE) self.list_builder.movie_menu_builder(watched_movies)
class Menus: def __init__(self): self.trakt = TraktAPI() self.language_code = g.get_language_code() self.trakt_database = TraktSyncDatabase() self.hidden_database = hidden.TraktSyncDatabase() self.bookmark_database = bookmark.TraktSyncDatabase() self.shows_database = shows.TraktSyncDatabase() self.list_builder = ListBuilder() self.page_limit = g.get_int_setting("item.limit") self.page_start = (g.PAGE - 1) * self.page_limit self.page_end = g.PAGE * self.page_limit ###################################################### # MENUS ###################################################### @trakt_auth_guard def on_deck_shows(self): hidden_shows = self.hidden_database.get_hidden_items( "progress_watched", "tvshow") bookmarked_items = [ i for i in self.bookmark_database.get_all_bookmark_items("episode") if i["trakt_show_id"] not in hidden_shows ][self.page_start:self.page_end] self.list_builder.mixed_episode_builder(bookmarked_items) @staticmethod def discover_shows(): g.add_directory_item( g.get_language_string(30004), action="genericEndpoint", mediatype="shows", endpoint="popular", description=g.get_language_string(30438), ) g.add_directory_item( g.get_language_string(30367), action="showsPopularRecent", description=g.get_language_string(30439), ) if g.get_setting("trakt.auth"): g.add_directory_item( g.get_language_string(30005), action="showsRecommended", description=g.get_language_string(30440), ) g.add_directory_item( g.get_language_string(30006), action="genericEndpoint", mediatype="shows", endpoint="trending", description=g.get_language_string(30441), ) g.add_directory_item( g.get_language_string(30368), action="showsTrendingRecent", description=g.get_language_string(30442), ) g.add_directory_item( g.get_language_string(30046), action="showsNew", description=g.get_language_string(30443), ) g.add_directory_item( g.get_language_string(30007), action="genericEndpoint", mediatype="shows", endpoint="played", description=g.get_language_string(30444), ) g.add_directory_item( g.get_language_string(30008), action="genericEndpoint", mediatype="shows", endpoint="watched", description=g.get_language_string(30445), ) g.add_directory_item( g.get_language_string(30009), action="genericEndpoint", mediatype="shows", endpoint="collected", description=g.get_language_string(30446), ) g.add_directory_item( g.get_language_string(30374), action="TrendingLists", mediatype="shows", description=g.get_language_string(30447), ) g.add_directory_item( g.get_language_string(30376), action="PopularLists", mediatype="shows", description=g.get_language_string(30448), ) if not g.get_bool_setting("general.hideUnAired"): g.add_directory_item( g.get_language_string(30010), action="genericEndpoint", mediatype="shows", endpoint="anticipated", description=g.get_language_string(30449), ) g.add_directory_item( g.get_language_string(30011), action="showsUpdated", description=g.get_language_string(30450), ) g.add_directory_item( g.get_language_string(30182), action="showsNetworks", description=g.get_language_string(30451), ) g.add_directory_item( g.get_language_string(30184), action="showYears", description=g.get_language_string(30452), ) g.add_directory_item( g.get_language_string(30042), action="tvGenres", description=g.get_language_string(30453), ) g.add_directory_item( g.get_language_string(30203), action="showsByActor", description=g.get_language_string(30454), ) if not g.get_bool_setting("searchHistory"): g.add_directory_item( g.get_language_string(30013), action="showsSearch", description=g.get_language_string(30394), ) else: g.add_directory_item( g.get_language_string(30013), action="showsSearchHistory", description=g.get_language_string(30396), ) g.close_directory(g.CONTENT_MENU) @staticmethod @trakt_auth_guard def my_shows(): g.add_directory_item( g.get_language_string(30043), action="onDeckShows", description=g.get_language_string(30455), ) g.add_directory_item( g.get_language_string(30014), action="showsMyCollection", description=g.get_language_string(30456), ) g.add_directory_item( g.get_language_string(30015), action="showsMyWatchlist", description=g.get_language_string(30457), ) g.add_directory_item( g.get_language_string(30092), action="showsRecentlyWatched", description=g.get_language_string(30507), ) g.add_directory_item( g.get_language_string(30223), action="showsNextUp", description=g.get_language_string(30458), ) g.add_directory_item( g.get_language_string(30224), action="myUpcomingEpisodes", description=g.get_language_string(30459), ) g.add_directory_item( g.get_language_string(30225), action="showsMyProgress", description=g.get_language_string(30460), ) g.add_directory_item( g.get_language_string(30226), action="showsMyRecentEpisodes", description=g.get_language_string(30461), ) g.add_directory_item( g.get_language_string(30227), action="myTraktLists", mediatype="shows", description=g.get_language_string(30462), ) g.add_directory_item( g.get_language_string(30372), action="myLikedLists", mediatype="shows", description=g.get_language_string(30463), ) g.add_directory_item( g.get_language_string(30346), action="myWatchedEpisodes", description=g.get_language_string(30464), ) g.close_directory(g.CONTENT_MENU) def generic_endpoint(self, endpoint): trakt_list = self.shows_database.extract_trakt_page( "shows/{}".format(endpoint), page=g.PAGE, extended="full") self.list_builder.show_list_builder(trakt_list) def shows_popular_recent(self): year_range = "{}-{}".format(datetime.datetime.now().year - 1, datetime.datetime.now().year) trakt_list = self.shows_database.extract_trakt_page("shows/popular", years=year_range, page=g.PAGE, extended="full") self.list_builder.show_list_builder(trakt_list) def shows_trending_recent(self): year_range = "{}-{}".format(datetime.datetime.now().year - 1, datetime.datetime.now().year) trakt_list = self.shows_database.extract_trakt_page("shows/trending", years=year_range, page=g.PAGE, extended="full") self.list_builder.show_list_builder(trakt_list) @trakt_auth_guard def my_shows_collection(self): no_paging = not g.get_bool_setting("general.paginatecollection") sort = "title" if g.get_int_setting( "general.sortcollection") == 1 else False trakt_list = self.trakt_database.get_collected_shows(g.PAGE) if sort == "title" and not no_paging: trakt_list = sorted( trakt_list, key=lambda k: tools.SORT_TOKEN_REGEX.sub( "", k["trakt_object"]["info"].get('title').lower())) offset = (g.PAGE - 1) * self.page_limit trakt_list = trakt_list[offset:offset + self.page_limit] self.list_builder.show_list_builder(trakt_list, no_paging=no_paging, sort=sort) @trakt_auth_guard def my_shows_watchlist(self): paginate = not g.get_bool_setting("general.paginatetraktlists") trakt_list = self.shows_database.extract_trakt_page( "users/me/watchlist/shows", extended="full", page=g.PAGE, ignore_cache=True, no_paging=paginate, pull_all=True, ) self.list_builder.show_list_builder(trakt_list, no_paging=paginate) @trakt_auth_guard def my_show_progress(self): no_paging = not g.get_bool_setting("general.paginatecollection") sort = "title" if g.get_int_setting( "general.sortcollection") == 1 else False trakt_list = self.trakt_database.get_unfinished_collected_shows(g.PAGE) if sort == "title" and not no_paging: trakt_list = sorted( trakt_list, key=lambda k: tools.SORT_TOKEN_REGEX.sub( "", k["trakt_object"]["info"].get('title').lower())) offset = (g.PAGE - 1) * self.page_limit trakt_list = trakt_list[offset:offset + self.page_limit] self.list_builder.show_list_builder(trakt_list, no_paging=no_paging, sort=sort) @trakt_auth_guard def shows_recommended(self): trakt_list = self.shows_database.extract_trakt_page( "recommendations/shows", ignore_collected=True, extended="full") self.list_builder.show_list_builder(trakt_list) def shows_new(self): hidden_items = self.hidden_database.get_hidden_items( "recommendations", "shows") date_string = datetime.datetime.today() - datetime.timedelta(days=29) trakt_list = self.shows_database.extract_trakt_page( "calendars/all/shows/new/{}/30".format( date_string.strftime("%d-%m-%Y")), languages=','.join({'en', self.language_code}), extended="full", no_paging=True, ignore_cache=True, hide_watched=False, hide_unaired=False) trakt_list = [ i for i in trakt_list if i["trakt_id"] not in hidden_items ][:self.page_limit] self.list_builder.show_list_builder(trakt_list, no_paging=True) def shows_recently_watched(self): self.list_builder.show_list_builder( self.trakt_database.get_recently_watched_shows(), no_paging=True) def my_next_up(self): episodes = self.trakt_database.get_nextup_episodes( g.get_int_setting("nextup.sort") == 1) self.list_builder.mixed_episode_builder(episodes, no_paging=True) @trakt_auth_guard def my_recent_episodes(self): hidden_shows = self.hidden_database.get_hidden_items( "calendar", "shows") date_string = datetime.datetime.today() - datetime.timedelta(days=13) trakt_list = self.trakt.get_json("calendars/my/shows/{}/14".format( date_string.strftime("%d-%m-%Y")), extended="full") trakt_list = sorted( [i for i in trakt_list if i["trakt_show_id"] not in hidden_shows], key=lambda t: t["first_aired"], reverse=True, ) self.list_builder.mixed_episode_builder(trakt_list) @trakt_auth_guard def my_upcoming_episodes(self): tomorrow = (datetime.date.today() + datetime.timedelta(days=1)).strftime(g.DATE_FORMAT) upcoming_episodes = self.trakt.get_json( "calendars/my/shows/{}/30".format(tomorrow), extended="full")[:self.page_limit] self.list_builder.mixed_episode_builder(upcoming_episodes, prepend_date=True, no_paging=True, hide_unaired=False) def shows_networks(self): trakt_list = self.trakt.get_json_cached("networks") list_items = [] for i in trakt_list: list_items.append( g.add_directory_item( i["name"], action="showsNetworkShows", action_args=i["name"], bulk_add=True, )) xbmcplugin.addDirectoryItems(g.PLUGIN_HANDLE, list_items, len(list_items)) g.close_directory(g.CONTENT_MENU) def shows_networks_results(self, network): trakt_list = self.shows_database.extract_trakt_page("shows/popular", networks=network, page=g.PAGE, extended="full") self.list_builder.show_list_builder(trakt_list) g.close_directory(g.CONTENT_SHOW) def shows_updated(self): date = datetime.date.today() - datetime.timedelta(days=29) date = date.strftime(g.DATE_FORMAT) trakt_list = self.shows_database.extract_trakt_page( "shows/updates/{}".format(date), extended="full", ignore_cache=True, hide_watched=False, hide_unaired=False) self.list_builder.show_list_builder(trakt_list, no_paging=True) @staticmethod def shows_search_history(): history = SearchHistory().get_search_history("tvshow") g.add_directory_item( g.get_language_string(30195), action="showsSearch", description=g.get_language_string(30394), ) g.add_directory_item( g.get_language_string(30193), action="clearSearchHistory", mediatype="tvshow", is_folder=False, description=g.get_language_string(30193), ) for i in history: g.add_directory_item( i, action="showsSearchResults", action_args=tools.construct_action_args(i), ) g.close_directory(g.CONTENT_MENU) def shows_search(self, query=None): if not query: query = g.get_keyboard_input(g.get_language_string(30013)) if not query: g.cancel_directory() return if g.get_bool_setting("searchHistory"): SearchHistory().add_search_history("tvshow", query) self.shows_search_results(query) def shows_search_results(self, query): trakt_list = self.shows_database.extract_trakt_page("search/show", query=query, page=g.PAGE, extended="full", field="title", hide_unaired=False, hide_watched=False) if not trakt_list: g.cancel_directory() return self.list_builder.show_list_builder([ show for show in trakt_list if float(show["trakt_object"]["info"]["score"]) > 0 ], hide_unaired=False, hide_watched=False) def shows_by_actor(self, query): if not query: query = g.get_keyboard_input(g.get_language_string(30013)) if not query: g.cancel_directory() return if g.get_bool_setting("searchHistory"): SearchHistory().add_search_history("showActor", query) query = g.transliterate_string(query) # Try to deal with transliterated chinese actor names as some character -> word transliterations can be joined # I have no idea of the rules and it could well be arbitrary # This approach will only work if only one pair of adjoining transliterated chars are joined name_parts = query.split() for i in range(len(name_parts), 0, -1): query = "-".join(name_parts[:i]) + "-".join(name_parts[i:i + 1]) query = tools.quote_plus(query) trakt_list = self.shows_database.extract_trakt_page( "people/{}/shows".format(query), extended="full", page=g.PAGE, hide_watched=False, hide_unaired=False, ) if not trakt_list: continue else: break try: if not trakt_list or 'trakt_id' not in trakt_list[0]: raise KeyError except KeyError: g.cancel_directory() return self.list_builder.show_list_builder(trakt_list, hide_watched=False, hide_unaired=False) def show_seasons(self, args): self.list_builder.season_list_builder(args["trakt_id"], no_paging=True) def season_episodes(self, args): self.list_builder.episode_list_builder(args["trakt_show_id"], args["trakt_id"], no_paging=True) def flat_episode_list(self, args): self.list_builder.episode_list_builder(args["trakt_id"], no_paging=True) def shows_genres(self): g.add_directory_item( g.get_language_string(30045), action="showsGenresGet", menu_item={ "art": dict.fromkeys(['icon', 'poster', 'thumb', 'fanart'], g.GENRES_PATH + "list.png") }) genres = self.trakt.get_json_cached("genres/shows", extended="full") if genres is None: g.cancel_directory() return for i in genres: g.add_directory_item( i["name"], action="showGenresGet", action_args=i["slug"], menu_item={ "art": dict.fromkeys(['icon', 'poster', 'thumb', 'fanart'], "{}{}.png".format(g.GENRES_PATH, i["slug"])) }) g.close_directory(g.CONTENT_GENRES) def shows_genre_list(self, args): trakt_endpoint = ("trending" if g.get_int_setting("general.genres.endpoint") == 0 else "popular") if args is None: genre_display_list = [] genre_string = "" genres = self.trakt.get_json_cached("genres/shows") for genre in genres: gi = xbmcgui.ListItem(genre["name"]) gi.setArt( {"thumb": "{}{}.png".format(g.GENRES_PATH, genre["slug"])}) genre_display_list.append(gi) genre_multiselect = xbmcgui.Dialog().multiselect( "{}: {}".format(g.ADDON_NAME, g.get_language_string(30320)), genre_display_list, useDetails=True) if genre_multiselect is None: return for selection in genre_multiselect: genre_string += ", {}".format(genres[selection]["slug"]) genre_string = genre_string[2:] else: genre_string = tools.unquote(args) trakt_list = self.shows_database.extract_trakt_page( "shows/{}".format(trakt_endpoint), genres=genre_string, page=g.PAGE, extended="full") if trakt_list is None: g.cancel_directory() return self.list_builder.show_list_builder(trakt_list, next_args=genre_string) def shows_related(self, args): trakt_list = self.shows_database.extract_trakt_page( "shows/{}/related".format(args), extended="full") self.list_builder.show_list_builder(trakt_list) def shows_years(self, year=None): if year is None: current_year = int( tools.parse_datetime(datetime.datetime.today().strftime( g.DATE_FORMAT)).year) all_years = reversed( [year for year in range(1900, current_year + 1)]) menu_items = [] for year in all_years: menu_items.append( g.add_directory_item(g.UNICODE(year), action="showYears", action_args=year, bulk_add=True)) xbmcplugin.addDirectoryItems(g.PLUGIN_HANDLE, menu_items, len(menu_items)) g.close_directory(g.CONTENT_SHOW) else: trakt_list = self.shows_database.extract_trakt_page( "shows/popular", years=year, page=g.PAGE, extended="full", hide_watched=False) self.list_builder.show_list_builder(trakt_list) @trakt_auth_guard def my_watched_episode(self): watched_episodes = self.trakt_database.get_watched_episodes(g.PAGE) self.list_builder.mixed_episode_builder(watched_episodes)
def smart_play_show(self): self.window = windows.smart_play_background() self.window.setBackground(self.poster) self.window.setText(tools.lang(32094).encode('utf-8')) self.window.show() self.window.setProgress(0) self.window.setProgress(40) self.window.setText(tools.lang(32095).encode('utf-8')) tools.playList.clear() if 'episodeInfo' not in self.info_dictionary: if tools.getSetting('trakt.auth') == '': tools.showDialog.ok(tools.addonName, tools.lang(32093).encode('utf-8')) return season, episode = self.get_resume_episode() if self.final_episode_check(season, episode) is True: season = 1 episode = 1 season_object = TraktAPI().json_response( 'shows/%s/seasons?extended=full' % self.info_dictionary['ids']['trakt']) season_object = [x for x in season_object if x['number'] == season] self.info_dictionary = tvshowMenus.Menus().seasonListBuilder( season_object, self.info_dictionary, smartPlay=True) self.info_dictionary = json.loads( tools.unquote(self.info_dictionary)) else: season = self.info_dictionary['episodeInfo']['info']['season'] episode = self.info_dictionary['episodeInfo']['info']['episode'] self.window.setText(tools.lang(32096).encode('utf-8')) self.window.setProgress(60) episode_list = database.get( TraktAPI().json_response, 12, 'shows/%s/seasons/%s?extended=full' % (self.show_trakt_id, str(season))) playlist = [] for i in episode_list: if i['number'] < episode: continue playlist.append(i) self.window.setText(tools.lang(32097).encode('utf-8')) self.window.setProgress(80) actionArgs = {} actionArgs['playlist'] = playlist actionArgs['info_dictionary'] = self.info_dictionary actionArgs = tools.quote(json.dumps(actionArgs)) # Begin nasty Kodi 18 Skin workaround tools.execute( 'RunPlugin(plugin://plugin.video.%s?action=buildPlaylist&actionArgs=%s)' % (tools.addonName.lower(), actionArgs)) self.window.close()
class SmartPlay: """ Provides smart operations for playback """ def __init__(self, item_information): self.list_builder = ListBuilder() if "info" not in item_information: item_information = tools.get_item_information(item_information) self.item_information = item_information if not isinstance(self.item_information, dict): raise TypeError("Item Information is not a dictionary") self.show_trakt_id = self.item_information.get("trakt_show_id") if not self.show_trakt_id and "action_args" in self.item_information: self.show_trakt_id = self._extract_show_id_from_args( self.item_information["action_args"] ) self.display_style = g.get_int_setting("smartplay.displaystyle") self.trakt_api = TraktAPI() @staticmethod def _extract_show_id_from_args(action_args): if action_args["mediatype"] in ["tvshow", "movie"]: return action_args["trakt_id"] elif action_args["mediatype"] in ["episode", "season"]: return action_args["trakt_show_id"] def get_season_info(self): """ Fetches all season information for current show from database :return: :rtype: """ return TraktSyncDatabase().get_season_list(self.show_trakt_id) def resume_show(self): """ Identifies resume point for a show and plays from there :return: :rtype: """ g.cancel_playback() g.close_all_dialogs() g.PLAYLIST.clear() window = self._get_window() window.set_text(g.get_language_string(30060)) window.show() window.set_text(g.get_language_string(30061)) season_id, episode = self.get_resume_episode() window.set_text(g.get_language_string(30062)) window.set_text(g.get_language_string(30063)) self.build_playlist(season_id, episode) window.set_text(g.get_language_string(30311)) g.log( "Begining play from Season ID {} Episode {}".format(season_id, episode), "info", ) window.close() del window xbmc.Player().play(g.PLAYLIST) def build_playlist(self, season_id=None, minimum_episode=None): """ Uses available information to add relevant episodes to the current playlist :param season_id: Trakt ID of season to build :type season_id: int :param minimum_episode: Minimum episodes to add from :type minimum_episode: int :return: :rtype: """ if season_id is None: season_id = self.item_information["info"]["trakt_season_id"] if minimum_episode is None: minimum_episode = int(self.item_information["info"]["episode"]) + 1 try: [ g.PLAYLIST.add(url=i[0], listitem=i[1]) for i in self.list_builder.episode_list_builder( self.show_trakt_id, season_id, minimum_episode=minimum_episode, smart_play=True, hide_unaired=True, ) ] except TypeError: g.log( "Unable to add more episodes to the playlist, they may not be available for the requested season", "warning", ) return def get_resume_episode(self): """ Fetches playback information for current show and identifies the next episode to be resumed/watched :return: (Season, Episode) tuple :rtype: tuple """ get = MetadataHandler().get_trakt_info info = MetadataHandler().info try: playback_history = self.trakt_api.get_json( "sync/history/shows/{}".format(self.show_trakt_id), extended="full", limit=1 )[0] action = playback_history["action"] episode_info = playback_history["episode"] season = get(episode_info, "season") episode = get(episode_info, "episode") except IndexError: # Capture failure to get old playback and resume from first episode action = "watch" season = 1 episode = 1 if action != "watch": episode += 1 all_seasons = self.get_season_info() season_info = [i for i in all_seasons if info(i).get("season") == season][0] if episode >= info(season_info).get("episode_count"): season += 1 episode = 1 if self.final_episode_check(season, episode): season = 1 episode = 1 season_id = info( [i for i in all_seasons if info(i).get("season") == season][0]).get("trakt_id") return season_id, episode def final_episode_check(self, season, episode): """ Checks to see if the current item is the last episode aired for the show :param season: Season number of item to check :type season: int :param episode: Episode number of item to check :type episode: int :return: True if item is last aired episode else false :rtype: bool """ get = MetadataHandler().get_trakt_info season = int(season) episode = int(episode) last_aired = self.trakt_api.get_json( "shows/{}/last_episode".format(self.show_trakt_id) ) if season > get(last_aired, "season"): return True if season == get(last_aired, "season") and episode == get(last_aired, "number"): return True return False def append_next_season(self): """ Checks if current episode is the last episode for the season, if true adds next seasons episodes to playlist :return: :rtype: """ episode = self.item_information["info"]["episode"] season = self.item_information["info"]["season"] season_info = self.get_season_info() current_season_info = [i for i in season_info if season == i["info"]["season"]][0] if episode != current_season_info["episode_count"]: return next_season = [i for i in season_info if i["info"]["season"] == season + 1] if len(next_season) == 0: return season_id = next_season[0]["trakt_id"] self.build_playlist(season_id, 1) @staticmethod 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 shuffle_play(self): """ Creates a playlist of shuffled episodes for selected show and plays it :return: :rtype: """ g.PLAYLIST.clear() window = self._get_window() window.show() window.set_text(g.get_language_string(30062)) season_list = self.trakt_api.get_json( "shows/{}/seasons".format(self.show_trakt_id), extended="episodes" ) if season_list[0]["trakt_object"]["info"]["season"] == 0: season_list.pop(0) window.set_text(g.get_language_string(30063)) episode_list = [ episode for season in season_list for episode in season["trakt_object"]["info"]["episodes"] ] random.shuffle(episode_list) episode_list = episode_list[:40] [ episode.update({"trakt_show_id": self.show_trakt_id}) for episode in episode_list ] playlist = self.list_builder.mixed_episode_builder( episode_list, smart_play=True ) window.set_text(g.get_language_string(30064)) for episode in playlist: if episode is not None: g.PLAYLIST.add(url=episode[0], listitem=episode[1]) window.close() del window g.PLAYLIST.shuffle() xbmc.Player().play(g.PLAYLIST) def play_from_random_point(self): """ Select a random episode for show and plays from that point onwards :return: :rtype: """ import random g.PLAYLIST.clear() season_id = random.choice(self.get_season_info())["trakt_id"] playlist = self.list_builder.episode_list_builder( self.show_trakt_id, trakt_season=season_id, smart_play=True ) random_episode = random.randint(0, len(playlist) - 1) playlist = playlist[random_episode] g.PLAYLIST.add(url=playlist[0], listitem=playlist[1]) xbmc.Player().play(g.PLAYLIST) def create_single_item_playlist_from_info(self): g.cancel_playback() name = self.item_information["info"]["title"] item = g.add_directory_item( name, action="getSources", menu_item=self.item_information, action_args=tools.construct_action_args(self.item_information), bulk_add=True, is_playable=True, ) g.PLAYLIST.add(url=g.BASE_URL + "/?" + g.PARAM_STRING, listitem=item[1]) return g.PLAYLIST def playlist_present_check(self, ignore_setting=False): """ Confirms if a playlist is currently present. If not or playlist is for a different item, clear current list and build a new one :param ignore_setting: Force playlist building if setting is disabled :type ignore_setting: bool :return: Playlist if playlist is present else False :rtype: any """ if g.get_bool_setting("smartplay.playlistcreate") or ignore_setting: if not self.item_information["info"]["mediatype"] == "episode": g.log("Movie playback requested, clearing playlist") g.PLAYLIST.clear() return False playlist_uris = [ g.PLAYLIST[i].getPath() # pylint: disable=unsubscriptable-object for i in range(g.PLAYLIST.size()) ] # Check to see if we are just starting playback and kodi has created a playlist if len(playlist_uris) == 1 and playlist_uris[0].split('/')[-1].lstrip('?') == g.PARAM_STRING: return False if g.PLAYLIST.getposition() == -1: return self.create_single_item_playlist_from_info() if [i for i in playlist_uris if g.ADDON_NAME.lower() not in i]: g.log("Cleaning up other addon items from playlsit", "debug") playlist_uris = [] action_args = [ g.legacy_action_args_converter( g.legacy_params_converter( dict(tools.parse_qsl(i.split("?")[-1])) ) )["action_args"] for i in playlist_uris] show_ids = set(tools.deconstruct_action_args(i).get('trakt_show_id') for i in action_args) if len(show_ids) > 1 and self.show_trakt_id not in show_ids: g.log("Cleaning up items from other shows", "debug") playlist_uris = [] if (len(playlist_uris) == 0 or (len(playlist_uris) > 1 and not any(g.PARAM_STRING in i for i in playlist_uris))) or \ g.PLAYLIST.getposition() == -1: return self.create_single_item_playlist_from_info() return False def is_season_final(self): """ Checks if episode in question is the final for the season :return: bool :rtype: True if last episode of season, else False """ season = [i for i in self.get_season_info() if int(self.item_information["info"]["season"]) == int(i["info"]["season"])][0] if self.item_information["info"]["episode"] == season["episode_count"]: return True else: return False @staticmethod def handle_resume_prompt(resume_switch, force_resume_off=False, force_resume_on=False, force_resume_check=False): """ Handles displaying of resume prompt for item if required :param resume_switch: Resume param from arg string :type resume_switch: any :param force_resume_off: Disable resuming of item :type force_resume_off: bool :param force_resume_on: Force try resuming item :type force_resume_on: bool :param force_resume_check: Force a database check for item resume point :type force_resume_check: bool :return: Resume time in seconds for item :rtype: int """ bookmark_style = g.get_int_setting("general.bookmarkstyle") if force_resume_check and not resume_switch: from resources.lib.database.trakt_sync.bookmark import TraktSyncDatabase trakt_id = g.REQUEST_PARAMS.get("action_args").get("trakt_id") bookmark = TraktSyncDatabase().get_bookmark(trakt_id) if bookmark: g.log("bookmark: {}".format(bookmark)) resume_switch = bookmark["resume_time"] if ( g.PLAYLIST.size() <= 1 and resume_switch is not None and bookmark_style != 2 and not force_resume_off ): if bookmark_style == 0 and not force_resume_on: import datetime selection = xbmcgui.Dialog().contextmenu( [ "{} {}".format( g.get_language_string(30059), datetime.timedelta(seconds=int(resume_switch)), ), g.get_language_string(30331), ] ) if selection == -1: g.cancel_playback() sys.exit() elif selection != 0: resume_switch = None else: resume_switch = None return resume_switch def _get_window(self): if self.display_style == 0: # not sure about this one either return PersistentBackground( *SkinManager().confirm_skin_path("persistent_background.xml"), item_information=self.item_information ) else: return BackgroundWindowAdapter()
def smart_play_show(self, append_playlist=False): self.window = windows.smart_play_background() self.window.setBackground(self.poster) self.window.setText(tools.lang(32094)) if not append_playlist: self.window.show() self.window.setProgress(0) self.window.setProgress(40) self.window.setText(tools.lang(32095)) if not append_playlist: tools.playList.clear() if 'episodeInfo' not in self.info_dictionary: if tools.getSetting('trakt.auth') == '': tools.showDialog.ok(tools.addonName, tools.lang(32093)) return else: self.user_history = TraktAPI().json_response( 'sync/history/shows/%s' % self.show_trakt_id) season, episode = self.get_resume_episode() if self.final_episode_check(season, episode) is True: season = 1 episode = 1 else: season = self.info_dictionary['episodeInfo']['info']['season'] episode = self.info_dictionary['episodeInfo']['info']['episode'] self.window.setText(tools.lang(32096)) self.window.setProgress(60) if append_playlist: # Add next seasons episodes to the currently playing playlist and then finish up playlist = tvshowMenus.Menus().episodeListBuilder( self.show_trakt_id, season, hide_unaired=True, smartPlay=True) for i in playlist: # Confirm that the episode meta we have received from TVDB are for the correct episodes # If trakt provides the incorrect TVDB ID it's possible to begin play from the incorrect episode params = dict(tools.parse_qsl(i[0].replace('?', ''))) actionArgs = json.loads(params.get('actionArgs')) if actionArgs['episodeInfo']['info']['episode'] < episode: continue # If the episode is confirmed ok, add it to our playlist. if tvshowMenus.Menus().is_aired( actionArgs['episodeInfo']['info']): tools.playList.add(url=i[0], listitem=i[1]) return season_episodes = tvshowMenus.Menus().episodeListBuilder( self.show_trakt_id, season, smartPlay=True) self.window.setText(tools.lang(32097)) self.window.setProgress(80) playlist = [] for ep in season_episodes: path_arguments = dict(tools.parse_qsl(ep[0].replace('?', ''))) episode_args = json.loads( tools.unquote(path_arguments['actionArgs'])) ep_no = int(episode_args['episodeInfo']['info']['episode']) if ep_no >= episode and tvshowMenus.Menus().is_aired( episode_args['episodeInfo']['info']): playlist.append(ep) self.window.setText('Starting Playback') self.window.setProgress(100) for i in playlist: tools.playList.add(url=i[0], listitem=i[1]) tools.log( 'Begining play from Season %s Episode %s' % (season, episode), 'info') self.window.close() tools.player().play(tools.playList)
import json import sys from threading import Thread from resources.lib.common import tools from resources.lib.indexers.tmdb import TMDBAPI from resources.lib.indexers.trakt import TraktAPI from resources.lib.indexers.tvdb import TVDBAPI from resources.lib.modules import database sysaddon = sys.argv[0] try: syshandle = int(sys.argv[1]) except: syshandle = '' trakt = TraktAPI() language_code = tools.get_language_code() class Menus: def __init__(self): self.itemList = [] self.threadList = [] self.direct_episode_threads = [] self.title_appends = tools.getSetting('general.appendtitles') ###################################################### # MENUS ###################################################### def onDeckShows(self): hidden_shows = trakt.get_trakt_hidden_items('watched')['shows']
class Menus: def __init__(self): self.trakt = TraktAPI() self.language_code = g.get_language_code() self.trakt_database = TraktSyncDatabase() self.hidden_database = hidden.TraktSyncDatabase() self.bookmark_database = bookmark.TraktSyncDatabase() self.shows_database = shows.TraktSyncDatabase() self.list_builder = ListBuilder() self.page_limit = g.get_int_setting("item.limit") ###################################################### # MENUS ###################################################### @trakt_auth_guard def on_deck_shows(self): hidden_shows = self.hidden_database.get_hidden_items( "progress_watched", "tvshow") bookmarked_items = [ i for i in self.bookmark_database.get_all_bookmark_items("episode") if i["trakt_show_id"] not in hidden_shows ][:self.page_limit] self.list_builder.mixed_episode_builder(bookmarked_items) @staticmethod def discover_shows(): g.add_directory_item( g.get_language_string(30004), action="genericEndpoint", mediatype="shows", endpoint="popular", description=g.get_language_string(30450), ) g.add_directory_item( g.get_language_string(30378), action="showsPopularRecent", description=g.get_language_string(30451), ) if g.get_setting("trakt.auth"): g.add_directory_item( g.get_language_string(30005), action="showsRecommended", description=g.get_language_string(30452), ) g.add_directory_item( g.get_language_string(30006), action="genericEndpoint", mediatype="shows", endpoint="trending", description=g.get_language_string(30453), ) g.add_directory_item( g.get_language_string(30379), action="showsTrendingRecent", description=g.get_language_string(30454), ) g.add_directory_item( g.get_language_string(30047), action="showsNew", description=g.get_language_string(30455), ) g.add_directory_item( g.get_language_string(30007), action="genericEndpoint", mediatype="shows", endpoint="played", description=g.get_language_string(30456), ) g.add_directory_item( g.get_language_string(30008), action="genericEndpoint", mediatype="shows", endpoint="watched", description=g.get_language_string(30457), ) g.add_directory_item( g.get_language_string(30009), action="genericEndpoint", mediatype="shows", endpoint="collected", description=g.get_language_string(30458), ) g.add_directory_item( g.get_language_string(30385), action="TrendingLists", mediatype="shows", description=g.get_language_string(30459), ) g.add_directory_item( g.get_language_string(30387), action="PopularLists", mediatype="shows", description=g.get_language_string(30460), ) if not g.get_bool_setting("general.hideUnAired"): g.add_directory_item( g.get_language_string(30010), action="genericEndpoint", mediatype="shows", endpoint="anticipated", description=g.get_language_string(30461), ) g.add_directory_item( g.get_language_string(30011), action="showsUpdated", description=g.get_language_string(30462), ) g.add_directory_item( g.get_language_string(30186), action="showsNetworks", description=g.get_language_string(30463), ) g.add_directory_item( g.get_language_string(30188), action="showYears", description=g.get_language_string(30464), ) g.add_directory_item( g.get_language_string(30043), action="tvGenres", description=g.get_language_string(30465), ) g.add_directory_item( g.get_language_string(30212), action="showsByActor", description=g.get_language_string(30466), ) if not g.get_bool_setting("searchHistory"): g.add_directory_item( g.get_language_string(30013), action="showsSearch", description=g.get_language_string(30405), ) else: g.add_directory_item( g.get_language_string(30013), action="showsSearchHistory", description=g.get_language_string(30407), ) g.close_directory(g.CONTENT_FOLDER) @staticmethod @trakt_auth_guard def my_shows(): g.add_directory_item( g.get_language_string(30044), action="onDeckShows", description=g.get_language_string(30467), ) g.add_directory_item( g.get_language_string(30014), action="showsMyCollection", description=g.get_language_string(30468), ) g.add_directory_item( g.get_language_string(30015), action="showsMyWatchlist", description=g.get_language_string(30469), ) g.add_directory_item( g.get_language_string(30096), action="showsRecentlyWatched", description=g.get_language_string(30521), ) g.add_directory_item( g.get_language_string(30232), action="showsNextUp", description=g.get_language_string(30470), ) g.add_directory_item( g.get_language_string(30233), action="myUpcomingEpisodes", description=g.get_language_string(30471), ) g.add_directory_item( g.get_language_string(30234), action="showsMyProgress", description=g.get_language_string(30472), ) g.add_directory_item( g.get_language_string(30235), action="showsMyRecentEpisodes", description=g.get_language_string(30473), ) g.add_directory_item( g.get_language_string(30236), action="myTraktLists", mediatype="shows", description=g.get_language_string(30474), ) g.add_directory_item( g.get_language_string(30383), action="myLikedLists", mediatype="shows", description=g.get_language_string(30475), ) g.add_directory_item( g.get_language_string(30356), action="myWatchedEpisodes", description=g.get_language_string(30476), ) g.close_directory(g.CONTENT_FOLDER) def generic_endpoint(self, endpoint): trakt_list = self.shows_database.extract_trakt_page( "shows/{}".format(endpoint), page=g.PAGE, extended="full") self.list_builder.show_list_builder(trakt_list) def shows_popular_recent(self): year_range = "{}-{}".format(datetime.datetime.now().year - 1, datetime.datetime.now().year) trakt_list = self.shows_database.extract_trakt_page("shows/popular", years=year_range, page=g.PAGE, extended="full") self.list_builder.show_list_builder(trakt_list) def shows_trending_recent(self): year_range = "{}-{}".format(datetime.datetime.now().year - 1, datetime.datetime.now().year) trakt_list = self.shows_database.extract_trakt_page("shows/trending", years=year_range, page=g.PAGE, extended="full") self.list_builder.show_list_builder(trakt_list) @trakt_auth_guard def my_shows_collection(self): paginate = not g.get_bool_setting("general.paginatecollection") sort = "title" if paginate else False trakt_list = self.trakt_database.get_collected_shows(g.PAGE) self.list_builder.show_list_builder(trakt_list, no_paging=paginate, sort=sort) @trakt_auth_guard def my_shows_watchlist(self): paginate = not g.get_bool_setting("general.paginatetraktlists") trakt_list = self.shows_database.extract_trakt_page( "users/me/watchlist/shows", extended="full", page=g.PAGE, ignore_cache=True, no_paging=paginate, pull_all=True, ) self.list_builder.show_list_builder(trakt_list, no_paging=paginate) @trakt_auth_guard def my_show_progress(self): trakt_list = self.trakt_database.get_unfinished_collected_shows(g.PAGE) self.list_builder.show_list_builder(trakt_list) @trakt_auth_guard def shows_recommended(self): trakt_list = self.shows_database.extract_trakt_page( "recommendations/shows", ignore_collected=True, extended="full") self.list_builder.show_list_builder(trakt_list) def shows_new(self): hidden_items = self.hidden_database.get_hidden_items( "recommendations", "shows") date_string = datetime.datetime.today() - datetime.timedelta(days=29) trakt_list = self.trakt.get_json( "calendars/all/shows/new/{}/30".format( date_string.strftime("%d-%m-%Y")), languages=self.language_code, extended="full", ) trakt_list = [ i for i in trakt_list if i["trakt_show_id"] not in hidden_items ] self.list_builder.show_list_builder(trakt_list[:40]) def shows_recently_watched(self): self.list_builder.show_list_builder( self.trakt_database.get_recently_watched_shows()) def my_next_up(self): episodes = self.trakt_database.get_nextup_episodes( g.get_int_setting("nextup.sort") == 1) self.list_builder.mixed_episode_builder(episodes) @trakt_auth_guard def my_recent_episodes(self): hidden_shows = self.hidden_database.get_hidden_items( "calendar", "shows") date_string = datetime.datetime.today() - datetime.timedelta(days=13) trakt_list = self.trakt.get_json("calendars/my/shows/{}/14".format( date_string.strftime("%d-%m-%Y"), extended="full")) trakt_list = sorted( [i for i in trakt_list if i["trakt_show_id"] not in hidden_shows], key=lambda t: t["first_aired"], reverse=True, ) self.list_builder.mixed_episode_builder(trakt_list) @trakt_auth_guard def my_upcoming_episodes(self): tomorrow = (datetime.date.today() + datetime.timedelta(days=1)).strftime("%Y-%m-%d") upcoming_episodes = self.trakt.get_json( "calendars/my/shows/{}/30".format(tomorrow), extended="full")[:self.page_limit] self.list_builder.mixed_episode_builder(upcoming_episodes, prepend_date=True, no_paging=True, hide_unaired=False) def shows_networks(self): trakt_list = self.trakt.get_json_cached("networks") list_items = [] for i in trakt_list: list_items.append( g.add_directory_item( i["name"], action="showsNetworkShows", action_args=i["name"], bulk_add=True, )) xbmcplugin.addDirectoryItems(g.PLUGIN_HANDLE, list_items, len(list_items)) g.close_directory(g.CONTENT_FOLDER) def shows_networks_results(self, network): trakt_list = self.shows_database.extract_trakt_page("shows/popular", networks=network, page=g.PAGE, extended="full") self.list_builder.show_list_builder(trakt_list) g.close_directory(g.CONTENT_SHOW) def shows_updated(self): date = datetime.date.today() - datetime.timedelta(days=31) date = date.strftime("%Y-%m-%d") trakt_list = self.trakt.get_json("shows/updates/{}".format(date), extended="full") self.list_builder.show_list_builder(trakt_list, no_paging=True) @staticmethod def shows_search_history(): history = SearchHistory().get_search_history("tvshow") g.add_directory_item( g.get_language_string(30204), action="showsSearch", description=g.get_language_string(30405), ) g.add_directory_item( g.get_language_string(30202), action="clearSearchHistory", mediatype="tvshow", is_folder=False, description=g.get_language_string(30202), ) for i in history: g.add_directory_item( i, action="showsSearchResults", action_args=tools.construct_action_args(i), ) g.close_directory(g.CONTENT_FOLDER) def shows_search(self, query=None): if not query: k = xbmc.Keyboard("", g.get_language_string(30013)) k.doModal() query = k.getText() if k.isConfirmed() else None del k if not query: g.cancel_directory() return query = g.decode_py2(query) if g.get_bool_setting("searchHistory"): SearchHistory().add_search_history("tvshow", query) query = g.deaccent_string(g.display_string(query)) self.shows_search_results(query) def shows_search_results(self, query): trakt_list = self.trakt.get_json_paged( "search/show", query=tools.unquote(query), page=g.PAGE, extended="full", field="title", ) self.list_builder.show_list_builder([ show for show in trakt_list if float(show["trakt_object"]["info"]["score"]) > 0 ]) def shows_by_actor(self, actor): if not actor: k = xbmc.Keyboard("", g.get_language_string(30013)) k.doModal() query = k.getText() if k.isConfirmed() else None del k if not query: g.cancel_directory() return else: query = tools.unquote(actor) if g.get_bool_setting("searchHistory"): SearchHistory().add_search_history("showActor", query) query = g.deaccent_string(query) query = query.replace(" ", "-") query = tools.quote_plus(query) self.list_builder.show_list_builder( self.trakt.get_json_paged("people/{}/shows".format(query), extended="full", page=g.PAGE), hide_watched=False, hide_unaired=False, ) def show_seasons(self, args): self.list_builder.season_list_builder(args["trakt_id"], no_paging=True) def season_episodes(self, args): self.list_builder.episode_list_builder(args["trakt_show_id"], args["trakt_id"], no_paging=True) def flat_episode_list(self, args): self.list_builder.episode_list_builder(args["trakt_id"], no_paging=True) def shows_genres(self): g.add_directory_item(g.get_language_string(30046), action="showGenresGet") genres = self.trakt.get_json_cached("genres/shows", extended="full") if genres is None: g.cancel_directory() return for i in genres: g.add_directory_item(i["name"], action="showGenresGet", action_args=i["slug"]) g.close_directory(g.CONTENT_GENRES) def shows_genre_list(self, args): trakt_endpoint = ("trending" if g.get_int_setting("general.genres.endpoint") == 0 else "popular") if args is None: genre_display_list = [] genre_string = "" genres = self.trakt.get_json_cached("genres/shows") for genre in genres: genre_display_list.append(genre["name"]) genre_multiselect = xbmcgui.Dialog().multiselect( "{}: {}".format(g.ADDON_NAME, g.get_language_string(30330)), genre_display_list, ) if genre_multiselect is None: return for selection in genre_multiselect: genre_string += ", {}".format(genres[selection]["slug"]) genre_string = genre_string[2:] else: genre_string = args trakt_list = self.shows_database.extract_trakt_page( "shows/{}".format(trakt_endpoint), genres=genre_string, page=g.PAGE, extended="full", ) if trakt_list is None: g.cancel_directory() return self.list_builder.show_list_builder(trakt_list) def shows_related(self, args): trakt_list = self.trakt.get_json("shows/{}/related".format(args), extended="full") self.list_builder.show_list_builder(trakt_list) def shows_years(self, year=None): if year is None: current_year = int( tools.parse_datetime( datetime.datetime.today().strftime("%Y-%m-%d")).year) all_years = reversed( [year for year in range(1900, current_year + 1)]) menu_items = [] for year in all_years: menu_items.append( g.add_directory_item(str(year), action="showYears", action_args=year, bulk_add=True)) xbmcplugin.addDirectoryItems(g.PLUGIN_HANDLE, menu_items, len(menu_items)) g.close_directory(g.CONTENT_SHOW) else: trakt_list = self.trakt.get_json("shows/popular", years=year, page=g.PAGE, extended="full") self.list_builder.show_list_builder(trakt_list) @trakt_auth_guard def my_watched_episode(self): watched_episodes = self.trakt_database.get_watched_episodes(g.PAGE) self.list_builder.mixed_episode_builder(watched_episodes)
def trakt_api(self): from resources.lib.indexers.trakt import TraktAPI return TraktAPI()
class TraktSyncDatabase(Database): def __init__(self, ): super(TraktSyncDatabase, self).__init__(g.TRAKT_SYNC_DB_PATH, schema, migrate_db_lock) self.metadataHandler = MetadataHandler() self.trakt_api = TraktAPI() self.activities = {} self.item_list = [] self.base_date = "1970-01-01T00:00:00" self.task_queue = ThreadPool() self.mill_task_queue = ThreadPool() self.parent_task_queue = ThreadPool() self.refresh_activities() # If you make changes to the required meta in any indexer that is cached in this database # You will need to update the below version number to match the new addon version # This will ensure that the metadata required for operations is available self.last_meta_update = "2.0.0" if self.activities is None: self.clear_all_meta(False) self.set_base_activities() if self.activities is not None: self._check_database_version() self.notification_prefix = "{}: Trakt".format(g.ADDON_NAME) self.hide_unaired = g.get_bool_setting("general.hideUnAired") self.hide_specials = g.get_bool_setting("general.hideSpecials") self.hide_watched = g.get_bool_setting("general.hideWatched") self.date_delay = g.get_bool_setting("general.datedelay") self.page_limit = g.get_int_setting("item.limit") def clear_specific_item_meta(self, trakt_id, media_type): if media_type == "tvshow": media_type = "shows" elif media_type == "show": media_type = "shows" elif media_type == "movie": media_type = "movies" elif media_type == "episode": media_type = "episodes" elif media_type == "season": media_type = "seasons" if media_type not in ["shows", "movies", "seasons", "episodes"]: raise InvalidMediaTypeException(media_type) self.execute_sql("DELETE from {}_meta where id=?".format(media_type), (trakt_id, )) self.execute_sql( "UPDATE {} SET info=null, art=null, cast=null, meta_hash=null where trakt_id=?" "".format(media_type), (trakt_id, ), ) @staticmethod def _get_datetime_now(): return datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S.000Z") def refresh_activities(self): self.activities = self.execute_sql( "SELECT * FROM activities WHERE sync_id=1").fetchone() def set_base_activities(self): self.execute_sql( "INSERT OR REPLACE INTO activities(sync_id, seren_version, trakt_username) VALUES(1, ?, ?)", (self.last_meta_update, g.get_setting("trakt.username")), ) self.activities = self.execute_sql( "SELECT * FROM activities WHERE sync_id=1").fetchone() def _check_database_version(self): # If we are updating from a database prior to database versioning, we must clear the meta data # Migrate from older versions before trakt username tracking if tools.compare_version_numbers(self.activities["seren_version"], self.last_meta_update): g.log("Rebuilding Trakt Sync Database Version") xbmcgui.Dialog().ok(g.ADDON_NAME, g.get_language_string(30363)) try: self.re_build_database(True) except: self.rebuild_database() def flush_activities(self, clear_meta=False): if clear_meta: self.clear_all_meta() self.execute_sql("DELETE FROM activities") self.set_base_activities() def clear_user_information(self, notify=True): username = self.activities["trakt_username"] self.execute_sql( [ "UPDATE episodes SET watched=?", "UPDATE episodes SET collected=?", "UPDATE movies SET watched=?", "UPDATE movies SET collected=?", "UPDATE shows SET unwatched_episodes=?", "UPDATE shows SET watched_episodes=?", "UPDATE seasons SET unwatched_episodes=?", "UPDATE seasons SET watched_episodes=?", ], (0, ), ) self.execute_sql( [ "UPDATE episodes SET last_watched_at=?", "UPDATE movies SET last_watched_at=?", ], (None, ), ) self.execute_sql([ "DELETE from bookmarks WHERE 1=1", "DELETE from hidden WHERE 1=1", ]) self.execute_sql("DELETE from lists WHERE username=?", (username, )) self.set_trakt_user("") self.set_base_activities() if notify: g.notification(self.notification_prefix, g.get_language_string(30297), time=5000) def set_trakt_user(self, trakt_username): g.log("Setting Trakt Username: {}".format(trakt_username)) self.execute_sql("UPDATE activities SET trakt_username=?", (trakt_username, )) def clear_all_meta(self, notify=True): if notify: confirm = xbmcgui.Dialog().yesno(g.ADDON_NAME, g.get_language_string(30201)) if confirm == 0: return self.execute_sql( [ "UPDATE shows SET info=?, cast=?, art=?, meta_hash=?", "UPDATE seasons SET info=?, cast=?, art=?, meta_hash=?", "UPDATE episodes SET info=?, cast=?, art=?, meta_hash=?", "UPDATE movies SET info=?, cast=?, art=?, meta_hash=?", ], (None, None, None, None), ) self.execute_sql([ "DELETE FROM movies_meta", "DELETE FROM shows_meta", "DELETE FROM seasons_meta", "DELETE FROM episodes_meta", ]) if notify: g.notification(self.notification_prefix, g.get_language_string(30298), time=5000) def re_build_database(self, silent=False): if not silent: confirm = xbmcgui.Dialog().yesno(g.ADDON_NAME, g.get_language_string(30201)) if confirm == 0: return self.clear_all_meta(False) self.clear_user_information(False) self.rebuild_database() self.set_base_activities() self.refresh_activities() from resources.lib.database.trakt_sync import activities sync_errors = activities.TraktSyncDatabase().sync_activities(silent) if sync_errors: g.notification(self.notification_prefix, g.get_language_string(30364), time=5000) elif sync_errors is None: self.refresh_activities() else: g.notification(self.notification_prefix, g.get_language_string(30299), time=5000) def filter_items_that_needs_updating(self, requested, media_type): if requested is None or len(requested) == 0: return [] query = """WITH requested(trakt_id, meta_hash) AS (VALUES {}) select r.trakt_id as trakt_id from requested as r left join {} as db on r.trakt_id == db.trakt_id where db.trakt_id IS NULL or (db.info is null or db.art is null or db.cast is null or r.meta_hash != db.meta_hash)""".format( ",".join([ "({}, '{}')".format(i.get("trakt_id"), self.metadataHandler.meta_hash) for i in requested ]), media_type, ) result = set(r["trakt_id"] for r in self.execute_sql(query).fetchall()) return [r for r in requested if r.get("trakt_id") in result] def save_to_meta_table(self, items, meta_type, provider_type, id_column): if items is None: return sql_statement = "replace into {}_meta (id ,type, meta_hash, value) VALUES (?, ?, ?, ?)".format( meta_type) obj = None meta_hash = None if provider_type == "trakt": obj = MetadataHandler.trakt_object meta_hash = self.trakt_api.meta_hash elif provider_type == "tmdb": obj = MetadataHandler.tmdb_object meta_hash = self.metadataHandler.tmdb_api.meta_hash elif provider_type == "tvdb": obj = MetadataHandler.tvdb_object meta_hash = self.metadataHandler.tvdb_api.meta_hash elif provider_type == "fanart": obj = MetadataHandler.fanart_object meta_hash = self.metadataHandler.fanarttv_api.meta_hash elif provider_type == "omdb": obj = MetadataHandler.omdb_object meta_hash = self.metadataHandler.omdb_api.meta_hash if obj is None or meta_hash is None: raise UnsupportedProviderType(provider_type) self.execute_sql( sql_statement, ((i.get(id_column), provider_type, meta_hash, self.clean_meta(obj(i))) for i in items if i and obj(i) and i.get(id_column) and MetadataHandler.full_meta_up_to_par(meta_type, obj(i))), ) for i in items: if i and obj(i): if obj(i).get("seasons"): self.save_to_meta_table(i.get("seasons"), "season", provider_type, id_column) if obj(i).get("episodes"): self.save_to_meta_table(i.get("episodes"), "episode", provider_type, id_column) @staticmethod def clean_meta(item): if not item: return None result = { "info": { key: value for key, value in item.get("info", {}).items() if key != "seasons" and key != "episodes" }, "art": item.get("art"), "cast": item.get("cast"), } if not result.get("info") and not result.get("art") and not result.get( "cast"): g.log( "Bad Item meta discovered when cleaning - item: {}".format( item), "error", ) return None else: return result def insert_trakt_movies(self, movies): g.log("Inserting Movies into sync database: {}".format( [i.get("trakt_id") for i in movies])) get = MetadataHandler.get_trakt_info self.execute_sql( self.upsert_movie_query, (( i.get("trakt_id"), None, None, None, get(i, "collected"), get(i, "watched"), tools.validate_date(get(i, "aired")), tools.validate_date(get(i, "dateadded")), get(i, "tmdb_id"), get(i, "imdb_id"), None, self._create_args(i), tools.validate_date(get(i, "collected_at")), tools.validate_date(get(i, "last_watched_at")), i.get("trakt_id"), ) for i in movies), ) self.save_to_meta_table(movies, "movies", "trakt", "trakt_id") def insert_trakt_shows(self, shows): g.log("Inserting Shows into sync database: {}".format( [i.get("trakt_id") for i in shows])) get = MetadataHandler.get_trakt_info self.execute_sql( self.upsert_show_query, (( i.get("trakt_id"), None, None, None, tools.validate_date(get(i, "aired")), tools.validate_date(get(i, "dateadded")), get(i, "tmdb_id"), get(i, "tvdb_id"), get(i, "imdb_id"), None, get(i, "season_count"), get(i, "episode_count"), self._create_args(i), get(i, "is_airing"), i.get("trakt_id"), ) for i in shows), ) self.save_to_meta_table(shows, "shows", "trakt", "trakt_id") def insert_trakt_episodes(self, episodes): g.log("Inserting episodes into sync database: {}".format( [i.get("trakt_id") for i in episodes])) get = MetadataHandler.get_trakt_info self.execute_sql( self.upsert_episode_query, (( i.get("trakt_id"), i.get("trakt_show_id"), i.get("trakt_season_id"), get(i, "playcount"), get(i, "collected"), tools.validate_date(get(i, "aired")), tools.validate_date(get(i, "dateadded")), get(i, "season"), get(i, "episode"), get(i, "tmdb_id"), get(i, "tvdb_id"), get(i, "imdb_id"), None, None, None, self._create_args(i), tools.validate_date(get(i, "last_watched_at")), tools.validate_date(get(i, "collected_at")), None, i.get("trakt_id"), ) for i in episodes), ) self.save_to_meta_table(episodes, "episodes", "trakt", "trakt_id") def insert_trakt_seasons(self, seasons): g.log("Inserting seasons into sync database: {}".format( [i.get("trakt_id") for i in seasons])) get = MetadataHandler.get_trakt_info self.execute_sql( self.upsert_season_query, (( i.get("trakt_show_id"), i.get("trakt_id"), None, None, None, tools.validate_date(get(i, "aired")), tools.validate_date(get(i, "dateadded")), get(i, "tmdb_id"), get(i, "tvdb_id"), None, None, get(i, "season"), self._create_args(i), i.get("trakt_id"), ) for i in seasons), ) self.save_to_meta_table(seasons, "seasons", "trakt", "trakt_id") def _mill_if_needed(self, list_to_update, queue_wrapper=None, mill_episodes=True): if queue_wrapper is None: queue_wrapper = self._queue_mill_tasks query = """select s.trakt_id, CASE WHEN (agg.episode_count is NULL or agg.episode_count != s.episode_count) or (agg.meta_count=0 or agg.meta_count!=s.episode_count) THEN 'True' ELSE 'False' END as needs_update from shows as s left join(select s.trakt_id, count(e.trakt_id) as episode_count, count(em.id) as meta_count from shows as s inner join episodes as e on s.trakt_id = e.trakt_show_id left join episodes_meta as em on em.id = e.trakt_id and em.type = 'trakt' and em.meta_hash = '{}' where e.season != 0 and Datetime(e.air_date) < Datetime('now') GROUP BY s.trakt_id) as agg on s.trakt_id == agg.trakt_id WHERE s.trakt_id in ({})""".format( self.trakt_api.meta_hash, ",".join( str(i.get("trakt_show_id", i.get("trakt_id"))) for i in list_to_update), ) needs_update = self.execute_sql(query).fetchall() if needs_update is None or all(x["needs_update"] == "False" for x in needs_update): return g.log( "{} items require season milling".format( len([i for i in needs_update if i["needs_update"] == "True"])), "debug") self.mill_seasons([ i for i in list_to_update if any(x["needs_update"] == "True" and x.get("trakt_id") == i.get( "trakt_show_id", i.get("trakt_id")) for x in needs_update) ], queue_wrapper, mill_episodes) def mill_seasons(self, trakt_collection, queue_wrapper, mill_episodes=False): with SyncLock( "mill_seasons_episodes_{}".format(mill_episodes), { show.get("trakt_show_id", show.get("trakt_id")) for show in trakt_collection }, ) as sync_lock: get = MetadataHandler.get_trakt_info queue_wrapper(self._pull_show_seasons, [(i, mill_episodes) for i in sync_lock.running_ids]) results = self.mill_task_queue.wait_completion() seasons = [] episodes = [] trakt_info = MetadataHandler.trakt_info for show in trakt_collection: extended_seasons = get(show, "seasons", []) for season in results.get(show.get("trakt_id"), []): if self.hide_specials and get(season, "season") == 0: continue trakt_info(season).update( {"trakt_show_id": get(show, "trakt_id")}) trakt_info(season).update( {"tmdb_show_id": get(show, "tmdb_id")}) trakt_info(season).update( {"tvdb_show_id": get(show, "tvdb_id")}) season.update({"trakt_show_id": show.get("trakt_id")}) season.update({"tmdb_show_id": show.get("tmdb_id")}) season.update({"tvdb_show_id": show.get("tvdb_id")}) trakt_info(season).update( {"dateadded": get(show, "dateadded")}) trakt_info(season).update( {"tvshowtitle": get(show, "title")}) if not get(season, "season") == 0: show.update( {"season_count": show.get("season_count", 0) + 1}) show.update({ 'episode_count': show.get("episode_count", 0) + get(season, "aired_episodes", 0) }) for episode in get(season, "episodes", []): trakt_info(episode).update( {"trakt_show_id": get(show, "trakt_id")}) trakt_info(episode).update( {"tmdb_show_id": get(show, "tmdb_id")}) trakt_info(episode).update( {"tvdb_show_id": get(show, "tvdb_id")}) trakt_info(episode).update( {"trakt_season_id": get(season, "trakt_id")}) episode.update({"trakt_show_id": show.get("trakt_id")}) episode.update({"tmdb_show_id": show.get("tmdb_id")}) episode.update({"tvdb_show_id": show.get("tvdb_id")}) episode.update( {"trakt_season_id": season.get("trakt_id")}) trakt_info(episode).update( {"tvshowtitle": get(show, "title")}) for extended_season in ( x for x in extended_seasons if get(x, "season") == get(season, "season")): [ tools.smart_merge_dictionary( episode, extended_episode) for extended_episode in (x for x in get(extended_season, "episodes", []) if get(x, "episode") == get( episode, "episode")) ] tools.smart_merge_dictionary( season, extended_season) episodes.append(episode) seasons.append(season) self.insert_trakt_seasons( self.filter_trakt_items_that_needs_updating( seasons, "seasons")) self.insert_trakt_episodes( self.filter_trakt_items_that_needs_updating( episodes, "episodes")) self.execute_sql( "UPDATE shows SET episode_count=?, season_count=? WHERE trakt_id=? ", [(i.get("episode_count", 0), i.get("season_count", 0), i["trakt_id"]) for i in trakt_collection]) self.update_shows_statistics({"trakt_id": i} for i in sync_lock.running_ids) if mill_episodes: self.update_season_statistics({"trakt_id": i} for i in sync_lock.running_ids) def filter_trakt_items_that_needs_updating(self, requested, media_type): if len(requested) == 0: return requested get = MetadataHandler.get_trakt_info query = """WITH requested(trakt_id, meta_hash, updated_at) AS (VALUES {}) select r.trakt_id as trakt_id from requested as r left join {} as db on r.trakt_id == db.trakt_id left join {}_meta as m on db.trakt_id == id and type=\"trakt\" where db.trakt_id IS NULL or m.value IS NULL or m.meta_hash != r.meta_hash or (Datetime(db.last_updated) < Datetime(r.updated_at))""".format( ",".join("({}, '{}', '{}')".format(i.get( "trakt_id"), self.trakt_api.meta_hash, get(i, "dateadded")) for i in requested if i.get("trakt_id")), media_type, media_type, ) result = set(r["trakt_id"] for r in self.execute_sql(query).fetchall()) return [ r for r in requested if r.get("trakt_id") and r.get("trakt_id") in result ] def _pull_show_seasons(self, show_id, mill_episodes=False): return { show_id: self.trakt_api.get_json( "/shows/{}/seasons".format(show_id), extended="full,episodes" if mill_episodes else "full", translations=g.get_language_code(), ) } @staticmethod def _create_args(item): get = MetadataHandler.get_trakt_info info = MetadataHandler.info args = { "trakt_id": get(item, "trakt_id", info(item).get("trakt_id")), "mediatype": get(item, "mediatype", info(item).get("mediatype")), } if args["trakt_id"] == None: import inspect g.log(inspect.stack()) g.log(item) if args["mediatype"] == "season": args["trakt_show_id"] = get(item, "trakt_show_id", info(item).get("trakt_show_id")) if args["mediatype"] == "episode": args["trakt_show_id"] = get(item, "trakt_show_id", info(item).get("trakt_show_id")) args["trakt_season_id"] = get(item, "trakt_season_id", info(item).get("trakt_season_id")) return tools.quote(json.dumps(args, sort_keys=True)) def _queue_mill_tasks(self, func, args): for arg in args: g.log( "Requesting season info for show {} - mill_episodes={}".format( arg[0], arg[1]), "debug") self.mill_task_queue.put(func, *arg) def requires_update(self, new_date, old_date): if tools.parse_datetime(new_date, tools.DATE_FORMAT, False) > tools.parse_datetime( old_date, "%Y-%m-%dT%H:%M:%S", False): return True else: return False @staticmethod def wrap_in_trakt_object(items): for item in items: if item.get("show") is not None: info = item["show"].pop("info") item["show"].update({"trakt_id": info.get("trakt_id")}) item["show"].update({"trakt_object": {"info": info}}) if item.get("episode") is not None: info = item["episode"].pop("info") item["episode"].update({"trakt_id": info.get("trakt_id")}) item["episode"].update({"tvdb_id": info.get("tvdb_id")}) item["episode"].update({"tmdb_id": info.get("tmdb_id")}) item["episode"].update({"trakt_object": {"info": info}}) return items def _get_single_meta(self, trakt_url, trakt_id, media_type): return self._update_single_meta( trakt_url, self.execute_sql( """select id as trakt_id, value as trakt_object from {}_meta where id = ? and type = 'trakt' """.format(media_type), (int(trakt_id), ), ).fetchone(), media_type, ) def _update_single_meta(self, trakt_url, item, media_type): trakt_object = MetadataHandler.trakt_object if item is None: item = {} if trakt_object(item) is None or trakt_object(item) == {}: new_object = self.trakt_api.get_json(trakt_url, extended="full") self.save_to_meta_table([new_object], media_type, "trakt", "trakt_id") item.update(new_object) return item @staticmethod def update_missing_trakt_objects(db_list_to_update, list_to_update): for item in db_list_to_update: if item.get("trakt_object") is None: try: item.update( next(i for i in list_to_update if int(i.get("trakt_id") or 0) == int( item.get("trakt_id") or 0))) except StopIteration: g.log( "Failed to find item in list to update, original item: \n {}" .format(item)) def _extract_trakt_page(self, url, media_type, **params): result = [] def _handle_page(page): if not page or len(page) == 0: return [] to_insert = self.filter_trakt_items_that_needs_updating( page, media_type) if media_type == "movies": self.insert_trakt_movies(to_insert) elif media_type == "shows": self.insert_trakt_shows(to_insert) query = ( "WITH requested(trakt_id) AS (VALUES {}) select r.trakt_id as trakt_id from requested as r inner " "join {} as db on r.trakt_id == db.trakt_id left join {}_meta as m on db.trakt_id == " "id and type = 'trakt' where 1=1".format( ",".join("({})".format(i.get("trakt_id")) for i in page), media_type, media_type, )) if self.hide_unaired: query += " AND Datetime(air_date) < Datetime('now')" if self.hide_watched: if media_type == "movies": query += " AND watched = 0" if media_type == "shows": query += " AND watched_episodes < episode_count" result.extend(self.execute_sql(query).fetchall()) no_paging = params.get("no_paging", False) pull_all = params.pop("pull_all", False) page_number = params.pop("page", 1) if pull_all: _handle_page(self.trakt_api.get_json_cached(url, **params)) if len(result) >= (self.page_limit * page_number) and not no_paging: return result[self.page_limit * (page_number - 1):self.page_limit * page_number] else: params["limit"] = params.pop("page", self.page_limit) for page in self.trakt_api.get_all_pages_json(url, **params): _handle_page(page) if len(result) >= (self.page_limit * page_number) and not no_paging: return result[self.page_limit * (page_number - 1):self.page_limit * page_number] if no_paging: return result return result[self.page_limit * (page_number - 1):] def update_shows_statistics(self, trakt_list): to_update = ",".join({str(i.get("trakt_id")) for i in trakt_list}) self.execute_sql( """INSERT or REPLACE into shows (trakt_id, info, art, cast, air_date, last_updated, tmdb_id, tvdb_id, imdb_id, meta_hash, season_count, episode_count, watched_episodes, unwatched_episodes, args, is_airing) SELECT old.trakt_id, old.info, old.art, old.cast, old.air_date, old.last_updated, old.tmdb_id, old.tvdb_id, old.imdb_id, old.meta_hash, old.season_count, old.episode_count, COALESCE( new.watched_episodes, old.watched_episodes), COALESCE(new.unwatched_episodes, old.unwatched_episodes), old.args, old.is_airing FROM (select sh.trakt_id, sh.episode_count - sum(CASE WHEN e.watched > 0 AND e.season != 0 AND Datetime(e.air_date) < Datetime('now') THEN 1 ELSE 0 END) as unwatched_episodes, sum(CASE WHEN e.watched > 0 AND e.season != 0 AND Datetime(e.air_date) < Datetime('now') THEN 1 ELSE 0 END) as watched_episodes from shows as sh left join episodes as e on e.trakt_show_id = sh.trakt_id group by sh.trakt_id) AS new LEFT JOIN (SELECT * FROM shows) AS old on old.trakt_id = new.trakt_id where old.trakt_id in ({})""".format(to_update)) def update_season_statistics(self, trakt_list): to_update = ",".join({str(i.get("trakt_id")) for i in trakt_list}) self.execute_sql( """INSERT or REPLACE into seasons ( trakt_show_id, trakt_id, info, art, cast, air_date, last_updated, tmdb_id, tvdb_id, meta_hash, episode_count, watched_episodes, unwatched_episodes, is_airing, season, args ) SELECT old.trakt_show_id, old.trakt_id, old.info, old.art, old.cast, old.air_date, old.last_updated, old.tmdb_id, old.tvdb_id, old.meta_hash, COALESCE(new.episode_count, old.episode_count), COALESCE(new.watched_episodes, old.watched_episodes), COALESCE(new.unwatched_episodes, old.unwatched_episodes), COALESCE(new.is_airing, old.is_airing), old.season, old.args FROM ( SELECT se.trakt_id, sum( CASE WHEN datetime(e.air_date) < datetime('now') THEN 1 ELSE 0 END) AS episode_count, sum( CASE WHEN e.watched == 0 AND datetime(e.air_date) < datetime('now') THEN 1 ELSE 0 END) AS unwatched_episodes, sum( CASE WHEN e.watched > 0 AND datetime(e.air_date) < datetime('now') THEN 1 ELSE 0 END) AS watched_episodes, CASE WHEN max(e.air_date) > datetime('now') THEN 1 ELSE 0 END AS is_airing FROM seasons AS se INNER JOIN episodes AS e ON e.trakt_season_id = se.trakt_id WHERE se.season != 0 GROUP BY se.trakt_id) AS new LEFT JOIN ( SELECT * FROM seasons) AS old ON new.trakt_id = old.trakt_id where old.trakt_id in ({})""".format(to_update)) @property def upsert_movie_query(self): return """INSERT or REPLACE into movies ( trakt_id, info, art, cast, collected, watched, air_date, last_updated, tmdb_id, imdb_id, meta_hash, args, collected_at, last_watched_at ) SELECT COALESCE( new.trakt_id, old.trakt_id), COALESCE(new.info, old.info), COALESCE(new.art, old.art), COALESCE(new.cast, old.cast), COALESCE(new.collected, old.collected), COALESCE(new.watched, old.watched), COALESCE(new.air_date, old.air_date), COALESCE(new.last_updated, old.last_updated), COALESCE(new.tmdb_id, old.tmdb_id), COALESCE(new.imdb_id, old.imdb_id), COALESCE(new.meta_hash, old.meta_hash), COALESCE(new.args, old.args), COALESCE(new.collected_at, old.collected_at), COALESCE(new.last_watched_at, old.last_watched_at) FROM ( SELECT ? AS trakt_id, ? AS info, ? AS art, ? AS cast, ? AS collected, ? as watched, ? AS air_date, ? AS last_updated, ? AS tmdb_id, ? AS imdb_id, ? AS meta_hash, ? AS args, ? AS collected_at, ? AS last_watched_at) AS new LEFT JOIN (SELECT * FROM movies WHERE trakt_id = ? limit 1) AS old """ @property def upsert_show_query(self): return """INSERT or REPLACE into shows (trakt_id, info, art, cast, air_date, last_updated, tmdb_id, tvdb_id, imdb_id, meta_hash, season_count, episode_count, watched_episodes, unwatched_episodes, args, is_airing) SELECT COALESCE(new.trakt_id, old.trakt_id), COALESCE(new.info, old.info), COALESCE(new.art, old.art), COALESCE( new.cast, old.cast), COALESCE(new.air_date, old.air_date), COALESCE(new.last_updated, old.last_updated), COALESCE(new.tmdb_id, old.tmdb_id), COALESCE(new.tvdb_id, old.tvdb_id), COALESCE(new.imdb_id, old.imdb_id), COALESCE(new.meta_hash, old.meta_hash), COALESCE(new.season_count, old.season_count), COALESCE(new.episode_count, old.episode_count), COALESCE(old.watched_episodes, 0), COALESCE(old.unwatched_episodes, 0), COALESCE(new.args, old.args), COALESCE(new.is_airing, old.is_airing) FROM (SELECT ? AS trakt_id, ? AS info, ? AS art, ? AS cast, ? AS air_date, ? AS last_updated, ? AS tmdb_id, ? AS tvdb_id, ? AS imdb_id, ? AS meta_hash, ? AS season_count,? AS episode_count, ? AS args, ? as is_airing) AS new LEFT JOIN (SELECT * FROM shows WHERE trakt_id = ? limit 1) AS old """ @property def upsert_season_query(self): return """INSERT or REPLACE into seasons ( trakt_show_id, trakt_id, info, art, cast, air_date, last_updated, tmdb_id, tvdb_id, meta_hash, episode_count, watched_episodes, unwatched_episodes, season, args, is_airing ) SELECT COALESCE(new.trakt_show_id, old.trakt_show_id), COALESCE(new.trakt_id, old.trakt_id), COALESCE(new.info, old.info), COALESCE(new.art, old.art), COALESCE(new.cast, old.cast), COALESCE(new.air_date, old.air_date), COALESCE(new.last_updated, old.last_updated), COALESCE(new.tmdb_id, old.tmdb_id), COALESCE(new.tvdb_id, old.tvdb_id), COALESCE(new.meta_hash, old.meta_hash), COALESCE(new.episode_count, old.episode_count), old.watched_episodes, old.unwatched_episodes, COALESCE(new.season, old.season), COALESCE(new.args, old.args), old.is_airing FROM ( SELECT ? AS trakt_show_id, ? AS trakt_id, ? AS info, ? AS art, ? AS cast, ? AS air_date, ? AS last_updated, ? AS tmdb_id, ? AS tvdb_id, ? AS meta_hash, ? AS episode_count, ? AS season, ? AS args) AS new LEFT JOIN ( SELECT * FROM seasons WHERE trakt_id = ? limit 1) AS old """ @property def upsert_episode_query(self): return """INSERT or REPLACE into episodes (trakt_id, trakt_show_id, trakt_season_id, watched, collected,