Example #1
0
def send_nzb_post(params, nzb):
    """
    Sends an NZB to SABnzbd via the API.

    :param params: Prepared post parameters.
    :param nzb: The NZBSearchResult object to send to SAB
    :return: result of the communication with sabnzbd (True/False)
    """

    log.info('Sending NZB to SABnzbd using the post multipart/form data.')
    url = urljoin(app.SAB_HOST, 'api')
    params['mode'] = 'addfile'
    files = {
        'name': nzb.extra_info[0]
    }

    data = session.params
    data.update(params)
    data['nzbname'] = sanitize_filename(nzb.name)

    # Empty session.params, because else these are added to the url.
    session.params = {}

    data = session.get_json(url, method='POST', data=data, files=files, verify=False)
    if not data:
        log.info('Error connecting to sab, no data returned')
    else:
        result, text = _check_sab_response(data)
        log.debug('Result text from SAB: {0}', text)
        del text
        return result
Example #2
0
    def _make_url(self, result):
        """Return url if result is a magnet link."""
        urls = []
        filename = ''

        if not result or not result.url:
            return urls, filename

        urls = [result.url]
        result_name = sanitize_filename(result.name)

        # TODO: Remove this in future versions, kept for the warning
        # Some NZB providers (e.g. Jackett) can also download torrents
        # A similar check is performed for NZB splitting in medusa/search/core.py @ search_providers()
        if (result.url.endswith(GenericProvider.TORRENT)
                or result.url.startswith('magnet:')
            ) and self.provider_type == GenericProvider.NZB:
            filename = join(app.TORRENT_DIR, result_name + '.torrent')
            log.warning(
                'Using Jackett providers as Newznab providers is deprecated!'
                ' Switch them to Jackett providers as soon as possible.')
        else:
            filename = join(self._get_storage_dir(),
                            result_name + '.' + self.provider_type)

        return urls, filename
Example #3
0
def send_nzb_post(params, nzb):
    """
    Sends an NZB to SABnzbd via the API.

    :param params: Prepared post parameters.
    :param nzb: The NZBSearchResult object to send to SAB
    :return: result of the communication with sabnzbd (True/False)
    """

    log.info('Sending NZB to SABnzbd using the post multipart/form data.')
    url = urljoin(app.SAB_HOST, 'api')
    params['mode'] = 'addfile'
    files = {'name': nzb.extra_info[0]}

    data = session.params
    data.update(params)
    data['nzbname'] = sanitize_filename(nzb.name)

    # Empty session.params, because else these are added to the url.
    session.params = {}

    response = session.post(url, data=data, files=files, verify=False)

    try:
        data = response.json()
    except ValueError:
        log.info('Error connecting to sab, no data returned')
    else:
        log.debug('Result text from SAB: {0}', data)
        result, text = _check_sab_response(data)
        del text
        return result
Example #4
0
def test_sanitize_filename(value, expected):
    # Given

    # When
    actual = sut.sanitize_filename(value)

    # Then
    assert expected == actual
Example #5
0
 def _save_magnet(self, result):
     # TODO: check if file exists
     #logger.WARNING
     result_name = sanitize_filename(result.name)
     filename = join(app.TORRENT_DIR, result_name + '.magnet')
     fo = open(filename, "wb")
     fo.write(result.url.encode())  # encode() to bytes
     fo.close()
     log.info(u'Saved magnet to {0}'.format(filename))
     return True
    def _make_url(self, result):
        """Return url if result is a magnet link."""
        urls = []
        filename = ''

        if not result or not result.url:
            return urls, filename

        if result.url.startswith('magnet:'):
            try:
                info_hash = re.findall(r'urn:btih:([\w]{32,40})',
                                       result.url)[0].upper()

                try:
                    torrent_name = re.findall('dn=([^&]+)', result.url)[0]
                except Exception:
                    torrent_name = 'NO_DOWNLOAD_NAME'

                if len(info_hash) == 32:
                    info_hash = b16encode(b32decode(info_hash)).upper()

                if not info_hash:
                    log.error(
                        'Unable to extract torrent hash from magnet: {0}',
                        result.url)
                    return urls, filename

                urls = [
                    cache_url.format(info_hash=info_hash,
                                     torrent_name=torrent_name)
                    for cache_url in self.bt_cache_urls
                ]
                shuffle(urls)
            except Exception:
                log.error(
                    'Unable to extract torrent hash or name from magnet: {0}',
                    result.url)
                return urls, filename
        else:
            # Required for Jackett providers that use magnet redirects
            # See: https://github.com/pymedusa/Medusa/issues/3435
            if self.kind() == 'TorznabProvider':
                redirect_url = self.get_redirect_url(result.url)
                if redirect_url != result.url:
                    result.url = redirect_url
                    return self._make_url(result)

            urls = [result.url]

        result_name = sanitize_filename(result.name)
        filename = join(self._get_storage_dir(),
                        result_name + '.' + self.provider_type)

        return urls, filename
Example #7
0
    def _make_url(self, result):
        """Return url if result is a magnet link."""
        if not result:
            return '', ''

        urls = []
        filename = ''

        if result.url.startswith('magnet:'):
            try:
                info_hash = re.findall(r'urn:btih:([\w]{32,40})',
                                       result.url)[0].upper()

                try:
                    torrent_name = re.findall('dn=([^&]+)', result.url)[0]
                except Exception:
                    torrent_name = 'NO_DOWNLOAD_NAME'

                if len(info_hash) == 32:
                    info_hash = b16encode(b32decode(info_hash)).upper()

                if not info_hash:
                    log.error(
                        'Unable to extract torrent hash from magnet: {0}',
                        result.url)
                    return urls, filename

                urls = [
                    x.format(info_hash=info_hash, torrent_name=torrent_name)
                    for x in self.bt_cache_urls
                ]
                shuffle(urls)
            except Exception:
                log.error(
                    'Unable to extract torrent hash or name from magnet: {0}',
                    result.url)
                return urls, filename
        else:
            urls = [result.url]

        result_name = sanitize_filename(result.name)

        # Some NZB providers (e.g. Jackett) can also download torrents
        if (result.url.endswith(GenericProvider.TORRENT)
                or result.url.startswith('magnet:')
            ) and self.provider_type == GenericProvider.NZB:
            filename = join(app.TORRENT_DIR, result_name + '.torrent')
        else:
            filename = join(self._get_storage_dir(),
                            result_name + '.' + self.provider_type)

        return urls, filename
