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
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
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
def test_sanitize_filename(value, expected): # Given # When actual = sut.sanitize_filename(value) # Then assert expected == actual
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
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
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
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
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))
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})
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
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)
def sanitizeFileName(name): return sanitize_filename(name)
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()
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()
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)
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()
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()
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)