def __init__(self): """Initialize the class.""" trakt_settings = {'trakt_api_key': app.TRAKT_API_KEY, 'trakt_api_secret': app.TRAKT_API_SECRET, 'trakt_access_token': app.TRAKT_ACCESS_TOKEN, 'trakt_refresh_token': app.TRAKT_REFRESH_TOKEN} self.trakt_api = TraktApi(app.SSL_VERIFY, app.TRAKT_TIMEOUT, **trakt_settings) self.todoWanted = [] self.show_watchlist = {} self.episode_watchlist = {} self.collection_list = {} self.amActive = False
def addShowToBlacklist(self, seriesid): # URL parameters data = {'shows': [{'ids': {'tvdb': seriesid}}]} trakt_settings = {'trakt_api_secret': app.TRAKT_API_SECRET, 'trakt_api_key': app.TRAKT_API_KEY, 'trakt_access_token': app.TRAKT_ACCESS_TOKEN, 'trakt_refresh_token': app.TRAKT_REFRESH_TOKEN} show_name = get_showname_from_indexer(INDEXER_TVDBV2, seriesid) try: trakt_api = TraktApi(timeout=app.TRAKT_TIMEOUT, ssl_verify=app.SSL_VERIFY, **trakt_settings) trakt_api.request('users/{0}/lists/{1}/items'.format (app.TRAKT_USERNAME, app.TRAKT_BLACKLIST_NAME), data, method='POST') ui.notifications.message('Success!', "Added show '{0}' to blacklist".format(show_name)) except Exception as e: ui.notifications.error('Error!', "Unable to add show '{0}' to blacklist. Check logs.".format(show_name)) log.warning("Error while adding show '{name}' to trakt blacklist: {error}", {'name': show_name, 'error': e})
def fetch_popular_shows(self, page_url=None, trakt_list=None): """Get a list of popular shows from different Trakt lists based on a provided trakt_list. :param page_url: the page url opened to the base api url, for retreiving a specific list :param trakt_list: a description of the trakt list :return: A list of RecommendedShow objects, an empty list of none returned :throw: ``Exception`` if an Exception is thrown not handled by the libtrats exceptions """ trending_shows = [] removed_from_medusa = [] # Create a trakt settings dict trakt_settings = {'trakt_api_secret': app.TRAKT_API_SECRET, 'trakt_api_key': app.TRAKT_API_KEY, 'trakt_access_token': app.TRAKT_ACCESS_TOKEN, 'trakt_refresh_token': app.TRAKT_REFRESH_TOKEN} trakt_api = TraktApi(timeout=app.TRAKT_TIMEOUT, ssl_verify=app.SSL_VERIFY, **trakt_settings) try: not_liked_show = '' if app.TRAKT_ACCESS_TOKEN != '': library_shows = self.fetch_and_refresh_token(trakt_api, 'sync/watched/shows?extended=noseasons') + \ self.fetch_and_refresh_token(trakt_api, 'sync/collection/shows?extended=full') medusa_shows = [show.indexerid for show in app.showList if show.indexerid] removed_from_medusa = [lshow['show']['ids']['tvdb'] for lshow in library_shows if lshow['show']['ids']['tvdb'] not in medusa_shows] if app.TRAKT_BLACKLIST_NAME is not None and app.TRAKT_BLACKLIST_NAME: not_liked_show = trakt_api.request('users/' + app.TRAKT_USERNAME + '/lists/' + app.TRAKT_BLACKLIST_NAME + '/items') or [] else: log.debug('Trakt blacklist name is empty') if trakt_list not in ['recommended', 'newshow', 'newseason']: limit_show = '?limit=' + text_type(100 + len(not_liked_show)) + '&' else: limit_show = '?' series = self.fetch_and_refresh_token(trakt_api, page_url + limit_show + 'extended=full,images') or [] # Let's trigger a cache cleanup. missing_posters.clean() for show in series: try: if 'show' not in show: show['show'] = show if not_liked_show and show['show']['ids']['tvdb'] in (s['show']['ids']['tvdb'] for s in not_liked_show if s['type'] == 'show'): continue trending_shows.append(self._create_recommended_show( storage_key=show['show']['ids']['trakt'], series=show )) except MultipleShowObjectsException: continue # Update the dogpile index. This will allow us to retrieve all stored dogpile shows from the dbm. blacklist = app.TRAKT_BLACKLIST_NAME not in '' except TraktException as error: log.warning('Could not connect to Trakt service: {0}', error) raise return blacklist, trending_shows, removed_from_medusa
def run(self): ShowQueueItem.run(self) logger.log(u"Starting to add show {0}".format("by ShowDir: {0}".format(self.showDir) if self.showDir else "by Indexer Id: {0}".format(self.indexer_id))) # make sure the Indexer IDs are valid try: lINDEXER_API_PARMS = app.indexerApi(self.indexer).api_params.copy() if self.lang: lINDEXER_API_PARMS['language'] = self.lang logger.log(u"" + str(app.indexerApi(self.indexer).name) + ": " + repr(lINDEXER_API_PARMS)) t = app.indexerApi(self.indexer).indexer(**lINDEXER_API_PARMS) s = t[self.indexer_id] # Let's try to create the show Dir if it's not provided. This way we force the show dir to build build using the # Indexers provided series name if not self.showDir and self.root_dir: show_name = get_showname_from_indexer(self.indexer, self.indexer_id, self.lang) if show_name: self.showDir = os.path.join(self.root_dir, sanitize_filename(show_name)) dir_exists = makeDir(self.showDir) if not dir_exists: logger.log(u"Unable to create the folder {0}, can't add the show".format(self.showDir)) return chmodAsParent(self.showDir) else: logger.log(u"Unable to get a show {0}, can't add the show".format(self.showDir)) return # this usually only happens if they have an NFO in their show dir which gave us a Indexer ID that has no proper english version of the show if getattr(s, 'seriesname', None) is None: logger.log(u"Show in {} has no name on {}, probably searched with the wrong language.".format (self.showDir, app.indexerApi(self.indexer).name), logger.ERROR) ui.notifications.error("Unable to add show", "Show in " + self.showDir + " has no name on " + str(app.indexerApi( self.indexer).name) + ", probably the wrong language. Delete .nfo and add manually in the correct language.") self._finishEarly() return # if the show has no episodes/seasons if not s: logger.log(u"Show " + str(s['seriesname']) + " is on " + str( app.indexerApi(self.indexer).name) + " but contains no season/episode data.") ui.notifications.error("Unable to add show", "Show " + str(s['seriesname']) + " is on " + str(app.indexerApi( self.indexer).name) + " but contains no season/episode data.") self._finishEarly() return except Exception as e: logger.log(u"%s Error while loading information from indexer %s. Error: %r" % (self.indexer_id, app.indexerApi(self.indexer).name, ex(e)), logger.ERROR) # logger.log(u"Show name with ID %s doesn't exist on %s anymore. If you are using trakt, it will be removed from your TRAKT watchlist. If you are adding manually, try removing the nfo and adding again" % # (self.indexer_id, api.indexerApi(self.indexer).name), logger.WARNING) ui.notifications.error( "Unable to add show", "Unable to look up the show in %s on %s using ID %s, not using the NFO. Delete .nfo and try adding manually again." % (self.showDir, app.indexerApi(self.indexer).name, self.indexer_id) ) if app.USE_TRAKT: trakt_id = app.indexerApi(self.indexer).config['trakt_id'] trakt_api = TraktApi(app.SSL_VERIFY, app.TRAKT_TIMEOUT) title = self.showDir.split("/")[-1] data = { 'shows': [ { 'title': title, 'ids': {} } ] } if trakt_id == 'tvdb_id': data['shows'][0]['ids']['tvdb'] = self.indexer_id else: data['shows'][0]['ids']['tvrage'] = self.indexer_id try: trakt_api.traktRequest("sync/watchlist/remove", data, method='POST') except TraktException as e: logger.log("Could not remove show '{0}' from watchlist. Error: {1}".format(title, e), logger.WARNING) self._finishEarly() return try: newShow = TVShow(self.indexer, self.indexer_id, self.lang) newShow.load_from_indexer() self.show = newShow # set up initial values self.show.location = self.showDir self.show.subtitles = self.subtitles if self.subtitles is not None else app.SUBTITLES_DEFAULT self.show.quality = self.quality if self.quality else app.QUALITY_DEFAULT self.show.flatten_folders = self.flatten_folders if self.flatten_folders is not None else app.FLATTEN_FOLDERS_DEFAULT self.show.anime = self.anime if self.anime is not None else app.ANIME_DEFAULT self.show.scene = self.scene if self.scene is not None else app.SCENE_DEFAULT self.show.paused = self.paused if self.paused is not None else False # set up default new/missing episode status logger.log(u"Setting all episodes to the specified default status: " + str(self.show.default_ep_status)) self.show.default_ep_status = self.default_status if self.show.anime: self.show.release_groups = BlackAndWhiteList(self.show.indexerid) if self.blacklist: self.show.release_groups.set_black_keywords(self.blacklist) if self.whitelist: self.show.release_groups.set_white_keywords(self.whitelist) # # be smartish about this # if self.show.genre and "talk show" in self.show.genre.lower(): # self.show.air_by_date = 1 # if self.show.genre and "documentary" in self.show.genre.lower(): # self.show.air_by_date = 0 # if self.show.classification and "sports" in self.show.classification.lower(): # self.show.sports = 1 except app.indexer_exception as e: logger.log( u"Unable to add show due to an error with " + app.indexerApi(self.indexer).name + ": " + ex(e), logger.ERROR) if self.show: ui.notifications.error( "Unable to add " + str(self.show.name) + " due to an error with " + app.indexerApi( self.indexer).name + "") else: ui.notifications.error( "Unable to add show due to an error with " + app.indexerApi(self.indexer).name + "") self._finishEarly() return except MultipleShowObjectsException: logger.log(u"The show in " + self.showDir + " is already in your show list, skipping", logger.WARNING) ui.notifications.error('Show skipped', "The show in " + self.showDir + " is already in your show list") self._finishEarly() return except Exception as e: logger.log(u"Error trying to add show: " + ex(e), logger.ERROR) logger.log(traceback.format_exc(), logger.DEBUG) self._finishEarly() raise logger.log(u"Retrieving show info from IMDb", logger.DEBUG) try: self.show.load_imdb_info() except imdb_exceptions.IMDbError as e: logger.log(u"Something wrong on IMDb api: " + ex(e), logger.WARNING) except Exception as e: logger.log(u"Error loading IMDb info: " + ex(e), logger.ERROR) try: self.show.save_to_db() except Exception as e: logger.log(u"Error saving the show to the database: " + ex(e), logger.ERROR) logger.log(traceback.format_exc(), logger.DEBUG) self._finishEarly() raise # add it to the show list app.showList.append(self.show) try: self.show.load_episodes_from_indexer() except Exception as e: logger.log( u"Error with " + app.indexerApi(self.show.indexer).name + ", not creating episode list: " + ex(e), logger.ERROR) logger.log(traceback.format_exc(), logger.DEBUG) # update internal name cache name_cache.buildNameCache(self.show) try: self.show.load_episodes_from_dir() except Exception as e: logger.log(u"Error searching dir for episodes: " + ex(e), logger.ERROR) logger.log(traceback.format_exc(), logger.DEBUG) # if they set default ep status to WANTED then run the backlog to search for episodes # FIXME: This needs to be a backlog queue item!!! if self.show.default_ep_status == WANTED: logger.log(u"Launching backlog for this show since its episodes are WANTED") app.backlogSearchScheduler.action.searchBacklog([self.show]) self.show.write_metadata() self.show.update_metadata() self.show.populate_cache() self.show.flush_episodes() if app.USE_TRAKT: # if there are specific episodes that need to be added by trakt app.traktCheckerScheduler.action.manage_new_show(self.show) # add show to trakt.tv library if app.TRAKT_SYNC: app.traktCheckerScheduler.action.add_show_trakt_library(self.show) if app.TRAKT_SYNC_WATCHLIST: logger.log(u"update watchlist") notifiers.trakt_notifier.update_watchlist(show_obj=self.show) # Load XEM data to DB for show scene_numbering.xem_refresh(self.show.indexerid, self.show.indexer, force=True) # check if show has XEM mapping so we can determin if searches should go by scene numbering or indexer numbering. if not self.scene and scene_numbering.get_xem_numbering_for_show(self.show.indexerid, self.show.indexer): self.show.scene = 1 # After initial add, set to default_status_after. self.show.default_ep_status = self.default_status_after self.finish()
class TraktChecker(object): """Trakt checker class.""" def __init__(self): """Initialize the class.""" trakt_settings = { 'trakt_api_key': app.TRAKT_API_KEY, 'trakt_api_secret': app.TRAKT_API_SECRET, 'trakt_access_token': app.TRAKT_ACCESS_TOKEN, 'trakt_refresh_token': app.TRAKT_REFRESH_TOKEN } self.trakt_api = TraktApi(app.SSL_VERIFY, app.TRAKT_TIMEOUT, **trakt_settings) self.todoWanted = [] self.show_watchlist = {} self.episode_watchlist = {} self.collection_list = {} self.amActive = False def run(self, force=False): """Run Trakt Checker.""" self.amActive = True # add shows from Trakt watchlist if app.TRAKT_SYNC_WATCHLIST: self.todoWanted = [] # its about to all get re-added if len(app.ROOT_DIRS) < 2: log.warning('No default root directory') ui.notifications.error( 'Unable to add show', 'You do not have any default root directory. ' 'Please configure in general settings!') return self.sync_watchlist() self.sync_library() self.amActive = False def _request(self, path, data=None, method='GET'): """Fetch shows from trakt and store the refresh token when needed.""" try: library_shows = self.trakt_api.request(path, data, method=method) or [] if self.trakt_api.access_token_refreshed: app.TRAKT_ACCESS_TOKEN = self.trakt_api.access_token app.TRAKT_REFRESH_TOKEN = self.trakt_api.refresh_token app.instance.save_config() except TokenExpiredException: log.warning(u'You need to get a PIN and authorize Medusa app') app.TRAKT_ACCESS_TOKEN = '' app.TRAKT_REFRESH_TOKEN = '' app.instance.save_config() raise TokenExpiredException( 'You need to get a PIN and authorize Medusa app') return library_shows def find_show(self, indexerid, indexer): """Find show in Trakt library.""" trakt_library = [] try: trakt_library = self._request('sync/collection/shows') except (TraktException, AuthException, TokenExpiredException) as error: log.info( 'Unable to retrieve shows from Trakt collection. Error: {error!r}', {'error': error}) if not trakt_library: log.info('No shows found in your Trakt library. Nothing to sync') return trakt_show = [ x for x in trakt_library if get_trakt_indexer(indexer) and int(indexerid) in [int(x['show']['ids'].get(get_trakt_indexer(indexer)))] ] return trakt_show if trakt_show else None def remove_show_trakt_library(self, show_obj): """Remove show from trakt library.""" if self.find_show(show_obj.indexerid, show_obj.indexer): # Check if TRAKT supports that indexer if not get_trakt_indexer(show_obj.indexer): return # URL parameters title = get_title_without_year(show_obj.name, show_obj.start_year) data = { 'shows': [{ 'title': title, 'year': show_obj.start_year, 'ids': {} }] } data['shows'][0]['ids'][get_trakt_indexer( show_obj.indexer)] = show_obj.indexerid log.info("Removing '{show}' from Trakt library", {'show': show_obj.name}) # Remove all episodes from the Trakt collection for this show try: self.remove_episode_trakt_collection(filter_show=show_obj) except (TraktException, AuthException, TokenExpiredException) as error: log.info( "Unable to remove all episodes from show '{show}' from Trakt library. Error: {error!r}", { 'show': show_obj.name, 'error': error }) try: self._request('sync/collection/remove', data, method='POST') except (TraktException, AuthException, TokenExpiredException) as error: log.info( "Unable to remove show '{show}' from Trakt library. Error: {error!r}", { 'show': show_obj.name, 'error': error }) def add_show_trakt_library(self, show_obj): """Add show to trakt library.""" data = {} if not self.find_show(show_obj.indexerid, show_obj.indexer): # Check if TRAKT supports that indexer if not get_trakt_indexer(show_obj.indexer): return # URL parameters title = get_title_without_year(show_obj.name, show_obj.start_year) data = { 'shows': [{ 'title': title, 'year': show_obj.start_year, 'ids': {} }] } data['shows'][0]['ids'][get_trakt_indexer( show_obj.indexer)] = show_obj.indexerid if data: log.info("Adding show '{show}' to Trakt library", {'show': show_obj.name}) try: self._request('sync/collection', data, method='POST') except (TraktException, AuthException, TokenExpiredException) as error: log.info( "Unable to add show '{show}' to Trakt library. Error: {error!r}", { 'show': show_obj.name, 'error': error }) return def sync_library(self): """Sync Trakt library.""" if app.TRAKT_SYNC and app.USE_TRAKT: log.debug('Syncing Trakt collection') if self._get_show_collection(): self.add_episode_trakt_collection() if app.TRAKT_SYNC_REMOVE: self.remove_episode_trakt_collection() log.debug('Synced Trakt collection') def remove_episode_trakt_collection(self, filter_show=None): """Remove episode from trakt collection. For episodes that no longer have a media file (location) :param filter_show: optional. Only remove episodes from trakt collection for given shows """ if app.TRAKT_SYNC_REMOVE and app.TRAKT_SYNC and app.USE_TRAKT: params = [] main_db_con = db.DBConnection() statuses = [DOWNLOADED, ARCHIVED] sql_selection = 'SELECT s.indexer, s.startyear, s.indexer_id, s.show_name,' \ 'e.season, e.episode, e.status ' \ 'FROM tv_episodes AS e, tv_shows AS s WHERE e.indexer = s.indexer AND ' \ 's.indexer_id = e.showid and e.location = "" ' \ 'AND e.status in ({0})'.format(','.join(['?'] * len(statuses))) if filter_show: sql_selection += ' AND s.indexer_id = ? AND e.indexer = ?' params = [filter_show.series_id, filter_show.indexer] sql_result = main_db_con.select(sql_selection, statuses + params) if sql_result: trakt_data = [] for cur_episode in sql_result: # Check if TRAKT supports that indexer if not get_trakt_indexer(cur_episode['indexer']): continue if self._check_list(indexer=cur_episode['indexer'], indexer_id=cur_episode['indexer_id'], season=cur_episode['season'], episode=cur_episode['episode'], list_type='Collection'): log.info( "Removing episode '{show}' {ep} from Trakt collection", { 'show': cur_episode['show_name'], 'ep': episode_num(cur_episode['season'], cur_episode['episode']) }) title = get_title_without_year( cur_episode['show_name'], cur_episode['startyear']) trakt_data.append( (cur_episode['indexer_id'], cur_episode['indexer'], title, cur_episode['startyear'], cur_episode['season'], cur_episode['episode'])) if trakt_data: try: data = self.trakt_bulk_data_generate(trakt_data) self._request('sync/collection/remove', data, method='POST') self._get_show_collection() except (TraktException, AuthException, TokenExpiredException) as error: log.info( 'Unable to remove episodes from Trakt collection. Error: {error!r}', {'error': error}) def add_episode_trakt_collection(self): """Add all existing episodes to Trakt collections. For episodes that have a media file (location) """ if app.TRAKT_SYNC and app.USE_TRAKT: main_db_con = db.DBConnection() statuses = [DOWNLOADED, ARCHIVED] sql_selection = 'SELECT s.indexer, s.startyear, s.indexer_id, s.show_name, e.season, e.episode ' \ 'FROM tv_episodes AS e, tv_shows AS s ' \ 'WHERE e.indexer = s.indexer AND s.indexer_id = e.showid ' \ "AND e.status in ({0}) AND e.location <> ''".format(','.join(['?'] * len(statuses))) sql_result = main_db_con.select(sql_selection, statuses) if sql_result: trakt_data = [] for cur_episode in sql_result: # Check if TRAKT supports that indexer if not get_trakt_indexer(cur_episode['indexer']): continue if not self._check_list( indexer=cur_episode['indexer'], indexer_id=cur_episode['indexer_id'], season=cur_episode['season'], episode=cur_episode['episode'], list_type='Collection'): log.info( "Adding episode '{show}' {ep} to Trakt collection", { 'show': cur_episode['show_name'], 'ep': episode_num(cur_episode['season'], cur_episode['episode']) }) title = get_title_without_year( cur_episode['show_name'], cur_episode['startyear']) trakt_data.append( (cur_episode['indexer_id'], cur_episode['indexer'], title, cur_episode['startyear'], cur_episode['season'], cur_episode['episode'])) if trakt_data: try: data = self.trakt_bulk_data_generate(trakt_data) self._request('sync/collection', data, method='POST') self._get_show_collection() except (TraktException, AuthException, TokenExpiredException) as error: log.info( 'Unable to add episodes to Trakt collection. Error: {error!r}', {'error': error}) def sync_watchlist(self): """Sync Trakt watchlist.""" if app.TRAKT_SYNC_WATCHLIST and app.USE_TRAKT: log.debug('Syncing Trakt Watchlist') self.remove_from_library() if self._get_show_watchlist(): log.debug('Syncing shows with Trakt watchlist') self.add_show_watchlist() self.sync_trakt_shows() if self._get_episode_watchlist(): log.debug('Syncing episodes with Trakt watchlist') self.remove_episode_watchlist() self.add_episode_watchlist() self.sync_trakt_episodes() log.debug('Synced Trakt watchlist') def remove_episode_watchlist(self): """Remove episode from Trakt watchlist.""" if app.TRAKT_SYNC_WATCHLIST and app.USE_TRAKT: main_db_con = db.DBConnection() statuses = [DOWNLOADED, ARCHIVED] sql_selection = 'SELECT s.indexer, s.startyear, e.showid, s.show_name, e.season, e.episode ' \ 'FROM tv_episodes AS e, tv_shows AS s ' \ 'WHERE e.indexer = s.indexer ' \ 'AND s.indexer_id = e.showid AND e.status in ({0})'.format(','.join(['?'] * len(statuses))) sql_result = main_db_con.select(sql_selection, statuses) if sql_result: trakt_data = [] for cur_episode in sql_result: # Check if TRAKT supports that indexer if not get_trakt_indexer(cur_episode['indexer']): continue if self._check_list(indexer=cur_episode['indexer'], indexer_id=cur_episode['showid'], season=cur_episode['season'], episode=cur_episode['episode']): log.info( "Removing episode '{show}' {ep} from Trakt watchlist", { 'show': cur_episode['show_name'], 'ep': episode_num(cur_episode['season'], cur_episode['episode']) }) title = get_title_without_year( cur_episode['show_name'], cur_episode['startyear']) trakt_data.append( (cur_episode['showid'], cur_episode['indexer'], title, cur_episode['startyear'], cur_episode['season'], cur_episode['episode'])) if trakt_data: try: data = self.trakt_bulk_data_generate(trakt_data) self._request('sync/watchlist/remove', data, method='POST') self._get_episode_watchlist() except (TraktException, AuthException, TokenExpiredException) as error: log.info( 'Unable to remove episodes from Trakt watchlist. Error: {error!r}', {'error': error}) def add_episode_watchlist(self): """Add episode to Tratk watchlist.""" if app.TRAKT_SYNC_WATCHLIST and app.USE_TRAKT: main_db_con = db.DBConnection() statuses = [SNATCHED, SNATCHED_BEST, SNATCHED_PROPER, WANTED] sql_selection = 'SELECT s.indexer, s.startyear, e.showid, s.show_name, e.season, e.episode ' \ 'FROM tv_episodes AS e, tv_shows AS s ' \ 'WHERE e.indexer = s.indexer AND s.indexer_id = e.showid AND s.paused = 0 ' \ 'AND e.status in ({0})'.format(','.join(['?'] * len(statuses))) sql_result = main_db_con.select(sql_selection, statuses) if sql_result: trakt_data = [] for cur_episode in sql_result: # Check if TRAKT supports that indexer if not get_trakt_indexer(cur_episode['indexer']): continue if not self._check_list(indexer=cur_episode['indexer'], indexer_id=cur_episode['showid'], season=cur_episode['season'], episode=cur_episode['episode']): log.info( "Adding episode '{show}' {ep} to Trakt watchlist", { 'show': cur_episode['show_name'], 'ep': episode_num(cur_episode['season'], cur_episode['episode']) }) title = get_title_without_year( cur_episode['show_name'], cur_episode['startyear']) trakt_data.append( (cur_episode['showid'], cur_episode['indexer'], title, cur_episode['startyear'], cur_episode['season'], cur_episode['episode'])) if trakt_data: try: data = self.trakt_bulk_data_generate(trakt_data) self._request('sync/watchlist', data, method='POST') self._get_episode_watchlist() except (TraktException, AuthException, TokenExpiredException) as error: log.info( 'Unable to add episode to Trakt watchlist. Error: {error!r}', {'error': error}) def add_show_watchlist(self): """Add show to Trakt watchlist. It will add all shows from Medusa library """ if app.TRAKT_SYNC_WATCHLIST and app.USE_TRAKT: if app.showList: trakt_data = [] for show_obj in app.showList: if not self._check_list(show_obj=show_obj, list_type='Show'): log.info("Adding show '{show}' to Trakt watchlist", {'show': show_obj.name}) title = get_title_without_year(show_obj.name, show_obj.start_year) show_el = { 'title': title, 'year': show_obj.start_year, 'ids': {} } trakt_data.append(show_el) if trakt_data: try: data = {'shows': trakt_data} self._request('sync/watchlist', data, method='POST') except (TraktException, AuthException, TokenExpiredException) as error: log.info( 'Unable to add shows to Trakt watchlist. Error: {error!r}', {'error': error}) self._get_show_watchlist() def remove_from_library(self): """Remove show from Medusa library is if ended/completed.""" if app.TRAKT_SYNC_WATCHLIST and app.USE_TRAKT and app.TRAKT_REMOVE_SHOW_FROM_APPLICATION: log.debug('Retrieving ended/completed shows to remove from Medusa') if app.showList: for show in app.showList: if show.status == 'Ended': trakt_id = show.externals.get('trakt_id', None) if not (trakt_id or show.imdb_id): log.info( "Unable to check Trakt progress for show '{show}' " 'because Trakt|IMDB ID is missing. Skipping', {'show': show.name}) continue try: progress = self._request( 'shows/{0}/progress/watched'.format( trakt_id or show.imdb_id)) except (TraktException, AuthException, TokenExpiredException) as error: log.info( "Unable to check if show '{show}' is ended/completed. Error: {error!r}", { 'show': show.name, 'error': error }) continue else: if progress.get('aired', True) == progress.get( 'completed', False): app.show_queue_scheduler.action.removeShow( show, full=True) log.info( "Show '{show}' has being queued to be removed from Medusa library", {'show': show.name}) def sync_trakt_shows(self): """Sync Trakt shows watchlist.""" if not self.show_watchlist: log.info('No shows found in your Trakt watchlist. Nothing to sync') else: trakt_default_indexer = int(app.TRAKT_DEFAULT_INDEXER) for watchlisted_show in self.show_watchlist: trakt_show = watchlisted_show['show'] if trakt_show['year'] and trakt_show['ids']['slug'].endswith( str(trakt_show['year'])): show_name = '{title} ({year})'.format( title=trakt_show['title'], year=trakt_show['year']) else: show_name = trakt_show['title'] show = None indexer = None for i in indexerConfig: trakt_indexer = get_trakt_indexer(i) indexer_id = trakt_show['ids'].get(trakt_indexer, -1) indexer = indexerConfig[i]['id'] show = Show.find_by_id(app.showList, indexer, indexer_id) if show: break if not show: # If can't find with available indexers try IMDB trakt_indexer = get_trakt_indexer(EXTERNAL_IMDB) indexer_id = trakt_show['ids'].get(trakt_indexer, -1) show = Show.find_by_id(app.showList, EXTERNAL_IMDB, indexer_id) if not show: # If can't find with available indexers try TRAKT trakt_indexer = get_trakt_indexer(EXTERNAL_TRAKT) indexer_id = trakt_show['ids'].get(trakt_indexer, -1) show = Show.find_by_id(app.showList, EXTERNAL_TRAKT, indexer_id) if show: continue indexer_id = trakt_show['ids'].get( get_trakt_indexer(trakt_default_indexer), -1) if int(app.TRAKT_METHOD_ADD) != 2: self.add_show(trakt_default_indexer, indexer_id, show_name, SKIPPED) else: self.add_show(trakt_default_indexer, indexer_id, show_name, WANTED) if int(app.TRAKT_METHOD_ADD) == 1 and indexer: new_show = Show.find_by_id(app.showList, indexer, indexer_id) if new_show: set_episode_to_wanted(new_show, 1, 1) else: log.warning( 'Unable to find the new added show.' 'Pilot will be set to wanted in the next Trakt run' ) self.todoWanted.append(indexer_id) log.debug('Synced shows with Trakt watchlist') def sync_trakt_episodes(self): """Sync Trakt episodes watchlist.""" if not self.episode_watchlist: log.info( 'No episodes found in your Trakt watchlist. Nothing to sync') return added_shows = [] trakt_default_indexer = int(app.TRAKT_DEFAULT_INDEXER) for watchlist_item in self.episode_watchlist: trakt_show = watchlist_item['show'] trakt_episode = watchlist_item['episode'].get('number', -1) trakt_season = watchlist_item['episode'].get('season', -1) show = None for i in indexerConfig: trakt_indexer = get_trakt_indexer(i) indexer_id = trakt_show['ids'].get(trakt_indexer, -1) indexer = indexerConfig[i]['id'] show = Show.find_by_id(app.showList, indexer, indexer_id) if show: break if not show: # If can't find with available indexers try IMDB trakt_indexer = get_trakt_indexer(EXTERNAL_IMDB) indexer_id = trakt_show['ids'].get(trakt_indexer, -1) show = Show.find_by_id(app.showList, EXTERNAL_IMDB, indexer_id) if not show: # If can't find with available indexers try TRAKT trakt_indexer = get_trakt_indexer(EXTERNAL_TRAKT) indexer_id = trakt_show['ids'].get(trakt_indexer, -1) show = Show.find_by_id(app.showList, EXTERNAL_TRAKT, indexer_id) # If can't find show add with default trakt indexer if not show: indexer_id = trakt_show['ids'].get( get_trakt_indexer(trakt_default_indexer), -1) # Only add show if we didn't added it before if indexer_id not in added_shows: self.add_show(trakt_default_indexer, indexer_id, trakt_show['title'], SKIPPED) added_shows.append(indexer_id) elif not trakt_season == 0 and not show.paused: set_episode_to_wanted(show, trakt_season, trakt_episode) log.debug('Synced episodes with Trakt watchlist') @staticmethod def add_show(indexer, indexer_id, show_name, status): """Add a new show with default settings.""" if not Show.find_by_id(app.showList, EXTERNAL_IMDB, indexer_id): root_dirs = app.ROOT_DIRS location = root_dirs[int(root_dirs[0]) + 1] if root_dirs else None if location: log.info( "Adding show '{show}' using indexer: '{indexer_name}' and ID: {id}", { 'show': show_name, 'indexer_name': indexerConfig[indexer]['identifier'], 'id': indexer_id }) app.show_queue_scheduler.action.addShow( indexer, indexer_id, None, default_status=status, quality=int(app.QUALITY_DEFAULT), season_folders=int(app.SEASON_FOLDERS_DEFAULT), paused=app.TRAKT_START_PAUSED, default_status_after=status, root_dir=location) tries = 0 while tries < 3: if Show.find_by_id(app.showList, indexer, indexer_id): return # Wait before show get's added and refreshed time.sleep(60) tries += 1 log.warning("Error creating show '{show}. Please check logs' ", {'show': show_name}) return else: log.warning( "Error creating show '{show}' folder. No default root directory", {'show': show_name}) return def manage_new_show(self, show): """Set episodes to wanted for the recently added show.""" log.debug( "Checking for wanted episodes for show '{show}' in Trakt watchlist", {'show': show.name}) episodes = [i for i in self.todoWanted if i[0] == show.indexerid] for episode in episodes: self.todoWanted.remove(episode) set_episode_to_wanted(show, episode[1], episode[2]) def _check_list(self, show_obj=None, indexer=None, indexer_id=None, season=None, episode=None, list_type=None): """Check if we can find the show in the Trakt watchlist|collection list.""" if 'Collection' == list_type: trakt_indexer = get_trakt_indexer(indexer) for collected_show in self.collection_list: if not collected_show['show']['ids'].get(trakt_indexer, '') == indexer_id: continue if 'seasons' in collected_show: for season_item in collected_show['seasons']: for episode_item in season_item['episodes']: trakt_season = season_item['number'] trakt_episode = episode_item['number'] if trakt_season == season and trakt_episode == episode: return True else: return False elif 'Show' == list_type: trakt_indexer = get_trakt_indexer(show_obj.indexer) for watchlisted_show in self.show_watchlist: if watchlisted_show['show']['ids'].get(trakt_indexer) == show_obj.indexerid or \ watchlisted_show['show']['ids'].get(get_trakt_indexer(EXTERNAL_IMDB), '') == show_obj.imdb_id: return True return False else: trakt_indexer = get_trakt_indexer(indexer) for watchlisted_episode in self.episode_watchlist: if watchlisted_episode['episode'].get('season', -1) == season and \ watchlisted_episode['episode'].get('number', -1) == episode and \ watchlisted_episode['show']['ids'].get(trakt_indexer, '') == indexer_id: return True return False def _get_show_watchlist(self): """Get shows watchlist.""" try: self.show_watchlist = self._request('sync/watchlist/shows') except (TraktException, AuthException, TokenExpiredException) as error: log.info( u'Unable to retrieve shows from Trakt watchlist. Error: {error!r}', {'error': error}) return False return True def _get_episode_watchlist(self): """Get episodes watchlist.""" try: self.episode_watchlist = self._request('sync/watchlist/episodes') except (TraktException, AuthException, TokenExpiredException) as error: log.info( u'Unable to retrieve episodes from Trakt watchlist. Error: {error!r}', {'error': error}) return False return True def _get_show_collection(self): """Get show collection.""" try: self.collection_list = self._request('sync/collection/shows') except (TraktException, AuthException, TokenExpiredException) as error: log.info( 'Unable to retrieve shows from Trakt collection. Error: {error!r}', {'error': error}) return False return True @staticmethod def trakt_bulk_data_generate(trakt_data): """Build the JSON structure to send back to Trakt.""" unique_shows = {} unique_seasons = {} for indexer_id, indexer, show_name, start_year, season, episode in trakt_data: if indexer_id not in unique_shows: unique_shows[indexer_id] = { 'title': show_name, 'year': start_year, 'ids': {}, 'seasons': [] } unique_shows[indexer_id]['ids'][get_trakt_indexer( indexer)] = indexer_id unique_seasons[indexer_id] = [] # Get the unique seasons per Show for indexer_id, indexer, show_name, start_year, season, episode in trakt_data: if season not in unique_seasons[indexer_id]: unique_seasons[indexer_id].append(season) # build the query show_list = [] seasons_list = {} for searched_show in unique_shows: show = [] seasons_list[searched_show] = [] for searched_season in unique_seasons[searched_show]: episodes_list = [] for indexer_id, indexer, show_name, start_year, season, episode in trakt_data: if season == searched_season and indexer_id == searched_show: episodes_list.append({'number': episode}) show = unique_shows[searched_show] show['seasons'].append({ 'number': searched_season, 'episodes': episodes_list }) if show: show_list.append(show) post_data = {'shows': show_list} return post_data
def update_library(ep_obj): """Send a request to trakt indicating that the given episode is part of our library. ep_obj: The Episode object to add to trakt """ # Check if TRAKT supports that indexer if not get_trakt_indexer(ep_obj.series.indexer): return # Create a trakt settings dict trakt_settings = { 'trakt_api_secret': app.TRAKT_API_SECRET, 'trakt_api_key': app.TRAKT_API_KEY, 'trakt_access_token': app.TRAKT_ACCESS_TOKEN, 'trakt_refresh_token': app.TRAKT_REFRESH_TOKEN } trakt_api = TraktApi(app.SSL_VERIFY, app.TRAKT_TIMEOUT, **trakt_settings) if app.USE_TRAKT: try: # URL parameters title = get_title_without_year(ep_obj.series.name, ep_obj.series.start_year) data = { 'shows': [{ 'title': title, 'year': ep_obj.series.start_year, 'ids': {}, }] } data['shows'][0]['ids'][get_trakt_indexer( ep_obj.series.indexer)] = ep_obj.series.indexerid if app.TRAKT_SYNC_WATCHLIST: if app.TRAKT_REMOVE_SERIESLIST: trakt_api.request('sync/watchlist/remove', data, method='POST') # Add Season and Episode + Related Episodes data['shows'][0]['seasons'] = [{ 'number': ep_obj.season, 'episodes': [] }] for relEp_Obj in [ep_obj] + ep_obj.related_episodes: data['shows'][0]['seasons'][0]['episodes'].append( {'number': relEp_Obj.episode}) if app.TRAKT_SYNC_WATCHLIST: if app.TRAKT_REMOVE_WATCHLIST: trakt_api.request('sync/watchlist/remove', data, method='POST') # update library trakt_api.request('sync/collection', data, method='POST') except (TokenExpiredException, TraktException, AuthException) as error: log.debug('Unable to update Trakt: {0}', error.message)
def update_watchlist(show_obj=None, s=None, e=None, data_show=None, data_episode=None, update='add'): """Send a request to trakt indicating that the given episode is part of our library. show_obj: The Series object to add to trakt s: season number e: episode number data_show: structured object of shows trakt type data_episode: structured object of episodes trakt type update: type o action add or remove """ # Check if TRAKT supports that indexer if not get_trakt_indexer(show_obj.indexer): return trakt_settings = { 'trakt_api_secret': app.TRAKT_API_SECRET, 'trakt_api_key': app.TRAKT_API_KEY, 'trakt_access_token': app.TRAKT_ACCESS_TOKEN, 'trakt_refresh_token': app.TRAKT_REFRESH_TOKEN } trakt_api = TraktApi(app.SSL_VERIFY, app.TRAKT_TIMEOUT, **trakt_settings) if app.USE_TRAKT: data = {} try: # URL parameters if show_obj is not None: title = get_title_without_year(show_obj.name, show_obj.start_year) data = { 'shows': [{ 'title': title, 'year': show_obj.start_year, 'ids': {}, }] } data['shows'][0]['ids'][get_trakt_indexer( show_obj.indexer)] = show_obj.indexerid elif data_show is not None: data.update(data_show) else: log.warning( "There's a coding problem contact developer. It's needed to be provided at" ' least one of the two: data_show or show_obj', ) return False if data_episode is not None: data['shows'][0].update(data_episode) elif s is not None: # trakt URL parameters season = { 'season': [{ 'number': s, }] } if e is not None: # trakt URL parameters episode = {'episodes': [{'number': e}]} season['season'][0].update(episode) data['shows'][0].update(season) trakt_url = 'sync/watchlist' if update == 'remove': trakt_url += '/remove' trakt_api.request(trakt_url, data, method='POST') except (TokenExpiredException, TraktException, AuthException) as error: log.debug('Unable to update Trakt watchlist: {0}', error.message) return False return True
class TraktChecker(object): """Trakt checker class.""" def __init__(self): """Initialize the class.""" trakt_settings = {'trakt_api_key': app.TRAKT_API_KEY, 'trakt_api_secret': app.TRAKT_API_SECRET, 'trakt_access_token': app.TRAKT_ACCESS_TOKEN, 'trakt_refresh_token': app.TRAKT_REFRESH_TOKEN} self.trakt_api = TraktApi(app.SSL_VERIFY, app.TRAKT_TIMEOUT, **trakt_settings) self.todoWanted = [] self.show_watchlist = {} self.episode_watchlist = {} self.collection_list = {} self.amActive = False def run(self, force=False): """Run Trakt Checker.""" self.amActive = True # add shows from Trakt watchlist if app.TRAKT_SYNC_WATCHLIST: self.todoWanted = [] # its about to all get re-added if len(app.ROOT_DIRS) < 2: log.warning('No default root directory') ui.notifications.error('Unable to add show', 'You do not have any default root directory. ' 'Please configure in general settings!') return self.sync_watchlist() self.sync_library() self.amActive = False def _request(self, path, data=None, method='GET'): """Fetch shows from trakt and store the refresh token when needed.""" try: library_shows = self.trakt_api.request(path, data, method=method) or [] if self.trakt_api.access_token_refreshed: app.TRAKT_ACCESS_TOKEN = self.trakt_api.access_token app.TRAKT_REFRESH_TOKEN = self.trakt_api.refresh_token app.instance.save_config() except TokenExpiredException: log.warning(u'You need to get a PIN and authorize Medusa app') app.TRAKT_ACCESS_TOKEN = '' app.TRAKT_REFRESH_TOKEN = '' app.instance.save_config() raise TokenExpiredException('You need to get a PIN and authorize Medusa app') return library_shows def find_show(self, indexerid, indexer): """Find show in Trakt library.""" trakt_library = [] try: trakt_library = self._request('sync/collection/shows') except (TraktException, AuthException, TokenExpiredException) as error: log.info('Unable to retrieve shows from Trakt collection. Error: {error!r}', {'error': error}) if not trakt_library: log.info('No shows found in your Trakt library. Nothing to sync') return trakt_show = [x for x in trakt_library if get_trakt_indexer(indexer) and int(indexerid) in [int(x['show']['ids'].get(get_trakt_indexer(indexer)))]] return trakt_show if trakt_show else None def remove_show_trakt_library(self, show_obj): """Remove show from trakt library.""" if self.find_show(show_obj.indexerid, show_obj.indexer): # Check if TRAKT supports that indexer if not get_trakt_indexer(show_obj.indexer): return # URL parameters title = get_title_without_year(show_obj.name, show_obj.start_year) data = { 'shows': [ { 'title': title, 'year': show_obj.start_year, 'ids': {} } ] } data['shows'][0]['ids'][get_trakt_indexer(show_obj.indexer)] = show_obj.indexerid log.info("Removing '{show}' from Trakt library", {'show': show_obj.name}) # Remove all episodes from the Trakt collection for this show try: self.remove_episode_trakt_collection(filter_show=show_obj) except (TraktException, AuthException, TokenExpiredException) as error: log.info("Unable to remove all episodes from show '{show}' from Trakt library. Error: {error!r}", { 'show': show_obj.name, 'error': error }) try: self._request('sync/collection/remove', data, method='POST') except (TraktException, AuthException, TokenExpiredException) as error: log.info("Unable to remove show '{show}' from Trakt library. Error: {error!r}", { 'show': show_obj.name, 'error': error }) def add_show_trakt_library(self, show_obj): """Add show to trakt library.""" data = {} if not self.find_show(show_obj.indexerid, show_obj.indexer): # Check if TRAKT supports that indexer if not get_trakt_indexer(show_obj.indexer): return # URL parameters title = get_title_without_year(show_obj.name, show_obj.start_year) data = { 'shows': [ { 'title': title, 'year': show_obj.start_year, 'ids': {} } ] } data['shows'][0]['ids'][get_trakt_indexer(show_obj.indexer)] = show_obj.indexerid if data: log.info("Adding show '{show}' to Trakt library", {'show': show_obj.name}) try: self._request('sync/collection', data, method='POST') except (TraktException, AuthException, TokenExpiredException) as error: log.info("Unable to add show '{show}' to Trakt library. Error: {error!r}", { 'show': show_obj.name, 'error': error }) return def sync_library(self): """Sync Trakt library.""" if app.TRAKT_SYNC and app.USE_TRAKT: log.debug('Syncing Trakt collection') if self._get_show_collection(): self.add_episode_trakt_collection() if app.TRAKT_SYNC_REMOVE: self.remove_episode_trakt_collection() log.debug('Synced Trakt collection') def remove_episode_trakt_collection(self, filter_show=None): """Remove episode from trakt collection. For episodes that no longer have a media file (location) :param filter_show: optional. Only remove episodes from trakt collection for given shows """ if app.TRAKT_SYNC_REMOVE and app.TRAKT_SYNC and app.USE_TRAKT: params = [] main_db_con = db.DBConnection() statuses = [DOWNLOADED, ARCHIVED] sql_selection = 'SELECT s.indexer, s.startyear, s.indexer_id, s.show_name,' \ 'e.season, e.episode, e.status ' \ 'FROM tv_episodes AS e, tv_shows AS s WHERE e.indexer = s.indexer AND ' \ 's.indexer_id = e.showid and e.location = "" ' \ 'AND e.status in ({0})'.format(','.join(['?'] * len(statuses))) if filter_show: sql_selection += ' AND s.indexer_id = ? AND e.indexer = ?' params = [filter_show.series_id, filter_show.indexer] sql_result = main_db_con.select(sql_selection, statuses + params) if sql_result: trakt_data = [] for cur_episode in sql_result: # Check if TRAKT supports that indexer if not get_trakt_indexer(cur_episode['indexer']): continue if self._check_list(indexer=cur_episode['indexer'], indexer_id=cur_episode['indexer_id'], season=cur_episode['season'], episode=cur_episode['episode'], list_type='Collection'): log.info("Removing episode '{show}' {ep} from Trakt collection", { 'show': cur_episode['show_name'], 'ep': episode_num(cur_episode['season'], cur_episode['episode']) }) title = get_title_without_year(cur_episode['show_name'], cur_episode['startyear']) trakt_data.append((cur_episode['indexer_id'], cur_episode['indexer'], title, cur_episode['startyear'], cur_episode['season'], cur_episode['episode'])) if trakt_data: try: data = self.trakt_bulk_data_generate(trakt_data) self._request('sync/collection/remove', data, method='POST') self._get_show_collection() except (TraktException, AuthException, TokenExpiredException) as error: log.info('Unable to remove episodes from Trakt collection. Error: {error!r}', { 'error': error }) def add_episode_trakt_collection(self): """Add all existing episodes to Trakt collections. For episodes that have a media file (location) """ if app.TRAKT_SYNC and app.USE_TRAKT: main_db_con = db.DBConnection() statuses = [DOWNLOADED, ARCHIVED] sql_selection = 'SELECT s.indexer, s.startyear, s.indexer_id, s.show_name, e.season, e.episode ' \ 'FROM tv_episodes AS e, tv_shows AS s ' \ 'WHERE e.indexer = s.indexer AND s.indexer_id = e.showid ' \ "AND e.status in ({0}) AND e.location <> ''".format(','.join(['?'] * len(statuses))) sql_result = main_db_con.select(sql_selection, statuses) if sql_result: trakt_data = [] for cur_episode in sql_result: # Check if TRAKT supports that indexer if not get_trakt_indexer(cur_episode['indexer']): continue if not self._check_list(indexer=cur_episode['indexer'], indexer_id=cur_episode['indexer_id'], season=cur_episode['season'], episode=cur_episode['episode'], list_type='Collection'): log.info("Adding episode '{show}' {ep} to Trakt collection", { 'show': cur_episode['show_name'], 'ep': episode_num(cur_episode['season'], cur_episode['episode']) }) title = get_title_without_year(cur_episode['show_name'], cur_episode['startyear']) trakt_data.append((cur_episode['indexer_id'], cur_episode['indexer'], title, cur_episode['startyear'], cur_episode['season'], cur_episode['episode'])) if trakt_data: try: data = self.trakt_bulk_data_generate(trakt_data) self._request('sync/collection', data, method='POST') self._get_show_collection() except (TraktException, AuthException, TokenExpiredException) as error: log.info('Unable to add episodes to Trakt collection. Error: {error!r}', {'error': error}) def sync_watchlist(self): """Sync Trakt watchlist.""" if app.TRAKT_SYNC_WATCHLIST and app.USE_TRAKT: log.debug('Syncing Trakt Watchlist') self.remove_from_library() if self._get_show_watchlist(): log.debug('Syncing shows with Trakt watchlist') self.add_show_watchlist() self.sync_trakt_shows() if self._get_episode_watchlist(): log.debug('Syncing episodes with Trakt watchlist') self.remove_episode_watchlist() self.add_episode_watchlist() self.sync_trakt_episodes() log.debug('Synced Trakt watchlist') def remove_episode_watchlist(self): """Remove episode from Trakt watchlist.""" if app.TRAKT_SYNC_WATCHLIST and app.USE_TRAKT: main_db_con = db.DBConnection() statuses = [DOWNLOADED, ARCHIVED] sql_selection = 'SELECT s.indexer, s.startyear, e.showid, s.show_name, e.season, e.episode ' \ 'FROM tv_episodes AS e, tv_shows AS s ' \ 'WHERE e.indexer = s.indexer ' \ 'AND s.indexer_id = e.showid AND e.status in ({0})'.format(','.join(['?'] * len(statuses))) sql_result = main_db_con.select(sql_selection, statuses) if sql_result: trakt_data = [] for cur_episode in sql_result: # Check if TRAKT supports that indexer if not get_trakt_indexer(cur_episode['indexer']): continue if self._check_list(indexer=cur_episode['indexer'], indexer_id=cur_episode['showid'], season=cur_episode['season'], episode=cur_episode['episode']): log.info("Removing episode '{show}' {ep} from Trakt watchlist", { 'show': cur_episode['show_name'], 'ep': episode_num(cur_episode['season'], cur_episode['episode']) }) title = get_title_without_year(cur_episode['show_name'], cur_episode['startyear']) trakt_data.append((cur_episode['showid'], cur_episode['indexer'], title, cur_episode['startyear'], cur_episode['season'], cur_episode['episode'])) if trakt_data: try: data = self.trakt_bulk_data_generate(trakt_data) self._request('sync/watchlist/remove', data, method='POST') self._get_episode_watchlist() except (TraktException, AuthException, TokenExpiredException) as error: log.info('Unable to remove episodes from Trakt watchlist. Error: {error!r}', { 'error': error }) def add_episode_watchlist(self): """Add episode to Tratk watchlist.""" if app.TRAKT_SYNC_WATCHLIST and app.USE_TRAKT: main_db_con = db.DBConnection() statuses = [SNATCHED, SNATCHED_BEST, SNATCHED_PROPER, WANTED] sql_selection = 'SELECT s.indexer, s.startyear, e.showid, s.show_name, e.season, e.episode ' \ 'FROM tv_episodes AS e, tv_shows AS s ' \ 'WHERE e.indexer = s.indexer AND s.indexer_id = e.showid AND s.paused = 0 ' \ 'AND e.status in ({0})'.format(','.join(['?'] * len(statuses))) sql_result = main_db_con.select(sql_selection, statuses) if sql_result: trakt_data = [] for cur_episode in sql_result: # Check if TRAKT supports that indexer if not get_trakt_indexer(cur_episode['indexer']): continue if not self._check_list(indexer=cur_episode['indexer'], indexer_id=cur_episode['showid'], season=cur_episode['season'], episode=cur_episode['episode']): log.info("Adding episode '{show}' {ep} to Trakt watchlist", { 'show': cur_episode['show_name'], 'ep': episode_num(cur_episode['season'], cur_episode['episode']) }) title = get_title_without_year(cur_episode['show_name'], cur_episode['startyear']) trakt_data.append((cur_episode['showid'], cur_episode['indexer'], title, cur_episode['startyear'], cur_episode['season'], cur_episode['episode'])) if trakt_data: try: data = self.trakt_bulk_data_generate(trakt_data) self._request('sync/watchlist', data, method='POST') self._get_episode_watchlist() except (TraktException, AuthException, TokenExpiredException) as error: log.info('Unable to add episode to Trakt watchlist. Error: {error!r}', { 'error': error }) def add_show_watchlist(self): """Add show to Trakt watchlist. It will add all shows from Medusa library """ if app.TRAKT_SYNC_WATCHLIST and app.USE_TRAKT: if app.showList: trakt_data = [] for show_obj in app.showList: if not self._check_list(show_obj=show_obj, list_type='Show'): log.info("Adding show '{show}' to Trakt watchlist", {'show': show_obj.name}) title = get_title_without_year(show_obj.name, show_obj.start_year) show_el = {'title': title, 'year': show_obj.start_year, 'ids': {}} trakt_data.append(show_el) if trakt_data: try: data = {'shows': trakt_data} self._request('sync/watchlist', data, method='POST') except (TraktException, AuthException, TokenExpiredException) as error: log.info('Unable to add shows to Trakt watchlist. Error: {error!r}', {'error': error}) self._get_show_watchlist() def remove_from_library(self): """Remove show from Medusa library is if ended/completed.""" if app.TRAKT_SYNC_WATCHLIST and app.USE_TRAKT and app.TRAKT_REMOVE_SHOW_FROM_APPLICATION: log.debug('Retrieving ended/completed shows to remove from Medusa') if app.showList: for show in app.showList: if show.status == 'Ended': trakt_id = show.externals.get('trakt_id', None) if not (trakt_id or show.imdb_id): log.info("Unable to check Trakt progress for show '{show}' " 'because Trakt|IMDB ID is missing. Skipping', {'show': show.name}) continue try: progress = self._request('shows/{0}/progress/watched'.format(trakt_id or show.imdb_id)) except (TraktException, AuthException, TokenExpiredException) as error: log.info("Unable to check if show '{show}' is ended/completed. Error: {error!r}", { 'show': show.name, 'error': error }) continue else: if progress.get('aired', True) == progress.get('completed', False): app.show_queue_scheduler.action.removeShow(show, full=True) log.info("Show '{show}' has being queued to be removed from Medusa library", { 'show': show.name }) def sync_trakt_shows(self): """Sync Trakt shows watchlist.""" if not self.show_watchlist: log.info('No shows found in your Trakt watchlist. Nothing to sync') else: trakt_default_indexer = int(app.TRAKT_DEFAULT_INDEXER) for watchlisted_show in self.show_watchlist: trakt_show = watchlisted_show['show'] if trakt_show['year'] and trakt_show['ids']['slug'].endswith(str(trakt_show['year'])): show_name = '{title} ({year})'.format(title=trakt_show['title'], year=trakt_show['year']) else: show_name = trakt_show['title'] show = None indexer = None for i in indexerConfig: trakt_indexer = get_trakt_indexer(i) indexer_id = trakt_show['ids'].get(trakt_indexer, -1) indexer = indexerConfig[i]['id'] show = Show.find_by_id(app.showList, indexer, indexer_id) if show: break if not show: # If can't find with available indexers try IMDB trakt_indexer = get_trakt_indexer(EXTERNAL_IMDB) indexer_id = trakt_show['ids'].get(trakt_indexer, -1) show = Show.find_by_id(app.showList, EXTERNAL_IMDB, indexer_id) if not show: # If can't find with available indexers try TRAKT trakt_indexer = get_trakt_indexer(EXTERNAL_TRAKT) indexer_id = trakt_show['ids'].get(trakt_indexer, -1) show = Show.find_by_id(app.showList, EXTERNAL_TRAKT, indexer_id) if show: continue indexer_id = trakt_show['ids'].get(get_trakt_indexer(trakt_default_indexer), -1) if int(app.TRAKT_METHOD_ADD) != 2: self.add_show(trakt_default_indexer, indexer_id, show_name, SKIPPED) else: self.add_show(trakt_default_indexer, indexer_id, show_name, WANTED) if int(app.TRAKT_METHOD_ADD) == 1 and indexer: new_show = Show.find_by_id(app.showList, indexer, indexer_id) if new_show: set_episode_to_wanted(new_show, 1, 1) else: log.warning('Unable to find the new added show.' 'Pilot will be set to wanted in the next Trakt run') self.todoWanted.append(indexer_id) log.debug('Synced shows with Trakt watchlist') def sync_trakt_episodes(self): """Sync Trakt episodes watchlist.""" if not self.episode_watchlist: log.info('No episodes found in your Trakt watchlist. Nothing to sync') return added_shows = [] trakt_default_indexer = int(app.TRAKT_DEFAULT_INDEXER) for watchlist_item in self.episode_watchlist: trakt_show = watchlist_item['show'] trakt_episode = watchlist_item['episode'].get('number', -1) trakt_season = watchlist_item['episode'].get('season', -1) show = None for i in indexerConfig: trakt_indexer = get_trakt_indexer(i) indexer_id = trakt_show['ids'].get(trakt_indexer, -1) indexer = indexerConfig[i]['id'] show = Show.find_by_id(app.showList, indexer, indexer_id) if show: break if not show: # If can't find with available indexers try IMDB trakt_indexer = get_trakt_indexer(EXTERNAL_IMDB) indexer_id = trakt_show['ids'].get(trakt_indexer, -1) show = Show.find_by_id(app.showList, EXTERNAL_IMDB, indexer_id) if not show: # If can't find with available indexers try TRAKT trakt_indexer = get_trakt_indexer(EXTERNAL_TRAKT) indexer_id = trakt_show['ids'].get(trakt_indexer, -1) show = Show.find_by_id(app.showList, EXTERNAL_TRAKT, indexer_id) # If can't find show add with default trakt indexer if not show: indexer_id = trakt_show['ids'].get(get_trakt_indexer(trakt_default_indexer), -1) # Only add show if we didn't added it before if indexer_id not in added_shows: self.add_show(trakt_default_indexer, indexer_id, trakt_show['title'], SKIPPED) added_shows.append(indexer_id) elif not trakt_season == 0 and not show.paused: set_episode_to_wanted(show, trakt_season, trakt_episode) log.debug('Synced episodes with Trakt watchlist') @staticmethod def add_show(indexer, indexer_id, show_name, status): """Add a new show with default settings.""" if not Show.find_by_id(app.showList, EXTERNAL_IMDB, indexer_id): root_dirs = app.ROOT_DIRS location = root_dirs[int(root_dirs[0]) + 1] if root_dirs else None if location: log.info("Adding show '{show}' using indexer: '{indexer_name}' and ID: {id}", { 'show': show_name, 'indexer_name': indexerConfig[indexer]['identifier'], 'id': indexer_id }) app.show_queue_scheduler.action.addShow(indexer, indexer_id, None, default_status=status, quality=int(app.QUALITY_DEFAULT), season_folders=int(app.SEASON_FOLDERS_DEFAULT), paused=app.TRAKT_START_PAUSED, default_status_after=status, root_dir=location) tries = 0 while tries < 3: if Show.find_by_id(app.showList, indexer, indexer_id): return # Wait before show get's added and refreshed time.sleep(60) tries += 1 log.warning("Error creating show '{show}. Please check logs' ", { 'show': show_name }) return else: log.warning("Error creating show '{show}' folder. No default root directory", { 'show': show_name }) return def manage_new_show(self, show): """Set episodes to wanted for the recently added show.""" log.debug("Checking for wanted episodes for show '{show}' in Trakt watchlist", {'show': show.name}) episodes = [i for i in self.todoWanted if i[0] == show.indexerid] for episode in episodes: self.todoWanted.remove(episode) set_episode_to_wanted(show, episode[1], episode[2]) def _check_list(self, show_obj=None, indexer=None, indexer_id=None, season=None, episode=None, list_type=None): """Check if we can find the show in the Trakt watchlist|collection list.""" if 'Collection' == list_type: trakt_indexer = get_trakt_indexer(indexer) for collected_show in self.collection_list: if not collected_show['show']['ids'].get(trakt_indexer, '') == indexer_id: continue if 'seasons' in collected_show: for season_item in collected_show['seasons']: for episode_item in season_item['episodes']: trakt_season = season_item['number'] trakt_episode = episode_item['number'] if trakt_season == season and trakt_episode == episode: return True else: return False elif 'Show' == list_type: trakt_indexer = get_trakt_indexer(show_obj.indexer) for watchlisted_show in self.show_watchlist: if watchlisted_show['show']['ids'].get(trakt_indexer) == show_obj.indexerid or \ watchlisted_show['show']['ids'].get(get_trakt_indexer(EXTERNAL_IMDB), '') == show_obj.imdb_id: return True return False else: trakt_indexer = get_trakt_indexer(indexer) for watchlisted_episode in self.episode_watchlist: if watchlisted_episode['episode'].get('season', -1) == season and \ watchlisted_episode['episode'].get('number', -1) == episode and \ watchlisted_episode['show']['ids'].get(trakt_indexer, '') == indexer_id: return True return False def _get_show_watchlist(self): """Get shows watchlist.""" try: self.show_watchlist = self._request('sync/watchlist/shows') except (TraktException, AuthException, TokenExpiredException) as error: log.info(u'Unable to retrieve shows from Trakt watchlist. Error: {error!r}', {'error': error}) return False return True def _get_episode_watchlist(self): """Get episodes watchlist.""" try: self.episode_watchlist = self._request('sync/watchlist/episodes') except (TraktException, AuthException, TokenExpiredException) as error: log.info(u'Unable to retrieve episodes from Trakt watchlist. Error: {error!r}', {'error': error}) return False return True def _get_show_collection(self): """Get show collection.""" try: self.collection_list = self._request('sync/collection/shows') except (TraktException, AuthException, TokenExpiredException) as error: log.info('Unable to retrieve shows from Trakt collection. Error: {error!r}', {'error': error}) return False return True @staticmethod def trakt_bulk_data_generate(trakt_data): """Build the JSON structure to send back to Trakt.""" unique_shows = {} unique_seasons = {} for indexer_id, indexer, show_name, start_year, season, episode in trakt_data: if indexer_id not in unique_shows: unique_shows[indexer_id] = {'title': show_name, 'year': start_year, 'ids': {}, 'seasons': []} unique_shows[indexer_id]['ids'][get_trakt_indexer(indexer)] = indexer_id unique_seasons[indexer_id] = [] # Get the unique seasons per Show for indexer_id, indexer, show_name, start_year, season, episode in trakt_data: if season not in unique_seasons[indexer_id]: unique_seasons[indexer_id].append(season) # build the query show_list = [] seasons_list = {} for searched_show in unique_shows: show = [] seasons_list[searched_show] = [] for searched_season in unique_seasons[searched_show]: episodes_list = [] for indexer_id, indexer, show_name, start_year, season, episode in trakt_data: if season == searched_season and indexer_id == searched_show: episodes_list.append({'number': episode}) show = unique_shows[searched_show] show['seasons'].append({'number': searched_season, 'episodes': episodes_list}) if show: show_list.append(show) post_data = {'shows': show_list} return post_data
def fetch_popular_shows(self, page_url=None, trakt_list=None): # pylint: disable=too-many-nested-blocks,too-many-branches """Get a list of popular shows from different Trakt lists based on a provided trakt_list. :param page_url: the page url opened to the base api url, for retreiving a specific list :param trakt_list: a description of the trakt list :return: A list of RecommendedShow objects, an empty list of none returned :throw: ``Exception`` if an Exception is thrown not handled by the libtrats exceptions """ trending_shows = [] removed_from_medusa = [] # Create a trakt settings dict trakt_settings = { 'trakt_api_secret': app.TRAKT_API_SECRET, 'trakt_api_key': app.TRAKT_API_KEY, 'trakt_access_token': app.TRAKT_ACCESS_TOKEN, 'trakt_refresh_token': app.TRAKT_REFRESH_TOKEN } trakt_api = TraktApi(timeout=app.TRAKT_TIMEOUT, ssl_verify=app.SSL_VERIFY, **trakt_settings) try: # pylint: disable=too-many-nested-blocks not_liked_show = '' if app.TRAKT_ACCESS_TOKEN != '': library_shows = self.fetch_and_refresh_token(trakt_api, 'sync/watched/shows?extended=noseasons') + \ self.fetch_and_refresh_token(trakt_api, 'sync/collection/shows?extended=full') medusa_shows = [ show.indexerid for show in app.showList if show.indexerid ] removed_from_medusa = [ lshow['show']['ids']['tvdb'] for lshow in library_shows if lshow['show']['ids']['tvdb'] not in medusa_shows ] if app.TRAKT_BLACKLIST_NAME is not None and app.TRAKT_BLACKLIST_NAME: not_liked_show = trakt_api.request( 'users/' + app.TRAKT_USERNAME + '/lists/' + app.TRAKT_BLACKLIST_NAME + '/items') or [] else: logger.log('Trakt blacklist name is empty', logger.DEBUG) if trakt_list not in ['recommended', 'newshow', 'newseason']: limit_show = '?limit=' + str(100 + len(not_liked_show)) + '&' else: limit_show = '?' shows = self.fetch_and_refresh_token( trakt_api, page_url + limit_show + 'extended=full,images') or [] for show in shows: try: if 'show' not in show: show['show'] = show if not_liked_show: if show['show']['ids']['tvdb'] not in ( show['show']['ids']['tvdb'] for show in not_liked_show if show['type'] == 'show'): trending_shows.append( self._create_recommended_show(show)) else: trending_shows.append( self._create_recommended_show(show)) except MultipleShowObjectsException: continue blacklist = app.TRAKT_BLACKLIST_NAME not in '' except TraktException as e: logger.log('Could not connect to Trakt service: %s' % ex(e), logger.WARNING) raise return blacklist, trending_shows, removed_from_medusa