Example #8
0
    def download_result(self, result):
        """
        Download result from provider.

        This is used when a blackhole is used for sending the nzb file to the nzb client.
        For now the url and the post data is stored as one string in the db, using a pipe (|) to separate them.

        :param result: A SearchResult object.
        :return: The result of the nzb download (True/False).
        """
        if not self.login():
            return False

        result_name = sanitize_filename(result.name)
        filename = join(self._get_storage_dir(),
                        result_name + '.' + self.provider_type)

        if result.url.startswith('http'):
            self.session.headers.update(
                {'Referer': '/'.join(result.url.split('/')[:3]) + '/'})

        log.info('Downloading {result} from {provider} at {url}', {
            'result': result.name,
            'provider': self.name,
            'url': result.url
        })

        verify = False if self.public else None

        url, data = result.url.split('|')

        data = {
            data.split('=')[1]: 'on',
            'action': 'nzb',
        }

        if download_file(url,
                         filename,
                         method='POST',
                         data=data,
                         session=self.session,
                         headers=self.headers,
                         verify=verify):

            if self._verify_download(filename):
                log.info('Saved {result} to {location}', {
                    'result': result.name,
                    'location': filename
                })
                return True

        return False
Example #9
0
    def _make_url(self, result):
        """Return url if result is a Magnet URI."""
        urls = []
        filename = ''

        if not result or not result.url:
            return urls, filename

        if result.url.startswith('magnet:'):
            try:
                torrent_name = self._get_torrent_name_from_magnet(result.url)
                info_hash = self._get_info_from_magnet(result.url)

                if not info_hash:
                    log.error(
                        'Unable to extract torrent hash from magnet: {0}',
                        result.url)
                    return urls, filename

                urls = [
                    cache_url.format(info_hash=info_hash,
                                     torrent_name=torrent_name)
                    for cache_url in self.bt_cache_urls
                ]
                shuffle(urls)
            except Exception:
                log.error(
                    'Unable to extract torrent hash or name from magnet: {0}',
                    result.url)
                return urls, filename
        else:
            # Required for Jackett providers that use magnet redirects
            # See: https://github.com/pymedusa/Medusa/issues/3435
            if self.kind() == 'TorznabProvider':
                redirect_url = self.get_redirect_url(result.url)
                if redirect_url != result.url:
                    result.url = redirect_url
                    return self._make_url(result)

            urls = [result.url]

        result_name = sanitize_filename(result.name)
        filename = join(self._get_storage_dir(), result_name)

        return urls, filename
Example #10
0
    def test_encoding(self):
        root_dir = 'C:\\Temp\\TV'
        strings = [u'Les Enfants De La T\xe9l\xe9', u'RT� One']

        app.SYS_ENCODING = None

        try:
            locale.setlocale(locale.LC_ALL, "")
            app.SYS_ENCODING = locale.getpreferredencoding()
        except (locale.Error, IOError):
            pass

        # For OSes that are poorly configured I'll just randomly force UTF-8
        if not app.SYS_ENCODING or app.SYS_ENCODING in ('ANSI_X3.4-1968', 'US-ASCII', 'ASCII'):
            app.SYS_ENCODING = 'UTF-8'

        for test in strings:
            show_dir = os.path.join(root_dir, sanitize_filename(test))
            self.assertTrue(isinstance(show_dir, text_type))
Example #11
0
    def test_encoding(self):
        root_dir = 'C:\\Temp\\TV'
        strings = [u'Les Enfants De La T\xe9l\xe9', u'RT� One']

        app.SYS_ENCODING = None

        try:
            locale.setlocale(locale.LC_ALL, "")
            app.SYS_ENCODING = locale.getpreferredencoding()
        except (locale.Error, IOError):
            pass

        # For OSes that are poorly configured I'll just randomly force UTF-8
        if not app.SYS_ENCODING or app.SYS_ENCODING in ('ANSI_X3.4-1968',
                                                        'US-ASCII', 'ASCII'):
            app.SYS_ENCODING = 'UTF-8'

        for test in strings:
            show_dir = os.path.join(root_dir, sanitize_filename(test))
            self.assertTrue(isinstance(show_dir, text_type))
Example #12
0
    def resource_check_for_existing_folder(self):
        """Check if the show selected, already has a folder located in one of the root dirs."""
        show_dir = self.get_argument('showdir' '').strip()
        root_dir = self.get_argument('rootdir', '').strip()
        title = self.get_argument('title', '').strip()

        if not show_dir and not (root_dir and title):
            return self._bad_request({
                'showDir':
                show_dir,
                'rootDir':
                root_dir,
                'title':
                title,
                'error':
                'missing information to determin location'
            })

        path = ''
        if show_dir:
            path = ensure_text(show_dir)
        elif root_dir and title:
            path = os.path.join(root_dir, sanitize_filename(title))

        path_info = {'path': path}
        path_info['pathExists'] = os.path.exists(path)
        if path_info['pathExists']:
            for cur_provider in itervalues(app.metadata_provider_dict):
                (series_id, series_name,
                 indexer) = cur_provider.retrieveShowMetadata(
                     path_info['path'])
                if all((series_id, series_name, indexer)):
                    path_info['metadata'] = {
                        'seriesId': int(series_id),
                        'seriesName': series_name,
                        'indexer': int(indexer)
                    }
                    break

        return self._ok(data={'pathInfo': path_info})
