Пример #1
0
 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
Пример #2
0
    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})
Пример #3
0
    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
Пример #4
0
    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()
Пример #5
0
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
Пример #6
0
    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)
Пример #7
0
    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
Пример #8
0
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
Пример #9
0
    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