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)) logger.log( "Error while adding show '{0}' to trakt blacklist: {1}".format( show_name, e), logger.WARNING)
def test_notify(username, blacklist_name=None): """ Sends a test notification to trakt with the given authentication info and returns a boolean representing success. api: The api string to use username: The username to use blacklist_name: slug of trakt list used to hide not interested show Returns: True if the request succeeded, False otherwise """ try: trakt_settings = {'trakt_api_secret': app.TRAKT_API_SECRET, 'trakt_api_key': app.TRAKT_API_KEY, 'trakt_access_token': app.TRAKT_ACCESS_TOKEN} trakt_api = TraktApi(app.SSL_VERIFY, app.TRAKT_TIMEOUT, **trakt_settings) trakt_api.validate_account() if blacklist_name and blacklist_name is not None: trakt_lists = trakt_api.request('users/' + username + '/lists') found = False for trakt_list in trakt_lists: if trakt_list['ids']['slug'] == blacklist_name: return 'Test notice sent successfully to Trakt' if not found: return "Trakt blacklist doesn't exists" else: return 'Test notice sent successfully to Trakt' except (TraktException, AuthException, ServerBusy) as trakt_ex: logger.log('Could not connect to Trakt service: {0}'.format(ex(trakt_ex)), logger.WARNING) return 'Test notice failed to Trakt: {0}'.format(ex(trakt_ex))
def test_notify(username, blacklist_name=None): """Send a test notification to trakt with the given authentication info and returns a boolean. api: The api string to use username: The username to use blacklist_name: slug of trakt list used to hide not interested show Returns: True if the request succeeded, False otherwise """ try: 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) trakt_api.validate_account() if blacklist_name and blacklist_name is not None: trakt_lists = trakt_api.request('users/' + username + '/lists') found = False for trakt_list in trakt_lists: if trakt_list['ids']['slug'] == blacklist_name: return 'Test notice sent successfully to Trakt' if not found: return "Trakt blacklist doesn't exists" else: return 'Test notice sent successfully to Trakt' except (TokenExpiredException, TraktException, AuthException) as error: log.warning('Unable to test TRAKT: {0}', error.message) return 'Test notice failed to Trakt: {0}'.format(error.message)
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 update_library(ep_obj): """ Sends a request to trakt indicating that the given episode is part of our library. ep_obj: The TVEpisode object to add to trakt """ trakt_id = app.indexerApi(ep_obj.show.indexer).config['trakt_id'] # 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_api = TraktApi(app.SSL_VERIFY, app.TRAKT_TIMEOUT, **trakt_settings) if app.USE_TRAKT: try: # URL parameters data = { 'shows': [ { 'title': ep_obj.show.name, 'year': ep_obj.show.startyear, 'ids': {}, } ] } if trakt_id == 'tvdb_id': data['shows'][0]['ids']['tvdb'] = ep_obj.show.indexerid else: data['shows'][0]['ids']['tvrage'] = ep_obj.show.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 (TraktException, AuthException, ServerBusy) as trakt_ex: logger.log('Could not connect to Trakt service: {0}'.format(ex(trakt_ex)), logger.WARNING)
class TraktChecker(object): def __init__(self): trakt_settings = {'trakt_api_key': app.TRAKT_API_KEY, 'trakt_api_secret': app.TRAKT_API_SECRET, 'trakt_access_token': app.TRAKT_ACCESS_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): # pylint: disable=unused-argument 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.split('|')) < 2: logger.log('No default root directory', logger.WARNING) ui.notifications.error('Unable to add show', 'You do not have any default root directory configured. ' 'Please configure in general settings!') return try: self.sync_watchlist() except Exception: logger.log(traceback.format_exc(), logger.DEBUG) try: # sync Trakt library with medusa library self.sync_library() except Exception: logger.log(traceback.format_exc(), logger.DEBUG) self.amActive = False def find_show(self, indexerid): try: trakt_library = self.trakt_api.request('sync/collection/shows') or [] if self.trakt_api.access_token_refreshed: app.TRAKT_ACCESS_TOKEN = self.trakt_api.access_token if not trakt_library: logger.log('No shows found in your library, aborting library update', logger.DEBUG) return trakt_show = [x for x in trakt_library if int(indexerid) in [int(x['show']['ids']['tvdb'] or 0), int(x['show']['ids']['tvrage'] or 0)]] except TraktException as e: logger.log('Could not connect to Trakt. Aborting library check. Error: {0}'.format(repr(e)), logger.WARNING) return trakt_show if trakt_show else None def remove_show_trakt_library(self, show_obj): """Remove Show from trakt collections""" if self.find_show(show_obj.indexerid): trakt_id = app.indexerApi(show_obj.indexer).config['trakt_id'] # URL parameters data = { 'shows': [ { 'title': show_obj.name, 'year': show_obj.startyear, 'ids': {} } ] } if trakt_id == 'tvdb_id': data['shows'][0]['ids']['tvdb'] = show_obj.indexerid else: data['shows'][0]['ids']['tvrage'] = show_obj.indexerid logger.log('Removing {0} from Trakt library'.format(show_obj.name), logger.DEBUG) # Remove all episodes from the Trakt collection for this show try: self.remove_episode_trakt_collection(filter_show=show_obj) except TraktException as e: logger.log('Could not connect to Trakt. Aborting removing episodes for show {0} from Trakt library. Error: {1}'. format(show_obj.name, repr(e)), logger.WARNING) try: self.trakt_api.request('sync/collection/remove', data, method='POST') except TraktException as e: logger.log('Could not connect to Trakt. Aborting removing show {0} from Trakt library. Error: {1}'. format(show_obj.name, repr(e)), logger.WARNING) def add_show_trakt_library(self, show_obj): """ Sends a request to trakt indicating that the given show and all its episodes is part of our library. show_obj: The TVShow object to add to trakt """ data = {} if not self.find_show(show_obj.indexerid): trakt_id = app.indexerApi(show_obj.indexer).config['trakt_id'] # URL parameters data = { 'shows': [ { 'title': show_obj.name, 'year': show_obj.startyear, 'ids': {} } ] } if trakt_id == 'tvdb_id': data['shows'][0]['ids']['tvdb'] = show_obj.indexerid else: data['shows'][0]['ids']['tvrage'] = show_obj.indexerid if data: logger.log('Adding {0} to Trakt library'.format(show_obj.name), logger.DEBUG) try: self.trakt_api.request('sync/collection', data, method='POST') except TraktException as e: logger.log('Could not connect to Trakt. Aborting adding show {0} to Trakt library. Error: {1}'.format(show_obj.name, repr(e)), logger.WARNING) return def sync_library(self): if app.TRAKT_SYNC and app.USE_TRAKT: logger.log('Starting to sync Medusa with Trakt collection', logger.DEBUG) if self._get_show_collection(): self.add_episode_trakt_collection() if app.TRAKT_SYNC_REMOVE: self.remove_episode_trakt_collection() def remove_episode_trakt_collection(self, filter_show=None): if app.TRAKT_SYNC_REMOVE and app.TRAKT_SYNC and app.USE_TRAKT: params = [] main_db_con = db.DBConnection() sql_selection = b'select tv_shows.indexer, tv_shows.startyear, showid, show_name, season, episode, tv_episodes.status,' \ b'tv_episodes.location from tv_episodes, tv_shows where tv_shows.indexer_id = tv_episodes.showid' if filter_show: sql_selection += b' AND tv_shows.indexer_id = ? AND tv_shows.indexer = ?' params = [filter_show.indexerid, filter_show.indexer] episodes = main_db_con.select(sql_selection, params) if episodes: trakt_data = [] for cur_episode in episodes: trakt_id = app.indexerApi(cur_episode[b'indexer']).config['trakt_id'] if self._check_list(trakt_id, cur_episode[b'showid'], cur_episode[b'season'], cur_episode[b'episode'], List='Collection'): if cur_episode[b'location'] == '': logger.log('Removing Episode {show} {ep} from collection'.format (show=cur_episode[b'show_name'], ep=episode_num(cur_episode[b'season'], cur_episode[b'episode'])), logger.DEBUG) trakt_data.append((cur_episode[b'showid'], cur_episode[b'indexer'], cur_episode[b'show_name'], cur_episode[b'startyear'], cur_episode[b'season'], cur_episode[b'episode'])) if trakt_data: try: data = self.trakt_bulk_data_generate(trakt_data) self.trakt_api.request('sync/collection/remove', data, method='POST') self._get_show_collection() except TraktException as e: logger.log('Could not connect to Trakt. Error: {0}'.format(ex(e)), logger.WARNING) def add_episode_trakt_collection(self): """Add all episodes from local library to Trakt collections. Enabled through app.TRAKT_SYNC_WATCHLIST setting""" if app.TRAKT_SYNC and app.USE_TRAKT: main_db_con = db.DBConnection() selection_status = ['?' for _ in Quality.DOWNLOADED + Quality.ARCHIVED] sql_selection = b'select tv_shows.indexer, tv_shows.startyear, showid, show_name, season, ' \ b'episode from tv_episodes,tv_shows where tv_shows.indexer_id = tv_episodes.showid ' \ b"and tv_episodes.status in ({0}) and tv_episodes.location <> ''".format(','.join(selection_status)) episodes = main_db_con.select(sql_selection, Quality.DOWNLOADED + Quality.ARCHIVED) if episodes: trakt_data = [] for cur_episode in episodes: trakt_id = app.indexerApi(cur_episode[b'indexer']).config['trakt_id'] if not self._check_list(trakt_id, cur_episode[b'showid'], cur_episode[b'season'], cur_episode[b'episode'], List='Collection'): logger.log('Adding Episode {show} {ep} to collection'.format (show=cur_episode[b'show_name'], ep=episode_num(cur_episode[b'season'], cur_episode[b'episode'])), logger.DEBUG) trakt_data.append((cur_episode[b'showid'], cur_episode[b'indexer'], cur_episode[b'show_name'], cur_episode[b'startyear'], cur_episode[b'season'], cur_episode[b'episode'])) if trakt_data: try: data = self.trakt_bulk_data_generate(trakt_data) self.trakt_api.request('sync/collection', data, method='POST') self._get_show_collection() except TraktException as e: logger.log('Could not connect to Trakt. Error: {0}'.format(ex(e)), logger.WARNING) def sync_watchlist(self): if app.TRAKT_SYNC_WATCHLIST and app.USE_TRAKT: logger.log('Starting to sync Medusa with Trakt Watchlist', logger.DEBUG) self.remove_from_library() if self._get_show_watchlist(): logger.log('Syncing shows with Trakt watchlist', logger.DEBUG) self.add_show_watchlist() self.fetch_trakt_shows() if self._get_episode_watchlist(): logger.log('Syncing episodes with Trakt watchlist', logger.DEBUG) self.remove_episode_watchlist() self.add_episode_watchlist() self.fetch_trakt_episodes() logger.log('Medusa is synced with Trakt watchlist', logger.DEBUG) def remove_episode_watchlist(self): if app.TRAKT_SYNC_WATCHLIST and app.USE_TRAKT: main_db_con = db.DBConnection() sql_selection = b'select tv_shows.indexer, tv_shows.startyear, showid, show_name, season, episode, ' \ b'tv_episodes.status from tv_episodes,tv_shows where tv_shows.indexer_id = tv_episodes.showid' episodes = main_db_con.select(sql_selection) if episodes: trakt_data = [] for cur_episode in episodes: trakt_id = app.indexerApi(cur_episode[b'indexer']).config['trakt_id'] if self._check_list(trakt_id, cur_episode[b'showid'], cur_episode[b'season'], cur_episode[b'episode']): if cur_episode[b'status'] not in Quality.SNATCHED + Quality.SNATCHED_PROPER + [UNKNOWN] + [WANTED]: logger.log('Removing Episode {show} {ep} from watchlist'.format (show=cur_episode[b'show_name'], ep=episode_num(cur_episode[b'season'], cur_episode[b'episode'])), logger.DEBUG) trakt_data.append((cur_episode[b'showid'], cur_episode[b'indexer'], cur_episode[b'show_name'], cur_episode[b'startyear'], cur_episode[b'season'], cur_episode[b'episode'])) if trakt_data: try: data = self.trakt_bulk_data_generate(trakt_data) self.trakt_api.request('sync/watchlist/remove', data, method='POST') self._get_episode_watchlist() except TraktException as e: logger.log('Could not connect to Trakt. Error: {0}'.format(ex(e)), logger.WARNING) def add_episode_watchlist(self): if app.TRAKT_SYNC_WATCHLIST and app.USE_TRAKT: main_db_con = db.DBConnection() selection_status = [b'?' for _ in Quality.SNATCHED + Quality.SNATCHED_PROPER + [WANTED]] sql_selection = b'select tv_shows.indexer, tv_shows.startyear, showid, show_name, season, episode from tv_episodes, ' \ b'tv_shows where tv_shows.indexer_id = tv_episodes.showid and tv_episodes.status in ({0})'.format(b','.join(selection_status)) episodes = main_db_con.select(sql_selection, Quality.SNATCHED + Quality.SNATCHED_PROPER + [WANTED]) if episodes: trakt_data = [] for cur_episode in episodes: trakt_id = app.indexerApi(cur_episode[b'indexer']).config['trakt_id'] if not self._check_list(trakt_id, cur_episode[b'showid'], cur_episode[b'season'], cur_episode[b'episode']): logger.log('Adding Episode {show} {ep} to watchlist'.format (show=cur_episode[b'show_name'], ep=episode_num(cur_episode[b'season'], cur_episode[b'episode'])), logger.DEBUG) trakt_data.append((cur_episode[b'showid'], cur_episode[b'indexer'], cur_episode[b'show_name'], cur_episode[b'startyear'], cur_episode[b'season'], cur_episode[b'episode'])) if trakt_data: try: data = self.trakt_bulk_data_generate(trakt_data) self.trakt_api.request('sync/watchlist', data, method='POST') self._get_episode_watchlist() except TraktException as e: logger.log('Could not connect to Trakt. Error: {0}'.format(ex(e)), logger.WARNING) def add_show_watchlist(self): if app.TRAKT_SYNC_WATCHLIST and app.USE_TRAKT: logger.log('Syncing shows to Trakt watchlist', logger.DEBUG) if app.showList: trakt_data = [] for show_obj in app.showList: trakt_id = app.indexerApi(show_obj.indexer).config['trakt_id'] if not self._check_list(trakt_id, show_obj.indexerid, 0, 0, List='Show'): logger.log('Adding Show {0} with ID: {1} to Trakt watchlist'.format(show_obj.name, show_obj.indexerid), logger.DEBUG) show_el = {'title': show_obj.name, 'year': show_obj.startyear, 'ids': {}} if trakt_id == 'tvdb_id': show_el['ids']['tvdb'] = show_obj.indexerid else: show_el['ids']['tvrage'] = show_obj.indexerid trakt_data.append(show_el) if trakt_data: try: data = {'shows': trakt_data} self.trakt_api.request('sync/watchlist', data, method='POST') self._get_show_watchlist() except TraktException as e: logger.log('Could not connect to Trakt. Error: {0}'.format(ex(e)), logger.WARNING) def remove_from_library(self): if app.TRAKT_SYNC_WATCHLIST and app.USE_TRAKT and app.TRAKT_REMOVE_SHOW_FROM_APPLICATION: logger.log('Retrieving ended/completed shows to remove from Medusa', logger.DEBUG) if app.showList: for show in app.showList: if show.status == 'Ended': if not show.imdbid: logger.log('Could not check trakt progress for {0} because the imdb id is missing from tvdb data, skipping'.format (show.name), logger.WARNING) continue try: progress = self.trakt_api.request('shows/{0}/progress/watched'.format(show.imdbid)) or [] if self.trakt_api.access_token_refreshed: app.TRAKT_ACCESS_TOKEN = self.trakt_api.access_token except TraktException as e: logger.log('Could not connect to Trakt. Aborting removing show {0} from Medusa. Error: {1}'.format(show.name, repr(e)), logger.WARNING) continue if not progress: continue if progress.get('aired', True) == progress.get('completed', False): app.showQueueScheduler.action.removeShow(show, full=True) logger.log('Show {0} has been removed from Medusa'.format(show.name), logger.DEBUG) def fetch_trakt_shows(self): if not self.show_watchlist: logger.log('No shows found in your watchlist, aborting watchlist update', logger.DEBUG) else: indexer = int(app.TRAKT_DEFAULT_INDEXER) trakt_id = app.indexerApi(indexer).config['trakt_id'] for watchlisted_show in self.show_watchlist[trakt_id]: indexer_id = int(watchlisted_show) show_obj = self.show_watchlist[trakt_id][watchlisted_show] if show_obj['year'] and show_obj['slug'].endswith(str(show_obj['year'])): show_name = '{0} ({1})'.format(show_obj['title'], show_obj['year']) else: show_name = show_obj['title'] if int(app.TRAKT_METHOD_ADD) != 2: self.add_show(indexer, indexer_id, show_name, SKIPPED) else: self.add_show(indexer, indexer_id, show_name, WANTED) if int(app.TRAKT_METHOD_ADD) == 1: new_show = Show.find(app.showList, indexer_id) if new_show: setEpisodeToWanted(new_show, 1, 1) else: self.todoWanted.append(indexer_id, 1, 1) def fetch_trakt_episodes(self): """ Sets episodes to wanted that are in trakt watchlist """ logger.log(u"Retrieving episodes to sync with Trakt episode's watchlist", logger.DEBUG) if not self.episode_watchlist: logger.log('No episode found in your watchlist, aborting episode update', logger.DEBUG) return managed_show = [] indexer = int(app.TRAKT_DEFAULT_INDEXER) trakt_id = app.indexerApi(indexer).config['trakt_id'] for watchlist_item in self.episode_watchlist[trakt_id]: indexer_id = int(watchlist_item) show = self.episode_watchlist[trakt_id][watchlist_item] new_show = Show.find(app.showList, indexer_id) try: if not new_show: if indexer_id not in managed_show: self.add_show(indexer, indexer_id, show['title'], SKIPPED) managed_show.append(indexer_id) for season_item in show['seasons']: season = int(season_item) for episode_item in show['seasons'][season_item]['episodes']: self.todoWanted.append((indexer_id, season, int(episode_item))) else: if new_show.indexer == indexer: for season_item in show['seasons']: season = int(season_item) for episode_item in show['seasons'][season_item]['episodes']: setEpisodeToWanted(new_show, season, int(episode_item)) except TypeError: logger.log('Could not parse the output from trakt for {0} '.format(show['title']), logger.DEBUG) @staticmethod def add_show(indexer, indexer_id, show_name, status): """ Adds a new show with the default settings """ if not Show.find(app.showList, int(indexer_id)): root_dirs = app.ROOT_DIRS.split('|') location = root_dirs[int(root_dirs[0]) + 1] if root_dirs else None if location: logger.log('Adding show {0} with ID: {1}'.format(show_name, indexer_id)) app.showQueueScheduler.action.addShow(indexer, indexer_id, None, default_status=status, quality=int(app.QUALITY_DEFAULT), flatten_folders=int(app.FLATTEN_FOLDERS_DEFAULT), paused=app.TRAKT_START_PAUSED, default_status_after=status, root_dir=location) else: logger.log('There was an error creating the show, no root directory setting found', logger.WARNING) return def manage_new_show(self, show): logger.log('Checking if trakt watchlist wants to search for episodes from new show {0}'.format(show.name), logger.DEBUG) episodes = [i for i in self.todoWanted if i[0] == show.indexerid] for episode in episodes: self.todoWanted.remove(episode) setEpisodeToWanted(show, episode[1], episode[2]) def _check_list(self, trakt_id, showid, season, episode, List=None): # pylint: disable=too-many-arguments """ Check in the Watchlist or collection list for Show Is the Show, Season and Episode in the trakt_id list (tvdb / tvrage) """ if 'Collection' == List: try: if self.collection_list[trakt_id][showid]['seasons'][season]['episodes'][episode] == episode: return True except KeyError: return False elif 'Show' == List: try: if self.show_watchlist[trakt_id][showid]['id'] == showid: return True except KeyError: return False else: try: if self.episode_watchlist[trakt_id][showid]['seasons'][season]['episodes'][episode] == episode: return True except KeyError: return False def _get_show_watchlist(self): """ Get Watchlist and parse once into addressable structure """ try: self.show_watchlist = {'tvdb_id': {}, 'tvrage_id': {}} trakt_show_watchlist = self.trakt_api.request('sync/watchlist/shows') if self.trakt_api.access_token_refreshed: app.TRAKT_ACCESS_TOKEN = self.trakt_api.access_token tvdb_id = 'tvdb' tvrage_id = 'tvrage' for watchlist_item in trakt_show_watchlist: tvdb = True if watchlist_item['show']['ids']['tvdb'] else False tvrage = True if watchlist_item['show']['ids']['tvrage'] else False title = watchlist_item['show']['title'] year = watchlist_item['show']['year'] slug = watchlist_item['show']['ids']['slug'] if tvdb: showid = watchlist_item['show']['ids'][tvdb_id] self.show_watchlist['{0}_id'.format(tvdb_id)][showid] = {'id': showid, 'title': title, 'year': year, 'slug': slug} if tvrage: showid = watchlist_item['show']['ids'][tvrage_id] self.show_watchlist['{0}_id'.format(tvrage_id)][showid] = {'id': showid, 'title': title, 'year': year, 'slug': slug} except TraktException as e: logger.log(u"Could not connect to Trakt. Unable to retrieve show's watchlist: {0!r}".format(e), logger.WARNING) return False return True def _get_episode_watchlist(self): """ Get Watchlist and parse once into addressable structure """ try: self.episode_watchlist = {'tvdb_id': {}, 'tvrage_id': {}} trakt_episode_watchlist = self.trakt_api.request('sync/watchlist/episodes') if self.trakt_api.access_token_refreshed: app.TRAKT_ACCESS_TOKEN = self.trakt_api.access_token tvdb_id = 'tvdb' tvrage_id = 'tvrage' for watchlist_item in trakt_episode_watchlist: tvdb = True if watchlist_item['show']['ids']['tvdb'] else False tvrage = True if watchlist_item['show']['ids']['tvrage'] else False title = watchlist_item['show']['title'] year = watchlist_item['show']['year'] season = watchlist_item['episode']['season'] episode = watchlist_item['episode']['number'] if tvdb: showid = watchlist_item['show']['ids'][tvdb_id] if showid not in self.episode_watchlist['{0}_id'.format(tvdb_id)].keys(): self.episode_watchlist['{0}_id'.format(tvdb_id)][showid] = {'id': showid, 'title': title, 'year': year, 'seasons': {}} if season not in self.episode_watchlist['{0}_id'.format(tvdb_id)][showid]['seasons'].keys(): self.episode_watchlist['{0}_id'.format(tvdb_id)][showid]['seasons'][season] = {'s': season, 'episodes': {}} if episode not in self.episode_watchlist['{0}_id'.format(tvdb_id)][showid]['seasons'][season]['episodes'].keys(): self.episode_watchlist['{0}_id'.format(tvdb_id)][showid]['seasons'][season]['episodes'][episode] = episode if tvrage: showid = watchlist_item['show']['ids'][tvrage_id] if showid not in self.episode_watchlist['{0}_id'.format(tvrage_id)].keys(): self.episode_watchlist['{0}_id'.format(tvrage_id)][showid] = {'id': showid, 'title': title, 'year': year, 'seasons': {}} if season not in self.episode_watchlist['{0}_id'.format(tvrage_id)][showid]['seasons'].keys(): self.episode_watchlist['{0}_id'.format(tvrage_id)][showid]['seasons'][season] = {'s': season, 'episodes': {}} if episode not in self.episode_watchlist['{0}_id'.format(tvrage_id)][showid]['seasons'][season]['episodes'].keys(): self.episode_watchlist['{0}_id'.format(tvrage_id)][showid]['seasons'][season]['episodes'][episode] = episode except TraktException as e: logger.log(u"Could not connect to Trakt. Unable to retrieve episode's watchlist: {0!r}".format(e), logger.WARNING) return False return True def _get_show_collection(self): # pylint: disable=too-many-branches """ Get Collection and parse once into addressable structure """ try: self.collection_list = {'tvdb_id': {}, 'tvrage_id': {}} logger.log('Getting Show Collection', logger.DEBUG) trakt_collection = self.trakt_api.request('sync/collection/shows') if self.trakt_api.access_token_refreshed: app.TRAKT_ACCESS_TOKEN = self.trakt_api.access_token tvdb_id = 'tvdb' tvrage_id = 'tvrage' for watchlist_item in trakt_collection: tvdb = True if watchlist_item['show']['ids']['tvdb'] else False tvrage = True if watchlist_item['show']['ids']['tvrage'] else False title = watchlist_item['show']['title'] year = watchlist_item['show']['year'] if 'seasons' in watchlist_item: for season_item in watchlist_item['seasons']: for episode_item in season_item['episodes']: season = season_item['number'] episode = episode_item['number'] if tvdb: showid = watchlist_item['show']['ids'][tvdb_id] if showid not in self.collection_list['{0}_id'.format(tvdb_id)].keys(): self.collection_list['{0}_id'.format(tvdb_id)][showid] = {'id': showid, 'title': title, 'year': year, 'seasons': {}} if season not in self.collection_list['{0}_id'.format(tvdb_id)][showid]['seasons'].keys(): self.collection_list['{0}_id'.format(tvdb_id)][showid]['seasons'][season] = {'s': season, 'episodes': {}} if episode not in self.collection_list['{0}_id'.format(tvdb_id)][showid]['seasons'][season]['episodes'].keys(): self.collection_list['{0}_id'.format(tvdb_id)][showid]['seasons'][season]['episodes'][episode] = episode if tvrage: showid = watchlist_item['show']['ids'][tvrage_id] if showid not in self.collection_list[tvrage_id + '_id'].keys(): self.collection_list[tvrage_id + '_id'][showid] = {'id': showid, 'title': title, 'year': year, 'seasons': {}} if season not in self.collection_list[tvrage_id + '_id'][showid]['seasons'].keys(): self.collection_list[tvrage_id + '_id'][showid]['seasons'][season] = {'s': season, 'episodes': {}} if episode not in self.collection_list[tvrage_id + '_id'][showid]['seasons'][season]['episodes'].keys(): self.collection_list[tvrage_id + '_id'][showid]['seasons'][season]['episodes'][episode] = episode except TraktException as e: logger.log(u"Could not connect to Trakt. Unable to retrieve show's collection: {0!r}".format(e), logger.WARNING) return False return True @staticmethod def trakt_bulk_data_generate(data): # pylint: disable=too-many-locals """ Build the JSON structure to send back to Trakt """ uniqueShows = {} uniqueSeasons = {} for showid, indexerid, show_name, startyear, season, episode in data: if showid not in uniqueShows: uniqueShows[showid] = {'title': show_name, 'year': startyear, 'ids': {}, 'seasons': []} trakt_id = app.indexerApi(indexerid).config['trakt_id'] if trakt_id == 'tvdb_id': uniqueShows[showid]['ids']['tvdb'] = showid else: uniqueShows[showid]['ids']['tvrage'] = showid uniqueSeasons[showid] = [] # Get the unique seasons per Show for showid, indexerid, show_name, startyear, season, episode in data: if season not in uniqueSeasons[showid]: uniqueSeasons[showid].append(season) # build the query showList = [] seasonsList = {} for searchedShow in uniqueShows: seasonsList[searchedShow] = [] for searchedSeason in uniqueSeasons[searchedShow]: episodesList = [] for showid, indexerid, show_name, startyear, season, episode in data: if season == searchedSeason and showid == searchedShow: episodesList.append({'number': episode}) show = uniqueShows[searchedShow] show['seasons'].append({'number': searchedSeason, 'episodes': episodesList}) showList.append(show) post_data = {'shows': showList} return post_data
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: if show['show']['ids']['tvdb'] in ( s['show']['ids']['tvdb'] for s in not_liked_show if s['type'] == 'show'): continue else: trending_shows.append( self._create_recommended_show( show, storage_key=b'trakt_{0}'.format( show['show']['ids']['trakt']))) except MultipleShowObjectsException: continue # Update the dogpile index. This will allow us to retrieve all stored dogpile shows from the dbm. update_recommended_series_cache_index( 'trakt', [binary_type(s.series_id) for s in trending_shows]) 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 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 e: log.info( 'Unable to retrieve shows from Trakt collection. Error: {error}', {'error': e.message}) 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 e: log.info( "Unable to remove all episodes from show '{show}' from Trakt library. Error: {error}", { 'show': show_obj.name, 'error': e.message }) try: self._request('sync/collection/remove', data, method='POST') except (TraktException, AuthException, TokenExpiredException) as e: log.info( "Unable to remove show '{show}' from Trakt library. Error: {error}", { 'show': show_obj.name, 'error': e.message }) 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 e: log.info( "Unable to add show '{show}' to Trakt library. Error: {error}", { 'show': show_obj.name, 'error': e.message }) 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 = b'SELECT s.indexer, s.startyear, s.indexer_id, s.show_name,' \ b'e.season, e.episode, e.status ' \ b'FROM tv_episodes AS e, tv_shows AS s WHERE e.indexer = s.indexer AND ' \ b's.indexer_id = e.showid and e.location = "" ' \ b'AND e.status in ({0})'.format(','.join(['?'] * len(statuses))) if filter_show: sql_selection += b' 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) episodes = [dict(e) for e in sql_result] if episodes: trakt_data = [] for cur_episode in episodes: # Check if TRAKT supports that indexer if not get_trakt_indexer(cur_episode[b'indexer']): continue if self._check_list(indexer=cur_episode[b'indexer'], indexer_id=cur_episode[b'indexer_id'], season=cur_episode[b'season'], episode=cur_episode[b'episode'], list_type='Collection'): log.info( "Removing episode '{show}' {ep} from Trakt collection", { 'show': cur_episode[b'show_name'], 'ep': episode_num(cur_episode[b'season'], cur_episode[b'episode']) }) title = get_title_without_year( cur_episode[b'show_name'], cur_episode[b'startyear']) trakt_data.append( (cur_episode[b'indexer_id'], cur_episode[b'indexer'], title, cur_episode[b'startyear'], cur_episode[b'season'], cur_episode[b'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 e: log.info( 'Unable to remove episodes from Trakt collection. Error: {error}', {'error': e.message}) 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 = b'SELECT s.indexer, s.startyear, s.indexer_id, s.show_name, e.season, e.episode ' \ b'FROM tv_episodes AS e, tv_shows AS s ' \ b'WHERE e.indexer = s.indexer AND s.indexer_id = e.showid ' \ b"AND e.status in ({0}) AND e.location <> ''".format(','.join(['?'] * len(statuses))) sql_result = main_db_con.select(sql_selection, statuses) episodes = [dict(e) for e in sql_result] if episodes: trakt_data = [] for cur_episode in episodes: # Check if TRAKT supports that indexer if not get_trakt_indexer(cur_episode[b'indexer']): continue if not self._check_list( indexer=cur_episode[b'indexer'], indexer_id=cur_episode[b'indexer_id'], season=cur_episode[b'season'], episode=cur_episode[b'episode'], list_type='Collection'): log.info( "Adding episode '{show}' {ep} to Trakt collection", { 'show': cur_episode[b'show_name'], 'ep': episode_num(cur_episode[b'season'], cur_episode[b'episode']) }) title = get_title_without_year( cur_episode[b'show_name'], cur_episode[b'startyear']) trakt_data.append( (cur_episode[b'indexer_id'], cur_episode[b'indexer'], title, cur_episode[b'startyear'], cur_episode[b'season'], cur_episode[b'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 e: log.info( 'Unable to add episodes to Trakt collection. Error: {error}', {'error': e.message}) 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 = b'SELECT s.indexer, s.startyear, e.showid, s.show_name, e.season, e.episode ' \ b'FROM tv_episodes AS e, tv_shows AS s ' \ b'WHERE e.indexer = s.indexer ' \ b'AND s.indexer_id = e.showid AND e.status in ({0})'.format(','.join(['?'] * len(statuses))) sql_result = main_db_con.select(sql_selection, statuses) episodes = [dict(i) for i in sql_result] if episodes: trakt_data = [] for cur_episode in episodes: # Check if TRAKT supports that indexer if not get_trakt_indexer(cur_episode[b'indexer']): continue if self._check_list(indexer=cur_episode[b'indexer'], indexer_id=cur_episode[b'showid'], season=cur_episode[b'season'], episode=cur_episode[b'episode']): log.info( "Removing episode '{show}' {ep} from Trakt watchlist", { 'show': cur_episode[b'show_name'], 'ep': episode_num(cur_episode[b'season'], cur_episode[b'episode']) }) title = get_title_without_year( cur_episode[b'show_name'], cur_episode[b'startyear']) trakt_data.append( (cur_episode[b'showid'], cur_episode[b'indexer'], title, cur_episode[b'startyear'], cur_episode[b'season'], cur_episode[b'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 e: log.info( 'Unable to remove episodes from Trakt watchlist. Error: {error}', {'error': e.message}) 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 = b'SELECT s.indexer, s.startyear, e.showid, s.show_name, e.season, e.episode ' \ b'FROM tv_episodes AS e, tv_shows AS s ' \ b'WHERE e.indexer = s.indexer AND s.indexer_id = e.showid AND s.paused = 0 ' \ b'AND e.status in ({0})'.format(','.join(['?'] * len(statuses))) sql_result = main_db_con.select(sql_selection, statuses) episodes = [dict(i) for i in sql_result] if episodes: trakt_data = [] for cur_episode in episodes: # Check if TRAKT supports that indexer if not get_trakt_indexer(cur_episode[b'indexer']): continue if not self._check_list(indexer=cur_episode[b'indexer'], indexer_id=cur_episode[b'showid'], season=cur_episode[b'season'], episode=cur_episode[b'episode']): log.info( "Adding episode '{show}' {ep} to Trakt watchlist", { 'show': cur_episode[b'show_name'], 'ep': episode_num(cur_episode[b'season'], cur_episode[b'episode']) }) title = get_title_without_year( cur_episode[b'show_name'], cur_episode[b'startyear']) trakt_data.append( (cur_episode[b'showid'], cur_episode[b'indexer'], title, cur_episode[b'startyear'], cur_episode[b'season'], cur_episode[b'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 e: log.info( 'Unable to add episode to Trakt watchlist. Error: {error}', {'error': e.message}) 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 e: log.info( 'Unable to add shows to Trakt watchlist. Error: {error}', {'error': e.message}) 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 e: log.info( "Unable to check if show '{show}' is ended/completed. Error: {error}", { 'show': show.name, 'error': e.message }) 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 e: log.info( u'Unable to retrieve shows from Trakt watchlist. Error: {error}', {'error': e.message}) 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 e: log.info( u'Unable to retrieve episodes from Trakt watchlist. Error: {error}', {'error': e.message}) 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 e: log.info( 'Unable to retrieve shows from Trakt collection. Error: {error}', {'error': e.message}) 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_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
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 update_watchlist(show_obj=None, s=None, e=None, data_show=None, data_episode=None, update='add'): """ Sends a request to trakt indicating that the given episode is part of our library. show_obj: The TVShow 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 """ 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: trakt_id = indexerApi(show_obj.indexer).config['trakt_id'] data = { 'shows': [{ 'title': show_obj.name, 'year': show_obj.startyear, 'ids': {}, }] } if trakt_id == 'tvdb_id': data['shows'][0]['ids']['tvdb'] = show_obj.indexerid else: data['shows'][0]['ids']['tvrage'] = show_obj.indexerid elif data_show is not None: data.update(data_show) else: logger.log( "There's a coding problem contact developer. " "It's needed to be provided at least one of the two: data_show or show_obj", logger.WARNING) 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 (TraktException, AuthException, ServerBusy) as trakt_ex: logger.log( 'Could not connect to Trakt service: {0}'.format( ex(trakt_ex)), logger.WARNING) 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