Example #13
0
    def _make_url(self, result):
        """Return url if result is a magnet link."""
        urls = []
        filename = ''

        if not result or not result.url:
            return urls, filename

        urls = [result.url]
        result_name = sanitize_filename(result.name)

        # TODO: Remove this in future versions, kept for the warning
        # Some NZB providers (e.g. Jackett) can also download torrents
        # A similar check is performed for NZB splitting in medusa/search/core.py @ search_providers()
        if (result.url.endswith(GenericProvider.TORRENT) or
                result.url.startswith('magnet:')) and self.provider_type == GenericProvider.NZB:
            filename = join(app.TORRENT_DIR, result_name + '.torrent')
            log.warning('Using Jackett providers as Newznab providers is deprecated!'
                        ' Switch them to Jackett providers as soon as possible.')
        else:
            filename = join(self._get_storage_dir(), result_name + '.' + self.provider_type)

        return urls, filename
Example #14
0
    def test_sanitize_filename(self):
        test_cases = {
            None: '',
            42: '',
            '': '',
            'filename': 'filename',
            'fi\\le/na*me': 'fi-le-na-me',
            'fi:le"na<me': 'filename',
            'fi>le|na?me': 'filename',
            ' . file\u2122name. .': 'file-u2122name',  # pylint: disable=anomalous-unicode-escape-in-string
        }

        unicode_test_cases = {
            u'': u'',
            u'filename': u'filename',
            u'fi\\le/na*me': u'fi-le-na-me',
            u'fi:le"na<me': u'filename',
            u'fi>le|na?me': u'filename',
            u' . file\u2122name. .': u'filename',
        }

        for tests in test_cases, unicode_test_cases:
            for (filename, result) in iteritems(tests):
                self.assertEqual(sanitize_filename(filename), result)
Example #15
0
 def sanitizeFileName(name):
     return sanitize_filename(name)
Example #16
0
    def addNewShow(self,
                   whichSeries=None,
                   indexer_lang=None,
                   rootDir=None,
                   defaultStatus=None,
                   quality_preset=None,
                   allowed_qualities=None,
                   preferred_qualities=None,
                   season_folders=None,
                   subtitles=None,
                   fullShowPath=None,
                   other_shows=None,
                   skipShow=None,
                   providedIndexer=None,
                   anime=None,
                   scene=None,
                   blacklist=None,
                   whitelist=None,
                   defaultStatusAfter=None):
        """
        Receive tvdb id, dir, and other options and create a show from them. If extra show dirs are
        provided then it forwards back to newShow, if not it goes to /home.
        """
        provided_indexer = providedIndexer

        indexer_lang = app.INDEXER_DEFAULT_LANGUAGE if not indexer_lang else indexer_lang

        # grab our list of other dirs if given
        if not other_shows:
            other_shows = []
        elif not isinstance(other_shows, list):
            other_shows = [other_shows]

        def finishAddShow():
            # if there are no extra shows then go home
            if not other_shows:
                return json_redirect('/home/')

            # go to add the next show
            return json_redirect(
                '/addShows/newShow/',
                [('show_to_add' if not i else 'other_shows', cur_dir)
                 for i, cur_dir in enumerate(other_shows)])

        # if we're skipping then behave accordingly
        if skipShow:
            return finishAddShow()

        # sanity check on our inputs
        if (not rootDir and not fullShowPath) or not whichSeries:
            return 'Missing params, no Indexer ID or folder:{series!r} and {root!r}/{path!r}'.format(
                series=whichSeries, root=rootDir, path=fullShowPath)

        # figure out what show we're adding and where
        series_pieces = whichSeries.split('|')
        if (whichSeries and rootDir) or (whichSeries and fullShowPath
                                         and len(series_pieces) > 1):
            if len(series_pieces) < 6:
                logger.log(
                    u'Unable to add show due to show selection. Not enough arguments: %s'
                    % (repr(series_pieces)), logger.ERROR)
                ui.notifications.error(
                    'Unknown error. Unable to add show due to problem with show selection.'
                )
                return json_redirect('/addShows/existingShows/')

            indexer = int(series_pieces[1])
            indexer_id = int(series_pieces[3])
            show_name = series_pieces[4]
        else:
            # if no indexer was provided use the default indexer set in General settings
            if not provided_indexer:
                provided_indexer = app.INDEXER_DEFAULT

            indexer = int(provided_indexer)
            indexer_id = int(whichSeries)
            show_name = os.path.basename(os.path.normpath(fullShowPath))

        # use the whole path if it's given, or else append the show name to the root dir to get the full show path
        if fullShowPath:
            show_dir = os.path.normpath(fullShowPath)
        else:
            show_dir = os.path.join(rootDir, sanitize_filename(show_name))

        # blanket policy - if the dir exists you should have used 'add existing show' numbnuts
        if os.path.isdir(show_dir) and not fullShowPath:
            ui.notifications.error(
                'Unable to add show',
                'Folder {path} exists already'.format(path=show_dir))
            return json_redirect('/addShows/existingShows/')

        # don't create show dir if config says not to
        if app.ADD_SHOWS_WO_DIR:
            logger.log(
                u'Skipping initial creation of {path} due to config.ini setting'
                .format(path=show_dir))
        else:
            dir_exists = helpers.make_dir(show_dir)
            if not dir_exists:
                logger.log(
                    u'Unable to create the folder {path}, can\'t add the show'.
                    format(path=show_dir), logger.ERROR)
                ui.notifications.error(
                    'Unable to add show',
                    'Unable to create the folder {path}, can\'t add the show'.
                    format(path=show_dir))
                # Don't redirect to default page because user wants to see the new show
                return json_redirect('/home/')
            else:
                helpers.chmod_as_parent(show_dir)

        # prepare the inputs for passing along
        scene = config.checkbox_to_value(scene)
        anime = config.checkbox_to_value(anime)
        season_folders = config.checkbox_to_value(season_folders)
        subtitles = config.checkbox_to_value(subtitles)

        if whitelist:
            whitelist = short_group_names(whitelist)
        if blacklist:
            blacklist = short_group_names(blacklist)

        if not allowed_qualities:
            allowed_qualities = []
        if not preferred_qualities or try_int(quality_preset, None):
            preferred_qualities = []
        if not isinstance(allowed_qualities, list):
            allowed_qualities = [allowed_qualities]
        if not isinstance(preferred_qualities, list):
            preferred_qualities = [preferred_qualities]
        new_quality = Quality.combine_qualities(
            [int(q) for q in allowed_qualities],
            [int(q) for q in preferred_qualities])

        # add the show
        app.show_queue_scheduler.action.addShow(indexer, indexer_id, show_dir,
                                                int(defaultStatus),
                                                new_quality, season_folders,
                                                indexer_lang, subtitles, anime,
                                                scene, None, blacklist,
                                                whitelist,
                                                int(defaultStatusAfter))
        ui.notifications.message(
            'Show added',
            'Adding the specified show into {path}'.format(path=show_dir))

        return finishAddShow()
