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)
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)
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 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,