Example #17
0
    def addNewShow(self, whichSeries=None, indexer_lang=None, rootDir=None, defaultStatus=None, quality_preset=None,
                   allowed_qualities=None, preferred_qualities=None, season_folders=None, subtitles=None,
                   fullShowPath=None, other_shows=None, skipShow=None, providedIndexer=None, anime=None,
                   scene=None, blacklist=None, whitelist=None, defaultStatusAfter=None):
        """
        Receive tvdb id, dir, and other options and create a show from them. If extra show dirs are
        provided then it forwards back to newShow, if not it goes to /home.
        """
        provided_indexer = providedIndexer

        indexer_lang = app.INDEXER_DEFAULT_LANGUAGE if not indexer_lang else indexer_lang

        # grab our list of other dirs if given
        if not other_shows:
            other_shows = []
        elif not isinstance(other_shows, list):
            other_shows = [other_shows]

        other_shows = decode_shows(other_shows)

        def finishAddShow():
            # if there are no extra shows then go home
            if not other_shows:
                return json_response(redirect='/home/')

            # go to add the next show
            return json_response(
                redirect='/addShows/newShow/',
                params=[
                    ('show_to_add' if not i else 'other_shows', cur_dir)
                    for i, cur_dir in enumerate(other_shows)
                ]
            )

        # if we're skipping then behave accordingly
        if skipShow:
            return finishAddShow()

        # sanity check on our inputs
        if (not rootDir and not fullShowPath) or not whichSeries:
            error_msg = 'Missing params, no Indexer ID or folder: {series!r} and {root!r}/{path!r}'.format(
                series=whichSeries, root=rootDir, path=fullShowPath)
            log.error(error_msg)
            return json_response(
                result=False,
                message=error_msg,
                redirect='/home/'
            )

        # figure out what show we're adding and where
        series_pieces = whichSeries.split('|')
        if (whichSeries and rootDir) or (whichSeries and fullShowPath and len(series_pieces) > 1):
            if len(series_pieces) < 6:
                log.error('Unable to add show due to show selection. Not enough arguments: {pieces!r}',
                          {'pieces': series_pieces})
                ui.notifications.error('Unknown error. Unable to add show due to problem with show selection.')
                return json_response(
                    result=False,
                    message='Unable to add show due to show selection. Not enough arguments: {0!r}'.format(series_pieces),
                    redirect='/addShows/existingShows/'
                )

            indexer = int(series_pieces[1])
            indexer_id = int(series_pieces[3])
            show_name = series_pieces[4]
        else:
            # if no indexer was provided use the default indexer set in General settings
            if not provided_indexer:
                provided_indexer = app.INDEXER_DEFAULT

            indexer = int(provided_indexer)
            indexer_id = int(whichSeries)
            show_name = os.path.basename(os.path.normpath(fullShowPath))

        # use the whole path if it's given, or else append the show name to the root dir to get the full show path
        if fullShowPath:
            show_dir = os.path.normpath(fullShowPath)
        else:
            show_dir = os.path.join(rootDir, sanitize_filename(show_name))

        # blanket policy - if the dir exists you should have used 'add existing show' numbnuts
        if os.path.isdir(show_dir) and not fullShowPath:
            ui.notifications.error('Unable to add show', 'Folder {path} exists already'.format(path=show_dir))
            return json_response(
                result=False,
                message='Unable to add show: Folder {path} exists already'.format(path=show_dir),
                redirect='/addShows/existingShows/'
            )

        # don't create show dir if config says not to
        if app.ADD_SHOWS_WO_DIR:
            log.info('Skipping initial creation of {path} due to config.ini setting',
                     {'path': show_dir})
        else:
            dir_exists = helpers.make_dir(show_dir)
            if not dir_exists:
                log.error("Unable to create the folder {path}, can't add the show",
                          {'path': show_dir})
                ui.notifications.error('Unable to add show',
                                       'Unable to create the folder {path}, can\'t add the show'.format(path=show_dir))
                # Don't redirect to default page because user wants to see the new show
                return json_response(
                    result=False,
                    message='Unable to add show: Unable to create the folder {path}'.format(path=show_dir),
                    redirect='/home/'
                )
            else:
                helpers.chmod_as_parent(show_dir)

        # prepare the inputs for passing along
        scene = config.checkbox_to_value(scene)
        anime = config.checkbox_to_value(anime)
        season_folders = config.checkbox_to_value(season_folders)
        subtitles = config.checkbox_to_value(subtitles)

        if whitelist:
            if not isinstance(whitelist, list):
                whitelist = [whitelist]
            whitelist = short_group_names(whitelist)
        if blacklist:
            if not isinstance(blacklist, list):
                blacklist = [blacklist]
            blacklist = short_group_names(blacklist)

        if not allowed_qualities:
            allowed_qualities = []
        if not preferred_qualities or try_int(quality_preset, None):
            preferred_qualities = []
        if not isinstance(allowed_qualities, list):
            allowed_qualities = [allowed_qualities]
        if not isinstance(preferred_qualities, list):
            preferred_qualities = [preferred_qualities]
        new_quality = Quality.combine_qualities([int(q) for q in allowed_qualities], [int(q) for q in preferred_qualities])

        # add the show
        app.show_queue_scheduler.action.addShow(indexer, indexer_id, show_dir, int(defaultStatus), new_quality,
                                                season_folders, indexer_lang, subtitles, anime,
                                                scene, None, blacklist, whitelist, int(defaultStatusAfter))
        ui.notifications.message('Show added', 'Adding the specified show into {path}'.format(path=show_dir))

        return finishAddShow()
Example #18
0
    def resource_search_indexers_for_show_name(self):
        """
        Search indexers for show name.

        Query parameters:
        :param query: Search term
        :param indexerId: Indexer to search, defined by ID. '0' for all indexers.
        :param language: 2-letter language code to search the indexer(s) in
        """
        query = self.get_argument('query', '').strip()
        indexer_id = self.get_argument('indexerId', '0').strip()
        language = self.get_argument('language', '').strip()

        if not query:
            return self._bad_request('No search term provided.')

        enabled_indexers = indexerApi().indexers
        indexer_id = try_int(indexer_id)
        if indexer_id > 0 and indexer_id not in enabled_indexers:
            return self._bad_request('Invalid indexer id.')

        if not language or language == 'null':
            language = app.INDEXER_DEFAULT_LANGUAGE

        search_terms = [query]

        # If search term ends with what looks like a year, enclose it in ()
        matches = re.match(r'^(.+ |)([12][0-9]{3})$', query)
        if matches:
            search_terms.append('{0}({1})'.format(matches.group(1),
                                                  matches.group(2)))

        for search_term in search_terms:
            # If search term begins with an article, let's also search for it without
            matches = re.match(r'^(?:a|an|the) (.+)$', search_term, re.I)
            if matches:
                search_terms.append(matches.group(1))

        results = {}
        final_results = []

        # Query indexers for each search term and build the list of results
        for indexer in enabled_indexers if indexer_id == 0 else [indexer_id]:
            indexer_instance = indexerApi(indexer)
            custom_api_params = indexer_instance.api_params.copy()
            custom_api_params['language'] = language
            custom_api_params['custom_ui'] = classes.AllShowsListUI

            try:
                indexer_api = indexer_instance.indexer(**custom_api_params)
            except IndexerUnavailable as msg:
                log.info('Could not initialize indexer {indexer}: {error}', {
                    'indexer': indexer_instance.name,
                    'error': msg
                })
                continue

            log.debug(
                'Searching for show with search term(s): {terms} on indexer: {indexer}',
                {
                    'terms': search_terms,
                    'indexer': indexer_api.name
                })
            for search_term in search_terms:
                try:
                    indexer_results = indexer_api[search_term]
                    # add search results
                    results.setdefault(indexer, []).extend(indexer_results)
                except IndexerException as error:
                    log.info(
                        'Error searching for show. term(s): {terms} indexer: {indexer} error: {error}',
                        {
                            'terms': search_terms,
                            'indexer': indexer_api.name,
                            'error': error
                        })
                except Exception as error:
                    log.error(
                        'Internal Error searching for show. term(s): {terms} indexer: {indexer} error: {error}',
                        {
                            'terms': search_terms,
                            'indexer': indexer_api.name,
                            'error': error
                        })

        # Get all possible show ids
        all_show_ids = {}
        for show in app.showList:
            for ex_indexer_name, ex_show_id in iteritems(show.externals):
                ex_indexer_id = reverse_mappings.get(ex_indexer_name)
                if not ex_indexer_id:
                    continue
                all_show_ids[(ex_indexer_id, ex_show_id)] = (show.indexer_name,
                                                             show.series_id)

        for indexer, shows in iteritems(results):
            indexer_api = indexerApi(indexer)
            indexer_results_set = set()
            for show in shows:
                show_id = int(show['id'])
                indexer_results_set.add(
                    (indexer_api.name, indexer, indexer_api.config['show_url'],
                     show_id, show['seriesname'], show['firstaired']
                     or 'N/A', show.get('network', '') or 'N/A',
                     sanitize_filename(show['seriesname']),
                     all_show_ids.get((indexer, show_id), False)))

            final_results.extend(indexer_results_set)

        language_id = indexerApi().config['langabbv_to_id'][language]
        data = {'results': final_results, 'languageId': language_id}
        return self._ok(data=data)
Example #19
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 u"by Indexer Id: {0}".format(self.indexer_id)))

        # make sure the Indexer IDs are valid
        try:
            l_indexer_api_params = indexerApi(self.indexer).api_params.copy()
            if self.lang:
                l_indexer_api_params['language'] = self.lang

            logger.log(u"" + str(indexerApi(self.indexer).name) + ": " +
                       repr(l_indexer_api_params))

            indexer_api = indexerApi(
                self.indexer).indexer(**l_indexer_api_params)
            s = indexer_api[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 = make_dir(self.showDir)
                    if not dir_exists:
                        logger.log(
                            u"Unable to create the folder {0}, can't add the show"
                            .format(self.showDir))
                        return

                    chmod_as_parent(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 {0} has no name on {1}, probably searched with the wrong language."
                    .format(self.showDir,
                            indexerApi(self.indexer).name), logger.ERROR)

                ui.notifications.error(
                    'Unable to add show',
                    'Show in {0} has no name on {1}, probably the wrong language. \
                                       Delete .nfo and manually add the correct language.'
                    .format(self.showDir,
                            indexerApi(self.indexer).name))
                self._finishEarly()
                return
            # if the show has no episodes/seasons
            if not s:
                logger.log(u"Show " + str(s['seriesname']) + u" is on " +
                           str(indexerApi(self.indexer).name) +
                           u" but contains no season/episode data.")
                ui.notifications.error(
                    "Unable to add show",
                    "Show {0} is on {1} but contains no season/episode data.".
                    format(s['seriesname'],
                           indexerApi(self.indexer).name))
                self._finishEarly()

                return

            # Check if we can already find this show in our current showList.
            try:
                check_existing_shows(s, self.indexer)
            except IndexerShowAllreadyInLibrary as e:
                logger.log(
                    u"Could not add the show %s, as it already is in your library."
                    u" Error: %s" % (s['seriesname'], e.message),
                    logger.WARNING)
                ui.notifications.error('Unable to add show',
                                       'reason: {0}'.format(e.message))
                self._finishEarly()

                # Clean up leftover if the newly created directory is empty.
                delete_empty_folders(self.showDir)
                return

        # TODO: Add more specific indexer exceptions, that should provide the user with some accurate feedback.
        except IndexerShowIncomplete as e:
            logger.log(
                u"%s Error while loading information from indexer %s. "
                u"Error: %s" %
                (self.indexer_id, indexerApi(self.indexer).name, e.message),
                logger.WARNING)
            ui.notifications.error(
                "Unable to add show",
                "Unable to look up the show in {0} on {1} using ID {2} "
                "Reason: {3}".format(self.showDir,
                                     indexerApi(self.indexer).name,
                                     self.indexer_id, e.message))
            self._finishEarly()
            return
        except IndexerShowNotFoundInLanguage as e:
            logger.log(
                u'{id}: Data retrieved from {indexer} was incomplete. The indexer does not provide '
                u'show information in the searched language {language}. Aborting: {error_msg}'
                .format(id=self.indexer_id,
                        indexer=indexerApi(self.indexer).name,
                        language=e.language,
                        error_msg=e.message), logger.WARNING)
            ui.notifications.error(
                'Error adding show!',
                'Unable to add show {indexer_id} on {indexer} with this language: {language}'
                .format(indexer_id=self.indexer_id,
                        indexer=indexerApi(self.indexer).name,
                        language=e.language))
            self._finishEarly()
            return
        except Exception as e:
            logger.log(
                u"%s Error while loading information from indexer %s. "
                u"Error: %r" %
                (self.indexer_id, indexerApi(self.indexer).name, e.message),
                logger.ERROR)
            ui.notifications.error(
                "Unable to add show",
                "Unable to look up the show in {0} on {1} using ID {2}, not using the NFO. "
                "Delete .nfo and try adding manually again.".format(
                    self.showDir,
                    indexerApi(self.indexer).name, self.indexer_id))
            self._finishEarly()
            return

        try:
            newShow = Series(self.indexer, self.indexer_id, self.lang)
            newShow.load_from_indexer(indexer_api)

            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 previously aired episodes to the specified status: {status}"
                .format(status=statusStrings[self.default_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 IndexerException as e:
            logger.log(
                u"Unable to add show due to an error with " +
                indexerApi(self.indexer).name + ": " + e.message, logger.ERROR)
            if self.show:
                ui.notifications.error("Unable to add " + str(self.show.name) +
                                       " due to an error with " +
                                       indexerApi(self.indexer).name + "")
            else:
                ui.notifications.error(
                    "Unable to add show due to an error with " +
                    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: " + e.message, 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 ImdbAPIError as e:
            logger.log(u"Something wrong on IMDb api: " + e.message,
                       logger.INFO)
        except Exception as e:
            logger.log(u"Error loading IMDb info: " + e.message, logger.ERROR)

        try:
            self.show.save_to_db()
        except Exception as e:
            logger.log(u"Error saving the show to the database: " + e.message,
                       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(tvapi=indexer_api)
        except Exception as e:
            logger.log(
                u"Error with " + indexerApi(self.show.indexer).name +
                ", not creating episode list: " + e.message, logger.ERROR)
            logger.log(traceback.format_exc(), logger.DEBUG)

        # update internal name cache
        name_cache.build_name_cache(self.show)

        try:
            self.show.load_episodes_from_dir()
        except Exception as e:
            logger.log(u"Error searching dir for episodes: " + e.message,
                       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.backlog_search_scheduler.action.search_backlog([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.trakt_checker_scheduler.action.manage_new_show(self.show)

            # add show to trakt.tv library
            if app.TRAKT_SYNC:
                app.trakt_checker_scheduler.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 determine 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()
Example #20
0
    def run(self):

        ShowQueueItem.run(self)

        log.info('Starting to add show by {0}',
                 ('show_dir: {0}'.format(self.show_dir) if self.show_dir else
                  'Indexer Id: {0}'.format(self.indexer_id)))

        # make sure the Indexer IDs are valid
        try:
            l_indexer_api_params = indexerApi(self.indexer).api_params.copy()
            if self.lang:
                l_indexer_api_params['language'] = self.lang

            log.info(
                '{indexer_name}: {indexer_params!r}', {
                    'indexer_name': indexerApi(self.indexer).name,
                    'indexer_params': l_indexer_api_params
                })

            indexer_api = indexerApi(
                self.indexer).indexer(**l_indexer_api_params)
            s = indexer_api[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.show_dir and self.root_dir:
                show_name = get_showname_from_indexer(self.indexer,
                                                      self.indexer_id,
                                                      self.lang)
                if show_name:
                    self.show_dir = os.path.join(self.root_dir,
                                                 sanitize_filename(show_name))
                    dir_exists = make_dir(self.show_dir)
                    if not dir_exists:
                        log.info(
                            "Unable to create the folder {0}, can't add the show",
                            self.show_dir)
                        return

                    chmod_as_parent(self.show_dir)
                else:
                    log.info("Unable to get a show {0}, can't add the show",
                             self.show_dir)
                    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:
                log.error(
                    'Show in {path} has no name on {indexer}, probably searched with the wrong language.',
                    {
                        'path': self.show_dir,
                        'indexer': indexerApi(self.indexer).name
                    })

                ui.notifications.error(
                    'Unable to add show',
                    'Show in {path} has no name on {indexer}, probably the wrong language.'
                    ' Delete .nfo and manually add the correct language.'.
                    format(path=self.show_dir,
                           indexer=indexerApi(self.indexer).name))
                self._finishEarly()
                return

            # Check if we can already find this show in our current showList.
            try:
                check_existing_shows(s, self.indexer)
            except IndexerShowAlreadyInLibrary as error:
                log.warning(
                    'Could not add the show {series}, as it already is in your library.'
                    ' Error: {error}', {
                        'series': s['seriesname'],
                        'error': error
                    })
                ui.notifications.error('Unable to add show',
                                       'reason: {0}'.format(error))
                self._finishEarly()

                # Clean up leftover if the newly created directory is empty.
                delete_empty_folders(self.show_dir)
                return

        # TODO: Add more specific indexer exceptions, that should provide the user with some accurate feedback.
        except IndexerShowNotFound as error:
            log.warning(
                '{id}: Unable to look up the show in {path} using id {id} on {indexer}.'
                ' Delete metadata files from the folder and try adding it again.\n'
                'With error: {error}', {
                    'id': self.indexer_id,
                    'path': self.show_dir,
                    'indexer': indexerApi(self.indexer).name,
                    'error': error
                })
            ui.notifications.error(
                'Unable to add show',
                'Unable to look up the show in {path} using id {id} on {indexer}.'
                ' Delete metadata files from the folder and try adding it again.'
                .format(path=self.show_dir,
                        id=self.indexer_id,
                        indexer=indexerApi(self.indexer).name))
            self._finishEarly()
            return
        except IndexerShowNotFoundInLanguage as error:
            log.warning(
                '{id}: Data retrieved from {indexer} was incomplete. The indexer does not provide'
                ' show information in the searched language {language}. Aborting: {error_msg}',
                {
                    'id': self.indexer_id,
                    'indexer': indexerApi(self.indexer).name,
                    'language': error.language,
                    'error_msg': error
                })
            ui.notifications.error(
                'Error adding show!',
                'Unable to add show {indexer_id} on {indexer} with this language: {language}'
                .format(indexer_id=self.indexer_id,
                        indexer=indexerApi(self.indexer).name,
                        language=error.language))
            self._finishEarly()
            return
        except Exception as error:
            log.error(
                '{id}: Error while loading information from indexer {indexer}. Error: {error!r}',
                {
                    'id': self.indexer_id,
                    'indexer': indexerApi(self.indexer).name,
                    'error': error
                })
            ui.notifications.error(
                'Unable to add show',
                'Unable to look up the show in {path} on {indexer} using ID {id}.'
                .format(path=self.show_dir,
                        indexer=indexerApi(self.indexer).name,
                        id=self.indexer_id))
            self._finishEarly()
            return

        try:
            newShow = Series(self.indexer, self.indexer_id, self.lang)
            newShow.load_from_indexer(indexer_api)

            self.show = newShow

            # set up initial values
            self.show.location = self.show_dir
            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.season_folders = self.season_folders if self.season_folders is not None \
                else app.SEASON_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
            log.info(
                'Setting all previously aired episodes to the specified status: {status}',
                {'status': statusStrings[self.default_status]})
            self.show.default_ep_status = self.default_status

            if self.show.anime:
                self.show.release_groups = BlackAndWhiteList(self.show)
                if self.blacklist:
                    self.show.release_groups.set_black_keywords(self.blacklist)
                if self.whitelist:
                    self.show.release_groups.set_white_keywords(self.whitelist)

        except IndexerException as error:
            log.error(
                'Unable to add show due to an error with {indexer}: {error}', {
                    'indexer': indexerApi(self.indexer).name,
                    'error': error
                })
            ui.notifications.error(
                'Unable to add {series_name} due to an error with {indexer_name}'
                .format(series_name=self.show.name if self.show else 'show',
                        indexer_name=indexerApi(self.indexer).name))
            self._finishEarly()
            return

        except MultipleShowObjectsException:
            log.warning(
                'The show in {show_dir} is already in your show list, skipping',
                {'show_dir': self.show_dir})
            ui.notifications.error(
                'Show skipped',
                'The show in {show_dir} is already in your show list'.format(
                    show_dir=self.show_dir))
            self._finishEarly()
            return

        except Exception as error:
            log.error('Error trying to add show: {0}', error)
            log.debug(traceback.format_exc())
            self._finishEarly()
            raise

        log.debug('Retrieving show info from IMDb')
        try:
            self.show.load_imdb_info()
        except ImdbAPIError as error:
            log.info('Something wrong on IMDb api: {0}', error)
        except RequestException as error:
            log.warning('Error loading IMDb info: {0}', error)

        try:
            log.debug('{id}: Saving new show to database',
                      {'id': self.show.series_id})
            self.show.save_to_db()
        except Exception as error:
            log.error('Error saving the show to the database: {0}', error)
            log.debug(traceback.format_exc())
            self._finishEarly()
            raise

        # add it to the show list
        app.showList.append(self.show)

        try:
            self.show.load_episodes_from_indexer(tvapi=indexer_api)
        except Exception as error:
            log.error(
                'Error with {indexer}, not creating episode list: {error}', {
                    'indexer': indexerApi(self.show.indexer).name,
                    'error': error
                })
            log.debug(traceback.format_exc())

        # update internal name cache
        name_cache.build_name_cache(self.show)

        try:
            self.show.load_episodes_from_dir()
        except Exception as error:
            log.error('Error searching dir for episodes: {0}', error)
            log.debug(traceback.format_exc())

        # if they set default ep status to WANTED then run the backlog to search for episodes
        if self.show.default_ep_status == WANTED:
            log.info(
                'Launching backlog for this show since its episodes are WANTED'
            )
            wanted_segments = self.show.get_wanted_segments()
            for season, segment in viewitems(wanted_segments):
                cur_backlog_queue_item = BacklogQueueItem(self.show, segment)
                app.forced_search_queue_scheduler.action.add_item(
                    cur_backlog_queue_item)

                log.info('Sending forced backlog for {show} season {season}'
                         ' because some episodes were set to wanted'.format(
                             show=self.show.name, season=season))

        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.trakt_checker_scheduler.action.manage_new_show(self.show)

            # add show to trakt.tv library
            if app.TRAKT_SYNC:
                app.trakt_checker_scheduler.action.add_show_trakt_library(
                    self.show)

            if app.TRAKT_SYNC_WATCHLIST:
                log.info('update watchlist')
                notifiers.trakt_notifier.update_watchlist(show_obj=self.show)

        # Load XEM data to DB for show
        scene_numbering.xem_refresh(self.show, force=True)

        # check if show has XEM mapping so we can determine if searches
        # should go by scene numbering or indexer numbering. Warn the user.
        if not self.scene and scene_numbering.get_xem_numbering_for_show(
                self.show):
            log.warning(
                '{id}: while adding the show {title} we noticed thexem.de has an episode mapping available'
                '\nyou might want to consider enabling the scene option for this show.',
                {
                    'id': self.show.series_id,
                    'title': self.show.name
                })
            ui.notifications.message(
                'consider enabling scene for this show',
                'for show {title} you might want to consider enabling the scene option'
                .format(title=self.show.name))

        # After initial add, set to default_status_after.
        self.show.default_ep_status = self.default_status_after

        try:
            log.debug('{id}: Saving new show info to database',
                      {'id': self.show.series_id})
            self.show.save_to_db()
        except Exception as error:
            log.warning(
                '{id}: Error saving new show info to database: {error_msg}', {
                    'id': self.show.series_id,
                    'error_msg': error
                })
            log.error(traceback.format_exc())

        # Send ws update to client
        ws.Message('showAdded', self.show.to_json(detailed=False)).push()

        self.finish()
Example #21
0
    def resource_search_indexers_for_show_name(self):
        """
        Search indexers for show name.

        Query parameters:
        :param query: Search term
        :param indexerId: Indexer to search, defined by ID. '0' for all indexers.
        :param language: 2-letter language code to search the indexer(s) in
        """
        query = self.get_argument('query', '').strip()
        indexer_id = self.get_argument('indexerId', '0').strip()
        language = self.get_argument('language', '').strip()

        if not query:
            return self._bad_request('No search term provided.')

        enabled_indexers = indexerApi().indexers
        indexer_id = try_int(indexer_id)
        if indexer_id > 0 and indexer_id not in enabled_indexers:
            return self._bad_request('Invalid indexer id.')

        if not language or language == 'null':
            language = app.INDEXER_DEFAULT_LANGUAGE

        search_terms = [query]

        # If search term ends with what looks like a year, enclose it in ()
        matches = re.match(r'^(.+ |)([12][0-9]{3})$', query)
        if matches:
            search_terms.append('{0}({1})'.format(matches.group(1), matches.group(2)))

        for search_term in search_terms:
            # If search term begins with an article, let's also search for it without
            matches = re.match(r'^(?:a|an|the) (.+)$', search_term, re.I)
            if matches:
                search_terms.append(matches.group(1))

        results = {}
        final_results = []

        # Query indexers for each search term and build the list of results
        for indexer in enabled_indexers if indexer_id == 0 else [indexer_id]:
            indexer_instance = indexerApi(indexer)
            custom_api_params = indexer_instance.api_params.copy()
            custom_api_params['language'] = language
            custom_api_params['custom_ui'] = classes.AllShowsListUI

            try:
                indexer_api = indexer_instance.indexer(**custom_api_params)
            except IndexerUnavailable as msg:
                log.info('Could not initialize indexer {indexer}: {error}',
                         {'indexer': indexer_instance.name, 'error': msg})
                continue

            log.debug('Searching for show with search term(s): {terms} on indexer: {indexer}',
                      {'terms': search_terms, 'indexer': indexer_api.name})
            for search_term in search_terms:
                try:
                    indexer_results = indexer_api[search_term]
                    # add search results
                    results.setdefault(indexer, []).extend(indexer_results)
                except IndexerException as error:
                    log.info('Error searching for show. term(s): {terms} indexer: {indexer} error: {error}',
                             {'terms': search_terms, 'indexer': indexer_api.name, 'error': error})
                except Exception as error:
                    log.error('Internal Error searching for show. term(s): {terms} indexer: {indexer} error: {error}',
                              {'terms': search_terms, 'indexer': indexer_api.name, 'error': error})

        # Get all possible show ids
        all_show_ids = {}
        for show in app.showList:
            for ex_indexer_name, ex_show_id in iteritems(show.externals):
                ex_indexer_id = reverse_mappings.get(ex_indexer_name)
                if not ex_indexer_id:
                    continue
                all_show_ids[(ex_indexer_id, ex_show_id)] = (show.indexer_name, show.series_id)

        for indexer, shows in iteritems(results):
            indexer_api = indexerApi(indexer)
            indexer_results_set = set()
            for show in shows:
                show_id = int(show['id'])
                indexer_results_set.add(
                    (
                        indexer_api.name,
                        indexer,
                        indexer_api.config['show_url'],
                        show_id,
                        show['seriesname'],
                        show['firstaired'] or 'N/A',
                        show.get('network', '') or 'N/A',
                        sanitize_filename(show['seriesname']),
                        all_show_ids.get((indexer, show_id), False)
                    )
                )

            final_results.extend(indexer_results_set)

        language_id = indexerApi().config['langabbv_to_id'][language]
        data = {
            'results': final_results,
            'languageId': language_id
        }
        return self._ok(data=data)