From c8e276e6ffd103d748868c74b3653951dc38ed67 Mon Sep 17 00:00:00 2001 From: P0psicles Date: Fri, 12 Jan 2018 17:39:56 +0100 Subject: [PATCH 01/86] Added initial IMDB indexer. Working: Searching + basic adding of show. --- medusa/indexers/imdb/__init__.py | 0 medusa/indexers/imdb/imdb_api.py | 484 ++++++++++++++++++++++++ medusa/indexers/imdb/imdb_exceptions.py | 75 ++++ medusa/indexers/indexer_config.py | 31 +- 4 files changed, 586 insertions(+), 4 deletions(-) create mode 100644 medusa/indexers/imdb/__init__.py create mode 100644 medusa/indexers/imdb/imdb_api.py create mode 100644 medusa/indexers/imdb/imdb_exceptions.py diff --git a/medusa/indexers/imdb/__init__.py b/medusa/indexers/imdb/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/medusa/indexers/imdb/imdb_api.py b/medusa/indexers/imdb/imdb_api.py new file mode 100644 index 0000000000..f5bde4d505 --- /dev/null +++ b/medusa/indexers/imdb/imdb_api.py @@ -0,0 +1,484 @@ +# coding=utf-8 + +from __future__ import unicode_literals + +import logging +from collections import OrderedDict +from imdbpie import imdbpie +from time import time +from six import text_type + +from medusa.indexers.indexer_base import (Actor, Actors, BaseIndexer) +from medusa.indexers.indexer_exceptions import ( + IndexerError, + IndexerException, +) +from medusa.logger.adapters.style import BraceAdapter + + +log = BraceAdapter(logging.getLogger(__name__)) +log.logger.addHandler(logging.NullHandler()) + + +class ImdbIdentifier(object): + def __init__(self, imdb_id): + """Initialize an identifier object. Can be used to get the full textual id e.a. 'tt3986523'. + Or the series_id: 3986523 + """ + self._imdb_id = None + self._series_id = None + self.imdb_id = imdb_id + + @property + def series_id(self): + return self._series_id + + @series_id.setter + def series_id(self, value): + self._series_id = value + + @property + def imdb_id(self): + return self._imdb_id + + @imdb_id.setter + def imdb_id(self, value): + self._imdb_id = value + self.series_id = int(value.split('tt')[-1]) + + +class Imdb(BaseIndexer): + """Create easy-to-use interface to name of season/episode name + >>> indexer_api = imdb() + >>> indexer_api['Scrubs'][1][24]['episodename'] + u'My Last Day' + """ + + def __init__(self, *args, **kwargs): # pylint: disable=too-many-locals,too-many-arguments + super(Imdb, self).__init__(*args, **kwargs) + + self.indexer = 10 + + # List of language from http://theimdb.com/api/0629B785CE550C8D/languages.xml + # Hard-coded here as it is realtively static, and saves another HTTP request, as + # recommended on http://theimdb.com/wiki/index.php/API:languages.xml + self.config['valid_languages'] = [ + 'da', 'fi', 'nl', 'de', 'it', 'es', 'fr', 'pl', 'hu', 'el', 'tr', + 'ru', 'he', 'ja', 'pt', 'zh', 'cs', 'sl', 'hr', 'ko', 'en', 'sv', 'no' + ] + + # thetvdb.com should be based around numeric language codes, + # but to link to a series like http://thetvdb.com/?tab=series&id=79349&lid=16 + # requires the language ID, thus this mapping is required (mainly + # for usage in tvdb_ui - internally tvdb_api will use the language abbreviations) + self.config['langabbv_to_id'] = {'el': 20, 'en': 7, 'zh': 27, + 'it': 15, 'cs': 28, 'es': 16, 'ru': 22, 'nl': 13, 'pt': 26, 'no': 9, + 'tr': 21, 'pl': 18, 'fr': 17, 'hr': 31, 'de': 14, 'da': 10, 'fi': 11, + 'hu': 19, 'ja': 25, 'he': 24, 'ko': 32, 'sv': 8, 'sl': 30} + + # Initiate the imdbpie API + self.imdb_api = imdbpie.Imdb(session=self.config['session']) + + self.config['artwork_prefix'] = '{base_url}{image_size}{file_path}' + + # An api to indexer series/episode object mapping + self.series_map = [ + ('id', 'imdb_id'), + ('id', 'base.id'), + ('seriesname', 'title'), + ('seriesname', 'base.title'), + ('summary', 'plot.summaries[0].text'), + ('firstaired', 'year'), + ('fanart', 'base.image.url'), + ('show_url', 'base.id'), + ('firstaired', 'base.seriesStartYear'), + ('contentrating', 'ratings.rating'), + ] + + self.episode_map = [ + ('id', 'id'), + ('episodename', 'title'), + ('firstaired', 'year'), + ] + + @classmethod + def get_nested_value(cls, value, config): + # Remove a level + split_config = config.split('.') + check_key = split_config[0] + + if check_key.endswith(']'): + list_index = int(check_key.split('[')[-1].rstrip(']')) + check_key = check_key.split('[')[0] + check_value = value.get(check_key)[list_index] + else: + check_value = value.get(check_key) + next_keys = '.'.join(split_config[1:]) + + if not check_value: + return None + + if isinstance(check_value, dict): + return cls.get_nested_value(check_value, next_keys) + else: + return check_value + + def _map_results(self, imdb_response, key_mappings=None, list_separator='|'): + """ + Map results to a a key_mapping dict. + + :param imdb_response: imdb response obect, or a list of response objects. + :type imdb_response: list(object) + :param key_mappings: Dict of imdb attributes, that are mapped to normalized keys. + :type key_mappings: dictionary + :param list_separator: A list separator used to transform lists to a character separator string. + :type list_separator: string. + """ + parsed_response = [] + + if not isinstance(imdb_response, list): + imdb_response = [imdb_response] + + # TVmaze does not number their special episodes. It does map it to a season. And that's something, medusa + # Doesn't support. So for now, we increment based on the order, we process the specials. And map it to + # season 0. We start with episode 1. + index_special_episodes = 1 + + for item in imdb_response: + return_dict = {} + try: + if item.get('type') in ('feature',): + continue + + # return_dict['id'] = ImdbIdentifier(item.pop('imdb_id')).series_id + for key, config in self.series_map: + value = Imdb.get_nested_value(item, config) + if key == 'id' and value: + value = ImdbIdentifier(value.rstrip('/')).series_id + + if value is not None: + return_dict[key] = value + + except Exception as error: + log.warning('Exception trying to parse attribute: {0}, with exception: {1!r}', item, error) + + parsed_response.append(return_dict) + + return parsed_response if len(parsed_response) != 1 else parsed_response[0] + + def _show_search(self, series, request_language='en'): + """ + Uses the TVMaze API to search for a show + :param show: The show name that's searched for as a string + :param request_language: Language in two letter code. TVMaze fallsback to en itself. + :return: A list of Show objects. + """ + + results = self.imdb_api.search_for_title(series) + + if results: + return results + else: + return None + + # Tvdb implementation + def search(self, series): + """This searches imdb.com for the series name + + :param series: the query for the series name + :return: An ordered dict with the show searched for. In the format of OrderedDict{"series": [list of shows]} + """ + series = series.encode('utf-8') + log.debug('Searching for show {0}', series) + + results = self._show_search(series, request_language=self.config['language']) + + if not results: + return + + mapped_results = self._map_results(results, self.series_map, '|') + + return OrderedDict({'series': mapped_results})['series'] + + def _get_show_by_id(self, imdb_id, request_language='en'): # pylint: disable=unused-argument + """ + Retrieve imdb show information by imdb id, or if no imdb id provided by passed external id. + + :param imdb_id: The shows imdb id + :return: An ordered dict with the show searched for. + """ + results = None + imdb_id = 'tt{0}'.format(text_type(imdb_id).zfill(7)) + if imdb_id: + log.debug('Getting all show data for {0}', imdb_id) + results = self.imdb_api.get_title(imdb_id) + + if not results: + return + + mapped_results = self._map_results(results, self.series_map) + + return OrderedDict({'series': mapped_results}) + + def _get_episodes(self, imdb_id, specials=False, aired_season=None): # pylint: disable=unused-argument + """ + Get all the episodes for a show by imdb id + + :param imdb_id: Series imdb id. + :return: An ordered dict with the show searched for. In the format of OrderedDict{"episode": [list of episodes]} + """ + # Parse episode data + log.debug('Getting all episodes of {0}', imdb_id) + + series_id = imdb_id + imdb_id = 'tt{0}'.format(text_type(imdb_id).zfill(7)) + results = self.imdb_api.get_title_episodes(imdb_id) + + if not results or not results.get('seasons'): + return False + + for season in results.get('seasons'): + for episode in season['episodes']: + season_no, episode_no = episode.get('season'), episode.get('episode') + + if season_no is None or episode_no is None: + log.warning('An episode has incomplete season/episode number (season: {0!r}, episode: {1!r})', seasnum, epno) + continue # Skip to next episode + + for k, config in self.episode_map: + v = Imdb.get_nested_value(episode, config) + if v is not None: + if k == 'id': + v = ImdbIdentifier(v.rstrip('/')).series_id + if k == 'firstaired': + v = '{year}-01-01'.format(year=v) + + self._set_item(series_id, season_no, episode_no, k, v) + + def _parse_images(self, imdb_id): + """Parse Show and Season posters. + + images are retrieved using t['show name]['_banners'], for example: + + >>> indexer_api = TVMaze(images = True) + >>> indexer_api['scrubs']['_banners'].keys() + ['fanart', 'poster', 'series', 'season'] + >>> t['scrubs']['_banners']['poster']['680x1000']['35308']['_bannerpath'] + u'http://theimdb.com/banners/posters/76156-2.jpg' + >>> + + Any key starting with an underscore has been processed (not the raw + data from the XML) + + This interface will be improved in future versions. + """ + log.debug('Getting show banners for {0}', imdb_id) + + try: + image_medium = self.shows[imdb_id]['image_medium'] + except Exception: + log.debug('Could not parse Poster for showid: {0}', imdb_id) + return False + + # Set the poster (using the original uploaded poster for now, as the medium formated is 210x195 + _images = {u'poster': {u'1014x1500': {u'1': {u'rating': 1, + u'language': u'en', + u'ratingcount': 1, + u'bannerpath': image_medium.split('/')[-1], + u'bannertype': u'poster', + u'bannertype2': u'210x195', + u'_bannerpath': image_medium, + u'id': u'1035106'}}}} + + season_images = self._parse_season_images(imdb_id) + if season_images: + _images.update(season_images) + + self._save_images(imdb_id, _images) + self._set_show_data(imdb_id, '_banners', _images) + + def _parse_season_images(self, imdb_id): + """Parse Show and Season posters.""" + seasons = {} + if imdb_id: + log.debug('Getting all show data for {0}', imdb_id) + try: + seasons = self.imdb_api.show_seasons(maze_id=imdb_id) + except BaseError as e: + log.warning('Getting show seasons for the season images failed. Cause: {0}', e) + + _images = {'season': {'original': {}}} + # Get the season posters + for season in seasons.keys(): + if not getattr(seasons[season], 'image', None): + continue + if season not in _images['season']['original']: + _images['season']['original'][season] = {seasons[season].id: {}} + _images['season']['original'][season][seasons[season].id]['_bannerpath'] = seasons[season].image['original'] + _images['season']['original'][season][seasons[season].id]['rating'] = 1 + + return _images + + def _parse_actors(self, imdb_id): + """Parsers actors XML, from + http://theimdb.com/api/[APIKEY]/series/[SERIES ID]/actors.xml + + Actors are retrieved using t['show name]['_actors'], for example: + + >>> indexer_api = TVMaze(actors = True) + >>> actors = indexer_api['scrubs']['_actors'] + >>> type(actors) + + >>> type(actors[0]) + + >>> actors[0] + + >>> sorted(actors[0].keys()) + ['id', 'image', 'name', 'role', 'sortorder'] + >>> actors[0]['name'] + u'Zach Braff' + >>> actors[0]['image'] + u'http://theimdb.com/banners/actors/43640.jpg' + + Any key starting with an underscore has been processed (not the raw + data from the indexer) + """ + log.debug('Getting actors for {0}', imdb_id) + return + # FIXME: implement cast + actors = self.imdb_api.show_cast(imdb_id) + + cur_actors = Actors() + for order, cur_actor in enumerate(actors.people): + save_actor = Actor() + save_actor['id'] = cur_actor.id + save_actor['image'] = cur_actor.image.get('original') if cur_actor.image else '' + save_actor['name'] = cur_actor.name + save_actor['role'] = cur_actor.character.name + save_actor['sortorder'] = order + cur_actors.append(save_actor) + self._set_show_data(imdb_id, '_actors', cur_actors) + + def _get_show_data(self, imdb_id, language='en'): # pylint: disable=too-many-branches,too-many-statements,too-many-locals + """Takes a series ID, gets the epInfo URL and parses the imdb json response + into the shows dict in layout: + shows[series_id][season_number][episode_number] + """ + + if self.config['language'] is None: + log.debug('Config language is none, using show language') + if language is None: + raise IndexerError("config['language'] was None, this should not happen") + get_show_in_language = language + else: + log.debug( + 'Configured language {0} override show language of {1}', ( + self.config['language'], + language + ) + ) + get_show_in_language = self.config['language'] + + # Parse show information + log.debug('Getting all series data for {0}', imdb_id) + + # Parse show information + series_info = self._get_show_by_id(imdb_id, request_language=get_show_in_language) + + if not series_info: + log.debug('Series result returned zero') + raise IndexerError('Series result returned zero') + + # save all retrieved show information to Show object. + for k, v in series_info['series'].items(): + if v is not None: + self._set_show_data(imdb_id, k, v) + + # Get external ids. + # As the external id's are not part of the shows default response, we need to make an additional call for it. + # Im checking for the external value. to make sure only externals with a value get in. + self._set_show_data(imdb_id, 'externals', {external_id: text_type(getattr(self.shows[imdb_id], external_id, None)) + for external_id in ['tvdb_id', 'imdb_id', 'tvrage_id'] + if getattr(self.shows[imdb_id], external_id, None)}) + + # get episode data + if self.config['episodes_enabled']: + self._get_episodes(imdb_id, specials=False, aired_season=None) + + # Parse banners + if self.config['banners_enabled']: + self._parse_images(imdb_id) + + # Parse actors + if self.config['actors_enabled']: + self._parse_actors(imdb_id) + + return True + + def _get_all_updates(self, start_date=None, end_date=None): + """Retrieve all updates (show,season,episode) from TVMaze.""" + results = [] + try: + updates = self.imdb_api.show_updates() + except (ShowIndexError, UpdateNotFound): + return results + except BaseError as e: + log.warning('Getting show updates failed. Cause: {0}', e) + return results + + if getattr(updates, 'updates', None): + for show_id, update_ts in updates.updates.items(): + if start_date < update_ts.seconds_since_epoch < (end_date or int(time())): + results.append(int(show_id)) + + return results + + # Public methods, usable separate from the default api's interface api['show_id'] + def get_last_updated_series(self, from_time, weeks=1, filter_show_list=None): + """Retrieve a list with updated shows + + :param from_time: epoch timestamp, with the start date/time + :param weeks: number of weeks to get updates for. + :param filter_show_list: Optional list of show objects, to use for filtering the returned list. + """ + total_updates = [] + updates = self._get_all_updates(from_time, from_time + (weeks * 604800)) # + seconds in a week + + if updates and filter_show_list: + new_list = [] + for show in filter_show_list: + if show.indexerid in total_updates: + new_list.append(show.indexerid) + updates = new_list + + return updates + + def get_id_by_external(self, **kwargs): + """Search imdb for a show, using an external id. + + Accepts as kwargs, so you'l need to add the externals as key/values. + :param tvrage: The tvrage id. + :param thetvdb: The tvdb id. + :param imdb: An imdb id (inc. tt). + :returns: A dict with externals, including the imdb id. + """ + mapping = {'thetvdb': 'tvdb_id', 'tvrage': 'tvrage_id', 'imdb': 'imdb_id'} + for external_id in mapping.values(): + if kwargs.get(external_id): + try: + result = self.imdb_api.get_show(**{external_id: kwargs.get(external_id)}) + if result: + externals = {mapping[imdb_external_id]: external_value + for imdb_external_id, external_value + in result.externals.items() + if external_value and mapping.get(imdb_external_id)} + externals['imdb_id'] = result.maze_id + return externals + except ShowNotFound: + log.debug('Could not get imdb externals using external key {0} and id {1}', + external_id, kwargs.get(external_id)) + continue + except BaseError as e: + log.warning('Could not get imdb externals. Cause: {0}', e) + continue + return {} diff --git a/medusa/indexers/imdb/imdb_exceptions.py b/medusa/indexers/imdb/imdb_exceptions.py new file mode 100644 index 0000000000..7a8edbc91e --- /dev/null +++ b/medusa/indexers/imdb/imdb_exceptions.py @@ -0,0 +1,75 @@ +# coding=utf-8 +# Author: p0psicles +# +# This file is part of Medusa. +# +# Medusa is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Medusa is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Medusa. If not, see . + +"""Custom exceptions used or raised by tvdbv2_api.""" + +__author__ = "p0psicles" +__version__ = "1.0" + +__all__ = ["tvdbv2_error", "tvdbv2_userabort", "tvdbv2_shownotfound", "tvdbv2_showincomplete", + "tvdbv2_seasonnotfound", "tvdbv2_episodenotfound", "tvdbv2_attributenotfound"] + + +class tvdbv2_exception(Exception): + """Any exception generated by tvdbv2_api + """ + pass + + +class tvdbv2_error(tvdbv2_exception): + """An error with thetvdb.com (Cannot connect, for example) + """ + pass + + +class tvdbv2_userabort(tvdbv2_exception): + """User aborted the interactive selection (via + the q command, ^c etc) + """ + pass + + +class tvdbv2_shownotfound(tvdbv2_exception): + """Show cannot be found on thetvdb.com (non-existant show) + """ + pass + + +class tvdbv2_showincomplete(tvdbv2_exception): + """Show found but incomplete on thetvdb.com (incomplete show) + """ + pass + + +class tvdbv2_seasonnotfound(tvdbv2_exception): + """Season cannot be found on thetvdb.com + """ + pass + + +class tvdbv2_episodenotfound(tvdbv2_exception): + """Episode cannot be found on thetvdb.com + """ + pass + + +class tvdbv2_attributenotfound(tvdbv2_exception): + """Raised if an episode does not have the requested + attribute (such as a episode name) + """ + pass diff --git a/medusa/indexers/indexer_config.py b/medusa/indexers/indexer_config.py index 0aee73fc51..ccb96c11b5 100644 --- a/medusa/indexers/indexer_config.py +++ b/medusa/indexers/indexer_config.py @@ -5,6 +5,7 @@ import re from medusa.app import BASE_PYMEDUSA_URL +from medusa.indexers.imdb.imdb_api import Imdb from medusa.indexers.tmdb.tmdb import Tmdb from medusa.indexers.tvdbv2.tvdbv2_api import TVDBv2 from medusa.indexers.tvmaze.tvmaze_api import TVmaze @@ -28,15 +29,19 @@ INDEXER_TVRAGE = 2 # Must keep INDEXER_TVMAZE = 3 INDEXER_TMDB = 4 -EXTERNAL_IMDB = 10 +# FIXME: Change all references to EXTERNAL_IMDB to INDEXER_IMDB +INDEXER_IMDB = EXTERNAL_IMDB = 10 EXTERNAL_ANIDB = 11 EXTERNAL_TRAKT = 12 -EXTERNAL_MAPPINGS = {EXTERNAL_IMDB: 'imdb_id', EXTERNAL_ANIDB: 'anidb_id', - INDEXER_TVRAGE: 'tvrage_id', EXTERNAL_TRAKT: 'trakt_id'} +EXTERNAL_MAPPINGS = { + EXTERNAL_ANIDB: 'anidb_id', + INDEXER_TVRAGE: 'tvrage_id', + EXTERNAL_TRAKT: 'trakt_id' +} # trakt indexer name vs Medusa indexer -TRAKT_INDEXERS = {'tvdb': INDEXER_TVDBV2, 'tmdb': INDEXER_TMDB, 'imdb': EXTERNAL_IMDB, 'trakt': EXTERNAL_TRAKT} +TRAKT_INDEXERS = {'tvdb': INDEXER_TVDBV2, 'tmdb': INDEXER_TMDB, 'imdb': INDEXER_IMDB, 'trakt': EXTERNAL_TRAKT} STATUS_MAP = { 'returning series': 'Continuing', @@ -113,6 +118,24 @@ 'show_url': 'https://www.themoviedb.org/tv/', 'mapped_to': 'tmdb_id', # The attribute to which other indexers can map there tmdb id to 'identifier': 'tmdb', # Also used as key for the custom scenename exceptions. (_get_custom_exceptions()) + }, + INDEXER_IMDB: { + 'enabled': True, + 'id': INDEXER_IMDB, + 'name': 'IMDB', + 'module': Imdb, + 'api_params': { + 'language': 'en', + 'use_zip': True, + 'session': MedusaSession(cache_control={'cache_etags': False}), + }, + 'xem_mapped_to': INDEXER_TVDBV2, + 'icon': 'imdb16.png', + 'scene_loc': '{base_url}/scene_exceptions/scene_exceptions_imdb.json'.format(base_url=BASE_PYMEDUSA_URL), + 'show_url': 'http://www.imdb.com/shows/', + 'base_url': 'https://v2.sg.media-imdb.com', + 'mapped_to': 'imdb_id', # The attribute to which other indexers can map there tvmaze id to + 'identifier': 'imdb', # Also used as key for the custom scenename exceptions. (_get_custom_exceptions()) } } From b76d78e1ffab3f64f35fd54e830f172ff1daf8dc Mon Sep 17 00:00:00 2001 From: P0psicles Date: Fri, 12 Jan 2018 17:47:16 +0100 Subject: [PATCH 02/86] Don't add video games and tv short. --- medusa/indexers/imdb/imdb_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/medusa/indexers/imdb/imdb_api.py b/medusa/indexers/imdb/imdb_api.py index f5bde4d505..4e9a6a3986 100644 --- a/medusa/indexers/imdb/imdb_api.py +++ b/medusa/indexers/imdb/imdb_api.py @@ -147,7 +147,7 @@ def _map_results(self, imdb_response, key_mappings=None, list_separator='|'): for item in imdb_response: return_dict = {} try: - if item.get('type') in ('feature',): + if item.get('type') in ('feature', 'video game', 'TV short'): continue # return_dict['id'] = ImdbIdentifier(item.pop('imdb_id')).series_id From 69120131e4dacd8413835fad31b1a5f90c7ce50b Mon Sep 17 00:00:00 2001 From: P0psicles Date: Sat, 13 Jan 2018 11:39:25 +0100 Subject: [PATCH 03/86] Fixed setting the imdb_id external. --- medusa/indexers/imdb/imdb_api.py | 8 ++++++-- medusa/tv/series.py | 5 +++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/medusa/indexers/imdb/imdb_api.py b/medusa/indexers/imdb/imdb_api.py index 4e9a6a3986..f04f6dd8bf 100644 --- a/medusa/indexers/imdb/imdb_api.py +++ b/medusa/indexers/imdb/imdb_api.py @@ -43,8 +43,12 @@ def imdb_id(self): @imdb_id.setter def imdb_id(self, value): - self._imdb_id = value - self.series_id = int(value.split('tt')[-1]) + if isinstance(value, text_type) and 'tt' in value: + self._imdb_id = value + self.series_id = int(value.split('tt')[-1]) + else: + self._imdb_id = 'tt{0}'.format(text_type(value).zfill(7)) + self.series_id = int(value) class Imdb(BaseIndexer): diff --git a/medusa/tv/series.py b/medusa/tv/series.py index ee1f1ece18..a193168ea0 100644 --- a/medusa/tv/series.py +++ b/medusa/tv/series.py @@ -69,6 +69,7 @@ from medusa.indexers.indexer_api import indexerApi from medusa.indexers.indexer_config import ( INDEXER_TVRAGE, + INDEXER_IMDB, STATUS_MAP, indexerConfig, indexer_id_to_slug, @@ -80,6 +81,7 @@ IndexerException, IndexerSeasonNotFound, ) +from medusa.indexers.imdb.imdb_api import ImdbIdentifier from medusa.indexers.tmdb.tmdb import Tmdb from medusa.logger.adapters.style import BraceAdapter from medusa.media.banner import ShowBanner @@ -1520,6 +1522,9 @@ def load_from_indexer(self, tvapi=None): # Enrich the externals, using reverse lookup. self.externals.update(get_externals(self)) + if self.indexer_api.indexer == INDEXER_IMDB: + self.externals['imdb_id'] = ImdbIdentifier(getattr(indexed_show, 'id')).imdb_id + self.imdb_id = self.externals.get('imdb_id') or getattr(indexed_show, 'imdb_id', '') if getattr(indexed_show, 'airs_dayofweek', '') and getattr(indexed_show, 'airs_time', ''): From e94dd248f52acadbb7515b11f063e0342735303a Mon Sep 17 00:00:00 2001 From: P0psicles Date: Sat, 13 Jan 2018 13:04:27 +0100 Subject: [PATCH 04/86] Added images (only poster for now). --- medusa/indexers/imdb/imdb_api.py | 128 ++++++++++++++++++++++++++----- medusa/metadata/kodi_12plus.py | 2 +- 2 files changed, 110 insertions(+), 20 deletions(-) diff --git a/medusa/indexers/imdb/imdb_api.py b/medusa/indexers/imdb/imdb_api.py index f04f6dd8bf..eb3056f7e6 100644 --- a/medusa/indexers/imdb/imdb_api.py +++ b/medusa/indexers/imdb/imdb_api.py @@ -1,7 +1,7 @@ # coding=utf-8 from __future__ import unicode_literals - +from itertools import chain import logging from collections import OrderedDict from imdbpie import imdbpie @@ -11,7 +11,6 @@ from medusa.indexers.indexer_base import (Actor, Actors, BaseIndexer) from medusa.indexers.indexer_exceptions import ( IndexerError, - IndexerException, ) from medusa.logger.adapters.style import BraceAdapter @@ -159,6 +158,8 @@ def _map_results(self, imdb_response, key_mappings=None, list_separator='|'): value = Imdb.get_nested_value(item, config) if key == 'id' and value: value = ImdbIdentifier(value.rstrip('/')).series_id + if key == 'contentrating': + value = text_type(value) if value is not None: return_dict[key] = value @@ -278,29 +279,118 @@ def _parse_images(self, imdb_id): """ log.debug('Getting show banners for {0}', imdb_id) - try: - image_medium = self.shows[imdb_id]['image_medium'] - except Exception: - log.debug('Could not parse Poster for showid: {0}', imdb_id) - return False + images = self.imdb_api.get_title_images(ImdbIdentifier(imdb_id).imdb_id) - # Set the poster (using the original uploaded poster for now, as the medium formated is 210x195 - _images = {u'poster': {u'1014x1500': {u'1': {u'rating': 1, - u'language': u'en', - u'ratingcount': 1, - u'bannerpath': image_medium.split('/')[-1], - u'bannertype': u'poster', - u'bannertype2': u'210x195', - u'_bannerpath': image_medium, - u'id': u'1035106'}}}} + _images = {} + try: + for image in images.get('images', []): + if image.get('type') not in ('poster',): + continue - season_images = self._parse_season_images(imdb_id) - if season_images: - _images.update(season_images) + image_type = image.get('type') + if image_type not in _images: + _images[image_type] = {} + + # Store the images for each resolution available + # Always provide a resolution or 'original'. + resolution = '{0}x{1}'.format(image['width'], image['height']) + if resolution not in _images[image_type]: + _images[image_type][resolution] = {} + + bid = image['id'].split('/')[-1] + + if image_type in ['season', 'seasonwide']: + if int(image.sub_key) not in _images[image_type][resolution]: + _images[image_type][resolution][int(image.sub_key)] = {} + if bid not in _images[image_type][resolution][int(image.sub_key)]: + _images[image_type][resolution][int(image.sub_key)][bid] = {} + base_path = _images[image_type][resolution][int(image.sub_key)][bid] + else: + if bid not in _images[image_type][resolution]: + _images[image_type][resolution][bid] = {} + base_path = _images[image_type][resolution][bid] + + base_path['bannertype'] = image_type + base_path['bannertype2'] = resolution + base_path['_bannerpath'] = image.get('url') + base_path['bannerpath'] = image.get('url').split('/')[-1] + base_path['id'] = bid + + except Exception as error: + log.warning('Could not parse Poster for show id: {0}, with exception: {1!r}', imdb_id, error) + return self._save_images(imdb_id, _images) self._set_show_data(imdb_id, '_banners', _images) + def _save_images(self, series_id, images): + """ + Save the highest rated images for the show. + + :param series_id: The series ID + :param images: A nested mapping of image info + images[type][res][id] = image_info_mapping + type: image type such as `banner`, `poster`, etc + res: resolution such as `1024x768`, `original`, etc + id: the image id + """ + # Get desired image types from images + image_types = 'banner', 'fanart', 'poster' + + def get_resolution(image): + w, h = image['bannertype2'].split('x') + return int(w) * int(h) + + # Iterate through desired image types + for img_type in image_types: + + try: + image_type = images[img_type] + except KeyError: + log.debug( + u'No {image}s found for {series}', { + 'image': img_type, + 'series': series_id, + } + ) + continue + + # Flatten image_type[res][id].values() into list of values + merged_images = chain.from_iterable( + resolution.values() + for resolution in image_type.values() + ) + + # Sort by resolution + sort_images = sorted( + merged_images, + key=get_resolution, + reverse=True, + ) + + if not sort_images: + continue + + # Get the highest rated image + highest_rated = sort_images[0] + img_url = highest_rated['_bannerpath'] + log.debug( + u'Selecting highest rated {image} (rating={img[rating]}):' + u' {img[_bannerpath]}', { + 'image': img_type, + 'img': highest_rated, + } + ) + log.debug( + u'{image} details: {img}', { + 'image': img_type.capitalize(), + 'img': highest_rated, + } + ) + + # Save the image + self._set_show_data(series_id, img_type, img_url) + def _parse_season_images(self, imdb_id): """Parse Show and Season posters.""" seasons = {} diff --git a/medusa/metadata/kodi_12plus.py b/medusa/metadata/kodi_12plus.py index 3df8c96a7f..a860f04a96 100644 --- a/medusa/metadata/kodi_12plus.py +++ b/medusa/metadata/kodi_12plus.py @@ -161,7 +161,7 @@ def _show_data(self, show_obj): if getattr(my_show, 'firstaired', None): premiered = etree.SubElement(tv_node, 'premiered') - premiered.text = my_show['firstaired'] + premiered.text = str(my_show['firstaired']) if getattr(my_show, 'network', None): studio = etree.SubElement(tv_node, 'studio') From 81f06efd3d3ade59b9a09606ca09cfd265ed7e36 Mon Sep 17 00:00:00 2001 From: P0psicles Date: Sun, 14 Jan 2018 10:51:39 +0100 Subject: [PATCH 05/86] Added show status and firstaired using releases route. --- medusa/indexers/imdb/imdb_api.py | 46 ++++++++------------------------ medusa/indexers/indexer_base.py | 32 +++++++++++++++++++++- 2 files changed, 42 insertions(+), 36 deletions(-) diff --git a/medusa/indexers/imdb/imdb_api.py b/medusa/indexers/imdb/imdb_api.py index eb3056f7e6..4aaabc1fb2 100644 --- a/medusa/indexers/imdb/imdb_api.py +++ b/medusa/indexers/imdb/imdb_api.py @@ -104,28 +104,6 @@ def __init__(self, *args, **kwargs): # pylint: disable=too-many-locals,too-many ('firstaired', 'year'), ] - @classmethod - def get_nested_value(cls, value, config): - # Remove a level - split_config = config.split('.') - check_key = split_config[0] - - if check_key.endswith(']'): - list_index = int(check_key.split('[')[-1].rstrip(']')) - check_key = check_key.split('[')[0] - check_value = value.get(check_key)[list_index] - else: - check_value = value.get(check_key) - next_keys = '.'.join(split_config[1:]) - - if not check_value: - return None - - if isinstance(check_value, dict): - return cls.get_nested_value(check_value, next_keys) - else: - return check_value - def _map_results(self, imdb_response, key_mappings=None, list_separator='|'): """ Map results to a a key_mapping dict. @@ -155,15 +133,17 @@ def _map_results(self, imdb_response, key_mappings=None, list_separator='|'): # return_dict['id'] = ImdbIdentifier(item.pop('imdb_id')).series_id for key, config in self.series_map: - value = Imdb.get_nested_value(item, config) + value = self.get_nested_value(item, config) if key == 'id' and value: value = ImdbIdentifier(value.rstrip('/')).series_id if key == 'contentrating': value = text_type(value) - if value is not None: return_dict[key] = value + # Check if the show is continuing + return_dict['status'] = 'Continuing' if item.get('base', {}).get('nextEpisode') else 'Ended' + except Exception as error: log.warning('Exception trying to parse attribute: {0}, with exception: {1!r}', item, error) @@ -213,7 +193,7 @@ def _get_show_by_id(self, imdb_id, request_language='en'): # pylint: disable=un :return: An ordered dict with the show searched for. """ results = None - imdb_id = 'tt{0}'.format(text_type(imdb_id).zfill(7)) + imdb_id = ImdbIdentifier(imdb_id).imdb_id if imdb_id: log.debug('Getting all show data for {0}', imdb_id) results = self.imdb_api.get_title(imdb_id) @@ -223,6 +203,11 @@ def _get_show_by_id(self, imdb_id, request_language='en'): # pylint: disable=un mapped_results = self._map_results(results, self.series_map) + # Get firstaired + releases = self.imdb_api.get_title_releases(imdb_id) + if releases.get('releases'): + first_released = sorted([r['date'] for r in releases['releases']])[0] + mapped_results['firstaired'] = first_released return OrderedDict({'series': mapped_results}) def _get_episodes(self, imdb_id, specials=False, aired_season=None): # pylint: disable=unused-argument @@ -251,7 +236,7 @@ def _get_episodes(self, imdb_id, specials=False, aired_season=None): # pylint: continue # Skip to next episode for k, config in self.episode_map: - v = Imdb.get_nested_value(episode, config) + v = self.get_nested_value(episode, config) if v is not None: if k == 'id': v = ImdbIdentifier(v.rstrip('/')).series_id @@ -263,15 +248,6 @@ def _get_episodes(self, imdb_id, specials=False, aired_season=None): # pylint: def _parse_images(self, imdb_id): """Parse Show and Season posters. - images are retrieved using t['show name]['_banners'], for example: - - >>> indexer_api = TVMaze(images = True) - >>> indexer_api['scrubs']['_banners'].keys() - ['fanart', 'poster', 'series', 'season'] - >>> t['scrubs']['_banners']['poster']['680x1000']['35308']['_bannerpath'] - u'http://theimdb.com/banners/posters/76156-2.jpg' - >>> - Any key starting with an underscore has been processed (not the raw data from the XML) diff --git a/medusa/indexers/indexer_base.py b/medusa/indexers/indexer_base.py index 015c81cc34..225d3e7cfb 100644 --- a/medusa/indexers/indexer_base.py +++ b/medusa/indexers/indexer_base.py @@ -121,7 +121,37 @@ def __init__(self, else: self.config['language'] = language - def _get_temp_dir(self): # pylint: disable=no-self-use + def get_nested_value(self, value, config): + """ + Get a nested value from a dictionary using a dot separated string. + + For example the config 'plot.summaries[0].text' will return the value for dict['plot']['summaries'][0]. + :param value: Dictionary you want to get a value from. + :param config: Dot separated string. + :return: The value matching the config. + """ + # Remove a level + split_config = config.split('.') + check_key = split_config[0] + + if check_key.endswith(']'): + list_index = int(check_key.split('[')[-1].rstrip(']')) + check_key = check_key.split('[')[0] + check_value = value.get(check_key)[list_index] + else: + check_value = value.get(check_key) + next_keys = '.'.join(split_config[1:]) + + if not check_value: + return None + + if isinstance(check_value, dict): + return self.get_nested_value(check_value, next_keys) + else: + return check_value + + @staticmethod + def _get_temp_dir(): # pylint: disable=no-self-use """Return the [system temp dir]/tvdb_api-u501 (or tvdb_api-myuser).""" if hasattr(os, 'getuid'): uid = 'u{0}'.format(os.getuid()) # pylint: disable=no-member From e32561958c5d23a5fbb620081a617e1cfaab5047 Mon Sep 17 00:00:00 2001 From: P0psicles Date: Sun, 14 Jan 2018 16:27:40 +0100 Subject: [PATCH 06/86] Added better support for poster and poster_thumbs. --- medusa/image_cache.py | 2 +- medusa/indexers/imdb/imdb_api.py | 181 ++++++++++++++++----------- medusa/indexers/tmdb/tmdb.py | 2 +- medusa/indexers/tvmaze/tvmaze_api.py | 7 +- medusa/metadata/generic.py | 5 +- 5 files changed, 118 insertions(+), 79 deletions(-) diff --git a/medusa/image_cache.py b/medusa/image_cache.py index 920ee26b94..d1e778461f 100644 --- a/medusa/image_cache.py +++ b/medusa/image_cache.py @@ -273,7 +273,7 @@ def fill_cache(series): :param series: Series object to cache images for """ - series_id = series.name + series_id = series.indexerid # get expected paths for artwork images = { img_type: get_path(img_type, series_id) diff --git a/medusa/indexers/imdb/imdb_api.py b/medusa/indexers/imdb/imdb_api.py index 4aaabc1fb2..6b96129e6f 100644 --- a/medusa/indexers/imdb/imdb_api.py +++ b/medusa/indexers/imdb/imdb_api.py @@ -1,6 +1,8 @@ # coding=utf-8 from __future__ import unicode_literals + +import re from itertools import chain import logging from collections import OrderedDict @@ -92,7 +94,7 @@ def __init__(self, *args, **kwargs): # pylint: disable=too-many-locals,too-many ('seriesname', 'base.title'), ('summary', 'plot.summaries[0].text'), ('firstaired', 'year'), - ('fanart', 'base.image.url'), + ('poster', 'base.image.url'), ('show_url', 'base.id'), ('firstaired', 'base.seriesStartYear'), ('contentrating', 'ratings.rating'), @@ -128,7 +130,8 @@ def _map_results(self, imdb_response, key_mappings=None, list_separator='|'): for item in imdb_response: return_dict = {} try: - if item.get('type') in ('feature', 'video game', 'TV short'): + title_type = item.get('type') or item.get('base',{}).get('titleType') + if title_type in ('feature', 'video game', 'TV short', None): continue # return_dict['id'] = ImdbIdentifier(item.pop('imdb_id')).series_id @@ -138,12 +141,17 @@ def _map_results(self, imdb_response, key_mappings=None, list_separator='|'): value = ImdbIdentifier(value.rstrip('/')).series_id if key == 'contentrating': value = text_type(value) + if key == 'poster': + return_dict['poster_thumb'] = value.split('V1')[0] + 'V1_SY{0}_AL_.jpg'.format('1000').split('/')[-1] if value is not None: return_dict[key] = value # Check if the show is continuing return_dict['status'] = 'Continuing' if item.get('base', {}).get('nextEpisode') else 'Ended' + # Add static value for airs time. + return_dict['airs_time'] = '0:00AM' + except Exception as error: log.warning('Exception trying to parse attribute: {0}, with exception: {1!r}', item, error) @@ -203,11 +211,25 @@ def _get_show_by_id(self, imdb_id, request_language='en'): # pylint: disable=un mapped_results = self._map_results(results, self.series_map) + if not mapped_results: + return + # Get firstaired releases = self.imdb_api.get_title_releases(imdb_id) if releases.get('releases'): - first_released = sorted([r['date'] for r in releases['releases']])[0] - mapped_results['firstaired'] = first_released + first_released = sorted([r for r in releases['releases']])[0] + mapped_results['firstaired'] = first_released['date'] + + companies = self.imdb_api.get_title_companies(imdb_id) + if companies: + distribution = [x['company'] for x in companies['distribution'] + if x['company'].get('region') and first_released['region'] in x['company']['region']] + if distribution: + match_short = re.compile(r'.*\((.+)\)$').search(distribution[0]['name']) + if match_short: + distribution = match_short.group(1) + mapped_results['network'] = distribution + return OrderedDict({'series': mapped_results}) def _get_episodes(self, imdb_id, specials=False, aired_season=None): # pylint: disable=unused-argument @@ -256,6 +278,7 @@ def _parse_images(self, imdb_id): log.debug('Getting show banners for {0}', imdb_id) images = self.imdb_api.get_title_images(ImdbIdentifier(imdb_id).imdb_id) + thumb_height = 640 _images = {} try: @@ -264,14 +287,20 @@ def _parse_images(self, imdb_id): continue image_type = image.get('type') + image_type_thumb = image_type + '_thumb' if image_type not in _images: _images[image_type] = {} + _images[image_type + '_thumb'] = {} # Store the images for each resolution available # Always provide a resolution or 'original'. resolution = '{0}x{1}'.format(image['width'], image['height']) + thumb_width = int((float(image['width']) / image['height']) * thumb_height) + resolution_thumb = '{0}x{1}'.format(thumb_width, thumb_height) + if resolution not in _images[image_type]: _images[image_type][resolution] = {} + _images[image_type_thumb][resolution_thumb] = {} bid = image['id'].split('/')[-1] @@ -280,11 +309,13 @@ def _parse_images(self, imdb_id): _images[image_type][resolution][int(image.sub_key)] = {} if bid not in _images[image_type][resolution][int(image.sub_key)]: _images[image_type][resolution][int(image.sub_key)][bid] = {} - base_path = _images[image_type][resolution][int(image.sub_key)][bid] + base_path = _images[image_type_thumb][resolution][int(image.sub_key)][bid] else: if bid not in _images[image_type][resolution]: _images[image_type][resolution][bid] = {} + _images[image_type_thumb][resolution_thumb][bid] = {} base_path = _images[image_type][resolution][bid] + base_path_thumb = _images[image_type_thumb][resolution_thumb][bid] base_path['bannertype'] = image_type base_path['bannertype2'] = resolution @@ -292,6 +323,12 @@ def _parse_images(self, imdb_id): base_path['bannerpath'] = image.get('url').split('/')[-1] base_path['id'] = bid + base_path_thumb['bannertype'] = image_type_thumb + base_path_thumb['bannertype2'] = resolution_thumb + base_path_thumb['_bannerpath'] = image['url'].split('V1')[0] + 'V1_SY{0}_AL_.jpg'.format(thumb_height) + base_path_thumb['bannerpath'] = image['url'].split('V1')[0] + 'V1_SY{0}_AL_.jpg'.format(thumb_height).split('/')[-1] + base_path_thumb['id'] = bid + except Exception as error: log.warning('Could not parse Poster for show id: {0}, with exception: {1!r}', imdb_id, error) return @@ -485,70 +522,70 @@ def _get_show_data(self, imdb_id, language='en'): # pylint: disable=too-many-br return True - def _get_all_updates(self, start_date=None, end_date=None): - """Retrieve all updates (show,season,episode) from TVMaze.""" - results = [] - try: - updates = self.imdb_api.show_updates() - except (ShowIndexError, UpdateNotFound): - return results - except BaseError as e: - log.warning('Getting show updates failed. Cause: {0}', e) - return results - - if getattr(updates, 'updates', None): - for show_id, update_ts in updates.updates.items(): - if start_date < update_ts.seconds_since_epoch < (end_date or int(time())): - results.append(int(show_id)) - - return results - - # Public methods, usable separate from the default api's interface api['show_id'] - def get_last_updated_series(self, from_time, weeks=1, filter_show_list=None): - """Retrieve a list with updated shows - - :param from_time: epoch timestamp, with the start date/time - :param weeks: number of weeks to get updates for. - :param filter_show_list: Optional list of show objects, to use for filtering the returned list. - """ - total_updates = [] - updates = self._get_all_updates(from_time, from_time + (weeks * 604800)) # + seconds in a week - - if updates and filter_show_list: - new_list = [] - for show in filter_show_list: - if show.indexerid in total_updates: - new_list.append(show.indexerid) - updates = new_list - - return updates - - def get_id_by_external(self, **kwargs): - """Search imdb for a show, using an external id. - - Accepts as kwargs, so you'l need to add the externals as key/values. - :param tvrage: The tvrage id. - :param thetvdb: The tvdb id. - :param imdb: An imdb id (inc. tt). - :returns: A dict with externals, including the imdb id. - """ - mapping = {'thetvdb': 'tvdb_id', 'tvrage': 'tvrage_id', 'imdb': 'imdb_id'} - for external_id in mapping.values(): - if kwargs.get(external_id): - try: - result = self.imdb_api.get_show(**{external_id: kwargs.get(external_id)}) - if result: - externals = {mapping[imdb_external_id]: external_value - for imdb_external_id, external_value - in result.externals.items() - if external_value and mapping.get(imdb_external_id)} - externals['imdb_id'] = result.maze_id - return externals - except ShowNotFound: - log.debug('Could not get imdb externals using external key {0} and id {1}', - external_id, kwargs.get(external_id)) - continue - except BaseError as e: - log.warning('Could not get imdb externals. Cause: {0}', e) - continue - return {} + # def _get_all_updates(self, start_date=None, end_date=None): + # """Retrieve all updates (show,season,episode) from TVMaze.""" + # results = [] + # try: + # updates = self.imdb_api.show_updates() + # except (ShowIndexError, UpdateNotFound): + # return results + # except BaseError as e: + # log.warning('Getting show updates failed. Cause: {0}', e) + # return results + # + # if getattr(updates, 'updates', None): + # for show_id, update_ts in updates.updates.items(): + # if start_date < update_ts.seconds_since_epoch < (end_date or int(time())): + # results.append(int(show_id)) + # + # return results + # + # # Public methods, usable separate from the default api's interface api['show_id'] + # def get_last_updated_series(self, from_time, weeks=1, filter_show_list=None): + # """Retrieve a list with updated shows + # + # :param from_time: epoch timestamp, with the start date/time + # :param weeks: number of weeks to get updates for. + # :param filter_show_list: Optional list of show objects, to use for filtering the returned list. + # """ + # total_updates = [] + # updates = self._get_all_updates(from_time, from_time + (weeks * 604800)) # + seconds in a week + # + # if updates and filter_show_list: + # new_list = [] + # for show in filter_show_list: + # if show.indexerid in total_updates: + # new_list.append(show.indexerid) + # updates = new_list + # + # return updates + + # def get_id_by_external(self, **kwargs): + # """Search imdb for a show, using an external id. + # + # Accepts as kwargs, so you'l need to add the externals as key/values. + # :param tvrage: The tvrage id. + # :param thetvdb: The tvdb id. + # :param imdb: An imdb id (inc. tt). + # :returns: A dict with externals, including the imdb id. + # """ + # mapping = {'thetvdb': 'tvdb_id', 'tvrage': 'tvrage_id', 'imdb': 'imdb_id'} + # for external_id in mapping.values(): + # if kwargs.get(external_id): + # try: + # result = self.imdb_api.get_show(**{external_id: kwargs.get(external_id)}) + # if result: + # externals = {mapping[imdb_external_id]: external_value + # for imdb_external_id, external_value + # in result.externals.items() + # if external_value and mapping.get(imdb_external_id)} + # externals['imdb_id'] = result.maze_id + # return externals + # except ShowNotFound: + # log.debug('Could not get imdb externals using external key {0} and id {1}', + # external_id, kwargs.get(external_id)) + # continue + # except BaseError as e: + # log.warning('Could not get imdb externals. Cause: {0}', e) + # continue + # return {} diff --git a/medusa/indexers/tmdb/tmdb.py b/medusa/indexers/tmdb/tmdb.py index ff4b1db03a..e58af40b2d 100644 --- a/medusa/indexers/tmdb/tmdb.py +++ b/medusa/indexers/tmdb/tmdb.py @@ -65,7 +65,7 @@ def __init__(self, *args, **kwargs): # pylint: disable=too-many-locals,too-many 'last_updated': 'lastupdated', 'network_id': 'networkid', 'vote_average': 'contentrating', - 'poster_path': 'poster', + 'poster_path': 'poster_thumb', 'genres': 'genre', 'type': 'classification', 'networks': 'network', diff --git a/medusa/indexers/tvmaze/tvmaze_api.py b/medusa/indexers/tvmaze/tvmaze_api.py index f9754017cc..ba0a98dc52 100644 --- a/medusa/indexers/tvmaze/tvmaze_api.py +++ b/medusa/indexers/tvmaze/tvmaze_api.py @@ -62,7 +62,7 @@ def __init__(self, *args, **kwargs): # pylint: disable=too-many-locals,too-many 'name': 'seriesname', 'summary': 'overview', 'premiered': 'firstaired', - 'image': 'fanart', + 'image': 'poster_thumb', 'url': 'show_url', 'genres': 'genre', 'epnum': 'absolute_number', @@ -113,9 +113,8 @@ def _map_results(self, tvmaze_response, key_mappings=None, list_separator='|'): return_dict['timezone'] = value.timezone if key == 'image': if value.get('medium'): - return_dict['image_medium'] = value.get('medium') - return_dict['image_original'] = value.get('original') - return_dict['poster'] = value.get('medium') + return_dict['poster_thumb'] = value.get('medium') + return_dict['poster'] = value.get('original') if key == 'externals': return_dict['tvrage_id'] = value.get('tvrage') return_dict['tvdb_id'] = value.get('thetvdb') diff --git a/medusa/metadata/generic.py b/medusa/metadata/generic.py index 6d7fe61f11..03c252c42f 100644 --- a/medusa/metadata/generic.py +++ b/medusa/metadata/generic.py @@ -738,7 +738,10 @@ def _retrieve_show_image(self, image_type, show_obj, episode=None, which=None): if image_type == u'thumbnail' and episode: image_url = self._get_episode_thumb_url(indexer_show_obj, episode) elif image_type == u'poster_thumb': - if getattr(indexer_show_obj, u'poster', None): + if getattr(indexer_show_obj, u'poster_thumb', None): + # For now only applies to tvmaze and imdb + image_url = indexer_show_obj[u'poster_thumb'] + elif getattr(indexer_show_obj, u'poster', None): image_url = re.sub(u'posters', u'_cache/posters', indexer_show_obj[u'poster']) if not image_url: # Try and get images from TMDB From dc25c36267e6d252fc12b665a62838a8fd3da1c8 Mon Sep 17 00:00:00 2001 From: P0psicles Date: Sun, 14 Jan 2018 16:28:07 +0100 Subject: [PATCH 07/86] Added routes that still need to be added to imdbpie.py --- ext/imdbpie/imdbpie.py | 43 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/ext/imdbpie/imdbpie.py b/ext/imdbpie/imdbpie.py index 101aeb5705..acb2241616 100644 --- a/ext/imdbpie/imdbpie.py +++ b/ext/imdbpie/imdbpie.py @@ -130,6 +130,48 @@ def get_title_plot_synopsis(self, imdb_id): self._redirection_title_check(imdb_id) return self._get_resource('/title/{0}/plotsynopsis'.format(imdb_id)) + def get_title_plot_taglines(self, imdb_id): + logger.info('getting title {0} plot taglines'.format(imdb_id)) + self.validate_imdb_id(imdb_id) + self._redirection_title_check(imdb_id) + return self._get_resource('/title/{0}/taglines'.format(imdb_id)) + + def get_title_news(self, imdb_id): + logger.info('getting title {0} plot news'.format(imdb_id)) + self.validate_imdb_id(imdb_id) + self._redirection_title_check(imdb_id) + return self._get_resource('/title/{0}/news'.format(imdb_id)) + + def get_title_trivia(self, imdb_id): + logger.info('getting title {0} plot trivia'.format(imdb_id)) + self.validate_imdb_id(imdb_id) + self._redirection_title_check(imdb_id) + return self._get_resource('/title/{0}/trivia'.format(imdb_id)) + + def get_title_soundtracks(self, imdb_id): + logger.info('getting title {0} plot soundtracks'.format(imdb_id)) + self.validate_imdb_id(imdb_id) + self._redirection_title_check(imdb_id) + return self._get_resource('/title/{0}/soundtracks'.format(imdb_id)) + + def get_title_goofs(self, imdb_id): + logger.info('getting title {0} plot goofs'.format(imdb_id)) + self.validate_imdb_id(imdb_id) + self._redirection_title_check(imdb_id) + return self._get_resource('/title/{0}/goofs'.format(imdb_id)) + + def get_title_technical(self, imdb_id): + logger.info('getting title {0} plot technical'.format(imdb_id)) + self.validate_imdb_id(imdb_id) + self._redirection_title_check(imdb_id) + return self._get_resource('/title/{0}/technical'.format(imdb_id)) + + def get_title_companies(self, imdb_id): + logger.info('getting title {0} plot technical'.format(imdb_id)) + self.validate_imdb_id(imdb_id) + self._redirection_title_check(imdb_id) + return self._get_resource('/title/{0}/companies'.format(imdb_id)) + def title_exists(self, imdb_id): self.validate_imdb_id(imdb_id) page_url = 'http://www.imdb.com/title/{0}/'.format(imdb_id) @@ -238,7 +280,6 @@ def get_title_episodes(self, imdb_id): if self.exclude_episodes: raise ValueError('exclude_episodes is current set to true') return self._get_resource('/title/{0}/episodes'.format(imdb_id)) - @staticmethod def _cache_response(file_path, resp): with open(file_path, 'w+') as f: From 6222031491a8bcbc3a24d8dff8777b112e9306d0 Mon Sep 17 00:00:00 2001 From: supergonkas Date: Sun, 14 Jan 2018 21:29:57 +0000 Subject: [PATCH 08/86] Fix show_url --- medusa/indexers/indexer_config.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/medusa/indexers/indexer_config.py b/medusa/indexers/indexer_config.py index ccb96c11b5..f8e66d18e1 100644 --- a/medusa/indexers/indexer_config.py +++ b/medusa/indexers/indexer_config.py @@ -99,7 +99,7 @@ 'scene_loc': '{base_url}/scene_exceptions/scene_exceptions_tvmaze.json'.format(base_url=BASE_PYMEDUSA_URL), 'show_url': 'http://www.tvmaze.com/shows/', 'base_url': 'http://api.tvmaze.com/', - 'mapped_to': 'tvmaze_id', # The attribute to which other indexers can map there tvmaze id to + 'mapped_to': 'tvmaze_id', # The attribute to which other indexers can map their tvmaze id to 'identifier': 'tvmaze', # Also used as key for the custom scenename exceptions. (_get_custom_exceptions()) }, INDEXER_TMDB: { @@ -116,13 +116,13 @@ 'scene_loc': '{base_url}/scene_exceptions/scene_exceptions_tmdb.json'.format(base_url=BASE_PYMEDUSA_URL), 'base_url': 'https://www.themoviedb.org/', 'show_url': 'https://www.themoviedb.org/tv/', - 'mapped_to': 'tmdb_id', # The attribute to which other indexers can map there tmdb id to + 'mapped_to': 'tmdb_id', # The attribute to which other indexers can map their tmdb id to 'identifier': 'tmdb', # Also used as key for the custom scenename exceptions. (_get_custom_exceptions()) }, INDEXER_IMDB: { 'enabled': True, 'id': INDEXER_IMDB, - 'name': 'IMDB', + 'name': 'IMDb', 'module': Imdb, 'api_params': { 'language': 'en', @@ -132,9 +132,9 @@ 'xem_mapped_to': INDEXER_TVDBV2, 'icon': 'imdb16.png', 'scene_loc': '{base_url}/scene_exceptions/scene_exceptions_imdb.json'.format(base_url=BASE_PYMEDUSA_URL), - 'show_url': 'http://www.imdb.com/shows/', + 'show_url': 'http://www.imdb.com/title/', 'base_url': 'https://v2.sg.media-imdb.com', - 'mapped_to': 'imdb_id', # The attribute to which other indexers can map there tvmaze id to + 'mapped_to': 'imdb_id', # The attribute to which other indexers can map their imdb id to 'identifier': 'imdb', # Also used as key for the custom scenename exceptions. (_get_custom_exceptions()) } } From 2223f4512ac99d9110af059ce59707cd6682ccfb Mon Sep 17 00:00:00 2001 From: P0psicles Date: Thu, 8 Feb 2018 23:48:29 +0100 Subject: [PATCH 09/86] Fix network and images. --- medusa/indexers/imdb/{imdb_api.py => api.py} | 36 +++++++------------ .../{imdb_exceptions.py => exceptions.py} | 0 medusa/indexers/indexer_config.py | 2 +- medusa/tv/series.py | 2 +- 4 files changed, 14 insertions(+), 26 deletions(-) rename medusa/indexers/imdb/{imdb_api.py => api.py} (95%) rename medusa/indexers/imdb/{imdb_exceptions.py => exceptions.py} (100%) diff --git a/medusa/indexers/imdb/imdb_api.py b/medusa/indexers/imdb/api.py similarity index 95% rename from medusa/indexers/imdb/imdb_api.py rename to medusa/indexers/imdb/api.py index 6b96129e6f..b8a09c2e0d 100644 --- a/medusa/indexers/imdb/imdb_api.py +++ b/medusa/indexers/imdb/api.py @@ -113,7 +113,7 @@ def _map_results(self, imdb_response, key_mappings=None, list_separator='|'): :param imdb_response: imdb response obect, or a list of response objects. :type imdb_response: list(object) :param key_mappings: Dict of imdb attributes, that are mapped to normalized keys. - :type key_mappings: dictionary + :type key_mappings: list :param list_separator: A list separator used to transform lists to a character separator string. :type list_separator: string. """ @@ -162,7 +162,7 @@ def _map_results(self, imdb_response, key_mappings=None, list_separator='|'): def _show_search(self, series, request_language='en'): """ Uses the TVMaze API to search for a show - :param show: The show name that's searched for as a string + :param series: The series name that's searched for as a string :param request_language: Language in two letter code. TVMaze fallsback to en itself. :return: A list of Show objects. """ @@ -174,7 +174,6 @@ def _show_search(self, series, request_language='en'): else: return None - # Tvdb implementation def search(self, series): """This searches imdb.com for the series name @@ -221,14 +220,11 @@ def _get_show_by_id(self, imdb_id, request_language='en'): # pylint: disable=un mapped_results['firstaired'] = first_released['date'] companies = self.imdb_api.get_title_companies(imdb_id) - if companies: - distribution = [x['company'] for x in companies['distribution'] - if x['company'].get('region') and first_released['region'] in x['company']['region']] - if distribution: - match_short = re.compile(r'.*\((.+)\)$').search(distribution[0]['name']) - if match_short: - distribution = match_short.group(1) - mapped_results['network'] = distribution + origins = self.imdb_api.get_title_versions(imdb_id)['origins'][0] + first_release = sorted([dist for dist in companies['distribution'] if origins in dist['regions']], key=lambda x: x['startYear']) + + if first_release: + mapped_results['network'] = first_release[0]['company']['name'] return OrderedDict({'series': mapped_results}) @@ -388,16 +384,9 @@ def get_resolution(image): highest_rated = sort_images[0] img_url = highest_rated['_bannerpath'] log.debug( - u'Selecting highest rated {image} (rating={img[rating]}):' - u' {img[_bannerpath]}', { + u'Selecting image with the highest resolution {image} (resolution={resolution}):', { 'image': img_type, - 'img': highest_rated, - } - ) - log.debug( - u'{image} details: {img}', { - 'image': img_type.capitalize(), - 'img': highest_rated, + 'resolution': highest_rated['bannertype2'], } ) @@ -479,10 +468,9 @@ def _get_show_data(self, imdb_id, language='en'): # pylint: disable=too-many-br get_show_in_language = language else: log.debug( - 'Configured language {0} override show language of {1}', ( - self.config['language'], - language - ) + 'Configured language {0} override show language of {1}', + self.config['language'], + language ) get_show_in_language = self.config['language'] diff --git a/medusa/indexers/imdb/imdb_exceptions.py b/medusa/indexers/imdb/exceptions.py similarity index 100% rename from medusa/indexers/imdb/imdb_exceptions.py rename to medusa/indexers/imdb/exceptions.py diff --git a/medusa/indexers/indexer_config.py b/medusa/indexers/indexer_config.py index 460132927a..a95fcf07bb 100644 --- a/medusa/indexers/indexer_config.py +++ b/medusa/indexers/indexer_config.py @@ -3,7 +3,7 @@ """Indexer config module.""" from medusa.app import BASE_PYMEDUSA_URL -from medusa.indexers.imdb.imdb_api import Imdb +from medusa.indexers.imdb.api import Imdb from medusa.indexers.tmdb.tmdb import Tmdb from medusa.indexers.tvdbv2.tvdbv2_api import TVDBv2 from medusa.indexers.tvmaze.tvmaze_api import TVmaze diff --git a/medusa/tv/series.py b/medusa/tv/series.py index 90857cb59a..4f2bcc3b3c 100644 --- a/medusa/tv/series.py +++ b/medusa/tv/series.py @@ -77,7 +77,7 @@ IndexerException, IndexerSeasonNotFound, ) -from medusa.indexers.imdb.imdb_api import ImdbIdentifier +from medusa.indexers.imdb.api import ImdbIdentifier from medusa.indexers.tmdb.tmdb import Tmdb from medusa.indexers.utils import ( indexer_id_to_slug, From 80a607bc0d1848635622ab44bcf19348c808cfe2 Mon Sep 17 00:00:00 2001 From: P0psicles Date: Fri, 9 Feb 2018 14:00:40 +0100 Subject: [PATCH 10/86] Added episode rating, overview and airdate. --- medusa/indexers/imdb/api.py | 172 +++++++++++---------------- medusa/indexers/tvdbv2/tvdbv2_api.py | 2 +- 2 files changed, 73 insertions(+), 101 deletions(-) diff --git a/medusa/indexers/imdb/api.py b/medusa/indexers/imdb/api.py index b8a09c2e0d..ff08e5dd75 100644 --- a/medusa/indexers/imdb/api.py +++ b/medusa/indexers/imdb/api.py @@ -2,14 +2,14 @@ from __future__ import unicode_literals -import re +from datetime import datetime from itertools import chain import logging from collections import OrderedDict from imdbpie import imdbpie -from time import time -from six import text_type - +import locale +from six import string_types, text_type +from medusa.bs4_parser import BS4Parser from medusa.indexers.indexer_base import (Actor, Actors, BaseIndexer) from medusa.indexers.indexer_exceptions import ( IndexerError, @@ -30,6 +30,10 @@ def __init__(self, imdb_id): self._series_id = None self.imdb_id = imdb_id + def _clean(self, imdb_id): + if isinstance(imdb_id, string_types): + return imdb_id.strip('/').split('/')[-1] + @property def series_id(self): return self._series_id @@ -44,9 +48,9 @@ def imdb_id(self): @imdb_id.setter def imdb_id(self, value): - if isinstance(value, text_type) and 'tt' in value: - self._imdb_id = value - self.series_id = int(value.split('tt')[-1]) + if isinstance(value, string_types) and 'tt' in value: + self._imdb_id = self._clean(value) + self.series_id = int(self._imdb_id.split('tt')[-1]) else: self._imdb_id = 'tt{0}'.format(text_type(value).zfill(7)) self.series_id = int(value) @@ -137,6 +141,8 @@ def _map_results(self, imdb_response, key_mappings=None, list_separator='|'): # return_dict['id'] = ImdbIdentifier(item.pop('imdb_id')).series_id for key, config in self.series_map: value = self.get_nested_value(item, config) + if not value: + continue if key == 'id' and value: value = ImdbIdentifier(value.rstrip('/')).series_id if key == 'contentrating': @@ -239,7 +245,7 @@ def _get_episodes(self, imdb_id, specials=False, aired_season=None): # pylint: log.debug('Getting all episodes of {0}', imdb_id) series_id = imdb_id - imdb_id = 'tt{0}'.format(text_type(imdb_id).zfill(7)) + imdb_id = ImdbIdentifier(imdb_id).imdb_id results = self.imdb_api.get_title_episodes(imdb_id) if not results or not results.get('seasons'): @@ -250,19 +256,75 @@ def _get_episodes(self, imdb_id, specials=False, aired_season=None): # pylint: season_no, episode_no = episode.get('season'), episode.get('episode') if season_no is None or episode_no is None: - log.warning('An episode has incomplete season/episode number (season: {0!r}, episode: {1!r})', seasnum, epno) + log.warning('An episode has incomplete season/episode number (season: {0!r}, episode: {1!r})', + season_no, episode_no) continue # Skip to next episode for k, config in self.episode_map: v = self.get_nested_value(episode, config) if v is not None: if k == 'id': - v = ImdbIdentifier(v.rstrip('/')).series_id + v = ImdbIdentifier(v).series_id if k == 'firstaired': v = '{year}-01-01'.format(year=v) self._set_item(series_id, season_no, episode_no, k, v) + if season.get('season'): + # Enrich episode for the current season. + self._enrich_episodes(imdb_id, season['season']) + + def _enrich_episodes(self, imdb_id, season): + """Enrich the episodes with additional information for a specific season.""" + episodes_url = 'http://www.imdb.com/title/{imdb_id}/episodes?season={season}' + series_id = ImdbIdentifier(imdb_id).series_id + try: + response = self.config['session'].get(episodes_url.format(imdb_id=ImdbIdentifier(imdb_id).imdb_id, season=season)) + with BS4Parser(response.text, 'html5lib') as html: + for episode in html.find_all('div', class_='list_item'): + try: + episode_no = int(episode.find('meta')['content']) + except AttributeError: + pass + try: + first_aired_raw = episode.find('div', class_='airdate').get_text(strip=True) + except AttributeError: + pass + + lc = locale.setlocale(locale.LC_TIME) + try: + locale.setlocale(locale.LC_ALL, 'C') + first_aired = datetime.strptime(first_aired_raw.replace('.', ''), '%d %b %Y').strftime('%Y-%m-%d') + except (AttributeError, ValueError): + first_aired = None + finally: + locale.setlocale(locale.LC_TIME, lc) + + try: + episode_rating = float(episode.find('span', class_='ipl-rating-star__rating').get_text(strip=True)) + except AttributeError: + episode_rating = None + + try: + episode_votes = int(episode.find('span', class_='ipl-rating-star__total-votes').get_text(strip=True).strip('()').replace(',', '')) + except AttributeError: + episode_votes = None + + try: + synopsis = episode.find('div', class_='item_description').get_text(strip=True) + if 'Know what this is about?' in synopsis: + synopsis = '' + except AttributeError: + synopsis = '' + + self._set_item(series_id, season, episode_no, 'firstaired', first_aired) + self._set_item(series_id, season, episode_no, 'rating', episode_rating) + self._set_item(series_id, season, episode_no, 'votes', episode_votes) + self._set_item(series_id, season, episode_no, 'overview', synopsis) + + except Exception as error: + log.exception('Error while trying to enrich imdb series {0}, {1}', series_id, error) + def _parse_images(self, imdb_id): """Parse Show and Season posters. @@ -393,28 +455,6 @@ def get_resolution(image): # Save the image self._set_show_data(series_id, img_type, img_url) - def _parse_season_images(self, imdb_id): - """Parse Show and Season posters.""" - seasons = {} - if imdb_id: - log.debug('Getting all show data for {0}', imdb_id) - try: - seasons = self.imdb_api.show_seasons(maze_id=imdb_id) - except BaseError as e: - log.warning('Getting show seasons for the season images failed. Cause: {0}', e) - - _images = {'season': {'original': {}}} - # Get the season posters - for season in seasons.keys(): - if not getattr(seasons[season], 'image', None): - continue - if season not in _images['season']['original']: - _images['season']['original'][season] = {seasons[season].id: {}} - _images['season']['original'][season][seasons[season].id]['_bannerpath'] = seasons[season].image['original'] - _images['season']['original'][season][seasons[season].id]['rating'] = 1 - - return _images - def _parse_actors(self, imdb_id): """Parsers actors XML, from http://theimdb.com/api/[APIKEY]/series/[SERIES ID]/actors.xml @@ -509,71 +549,3 @@ def _get_show_data(self, imdb_id, language='en'): # pylint: disable=too-many-br self._parse_actors(imdb_id) return True - - # def _get_all_updates(self, start_date=None, end_date=None): - # """Retrieve all updates (show,season,episode) from TVMaze.""" - # results = [] - # try: - # updates = self.imdb_api.show_updates() - # except (ShowIndexError, UpdateNotFound): - # return results - # except BaseError as e: - # log.warning('Getting show updates failed. Cause: {0}', e) - # return results - # - # if getattr(updates, 'updates', None): - # for show_id, update_ts in updates.updates.items(): - # if start_date < update_ts.seconds_since_epoch < (end_date or int(time())): - # results.append(int(show_id)) - # - # return results - # - # # Public methods, usable separate from the default api's interface api['show_id'] - # def get_last_updated_series(self, from_time, weeks=1, filter_show_list=None): - # """Retrieve a list with updated shows - # - # :param from_time: epoch timestamp, with the start date/time - # :param weeks: number of weeks to get updates for. - # :param filter_show_list: Optional list of show objects, to use for filtering the returned list. - # """ - # total_updates = [] - # updates = self._get_all_updates(from_time, from_time + (weeks * 604800)) # + seconds in a week - # - # if updates and filter_show_list: - # new_list = [] - # for show in filter_show_list: - # if show.indexerid in total_updates: - # new_list.append(show.indexerid) - # updates = new_list - # - # return updates - - # def get_id_by_external(self, **kwargs): - # """Search imdb for a show, using an external id. - # - # Accepts as kwargs, so you'l need to add the externals as key/values. - # :param tvrage: The tvrage id. - # :param thetvdb: The tvdb id. - # :param imdb: An imdb id (inc. tt). - # :returns: A dict with externals, including the imdb id. - # """ - # mapping = {'thetvdb': 'tvdb_id', 'tvrage': 'tvrage_id', 'imdb': 'imdb_id'} - # for external_id in mapping.values(): - # if kwargs.get(external_id): - # try: - # result = self.imdb_api.get_show(**{external_id: kwargs.get(external_id)}) - # if result: - # externals = {mapping[imdb_external_id]: external_value - # for imdb_external_id, external_value - # in result.externals.items() - # if external_value and mapping.get(imdb_external_id)} - # externals['imdb_id'] = result.maze_id - # return externals - # except ShowNotFound: - # log.debug('Could not get imdb externals using external key {0} and id {1}', - # external_id, kwargs.get(external_id)) - # continue - # except BaseError as e: - # log.warning('Could not get imdb externals. Cause: {0}', e) - # continue - # return {} diff --git a/medusa/indexers/tvdbv2/tvdbv2_api.py b/medusa/indexers/tvdbv2/tvdbv2_api.py index 5bbb164c78..4f72161a28 100644 --- a/medusa/indexers/tvdbv2/tvdbv2_api.py +++ b/medusa/indexers/tvdbv2/tvdbv2_api.py @@ -226,7 +226,7 @@ def _get_episodes(self, tvdb_id, specials=False, aired_season=None): def _get_episodes_info(self, tvdb_id, episodes, season=None): """Add full episode information for existing episodes.""" - series = Show.find_by_id(app.showList, self.indexer, tvdb_id) + series = Show.find_by_id(app.showList, 1, tvdb_id) if not series: return episodes From cad5dea3e0a84d0624091c75b6444db8dbc5c6b8 Mon Sep 17 00:00:00 2001 From: P0psicles Date: Fri, 9 Feb 2018 14:17:19 +0100 Subject: [PATCH 11/86] Fixed bug where adding show without a summary, throwed an error. --- medusa/indexers/indexer_base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/medusa/indexers/indexer_base.py b/medusa/indexers/indexer_base.py index 2aef6398a6..882d77fd55 100644 --- a/medusa/indexers/indexer_base.py +++ b/medusa/indexers/indexer_base.py @@ -143,7 +143,9 @@ def get_nested_value(self, value, config): if check_key.endswith(']'): list_index = int(check_key.split('[')[-1].rstrip(']')) check_key = check_key.split('[')[0] - check_value = value.get(check_key)[list_index] + check_value = value.get(check_key) + if check_value and list_index < len(check_value): + check_value = check_value[list_index] else: check_value = value.get(check_key) next_keys = '.'.join(split_config[1:]) From d3a4e7d13a7ecd8f502befb8a221ed507163f131 Mon Sep 17 00:00:00 2001 From: P0psicles Date: Fri, 9 Feb 2018 15:15:09 +0100 Subject: [PATCH 12/86] Added actors. * Don't display imdb icon, when it's an imdb show, as there is already an icon shown. * added cross-env to package.json for building. --- medusa/indexers/imdb/api.py | 45 +- themes-default/slim/gulpfile.js | 7 +- themes-default/slim/package.json | 1 + .../slim/views/partials/showheader.mako | 12 +- themes-default/slim/yarn.lock | 637 ++---------------- themes/dark/package.json | 1 + themes/dark/templates/layouts/main.mako | 3 +- .../dark/templates/partials/showheader.mako | 12 +- themes/light/package.json | 1 + .../light/templates/partials/showheader.mako | 12 +- 10 files changed, 87 insertions(+), 644 deletions(-) diff --git a/medusa/indexers/imdb/api.py b/medusa/indexers/imdb/api.py index ff08e5dd75..03cbdba849 100644 --- a/medusa/indexers/imdb/api.py +++ b/medusa/indexers/imdb/api.py @@ -149,8 +149,8 @@ def _map_results(self, imdb_response, key_mappings=None, list_separator='|'): value = text_type(value) if key == 'poster': return_dict['poster_thumb'] = value.split('V1')[0] + 'V1_SY{0}_AL_.jpg'.format('1000').split('/')[-1] - if value is not None: - return_dict[key] = value + + return_dict[key] = value # Check if the show is continuing return_dict['status'] = 'Continuing' if item.get('base', {}).get('nextEpisode') else 'Ended' @@ -296,7 +296,10 @@ def _enrich_episodes(self, imdb_id, season): locale.setlocale(locale.LC_ALL, 'C') first_aired = datetime.strptime(first_aired_raw.replace('.', ''), '%d %b %Y').strftime('%Y-%m-%d') except (AttributeError, ValueError): - first_aired = None + try: + first_aired = datetime.strptime(first_aired_raw.replace('.', ''), '%b %Y').strftime('%Y-%m-01') + except AttributeError: + first_aired = None finally: locale.setlocale(locale.LC_TIME, lc) @@ -456,41 +459,25 @@ def get_resolution(image): self._set_show_data(series_id, img_type, img_url) def _parse_actors(self, imdb_id): - """Parsers actors XML, from - http://theimdb.com/api/[APIKEY]/series/[SERIES ID]/actors.xml - - Actors are retrieved using t['show name]['_actors'], for example: - - >>> indexer_api = TVMaze(actors = True) - >>> actors = indexer_api['scrubs']['_actors'] - >>> type(actors) - - >>> type(actors[0]) - - >>> actors[0] - - >>> sorted(actors[0].keys()) - ['id', 'image', 'name', 'role', 'sortorder'] - >>> actors[0]['name'] - u'Zach Braff' - >>> actors[0]['image'] - u'http://theimdb.com/banners/actors/43640.jpg' + """Get and parse actors using the get_title_credits route. + + Actors are retrieved using t['show name]['_actors']. Any key starting with an underscore has been processed (not the raw data from the indexer) """ log.debug('Getting actors for {0}', imdb_id) - return + # FIXME: implement cast - actors = self.imdb_api.show_cast(imdb_id) + actors = self.imdb_api.get_title_credits(ImdbIdentifier(imdb_id).imdb_id) cur_actors = Actors() - for order, cur_actor in enumerate(actors.people): + for order, cur_actor in enumerate(actors['credits']['cast'][:25]): save_actor = Actor() - save_actor['id'] = cur_actor.id - save_actor['image'] = cur_actor.image.get('original') if cur_actor.image else '' - save_actor['name'] = cur_actor.name - save_actor['role'] = cur_actor.character.name + save_actor['id'] = cur_actor['id'].split('/')[-2] + save_actor['image'] = cur_actor.get('image', {}).get('url', None) + save_actor['name'] = cur_actor['name'] + save_actor['role'] = cur_actor['characters'][0] save_actor['sortorder'] = order cur_actors.append(save_actor) self._set_show_data(imdb_id, '_actors', cur_actors) diff --git a/themes-default/slim/gulpfile.js b/themes-default/slim/gulpfile.js index ee04f2782b..57a53c1419 100644 --- a/themes-default/slim/gulpfile.js +++ b/themes-default/slim/gulpfile.js @@ -57,13 +57,16 @@ const staticAssets = [ const getCssTheme = theme => { // Using the --csstheme is mandatory. if (argv.csstheme === undefined) { - console.log('You need to pass a csstheme to build with the param --csstheme'); + console.log('You need to pass a csstheme to build with the param --csstheme. ' + + '\nMake sure your running with the command gulp and not yarn/npm, as those do not work nice with yargs.\n' + + 'For example `gulp build --csstheme dark`'); process.exit(1); } // Check if the theme provided is available in the package.json config. if (!config.cssThemes[theme]) { - console.log(`Please provide a valid theme with the --cssTheme parameter, theme ${theme} is not available in the package.json config section.`); + console.log(`Please provide a valid theme with the --cssTheme parameter, theme ${theme} is not available ` + + `in the package.json config section.`); process.exit(1); } return config.cssThemes[theme]; diff --git a/themes-default/slim/package.json b/themes-default/slim/package.json index 180e28b78f..cf62c07260 100644 --- a/themes-default/slim/package.json +++ b/themes-default/slim/package.json @@ -31,6 +31,7 @@ "babelify": "^8.0.0", "browserify": "^14.5.0", "concurrently": "^3.5.0", + "cross-env": "^5.1.3", "cssnano": "^3.10.0", "eslint": "^4.16.0", "eslint-config-xo": "^0.19.0", diff --git a/themes-default/slim/views/partials/showheader.mako b/themes-default/slim/views/partials/showheader.mako index 81f546ef42..89d3483c0d 100644 --- a/themes-default/slim/views/partials/showheader.mako +++ b/themes-default/slim/views/partials/showheader.mako @@ -124,14 +124,16 @@ [imdb] % endif - % if show.externals.get('trakt_id'): + % if show.externals.get('trakt_id'): [trakt] - % endif - - ${indexerApi(show.indexer).name} - + % endif + % if not show.indexer_name == 'imdb': + + ${indexerApi(show.indexer).name} + + %endif % if xem_numbering or xem_absolute_numbering: [xem] diff --git a/themes-default/slim/yarn.lock b/themes-default/slim/yarn.lock index c97116beb7..f4b9a896e5 100644 --- a/themes-default/slim/yarn.lock +++ b/themes-default/slim/yarn.lock @@ -75,10 +75,6 @@ JSONStream@^1.0.3: jsonparse "^1.2.0" through ">=2.2.7 <3" -"JSV@>= 4.0.x": - version "4.0.2" - resolved "https://registry.yarnpkg.com/JSV/-/JSV-4.0.2.tgz#d077f6825571f82132f9dffaed587b4029feff57" - abbrev@1, abbrev@^1.0.7: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -137,10 +133,6 @@ alphanum-sort@^1.0.1, alphanum-sort@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" -amanda@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/amanda/-/amanda-0.5.1.tgz#20033776c0e4b043e534a872e36eb6c6abe84103" - amdefine@>=0.0.4: version "1.0.1" resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" @@ -232,10 +224,6 @@ anymatch@^1.3.0: micromatch "^2.1.5" normalize-path "^2.0.0" -api-blueprint-http-formatter@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/api-blueprint-http-formatter/-/api-blueprint-http-formatter-0.0.1.tgz#2a7eb3cf82dec17da3622fedb7ea0b2d3069c9b7" - append-transform@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-0.4.0.tgz#d76ebf8ca94d276e247a36bad44a4b74ab611991" @@ -390,10 +378,6 @@ assert@^1.4.0: dependencies: util "0.10.3" -assertion-error@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" - assign-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" @@ -420,20 +404,10 @@ async@^1.4.0, async@~1.5: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" -async@^2.3.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4" - dependencies: - lodash "^4.14.0" - async@~0.9.0: version "0.9.2" resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" -async@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/async/-/async-1.0.0.tgz#f8fc04ca3a13784ade9e1641af98578cfbd647a9" - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -828,7 +802,7 @@ babel-register@^6.26.0: mkdirp "^0.5.1" source-map-support "^0.4.15" -babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0: +babel-runtime@^6.22.0, babel-runtime@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" dependencies: @@ -1282,10 +1256,6 @@ call-matcher@^1.0.0: espurify "^1.6.0" estraverse "^4.0.0" -call-me-maybe@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" - call-signature@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/call-signature/-/call-signature-0.0.2.tgz#a84abc825a55ef4cb2b028bd74e205a65b9a4996" @@ -1344,14 +1314,14 @@ capture-stack-trace@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz#4a6fa07399c26bba47f0b2496b4d0fb408c5550d" -caseless@^0.12.0, caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - caseless@~0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + caw@^1.0.1: version "1.2.0" resolved "https://registry.yarnpkg.com/caw/-/caw-1.2.0.tgz#ffb226fe7efc547288dc62ee3e97073c212d1034" @@ -1368,14 +1338,6 @@ center-align@^0.1.1: align-text "^0.1.3" lazy-cache "^1.0.3" -chai@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/chai/-/chai-3.5.0.tgz#4d02637b067fe958bdbfdd3a40ec56fef7373247" - dependencies: - assertion-error "^1.0.1" - deep-eql "^0.1.3" - type-detect "^1.0.0" - chalk@0.5.1, chalk@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.5.1.tgz#663b3a648b68b55d04690d49167aa837858f2174" @@ -1386,7 +1348,7 @@ chalk@0.5.1, chalk@^0.5.1: strip-ansi "^0.3.0" supports-color "^0.2.0" -chalk@^0.4.0, chalk@~0.4.0: +chalk@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.4.0.tgz#5199a3ddcd0c1efe23bc08c1b027b06176e0c64f" dependencies: @@ -1412,10 +1374,6 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0: escape-string-regexp "^1.0.5" supports-color "^4.0.0" -chance@^1.0.11: - version "1.0.13" - resolved "https://registry.yarnpkg.com/chance/-/chance-1.0.13.tgz#666bec2db42b3084456a3e4f4c28a82db5ccb7e6" - chardet@^0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" @@ -1627,10 +1585,6 @@ code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" -coffee-script@^1.12.5: - version "1.12.7" - resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.12.7.tgz#c05dae0cb79591d05b3070a8433a98c9a89ccc53" - collection-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" @@ -1693,11 +1647,7 @@ colormin@^1.0.5: css-color-names "0.0.4" has "^1.0.1" -colors@1.0.x: - version "1.0.3" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" - -colors@^1.1.2, colors@~1.1.2: +colors@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" @@ -1725,17 +1675,11 @@ combined-stream@^1.0.5, combined-stream@~1.0.5: dependencies: delayed-stream "~1.0.0" -commander@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-1.2.0.tgz#fd5713bfa153c7d6cc599378a5ab4c45c535029e" - dependencies: - keypress "0.1.x" - commander@2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.6.0.tgz#9df7e52fb2a0cb0fb89058ee80c3104225f37e1d" -commander@^2.7.1, commander@^2.9.0, commander@~2.13.0: +commander@^2.9.0, commander@~2.13.0: version "2.13.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" @@ -1953,6 +1897,13 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" +cross-env@^5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-5.1.3.tgz#f8ae18faac87692b0a8b4d2f7000d4ec3a85dfd7" + dependencies: + cross-spawn "^5.1.0" + is-windows "^1.0.0" + cross-spawn@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982" @@ -2078,45 +2029,12 @@ csso@~2.3.1: clap "^1.0.9" source-map "^0.5.3" -csv-generate@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/csv-generate/-/csv-generate-0.0.6.tgz#97e4e63ae46b21912cd9475bc31469d26f5ade66" - -csv-parse@^1.0.0: - version "1.3.3" - resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-1.3.3.tgz#d1cfd8743c2f849a0abb2fd544db56695d19a490" - -csv-stringify@^0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/csv-stringify/-/csv-stringify-0.0.8.tgz#52cc3b3dfc197758c55ad325a95be85071f9e51b" - -csv@^0.4.2: - version "0.4.6" - resolved "https://registry.yarnpkg.com/csv/-/csv-0.4.6.tgz#8dbae7ddfdbaae62c1ea987c3e0f8a9ac737b73d" - dependencies: - csv-generate "^0.0.6" - csv-parse "^1.0.0" - csv-stringify "^0.0.8" - stream-transform "^0.1.0" - -curl-trace-parser@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/curl-trace-parser/-/curl-trace-parser-0.0.8.tgz#8420f4890fd998822fb4b48bfd667123e029370b" - dependencies: - api-blueprint-http-formatter "0.0.1" - commander "1.2.0" - http-string-parser "0.0.4" - currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" dependencies: array-find-index "^1.0.1" -cycle@1.0.x: - version "1.0.3" - resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2" - d@1: version "1.0.0" resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f" @@ -2163,7 +2081,7 @@ debug-log@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debug-log/-/debug-log-1.0.1.tgz#2307632d4c04382b8df8a32f70b895046d52745f" -debug@3.X, debug@^3.0.0, debug@^3.0.1, debug@^3.1.0: +debug@3.X, debug@^3.0.1, debug@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" dependencies: @@ -2185,10 +2103,6 @@ decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" -deckardcain@^0.3.2: - version "0.3.3" - resolved "https://registry.yarnpkg.com/deckardcain/-/deckardcain-0.3.3.tgz#dbe0c99c5155dbda7fd09c7052e56fbbe5753e5c" - decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" @@ -2259,20 +2173,10 @@ deep-assign@^1.0.0: dependencies: is-obj "^1.0.0" -deep-eql@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-0.1.3.tgz#ef558acab8de25206cd713906d74e56930eb69f2" - dependencies: - type-detect "0.1.1" - -deep-equal@^1.0.0, deep-equal@^1.0.1: +deep-equal@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" -deep-extend@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.5.0.tgz#6ef4a09b05f98b0e358d6d93d4ca3caec6672803" - deep-extend@~0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" @@ -2352,12 +2256,6 @@ deps-sort@^2.0.0: subarg "^1.0.0" through2 "^2.0.0" -deref@^0.7.0: - version "0.7.3" - resolved "https://registry.yarnpkg.com/deref/-/deref-0.7.3.tgz#3ef3fc3cbc149b2f999b6bc051451c08c13c8bc2" - dependencies: - deep-extend "^0.5.0" - des.js@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" @@ -2402,10 +2300,6 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" -discontinuous-range@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a" - doctrine@1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" @@ -2500,58 +2394,6 @@ download@^4.0.0, download@^4.1.2: vinyl-fs "^2.2.0" ware "^1.2.0" -drafter.js@^2.6.0: - version "2.6.7" - resolved "https://registry.yarnpkg.com/drafter.js/-/drafter.js-2.6.7.tgz#59c43a8586527340cb35bddc54180559faeef001" - -drafter@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/drafter/-/drafter-1.2.0.tgz#f4d11e8dee1f27f9a40485b8740c44361e9cf859" - dependencies: - drafter.js "^2.6.0" - optionalDependencies: - protagonist "^1.6.0" - -dredd-transactions@^4.2.0: - version "4.6.1" - resolved "https://registry.yarnpkg.com/dredd-transactions/-/dredd-transactions-4.6.1.tgz#e532b5b833b807ac1e0f5e526eb5349f4651bd1d" - dependencies: - caseless "^0.12.0" - clone "^2.1.1" - fury "3.0.0-beta.4" - fury-adapter-apib-parser "0.9.0" - fury-adapter-swagger "0.15.0" - uri-template "^1.0.0" - -dredd@^3.3.0: - version "3.5.1" - resolved "https://registry.yarnpkg.com/dredd/-/dredd-3.5.1.tgz#65b17575d24af4c01ac95c8d40c7ab3304107587" - dependencies: - async "^2.3.0" - caseless "^0.12.0" - chai "^3.5.0" - clone "^2.1.1" - coffee-script "^1.12.5" - colors "^1.1.2" - cross-spawn "^5.0.1" - dredd-transactions "^4.2.0" - file "^0.2.2" - gavel "^1.1.1" - glob "^7.0.5" - html "^1.0.0" - htmlencode "0.0.4" - inquirer "^1.1.0" - js-yaml "^3.8.3" - markdown-it "^8.3.1" - optimist "^0.6.1" - pitboss-ng "^0.3.2" - proxyquire "^1.7.10" - request "^2.81.0" - spawn-args "^0.2.0" - uuid "^3.0.0" - which "^1.2.14" - winston "^2.2.0" - duplexer2@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db" @@ -3119,14 +2961,6 @@ extend@^3.0.0, extend@~3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" -external-editor@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-1.1.1.tgz#12d7b0db850f7ff7e7081baf4005700060c4600b" - dependencies: - extend "^3.0.0" - spawn-sync "^1.0.15" - tmp "^0.0.29" - external-editor@^2.0.4: version "2.1.0" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.1.0.tgz#3d026a21b7f95b5726387d4200ac160d372c3b48" @@ -3158,14 +2992,6 @@ extsprintf@1.3.0, extsprintf@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" -eyes@0.1.x: - version "0.1.8" - resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" - -faker@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/faker/-/faker-4.1.0.tgz#1e45bbbecc6774b3c195fad2835109c6d748cc3f" - fancy-log@^1.1.0, fancy-log@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.2.tgz#f41125e3d84f2e7d89a43d06d958c8f78be16be1" @@ -3230,10 +3056,6 @@ file-type@^4.1.0: version "4.4.0" resolved "https://registry.yarnpkg.com/file-type/-/file-type-4.4.0.tgz#1b600e5fca1fbdc6e80c0a70c71c8dba5f7906c5" -file@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/file/-/file-0.2.2.tgz#c3dfd8f8cf3535ae455c2b423c2e52635d76b4d3" - filename-regex@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" @@ -3250,13 +3072,6 @@ filenamify@^1.0.1: strip-outer "^1.0.0" trim-repeated "^1.0.0" -fill-keys@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/fill-keys/-/fill-keys-1.0.2.tgz#9a8fa36f4e8ad634e3bf6b4f3c8882551452eb20" - dependencies: - is-object "~1.0.1" - merge-descriptors "~1.0.0" - fill-range@^2.1.0: version "2.2.3" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" @@ -3398,10 +3213,6 @@ for-own@^1.0.0: dependencies: for-in "^1.0.1" -foreach@^2.0.4: - version "2.0.5" - resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" - foreground-child@^1.5.3, foreground-child@^1.5.6: version "1.5.6" resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-1.5.6.tgz#4fd71ad2dfde96789b980a5c0a295937cb2f5ce9" @@ -3425,10 +3236,6 @@ form-data@~2.1.1: combined-stream "^1.0.5" mime-types "^2.1.12" -format-util@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/format-util/-/format-util-1.0.3.tgz#032dca4a116262a12c43f4c3ec8566416c5b2d95" - fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -3479,36 +3286,6 @@ functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" -fury-adapter-apib-parser@0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/fury-adapter-apib-parser/-/fury-adapter-apib-parser-0.9.0.tgz#23933f499a8a1aed87949893f14158dc46b1ee10" - dependencies: - babel-runtime "^6.23.0" - deckardcain "^0.3.2" - drafter "^1.2.0" - minim "^0.19.0" - -fury-adapter-swagger@0.15.0: - version "0.15.0" - resolved "https://registry.yarnpkg.com/fury-adapter-swagger/-/fury-adapter-swagger-0.15.0.tgz#11e2cd2744726aebec473a2ba5b7bb9582c3acc5" - dependencies: - babel-runtime "^6.23.0" - js-yaml "^3.4.2" - json-schema-faker "^0.4.0" - lodash "^4.15.0" - media-typer "^0.3.0" - swagger-parser "^3.3.0" - yaml-js "^0.1.5" - z-schema "^3.16.1" - -fury@3.0.0-beta.4: - version "3.0.0-beta.4" - resolved "https://registry.yarnpkg.com/fury/-/fury-3.0.0-beta.4.tgz#c474d92b38b621850681da1e573e5de700e48c9f" - dependencies: - babel-runtime "^6.23.0" - minim "^0.19.0" - minim-parse-result "^0.8.0" - gather-stream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/gather-stream/-/gather-stream-1.0.0.tgz#b33994af457a8115700d410f317733cbe7a0904b" @@ -3526,25 +3303,6 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" -gavel@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/gavel/-/gavel-1.1.1.tgz#01777d4be16cae90ba199b635effc65adbd040fe" - dependencies: - amanda "^0.5.1" - async "^2.3.0" - caseless "^0.12.0" - clone "^2.1.1" - commander "^2.9.0" - curl-trace-parser "0.0.8" - deep-equal "^1.0.1" - googlediff "^0.1.0" - http-string-parser "0.0.5" - is-type "0.0.1" - json-pointer "^0.6.0" - jsonlint "git+https://git@github.com/josdejong/jsonlint.git" - media-typer "^0.3.0" - tv4 "^1.3.0" - gaze@^0.5.1: version "0.5.2" resolved "https://registry.yarnpkg.com/gaze/-/gaze-0.5.2.tgz#40b709537d24d1d45767db5a908689dfe69ac44f" @@ -3805,10 +3563,6 @@ glogg@^1.0.0: dependencies: sparkles "^1.0.0" -googlediff@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/googlediff/-/googlediff-0.1.0.tgz#99acf05cc06223eb66c29008d81f9b2d18c2453d" - got@^3.2.0: version "3.3.1" resolved "https://registry.yarnpkg.com/got/-/got-3.3.1.tgz#e5d0ed4af55fc3eef4d56007769d98192bcb2eca" @@ -4271,16 +4025,6 @@ html-tags@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-2.0.0.tgz#10b30a386085f43cede353cc8fa7cb0deeea668b" -html@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/html/-/html-1.0.0.tgz#a544fa9ea5492bfb3a2cca8210a10be7b5af1f61" - dependencies: - concat-stream "^1.4.7" - -htmlencode@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/htmlencode/-/htmlencode-0.0.4.tgz#f7e2d6afbe18a87a78e63ba3308e753766740e3f" - htmlescape@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/htmlescape/-/htmlescape-1.1.1.tgz#3a03edc2214bca3b66424a3e7959349509cb0351" @@ -4314,14 +4058,6 @@ http-signature@~1.1.0: jsprim "^1.2.2" sshpk "^1.7.0" -http-string-parser@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/http-string-parser/-/http-string-parser-0.0.4.tgz#6b2538e3520d42b349a0ac4b7234e0e39476c5b3" - -http-string-parser@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/http-string-parser/-/http-string-parser-0.0.5.tgz#8f2da0781fe0a6e480343f53d2ecf93af86461c8" - https-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" @@ -4525,25 +4261,6 @@ inquirer@^0.12.0: strip-ansi "^3.0.0" through "^2.3.6" -inquirer@^1.1.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-1.2.3.tgz#4dec6f32f37ef7bb0b2ed3f1d1a5c3f545074918" - dependencies: - ansi-escapes "^1.1.0" - chalk "^1.0.0" - cli-cursor "^1.0.1" - cli-width "^2.0.0" - external-editor "^1.1.0" - figures "^1.3.5" - lodash "^4.3.0" - mute-stream "0.0.6" - pinkie-promise "^2.0.0" - run-async "^2.2.0" - rx "^4.1.0" - string-width "^1.0.1" - strip-ansi "^3.0.0" - through "^2.3.6" - inquirer@^3.0.6: version "3.3.0" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" @@ -4825,10 +4542,6 @@ is-obj@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" -is-object@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" - is-observable@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/is-observable/-/is-observable-0.2.0.tgz#b361311d83c6e5d726cabf5e250b0237106f5ae2" @@ -4938,12 +4651,6 @@ is-tar@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-tar/-/is-tar-1.0.0.tgz#2f6b2e1792c1f5bb36519acaa9d65c0d26fe853d" -is-type@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/is-type/-/is-type-0.0.1.tgz#f651d85c365d44955d14a51d8d7061f3f6b4779c" - dependencies: - core-util-is "~1.0.0" - is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" @@ -4966,7 +4673,7 @@ is-valid-glob@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-0.3.0.tgz#d4b55c69f51886f9b65c70d6c2622d37e29f48fe" -is-windows@^1.0.1: +is-windows@^1.0.0, is-windows@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.1.tgz#310db70f742d259a16a369202b51af84233310d9" @@ -5000,7 +4707,7 @@ isobject@^3.0.0, isobject@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" -isstream@0.1.x, isstream@~0.1.2: +isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -5088,7 +4795,7 @@ js-types@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/js-types/-/js-types-1.0.0.tgz#d242e6494ed572ad3c92809fc8bed7f7687cbf03" -js-yaml@^3.10.0, js-yaml@^3.4.2, js-yaml@^3.4.3, js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.3, js-yaml@^3.6.1, js-yaml@^3.8.2, js-yaml@^3.8.3, js-yaml@^3.9.1: +js-yaml@^3.10.0, js-yaml@^3.4.3, js-yaml@^3.5.1, js-yaml@^3.5.3, js-yaml@^3.6.1, js-yaml@^3.8.2, js-yaml@^3.9.1: version "3.10.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" dependencies: @@ -5131,32 +4838,6 @@ json-parse-better-errors@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.1.tgz#50183cd1b2d25275de069e9e71b467ac9eab973a" -json-pointer@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/json-pointer/-/json-pointer-0.6.0.tgz#8e500550a6aac5464a473377da57aa6cc22828d7" - dependencies: - foreach "^2.0.4" - -json-schema-faker@^0.4.0: - version "0.4.6" - resolved "https://registry.yarnpkg.com/json-schema-faker/-/json-schema-faker-0.4.6.tgz#34ba1b3aa32690f390db5939376c10544a73e8bb" - dependencies: - chance "^1.0.11" - deref "^0.7.0" - faker "^4.1.0" - randexp "^0.4.6" - tslib "^1.8.0" - -json-schema-ref-parser@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/json-schema-ref-parser/-/json-schema-ref-parser-1.4.1.tgz#c0c2e438bf0796723b02451bae8bc7dd0b37fed0" - dependencies: - call-me-maybe "^1.0.1" - debug "^2.2.0" - es6-promise "^3.0.2" - js-yaml "^3.4.6" - ono "^2.0.1" - json-schema-traverse@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" @@ -5202,13 +4883,6 @@ jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" -"jsonlint@git+https://git@github.com/josdejong/jsonlint.git": - version "1.6.2" - resolved "git+https://git@github.com/josdejong/jsonlint.git#85a19d77126771f3177582e3d09c6ffae185d391" - dependencies: - JSV ">= 4.0.x" - nomnom ">= 1.5.x" - jsonparse@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-0.0.5.tgz#330542ad3f0a654665b778f3eb2d9a9fa507ac64" @@ -5230,10 +4904,6 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -keypress@0.1.x: - version "0.1.0" - resolved "https://registry.yarnpkg.com/keypress/-/keypress-0.1.0.tgz#4a3188d4291b66b4f65edb99f806aa9ae293592a" - kind-of@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-1.1.0.tgz#140a3d2d41a36d2efcfa9377b62c24f8495a5c44" @@ -5363,12 +5033,6 @@ liftoff@^2.1.0: rechoir "^0.6.2" resolve "^1.1.7" -linkify-it@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.0.3.tgz#d94a4648f9b1c179d64fa97291268bdb6ce9434f" - dependencies: - uc.micro "^1.0.1" - livereload-js@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/livereload-js/-/livereload-js-2.3.0.tgz#c3ab22e8aaf5bf3505d80d098cbad67726548c9a" @@ -5521,10 +5185,6 @@ lodash.flattendeep@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" -lodash.get@^4.0.0: - version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" - lodash.isarguments@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" @@ -5606,7 +5266,7 @@ lodash@3.7.x: version "3.7.0" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.7.0.tgz#3678bd8ab995057c07ade836ed2ef087da811d45" -lodash@^4.0.0, lodash@^4.1.0, lodash@^4.11.1, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.14.2, lodash@^4.15.0, lodash@^4.17.4, lodash@^4.3.0, lodash@^4.5.1, lodash@~4.17.4: +lodash@^4.0.0, lodash@^4.1.0, lodash@^4.11.1, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.4, lodash@^4.3.0, lodash@^4.5.1, lodash@~4.17.4: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" @@ -5720,16 +5380,6 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -markdown-it@^8.3.1: - version "8.4.0" - resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-8.4.0.tgz#e2400881bf171f7018ed1bd9da441dac8af6306d" - dependencies: - argparse "^1.0.7" - entities "~1.1.1" - linkify-it "^2.0.0" - mdurl "^1.0.1" - uc.micro "^1.0.3" - matcher@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/matcher/-/matcher-1.0.0.tgz#aaf0c4816eb69b92094674175625f3466b0e3e19" @@ -5767,11 +5417,7 @@ md5.js@^1.3.4: hash-base "^3.0.0" inherits "^2.0.1" -mdurl@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" - -media-typer@0.3.0, media-typer@^0.3.0: +media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -5809,10 +5455,6 @@ meow@^3.1.0, meow@^3.3.0, meow@^3.4.2, meow@^3.5.0, meow@^3.7.0: redent "^1.0.0" trim-newlines "^1.0.0" -merge-descriptors@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - merge-source-map@^1.0.2: version "1.1.0" resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.1.0.tgz#2fdde7e6020939f70906a68f2d7ae685e4c8c646" @@ -5893,26 +5535,6 @@ mini-lr@^0.1.8: parseurl "~1.3.0" qs "~2.2.3" -minim-api-description@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/minim-api-description/-/minim-api-description-0.6.0.tgz#ad5349fdba501e50c402e842c2829f48cbeeb603" - dependencies: - babel-runtime "^6.23.0" - -minim-parse-result@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/minim-parse-result/-/minim-parse-result-0.8.0.tgz#e1838f1fe4cbec6bf759503bb4d41c7429c8aa97" - dependencies: - babel-runtime "^6.23.0" - minim-api-description "^0.6.0" - -minim@^0.19.0: - version "0.19.2" - resolved "https://registry.yarnpkg.com/minim/-/minim-0.19.2.tgz#d69fcd70eb164d1bb4976f627ee2b544c0f44495" - dependencies: - lodash "^4.15.0" - uptown "^0.4.1" - minimalistic-assert@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3" @@ -5998,10 +5620,6 @@ module-deps@^4.0.8: through2 "^2.0.0" xtend "^4.0.0" -module-not-found-error@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/module-not-found-error/-/module-not-found-error-1.0.1.tgz#cf8b4ff4f29640674d6cdd02b0e3bc523c2bbdc0" - ms@0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" @@ -6041,7 +5659,7 @@ mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" -nan@^2.3.0, nan@^2.3.2, nan@^2.6.2: +nan@^2.3.0, nan@^2.3.2: version "2.8.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a" @@ -6156,13 +5774,6 @@ node-status-codes@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/node-status-codes/-/node-status-codes-1.0.0.tgz#5ae5541d024645d32a58fcddc9ceecea7ae3ac2f" -"nomnom@>= 1.5.x": - version "1.8.1" - resolved "https://registry.yarnpkg.com/nomnom/-/nomnom-1.8.1.tgz#2151f722472ba79e50a76fc125bb8c8f2e4dc2a7" - dependencies: - chalk "~0.4.0" - underscore "~1.6.0" - "nopt@2 || 3", nopt@~3.0.1: version "3.0.6" resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" @@ -6384,16 +5995,6 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" -ono@^2.0.1: - version "2.2.5" - resolved "https://registry.yarnpkg.com/ono/-/ono-2.2.5.tgz#daf09488b51174da7a7e4275dfab31b438ffa0e3" - -ono@^4.0.2: - version "4.0.3" - resolved "https://registry.yarnpkg.com/ono/-/ono-4.0.3.tgz#b36050f71b02841bfb59f368deab8b07375e2219" - dependencies: - format-util "^1.0.3" - open@^0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/open/-/open-0.0.5.tgz#42c3e18ec95466b6bf0dc42f3a2945c3f0cad8fc" @@ -6480,11 +6081,7 @@ os-name@^1.0.3: osx-release "^1.0.0" win-release "^1.0.0" -os-shim@^0.1.2: - version "0.1.3" - resolved "https://registry.yarnpkg.com/os-shim/-/os-shim-0.1.3.tgz#6b62c3791cf7909ea35ed46e17658bb417cb3917" - -os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2: +os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -6713,10 +6310,6 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" -pct-encode@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/pct-encode/-/pct-encode-1.0.2.tgz#b99b7b044d6bd7c39e4839a7a80122ad7515caa5" - pend@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" @@ -6725,10 +6318,6 @@ performance-now@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" -pidusage@1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/pidusage/-/pidusage-1.1.5.tgz#b8c8d32bdfaf36212ca9e741028876ea33217e66" - pify@^2.0.0, pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -6764,14 +6353,6 @@ pipetteur@^2.0.0: onecolor "^3.0.4" synesthesia "^1.0.1" -pitboss-ng@^0.3.2: - version "0.3.3" - resolved "https://registry.yarnpkg.com/pitboss-ng/-/pitboss-ng-0.3.3.tgz#741de76cd1b910eccc815565e93cd655d0e71f73" - dependencies: - clone "^1.0.2" - csv "^0.4.2" - pidusage "1.1.5" - pkg-conf@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/pkg-conf/-/pkg-conf-2.1.0.tgz#2126514ca6f2abfebd168596df18ba57867f0058" @@ -7190,12 +6771,6 @@ progress@^2.0.0: dependencies: asap "~2.0.3" -protagonist@^1.6.0: - version "1.6.8" - resolved "https://registry.yarnpkg.com/protagonist/-/protagonist-1.6.8.tgz#16d5d6316f1b6d4e493ec9450639dbc35503be6e" - dependencies: - nan "^2.6.2" - proto-list@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" @@ -7208,14 +6783,6 @@ proxy-from-env@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" -proxyquire@^1.7.10: - version "1.8.0" - resolved "https://registry.yarnpkg.com/proxyquire/-/proxyquire-1.8.0.tgz#02d514a5bed986f04cbb2093af16741535f79edc" - dependencies: - fill-keys "^1.0.2" - module-not-found-error "^1.0.0" - resolve "~1.1.7" - pseudomap@^1.0.1, pseudomap@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" @@ -7277,13 +6844,6 @@ querystringify@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-1.0.0.tgz#6286242112c5b712fa654e526652bf6a13ff05cb" -randexp@^0.4.6: - version "0.4.6" - resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3" - dependencies: - discontinuous-range "1.0.0" - ret "~0.1.10" - randomatic@^1.1.3: version "1.1.7" resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" @@ -7544,18 +7104,18 @@ replace-ext@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" -request@2, request@2.81.0, request@^2.81.0: - version "2.81.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" +request@2, request@~2.79.0: + version "2.79.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" dependencies: aws-sign2 "~0.6.0" aws4 "^1.2.1" - caseless "~0.12.0" + caseless "~0.11.0" combined-stream "~1.0.5" extend "~3.0.0" forever-agent "~0.6.1" form-data "~2.1.1" - har-validator "~4.2.1" + har-validator "~2.0.6" hawk "~3.1.3" http-signature "~1.1.0" is-typedarray "~1.0.0" @@ -7563,26 +7123,24 @@ request@2, request@2.81.0, request@^2.81.0: json-stringify-safe "~5.0.1" mime-types "~2.1.7" oauth-sign "~0.8.1" - performance-now "^0.2.0" - qs "~6.4.0" - safe-buffer "^5.0.1" + qs "~6.3.0" stringstream "~0.0.4" tough-cookie "~2.3.0" - tunnel-agent "^0.6.0" + tunnel-agent "~0.4.1" uuid "^3.0.0" -request@~2.79.0: - version "2.79.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" +request@2.81.0: + version "2.81.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" dependencies: aws-sign2 "~0.6.0" aws4 "^1.2.1" - caseless "~0.11.0" + caseless "~0.12.0" combined-stream "~1.0.5" extend "~3.0.0" forever-agent "~0.6.1" form-data "~2.1.1" - har-validator "~2.0.6" + har-validator "~4.2.1" hawk "~3.1.3" http-signature "~1.1.0" is-typedarray "~1.0.0" @@ -7590,10 +7148,12 @@ request@~2.79.0: json-stringify-safe "~5.0.1" mime-types "~2.1.7" oauth-sign "~0.8.1" - qs "~6.3.0" + performance-now "^0.2.0" + qs "~6.4.0" + safe-buffer "^5.0.1" stringstream "~0.0.4" tough-cookie "~2.3.0" - tunnel-agent "~0.4.1" + tunnel-agent "^0.6.0" uuid "^3.0.0" require-directory@^2.1.1: @@ -7658,7 +7218,7 @@ resolve-url@^0.2.1, resolve-url@~0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" -resolve@1.1.7, resolve@~1.1.7: +resolve@1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" @@ -7682,10 +7242,6 @@ restore-cursor@^2.0.0: onetime "^2.0.0" signal-exit "^3.0.2" -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - rev-hash@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/rev-hash/-/rev-hash-2.0.0.tgz#7720a236ed0c258df3e64bec03ec048b05b924c4" @@ -8192,21 +7748,10 @@ sparkles@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.0.tgz#1acbbfb592436d10bbe8f785b7cc6f82815012c3" -spawn-args@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/spawn-args/-/spawn-args-0.2.0.tgz#fb7d0bd1d70fd4316bd9e3dec389e65f9d6361bb" - spawn-command@^0.0.2-1: version "0.0.2" resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2.tgz#9544e1a43ca045f8531aac1a48cb29bdae62338e" -spawn-sync@^1.0.15: - version "1.0.15" - resolved "https://registry.yarnpkg.com/spawn-sync/-/spawn-sync-1.0.15.tgz#b00799557eb7fb0c8376c29d44e8a1ea67e57476" - dependencies: - concat-stream "^1.4.7" - os-shim "^0.1.2" - spawn-wrap@^1.4.2: version "1.4.2" resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-1.4.2.tgz#cff58e73a8224617b6561abdc32586ea0c82248c" @@ -8280,10 +7825,6 @@ sshpk@^1.7.0: jsbn "~0.1.0" tweetnacl "~0.14.0" -stack-trace@0.0.x: - version "0.0.10" - resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" - stack-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.1.tgz#d4f33ab54e8e38778b0ca5cfd3b3afb12db68620" @@ -8361,10 +7902,6 @@ stream-splicer@^2.0.0: inherits "^2.0.1" readable-stream "^2.0.2" -stream-transform@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/stream-transform/-/stream-transform-0.1.2.tgz#7d8e6b4e03ac4781778f8c79517501bfb0762a9f" - strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" @@ -8638,27 +8175,6 @@ swagger-js-codegen@^1.6.9: mustache "^2.0.0" update-notifier "^2.1.0" -swagger-methods@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/swagger-methods/-/swagger-methods-1.0.4.tgz#2c5b844f4a22ab2f5e773f98193c28e386b1c37e" - -swagger-parser@^3.3.0: - version "3.4.2" - resolved "https://registry.yarnpkg.com/swagger-parser/-/swagger-parser-3.4.2.tgz#244d67d6eeed08c00acb5d95950d5aefbd6185a3" - dependencies: - call-me-maybe "^1.0.1" - debug "^3.0.0" - es6-promise "^4.1.1" - json-schema-ref-parser "^1.4.1" - ono "^4.0.2" - swagger-methods "^1.0.0" - swagger-schema-official "2.0.0-bab6bed" - z-schema "^3.16.1" - -swagger-schema-official@2.0.0-bab6bed: - version "2.0.0-bab6bed" - resolved "https://registry.yarnpkg.com/swagger-schema-official/-/swagger-schema-official-2.0.0-bab6bed.tgz#70070468d6d2977ca5237b2e519ca7d06a2ea3fd" - symbol-observable@^0.2.2: version "0.2.4" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-0.2.4.tgz#95a83db26186d6af7e7a18dbd9760a2f86d08f40" @@ -8867,12 +8383,6 @@ timers-ext@^0.1.2: es5-ext "~0.10.14" next-tick "1" -tmp@^0.0.29: - version "0.0.29" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.29.tgz#f25125ff0dd9da3ccb0c2dd371ee1288bb9128c0" - dependencies: - os-tmpdir "~1.0.1" - tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -8962,10 +8472,6 @@ trim-right@^1.0.1: dependencies: glob "^6.0.4" -tslib@^1.8.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8" - tty-browserify@~0.0.0: version "0.0.1" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.1.tgz#3f05251ee17904dfd0677546670db9651682b811" @@ -8980,10 +8486,6 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" -tv4@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/tv4/-/tv4-1.3.0.tgz#d020c846fadd50c855abb25ebaecc68fc10f7963" - tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" @@ -8994,14 +8496,6 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-detect@0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-0.1.1.tgz#0ba5ec2a885640e470ea4e8505971900dac58822" - -type-detect@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2" - type-is@~1.6.10: version "1.6.15" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" @@ -9013,10 +8507,6 @@ typedarray@^0.0.6, typedarray@~0.0.5: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" -uc.micro@^1.0.1, uc.micro@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.3.tgz#7ed50d5e0f9a9fb0a573379259f2a77458d50192" - uglify-es@^3.2.0: version "3.3.9" resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677" @@ -9057,10 +8547,6 @@ undefsafe@0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-0.0.3.tgz#ecca3a03e56b9af17385baac812ac83b994a962f" -underscore@~1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8" - union-value@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" @@ -9165,18 +8651,6 @@ update-notifier@^2.1.0, update-notifier@^2.3.0: semver-diff "^2.0.0" xdg-basedir "^3.0.0" -uptown@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/uptown/-/uptown-0.4.1.tgz#2aa732b75c05c34432373498e2b419fbd9cd5452" - dependencies: - lodash "^4.14.2" - -uri-template@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/uri-template/-/uri-template-1.0.1.tgz#14a925a37e4d93f7625432aa116b05e50cae81ad" - dependencies: - pct-encode "~1.0.0" - urix@^0.1.0, urix@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" @@ -9260,10 +8734,6 @@ validate-npm-package-license@^3.0.1: spdx-correct "~1.0.0" spdx-expression-parse "~1.0.0" -validator@^9.0.0: - version "9.2.0" - resolved "https://registry.yarnpkg.com/validator/-/validator-9.2.0.tgz#ad216eed5f37cac31a6fe00ceab1f6b88bded03e" - vendors@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.1.tgz#37ad73c8ee417fb3d580e785312307d274847f22" @@ -9467,17 +8937,6 @@ window-size@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075" -winston@^2.2.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/winston/-/winston-2.4.0.tgz#808050b93d52661ed9fb6c26b3f0c826708b0aee" - dependencies: - async "~1.0.0" - colors "1.0.x" - cycle "1.0.x" - eyes "0.1.x" - isstream "0.1.x" - stack-trace "0.0.x" - wordwrap@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" @@ -9633,10 +9092,6 @@ yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" -yaml-js@^0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/yaml-js/-/yaml-js-0.1.5.tgz#a01369010b3558d8aaed2394615dfd0780fd8fac" - yargs-parser@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-2.4.1.tgz#85568de3cf150ff49fa51825f03a8c880ddcc5c4" @@ -9774,16 +9229,6 @@ yauzl@^2.2.1: buffer-crc32 "~0.2.3" fd-slicer "~1.0.1" -z-schema@^3.16.1: - version "3.19.0" - resolved "https://registry.yarnpkg.com/z-schema/-/z-schema-3.19.0.tgz#d86e90e5d02113c7b8824ae477dd57208d17a5a8" - dependencies: - lodash.get "^4.0.0" - lodash.isequal "^4.0.0" - validator "^9.0.0" - optionalDependencies: - commander "^2.7.1" - zip@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/zip/-/zip-1.2.0.tgz#ad0ad42265309be42eb56fc86194e17c24e66a9c" diff --git a/themes/dark/package.json b/themes/dark/package.json index 180e28b78f..cf62c07260 100644 --- a/themes/dark/package.json +++ b/themes/dark/package.json @@ -31,6 +31,7 @@ "babelify": "^8.0.0", "browserify": "^14.5.0", "concurrently": "^3.5.0", + "cross-env": "^5.1.3", "cssnano": "^3.10.0", "eslint": "^4.16.0", "eslint-config-xo": "^0.19.0", diff --git a/themes/dark/templates/layouts/main.mako b/themes/dark/templates/layouts/main.mako index 859202e6d4..481242d30c 100644 --- a/themes/dark/templates/layouts/main.mako +++ b/themes/dark/templates/layouts/main.mako @@ -1,6 +1,5 @@ <%! from medusa import app - from medusa.server.core import clean_url_path %> @@ -49,7 +48,7 @@ <%block name="css" /> - +
diff --git a/themes/dark/templates/partials/showheader.mako b/themes/dark/templates/partials/showheader.mako index 81f546ef42..89d3483c0d 100644 --- a/themes/dark/templates/partials/showheader.mako +++ b/themes/dark/templates/partials/showheader.mako @@ -124,14 +124,16 @@ [imdb] % endif - % if show.externals.get('trakt_id'): + % if show.externals.get('trakt_id'): [trakt] - % endif - - ${indexerApi(show.indexer).name} - + % endif + % if not show.indexer_name == 'imdb': + + ${indexerApi(show.indexer).name} + + %endif % if xem_numbering or xem_absolute_numbering: [xem] diff --git a/themes/light/package.json b/themes/light/package.json index 180e28b78f..cf62c07260 100644 --- a/themes/light/package.json +++ b/themes/light/package.json @@ -31,6 +31,7 @@ "babelify": "^8.0.0", "browserify": "^14.5.0", "concurrently": "^3.5.0", + "cross-env": "^5.1.3", "cssnano": "^3.10.0", "eslint": "^4.16.0", "eslint-config-xo": "^0.19.0", diff --git a/themes/light/templates/partials/showheader.mako b/themes/light/templates/partials/showheader.mako index 81f546ef42..89d3483c0d 100644 --- a/themes/light/templates/partials/showheader.mako +++ b/themes/light/templates/partials/showheader.mako @@ -124,14 +124,16 @@ [imdb] % endif - % if show.externals.get('trakt_id'): + % if show.externals.get('trakt_id'): [trakt] - % endif - - ${indexerApi(show.indexer).name} - + % endif + % if not show.indexer_name == 'imdb': + + ${indexerApi(show.indexer).name} + + %endif % if xem_numbering or xem_absolute_numbering: [xem] From 06a99f4276d06b2a1dedadce4f60eb382d22cfe3 Mon Sep 17 00:00:00 2001 From: P0psicles Date: Sat, 10 Feb 2018 15:57:02 +0100 Subject: [PATCH 13/86] Added fanart and production_art (as fanart). --- medusa/indexers/imdb/api.py | 80 ++++++++++++------------------- medusa/indexers/indexer_config.py | 4 +- 2 files changed, 32 insertions(+), 52 deletions(-) diff --git a/medusa/indexers/imdb/api.py b/medusa/indexers/imdb/api.py index 03cbdba849..4a390fc489 100644 --- a/medusa/indexers/imdb/api.py +++ b/medusa/indexers/imdb/api.py @@ -68,23 +68,6 @@ def __init__(self, *args, **kwargs): # pylint: disable=too-many-locals,too-many self.indexer = 10 - # List of language from http://theimdb.com/api/0629B785CE550C8D/languages.xml - # Hard-coded here as it is realtively static, and saves another HTTP request, as - # recommended on http://theimdb.com/wiki/index.php/API:languages.xml - self.config['valid_languages'] = [ - 'da', 'fi', 'nl', 'de', 'it', 'es', 'fr', 'pl', 'hu', 'el', 'tr', - 'ru', 'he', 'ja', 'pt', 'zh', 'cs', 'sl', 'hr', 'ko', 'en', 'sv', 'no' - ] - - # thetvdb.com should be based around numeric language codes, - # but to link to a series like http://thetvdb.com/?tab=series&id=79349&lid=16 - # requires the language ID, thus this mapping is required (mainly - # for usage in tvdb_ui - internally tvdb_api will use the language abbreviations) - self.config['langabbv_to_id'] = {'el': 20, 'en': 7, 'zh': 27, - 'it': 15, 'cs': 28, 'es': 16, 'ru': 22, 'nl': 13, 'pt': 26, 'no': 9, - 'tr': 21, 'pl': 18, 'fr': 17, 'hr': 31, 'de': 14, 'da': 10, 'fi': 11, - 'hu': 19, 'ja': 25, 'he': 24, 'ko': 32, 'sv': 8, 'sl': 30} - # Initiate the imdbpie API self.imdb_api = imdbpie.Imdb(session=self.config['session']) @@ -126,11 +109,6 @@ def _map_results(self, imdb_response, key_mappings=None, list_separator='|'): if not isinstance(imdb_response, list): imdb_response = [imdb_response] - # TVmaze does not number their special episodes. It does map it to a season. And that's something, medusa - # Doesn't support. So for now, we increment based on the order, we process the specials. And map it to - # season 0. We start with episode 1. - index_special_episodes = 1 - for item in imdb_response: return_dict = {} try: @@ -138,7 +116,6 @@ def _map_results(self, imdb_response, key_mappings=None, list_separator='|'): if title_type in ('feature', 'video game', 'TV short', None): continue - # return_dict['id'] = ImdbIdentifier(item.pop('imdb_id')).series_id for key, config in self.series_map: value = self.get_nested_value(item, config) if not value: @@ -165,7 +142,7 @@ def _map_results(self, imdb_response, key_mappings=None, list_separator='|'): return parsed_response if len(parsed_response) != 1 else parsed_response[0] - def _show_search(self, series, request_language='en'): + def _show_search(self, series): """ Uses the TVMaze API to search for a show :param series: The series name that's searched for as a string @@ -189,7 +166,7 @@ def search(self, series): series = series.encode('utf-8') log.debug('Searching for show {0}', series) - results = self._show_search(series, request_language=self.config['language']) + results = self._show_search(series) if not results: return @@ -198,7 +175,7 @@ def search(self, series): return OrderedDict({'series': mapped_results})['series'] - def _get_show_by_id(self, imdb_id, request_language='en'): # pylint: disable=unused-argument + def _get_show_by_id(self, imdb_id): # pylint: disable=unused-argument """ Retrieve imdb show information by imdb id, or if no imdb id provided by passed external id. @@ -234,7 +211,7 @@ def _get_show_by_id(self, imdb_id, request_language='en'): # pylint: disable=un return OrderedDict({'series': mapped_results}) - def _get_episodes(self, imdb_id, specials=False, aired_season=None): # pylint: disable=unused-argument + def _get_episodes(self, imdb_id): # pylint: disable=unused-argument """ Get all the episodes for a show by imdb id @@ -275,9 +252,16 @@ def _get_episodes(self, imdb_id, specials=False, aired_season=None): # pylint: self._enrich_episodes(imdb_id, season['season']) def _enrich_episodes(self, imdb_id, season): - """Enrich the episodes with additional information for a specific season.""" + """ + Enrich the episodes with additional information for a specific season. + + For this we're making use of html scraping using beautiful soup. + :param imdb_id: imdb id including the `tt`. + :param season: season passed as integer. + """ episodes_url = 'http://www.imdb.com/title/{imdb_id}/episodes?season={season}' series_id = ImdbIdentifier(imdb_id).series_id + series_status = 'Ended' try: response = self.config['session'].get(episodes_url.format(imdb_id=ImdbIdentifier(imdb_id).imdb_id, season=season)) with BS4Parser(response.text, 'html5lib') as html: @@ -286,6 +270,7 @@ def _enrich_episodes(self, imdb_id, season): episode_no = int(episode.find('meta')['content']) except AttributeError: pass + try: first_aired_raw = episode.find('div', class_='airdate').get_text(strip=True) except AttributeError: @@ -298,8 +283,14 @@ def _enrich_episodes(self, imdb_id, season): except (AttributeError, ValueError): try: first_aired = datetime.strptime(first_aired_raw.replace('.', ''), '%b %Y').strftime('%Y-%m-01') - except AttributeError: - first_aired = None + series_status = 'Continuing' + except (AttributeError, ValueError): + try: + datetime.strptime(first_aired_raw.replace('.', ''), '%Y').strftime('%Y') + first_aired = None + series_status = 'Continuing' + except (AttributeError, ValueError): + first_aired = None finally: locale.setlocale(locale.LC_TIME, lc) @@ -309,7 +300,9 @@ def _enrich_episodes(self, imdb_id, season): episode_rating = None try: - episode_votes = int(episode.find('span', class_='ipl-rating-star__total-votes').get_text(strip=True).strip('()').replace(',', '')) + episode_votes = int(episode.find('span', class_='ipl-rating-star__total-votes').get_text( + strip=True + ).strip('()').replace(',', '')) except AttributeError: episode_votes = None @@ -324,6 +317,7 @@ def _enrich_episodes(self, imdb_id, season): self._set_item(series_id, season, episode_no, 'rating', episode_rating) self._set_item(series_id, season, episode_no, 'votes', episode_votes) self._set_item(series_id, season, episode_no, 'overview', synopsis) + self._set_show_data(series_id, 'status', series_status) except Exception as error: log.exception('Error while trying to enrich imdb series {0}, {1}', series_id, error) @@ -339,15 +333,15 @@ def _parse_images(self, imdb_id): log.debug('Getting show banners for {0}', imdb_id) images = self.imdb_api.get_title_images(ImdbIdentifier(imdb_id).imdb_id) + image_mapping = {'poster': 'poster', 'still_frame': 'fanart', 'production_art': 'fanart'} thumb_height = 640 _images = {} try: for image in images.get('images', []): - if image.get('type') not in ('poster',): + image_type = image_mapping.get(image.get('type')) + if image_type not in ('poster', 'fanart'): continue - - image_type = image.get('type') image_type_thumb = image_type + '_thumb' if image_type not in _images: _images[image_type] = {} @@ -468,7 +462,6 @@ def _parse_actors(self, imdb_id): """ log.debug('Getting actors for {0}', imdb_id) - # FIXME: implement cast actors = self.imdb_api.get_title_credits(ImdbIdentifier(imdb_id).imdb_id) cur_actors = Actors() @@ -488,24 +481,11 @@ def _get_show_data(self, imdb_id, language='en'): # pylint: disable=too-many-br shows[series_id][season_number][episode_number] """ - if self.config['language'] is None: - log.debug('Config language is none, using show language') - if language is None: - raise IndexerError("config['language'] was None, this should not happen") - get_show_in_language = language - else: - log.debug( - 'Configured language {0} override show language of {1}', - self.config['language'], - language - ) - get_show_in_language = self.config['language'] - # Parse show information log.debug('Getting all series data for {0}', imdb_id) # Parse show information - series_info = self._get_show_by_id(imdb_id, request_language=get_show_in_language) + series_info = self._get_show_by_id(imdb_id) if not series_info: log.debug('Series result returned zero') @@ -525,7 +505,7 @@ def _get_show_data(self, imdb_id, language='en'): # pylint: disable=too-many-br # get episode data if self.config['episodes_enabled']: - self._get_episodes(imdb_id, specials=False, aired_season=None) + self._get_episodes(imdb_id) # Parse banners if self.config['banners_enabled']: diff --git a/medusa/indexers/indexer_config.py b/medusa/indexers/indexer_config.py index a95fcf07bb..4a3fd4b68e 100644 --- a/medusa/indexers/indexer_config.py +++ b/medusa/indexers/indexer_config.py @@ -7,7 +7,7 @@ from medusa.indexers.tmdb.tmdb import Tmdb from medusa.indexers.tvdbv2.tvdbv2_api import TVDBv2 from medusa.indexers.tvmaze.tvmaze_api import TVmaze -from medusa.session.core import MedusaSession +from medusa.session.core import MedusaSafeSession, MedusaSession initConfig = { @@ -125,7 +125,7 @@ 'api_params': { 'language': 'en', 'use_zip': True, - 'session': MedusaSession(cache_control={'cache_etags': False}), + 'session': MedusaSafeSession(cache_control={'cache_etags': False}), }, 'xem_mapped_to': INDEXER_TVDBV2, 'icon': 'imdb16.png', From 730620a8ae126af09baec8daeb0d2fa6f3ec2aec Mon Sep 17 00:00:00 2001 From: P0psicles Date: Sat, 10 Feb 2018 17:11:28 +0100 Subject: [PATCH 14/86] Use the poster initially provided with the show, as the images api, doesn't have any ratings attached to the images. Use the production art as fanart. * Rename the exceptions to be used as imdb exceptions. --- medusa/indexers/imdb/api.py | 23 ++++++++++++++------ medusa/indexers/imdb/exceptions.py | 34 +++++++++++++++--------------- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/medusa/indexers/imdb/api.py b/medusa/indexers/imdb/api.py index 4a390fc489..fa00373c3b 100644 --- a/medusa/indexers/imdb/api.py +++ b/medusa/indexers/imdb/api.py @@ -85,6 +85,7 @@ def __init__(self, *args, **kwargs): # pylint: disable=too-many-locals,too-many ('show_url', 'base.id'), ('firstaired', 'base.seriesStartYear'), ('contentrating', 'ratings.rating'), + ('nextepisode', 'base.nextEpisode'), ] self.episode_map = [ @@ -112,10 +113,12 @@ def _map_results(self, imdb_response, key_mappings=None, list_separator='|'): for item in imdb_response: return_dict = {} try: - title_type = item.get('type') or item.get('base',{}).get('titleType') + title_type = item.get('type') or item.get('base', {}).get('titleType') if title_type in ('feature', 'video game', 'TV short', None): continue + return_dict['status'] = 'Ended' + for key, config in self.series_map: value = self.get_nested_value(item, config) if not value: @@ -126,12 +129,11 @@ def _map_results(self, imdb_response, key_mappings=None, list_separator='|'): value = text_type(value) if key == 'poster': return_dict['poster_thumb'] = value.split('V1')[0] + 'V1_SY{0}_AL_.jpg'.format('1000').split('/')[-1] + if key == 'nextepisode' and value: + return_dict['status'] = 'Continuing' return_dict[key] = value - # Check if the show is continuing - return_dict['status'] = 'Continuing' if item.get('base', {}).get('nextEpisode') else 'Ended' - # Add static value for airs time. return_dict['airs_time'] = '0:00AM' @@ -264,6 +266,10 @@ def _enrich_episodes(self, imdb_id, season): series_status = 'Ended' try: response = self.config['session'].get(episodes_url.format(imdb_id=ImdbIdentifier(imdb_id).imdb_id, season=season)) + if not response or not response.text: + log.warning('Problem requesting episode information for show {0}, and season {1}.', imdb_id, season) + return + with BS4Parser(response.text, 'html5lib') as html: for episode in html.find_all('div', class_='list_item'): try: @@ -329,11 +335,13 @@ def _parse_images(self, imdb_id): data from the XML) This interface will be improved in future versions. + Available sources: amazon, custom, getty, paidcustomer, presskit, userupload. + Available types: behind_the_scenes, event, poster, product, production_art, publicity, still_frame """ log.debug('Getting show banners for {0}', imdb_id) images = self.imdb_api.get_title_images(ImdbIdentifier(imdb_id).imdb_id) - image_mapping = {'poster': 'poster', 'still_frame': 'fanart', 'production_art': 'fanart'} + image_mapping = {'poster': 'poster', 'production_art': 'fanart'} # Removed 'still_frame': 'fanart', thumb_height = 640 _images = {} @@ -449,8 +457,9 @@ def get_resolution(image): } ) - # Save the image - self._set_show_data(series_id, img_type, img_url) + # Save the image, but not for the poster, as we're using the poster that comes with the series data. + if img_type != 'poster': + self._set_show_data(series_id, img_type, img_url) def _parse_actors(self, imdb_id): """Get and parse actors using the get_title_credits route. diff --git a/medusa/indexers/imdb/exceptions.py b/medusa/indexers/imdb/exceptions.py index 7a8edbc91e..9b25058bfc 100644 --- a/medusa/indexers/imdb/exceptions.py +++ b/medusa/indexers/imdb/exceptions.py @@ -16,59 +16,59 @@ # You should have received a copy of the GNU General Public License # along with Medusa. If not, see . -"""Custom exceptions used or raised by tvdbv2_api.""" +"""Custom exceptions used or raised by imdb_api.""" __author__ = "p0psicles" __version__ = "1.0" -__all__ = ["tvdbv2_error", "tvdbv2_userabort", "tvdbv2_shownotfound", "tvdbv2_showincomplete", - "tvdbv2_seasonnotfound", "tvdbv2_episodenotfound", "tvdbv2_attributenotfound"] +__all__ = ["imdb_error", "imdb_userabort", "imdb_shownotfound", "imdb_showincomplete", + "imdb_seasonnotfound", "imdb_episodenotfound", "imdb_attributenotfound"] -class tvdbv2_exception(Exception): - """Any exception generated by tvdbv2_api +class imdb_exception(Exception): + """Any exception generated by imdb_api """ pass -class tvdbv2_error(tvdbv2_exception): - """An error with thetvdb.com (Cannot connect, for example) +class imdb_error(imdb_exception): + """An error with the indexer (Cannot connect, for example) """ pass -class tvdbv2_userabort(tvdbv2_exception): +class imdb_userabort(imdb_exception): """User aborted the interactive selection (via the q command, ^c etc) """ pass -class tvdbv2_shownotfound(tvdbv2_exception): - """Show cannot be found on thetvdb.com (non-existant show) +class imdb_shownotfound(imdb_exception): + """Show cannot be found on the indexer (non-existant show) """ pass -class tvdbv2_showincomplete(tvdbv2_exception): - """Show found but incomplete on thetvdb.com (incomplete show) +class imdb_showincomplete(imdb_exception): + """Show found but incomplete on the indexer (incomplete show) """ pass -class tvdbv2_seasonnotfound(tvdbv2_exception): - """Season cannot be found on thetvdb.com +class imdb_seasonnotfound(imdb_exception): + """Season cannot be found on indexer. """ pass -class tvdbv2_episodenotfound(tvdbv2_exception): - """Episode cannot be found on thetvdb.com +class imdb_episodenotfound(imdb_exception): + """Episode cannot be found on the indexer """ pass -class tvdbv2_attributenotfound(tvdbv2_exception): +class imdb_attributenotfound(imdb_exception): """Raised if an episode does not have the requested attribute (such as a episode name) """ From eace45e6c01c97dc5a9b359f795286e323efab9c Mon Sep 17 00:00:00 2001 From: P0psicles Date: Sat, 10 Feb 2018 19:28:42 +0100 Subject: [PATCH 15/86] Refactor indexers. * Use get_nested_value function for tvmaze. --- medusa/__main__.py | 2 +- medusa/databases/main_db.py | 2 +- medusa/helpers/__init__.py | 10 +- medusa/helpers/externals.py | 6 +- medusa/indexers/{indexer_api.py => api.py} | 2 +- medusa/indexers/{indexer_base.py => base.py} | 13 ++- .../indexers/{indexer_config.py => config.py} | 8 +- .../{indexer_exceptions.py => exceptions.py} | 0 medusa/indexers/imdb/api.py | 16 ++- medusa/indexers/tmdb/{tmdb.py => api.py} | 4 +- .../{tmdb_exceptions.py => exceptions.py} | 0 .../indexers/tvdbv2/{tvdbv2_api.py => api.py} | 10 +- .../{tvdbv2_exceptions.py => exceptions.py} | 0 medusa/indexers/tvdbv2/fallback.py | 2 +- .../indexers/tvmaze/{tvmaze_api.py => api.py} | 98 ++++++++----------- medusa/indexers/tvmaze/tvmaze_exceptions.py | 75 -------------- medusa/indexers/{indexer_ui.py => ui.py} | 2 +- medusa/indexers/utils.py | 2 +- medusa/metadata/generic.py | 6 +- medusa/metadata/kodi_12plus.py | 8 +- medusa/metadata/mede8er.py | 4 +- medusa/metadata/media_browser.py | 4 +- medusa/metadata/tivo.py | 4 +- medusa/metadata/wdtv.py | 4 +- medusa/name_parser/parser.py | 4 +- medusa/providers/generic_provider.py | 2 +- medusa/providers/nzb/newznab.py | 2 +- medusa/providers/torrent/json/btn.py | 2 +- medusa/scene_exceptions.py | 4 +- medusa/scene_numbering.py | 2 +- medusa/server/api/v1/core.py | 6 +- medusa/server/api/v2/config.py | 2 +- medusa/server/web/home/add_shows.py | 6 +- medusa/server/web/home/handler.py | 4 +- medusa/show/recommendations/anidb.py | 2 +- medusa/show/recommendations/imdb.py | 2 +- medusa/show/recommendations/trakt.py | 4 +- medusa/show/show.py | 2 +- medusa/show_queue.py | 4 +- medusa/show_updater.py | 4 +- medusa/trakt_checker.py | 2 +- medusa/tv/base.py | 2 +- medusa/tv/episode.py | 6 +- medusa/tv/series.py | 8 +- tests/apiv2/test_config.py | 2 +- tests/conftest.py | 2 +- .../legacy/views/addShows_newShow.mako | 2 +- .../legacy/views/config_general.mako | 2 +- .../legacy/views/config_notifications.mako | 2 +- themes-default/legacy/views/displayShow.mako | 2 +- themes-default/legacy/views/editShow.mako | 2 +- .../legacy/views/home_massAddTable.mako | 2 +- .../legacy/views/partials/home/banner.mako | 2 +- .../legacy/views/partials/home/simple.mako | 2 +- .../legacy/views/partials/home/small.mako | 2 +- .../legacy/views/partials/showheader.mako | 2 +- themes-default/legacy/views/schedule.mako | 2 +- .../slim/views/addShows_newShow.mako | 2 +- themes-default/slim/views/config_general.mako | 2 +- .../slim/views/config_notifications.mako | 2 +- themes-default/slim/views/displayShow.mako | 2 +- themes-default/slim/views/editShow.mako | 2 +- .../slim/views/home_massAddTable.mako | 2 +- .../slim/views/partials/home/banner.mako | 2 +- .../slim/views/partials/home/simple.mako | 2 +- .../slim/views/partials/home/small.mako | 2 +- .../slim/views/partials/showheader.mako | 2 +- themes-default/slim/views/schedule.mako | 2 +- themes/dark/templates/addShows_newShow.mako | 2 +- themes/dark/templates/config_general.mako | 2 +- .../dark/templates/config_notifications.mako | 2 +- themes/dark/templates/displayShow.mako | 2 +- themes/dark/templates/editShow.mako | 2 +- themes/dark/templates/home_massAddTable.mako | 2 +- .../dark/templates/partials/home/banner.mako | 2 +- .../dark/templates/partials/home/simple.mako | 2 +- .../dark/templates/partials/home/small.mako | 2 +- .../dark/templates/partials/showheader.mako | 2 +- themes/dark/templates/schedule.mako | 2 +- themes/light/templates/addShows_newShow.mako | 2 +- themes/light/templates/config_general.mako | 2 +- .../light/templates/config_notifications.mako | 2 +- themes/light/templates/displayShow.mako | 2 +- themes/light/templates/editShow.mako | 2 +- themes/light/templates/home_massAddTable.mako | 2 +- .../light/templates/partials/home/banner.mako | 2 +- .../light/templates/partials/home/simple.mako | 2 +- .../light/templates/partials/home/small.mako | 2 +- .../light/templates/partials/showheader.mako | 2 +- themes/light/templates/schedule.mako | 2 +- 90 files changed, 186 insertions(+), 258 deletions(-) rename medusa/indexers/{indexer_api.py => api.py} (96%) rename medusa/indexers/{indexer_base.py => base.py} (98%) rename medusa/indexers/{indexer_config.py => config.py} (95%) rename medusa/indexers/{indexer_exceptions.py => exceptions.py} (100%) rename medusa/indexers/tmdb/{tmdb.py => api.py} (99%) rename medusa/indexers/tmdb/{tmdb_exceptions.py => exceptions.py} (100%) rename medusa/indexers/tvdbv2/{tvdbv2_api.py => api.py} (98%) rename medusa/indexers/tvdbv2/{tvdbv2_exceptions.py => exceptions.py} (100%) rename medusa/indexers/tvmaze/{tvmaze_api.py => api.py} (86%) delete mode 100644 medusa/indexers/tvmaze/tvmaze_exceptions.py rename medusa/indexers/{indexer_ui.py => ui.py} (98%) diff --git a/medusa/__main__.py b/medusa/__main__.py index beffe66af6..673390573f 100755 --- a/medusa/__main__.py +++ b/medusa/__main__.py @@ -74,7 +74,7 @@ ) from medusa.databases import cache_db, failed_db, main_db from medusa.event_queue import Events -from medusa.indexers.indexer_config import INDEXER_TVDBV2, INDEXER_TVMAZE +from medusa.indexers.config import INDEXER_TVDBV2, INDEXER_TVMAZE from medusa.providers.generic_provider import GenericProvider from medusa.providers.nzb.newznab import NewznabProvider from medusa.providers.torrent.rss.rsstorrent import TorrentRssProvider diff --git a/medusa/databases/main_db.py b/medusa/databases/main_db.py index 7fbd58ea34..d201a34499 100644 --- a/medusa/databases/main_db.py +++ b/medusa/databases/main_db.py @@ -8,7 +8,7 @@ from medusa import common, db, helpers, subtitles from medusa.helper.common import dateTimeFormat, episode_num -from medusa.indexers.indexer_config import STATUS_MAP +from medusa.indexers.config import STATUS_MAP from medusa.logger.adapters.style import BraceAdapter from medusa.name_parser.parser import NameParser diff --git a/medusa/helpers/__init__.py b/medusa/helpers/__init__.py index 3670aa0a45..9113e1de50 100644 --- a/medusa/helpers/__init__.py +++ b/medusa/helpers/__init__.py @@ -45,7 +45,7 @@ from medusa.helper.common import (episode_num, http_code_description, media_extensions, pretty_file_size, subtitle_extensions) from medusa.helpers.utils import generate -from medusa.indexers.indexer_exceptions import IndexerException +from medusa.indexers.exceptions import IndexerException from medusa.logger.adapters.style import BraceAdapter, BraceMessage from medusa.session.core import MedusaSafeSession from medusa.show.show import Show @@ -195,7 +195,7 @@ def search_indexer_for_show_id(show_name, indexer=None, series_id=None, ui=None) :param ui: Custom UI for indexer use :return: """ - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi show_names = [re.sub('[. -]', ' ', show_name)] # Query Indexers for each search term and build the list of results @@ -1085,8 +1085,8 @@ def real_path(path): def validate_show(show, season=None, episode=None): """Reindex show from originating indexer, and return indexer information for the passed episode.""" - from medusa.indexers.indexer_api import indexerApi - from medusa.indexers.indexer_exceptions import IndexerEpisodeNotFound, IndexerSeasonNotFound, IndexerShowNotFound + from medusa.indexers.api import indexerApi + from medusa.indexers.exceptions import IndexerEpisodeNotFound, IndexerSeasonNotFound, IndexerShowNotFound indexer_lang = show.lang try: @@ -1613,7 +1613,7 @@ def get_tvdb_from_id(indexer_id, indexer): def get_showname_from_indexer(indexer, indexer_id, lang='en'): - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi indexer_api_params = indexerApi(indexer).api_params.copy() if lang: indexer_api_params['language'] = lang diff --git a/medusa/helpers/externals.py b/medusa/helpers/externals.py index 8603d77704..1835d130ce 100644 --- a/medusa/helpers/externals.py +++ b/medusa/helpers/externals.py @@ -5,9 +5,9 @@ import logging from medusa import app, db -from medusa.indexers.indexer_api import indexerApi -from medusa.indexers.indexer_config import indexerConfig -from medusa.indexers.indexer_exceptions import IndexerException, IndexerShowAllreadyInLibrary, IndexerUnavailable +from medusa.indexers.api import indexerApi +from medusa.indexers.config import indexerConfig +from medusa.indexers.exceptions import IndexerException, IndexerShowAllreadyInLibrary, IndexerUnavailable from medusa.indexers.utils import mappings from medusa.logger.adapters.style import BraceAdapter diff --git a/medusa/indexers/indexer_api.py b/medusa/indexers/api.py similarity index 96% rename from medusa/indexers/indexer_api.py rename to medusa/indexers/api.py index 8eb2c3c39b..e9903b3f33 100644 --- a/medusa/indexers/indexer_api.py +++ b/medusa/indexers/api.py @@ -4,7 +4,7 @@ from medusa import app from medusa.helper.common import try_int -from medusa.indexers.indexer_config import indexerConfig, initConfig +from medusa.indexers.config import indexerConfig, initConfig from medusa.indexers.tvdbv2.fallback import PlexFallBackConfig diff --git a/medusa/indexers/indexer_base.py b/medusa/indexers/base.py similarity index 98% rename from medusa/indexers/indexer_base.py rename to medusa/indexers/base.py index 882d77fd55..fa89497c62 100644 --- a/medusa/indexers/indexer_base.py +++ b/medusa/indexers/base.py @@ -14,14 +14,14 @@ from medusa import statistics as stats from medusa.helpers.utils import gen_values_by_key -from medusa.indexers.indexer_exceptions import ( +from medusa.indexers.exceptions import ( IndexerAttributeNotFound, IndexerEpisodeNotFound, IndexerSeasonNotFound, IndexerSeasonUpdatesNotSupported, IndexerShowNotFound, ) -from medusa.indexers.indexer_ui import BaseUI, ConsoleUI +from medusa.indexers.ui import BaseUI, ConsoleUI from medusa.logger.adapters.style import BraceAdapter from medusa.statistics import weights @@ -156,7 +156,14 @@ def get_nested_value(self, value, config): if isinstance(check_value, dict): return self.get_nested_value(check_value, next_keys) else: - return check_value + try: + # Some object have a __dict__ attr. Let's try that. + # It shouldn't match basic types like strings, integers or floats. + parse_dict = check_value.__dict__ + except AttributeError: + return check_value + else: + return self.get_nested_value(parse_dict, next_keys) @staticmethod def _get_temp_dir(): # pylint: disable=no-self-use diff --git a/medusa/indexers/indexer_config.py b/medusa/indexers/config.py similarity index 95% rename from medusa/indexers/indexer_config.py rename to medusa/indexers/config.py index 4a3fd4b68e..b6e810b953 100644 --- a/medusa/indexers/indexer_config.py +++ b/medusa/indexers/config.py @@ -4,9 +4,9 @@ from medusa.app import BASE_PYMEDUSA_URL from medusa.indexers.imdb.api import Imdb -from medusa.indexers.tmdb.tmdb import Tmdb -from medusa.indexers.tvdbv2.tvdbv2_api import TVDBv2 -from medusa.indexers.tvmaze.tvmaze_api import TVmaze +from medusa.indexers.tmdb.api import Tmdb +from medusa.indexers.tvdbv2.api import TVDBv2 +from medusa.indexers.tvmaze.api import TVmaze from medusa.session.core import MedusaSafeSession, MedusaSession @@ -125,7 +125,7 @@ 'api_params': { 'language': 'en', 'use_zip': True, - 'session': MedusaSafeSession(cache_control={'cache_etags': False}), + 'session': MedusaSession(cache_control={'cache_etags': False}), }, 'xem_mapped_to': INDEXER_TVDBV2, 'icon': 'imdb16.png', diff --git a/medusa/indexers/indexer_exceptions.py b/medusa/indexers/exceptions.py similarity index 100% rename from medusa/indexers/indexer_exceptions.py rename to medusa/indexers/exceptions.py diff --git a/medusa/indexers/imdb/api.py b/medusa/indexers/imdb/api.py index fa00373c3b..ddd45953bb 100644 --- a/medusa/indexers/imdb/api.py +++ b/medusa/indexers/imdb/api.py @@ -10,9 +10,9 @@ import locale from six import string_types, text_type from medusa.bs4_parser import BS4Parser -from medusa.indexers.indexer_base import (Actor, Actors, BaseIndexer) -from medusa.indexers.indexer_exceptions import ( - IndexerError, +from medusa.indexers.base import (Actor, Actors, BaseIndexer) +from medusa.indexers.exceptions import ( + IndexerError, IndexerShowIncomplete ) from medusa.logger.adapters.style import BraceAdapter @@ -225,7 +225,15 @@ def _get_episodes(self, imdb_id): # pylint: disable=unused-argument series_id = imdb_id imdb_id = ImdbIdentifier(imdb_id).imdb_id - results = self.imdb_api.get_title_episodes(imdb_id) + try: + results = self.imdb_api.get_title_episodes(imdb_id) + except LookupError as error: + raise IndexerShowIncomplete( + 'Show episode search exception, ' + 'could not get any episodes. Exception: {e!r}'.format( + e=error + ) + ) if not results or not results.get('seasons'): return False diff --git a/medusa/indexers/tmdb/tmdb.py b/medusa/indexers/tmdb/api.py similarity index 99% rename from medusa/indexers/tmdb/tmdb.py rename to medusa/indexers/tmdb/api.py index 80c976cda4..39d2ce305c 100644 --- a/medusa/indexers/tmdb/tmdb.py +++ b/medusa/indexers/tmdb/api.py @@ -12,8 +12,8 @@ from dateutil import parser from medusa.app import TMDB_API_KEY -from medusa.indexers.indexer_base import (Actor, Actors, BaseIndexer) -from medusa.indexers.indexer_exceptions import IndexerError, IndexerException, IndexerShowIncomplete, IndexerUnavailable +from medusa.indexers.base import (Actor, Actors, BaseIndexer) +from medusa.indexers.exceptions import IndexerError, IndexerException, IndexerShowIncomplete, IndexerUnavailable from medusa.logger.adapters.style import BraceAdapter from requests.exceptions import RequestException diff --git a/medusa/indexers/tmdb/tmdb_exceptions.py b/medusa/indexers/tmdb/exceptions.py similarity index 100% rename from medusa/indexers/tmdb/tmdb_exceptions.py rename to medusa/indexers/tmdb/exceptions.py diff --git a/medusa/indexers/tvdbv2/tvdbv2_api.py b/medusa/indexers/tvdbv2/api.py similarity index 98% rename from medusa/indexers/tvdbv2/tvdbv2_api.py rename to medusa/indexers/tvdbv2/api.py index 4f72161a28..d854d38724 100644 --- a/medusa/indexers/tvdbv2/tvdbv2_api.py +++ b/medusa/indexers/tvdbv2/api.py @@ -8,11 +8,11 @@ from medusa import app from medusa.app import TVDB_API_KEY from medusa.helper.metadata import needs_metadata -from medusa.indexers.indexer_base import (Actor, Actors, BaseIndexer) -from medusa.indexers.indexer_exceptions import (IndexerAuthFailed, IndexerError, IndexerException, - IndexerShowIncomplete, IndexerShowNotFound, - IndexerShowNotFoundInLanguage, IndexerUnavailable) -from medusa.indexers.indexer_ui import BaseUI, ConsoleUI +from medusa.indexers.base import (Actor, Actors, BaseIndexer) +from medusa.indexers.exceptions import (IndexerAuthFailed, IndexerError, IndexerException, + IndexerShowIncomplete, IndexerShowNotFound, + IndexerShowNotFoundInLanguage, IndexerUnavailable) +from medusa.indexers.ui import BaseUI, ConsoleUI from medusa.indexers.tvdbv2.fallback import PlexFallback from medusa.logger.adapters.style import BraceAdapter from medusa.show.show import Show diff --git a/medusa/indexers/tvdbv2/tvdbv2_exceptions.py b/medusa/indexers/tvdbv2/exceptions.py similarity index 100% rename from medusa/indexers/tvdbv2/tvdbv2_exceptions.py rename to medusa/indexers/tvdbv2/exceptions.py diff --git a/medusa/indexers/tvdbv2/fallback.py b/medusa/indexers/tvdbv2/fallback.py index 8e5a87936c..94eabea0f0 100644 --- a/medusa/indexers/tvdbv2/fallback.py +++ b/medusa/indexers/tvdbv2/fallback.py @@ -5,7 +5,7 @@ import logging from medusa import app, ui -from medusa.indexers.indexer_exceptions import ( +from medusa.indexers.exceptions import ( IndexerEpisodeNotFound, IndexerSeasonNotFound, IndexerShowIncomplete, IndexerShowNotFound, IndexerShowNotFoundInLanguage, IndexerUnavailable ) diff --git a/medusa/indexers/tvmaze/tvmaze_api.py b/medusa/indexers/tvmaze/api.py similarity index 86% rename from medusa/indexers/tvmaze/tvmaze_api.py rename to medusa/indexers/tvmaze/api.py index 3726c76f19..38bbbf08c7 100644 --- a/medusa/indexers/tvmaze/tvmaze_api.py +++ b/medusa/indexers/tvmaze/api.py @@ -6,8 +6,8 @@ from collections import OrderedDict from time import time -from medusa.indexers.indexer_base import (Actor, Actors, BaseIndexer) -from medusa.indexers.indexer_exceptions import ( +from medusa.indexers.base import (Actor, Actors, BaseIndexer) +from medusa.indexers.exceptions import ( IndexerError, IndexerException, IndexerShowNotFound, @@ -55,23 +55,35 @@ def __init__(self, *args, **kwargs): # pylint: disable=too-many-locals,too-many self.config['artwork_prefix'] = '{base_url}{image_size}{file_path}' # An api to indexer series/episode object mapping - self.series_map = { - 'id': 'id', - 'maze_id': 'id', - 'name': 'seriesname', - 'summary': 'overview', - 'premiered': 'firstaired', - 'image': 'poster_thumb', - 'url': 'show_url', - 'genres': 'genre', - 'epnum': 'absolute_number', - 'title': 'episodename', - 'airdate': 'firstaired', - 'screencap': 'filename', - 'episode_number': 'episodenumber', - 'season_number': 'seasonnumber', - 'rating': 'contentrating', - } + self.series_map = [ + ('id', 'id'), + ('id', 'maze_id'), + ('seriesname', 'name'), + ('overview', 'summary'), + ('firstaired', 'premiered'), + ('poster_thumb', 'image'), + ('show_url', 'url'), + ('genre', 'genres'), + ('absolute_number', 'epnum'), + ('episodename', 'title'), + ('firstaired', 'airdate'), + ('filename', 'screencap'), + ('episodenumber', 'episode_number'), + ('seasonnumber', 'season_number'), + ('contentrating', 'rating'), + ('airs_time', 'schedule.time'), + ('airs_dayofweek', 'schedule.days[0]'), + ('network', 'network.name'), + ('code', 'network.code'), + ('timezone', 'network.timezone'), + + ('tvrage_id', 'externals.tvrage'), + ('tvdb_id', 'externals.thetvdb'), + ('imdb_id', 'externals.imdb'), + ('contentrating', 'rating'), + ('contentrating', 'rating.average'), + + ] def _map_results(self, tvmaze_response, key_mappings=None, list_separator='|'): """ @@ -97,44 +109,20 @@ def _map_results(self, tvmaze_response, key_mappings=None, list_separator='|'): for item in tvmaze_response: return_dict = {} try: - for key, value in item.__dict__.iteritems(): - if value is None or value == []: + + for key, config in self.series_map: + value = self.get_nested_value(item.__dict__, config) + if not value: continue - # These keys have more complex dictionaries, let's map these manually - if key in ['schedule', 'network', 'image', 'externals', 'rating']: - if key == 'schedule': - return_dict['airs_time'] = value.get('time') or '0:00AM' - return_dict['airs_dayofweek'] = value.get('days')[0] if value.get('days') else None - if key == 'network': - return_dict['network'] = value.name - return_dict['code'] = value.code - return_dict['timezone'] = value.timezone - if key == 'image': - if value.get('medium'): - return_dict['poster_thumb'] = value.get('medium') - return_dict['poster'] = value.get('original') - if key == 'externals': - return_dict['tvrage_id'] = value.get('tvrage') - return_dict['tvdb_id'] = value.get('thetvdb') - return_dict['imdb_id'] = value.get('imdb') - if key == 'rating': - return_dict['contentrating'] = value.get('average')\ - if isinstance(value, dict) else value - else: - # Do some value sanitizing - if isinstance(value, list): - if all(isinstance(x, (string_types, integer_types)) for x in value): - value = list_separator.join(text_type(v) for v in value) - - # Try to map the key - if key in key_mappings: - key = key_mappings[key] - - # Set value to key - return_dict[key] = text_type(value) if isinstance(value, (float, integer_types)) else value - - # For episodes + # Do some value sanitizing + if isinstance(value, list): + if all(isinstance(x, (string_types, integer_types)) for x in value): + value = list_separator.join(text_type(v) for v in value) + + return_dict[key] = value + + # For special episodes if hasattr(item, 'season_number') and getattr(item, 'episode_number') is None: return_dict['episodenumber'] = text_type(index_special_episodes) return_dict['seasonnumber'] = 0 diff --git a/medusa/indexers/tvmaze/tvmaze_exceptions.py b/medusa/indexers/tvmaze/tvmaze_exceptions.py deleted file mode 100644 index 7a8edbc91e..0000000000 --- a/medusa/indexers/tvmaze/tvmaze_exceptions.py +++ /dev/null @@ -1,75 +0,0 @@ -# coding=utf-8 -# Author: p0psicles -# -# This file is part of Medusa. -# -# Medusa is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Medusa is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Medusa. If not, see . - -"""Custom exceptions used or raised by tvdbv2_api.""" - -__author__ = "p0psicles" -__version__ = "1.0" - -__all__ = ["tvdbv2_error", "tvdbv2_userabort", "tvdbv2_shownotfound", "tvdbv2_showincomplete", - "tvdbv2_seasonnotfound", "tvdbv2_episodenotfound", "tvdbv2_attributenotfound"] - - -class tvdbv2_exception(Exception): - """Any exception generated by tvdbv2_api - """ - pass - - -class tvdbv2_error(tvdbv2_exception): - """An error with thetvdb.com (Cannot connect, for example) - """ - pass - - -class tvdbv2_userabort(tvdbv2_exception): - """User aborted the interactive selection (via - the q command, ^c etc) - """ - pass - - -class tvdbv2_shownotfound(tvdbv2_exception): - """Show cannot be found on thetvdb.com (non-existant show) - """ - pass - - -class tvdbv2_showincomplete(tvdbv2_exception): - """Show found but incomplete on thetvdb.com (incomplete show) - """ - pass - - -class tvdbv2_seasonnotfound(tvdbv2_exception): - """Season cannot be found on thetvdb.com - """ - pass - - -class tvdbv2_episodenotfound(tvdbv2_exception): - """Episode cannot be found on thetvdb.com - """ - pass - - -class tvdbv2_attributenotfound(tvdbv2_exception): - """Raised if an episode does not have the requested - attribute (such as a episode name) - """ - pass diff --git a/medusa/indexers/indexer_ui.py b/medusa/indexers/ui.py similarity index 98% rename from medusa/indexers/indexer_ui.py rename to medusa/indexers/ui.py index b2ed0f8ee9..0b82a54db9 100644 --- a/medusa/indexers/indexer_ui.py +++ b/medusa/indexers/ui.py @@ -5,7 +5,7 @@ import logging import warnings -from indexer_exceptions import IndexerUserAbort +from exceptions import IndexerUserAbort __author__ = 'p0psicles' __version__ = '1.0' diff --git a/medusa/indexers/utils.py b/medusa/indexers/utils.py index 827a85f3d3..7908f4d7f2 100644 --- a/medusa/indexers/utils.py +++ b/medusa/indexers/utils.py @@ -3,7 +3,7 @@ import re -from medusa.indexers.indexer_config import EXTERNAL_MAPPINGS, TRAKT_INDEXERS, indexerConfig +from medusa.indexers.config import EXTERNAL_MAPPINGS, TRAKT_INDEXERS, indexerConfig # For example: {1: 'tvdb_id', 3: 'tvmaze_id', 4: 'tmdb_id'} diff --git a/medusa/metadata/generic.py b/medusa/metadata/generic.py index f07079a321..84cf349471 100644 --- a/medusa/metadata/generic.py +++ b/medusa/metadata/generic.py @@ -9,9 +9,9 @@ from medusa.helper.common import replace_extension from medusa.helper.exceptions import ex from medusa.helper.metadata import get_image -from medusa.indexers.indexer_config import INDEXER_TMDB, INDEXER_TVDBV2, INDEXER_TVMAZE -from medusa.indexers.indexer_exceptions import (IndexerEpisodeNotFound, IndexerException, - IndexerSeasonNotFound, IndexerShowNotFound) +from medusa.indexers.config import INDEXER_TMDB, INDEXER_TVDBV2, INDEXER_TVMAZE +from medusa.indexers.exceptions import (IndexerEpisodeNotFound, IndexerException, + IndexerSeasonNotFound, IndexerShowNotFound) from medusa.logger.adapters.style import BraceAdapter from requests.exceptions import RequestException diff --git a/medusa/metadata/kodi_12plus.py b/medusa/metadata/kodi_12plus.py index 837e8d676a..c33e8ba46e 100644 --- a/medusa/metadata/kodi_12plus.py +++ b/medusa/metadata/kodi_12plus.py @@ -9,10 +9,10 @@ from medusa import helpers from medusa.app import TVDB_API_KEY from medusa.helper.common import dateFormat, episode_num -from medusa.indexers.indexer_api import indexerApi -from medusa.indexers.indexer_config import INDEXER_TVDBV2 -from medusa.indexers.indexer_exceptions import IndexerEpisodeNotFound, IndexerSeasonNotFound -from medusa.indexers.tvdbv2.tvdbv2_api import API_BASE_TVDB +from medusa.indexers.api import indexerApi +from medusa.indexers.config import INDEXER_TVDBV2 +from medusa.indexers.exceptions import IndexerEpisodeNotFound, IndexerSeasonNotFound +from medusa.indexers.tvdbv2.api import API_BASE_TVDB from medusa.logger.adapters.style import BraceAdapter from medusa.metadata import generic diff --git a/medusa/metadata/mede8er.py b/medusa/metadata/mede8er.py index 820ead13c5..664aed09f6 100644 --- a/medusa/metadata/mede8er.py +++ b/medusa/metadata/mede8er.py @@ -10,8 +10,8 @@ from medusa import helpers from medusa.helper.common import dateFormat, episode_num, replace_extension from medusa.helper.exceptions import ex -from medusa.indexers.indexer_api import indexerApi -from medusa.indexers.indexer_exceptions import IndexerEpisodeNotFound, IndexerSeasonNotFound +from medusa.indexers.api import indexerApi +from medusa.indexers.exceptions import IndexerEpisodeNotFound, IndexerSeasonNotFound from medusa.logger.adapters.style import BraceAdapter from medusa.metadata import media_browser diff --git a/medusa/metadata/media_browser.py b/medusa/metadata/media_browser.py index 242c2ee090..44f510f905 100644 --- a/medusa/metadata/media_browser.py +++ b/medusa/metadata/media_browser.py @@ -7,8 +7,8 @@ from medusa import app, helpers from medusa.helper.common import dateFormat, episode_num, replace_extension -from medusa.indexers.indexer_api import indexerApi -from medusa.indexers.indexer_exceptions import IndexerEpisodeNotFound, IndexerSeasonNotFound +from medusa.indexers.api import indexerApi +from medusa.indexers.exceptions import IndexerEpisodeNotFound, IndexerSeasonNotFound from medusa.logger.adapters.style import BraceAdapter from medusa.metadata import generic diff --git a/medusa/metadata/tivo.py b/medusa/metadata/tivo.py index d63c3526c5..e6b2d3d935 100644 --- a/medusa/metadata/tivo.py +++ b/medusa/metadata/tivo.py @@ -10,8 +10,8 @@ from medusa import helpers from medusa.helper.common import episode_num from medusa.helper.exceptions import ex -from medusa.indexers.indexer_api import indexerApi -from medusa.indexers.indexer_exceptions import IndexerEpisodeNotFound, IndexerSeasonNotFound +from medusa.indexers.api import indexerApi +from medusa.indexers.exceptions import IndexerEpisodeNotFound, IndexerSeasonNotFound from medusa.logger.adapters.style import BraceAdapter from medusa.metadata import generic from six import text_type diff --git a/medusa/metadata/wdtv.py b/medusa/metadata/wdtv.py index 9c8ce6ceba..9f9d6d4447 100644 --- a/medusa/metadata/wdtv.py +++ b/medusa/metadata/wdtv.py @@ -9,8 +9,8 @@ from medusa import helpers from medusa.helper.common import dateFormat, episode_num as ep_num, replace_extension -from medusa.indexers.indexer_api import indexerApi -from medusa.indexers.indexer_exceptions import IndexerEpisodeNotFound, IndexerSeasonNotFound +from medusa.indexers.api import indexerApi +from medusa.indexers.exceptions import IndexerEpisodeNotFound, IndexerSeasonNotFound from medusa.logger.adapters.style import BraceAdapter from medusa.metadata import generic from six import text_type diff --git a/medusa/name_parser/parser.py b/medusa/name_parser/parser.py index efd3550b01..afd9d7efa0 100644 --- a/medusa/name_parser/parser.py +++ b/medusa/name_parser/parser.py @@ -18,8 +18,8 @@ scene_numbering, ) from medusa.helper.common import episode_num -from medusa.indexers.indexer_api import indexerApi -from medusa.indexers.indexer_exceptions import ( +from medusa.indexers.api import indexerApi +from medusa.indexers.exceptions import ( IndexerEpisodeNotFound, IndexerError, IndexerException, diff --git a/medusa/providers/generic_provider.py b/medusa/providers/generic_provider.py index 47c5912c07..9c0c3f852f 100644 --- a/medusa/providers/generic_provider.py +++ b/medusa/providers/generic_provider.py @@ -37,7 +37,7 @@ from medusa.helpers import ( download_file, ) -from medusa.indexers.indexer_config import INDEXER_TVDBV2 +from medusa.indexers.config import INDEXER_TVDBV2 from medusa.logger.adapters.style import BraceAdapter from medusa.name_parser.parser import ( InvalidNameException, diff --git a/medusa/providers/nzb/newznab.py b/medusa/providers/nzb/newznab.py index c475114636..c0ec062197 100644 --- a/medusa/providers/nzb/newznab.py +++ b/medusa/providers/nzb/newznab.py @@ -22,7 +22,7 @@ ) from medusa.helper.encoding import ss from medusa.helpers.utils import split_and_strip -from medusa.indexers.indexer_config import ( +from medusa.indexers.config import ( INDEXER_TMDB, INDEXER_TVDBV2, INDEXER_TVMAZE, diff --git a/medusa/providers/torrent/json/btn.py b/medusa/providers/torrent/json/btn.py index 0bfd929ab8..8d9a6f84da 100644 --- a/medusa/providers/torrent/json/btn.py +++ b/medusa/providers/torrent/json/btn.py @@ -17,7 +17,7 @@ ) from medusa.common import cpu_presets from medusa.helper.common import episode_num -from medusa.indexers.indexer_config import INDEXER_TVDBV2 +from medusa.indexers.config import INDEXER_TVDBV2 from medusa.logger.adapters.style import BraceAdapter from medusa.providers.torrent.torrent_provider import TorrentProvider diff --git a/medusa/scene_exceptions.py b/medusa/scene_exceptions.py index 3b8134c8aa..5f3e918c2c 100644 --- a/medusa/scene_exceptions.py +++ b/medusa/scene_exceptions.py @@ -12,8 +12,8 @@ import adba from medusa import app, db, helpers -from medusa.indexers.indexer_api import indexerApi -from medusa.indexers.indexer_config import INDEXER_TVDBV2 +from medusa.indexers.api import indexerApi +from medusa.indexers.config import INDEXER_TVDBV2 from medusa.session.core import MedusaSafeSession from six import iteritems diff --git a/medusa/scene_numbering.py b/medusa/scene_numbering.py index c6365c912a..ba5ddd36c8 100644 --- a/medusa/scene_numbering.py +++ b/medusa/scene_numbering.py @@ -27,7 +27,7 @@ from medusa import db, logger from medusa.helper.exceptions import ex -from medusa.indexers.indexer_api import indexerApi +from medusa.indexers.api import indexerApi from medusa.scene_exceptions import safe_session diff --git a/medusa/server/api/v1/core.py b/medusa/server/api/v1/core.py index 6b390ba762..3f4a2a9142 100644 --- a/medusa/server/api/v1/core.py +++ b/medusa/server/api/v1/core.py @@ -41,9 +41,9 @@ ) from medusa.helper.exceptions import CantUpdateShowException, ShowDirectoryNotFoundException from medusa.helpers.quality import get_quality_string -from medusa.indexers.indexer_api import indexerApi -from medusa.indexers.indexer_config import INDEXER_TVDBV2 -from medusa.indexers.indexer_exceptions import IndexerError, IndexerShowIncomplete, IndexerShowNotFound +from medusa.indexers.api import indexerApi +from medusa.indexers.config import INDEXER_TVDBV2 +from medusa.indexers.exceptions import IndexerError, IndexerShowIncomplete, IndexerShowNotFound from medusa.logger import LOGGING_LEVELS, filter_logline, read_loglines from medusa.logger.adapters.style import BraceAdapter from medusa.media.banner import ShowBanner diff --git a/medusa/server/api/v2/config.py b/medusa/server/api/v2/config.py index 5a37bdf553..61a323d49e 100644 --- a/medusa/server/api/v2/config.py +++ b/medusa/server/api/v2/config.py @@ -9,7 +9,7 @@ db, ) from medusa.helper.mappings import NonEmptyDict -from medusa.indexers.indexer_config import indexerConfig +from medusa.indexers.config import indexerConfig from medusa.server.api.v2.base import ( BaseRequestHandler, BooleanField, diff --git a/medusa/server/web/home/add_shows.py b/medusa/server/web/home/add_shows.py index 61e8f56660..752c33859a 100644 --- a/medusa/server/web/home/add_shows.py +++ b/medusa/server/web/home/add_shows.py @@ -12,9 +12,9 @@ from medusa.common import Quality from medusa.helper.common import sanitize_filename, try_int from medusa.helpers import get_showname_from_indexer -from medusa.indexers.indexer_api import indexerApi -from medusa.indexers.indexer_config import INDEXER_TVDBV2 -from medusa.indexers.indexer_exceptions import IndexerException, IndexerUnavailable +from medusa.indexers.api import indexerApi +from medusa.indexers.config import INDEXER_TVDBV2 +from medusa.indexers.exceptions import IndexerException, IndexerUnavailable from medusa.server.web.core import PageTemplate from medusa.server.web.home.handler import Home from medusa.show.recommendations.anidb import AnidbPopular diff --git a/medusa/server/web/home/handler.py b/medusa/server/web/home/handler.py index 1ddd2b7528..3357dd7215 100644 --- a/medusa/server/web/home/handler.py +++ b/medusa/server/web/home/handler.py @@ -58,8 +58,8 @@ ShowDirectoryNotFoundException, ex, ) -from medusa.indexers.indexer_api import indexerApi -from medusa.indexers.indexer_exceptions import ( +from medusa.indexers.api import indexerApi +from medusa.indexers.exceptions import ( IndexerException, IndexerShowNotFoundInLanguage, ) diff --git a/medusa/show/recommendations/anidb.py b/medusa/show/recommendations/anidb.py index 04dd5a1539..c69a8cd3ca 100644 --- a/medusa/show/recommendations/anidb.py +++ b/medusa/show/recommendations/anidb.py @@ -6,7 +6,7 @@ import traceback from medusa import app -from medusa.indexers.indexer_config import INDEXER_TVDBV2 +from medusa.indexers.config import INDEXER_TVDBV2 from medusa.logger.adapters.style import BraceAdapter from medusa.session.core import MedusaSession from medusa.show.recommendations.recommended import (MissingTvdbMapping, RecommendedShow) diff --git a/medusa/show/recommendations/imdb.py b/medusa/show/recommendations/imdb.py index 5c77a755c6..4b73d837bc 100644 --- a/medusa/show/recommendations/imdb.py +++ b/medusa/show/recommendations/imdb.py @@ -9,7 +9,7 @@ from datetime import date from imdbpie import imdbpie from medusa import app, helpers -from medusa.indexers.indexer_config import INDEXER_TVDBV2 +from medusa.indexers.config import INDEXER_TVDBV2 from medusa.logger.adapters.style import BraceAdapter from medusa.session.core import MedusaSession from medusa.show.recommendations import ExpiringKeyValue diff --git a/medusa/show/recommendations/trakt.py b/medusa/show/recommendations/trakt.py index 410e488408..24bb14b66c 100644 --- a/medusa/show/recommendations/trakt.py +++ b/medusa/show/recommendations/trakt.py @@ -8,8 +8,8 @@ from medusa import app from medusa.helper.common import try_int from medusa.helper.exceptions import MultipleShowObjectsException -from medusa.indexers.indexer_api import indexerApi -from medusa.indexers.indexer_config import INDEXER_TVDBV2 +from medusa.indexers.api import indexerApi +from medusa.indexers.config import INDEXER_TVDBV2 from medusa.logger.adapters.style import BraceAdapter from medusa.show.recommendations import ExpiringList from medusa.show.recommendations.recommended import RecommendedShow diff --git a/medusa/show/show.py b/medusa/show/show.py index 4c172b5e7a..c803ae3b82 100644 --- a/medusa/show/show.py +++ b/medusa/show/show.py @@ -83,7 +83,7 @@ def find(shows, indexer_id, indexer=None): DeprecationWarning, ) - from medusa.indexers.indexer_config import EXTERNAL_IMDB, EXTERNAL_TRAKT + from medusa.indexers.config import EXTERNAL_IMDB, EXTERNAL_TRAKT if indexer_id is None or shows is None or len(shows) == 0: return None diff --git a/medusa/show_queue.py b/medusa/show_queue.py index f0c3e831cd..0732d4664c 100644 --- a/medusa/show_queue.py +++ b/medusa/show_queue.py @@ -49,8 +49,8 @@ ) from medusa.helpers.externals import check_existing_shows from medusa.image_cache import replace_images -from medusa.indexers.indexer_api import indexerApi -from medusa.indexers.indexer_exceptions import ( +from medusa.indexers.api import indexerApi +from medusa.indexers.exceptions import ( IndexerAttributeNotFound, IndexerError, IndexerException, diff --git a/medusa/show_updater.py b/medusa/show_updater.py index bf05510868..d065bb0a4e 100644 --- a/medusa/show_updater.py +++ b/medusa/show_updater.py @@ -22,8 +22,8 @@ from medusa import app, db, network_timezones, ui from medusa.helper.exceptions import CantRefreshShowException, CantUpdateShowException -from medusa.indexers.indexer_api import indexerApi -from medusa.indexers.indexer_exceptions import IndexerException, IndexerUnavailable +from medusa.indexers.api import indexerApi +from medusa.indexers.exceptions import IndexerException, IndexerUnavailable from medusa.scene_exceptions import refresh_exceptions_cache from medusa.session.core import MedusaSession diff --git a/medusa/trakt_checker.py b/medusa/trakt_checker.py index 14c46f3755..55b896e758 100644 --- a/medusa/trakt_checker.py +++ b/medusa/trakt_checker.py @@ -11,7 +11,7 @@ from medusa.common import Quality, SKIPPED, WANTED from medusa.helper.common import episode_num from medusa.helpers import get_title_without_year -from medusa.indexers.indexer_config import EXTERNAL_IMDB, EXTERNAL_TRAKT, indexerConfig +from medusa.indexers.config import EXTERNAL_IMDB, EXTERNAL_TRAKT, indexerConfig from medusa.indexers.utils import get_trakt_indexer from medusa.logger.adapters.style import BraceAdapter from medusa.search.queue import BacklogQueueItem diff --git a/medusa/tv/base.py b/medusa/tv/base.py index ae38c44b4f..ae8603e7e3 100644 --- a/medusa/tv/base.py +++ b/medusa/tv/base.py @@ -3,7 +3,7 @@ import threading -from medusa.indexers.indexer_config import INDEXER_TVDBV2 +from medusa.indexers.config import INDEXER_TVDBV2 class Identifier(object): diff --git a/medusa/tv/episode.py b/medusa/tv/episode.py index 9a49238e6a..1397d143e7 100644 --- a/medusa/tv/episode.py +++ b/medusa/tv/episode.py @@ -53,9 +53,9 @@ ex, ) from medusa.helper.mappings import NonEmptyDict -from medusa.indexers.indexer_api import indexerApi -from medusa.indexers.indexer_config import indexerConfig -from medusa.indexers.indexer_exceptions import ( +from medusa.indexers.api import indexerApi +from medusa.indexers.config import indexerConfig +from medusa.indexers.exceptions import ( IndexerEpisodeNotFound, IndexerError, IndexerSeasonNotFound, diff --git a/medusa/tv/series.py b/medusa/tv/series.py index 4f2bcc3b3c..c10da10d3e 100644 --- a/medusa/tv/series.py +++ b/medusa/tv/series.py @@ -65,20 +65,20 @@ from medusa.helper.mappings import NonEmptyDict from medusa.helpers.externals import get_externals, load_externals_from_db from medusa.helpers.utils import safe_get -from medusa.indexers.indexer_api import indexerApi -from medusa.indexers.indexer_config import ( +from medusa.indexers.api import indexerApi +from medusa.indexers.config import ( INDEXER_TVRAGE, INDEXER_IMDB, STATUS_MAP, indexerConfig ) -from medusa.indexers.indexer_exceptions import ( +from medusa.indexers.exceptions import ( IndexerAttributeNotFound, IndexerException, IndexerSeasonNotFound, ) from medusa.indexers.imdb.api import ImdbIdentifier -from medusa.indexers.tmdb.tmdb import Tmdb +from medusa.indexers.tmdb.api import Tmdb from medusa.indexers.utils import ( indexer_id_to_slug, mappings, diff --git a/tests/apiv2/test_config.py b/tests/apiv2/test_config.py index 017a0d3853..853bfc4d87 100644 --- a/tests/apiv2/test_config.py +++ b/tests/apiv2/test_config.py @@ -6,7 +6,7 @@ from medusa import app, db from medusa.helper.mappings import NonEmptyDict -from medusa.indexers.indexer_config import indexerConfig +from medusa.indexers.config import indexerConfig import pytest diff --git a/tests/conftest.py b/tests/conftest.py index b8cd7b1a0f..6e110f8793 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,7 +14,7 @@ from medusa import app, cache from medusa.common import DOWNLOADED, Quality from medusa.helper.common import dateTimeFormat -from medusa.indexers.indexer_config import INDEXER_TVDBV2 +from medusa.indexers.config import INDEXER_TVDBV2 from medusa.logger import CensoredFormatter, ContextFilter, FORMATTER_PATTERN, instance from medusa.logger import read_loglines as logger_read_loglines from medusa.tv import Episode, Series diff --git a/themes-default/legacy/views/addShows_newShow.mako b/themes-default/legacy/views/addShows_newShow.mako index cfbc5cc37e..71cbee93b2 100644 --- a/themes-default/legacy/views/addShows_newShow.mako +++ b/themes-default/legacy/views/addShows_newShow.mako @@ -2,7 +2,7 @@ <%! from medusa import app from medusa.helpers import anon_url - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi %> <%block name="scripts"> diff --git a/themes-default/legacy/views/config_general.mako b/themes-default/legacy/views/config_general.mako index fcb511a284..1702e9dd99 100644 --- a/themes-default/legacy/views/config_general.mako +++ b/themes-default/legacy/views/config_general.mako @@ -8,7 +8,7 @@ from medusa.sbdatetime import sbdatetime, date_presets, time_presets from medusa.metadata.generic import GenericMetadata from medusa.helpers import anon_url - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi gh_branch = app.GIT_REMOTE_BRANCHES or app.version_check_scheduler.action.list_remote_branches() %> <%block name="content"> diff --git a/themes-default/legacy/views/config_notifications.mako b/themes-default/legacy/views/config_notifications.mako index 548bad6bb2..32b985b326 100644 --- a/themes-default/legacy/views/config_notifications.mako +++ b/themes-default/legacy/views/config_notifications.mako @@ -5,7 +5,7 @@ from medusa.helpers import anon_url from medusa.common import SKIPPED, WANTED, UNAIRED, ARCHIVED, IGNORED, SNATCHED, SNATCHED_PROPER, SNATCHED_BEST, FAILED from medusa.common import Quality, qualityPresets, statusStrings, qualityPresetStrings, cpu_presets, MULTI_EP_STRINGS - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.indexers.utils import get_trakt_indexer %> <%block name="content"> diff --git a/themes-default/legacy/views/displayShow.mako b/themes-default/legacy/views/displayShow.mako index f82840a74e..952b32713a 100644 --- a/themes-default/legacy/views/displayShow.mako +++ b/themes-default/legacy/views/displayShow.mako @@ -8,7 +8,7 @@ from medusa.common import Quality, qualityPresets, statusStrings, Overview from medusa.helpers import anon_url from medusa.helper.common import pretty_file_size - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.indexers.utils import mappings %> <%block name="scripts"> diff --git a/themes-default/legacy/views/editShow.mako b/themes-default/legacy/views/editShow.mako index 5fb7253b87..a07a94e396 100644 --- a/themes-default/legacy/views/editShow.mako +++ b/themes-default/legacy/views/editShow.mako @@ -5,7 +5,7 @@ from medusa.common import SKIPPED, WANTED, UNAIRED, ARCHIVED, IGNORED, SNATCHED, SNATCHED_PROPER, SNATCHED_BEST, FAILED from medusa.common import statusStrings from medusa.helper import exceptions - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.indexers.utils import mappings from medusa import scene_exceptions %> diff --git a/themes-default/legacy/views/home_massAddTable.mako b/themes-default/legacy/views/home_massAddTable.mako index 8a6a5764df..4c1e06cecb 100644 --- a/themes-default/legacy/views/home_massAddTable.mako +++ b/themes-default/legacy/views/home_massAddTable.mako @@ -1,7 +1,7 @@ <%! from medusa import app from medusa.helpers import anon_url - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi %> diff --git a/themes-default/legacy/views/partials/home/banner.mako b/themes-default/legacy/views/partials/home/banner.mako index 4f212a1587..338481ba2a 100644 --- a/themes-default/legacy/views/partials/home/banner.mako +++ b/themes-default/legacy/views/partials/home/banner.mako @@ -3,7 +3,7 @@ import calendar from medusa import sbdatetime from medusa import network_timezones - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.helpers import anon_url from medusa.helper.common import pretty_file_size from medusa.scene_numbering import get_xem_numbering_for_show diff --git a/themes-default/legacy/views/partials/home/simple.mako b/themes-default/legacy/views/partials/home/simple.mako index ef3f4efad8..ede1af77ee 100644 --- a/themes-default/legacy/views/partials/home/simple.mako +++ b/themes-default/legacy/views/partials/home/simple.mako @@ -3,7 +3,7 @@ import calendar from medusa import sbdatetime from medusa import network_timezones - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.helpers import anon_url from medusa.helper.common import pretty_file_size from medusa.scene_numbering import get_xem_numbering_for_show diff --git a/themes-default/legacy/views/partials/home/small.mako b/themes-default/legacy/views/partials/home/small.mako index 418c63d8cb..8513d03dd2 100644 --- a/themes-default/legacy/views/partials/home/small.mako +++ b/themes-default/legacy/views/partials/home/small.mako @@ -3,7 +3,7 @@ import calendar from medusa import sbdatetime from medusa import network_timezones - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.helpers import anon_url from medusa.helper.common import pretty_file_size from medusa.scene_numbering import get_xem_numbering_for_show diff --git a/themes-default/legacy/views/partials/showheader.mako b/themes-default/legacy/views/partials/showheader.mako index 81f546ef42..e970bb0296 100644 --- a/themes-default/legacy/views/partials/showheader.mako +++ b/themes-default/legacy/views/partials/showheader.mako @@ -7,7 +7,7 @@ from medusa.common import Quality, qualityPresets, statusStrings, Overview from medusa.helpers import anon_url from medusa.helper.common import pretty_file_size - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi %> <%namespace file="/inc_defs.mako" import="renderQualityPill"/> diff --git a/themes-default/legacy/views/schedule.mako b/themes-default/legacy/views/schedule.mako index 2bb6e1572f..dd96ed6cdf 100644 --- a/themes-default/legacy/views/schedule.mako +++ b/themes-default/legacy/views/schedule.mako @@ -2,7 +2,7 @@ <%! from medusa import app from medusa.helpers import anon_url - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.indexers.utils import indexer_id_to_name, mappings from medusa import sbdatetime from random import choice diff --git a/themes-default/slim/views/addShows_newShow.mako b/themes-default/slim/views/addShows_newShow.mako index cfbc5cc37e..71cbee93b2 100644 --- a/themes-default/slim/views/addShows_newShow.mako +++ b/themes-default/slim/views/addShows_newShow.mako @@ -2,7 +2,7 @@ <%! from medusa import app from medusa.helpers import anon_url - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi %> <%block name="scripts"> diff --git a/themes-default/slim/views/config_general.mako b/themes-default/slim/views/config_general.mako index fcb511a284..1702e9dd99 100644 --- a/themes-default/slim/views/config_general.mako +++ b/themes-default/slim/views/config_general.mako @@ -8,7 +8,7 @@ from medusa.sbdatetime import sbdatetime, date_presets, time_presets from medusa.metadata.generic import GenericMetadata from medusa.helpers import anon_url - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi gh_branch = app.GIT_REMOTE_BRANCHES or app.version_check_scheduler.action.list_remote_branches() %> <%block name="content"> diff --git a/themes-default/slim/views/config_notifications.mako b/themes-default/slim/views/config_notifications.mako index 548bad6bb2..32b985b326 100644 --- a/themes-default/slim/views/config_notifications.mako +++ b/themes-default/slim/views/config_notifications.mako @@ -5,7 +5,7 @@ from medusa.helpers import anon_url from medusa.common import SKIPPED, WANTED, UNAIRED, ARCHIVED, IGNORED, SNATCHED, SNATCHED_PROPER, SNATCHED_BEST, FAILED from medusa.common import Quality, qualityPresets, statusStrings, qualityPresetStrings, cpu_presets, MULTI_EP_STRINGS - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.indexers.utils import get_trakt_indexer %> <%block name="content"> diff --git a/themes-default/slim/views/displayShow.mako b/themes-default/slim/views/displayShow.mako index f82840a74e..952b32713a 100644 --- a/themes-default/slim/views/displayShow.mako +++ b/themes-default/slim/views/displayShow.mako @@ -8,7 +8,7 @@ from medusa.common import Quality, qualityPresets, statusStrings, Overview from medusa.helpers import anon_url from medusa.helper.common import pretty_file_size - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.indexers.utils import mappings %> <%block name="scripts"> diff --git a/themes-default/slim/views/editShow.mako b/themes-default/slim/views/editShow.mako index 5fb7253b87..a07a94e396 100644 --- a/themes-default/slim/views/editShow.mako +++ b/themes-default/slim/views/editShow.mako @@ -5,7 +5,7 @@ from medusa.common import SKIPPED, WANTED, UNAIRED, ARCHIVED, IGNORED, SNATCHED, SNATCHED_PROPER, SNATCHED_BEST, FAILED from medusa.common import statusStrings from medusa.helper import exceptions - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.indexers.utils import mappings from medusa import scene_exceptions %> diff --git a/themes-default/slim/views/home_massAddTable.mako b/themes-default/slim/views/home_massAddTable.mako index 8a6a5764df..4c1e06cecb 100644 --- a/themes-default/slim/views/home_massAddTable.mako +++ b/themes-default/slim/views/home_massAddTable.mako @@ -1,7 +1,7 @@ <%! from medusa import app from medusa.helpers import anon_url - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi %>
diff --git a/themes-default/slim/views/partials/home/banner.mako b/themes-default/slim/views/partials/home/banner.mako index 4f212a1587..338481ba2a 100644 --- a/themes-default/slim/views/partials/home/banner.mako +++ b/themes-default/slim/views/partials/home/banner.mako @@ -3,7 +3,7 @@ import calendar from medusa import sbdatetime from medusa import network_timezones - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.helpers import anon_url from medusa.helper.common import pretty_file_size from medusa.scene_numbering import get_xem_numbering_for_show diff --git a/themes-default/slim/views/partials/home/simple.mako b/themes-default/slim/views/partials/home/simple.mako index ef3f4efad8..ede1af77ee 100644 --- a/themes-default/slim/views/partials/home/simple.mako +++ b/themes-default/slim/views/partials/home/simple.mako @@ -3,7 +3,7 @@ import calendar from medusa import sbdatetime from medusa import network_timezones - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.helpers import anon_url from medusa.helper.common import pretty_file_size from medusa.scene_numbering import get_xem_numbering_for_show diff --git a/themes-default/slim/views/partials/home/small.mako b/themes-default/slim/views/partials/home/small.mako index 418c63d8cb..8513d03dd2 100644 --- a/themes-default/slim/views/partials/home/small.mako +++ b/themes-default/slim/views/partials/home/small.mako @@ -3,7 +3,7 @@ import calendar from medusa import sbdatetime from medusa import network_timezones - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.helpers import anon_url from medusa.helper.common import pretty_file_size from medusa.scene_numbering import get_xem_numbering_for_show diff --git a/themes-default/slim/views/partials/showheader.mako b/themes-default/slim/views/partials/showheader.mako index 89d3483c0d..dd20839a43 100644 --- a/themes-default/slim/views/partials/showheader.mako +++ b/themes-default/slim/views/partials/showheader.mako @@ -7,7 +7,7 @@ from medusa.common import Quality, qualityPresets, statusStrings, Overview from medusa.helpers import anon_url from medusa.helper.common import pretty_file_size - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi %> <%namespace file="/inc_defs.mako" import="renderQualityPill"/> diff --git a/themes-default/slim/views/schedule.mako b/themes-default/slim/views/schedule.mako index 2bb6e1572f..dd96ed6cdf 100644 --- a/themes-default/slim/views/schedule.mako +++ b/themes-default/slim/views/schedule.mako @@ -2,7 +2,7 @@ <%! from medusa import app from medusa.helpers import anon_url - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.indexers.utils import indexer_id_to_name, mappings from medusa import sbdatetime from random import choice diff --git a/themes/dark/templates/addShows_newShow.mako b/themes/dark/templates/addShows_newShow.mako index cfbc5cc37e..71cbee93b2 100644 --- a/themes/dark/templates/addShows_newShow.mako +++ b/themes/dark/templates/addShows_newShow.mako @@ -2,7 +2,7 @@ <%! from medusa import app from medusa.helpers import anon_url - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi %> <%block name="scripts"> diff --git a/themes/dark/templates/config_general.mako b/themes/dark/templates/config_general.mako index fcb511a284..1702e9dd99 100644 --- a/themes/dark/templates/config_general.mako +++ b/themes/dark/templates/config_general.mako @@ -8,7 +8,7 @@ from medusa.sbdatetime import sbdatetime, date_presets, time_presets from medusa.metadata.generic import GenericMetadata from medusa.helpers import anon_url - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi gh_branch = app.GIT_REMOTE_BRANCHES or app.version_check_scheduler.action.list_remote_branches() %> <%block name="content"> diff --git a/themes/dark/templates/config_notifications.mako b/themes/dark/templates/config_notifications.mako index 548bad6bb2..32b985b326 100644 --- a/themes/dark/templates/config_notifications.mako +++ b/themes/dark/templates/config_notifications.mako @@ -5,7 +5,7 @@ from medusa.helpers import anon_url from medusa.common import SKIPPED, WANTED, UNAIRED, ARCHIVED, IGNORED, SNATCHED, SNATCHED_PROPER, SNATCHED_BEST, FAILED from medusa.common import Quality, qualityPresets, statusStrings, qualityPresetStrings, cpu_presets, MULTI_EP_STRINGS - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.indexers.utils import get_trakt_indexer %> <%block name="content"> diff --git a/themes/dark/templates/displayShow.mako b/themes/dark/templates/displayShow.mako index f82840a74e..952b32713a 100644 --- a/themes/dark/templates/displayShow.mako +++ b/themes/dark/templates/displayShow.mako @@ -8,7 +8,7 @@ from medusa.common import Quality, qualityPresets, statusStrings, Overview from medusa.helpers import anon_url from medusa.helper.common import pretty_file_size - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.indexers.utils import mappings %> <%block name="scripts"> diff --git a/themes/dark/templates/editShow.mako b/themes/dark/templates/editShow.mako index 5fb7253b87..a07a94e396 100644 --- a/themes/dark/templates/editShow.mako +++ b/themes/dark/templates/editShow.mako @@ -5,7 +5,7 @@ from medusa.common import SKIPPED, WANTED, UNAIRED, ARCHIVED, IGNORED, SNATCHED, SNATCHED_PROPER, SNATCHED_BEST, FAILED from medusa.common import statusStrings from medusa.helper import exceptions - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.indexers.utils import mappings from medusa import scene_exceptions %> diff --git a/themes/dark/templates/home_massAddTable.mako b/themes/dark/templates/home_massAddTable.mako index 8a6a5764df..4c1e06cecb 100644 --- a/themes/dark/templates/home_massAddTable.mako +++ b/themes/dark/templates/home_massAddTable.mako @@ -1,7 +1,7 @@ <%! from medusa import app from medusa.helpers import anon_url - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi %>
diff --git a/themes/dark/templates/partials/home/banner.mako b/themes/dark/templates/partials/home/banner.mako index 4f212a1587..338481ba2a 100644 --- a/themes/dark/templates/partials/home/banner.mako +++ b/themes/dark/templates/partials/home/banner.mako @@ -3,7 +3,7 @@ import calendar from medusa import sbdatetime from medusa import network_timezones - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.helpers import anon_url from medusa.helper.common import pretty_file_size from medusa.scene_numbering import get_xem_numbering_for_show diff --git a/themes/dark/templates/partials/home/simple.mako b/themes/dark/templates/partials/home/simple.mako index ef3f4efad8..ede1af77ee 100644 --- a/themes/dark/templates/partials/home/simple.mako +++ b/themes/dark/templates/partials/home/simple.mako @@ -3,7 +3,7 @@ import calendar from medusa import sbdatetime from medusa import network_timezones - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.helpers import anon_url from medusa.helper.common import pretty_file_size from medusa.scene_numbering import get_xem_numbering_for_show diff --git a/themes/dark/templates/partials/home/small.mako b/themes/dark/templates/partials/home/small.mako index 418c63d8cb..8513d03dd2 100644 --- a/themes/dark/templates/partials/home/small.mako +++ b/themes/dark/templates/partials/home/small.mako @@ -3,7 +3,7 @@ import calendar from medusa import sbdatetime from medusa import network_timezones - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.helpers import anon_url from medusa.helper.common import pretty_file_size from medusa.scene_numbering import get_xem_numbering_for_show diff --git a/themes/dark/templates/partials/showheader.mako b/themes/dark/templates/partials/showheader.mako index 89d3483c0d..dd20839a43 100644 --- a/themes/dark/templates/partials/showheader.mako +++ b/themes/dark/templates/partials/showheader.mako @@ -7,7 +7,7 @@ from medusa.common import Quality, qualityPresets, statusStrings, Overview from medusa.helpers import anon_url from medusa.helper.common import pretty_file_size - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi %> <%namespace file="/inc_defs.mako" import="renderQualityPill"/> diff --git a/themes/dark/templates/schedule.mako b/themes/dark/templates/schedule.mako index 2bb6e1572f..dd96ed6cdf 100644 --- a/themes/dark/templates/schedule.mako +++ b/themes/dark/templates/schedule.mako @@ -2,7 +2,7 @@ <%! from medusa import app from medusa.helpers import anon_url - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.indexers.utils import indexer_id_to_name, mappings from medusa import sbdatetime from random import choice diff --git a/themes/light/templates/addShows_newShow.mako b/themes/light/templates/addShows_newShow.mako index cfbc5cc37e..71cbee93b2 100644 --- a/themes/light/templates/addShows_newShow.mako +++ b/themes/light/templates/addShows_newShow.mako @@ -2,7 +2,7 @@ <%! from medusa import app from medusa.helpers import anon_url - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi %> <%block name="scripts"> diff --git a/themes/light/templates/config_general.mako b/themes/light/templates/config_general.mako index fcb511a284..1702e9dd99 100644 --- a/themes/light/templates/config_general.mako +++ b/themes/light/templates/config_general.mako @@ -8,7 +8,7 @@ from medusa.sbdatetime import sbdatetime, date_presets, time_presets from medusa.metadata.generic import GenericMetadata from medusa.helpers import anon_url - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi gh_branch = app.GIT_REMOTE_BRANCHES or app.version_check_scheduler.action.list_remote_branches() %> <%block name="content"> diff --git a/themes/light/templates/config_notifications.mako b/themes/light/templates/config_notifications.mako index 548bad6bb2..32b985b326 100644 --- a/themes/light/templates/config_notifications.mako +++ b/themes/light/templates/config_notifications.mako @@ -5,7 +5,7 @@ from medusa.helpers import anon_url from medusa.common import SKIPPED, WANTED, UNAIRED, ARCHIVED, IGNORED, SNATCHED, SNATCHED_PROPER, SNATCHED_BEST, FAILED from medusa.common import Quality, qualityPresets, statusStrings, qualityPresetStrings, cpu_presets, MULTI_EP_STRINGS - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.indexers.utils import get_trakt_indexer %> <%block name="content"> diff --git a/themes/light/templates/displayShow.mako b/themes/light/templates/displayShow.mako index f82840a74e..952b32713a 100644 --- a/themes/light/templates/displayShow.mako +++ b/themes/light/templates/displayShow.mako @@ -8,7 +8,7 @@ from medusa.common import Quality, qualityPresets, statusStrings, Overview from medusa.helpers import anon_url from medusa.helper.common import pretty_file_size - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.indexers.utils import mappings %> <%block name="scripts"> diff --git a/themes/light/templates/editShow.mako b/themes/light/templates/editShow.mako index 5fb7253b87..a07a94e396 100644 --- a/themes/light/templates/editShow.mako +++ b/themes/light/templates/editShow.mako @@ -5,7 +5,7 @@ from medusa.common import SKIPPED, WANTED, UNAIRED, ARCHIVED, IGNORED, SNATCHED, SNATCHED_PROPER, SNATCHED_BEST, FAILED from medusa.common import statusStrings from medusa.helper import exceptions - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.indexers.utils import mappings from medusa import scene_exceptions %> diff --git a/themes/light/templates/home_massAddTable.mako b/themes/light/templates/home_massAddTable.mako index 8a6a5764df..4c1e06cecb 100644 --- a/themes/light/templates/home_massAddTable.mako +++ b/themes/light/templates/home_massAddTable.mako @@ -1,7 +1,7 @@ <%! from medusa import app from medusa.helpers import anon_url - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi %>
diff --git a/themes/light/templates/partials/home/banner.mako b/themes/light/templates/partials/home/banner.mako index 4f212a1587..338481ba2a 100644 --- a/themes/light/templates/partials/home/banner.mako +++ b/themes/light/templates/partials/home/banner.mako @@ -3,7 +3,7 @@ import calendar from medusa import sbdatetime from medusa import network_timezones - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.helpers import anon_url from medusa.helper.common import pretty_file_size from medusa.scene_numbering import get_xem_numbering_for_show diff --git a/themes/light/templates/partials/home/simple.mako b/themes/light/templates/partials/home/simple.mako index ef3f4efad8..ede1af77ee 100644 --- a/themes/light/templates/partials/home/simple.mako +++ b/themes/light/templates/partials/home/simple.mako @@ -3,7 +3,7 @@ import calendar from medusa import sbdatetime from medusa import network_timezones - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.helpers import anon_url from medusa.helper.common import pretty_file_size from medusa.scene_numbering import get_xem_numbering_for_show diff --git a/themes/light/templates/partials/home/small.mako b/themes/light/templates/partials/home/small.mako index 418c63d8cb..8513d03dd2 100644 --- a/themes/light/templates/partials/home/small.mako +++ b/themes/light/templates/partials/home/small.mako @@ -3,7 +3,7 @@ import calendar from medusa import sbdatetime from medusa import network_timezones - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.helpers import anon_url from medusa.helper.common import pretty_file_size from medusa.scene_numbering import get_xem_numbering_for_show diff --git a/themes/light/templates/partials/showheader.mako b/themes/light/templates/partials/showheader.mako index 89d3483c0d..dd20839a43 100644 --- a/themes/light/templates/partials/showheader.mako +++ b/themes/light/templates/partials/showheader.mako @@ -7,7 +7,7 @@ from medusa.common import Quality, qualityPresets, statusStrings, Overview from medusa.helpers import anon_url from medusa.helper.common import pretty_file_size - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi %> <%namespace file="/inc_defs.mako" import="renderQualityPill"/> diff --git a/themes/light/templates/schedule.mako b/themes/light/templates/schedule.mako index 2bb6e1572f..dd96ed6cdf 100644 --- a/themes/light/templates/schedule.mako +++ b/themes/light/templates/schedule.mako @@ -2,7 +2,7 @@ <%! from medusa import app from medusa.helpers import anon_url - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.indexers.utils import indexer_id_to_name, mappings from medusa import sbdatetime from random import choice From 4d9986bdfcae4918ffdcb90088869cbe250d42fd Mon Sep 17 00:00:00 2001 From: P0psicles Date: Sun, 11 Feb 2018 15:23:06 +0100 Subject: [PATCH 16/86] Use get_nested_value() function for tmdb. * Fixed get_nested_value for when it's getting 0 or empty strings. --- medusa/indexers/base.py | 2 +- medusa/indexers/tmdb/api.py | 106 +++++++++++++++++++----------------- 2 files changed, 57 insertions(+), 51 deletions(-) diff --git a/medusa/indexers/base.py b/medusa/indexers/base.py index fa89497c62..e49b79ad99 100644 --- a/medusa/indexers/base.py +++ b/medusa/indexers/base.py @@ -150,7 +150,7 @@ def get_nested_value(self, value, config): check_value = value.get(check_key) next_keys = '.'.join(split_config[1:]) - if not check_value: + if check_value is None: return None if isinstance(check_value, dict): diff --git a/medusa/indexers/tmdb/api.py b/medusa/indexers/tmdb/api.py index 39d2ce305c..fe7b2df2c1 100644 --- a/medusa/indexers/tmdb/api.py +++ b/medusa/indexers/tmdb/api.py @@ -49,43 +49,43 @@ def __init__(self, *args, **kwargs): # pylint: disable=too-many-locals,too-many self.config['artwork_prefix'] = '{base_url}{image_size}{file_path}' # An api to indexer series/episode object mapping - self.series_map = { - 'id': 'id', - 'name': 'seriesname', - 'original_name': 'aliasnames', - 'overview': 'overview', - 'air_date': 'firstaired', - 'first_air_date': 'firstaired', - 'backdrop_path': 'fanart', - 'url': 'show_url', - 'episode_number': 'episodenumber', - 'season_number': 'seasonnumber', - 'dvd_episode_number': 'dvd_episodenumber', - 'last_air_date': 'airs_dayofweek', - 'last_updated': 'lastupdated', - 'network_id': 'networkid', - 'vote_average': 'contentrating', - 'poster_path': 'poster_thumb', - 'genres': 'genre', - 'type': 'classification', - 'networks': 'network', - 'episode_run_time': 'runtime' - } - - self.episodes_map = { - 'id': 'id', - 'name': 'episodename', - 'overview': 'overview', - 'air_date': 'firstaired', - 'episode_run_time': 'runtime', - 'episode_number': 'episodenumber', - 'season_number': 'seasonnumber', - 'vote_average': 'contentrating', - 'still_path': 'filename' - } - - @staticmethod - def _map_results(tmdb_response, key_mappings=None, list_separator='|'): + self.series_map = [ + ('id', 'id'), + ('seriesname', 'name'), + ('aliasnames', 'original_name'), + ('overview', 'overview'), + ('firstaired', 'air_date'), + ('firstaired', 'first_air_date'), + ('show_url', 'url'), + ('episodenumber', 'episode_number'), + ('seasonnumber', 'season_number'), + ('dvd_episodenumber', 'dvd_episode_number'), + ('airs_dayofweek', 'last_air_date'), + ('lastupdated', 'last_updated'), + ('networkid', 'network_id'), + ('contentrating', 'vote_average'), + ('genre', 'genres'), + ('classification', 'type'), + ('network', 'networks[0].name'), + ('runtime', 'episode_run_time'), + ('seasons', 'seasons'), + ('poster_thumb', 'poster_path'), + ('fanart', 'backdrop_path'), + ] + + self.episodes_map = [ + ('id', 'id'), + ('episodename', 'name'), + ('overview', 'overview'), + ('firstaired', 'air_date'), + ('runtime', 'episode_run_time'), + ('episodenumber', 'episode_number'), + ('seasonnumber', 'season_number'), + ('contentrating', 'vote_average'), + ('filename', 'still_path'), + ] + + def _map_results(self, tmdb_response, key_mappings=None, list_separator='|'): """Map results to a a key_mapping dict. :type tmdb_response: object @@ -103,7 +103,10 @@ def week_day(input_date): for item in tmdb_response: return_dict = {} try: - for key, value in item.items(): + + for key, config in key_mappings: + value = self.get_nested_value(item, config) + if value is None or value == []: continue @@ -113,23 +116,16 @@ def week_day(input_date): value = list_separator.join(text_type(v) for v in value) # Process genres - if key == 'genres': + if key == 'genre': value = list_separator.join(item['name'] for item in value) - if key == 'networks': - value = value[0].get('name') if value else '' - - if key == 'last_air_date': + if key == 'airs_dayofweek': value = week_day(value) - if key == 'episode_run_time': + if key == 'runtime': # Using the longest episode runtime if there are multiple. value = max(value) if isinstance(value, list) else '' - # Try to map the key - if key in key_mappings: - key = key_mappings[key] - # Set value to key return_dict[key] = value @@ -444,17 +440,27 @@ def _get_show_data(self, sid, language='en'): # pylint: disable=too-many-branch # Create a key/value dict, to map the image type to a default image width. # possitlbe widths can also be retrieved from self.configuration.images['poster_sizes'] and # self.configuration.images['still_sizes'] - image_width = {'fanart': 'w1280', 'poster': 'w500'} + image_width = {'fanart': 'w1280', 'poster_thumb': 'w500'} for k, v in series_info['series'].items(): if v is not None: - if k in ['fanart', 'banner', 'poster']: + if k in ['fanart', 'banner', 'poster_thumb']: v = self.config['artwork_prefix'].format(base_url=self.tmdb_configuration.images['base_url'], image_size=image_width[k], file_path=v) self._set_show_data(sid, k, v) + # Let's also store the poster with its original size. + if series_info['series'].get('poster_thumb'): + self._set_show_data(sid, 'poster', + self.config['artwork_prefix'].format( + base_url=self.tmdb_configuration.images['base_url'], + image_size='original', + file_path=series_info['series'].get('poster_thumb') + ) +) + # Get external ids. # As the external id's are not part of the shows default response, we need to make an additional call for it. self._set_show_data(sid, 'externals', self.tmdb.TV(sid).external_ids()) From 817e695bf11598f305b066ed09702411e90845a9 Mon Sep 17 00:00:00 2001 From: P0psicles Date: Sun, 11 Feb 2018 15:25:49 +0100 Subject: [PATCH 17/86] tvmaze exceptions got removed. --- medusa/indexers/tvmaze/exceptions.py | 75 ++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 medusa/indexers/tvmaze/exceptions.py diff --git a/medusa/indexers/tvmaze/exceptions.py b/medusa/indexers/tvmaze/exceptions.py new file mode 100644 index 0000000000..f52f52f79e --- /dev/null +++ b/medusa/indexers/tvmaze/exceptions.py @@ -0,0 +1,75 @@ +# coding=utf-8 +# Author: p0psicles +# +# This file is part of Medusa. +# +# Medusa is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Medusa is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Medusa. If not, see . + +"""Custom exceptions used or raised by tvmaze api.""" + +__author__ = "p0psicles" +__version__ = "1.0" + +__all__ = ["tvmaze_error", "tvmaze_userabort", "tvmaze_shownotfound", "tvmaze_showincomplete", + "tvmaze_seasonnotfound", "tvmaze_episodenotfound", "tvmaze_attributenotfound"] + + +class tvmaze_exception(Exception): + """Any exception generated by tvmaze_api + """ + pass + + +class tvmaze_error(tvmaze_exception): + """An error with thetvdb.com (Cannot connect, for example) + """ + pass + + +class tvmaze_userabort(tvmaze_exception): + """User aborted the interactive selection (via + the q command, ^c etc) + """ + pass + + +class tvmaze_shownotfound(tvmaze_exception): + """Show cannot be found on thetvdb.com (non-existant show) + """ + pass + + +class tvmaze_showincomplete(tvmaze_exception): + """Show found but incomplete on thetvdb.com (incomplete show) + """ + pass + + +class tvmaze_seasonnotfound(tvmaze_exception): + """Season cannot be found on thetvdb.com + """ + pass + + +class tvmaze_episodenotfound(tvmaze_exception): + """Episode cannot be found on thetvdb.com + """ + pass + + +class tvmaze_attributenotfound(tvmaze_exception): + """Raised if an episode does not have the requested + attribute (such as a episode name) + """ + pass From f3e87f3c050058f585d04fc0c590c385bec7b5c5 Mon Sep 17 00:00:00 2001 From: P0psicles Date: Sun, 11 Feb 2018 17:13:06 +0100 Subject: [PATCH 18/86] Get airs day of week, from last 10 airdates. --- medusa/indexers/imdb/api.py | 52 +++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/medusa/indexers/imdb/api.py b/medusa/indexers/imdb/api.py index ddd45953bb..ad921c3eaf 100644 --- a/medusa/indexers/imdb/api.py +++ b/medusa/indexers/imdb/api.py @@ -5,7 +5,7 @@ from datetime import datetime from itertools import chain import logging -from collections import OrderedDict +from collections import namedtuple, OrderedDict from imdbpie import imdbpie import locale from six import string_types, text_type @@ -269,19 +269,35 @@ def _enrich_episodes(self, imdb_id, season): :param imdb_id: imdb id including the `tt`. :param season: season passed as integer. """ + def parse_date_with_local(date, template, use_locale, method='strptime'): + lc = locale.setlocale(locale.LC_TIME) + locale.setlocale(locale.LC_ALL, use_locale) + try: + if method == 'strptime': + return datetime.strptime(date, template) + else: + return date.strftime(template) + except (AttributeError, ValueError): + raise + finally: + locale.setlocale(locale.LC_TIME, lc) + episodes_url = 'http://www.imdb.com/title/{imdb_id}/episodes?season={season}' series_id = ImdbIdentifier(imdb_id).series_id series_status = 'Ended' + episodes = [] + try: response = self.config['session'].get(episodes_url.format(imdb_id=ImdbIdentifier(imdb_id).imdb_id, season=season)) if not response or not response.text: log.warning('Problem requesting episode information for show {0}, and season {1}.', imdb_id, season) return + Episode = namedtuple('Episode', ['episode_number', 'season_number', 'first_aired', 'episode_rating', 'episode_votes', 'synopsis']) with BS4Parser(response.text, 'html5lib') as html: for episode in html.find_all('div', class_='list_item'): try: - episode_no = int(episode.find('meta')['content']) + episode_number = int(episode.find('meta')['content']) except AttributeError: pass @@ -290,23 +306,19 @@ def _enrich_episodes(self, imdb_id, season): except AttributeError: pass - lc = locale.setlocale(locale.LC_TIME) try: - locale.setlocale(locale.LC_ALL, 'C') - first_aired = datetime.strptime(first_aired_raw.replace('.', ''), '%d %b %Y').strftime('%Y-%m-%d') + first_aired = parse_date_with_local(first_aired_raw.replace('.', ''), '%d %b %Y', 'C').strftime('%Y-%m-%d') except (AttributeError, ValueError): try: - first_aired = datetime.strptime(first_aired_raw.replace('.', ''), '%b %Y').strftime('%Y-%m-01') + first_aired = parse_date_with_local(first_aired_raw.replace('.', ''), '%b %Y', 'C').strftime('%Y-%m-01') series_status = 'Continuing' except (AttributeError, ValueError): try: - datetime.strptime(first_aired_raw.replace('.', ''), '%Y').strftime('%Y') + parse_date_with_local(first_aired_raw.replace('.', ''), '%Y', 'C').strftime('%Y') first_aired = None series_status = 'Continuing' except (AttributeError, ValueError): first_aired = None - finally: - locale.setlocale(locale.LC_TIME, lc) try: episode_rating = float(episode.find('span', class_='ipl-rating-star__rating').get_text(strip=True)) @@ -327,15 +339,29 @@ def _enrich_episodes(self, imdb_id, season): except AttributeError: synopsis = '' - self._set_item(series_id, season, episode_no, 'firstaired', first_aired) - self._set_item(series_id, season, episode_no, 'rating', episode_rating) - self._set_item(series_id, season, episode_no, 'votes', episode_votes) - self._set_item(series_id, season, episode_no, 'overview', synopsis) + episodes.append(Episode(episode_number=episode_number, season_number=season, first_aired=first_aired, + episode_rating=episode_rating, episode_votes=episode_votes, synopsis=synopsis)) self._set_show_data(series_id, 'status', series_status) except Exception as error: log.exception('Error while trying to enrich imdb series {0}, {1}', series_id, error) + for episode in episodes: + self._set_item(series_id, episode.season_number, episode.episode_number, 'firstaired', episode.first_aired) + self._set_item(series_id, episode.season_number, episode.episode_number, 'rating', episode.episode_rating) + self._set_item(series_id, episode.season_number, episode.episode_number, 'votes', episode.episode_votes) + self._set_item(series_id, episode.season_number, episode.episode_number, 'overview', episode.synopsis) + + # Get the last (max 10 airdates) and try to calculate an airday + time. + last_airdates = sorted(episodes, key=lambda x: x.first_aired, reverse=True)[:10] + weekdays = {} + for aired in last_airdates: + day = parse_date_with_local(datetime.strptime(aired.first_aired, '%Y-%m-%d'), '%A', 'C', method='strftime') + weekdays[day] = 1 if day not in weekdays else weekdays[day] + 1 + + airs_day_of_week = sorted(weekdays.keys(), key=lambda x: weekdays[x])[0] + self._set_show_data(series_id, 'airs_dayofweek', airs_day_of_week) + def _parse_images(self, imdb_id): """Parse Show and Season posters. From 20b0d485da2f6dc01122f592a014d7091d44ff7d Mon Sep 17 00:00:00 2001 From: P0psicles Date: Sun, 11 Feb 2018 18:41:56 +0100 Subject: [PATCH 19/86] Added episode thumbnails with kodi_12plus. --- medusa/indexers/imdb/api.py | 13 ++++++++++--- medusa/metadata/kodi_12plus.py | 4 ++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/medusa/indexers/imdb/api.py b/medusa/indexers/imdb/api.py index ad921c3eaf..d594c368b5 100644 --- a/medusa/indexers/imdb/api.py +++ b/medusa/indexers/imdb/api.py @@ -213,7 +213,7 @@ def _get_show_by_id(self, imdb_id): # pylint: disable=unused-argument return OrderedDict({'series': mapped_results}) - def _get_episodes(self, imdb_id): # pylint: disable=unused-argument + def _get_episodes(self, imdb_id, *args): # pylint: disable=unused-argument """ Get all the episodes for a show by imdb id @@ -293,7 +293,7 @@ def parse_date_with_local(date, template, use_locale, method='strptime'): log.warning('Problem requesting episode information for show {0}, and season {1}.', imdb_id, season) return - Episode = namedtuple('Episode', ['episode_number', 'season_number', 'first_aired', 'episode_rating', 'episode_votes', 'synopsis']) + Episode = namedtuple('Episode', ['episode_number', 'season_number', 'first_aired', 'episode_rating', 'episode_votes', 'synopsis', 'thumbnail']) with BS4Parser(response.text, 'html5lib') as html: for episode in html.find_all('div', class_='list_item'): try: @@ -339,8 +339,14 @@ def parse_date_with_local(date, template, use_locale, method='strptime'): except AttributeError: synopsis = '' + try: + episode_thumbnail = episode.find('img', class_='zero-z-index')['src'] + except: + episode_thumbnail = None + episodes.append(Episode(episode_number=episode_number, season_number=season, first_aired=first_aired, - episode_rating=episode_rating, episode_votes=episode_votes, synopsis=synopsis)) + episode_rating=episode_rating, episode_votes=episode_votes, + synopsis=synopsis, thumbnail=episode_thumbnail)) self._set_show_data(series_id, 'status', series_status) except Exception as error: @@ -351,6 +357,7 @@ def parse_date_with_local(date, template, use_locale, method='strptime'): self._set_item(series_id, episode.season_number, episode.episode_number, 'rating', episode.episode_rating) self._set_item(series_id, episode.season_number, episode.episode_number, 'votes', episode.episode_votes) self._set_item(series_id, episode.season_number, episode.episode_number, 'overview', episode.synopsis) + self._set_item(series_id, episode.season_number, episode.episode_number, 'filename', episode.thumbnail) # Get the last (max 10 airdates) and try to calculate an airday + time. last_airdates = sorted(episodes, key=lambda x: x.first_aired, reverse=True)[:10] diff --git a/medusa/metadata/kodi_12plus.py b/medusa/metadata/kodi_12plus.py index c33e8ba46e..32af8752ea 100644 --- a/medusa/metadata/kodi_12plus.py +++ b/medusa/metadata/kodi_12plus.py @@ -194,7 +194,7 @@ def _show_data(self, series_obj): cur_actor_role = etree.SubElement(cur_actor, 'role') cur_actor_role.text = actor['role'].strip() - if 'image' in actor and actor['image'].strip(): + if actor.get('image') and actor['image'].strip(): cur_actor_thumb = etree.SubElement(cur_actor, 'thumb') cur_actor_thumb.text = actor['image'].strip() @@ -331,7 +331,7 @@ def _ep_data(self, ep_obj): cur_actor_role = etree.SubElement(cur_actor, 'role') cur_actor_role.text = actor['role'].strip() - if 'image' in actor and actor['image'].strip(): + if actor.get('image') and actor['image'].strip(): cur_actor_thumb = etree.SubElement(cur_actor, 'thumb') cur_actor_thumb.text = actor['image'].strip() From f1bfbc1b91610d5333a69106d8eddf52245ae589 Mon Sep 17 00:00:00 2001 From: P0psicles Date: Sun, 18 Feb 2018 19:27:31 +0100 Subject: [PATCH 20/86] Fix issues with shows missing info. * correct merge conflict. --- medusa/indexers/imdb/api.py | 21 ++++++++++++++------- medusa/show/recommendations/anidb.py | 2 +- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/medusa/indexers/imdb/api.py b/medusa/indexers/imdb/api.py index d594c368b5..619c9efe47 100644 --- a/medusa/indexers/imdb/api.py +++ b/medusa/indexers/imdb/api.py @@ -205,11 +205,17 @@ def _get_show_by_id(self, imdb_id): # pylint: disable=unused-argument mapped_results['firstaired'] = first_released['date'] companies = self.imdb_api.get_title_companies(imdb_id) - origins = self.imdb_api.get_title_versions(imdb_id)['origins'][0] - first_release = sorted([dist for dist in companies['distribution'] if origins in dist['regions']], key=lambda x: x['startYear']) - if first_release: - mapped_results['network'] = first_release[0]['company']['name'] + # If there was a release it had to be distributed. + if companies.get('distribution'): + origins = self.imdb_api.get_title_versions(imdb_id)['origins'][0] + released_in_regions = [ + dist for dist in companies['distribution'] if dist.get('regions') and origins in dist['regions'] + ] + first_release = sorted(released_in_regions, key=lambda x: x['startYear']) + + if first_release: + mapped_results['network'] = first_release[0]['company']['name'] return OrderedDict({'series': mapped_results}) @@ -363,10 +369,11 @@ def parse_date_with_local(date, template, use_locale, method='strptime'): last_airdates = sorted(episodes, key=lambda x: x.first_aired, reverse=True)[:10] weekdays = {} for aired in last_airdates: - day = parse_date_with_local(datetime.strptime(aired.first_aired, '%Y-%m-%d'), '%A', 'C', method='strftime') - weekdays[day] = 1 if day not in weekdays else weekdays[day] + 1 + if aired.first_aired: + day = parse_date_with_local(datetime.strptime(aired.first_aired, '%Y-%m-%d'), '%A', 'C', method='strftime') + weekdays[day] = 1 if day not in weekdays else weekdays[day] + 1 - airs_day_of_week = sorted(weekdays.keys(), key=lambda x: weekdays[x])[0] + airs_day_of_week = sorted(weekdays.keys(), key=lambda x: weekdays[x], reverse=True)[0] if weekdays else None self._set_show_data(series_id, 'airs_dayofweek', airs_day_of_week) def _parse_images(self, imdb_id): diff --git a/medusa/show/recommendations/anidb.py b/medusa/show/recommendations/anidb.py index 4dc105d781..a27e6462e7 100644 --- a/medusa/show/recommendations/anidb.py +++ b/medusa/show/recommendations/anidb.py @@ -7,7 +7,7 @@ from medusa import app from medusa.cache import recommended_series_cache -from medusa.indexers.indexer_config import INDEXER_TVDBV2 +from medusa.indexers.config import INDEXER_TVDBV2 from medusa.logger.adapters.style import BraceAdapter from medusa.session.core import MedusaSession from medusa.show.recommendations.recommended import ( From f956eecebcf55627eb0e1988943093267acc5459 Mon Sep 17 00:00:00 2001 From: P0psicles Date: Mon, 19 Feb 2018 08:40:07 +0100 Subject: [PATCH 21/86] Fix exception for when search doesn't return any results. The raise, was removed somewhere. --- medusa/indexers/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/medusa/indexers/base.py b/medusa/indexers/base.py index e49b79ad99..4c75c96544 100644 --- a/medusa/indexers/base.py +++ b/medusa/indexers/base.py @@ -184,7 +184,7 @@ def _get_show_data(self, sid, language): return None def _get_series(self, series): - """Search themoviedb.org for the series name. + """Search indexer for the series name. If a custom_ui UI is configured, it uses this to select the correct series. If not, and interactive == True, ConsoleUI is used, if not @@ -196,7 +196,7 @@ def _get_series(self, series): all_series = self.search(series) if not all_series: log.debug('Series result returned zero') - IndexerShowNotFound('Show search returned zero results (cannot find show on Indexer)') + raise IndexerShowNotFound('Show search returned zero results (cannot find show on Indexer)') if not isinstance(all_series, list): all_series = [all_series] From 65fb5d6cd06964b9abec500b6433348fcf203840 Mon Sep 17 00:00:00 2001 From: P0psicles Date: Mon, 19 Feb 2018 08:41:08 +0100 Subject: [PATCH 22/86] Use templating for the show_url. * Imdb requires the tt with appended 0's in front of the id. --- medusa/indexers/config.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/medusa/indexers/config.py b/medusa/indexers/config.py index b6e810b953..0372460dc7 100644 --- a/medusa/indexers/config.py +++ b/medusa/indexers/config.py @@ -78,7 +78,7 @@ 'icon': 'thetvdb16.png', 'scene_loc': '{base_url}/scene_exceptions/scene_exceptions_tvdb.json'.format(base_url=BASE_PYMEDUSA_URL), 'base_url': 'https://api.thetvdb.com/', - 'show_url': 'http://thetvdb.com/?tab=series&id=', + 'show_url': 'http://thetvdb.com/?tab=series&id={0}', 'mapped_to': 'tvdb_id', # The attribute to which other indexers can map there thetvdb id to 'identifier': 'tvdb', # Also used as key for the custom scenename exceptions. (_get_custom_exceptions()) }, @@ -95,7 +95,7 @@ 'xem_mapped_to': INDEXER_TVDBV2, 'icon': 'tvmaze16.png', 'scene_loc': '{base_url}/scene_exceptions/scene_exceptions_tvmaze.json'.format(base_url=BASE_PYMEDUSA_URL), - 'show_url': 'http://www.tvmaze.com/shows/', + 'show_url': 'http://www.tvmaze.com/shows/{0}', 'base_url': 'http://api.tvmaze.com/', 'mapped_to': 'tvmaze_id', # The attribute to which other indexers can map their tvmaze id to 'identifier': 'tvmaze', # Also used as key for the custom scenename exceptions. (_get_custom_exceptions()) @@ -113,7 +113,7 @@ 'icon': 'tmdb16.png', 'scene_loc': '{base_url}/scene_exceptions/scene_exceptions_tmdb.json'.format(base_url=BASE_PYMEDUSA_URL), 'base_url': 'https://www.themoviedb.org/', - 'show_url': 'https://www.themoviedb.org/tv/', + 'show_url': 'https://www.themoviedb.org/tv/{0}', 'mapped_to': 'tmdb_id', # The attribute to which other indexers can map their tmdb id to 'identifier': 'tmdb', # Also used as key for the custom scenename exceptions. (_get_custom_exceptions()) }, @@ -130,7 +130,7 @@ 'xem_mapped_to': INDEXER_TVDBV2, 'icon': 'imdb16.png', 'scene_loc': '{base_url}/scene_exceptions/scene_exceptions_imdb.json'.format(base_url=BASE_PYMEDUSA_URL), - 'show_url': 'http://www.imdb.com/title/', + 'show_url': 'http://www.imdb.com/title/tt{0:07d}', 'base_url': 'https://v2.sg.media-imdb.com', 'mapped_to': 'imdb_id', # The attribute to which other indexers can map their imdb id to 'identifier': 'imdb', # Also used as key for the custom scenename exceptions. (_get_custom_exceptions()) From 18009614ee1d254bafc130ff22e84480393cadf6 Mon Sep 17 00:00:00 2001 From: P0psicles Date: Mon, 19 Feb 2018 08:42:33 +0100 Subject: [PATCH 23/86] Change the addShows addShows/searchIndexersForShowName function to return a key/value pair of values. In stead of list. * Refactored mako for the refactor of the indexer_api to api. --- medusa/server/web/home/add_shows.py | 10 +++- .../legacy/static/js/add-shows/new-show.js | 20 +++---- .../slim/static/js/add-shows/new-show.js | 22 +++---- themes-default/slim/yarn.lock | 59 ++++++++++--------- themes/dark/assets/js/add-shows/new-show.js | 22 +++---- .../dark/assets/js/add-shows/new-show.js.map | 2 +- themes/dark/package.json | 2 +- themes/legacy/assets/js/add-shows/new-show.js | 20 +++---- .../assets/js/add-shows/new-show.js.map | 2 +- themes/legacy/templates/addShows_newShow.mako | 2 +- themes/legacy/templates/config_general.mako | 2 +- .../templates/config_notifications.mako | 2 +- themes/legacy/templates/displayShow.mako | 2 +- themes/legacy/templates/editShow.mako | 2 +- .../legacy/templates/home_massAddTable.mako | 2 +- themes/legacy/templates/layouts/main.mako | 3 +- .../templates/partials/home/banner.mako | 2 +- .../templates/partials/home/simple.mako | 2 +- .../legacy/templates/partials/home/small.mako | 2 +- .../legacy/templates/partials/showheader.mako | 2 +- themes/legacy/templates/schedule.mako | 2 +- themes/light/assets/js/add-shows/new-show.js | 22 +++---- .../light/assets/js/add-shows/new-show.js.map | 2 +- 23 files changed, 108 insertions(+), 100 deletions(-) diff --git a/medusa/server/web/home/add_shows.py b/medusa/server/web/home/add_shows.py index 752c33859a..c53ed02207 100644 --- a/medusa/server/web/home/add_shows.py +++ b/medusa/server/web/home/add_shows.py @@ -95,9 +95,13 @@ def searchIndexersForShowName(search_term, lang=None, indexer=None): logger.log(u'Error searching for show: {error}'.format(error=e.message)) for i, shows in iteritems(results): - final_results.extend({(indexerApi(i).name, i, indexerApi(i).config['show_url'], int(show['id']), - show['seriesname'].encode('utf-8'), show['firstaired'] or 'N/A', - show.get('network', '').encode('utf-8') or 'N/A') for show in shows}) + final_results.extend({'indexerName': indexerApi(i).name, + 'indexer': i, + 'showUrl': indexerApi(i).config['show_url'].format(show['id']), + 'seriesId': int(show['id']), + 'seriesName': show['seriesname'].encode('utf-8'), + 'firstAired': show['firstaired'] or 'N/A', + 'network': show.get('network', '').encode('utf-8') or 'N/A'} for show in shows) lang_id = indexerApi().config['langabbv_to_id'][lang] return json.dumps({'results': final_results, 'langid': lang_id}) diff --git a/themes-default/legacy/static/js/add-shows/new-show.js b/themes-default/legacy/static/js/add-shows/new-show.js index 96f0cf76b9..ac9ad19af5 100644 --- a/themes-default/legacy/static/js/add-shows/new-show.js +++ b/themes-default/legacy/static/js/add-shows/new-show.js @@ -106,27 +106,27 @@ MEDUSA.addShows.newShow = function() { checked = ''; } - var whichSeries = obj.join('|'); + var whichSeries = obj.indexerName + '|' + obj.indexer + '|' + obj.showUrl + '|' + obj.seriesId + '|' + obj.seriesName + '|' + obj.firstAired + '|' + obj.network; resultStr += ' '; - if (data.langid && data.langid !== '' && obj[1] === 1) { // For now only add the language id to the tvdb url, as the others might have different routes. - resultStr += '' + obj[4] + ''; + if (data.langid && data.langid !== '' && obj.indexer === 1) { // For now only add the language id to the tvdb url, as the others might have different routes. + resultStr += '' + obj.seriesName + ''; } else { - resultStr += '' + obj[4] + ''; + resultStr += '' + obj.seriesName + ''; } - if (obj[5] !== null) { - var startDate = new Date(obj[5]); + if (obj.firstAired !== null) { + var startDate = new Date(obj.firstAired); var today = new Date(); if (startDate > today) { - resultStr += ' (will debut on ' + obj[5] + ' on ' + obj[6] + ')'; + resultStr += ' (will debut on ' + obj.firstAired + ' on ' + obj.network + ')'; } else { - resultStr += ' (started on ' + obj[5] + ' on ' + obj[6] + ')'; + resultStr += ' (started on ' + obj.firstAired + ' on ' + obj.network + ')'; } } - if (obj[0] !== null) { - resultStr += ' [' + obj[0] + ']'; + if (obj.indexerName !== null) { + resultStr += ' [' + obj.indexerName + ']'; } resultStr += '
'; diff --git a/themes-default/slim/static/js/add-shows/new-show.js b/themes-default/slim/static/js/add-shows/new-show.js index 8077b4840c..7446593c90 100644 --- a/themes-default/slim/static/js/add-shows/new-show.js +++ b/themes-default/slim/static/js/add-shows/new-show.js @@ -106,27 +106,27 @@ MEDUSA.addShows.newShow = function() { checked = ''; } - const whichSeries = obj.join('|'); + const whichSeries = `${obj.indexerName}|${obj.indexer}|${obj.showUrl}|${obj.seriesId}|${obj.seriesName}|${obj.firstAired}|${obj.network}`; - resultStr += ' '; - if (data.langid && data.langid !== '' && obj[1] === 1) { // For now only add the language id to the tvdb url, as the others might have different routes. - resultStr += '' + obj[4] + ''; + resultStr += ` `; + if (data.langid && data.langid !== '' && obj.indexer === 1) { // For now only add the language id to the tvdb url, as the others might have different routes. + resultStr += `${obj.seriesName}`; } else { - resultStr += '' + obj[4] + ''; + resultStr += `${obj.seriesName}`; } - if (obj[5] !== null) { - const startDate = new Date(obj[5]); + if (obj.firstAired !== null) { + const startDate = new Date(obj.firstAired); const today = new Date(); if (startDate > today) { - resultStr += ' (will debut on ' + obj[5] + ' on ' + obj[6] + ')'; + resultStr += ` (will debut on ${obj.firstAired} on ${obj.network})`; } else { - resultStr += ' (started on ' + obj[5] + ' on ' + obj[6] + ')'; + resultStr += ` (started on ${obj.firstAired} on ${obj.network})`; } } - if (obj[0] !== null) { - resultStr += ' [' + obj[0] + ']'; + if (obj.indexerName !== null) { + resultStr += ` [${obj.indexerName}]`; } resultStr += '
'; diff --git a/themes-default/slim/yarn.lock b/themes-default/slim/yarn.lock index 59963fae66..dbe3b7aa89 100644 --- a/themes-default/slim/yarn.lock +++ b/themes-default/slim/yarn.lock @@ -1922,6 +1922,13 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" +cross-env@^5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-5.1.3.tgz#f8ae18faac87692b0a8b4d2f7000d4ec3a85dfd7" + dependencies: + cross-spawn "^5.1.0" + is-windows "^1.0.0" + cross-spawn@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982" @@ -2806,9 +2813,9 @@ eslint@^3.18.0: text-table "~0.2.0" user-home "^2.0.0" -eslint@^4.0.0, eslint@^4.16.0: - version "4.16.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.16.0.tgz#934ada9e98715e1d7bbfd6f6f0519ed2fab35cc1" +eslint@^4.0.0, eslint@^4.17.0: + version "4.17.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.17.0.tgz#dc24bb51ede48df629be7031c71d9dc0ee4f3ddf" dependencies: ajv "^5.3.0" babel-code-frame "^6.22.0" @@ -2848,9 +2855,9 @@ eslint@^4.0.0, eslint@^4.16.0: table "^4.0.1" text-table "~0.2.0" -eslint@^4.17.0: - version "4.17.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.17.0.tgz#dc24bb51ede48df629be7031c71d9dc0ee4f3ddf" +eslint@^4.16.0: + version "4.16.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.16.0.tgz#934ada9e98715e1d7bbfd6f6f0519ed2fab35cc1" dependencies: ajv "^5.3.0" babel-code-frame "^6.22.0" @@ -4838,7 +4845,7 @@ is-valid-glob@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-0.3.0.tgz#d4b55c69f51886f9b65c70d6c2622d37e29f48fe" -is-windows@^1.0.1: +is-windows@^1.0.0, is-windows@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.1.tgz#310db70f742d259a16a369202b51af84233310d9" @@ -5402,11 +5409,7 @@ lodash.merge@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.0.tgz#69884ba144ac33fe699737a6086deffadd0f89c5" -lodash.mergewith@^4.3.1, lodash.mergewith@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz#150cf0a16791f5903b8891eab154609274bdea55" - -lodash.mergewith@^4.6.1: +lodash.mergewith@^4.3.1, lodash.mergewith@^4.6.0, lodash.mergewith@^4.6.1: version "4.6.1" resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927" @@ -7372,18 +7375,18 @@ replace-ext@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" -request@2, request@2.81.0: - version "2.81.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" +request@2, request@~2.79.0: + version "2.79.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" dependencies: aws-sign2 "~0.6.0" aws4 "^1.2.1" - caseless "~0.12.0" + caseless "~0.11.0" combined-stream "~1.0.5" extend "~3.0.0" forever-agent "~0.6.1" form-data "~2.1.1" - har-validator "~4.2.1" + har-validator "~2.0.6" hawk "~3.1.3" http-signature "~1.1.0" is-typedarray "~1.0.0" @@ -7391,26 +7394,24 @@ request@2, request@2.81.0: json-stringify-safe "~5.0.1" mime-types "~2.1.7" oauth-sign "~0.8.1" - performance-now "^0.2.0" - qs "~6.4.0" - safe-buffer "^5.0.1" + qs "~6.3.0" stringstream "~0.0.4" tough-cookie "~2.3.0" - tunnel-agent "^0.6.0" + tunnel-agent "~0.4.1" uuid "^3.0.0" -request@~2.79.0: - version "2.79.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" +request@2.81.0: + version "2.81.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" dependencies: aws-sign2 "~0.6.0" aws4 "^1.2.1" - caseless "~0.11.0" + caseless "~0.12.0" combined-stream "~1.0.5" extend "~3.0.0" forever-agent "~0.6.1" form-data "~2.1.1" - har-validator "~2.0.6" + har-validator "~4.2.1" hawk "~3.1.3" http-signature "~1.1.0" is-typedarray "~1.0.0" @@ -7418,10 +7419,12 @@ request@~2.79.0: json-stringify-safe "~5.0.1" mime-types "~2.1.7" oauth-sign "~0.8.1" - qs "~6.3.0" + performance-now "^0.2.0" + qs "~6.4.0" + safe-buffer "^5.0.1" stringstream "~0.0.4" tough-cookie "~2.3.0" - tunnel-agent "~0.4.1" + tunnel-agent "^0.6.0" uuid "^3.0.0" require-directory@^2.1.1: diff --git a/themes/dark/assets/js/add-shows/new-show.js b/themes/dark/assets/js/add-shows/new-show.js index 8077b4840c..7446593c90 100644 --- a/themes/dark/assets/js/add-shows/new-show.js +++ b/themes/dark/assets/js/add-shows/new-show.js @@ -106,27 +106,27 @@ MEDUSA.addShows.newShow = function() { checked = ''; } - const whichSeries = obj.join('|'); + const whichSeries = `${obj.indexerName}|${obj.indexer}|${obj.showUrl}|${obj.seriesId}|${obj.seriesName}|${obj.firstAired}|${obj.network}`; - resultStr += ' '; - if (data.langid && data.langid !== '' && obj[1] === 1) { // For now only add the language id to the tvdb url, as the others might have different routes. - resultStr += '' + obj[4] + ''; + resultStr += ` `; + if (data.langid && data.langid !== '' && obj.indexer === 1) { // For now only add the language id to the tvdb url, as the others might have different routes. + resultStr += `${obj.seriesName}`; } else { - resultStr += '' + obj[4] + ''; + resultStr += `${obj.seriesName}`; } - if (obj[5] !== null) { - const startDate = new Date(obj[5]); + if (obj.firstAired !== null) { + const startDate = new Date(obj.firstAired); const today = new Date(); if (startDate > today) { - resultStr += ' (will debut on ' + obj[5] + ' on ' + obj[6] + ')'; + resultStr += ` (will debut on ${obj.firstAired} on ${obj.network})`; } else { - resultStr += ' (started on ' + obj[5] + ' on ' + obj[6] + ')'; + resultStr += ` (started on ${obj.firstAired} on ${obj.network})`; } } - if (obj[0] !== null) { - resultStr += ' [' + obj[0] + ']'; + if (obj.indexerName !== null) { + resultStr += ` [${obj.indexerName}]`; } resultStr += '
'; diff --git a/themes/dark/assets/js/add-shows/new-show.js.map b/themes/dark/assets/js/add-shows/new-show.js.map index a2d9030710..6f0e86ddc0 100644 --- a/themes/dark/assets/js/add-shows/new-show.js.map +++ b/themes/dark/assets/js/add-shows/new-show.js.map @@ -1 +1 @@ -{"version":3,"names":[],"mappings":"","sources":["js/add-shows/new-show.js"],"sourcesContent":["(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require==\"function\"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error(\"Cannot find module '\"+o+\"'\");throw f.code=\"MODULE_NOT_FOUND\",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require==\"function\"&&require;for(var o=0;o
diff --git a/themes/legacy/templates/layouts/main.mako b/themes/legacy/templates/layouts/main.mako index 9173f92e33..cd5cd9c66d 100644 --- a/themes/legacy/templates/layouts/main.mako +++ b/themes/legacy/templates/layouts/main.mako @@ -1,5 +1,6 @@ <%! from medusa import app + from medusa.server.core import clean_url_path %> @@ -48,7 +49,7 @@ <%block name="css" /> - +
diff --git a/themes/legacy/templates/partials/home/banner.mako b/themes/legacy/templates/partials/home/banner.mako index 4f212a1587..338481ba2a 100644 --- a/themes/legacy/templates/partials/home/banner.mako +++ b/themes/legacy/templates/partials/home/banner.mako @@ -3,7 +3,7 @@ import calendar from medusa import sbdatetime from medusa import network_timezones - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.helpers import anon_url from medusa.helper.common import pretty_file_size from medusa.scene_numbering import get_xem_numbering_for_show diff --git a/themes/legacy/templates/partials/home/simple.mako b/themes/legacy/templates/partials/home/simple.mako index ef3f4efad8..ede1af77ee 100644 --- a/themes/legacy/templates/partials/home/simple.mako +++ b/themes/legacy/templates/partials/home/simple.mako @@ -3,7 +3,7 @@ import calendar from medusa import sbdatetime from medusa import network_timezones - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.helpers import anon_url from medusa.helper.common import pretty_file_size from medusa.scene_numbering import get_xem_numbering_for_show diff --git a/themes/legacy/templates/partials/home/small.mako b/themes/legacy/templates/partials/home/small.mako index 418c63d8cb..8513d03dd2 100644 --- a/themes/legacy/templates/partials/home/small.mako +++ b/themes/legacy/templates/partials/home/small.mako @@ -3,7 +3,7 @@ import calendar from medusa import sbdatetime from medusa import network_timezones - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.helpers import anon_url from medusa.helper.common import pretty_file_size from medusa.scene_numbering import get_xem_numbering_for_show diff --git a/themes/legacy/templates/partials/showheader.mako b/themes/legacy/templates/partials/showheader.mako index 81f546ef42..e970bb0296 100644 --- a/themes/legacy/templates/partials/showheader.mako +++ b/themes/legacy/templates/partials/showheader.mako @@ -7,7 +7,7 @@ from medusa.common import Quality, qualityPresets, statusStrings, Overview from medusa.helpers import anon_url from medusa.helper.common import pretty_file_size - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi %> <%namespace file="/inc_defs.mako" import="renderQualityPill"/> diff --git a/themes/legacy/templates/schedule.mako b/themes/legacy/templates/schedule.mako index 2bb6e1572f..dd96ed6cdf 100644 --- a/themes/legacy/templates/schedule.mako +++ b/themes/legacy/templates/schedule.mako @@ -2,7 +2,7 @@ <%! from medusa import app from medusa.helpers import anon_url - from medusa.indexers.indexer_api import indexerApi + from medusa.indexers.api import indexerApi from medusa.indexers.utils import indexer_id_to_name, mappings from medusa import sbdatetime from random import choice diff --git a/themes/light/assets/js/add-shows/new-show.js b/themes/light/assets/js/add-shows/new-show.js index 8077b4840c..7446593c90 100644 --- a/themes/light/assets/js/add-shows/new-show.js +++ b/themes/light/assets/js/add-shows/new-show.js @@ -106,27 +106,27 @@ MEDUSA.addShows.newShow = function() { checked = ''; } - const whichSeries = obj.join('|'); + const whichSeries = `${obj.indexerName}|${obj.indexer}|${obj.showUrl}|${obj.seriesId}|${obj.seriesName}|${obj.firstAired}|${obj.network}`; - resultStr += ' '; - if (data.langid && data.langid !== '' && obj[1] === 1) { // For now only add the language id to the tvdb url, as the others might have different routes. - resultStr += '' + obj[4] + ''; + resultStr += ` `; + if (data.langid && data.langid !== '' && obj.indexer === 1) { // For now only add the language id to the tvdb url, as the others might have different routes. + resultStr += `${obj.seriesName}`; } else { - resultStr += '' + obj[4] + ''; + resultStr += `${obj.seriesName}`; } - if (obj[5] !== null) { - const startDate = new Date(obj[5]); + if (obj.firstAired !== null) { + const startDate = new Date(obj.firstAired); const today = new Date(); if (startDate > today) { - resultStr += ' (will debut on ' + obj[5] + ' on ' + obj[6] + ')'; + resultStr += ` (will debut on ${obj.firstAired} on ${obj.network})`; } else { - resultStr += ' (started on ' + obj[5] + ' on ' + obj[6] + ')'; + resultStr += ` (started on ${obj.firstAired} on ${obj.network})`; } } - if (obj[0] !== null) { - resultStr += ' [' + obj[0] + ']'; + if (obj.indexerName !== null) { + resultStr += ` [${obj.indexerName}]`; } resultStr += '
'; diff --git a/themes/light/assets/js/add-shows/new-show.js.map b/themes/light/assets/js/add-shows/new-show.js.map index a2d9030710..6f0e86ddc0 100644 --- a/themes/light/assets/js/add-shows/new-show.js.map +++ b/themes/light/assets/js/add-shows/new-show.js.map @@ -1 +1 @@ -{"version":3,"names":[],"mappings":"","sources":["js/add-shows/new-show.js"],"sourcesContent":["(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require==\"function\"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error(\"Cannot find module '\"+o+\"'\");throw f.code=\"MODULE_NOT_FOUND\",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require==\"function\"&&require;for(var o=0;o
    @@ -335,7 +320,7 @@ import { mapActions, mapState, mapGetters } from 'vuex'; import { api } from '../api'; import { combineQualities, humanFileSize } from '../utils/core'; import { attachImdbTooltip } from '../utils/jquery'; -import { AppLink, Asset, QualityPill, StateSwitch } from './helpers'; +import { AppLink, Asset, Externals, QualityPill, StateSwitch } from './helpers'; /** * Return the first item of `values` that is not `null`, `undefined` or `NaN`. @@ -353,6 +338,7 @@ export default { components: { AppLink, Asset, + Externals, QualityPill, StateSwitch, Truncate diff --git a/themes/dark/assets/js/medusa-runtime.js b/themes/dark/assets/js/medusa-runtime.js index dce01678d8..d14f7b2b74 100644 --- a/themes/dark/assets/js/medusa-runtime.js +++ b/themes/dark/assets/js/medusa-runtime.js @@ -411,7 +411,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var ___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! . */ \"./src/components/helpers/index.js\");\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n name: 'externals',\n components: {\n AppLink: ___WEBPACK_IMPORTED_MODULE_0__.AppLink\n },\n props: {\n show: Object\n },\n computed: {\n externals() {\n const {\n show\n } = this;\n debugger;\n return show.externals;\n }\n\n }\n});\n\n//# sourceURL=webpack://slim/./src/components/helpers/externals.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-1%5B0%5D.rules%5B0%5D!./node_modules/vue-loader/lib/index.js??vue-loader-options"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var ___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! . */ \"./src/components/helpers/index.js\");\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n name: 'externals',\n components: {\n AppLink: ___WEBPACK_IMPORTED_MODULE_0__.AppLink\n },\n props: {\n show: Object\n },\n computed: {\n externals() {\n const {\n show\n } = this;\n return show.externals;\n }\n\n }\n});\n\n//# sourceURL=webpack://slim/./src/components/helpers/externals.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-1%5B0%5D.rules%5B0%5D!./node_modules/vue-loader/lib/index.js??vue-loader-options"); /***/ }), @@ -1060,7 +1060,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var vue_truncate_collapsed__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue-truncate-collapsed */ \"./node_modules/vue-truncate-collapsed/dist/vue-truncate-collapsed.min.es.js\");\n/* harmony import */ var country_language__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! country-language */ \"./node_modules/country-language/index.js\");\n/* harmony import */ var vue_scrollto__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! vue-scrollto */ \"./node_modules/vue-scrollto/vue-scrollto.js\");\n/* harmony import */ var vue_scrollto__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(vue_scrollto__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var vuex__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! vuex */ \"./node_modules/vuex/dist/vuex.esm.js\");\n/* harmony import */ var _api__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../api */ \"./src/api.js\");\n/* harmony import */ var _utils_core__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../utils/core */ \"./src/utils/core.js\");\n/* harmony import */ var _utils_jquery__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../utils/jquery */ \"./src/utils/jquery.js\");\n/* harmony import */ var _helpers__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./helpers */ \"./src/components/helpers/index.js\");\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n\n\n\n\n\n\n\n/**\n * Return the first item of `values` that is not `null`, `undefined` or `NaN`.\n * @param {...any} values - Values to check.\n * @returns {any} - The first item that fits the criteria, `undefined` otherwise.\n */\n\nconst resolveToValue = function () {\n for (var _len = arguments.length, values = new Array(_len), _key = 0; _key < _len; _key++) {\n values[_key] = arguments[_key];\n }\n\n return values.find(value => {\n return !Number.isNaN(value) && value !== null && value !== undefined;\n });\n};\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n name: 'show-header',\n components: {\n AppLink: _helpers__WEBPACK_IMPORTED_MODULE_6__.AppLink,\n Asset: _helpers__WEBPACK_IMPORTED_MODULE_6__.Asset,\n QualityPill: _helpers__WEBPACK_IMPORTED_MODULE_6__.QualityPill,\n StateSwitch: _helpers__WEBPACK_IMPORTED_MODULE_6__.StateSwitch,\n Truncate: vue_truncate_collapsed__WEBPACK_IMPORTED_MODULE_0__[\"default\"]\n },\n props: {\n /**\n * Page type: show or snatch-selection\n */\n type: {\n type: String,\n default: 'show',\n validator: value => ['show', 'snatch-selection'].includes(value)\n },\n\n /**\n * Show Slug\n */\n slug: {\n type: String\n },\n\n /**\n * Season\n */\n showSeason: {\n type: Number\n },\n\n /**\n * Episode\n */\n showEpisode: {\n type: Number\n },\n\n /**\n * Manual Search Type (snatch-selection)\n */\n manualSearchType: {\n type: String\n }\n },\n\n data() {\n return {\n jumpToSeason: 'jump',\n selectedStatus: 'Change status to:',\n selectedQuality: 'Change quality to:',\n overviewStatus: [{\n id: 'wanted',\n checked: true,\n name: 'Wanted'\n }, {\n id: 'allowed',\n checked: true,\n name: 'Allowed'\n }, {\n id: 'preferred',\n checked: true,\n name: 'Preferred'\n }, {\n id: 'skipped',\n checked: true,\n name: 'Skipped'\n }, {\n id: 'snatched',\n checked: true,\n name: 'Snatched'\n }]\n };\n },\n\n computed: { ...(0,vuex__WEBPACK_IMPORTED_MODULE_7__.mapState)({\n general: state => state.config.general,\n subtitles: state => state.config.subtitles,\n layout: state => state.config.layout,\n shows: state => state.shows.shows,\n indexers: state => state.config.indexers,\n indexerConfig: state => state.config.indexers.indexers,\n displaySpecials: state => state.config.layout.show.specials,\n qualities: state => state.config.consts.qualities.values,\n statuses: state => state.config.consts.statuses,\n search: state => state.config.search,\n configLoaded: state => state.config.layout.fanartBackground !== null\n }),\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_7__.mapGetters)({\n show: 'getCurrentShow',\n getEpisode: 'getEpisode',\n getOverviewStatus: 'getOverviewStatus',\n getQualityPreset: 'getQualityPreset',\n getStatus: 'getStatus',\n getShowIndexerUrl: 'getShowIndexerUrl'\n }),\n\n season() {\n return resolveToValue(this.showSeason, Number(this.$route.query.season));\n },\n\n episode() {\n return resolveToValue(this.showEpisode, Number(this.$route.query.episode));\n },\n\n activeShowQueueStatuses() {\n const {\n showQueueStatus\n } = this.show;\n\n if (!showQueueStatus) {\n return [];\n }\n\n return showQueueStatus.filter(status => status.active === true);\n },\n\n showGenres() {\n const {\n show,\n dedupeGenres\n } = this;\n const {\n imdbInfo\n } = show;\n const {\n genres\n } = imdbInfo;\n let result = [];\n\n if (genres) {\n result = dedupeGenres(genres.split('|'));\n }\n\n return result;\n },\n\n episodeSummary() {\n const {\n getOverviewStatus,\n show\n } = this;\n const {\n seasons\n } = show;\n const summary = {\n Unaired: 0,\n Skipped: 0,\n Wanted: 0,\n Snatched: 0,\n Preferred: 0,\n Allowed: 0\n };\n seasons.forEach(season => {\n season.children.forEach(episode => {\n summary[getOverviewStatus(episode.status, episode.quality, show.config.qualities)] += 1;\n });\n });\n return summary;\n },\n\n changeStatusOptions() {\n const {\n search,\n getStatus,\n statuses\n } = this;\n const {\n general\n } = search;\n\n if (statuses.length === 0) {\n return [];\n } // Get status objects, in this order\n\n\n const defaultOptions = ['wanted', 'skipped', 'ignored', 'downloaded', 'archived'].map(key => getStatus({\n key\n }));\n\n if (general.failedDownloads.enabled) {\n defaultOptions.push(getStatus({\n key: 'failed'\n }));\n }\n\n return defaultOptions;\n },\n\n combinedQualities() {\n const {\n allowed,\n preferred\n } = this.show.config.qualities;\n return (0,_utils_core__WEBPACK_IMPORTED_MODULE_4__.combineQualities)(allowed, preferred);\n },\n\n seasons() {\n const {\n show\n } = this;\n const {\n seasonCount\n } = show;\n\n if (!seasonCount) {\n return [];\n } // Only return an array with seasons (integers)\n\n\n return seasonCount.map(season => season.season);\n },\n\n episodeTitle() {\n const {\n getEpisode,\n show,\n season,\n episode\n } = this;\n\n if (!(show.id.slug && season && episode)) {\n return '';\n }\n\n const curEpisode = getEpisode({\n showSlug: show.id.slug,\n season,\n episode\n });\n\n if (curEpisode) {\n return curEpisode.title;\n }\n\n return '';\n }\n\n },\n\n mounted() {\n ['load', 'resize'].map(event => {\n return window.addEventListener(event, () => {\n this.$nextTick(() => this.reflowLayout());\n });\n });\n this.$watch('show', function (slug) {\n // eslint-disable-line object-shorthand\n // Show has changed. Meaning we should reflow the layout.\n if (slug) {\n const {\n reflowLayout\n } = this;\n this.$nextTick(() => reflowLayout());\n }\n }, {\n deep: true\n });\n },\n\n methods: { ...(0,vuex__WEBPACK_IMPORTED_MODULE_7__.mapActions)(['setSpecials']),\n combineQualities: _utils_core__WEBPACK_IMPORTED_MODULE_4__.combineQualities,\n humanFileSize: _utils_core__WEBPACK_IMPORTED_MODULE_4__.humanFileSize,\n\n changeStatusClicked() {\n const {\n changeStatusOptions,\n changeQualityOptions,\n selectedStatus,\n selectedQuality\n } = this;\n this.$emit('update', {\n newStatus: selectedStatus,\n newQuality: selectedQuality,\n statusOptions: changeStatusOptions,\n qualityOptions: changeQualityOptions\n });\n },\n\n toggleSpecials() {\n const {\n setSpecials\n } = this;\n setSpecials(!this.displaySpecials);\n },\n\n reverse(array) {\n return array ? array.slice().reverse() : [];\n },\n\n dedupeGenres(genres) {\n return genres ? [...new Set(genres.slice(0).map(genre => genre.replace('-', ' ')))] : [];\n },\n\n getCountryISO2ToISO3(country) {\n return (0,country_language__WEBPACK_IMPORTED_MODULE_1__.getLanguage)(country).iso639_2en;\n },\n\n toggleConfigOption(option) {\n const {\n show\n } = this;\n const {\n config\n } = show;\n this.show.config[option] = !this.show.config[option];\n const data = {\n config: {\n [option]: config[option]\n }\n };\n _api__WEBPACK_IMPORTED_MODULE_3__.api.patch('series/' + show.id.slug, data).then(_ => {\n this.$snotify.success(`${data.config[option] ? 'enabled' : 'disabled'} show option ${option}`, 'Saved', {\n timeout: 5000\n });\n }).catch(error => {\n this.$snotify.error('Error while trying to save \"' + show.title + '\": ' + error.message || 0, 'Error');\n });\n },\n\n reflowLayout() {\n (0,_utils_jquery__WEBPACK_IMPORTED_MODULE_5__.attachImdbTooltip)(); // eslint-disable-line no-undef\n }\n\n },\n watch: {\n jumpToSeason(season) {\n // Don't jump until an option is selected\n if (season !== 'jump') {\n // Calculate duration\n let duration = (this.seasons.length - season) * 50;\n duration = Math.max(500, Math.min(duration, 2000)); // Limit to (500ms <= duration <= 2000ms)\n // Calculate offset\n\n let offset = -50; // Navbar\n // Needs extra offset when the sub menu is \"fixed\".\n\n offset -= window.matchMedia('(min-width: 1281px)').matches ? 40 : 0;\n const name = `season-${season}`;\n console.debug(`Jumping to #${name} (${duration}ms)`);\n (0,vue_scrollto__WEBPACK_IMPORTED_MODULE_2__.scrollTo)(`[name=\"${name}\"]`, duration, {\n container: 'body',\n easing: 'ease-in-out',\n offset\n }); // Update URL hash\n\n window.location.hash = name; // Reset jump\n\n this.jumpToSeason = 'jump';\n }\n }\n\n }\n});\n\n//# sourceURL=webpack://slim/./src/components/show-header.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-1%5B0%5D.rules%5B0%5D!./node_modules/vue-loader/lib/index.js??vue-loader-options"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var vue_truncate_collapsed__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue-truncate-collapsed */ \"./node_modules/vue-truncate-collapsed/dist/vue-truncate-collapsed.min.es.js\");\n/* harmony import */ var country_language__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! country-language */ \"./node_modules/country-language/index.js\");\n/* harmony import */ var vue_scrollto__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! vue-scrollto */ \"./node_modules/vue-scrollto/vue-scrollto.js\");\n/* harmony import */ var vue_scrollto__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(vue_scrollto__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var vuex__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! vuex */ \"./node_modules/vuex/dist/vuex.esm.js\");\n/* harmony import */ var _api__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../api */ \"./src/api.js\");\n/* harmony import */ var _utils_core__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../utils/core */ \"./src/utils/core.js\");\n/* harmony import */ var _utils_jquery__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../utils/jquery */ \"./src/utils/jquery.js\");\n/* harmony import */ var _helpers__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./helpers */ \"./src/components/helpers/index.js\");\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n\n\n\n\n\n\n\n/**\n * Return the first item of `values` that is not `null`, `undefined` or `NaN`.\n * @param {...any} values - Values to check.\n * @returns {any} - The first item that fits the criteria, `undefined` otherwise.\n */\n\nconst resolveToValue = function () {\n for (var _len = arguments.length, values = new Array(_len), _key = 0; _key < _len; _key++) {\n values[_key] = arguments[_key];\n }\n\n return values.find(value => {\n return !Number.isNaN(value) && value !== null && value !== undefined;\n });\n};\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n name: 'show-header',\n components: {\n AppLink: _helpers__WEBPACK_IMPORTED_MODULE_6__.AppLink,\n Asset: _helpers__WEBPACK_IMPORTED_MODULE_6__.Asset,\n Externals: _helpers__WEBPACK_IMPORTED_MODULE_6__.Externals,\n QualityPill: _helpers__WEBPACK_IMPORTED_MODULE_6__.QualityPill,\n StateSwitch: _helpers__WEBPACK_IMPORTED_MODULE_6__.StateSwitch,\n Truncate: vue_truncate_collapsed__WEBPACK_IMPORTED_MODULE_0__[\"default\"]\n },\n props: {\n /**\n * Page type: show or snatch-selection\n */\n type: {\n type: String,\n default: 'show',\n validator: value => ['show', 'snatch-selection'].includes(value)\n },\n\n /**\n * Show Slug\n */\n slug: {\n type: String\n },\n\n /**\n * Season\n */\n showSeason: {\n type: Number\n },\n\n /**\n * Episode\n */\n showEpisode: {\n type: Number\n },\n\n /**\n * Manual Search Type (snatch-selection)\n */\n manualSearchType: {\n type: String\n }\n },\n\n data() {\n return {\n jumpToSeason: 'jump',\n selectedStatus: 'Change status to:',\n selectedQuality: 'Change quality to:',\n overviewStatus: [{\n id: 'wanted',\n checked: true,\n name: 'Wanted'\n }, {\n id: 'allowed',\n checked: true,\n name: 'Allowed'\n }, {\n id: 'preferred',\n checked: true,\n name: 'Preferred'\n }, {\n id: 'skipped',\n checked: true,\n name: 'Skipped'\n }, {\n id: 'snatched',\n checked: true,\n name: 'Snatched'\n }]\n };\n },\n\n computed: { ...(0,vuex__WEBPACK_IMPORTED_MODULE_7__.mapState)({\n general: state => state.config.general,\n subtitles: state => state.config.subtitles,\n layout: state => state.config.layout,\n shows: state => state.shows.shows,\n indexers: state => state.config.indexers,\n indexerConfig: state => state.config.indexers.indexers,\n displaySpecials: state => state.config.layout.show.specials,\n qualities: state => state.config.consts.qualities.values,\n statuses: state => state.config.consts.statuses,\n search: state => state.config.search,\n configLoaded: state => state.config.layout.fanartBackground !== null\n }),\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_7__.mapGetters)({\n show: 'getCurrentShow',\n getEpisode: 'getEpisode',\n getOverviewStatus: 'getOverviewStatus',\n getQualityPreset: 'getQualityPreset',\n getStatus: 'getStatus',\n getShowIndexerUrl: 'getShowIndexerUrl'\n }),\n\n season() {\n return resolveToValue(this.showSeason, Number(this.$route.query.season));\n },\n\n episode() {\n return resolveToValue(this.showEpisode, Number(this.$route.query.episode));\n },\n\n activeShowQueueStatuses() {\n const {\n showQueueStatus\n } = this.show;\n\n if (!showQueueStatus) {\n return [];\n }\n\n return showQueueStatus.filter(status => status.active === true);\n },\n\n showGenres() {\n const {\n show,\n dedupeGenres\n } = this;\n const {\n imdbInfo\n } = show;\n const {\n genres\n } = imdbInfo;\n let result = [];\n\n if (genres) {\n result = dedupeGenres(genres.split('|'));\n }\n\n return result;\n },\n\n episodeSummary() {\n const {\n getOverviewStatus,\n show\n } = this;\n const {\n seasons\n } = show;\n const summary = {\n Unaired: 0,\n Skipped: 0,\n Wanted: 0,\n Snatched: 0,\n Preferred: 0,\n Allowed: 0\n };\n seasons.forEach(season => {\n season.children.forEach(episode => {\n summary[getOverviewStatus(episode.status, episode.quality, show.config.qualities)] += 1;\n });\n });\n return summary;\n },\n\n changeStatusOptions() {\n const {\n search,\n getStatus,\n statuses\n } = this;\n const {\n general\n } = search;\n\n if (statuses.length === 0) {\n return [];\n } // Get status objects, in this order\n\n\n const defaultOptions = ['wanted', 'skipped', 'ignored', 'downloaded', 'archived'].map(key => getStatus({\n key\n }));\n\n if (general.failedDownloads.enabled) {\n defaultOptions.push(getStatus({\n key: 'failed'\n }));\n }\n\n return defaultOptions;\n },\n\n combinedQualities() {\n const {\n allowed,\n preferred\n } = this.show.config.qualities;\n return (0,_utils_core__WEBPACK_IMPORTED_MODULE_4__.combineQualities)(allowed, preferred);\n },\n\n seasons() {\n const {\n show\n } = this;\n const {\n seasonCount\n } = show;\n\n if (!seasonCount) {\n return [];\n } // Only return an array with seasons (integers)\n\n\n return seasonCount.map(season => season.season);\n },\n\n episodeTitle() {\n const {\n getEpisode,\n show,\n season,\n episode\n } = this;\n\n if (!(show.id.slug && season && episode)) {\n return '';\n }\n\n const curEpisode = getEpisode({\n showSlug: show.id.slug,\n season,\n episode\n });\n\n if (curEpisode) {\n return curEpisode.title;\n }\n\n return '';\n }\n\n },\n\n mounted() {\n ['load', 'resize'].map(event => {\n return window.addEventListener(event, () => {\n this.$nextTick(() => this.reflowLayout());\n });\n });\n this.$watch('show', function (slug) {\n // eslint-disable-line object-shorthand\n // Show has changed. Meaning we should reflow the layout.\n if (slug) {\n const {\n reflowLayout\n } = this;\n this.$nextTick(() => reflowLayout());\n }\n }, {\n deep: true\n });\n },\n\n methods: { ...(0,vuex__WEBPACK_IMPORTED_MODULE_7__.mapActions)(['setSpecials']),\n combineQualities: _utils_core__WEBPACK_IMPORTED_MODULE_4__.combineQualities,\n humanFileSize: _utils_core__WEBPACK_IMPORTED_MODULE_4__.humanFileSize,\n\n changeStatusClicked() {\n const {\n changeStatusOptions,\n changeQualityOptions,\n selectedStatus,\n selectedQuality\n } = this;\n this.$emit('update', {\n newStatus: selectedStatus,\n newQuality: selectedQuality,\n statusOptions: changeStatusOptions,\n qualityOptions: changeQualityOptions\n });\n },\n\n toggleSpecials() {\n const {\n setSpecials\n } = this;\n setSpecials(!this.displaySpecials);\n },\n\n reverse(array) {\n return array ? array.slice().reverse() : [];\n },\n\n dedupeGenres(genres) {\n return genres ? [...new Set(genres.slice(0).map(genre => genre.replace('-', ' ')))] : [];\n },\n\n getCountryISO2ToISO3(country) {\n return (0,country_language__WEBPACK_IMPORTED_MODULE_1__.getLanguage)(country).iso639_2en;\n },\n\n toggleConfigOption(option) {\n const {\n show\n } = this;\n const {\n config\n } = show;\n this.show.config[option] = !this.show.config[option];\n const data = {\n config: {\n [option]: config[option]\n }\n };\n _api__WEBPACK_IMPORTED_MODULE_3__.api.patch('series/' + show.id.slug, data).then(_ => {\n this.$snotify.success(`${data.config[option] ? 'enabled' : 'disabled'} show option ${option}`, 'Saved', {\n timeout: 5000\n });\n }).catch(error => {\n this.$snotify.error('Error while trying to save \"' + show.title + '\": ' + error.message || 0, 'Error');\n });\n },\n\n reflowLayout() {\n (0,_utils_jquery__WEBPACK_IMPORTED_MODULE_5__.attachImdbTooltip)(); // eslint-disable-line no-undef\n }\n\n },\n watch: {\n jumpToSeason(season) {\n // Don't jump until an option is selected\n if (season !== 'jump') {\n // Calculate duration\n let duration = (this.seasons.length - season) * 50;\n duration = Math.max(500, Math.min(duration, 2000)); // Limit to (500ms <= duration <= 2000ms)\n // Calculate offset\n\n let offset = -50; // Navbar\n // Needs extra offset when the sub menu is \"fixed\".\n\n offset -= window.matchMedia('(min-width: 1281px)').matches ? 40 : 0;\n const name = `season-${season}`;\n console.debug(`Jumping to #${name} (${duration}ms)`);\n (0,vue_scrollto__WEBPACK_IMPORTED_MODULE_2__.scrollTo)(`[name=\"${name}\"]`, duration, {\n container: 'body',\n easing: 'ease-in-out',\n offset\n }); // Update URL hash\n\n window.location.hash = name; // Reset jump\n\n this.jumpToSeason = 'jump';\n }\n }\n\n }\n});\n\n//# sourceURL=webpack://slim/./src/components/show-header.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-1%5B0%5D.rules%5B0%5D!./node_modules/vue-loader/lib/index.js??vue-loader-options"); /***/ }), @@ -2122,6 +2122,16 @@ eval("// Imports\nvar ___CSS_LOADER_API_NO_SOURCEMAP_IMPORT___ = __webpack_requi /***/ }), +/***/ "./node_modules/css-loader/dist/cjs.js??clonedRuleSet-3[0].rules[0].use[1]!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/vue-loader/lib/index.js??vue-loader-options!./src/components/helpers/externals.vue?vue&type=style&index=0&lang=css&": +/*!*****************************************************************************************************************************************************************************************************************************************************************************!*\ + !*** ./node_modules/css-loader/dist/cjs.js??clonedRuleSet-3[0].rules[0].use[1]!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/vue-loader/lib/index.js??vue-loader-options!./src/components/helpers/externals.vue?vue&type=style&index=0&lang=css& ***! + \*****************************************************************************************************************************************************************************************************************************************************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +eval("// Imports\nvar ___CSS_LOADER_API_NO_SOURCEMAP_IMPORT___ = __webpack_require__(/*! ../../../node_modules/css-loader/dist/runtime/noSourceMaps.js */ \"./node_modules/css-loader/dist/runtime/noSourceMaps.js\");\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\nvar ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(___CSS_LOADER_API_NO_SOURCEMAP_IMPORT___);\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, \"\\n.external-ids {\\n display: inline-block;\\n}\\n.external-ids > * {\\n margin-left: 2px;\\n}\\n\", \"\"]);\n// Exports\nmodule.exports = ___CSS_LOADER_EXPORT___;\n\n\n//# sourceURL=webpack://slim/./src/components/helpers/externals.vue?./node_modules/css-loader/dist/cjs.js??clonedRuleSet-3%5B0%5D.rules%5B0%5D.use%5B1%5D!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/vue-loader/lib/index.js??vue-loader-options"); + +/***/ }), + /***/ "./node_modules/css-loader/dist/cjs.js??clonedRuleSet-3[0].rules[0].use[1]!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/vue-loader/lib/index.js??vue-loader-options!./src/components/helpers/file-browser.vue?vue&type=style&index=0&id=e1171a9e&scoped=true&lang=css&": /*!********************************************************************************************************************************************************************************************************************************************************************************************************!*\ !*** ./node_modules/css-loader/dist/cjs.js??clonedRuleSet-3[0].rules[0].use[1]!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/vue-loader/lib/index.js??vue-loader-options!./src/components/helpers/file-browser.vue?vue&type=style&index=0&id=e1171a9e&scoped=true&lang=css& ***! @@ -3086,7 +3096,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _externals_vue_vue_type_template_id_69d6301e___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./externals.vue?vue&type=template&id=69d6301e& */ \"./src/components/helpers/externals.vue?vue&type=template&id=69d6301e&\");\n/* harmony import */ var _externals_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./externals.vue?vue&type=script&lang=js& */ \"./src/components/helpers/externals.vue?vue&type=script&lang=js&\");\n/* harmony import */ var _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! !../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js */ \"./node_modules/vue-loader/lib/runtime/componentNormalizer.js\");\n\n\n\n\n\n/* normalize component */\n;\nvar component = (0,_node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__[\"default\"])(\n _externals_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n _externals_vue_vue_type_template_id_69d6301e___WEBPACK_IMPORTED_MODULE_0__.render,\n _externals_vue_vue_type_template_id_69d6301e___WEBPACK_IMPORTED_MODULE_0__.staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\n/* hot reload */\nif (false) { var api; }\ncomponent.options.__file = \"src/components/helpers/externals.vue\"\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (component.exports);\n\n//# sourceURL=webpack://slim/./src/components/helpers/externals.vue?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _externals_vue_vue_type_template_id_69d6301e___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./externals.vue?vue&type=template&id=69d6301e& */ \"./src/components/helpers/externals.vue?vue&type=template&id=69d6301e&\");\n/* harmony import */ var _externals_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./externals.vue?vue&type=script&lang=js& */ \"./src/components/helpers/externals.vue?vue&type=script&lang=js&\");\n/* harmony import */ var _externals_vue_vue_type_style_index_0_lang_css___WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./externals.vue?vue&type=style&index=0&lang=css& */ \"./src/components/helpers/externals.vue?vue&type=style&index=0&lang=css&\");\n/* harmony import */ var _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! !../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js */ \"./node_modules/vue-loader/lib/runtime/componentNormalizer.js\");\n\n\n\n;\n\n\n/* normalize component */\n\nvar component = (0,_node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_3__[\"default\"])(\n _externals_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n _externals_vue_vue_type_template_id_69d6301e___WEBPACK_IMPORTED_MODULE_0__.render,\n _externals_vue_vue_type_template_id_69d6301e___WEBPACK_IMPORTED_MODULE_0__.staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\n/* hot reload */\nif (false) { var api; }\ncomponent.options.__file = \"src/components/helpers/externals.vue\"\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (component.exports);\n\n//# sourceURL=webpack://slim/./src/components/helpers/externals.vue?"); /***/ }), @@ -6500,6 +6510,17 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _nod /***/ }), +/***/ "./src/components/helpers/externals.vue?vue&type=style&index=0&lang=css&": +/*!*******************************************************************************!*\ + !*** ./src/components/helpers/externals.vue?vue&type=style&index=0&lang=css& ***! + \*******************************************************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_vue_style_loader_index_js_node_modules_css_loader_dist_cjs_js_clonedRuleSet_3_0_rules_0_use_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_vue_loader_lib_index_js_vue_loader_options_externals_vue_vue_type_style_index_0_lang_css___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../node_modules/vue-style-loader/index.js!../../../node_modules/css-loader/dist/cjs.js??clonedRuleSet-3[0].rules[0].use[1]!../../../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./externals.vue?vue&type=style&index=0&lang=css& */ \"./node_modules/vue-style-loader/index.js!./node_modules/css-loader/dist/cjs.js??clonedRuleSet-3[0].rules[0].use[1]!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/vue-loader/lib/index.js??vue-loader-options!./src/components/helpers/externals.vue?vue&type=style&index=0&lang=css&\");\n/* harmony import */ var _node_modules_vue_style_loader_index_js_node_modules_css_loader_dist_cjs_js_clonedRuleSet_3_0_rules_0_use_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_vue_loader_lib_index_js_vue_loader_options_externals_vue_vue_type_style_index_0_lang_css___WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_vue_style_loader_index_js_node_modules_css_loader_dist_cjs_js_clonedRuleSet_3_0_rules_0_use_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_vue_loader_lib_index_js_vue_loader_options_externals_vue_vue_type_style_index_0_lang_css___WEBPACK_IMPORTED_MODULE_0__);\n/* harmony reexport (unknown) */ var __WEBPACK_REEXPORT_OBJECT__ = {};\n/* harmony reexport (unknown) */ for(const __WEBPACK_IMPORT_KEY__ in _node_modules_vue_style_loader_index_js_node_modules_css_loader_dist_cjs_js_clonedRuleSet_3_0_rules_0_use_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_vue_loader_lib_index_js_vue_loader_options_externals_vue_vue_type_style_index_0_lang_css___WEBPACK_IMPORTED_MODULE_0__) if(__WEBPACK_IMPORT_KEY__ !== \"default\") __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = () => _node_modules_vue_style_loader_index_js_node_modules_css_loader_dist_cjs_js_clonedRuleSet_3_0_rules_0_use_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_vue_loader_lib_index_js_vue_loader_options_externals_vue_vue_type_style_index_0_lang_css___WEBPACK_IMPORTED_MODULE_0__[__WEBPACK_IMPORT_KEY__]\n/* harmony reexport (unknown) */ __webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);\n\n\n//# sourceURL=webpack://slim/./src/components/helpers/externals.vue?"); + +/***/ }), + /***/ "./src/components/helpers/file-browser.vue?vue&type=style&index=0&id=e1171a9e&scoped=true&lang=css&": /*!**********************************************************************************************************!*\ !*** ./src/components/helpers/file-browser.vue?vue&type=style&index=0&id=e1171a9e&scoped=true&lang=css& ***! @@ -7497,7 +7518,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"render\": () => (/* binding */ render),\n/* harmony export */ \"staticRenderFns\": () => (/* binding */ staticRenderFns)\n/* harmony export */ });\nvar render = function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\n \"div\",\n [\n _c(\n \"app-link\",\n {\n attrs: {\n href: \"https://www.imdb.com/title/\" + _vm.show.imdbInfo.imdbId,\n title: \"https://www.imdb.com/title/\" + _vm.show.imdbInfo.imdbId\n }\n },\n [\n _c(\"img\", {\n staticStyle: { \"margin-top\": \"-1px\", \"vertical-align\": \"middle\" },\n attrs: {\n alt: \"[imdb]\",\n height: \"16\",\n width: \"16\",\n src: \"images/imdb16.png\"\n }\n })\n ]\n ),\n _vm._v(\" \"),\n _vm.show.id.trakt\n ? _c(\n \"app-link\",\n {\n attrs: {\n href: \"https://trakt.tv/shows/\" + _vm.show.id.trakt,\n title: \"https://trakt.tv/shows/\" + _vm.show.id.trakt\n }\n },\n [\n _c(\"img\", {\n attrs: {\n alt: \"[trakt]\",\n height: \"16\",\n width: \"16\",\n src: \"images/trakt.png\"\n }\n })\n ]\n )\n : _vm._e(),\n _vm._v(\" \"),\n _vm.show.xemNumbering && _vm.show.xemNumbering.length > 0\n ? _c(\n \"app-link\",\n {\n attrs: {\n href: \"http://thexem.de/search?q=\" + _vm.show.title,\n title: \"http://thexem.de/search?q=\" + _vm.show.title\n }\n },\n [\n _c(\"img\", {\n staticStyle: {\n \"margin-top\": \"-1px\",\n \"vertical-align\": \"middle\"\n },\n attrs: {\n alt: \"[xem]\",\n height: \"16\",\n width: \"16\",\n src: \"images/xem.png\"\n }\n })\n ]\n )\n : _vm._e(),\n _vm._v(\" \"),\n _vm.show.id.tvdb\n ? _c(\n \"app-link\",\n {\n attrs: {\n href: \"https://fanart.tv/series/\" + _vm.show.id.tvdb,\n title: \"https://fanart.tv/series/\" + _vm.show.id.tvdb\n }\n },\n [\n _c(\"img\", {\n staticClass: \"fanart\",\n attrs: {\n alt: \"[fanart.tv]\",\n height: \"16\",\n width: \"16\",\n src: \"images/fanart.tv.png\"\n }\n })\n ]\n )\n : _vm._e()\n ],\n 1\n )\n}\nvar staticRenderFns = []\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack://slim/./src/components/helpers/externals.vue?./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib/index.js??vue-loader-options"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"render\": () => (/* binding */ render),\n/* harmony export */ \"staticRenderFns\": () => (/* binding */ staticRenderFns)\n/* harmony export */ });\nvar render = function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\n \"div\",\n { staticClass: \"external-ids\" },\n [\n _c(\"span\", [_vm._v(\" - \")]),\n _vm._v(\" \"),\n _vm.externals.imdb && _vm.show.imdbInfo\n ? _c(\n \"app-link\",\n {\n attrs: {\n href: \"https://www.imdb.com/title/\" + _vm.show.imdbInfo.imdbId,\n title: \"https://www.imdb.com/title/\" + _vm.show.imdbInfo.imdbId\n }\n },\n [\n _c(\"img\", {\n staticStyle: {\n \"margin-top\": \"-1px\",\n \"vertical-align\": \"middle\"\n },\n attrs: {\n alt: \"[imdb]\",\n height: \"16\",\n width: \"16\",\n src: \"images/imdb16.png\"\n }\n })\n ]\n )\n : _vm._e(),\n _vm._v(\" \"),\n _vm.externals.trakt\n ? _c(\n \"app-link\",\n {\n attrs: {\n href: \"https://trakt.tv/shows/\" + _vm.externals.trakt,\n title: \"https://trakt.tv/shows/\" + _vm.externals.trakt\n }\n },\n [\n _c(\"img\", {\n attrs: {\n alt: \"[trakt]\",\n height: \"16\",\n width: \"16\",\n src: \"images/trakt.png\"\n }\n })\n ]\n )\n : _vm._e(),\n _vm._v(\" \"),\n _vm.externals.tvdb\n ? _c(\n \"app-link\",\n {\n attrs: {\n href:\n \"https://www.thetvdb.com/dereferrer/series/\" +\n _vm.externals.tvdb,\n title:\n \"https://www.thetvdb.com/dereferrer/series/\" +\n _vm.externals.tvdb\n }\n },\n [\n _c(\"img\", {\n attrs: {\n alt: \"[tvdb]\",\n height: \"16\",\n width: \"16\",\n src: \"images/thetvdb16.png\"\n }\n })\n ]\n )\n : _vm._e(),\n _vm._v(\" \"),\n _vm.externals.tvmaze\n ? _c(\n \"app-link\",\n {\n attrs: {\n href: \"https://www.tvmaze.com/shows/\" + _vm.externals.tvmaze,\n title: \"https://www.tvmaze.com/shows/\" + _vm.externals.tvmaze\n }\n },\n [\n _c(\"img\", {\n attrs: {\n alt: \"[tvmaze]\",\n height: \"16\",\n width: \"16\",\n src: \"images/tvmaze16.png\"\n }\n })\n ]\n )\n : _vm._e(),\n _vm._v(\" \"),\n _vm.externals.tmdb\n ? _c(\n \"app-link\",\n {\n attrs: {\n href: \"https://www.themoviedb.org/tv/\" + _vm.externals.tmdb,\n title: \"https://www.themoviedb.org/tv/\" + _vm.externals.tmdb\n }\n },\n [\n _c(\"img\", {\n attrs: {\n alt: \"[tmdb]\",\n height: \"16\",\n width: \"16\",\n src: \"images/tmdb16.png\"\n }\n })\n ]\n )\n : _vm._e(),\n _vm._v(\" \"),\n _vm.show.xemNumbering && _vm.show.xemNumbering.length > 0\n ? _c(\n \"app-link\",\n {\n attrs: {\n href: \"http://thexem.de/search?q=\" + _vm.show.title,\n title: \"http://thexem.de/search?q=\" + _vm.show.title\n }\n },\n [\n _c(\"img\", {\n staticStyle: {\n \"margin-top\": \"-1px\",\n \"vertical-align\": \"middle\"\n },\n attrs: {\n alt: \"[xem]\",\n height: \"16\",\n width: \"16\",\n src: \"images/xem.png\"\n }\n })\n ]\n )\n : _vm._e(),\n _vm._v(\" \"),\n _vm.externals.tvdb\n ? _c(\n \"app-link\",\n {\n attrs: {\n href: \"https://fanart.tv/series/\" + _vm.externals.tvdb,\n title: \"https://fanart.tv/series/\" + _vm.externals.tvdb\n }\n },\n [\n _c(\"img\", {\n staticClass: \"fanart\",\n attrs: {\n alt: \"[fanart.tv]\",\n height: \"16\",\n width: \"16\",\n src: \"images/fanart.tv.png\"\n }\n })\n ]\n )\n : _vm._e()\n ],\n 1\n )\n}\nvar staticRenderFns = []\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack://slim/./src/components/helpers/externals.vue?./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib/index.js??vue-loader-options"); /***/ }), @@ -8146,7 +8167,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"render\": () => (/* binding */ render),\n/* harmony export */ \"staticRenderFns\": () => (/* binding */ staticRenderFns)\n/* harmony export */ });\nvar render = function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\n \"div\",\n { staticClass: \"show-header-container\" },\n [\n _c(\"div\", { staticClass: \"row\" }, [\n _vm.show\n ? _c(\n \"div\",\n {\n staticClass: \"col-lg-12\",\n attrs: { id: \"showtitle\", \"data-showname\": _vm.show.title }\n },\n [\n _c(\"div\", [\n _c(\n \"h1\",\n {\n staticClass: \"title\",\n attrs: {\n \"data-indexer-name\": _vm.show.indexer,\n \"data-series-id\": _vm.show.id[_vm.show.indexer],\n id: \"scene_exception_\" + _vm.show.id[_vm.show.indexer]\n }\n },\n [\n _c(\n \"app-link\",\n {\n staticClass: \"snatchTitle\",\n attrs: {\n href:\n \"home/displayShow?showslug=\" + _vm.show.id.slug\n }\n },\n [_vm._v(_vm._s(_vm.show.title))]\n )\n ],\n 1\n )\n ]),\n _vm._v(\" \"),\n _vm.type === \"snatch-selection\"\n ? _c(\n \"div\",\n {\n staticClass: \"pull-right episode-info\",\n attrs: { id: \"show-specials-and-seasons\" }\n },\n [\n _c(\n \"span\",\n { staticClass: \"h2footer display-specials\" },\n [\n _vm._v(\n \"\\n Manual search for: Season \" +\n _vm._s(_vm.season)\n ),\n _vm.episode !== undefined &&\n _vm.manualSearchType !== \"season\"\n ? [_vm._v(\" Episode \" + _vm._s(_vm.episode))]\n : _vm._e()\n ],\n 2\n ),\n _vm._v(\" \"),\n _vm.manualSearchType !== \"season\" && _vm.episodeTitle\n ? _c(\"span\", [\n _vm._v(\n \"\\n \" +\n _vm._s(_vm.episodeTitle) +\n \"\\n \"\n )\n ])\n : _vm._e()\n ]\n )\n : _vm._e(),\n _vm._v(\" \"),\n _vm.type !== \"snatch-selection\" && _vm.seasons.length >= 1\n ? _c(\n \"div\",\n {\n staticClass: \"pull-right\",\n attrs: { id: \"show-specials-and-seasons\" }\n },\n [\n _vm.seasons.includes(0)\n ? _c(\n \"span\",\n { staticClass: \"h2footer display-specials\" },\n [\n _vm._v(\n \"\\n Display Specials: \"\n ),\n _c(\n \"a\",\n {\n staticClass: \"inner\",\n staticStyle: { cursor: \"pointer\" },\n on: {\n click: function($event) {\n $event.preventDefault()\n return _vm.toggleSpecials()\n }\n }\n },\n [\n _vm._v(\n _vm._s(\n _vm.displaySpecials ? \"Hide\" : \"Show\"\n )\n )\n ]\n )\n ]\n )\n : _vm._e(),\n _vm._v(\" \"),\n _c(\n \"div\",\n { staticClass: \"h2footer display-seasons clear\" },\n [\n _c(\n \"span\",\n [\n _vm.seasons.length >= 15\n ? _c(\n \"select\",\n {\n directives: [\n {\n name: \"model\",\n rawName: \"v-model\",\n value: _vm.jumpToSeason,\n expression: \"jumpToSeason\"\n }\n ],\n staticClass: \"form-control input-sm\",\n staticStyle: { position: \"relative\" },\n attrs: { id: \"seasonJump\" },\n on: {\n change: function($event) {\n var $$selectedVal = Array.prototype.filter\n .call(\n $event.target.options,\n function(o) {\n return o.selected\n }\n )\n .map(function(o) {\n var val =\n \"_value\" in o\n ? o._value\n : o.value\n return val\n })\n _vm.jumpToSeason = $event.target\n .multiple\n ? $$selectedVal\n : $$selectedVal[0]\n }\n }\n },\n [\n _c(\n \"option\",\n { attrs: { value: \"jump\" } },\n [_vm._v(\"Jump to Season\")]\n ),\n _vm._v(\" \"),\n _vm._l(_vm.seasons, function(\n seasonNumber\n ) {\n return _c(\n \"option\",\n {\n key:\n \"jumpToSeason-\" + seasonNumber,\n domProps: { value: seasonNumber }\n },\n [\n _vm._v(\n \"\\n \" +\n _vm._s(\n seasonNumber === 0\n ? \"Specials\"\n : \"Season \" + seasonNumber\n ) +\n \"\\n \"\n )\n ]\n )\n })\n ],\n 2\n )\n : _vm.seasons.length >= 1\n ? [\n _vm._v(\n \"\\n Season:\\n \"\n ),\n _vm._l(_vm.reverse(_vm.seasons), function(\n seasonNumber,\n index\n ) {\n return [\n _c(\n \"app-link\",\n {\n key:\n \"jumpToSeason-\" + seasonNumber,\n attrs: {\n href: \"#season-\" + seasonNumber\n },\n nativeOn: {\n click: function($event) {\n $event.preventDefault()\n _vm.jumpToSeason = seasonNumber\n }\n }\n },\n [\n _vm._v(\n \"\\n \" +\n _vm._s(\n seasonNumber === 0\n ? \"Specials\"\n : seasonNumber\n ) +\n \"\\n \"\n )\n ]\n ),\n _vm._v(\" \"),\n index !== _vm.seasons.length - 1\n ? _c(\n \"span\",\n {\n key: \"separator-\" + index,\n staticClass: \"separator\"\n },\n [_vm._v(\"| \")]\n )\n : _vm._e()\n ]\n })\n ]\n : _vm._e()\n ],\n 2\n )\n ]\n )\n ]\n )\n : _vm._e()\n ]\n )\n : _vm._e()\n ]),\n _vm._v(\" \"),\n _vm._l(_vm.activeShowQueueStatuses, function(queueItem) {\n return _c(\"div\", { key: queueItem.action, staticClass: \"row\" }, [\n _c(\"div\", { staticClass: \"alert alert-info\" }, [\n _vm._v(\"\\n \" + _vm._s(queueItem.message) + \"\\n \")\n ])\n ])\n }),\n _vm._v(\" \"),\n _c(\n \"div\",\n {\n staticClass: \"shadow shadow-background\",\n attrs: { id: \"summaryBackground\" }\n },\n [\n _c(\"div\", { staticClass: \"row\", attrs: { id: \"row-show-summary\" } }, [\n _c(\n \"div\",\n { staticClass: \"col-md-12\", attrs: { id: \"col-show-summary\" } },\n [\n _c(\"div\", { staticClass: \"show-poster-container\" }, [\n _c(\"div\", { staticClass: \"row\" }, [\n _c(\n \"div\",\n { staticClass: \"image-flex-container col-md-12\" },\n [\n _vm.show.id.slug\n ? _c(\"asset\", {\n attrs: {\n \"default-src\": \"images/poster.png\",\n \"show-slug\": _vm.show.id.slug,\n type: \"posterThumb\",\n cls: \"show-image shadow\",\n link: true\n }\n })\n : _vm._e()\n ],\n 1\n )\n ])\n ]),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"ver-spacer\" }),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"show-info-container\" }, [\n _c(\"div\", { staticClass: \"row\" }, [\n _c(\n \"div\",\n {\n staticClass:\n \"pull-right col-lg-3 col-md-3 hidden-sm hidden-xs\"\n },\n [\n _vm.show.id.slug\n ? _c(\"asset\", {\n attrs: {\n \"default-src\": \"images/banner.png\",\n \"show-slug\": _vm.show.id.slug,\n type: \"banner\",\n cls: \"show-banner pull-right shadow\",\n link: true\n }\n })\n : _vm._e()\n ],\n 1\n ),\n _vm._v(\" \"),\n _c(\n \"div\",\n {\n staticClass:\n \"pull-left col-lg-9 col-md-9 col-sm-12 col-xs-12\",\n attrs: { id: \"indexers\" }\n },\n [\n _vm.show.rating.imdb && _vm.show.rating.imdb.rating\n ? _c(\n \"span\",\n {\n staticClass: \"imdbstars\",\n attrs: {\n \"qtip-content\":\n _vm.show.rating.imdb.rating +\n \" / 10 Stars
    \" +\n _vm.show.rating.imdb.votes +\n \" Votes\"\n }\n },\n [\n _c(\"span\", {\n style: {\n width:\n Number(_vm.show.rating.imdb.rating) * 10 +\n \"%\"\n }\n })\n ]\n )\n : _vm._e(),\n _vm._v(\" \"),\n !_vm.show.imdbInfo || !_vm.show.imdbInfo.imdbId\n ? [\n _vm.show.year.start\n ? _c(\"span\", [\n _vm._v(\n \"(\" +\n _vm._s(_vm.show.year.start) +\n \") - \" +\n _vm._s(_vm.show.runtime) +\n \" minutes - \"\n )\n ])\n : _vm._e()\n ]\n : [\n _vm._l(_vm.show.countryCodes, function(country) {\n return _c(\"img\", {\n key: \"flag-\" + country,\n class: [\"country-flag\", \"flag-\" + country],\n staticStyle: {\n \"margin-left\": \"3px\",\n \"vertical-align\": \"middle\"\n },\n attrs: {\n src: \"images/blank.png\",\n width: \"16\",\n height: \"11\"\n }\n })\n }),\n _vm._v(\" \"),\n _vm.show.imdbInfo.year\n ? _c(\"span\", [\n _vm._v(\n \"\\n (\" +\n _vm._s(_vm.show.imdbInfo.year) +\n \") -\\n \"\n )\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _c(\"span\", [\n _vm._v(\n \"\\n \" +\n _vm._s(\n _vm.show.imdbInfo.runtimes ||\n _vm.show.runtime\n ) +\n \" minutes\\n \"\n )\n ]),\n _vm._v(\" \"),\n _vm.show.indexer !== \"imdb\"\n ? _c(\n \"app-link\",\n {\n attrs: {\n href:\n \"https://www.imdb.com/title/\" +\n _vm.show.imdbInfo.imdbId,\n title:\n \"https://www.imdb.com/title/\" +\n _vm.show.imdbInfo.imdbId\n }\n },\n [\n _c(\"img\", {\n staticStyle: {\n \"margin-top\": \"-1px\",\n \"vertical-align\": \"middle\"\n },\n attrs: {\n alt: \"[imdb]\",\n height: \"16\",\n width: \"16\",\n src: \"images/imdb16.png\"\n }\n })\n ]\n )\n : _vm._e()\n ],\n _vm._v(\" \"),\n _c(\n \"div\",\n { attrs: { id: \"indexer-wrapper\" } },\n [\n _vm.getShowIndexerUrl(_vm.show) &&\n _vm.indexerConfig[_vm.show.indexer].icon\n ? _c(\n \"app-link\",\n {\n attrs: {\n href: _vm.getShowIndexerUrl(_vm.show),\n title: _vm.getShowIndexerUrl(_vm.show)\n }\n },\n [\n _c(\"img\", {\n attrs: {\n id: \"stored-by-indexer\",\n src: \"images/star.png\"\n }\n }),\n _vm._v(\" \"),\n _c(\"img\", {\n staticStyle: {\n \"margin-top\": \"-1px\",\n \"vertical-align\": \"middle\"\n },\n attrs: {\n alt:\n _vm.indexerConfig[_vm.show.indexer]\n .name,\n height: \"16\",\n width: \"16\",\n src:\n \"images/\" +\n _vm.indexerConfig[_vm.show.indexer]\n .icon\n }\n })\n ]\n )\n : _vm._e()\n ],\n 1\n ),\n _vm._v(\" \"),\n _vm.show.id.trakt\n ? _c(\n \"app-link\",\n {\n attrs: {\n href:\n \"https://trakt.tv/shows/\" +\n _vm.show.id.trakt,\n title:\n \"https://trakt.tv/shows/\" +\n _vm.show.id.trakt\n }\n },\n [\n _c(\"img\", {\n attrs: {\n alt: \"[trakt]\",\n height: \"16\",\n width: \"16\",\n src: \"images/trakt.png\"\n }\n })\n ]\n )\n : _vm._e(),\n _vm._v(\" \"),\n _vm.show.xemNumbering &&\n _vm.show.xemNumbering.length > 0\n ? _c(\n \"app-link\",\n {\n attrs: {\n href:\n \"http://thexem.de/search?q=\" +\n _vm.show.title,\n title:\n \"http://thexem.de/search?q=\" +\n _vm.show.title\n }\n },\n [\n _c(\"img\", {\n staticStyle: {\n \"margin-top\": \"-1px\",\n \"vertical-align\": \"middle\"\n },\n attrs: {\n alt: \"[xem]\",\n height: \"16\",\n width: \"16\",\n src: \"images/xem.png\"\n }\n })\n ]\n )\n : _vm._e(),\n _vm._v(\" \"),\n _vm.show.id.tvdb\n ? _c(\n \"app-link\",\n {\n attrs: {\n href:\n \"https://fanart.tv/series/\" +\n _vm.show.id.tvdb,\n title:\n \"https://fanart.tv/series/\" +\n _vm.show.id[_vm.show.indexer]\n }\n },\n [\n _c(\"img\", {\n staticClass: \"fanart\",\n attrs: {\n alt: \"[fanart.tv]\",\n height: \"16\",\n width: \"16\",\n src: \"images/fanart.tv.png\"\n }\n })\n ]\n )\n : _vm._e()\n ],\n 2\n ),\n _vm._v(\" \"),\n _c(\n \"div\",\n {\n staticClass:\n \"pull-left col-lg-9 col-md-9 col-sm-12 col-xs-12\",\n attrs: { id: \"tags\" }\n },\n [\n _vm.show.genres\n ? _c(\n \"ul\",\n { staticClass: \"tags\" },\n _vm._l(\n _vm.dedupeGenres(_vm.show.genres),\n function(genre) {\n return _c(\n \"app-link\",\n {\n key: genre.toString(),\n attrs: {\n href:\n \"https://trakt.tv/shows/popular/?genres=\" +\n genre.toLowerCase().replace(\" \", \"-\"),\n title:\n \"View other popular \" +\n genre +\n \" shows on trakt.tv\"\n }\n },\n [_c(\"li\", [_vm._v(_vm._s(genre))])]\n )\n }\n ),\n 1\n )\n : _c(\n \"ul\",\n { staticClass: \"tags\" },\n _vm._l(_vm.showGenres, function(genre) {\n return _c(\n \"app-link\",\n {\n key: genre.toString(),\n attrs: {\n href:\n \"https://www.imdb.com/search/title?count=100&title_type=tv_series&genres=\" +\n genre.toLowerCase().replace(\" \", \"-\"),\n title:\n \"View other popular \" +\n genre +\n \" shows on IMDB\"\n }\n },\n [_c(\"li\", [_vm._v(_vm._s(genre))])]\n )\n }),\n 1\n )\n ]\n )\n ]),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"row\" }, [\n _vm.configLoaded\n ? _c(\n \"div\",\n {\n ref: \"summary\",\n staticClass: \"col-md-12\",\n attrs: { id: \"summary\" }\n },\n [\n _c(\"div\", { staticClass: \"row\" }, [\n _c(\n \"div\",\n {\n staticClass:\n \"col-lg-9 col-md-8 col-sm-8 col-xs-12\",\n attrs: { id: \"show-summary\" }\n },\n [\n _c(\n \"table\",\n { staticClass: \"summaryTable pull-left\" },\n [\n _vm.show.plot\n ? _c(\"tr\", [\n _c(\n \"td\",\n {\n staticStyle: {\n \"padding-bottom\": \"15px\"\n },\n attrs: { colspan: \"2\" }\n },\n [\n _c(\"truncate\", {\n attrs: {\n \"action-class\":\n \"btn-medusa\",\n length: 250,\n clamp: \"show more\",\n less: \"show less\",\n text: _vm.show.plot\n },\n on: {\n toggle: function($event) {\n return _vm.$emit(\"reflow\")\n }\n }\n })\n ],\n 1\n )\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _vm.getQualityPreset({\n value: _vm.combinedQualities\n }) !== undefined\n ? _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Quality:\")]\n ),\n _vm._v(\" \"),\n _c(\n \"td\",\n [\n _c(\"quality-pill\", {\n attrs: {\n quality:\n _vm.combinedQualities\n }\n })\n ],\n 1\n )\n ])\n : [\n _vm.combineQualities(\n _vm.show.config.qualities.allowed\n ) > 0\n ? _c(\"tr\", [\n _c(\n \"td\",\n {\n staticClass: \"showLegend\"\n },\n [\n _vm._v(\n \"Allowed Qualities:\"\n )\n ]\n ),\n _vm._v(\" \"),\n _c(\n \"td\",\n [\n _vm._l(\n _vm.show.config\n .qualities.allowed,\n function(\n curQuality,\n index\n ) {\n return [\n _vm._v(\n \"\\n \" +\n _vm._s(\n index > 0\n ? \", \"\n : \"\"\n ) +\n \"\\n \"\n ),\n _c(\"quality-pill\", {\n key:\n \"allowed-\" +\n curQuality,\n attrs: {\n quality: curQuality\n }\n })\n ]\n }\n )\n ],\n 2\n )\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _vm.combineQualities(\n _vm.show.config.qualities\n .preferred\n ) > 0\n ? _c(\"tr\", [\n _c(\n \"td\",\n {\n staticClass: \"showLegend\"\n },\n [\n _vm._v(\n \"Preferred Qualities:\"\n )\n ]\n ),\n _vm._v(\" \"),\n _c(\n \"td\",\n [\n _vm._l(\n _vm.show.config\n .qualities.preferred,\n function(\n curQuality,\n index\n ) {\n return [\n _vm._v(\n \"\\n \" +\n _vm._s(\n index > 0\n ? \", \"\n : \"\"\n ) +\n \"\\n \"\n ),\n _c(\"quality-pill\", {\n key:\n \"preferred-\" +\n curQuality,\n attrs: {\n quality: curQuality\n }\n })\n ]\n }\n )\n ],\n 2\n )\n ])\n : _vm._e()\n ],\n _vm._v(\" \"),\n _vm.show.network && _vm.show.airs\n ? _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Originally Airs: \")]\n ),\n _c(\"td\", [\n _vm._v(_vm._s(_vm.show.airs)),\n !_vm.show.airsFormatValid\n ? _c(\n \"b\",\n {\n staticClass:\n \"invalid-value\"\n },\n [\n _vm._v(\n \" (invalid time format)\"\n )\n ]\n )\n : _vm._e(),\n _vm._v(\n \" on \" +\n _vm._s(_vm.show.network)\n )\n ])\n ])\n : _vm.show.network\n ? _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Originally Airs: \")]\n ),\n _c(\"td\", [\n _vm._v(_vm._s(_vm.show.network))\n ])\n ])\n : _vm.show.airs\n ? _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Originally Airs: \")]\n ),\n _c(\"td\", [\n _vm._v(_vm._s(_vm.show.airs)),\n !_vm.show.airsFormatValid\n ? _c(\n \"b\",\n {\n staticClass:\n \"invalid-value\"\n },\n [\n _vm._v(\n \" (invalid time format)\"\n )\n ]\n )\n : _vm._e()\n ])\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Show Status: \")]\n ),\n _c(\"td\", [\n _vm._v(_vm._s(_vm.show.status))\n ])\n ]),\n _vm._v(\" \"),\n _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Default EP Status: \")]\n ),\n _c(\"td\", [\n _vm._v(\n _vm._s(\n _vm.show.config\n .defaultEpisodeStatus\n )\n )\n ])\n ]),\n _vm._v(\" \"),\n _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [\n _c(\n \"span\",\n {\n class: {\n \"invalid-value\": !_vm.show\n .config.locationValid\n }\n },\n [_vm._v(\"Location: \")]\n )\n ]\n ),\n _c(\"td\", [\n _c(\n \"span\",\n {\n class: {\n \"invalid-value\": !_vm.show\n .config.locationValid\n }\n },\n [\n _vm._v(\n _vm._s(_vm.show.config.location)\n )\n ]\n ),\n _vm._v(\n _vm._s(\n _vm.show.config.locationValid\n ? \"\"\n : \" (Missing)\"\n )\n )\n ])\n ]),\n _vm._v(\" \"),\n _vm.show.config.aliases.filter(function(\n alias\n ) {\n return alias.season === -1\n }).length > 0\n ? _c(\"tr\", [\n _c(\n \"td\",\n {\n staticClass: \"showLegend\",\n staticStyle: {\n \"vertical-align\": \"top\"\n }\n },\n [_vm._v(\"Scene Name:\")]\n ),\n _vm._v(\" \"),\n _c(\"td\", [\n _vm._v(\n _vm._s(\n _vm.show.config.aliases\n .filter(function(alias) {\n return alias.season === -1\n })\n .map(function(alias) {\n return alias.title\n })\n .join(\", \")\n )\n )\n ])\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _vm.show.config.release.requiredWords\n .length +\n _vm.search.filters.required.length >\n 0\n ? _c(\"tr\", [\n _c(\n \"td\",\n {\n staticClass: \"showLegend\",\n staticStyle: {\n \"vertical-align\": \"top\"\n }\n },\n [\n _c(\n \"span\",\n {\n class: {\n required:\n _vm.type ===\n \"snatch-selection\"\n }\n },\n [_vm._v(\"Required Words: \")]\n )\n ]\n ),\n _vm._v(\" \"),\n _c(\"td\", [\n _vm.show.config.release\n .requiredWords.length\n ? _c(\n \"span\",\n {\n staticClass: \"break-word\"\n },\n [\n _vm._v(\n \"\\n \" +\n _vm._s(\n _vm.show.config.release.requiredWords.join(\n \", \"\n )\n ) +\n \"\\n \"\n )\n ]\n )\n : _vm._e(),\n _vm._v(\" \"),\n _vm.search.filters.required\n .length > 0\n ? _c(\n \"span\",\n {\n staticClass:\n \"break-word global-filter\"\n },\n [\n _c(\n \"app-link\",\n {\n attrs: {\n href:\n \"config/search/#searchfilters\"\n }\n },\n [\n _vm.show.config\n .release\n .requiredWords\n .length > 0\n ? [\n _vm.show.config\n .release\n .requiredWordsExclude\n ? _c(\"span\", [\n _vm._v(\n \" excluded from: \"\n )\n ])\n : _c(\"span\", [\n _vm._v(\n \"+ \"\n )\n ])\n ]\n : _vm._e(),\n _vm._v(\n \"\\n \" +\n _vm._s(\n _vm.search.filters.required.join(\n \", \"\n )\n ) +\n \"\\n \"\n )\n ],\n 2\n )\n ],\n 1\n )\n : _vm._e()\n ])\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _vm.show.config.release.ignoredWords\n .length +\n _vm.search.filters.ignored.length >\n 0\n ? _c(\"tr\", [\n _c(\n \"td\",\n {\n staticClass: \"showLegend\",\n staticStyle: {\n \"vertical-align\": \"top\"\n }\n },\n [\n _c(\n \"span\",\n {\n class: {\n ignored:\n _vm.type ===\n \"snatch-selection\"\n }\n },\n [_vm._v(\"Ignored Words: \")]\n )\n ]\n ),\n _vm._v(\" \"),\n _c(\"td\", [\n _vm.show.config.release\n .ignoredWords.length\n ? _c(\n \"span\",\n {\n staticClass: \"break-word\"\n },\n [\n _vm._v(\n \"\\n \" +\n _vm._s(\n _vm.show.config.release.ignoredWords.join(\n \", \"\n )\n ) +\n \"\\n \"\n )\n ]\n )\n : _vm._e(),\n _vm._v(\" \"),\n _vm.search.filters.ignored\n .length > 0\n ? _c(\n \"span\",\n {\n staticClass:\n \"break-word global-filter\"\n },\n [\n _c(\n \"app-link\",\n {\n attrs: {\n href:\n \"config/search/#searchfilters\"\n }\n },\n [\n _vm.show.config\n .release\n .ignoredWords\n .length > 0\n ? [\n _vm.show.config\n .release\n .ignoredWordsExclude\n ? _c(\"span\", [\n _vm._v(\n \" excluded from: \"\n )\n ])\n : _c(\"span\", [\n _vm._v(\n \"+ \"\n )\n ])\n ]\n : _vm._e(),\n _vm._v(\n \"\\n \" +\n _vm._s(\n _vm.search.filters.ignored.join(\n \", \"\n )\n ) +\n \"\\n \"\n )\n ],\n 2\n )\n ],\n 1\n )\n : _vm._e()\n ])\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _vm.search.filters.preferred.length > 0\n ? _c(\"tr\", [\n _c(\n \"td\",\n {\n staticClass: \"showLegend\",\n staticStyle: {\n \"vertical-align\": \"top\"\n }\n },\n [\n _c(\n \"span\",\n {\n class: {\n preferred:\n _vm.type ===\n \"snatch-selection\"\n }\n },\n [_vm._v(\"Preferred Words: \")]\n )\n ]\n ),\n _vm._v(\" \"),\n _c(\n \"td\",\n [\n _c(\n \"app-link\",\n {\n attrs: {\n href:\n \"config/search/#searchfilters\"\n }\n },\n [\n _c(\n \"span\",\n {\n staticClass:\n \"break-word\"\n },\n [\n _vm._v(\n _vm._s(\n _vm.search.filters.preferred.join(\n \", \"\n )\n )\n )\n ]\n )\n ]\n )\n ],\n 1\n )\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _vm.search.filters.undesired.length > 0\n ? _c(\"tr\", [\n _c(\n \"td\",\n {\n staticClass: \"showLegend\",\n staticStyle: {\n \"vertical-align\": \"top\"\n }\n },\n [\n _c(\n \"span\",\n {\n class: {\n undesired:\n _vm.type ===\n \"snatch-selection\"\n }\n },\n [_vm._v(\"Undesired Words: \")]\n )\n ]\n ),\n _vm._v(\" \"),\n _c(\n \"td\",\n [\n _c(\n \"app-link\",\n {\n attrs: {\n href:\n \"config/search/#searchfilters\"\n }\n },\n [\n _c(\n \"span\",\n {\n staticClass:\n \"break-word\"\n },\n [\n _vm._v(\n _vm._s(\n _vm.search.filters.undesired.join(\n \", \"\n )\n )\n )\n ]\n )\n ]\n )\n ],\n 1\n )\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _vm.show.config.release.whitelist &&\n _vm.show.config.release.whitelist.length >\n 0\n ? _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Wanted Groups:\")]\n ),\n _vm._v(\" \"),\n _c(\"td\", [\n _vm._v(\n _vm._s(\n _vm.show.config.release.whitelist.join(\n \", \"\n )\n )\n )\n ])\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _vm.show.config.release.blacklist &&\n _vm.show.config.release.blacklist.length >\n 0\n ? _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Unwanted Groups:\")]\n ),\n _vm._v(\" \"),\n _c(\"td\", [\n _vm._v(\n _vm._s(\n _vm.show.config.release.blacklist.join(\n \", \"\n )\n )\n )\n ])\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _vm.show.config.airdateOffset !== 0\n ? _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Daily search offset:\")]\n ),\n _vm._v(\" \"),\n _c(\"td\", [\n _vm._v(\n _vm._s(\n _vm.show.config.airdateOffset\n ) + \" hours\"\n )\n ])\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _vm.show.config.locationValid &&\n _vm.show.size > -1\n ? _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Size:\")]\n ),\n _vm._v(\" \"),\n _c(\"td\", [\n _vm._v(\n _vm._s(\n _vm.humanFileSize(\n _vm.show.size\n )\n )\n )\n ])\n ])\n : _vm._e()\n ],\n 2\n )\n ]\n ),\n _vm._v(\" \"),\n _c(\n \"div\",\n {\n staticClass:\n \"col-lg-3 col-md-4 col-sm-4 col-xs-12 pull-xs-left\",\n attrs: { id: \"show-status\" }\n },\n [\n _c(\n \"table\",\n {\n staticClass:\n \"pull-xs-left pull-md-right pull-sm-right pull-lg-right\"\n },\n [\n _vm.show.language\n ? _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Info Language:\")]\n ),\n _c(\"td\", [\n _c(\"img\", {\n attrs: {\n src:\n \"images/subtitles/flags/\" +\n _vm.getCountryISO2ToISO3(\n _vm.show.language\n ) +\n \".png\",\n width: \"16\",\n height: \"11\",\n alt: _vm.show.language,\n title: _vm.show.language,\n onError:\n \"this.onerror=null;this.src='images/flags/unknown.png';\"\n }\n })\n ])\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _vm.subtitles.enabled\n ? _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Subtitles: \")]\n ),\n _c(\n \"td\",\n [\n _c(\"state-switch\", {\n attrs: {\n theme: _vm.layout.themeName,\n state:\n _vm.show.config\n .subtitlesEnabled\n },\n on: {\n click: function($event) {\n return _vm.toggleConfigOption(\n \"subtitlesEnabled\"\n )\n }\n }\n })\n ],\n 1\n )\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Season Folders: \")]\n ),\n _c(\n \"td\",\n [\n _c(\"state-switch\", {\n attrs: {\n theme: _vm.layout.themeName,\n state:\n _vm.show.config\n .seasonFolders ||\n _vm.general.namingForceFolders\n }\n })\n ],\n 1\n )\n ]),\n _vm._v(\" \"),\n _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Paused: \")]\n ),\n _c(\n \"td\",\n [\n _c(\"state-switch\", {\n attrs: {\n theme: _vm.layout.themeName,\n state: _vm.show.config.paused\n },\n on: {\n click: function($event) {\n return _vm.toggleConfigOption(\n \"paused\"\n )\n }\n }\n })\n ],\n 1\n )\n ]),\n _vm._v(\" \"),\n _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Air-by-Date: \")]\n ),\n _c(\n \"td\",\n [\n _c(\"state-switch\", {\n attrs: {\n theme: _vm.layout.themeName,\n state: _vm.show.config.airByDate\n },\n on: {\n click: function($event) {\n return _vm.toggleConfigOption(\n \"airByDate\"\n )\n }\n }\n })\n ],\n 1\n )\n ]),\n _vm._v(\" \"),\n _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Sports: \")]\n ),\n _c(\n \"td\",\n [\n _c(\"state-switch\", {\n attrs: {\n theme: _vm.layout.themeName,\n state: _vm.show.config.sports\n },\n on: {\n click: function($event) {\n return _vm.toggleConfigOption(\n \"sports\"\n )\n }\n }\n })\n ],\n 1\n )\n ]),\n _vm._v(\" \"),\n _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Anime: \")]\n ),\n _c(\n \"td\",\n [\n _c(\"state-switch\", {\n attrs: {\n theme: _vm.layout.themeName,\n state: _vm.show.config.anime\n },\n on: {\n click: function($event) {\n return _vm.toggleConfigOption(\n \"anime\"\n )\n }\n }\n })\n ],\n 1\n )\n ]),\n _vm._v(\" \"),\n _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"DVD Order: \")]\n ),\n _c(\n \"td\",\n [\n _c(\"state-switch\", {\n attrs: {\n theme: _vm.layout.themeName,\n state: _vm.show.config.dvdOrder\n },\n on: {\n click: function($event) {\n return _vm.toggleConfigOption(\n \"dvdOrder\"\n )\n }\n }\n })\n ],\n 1\n )\n ]),\n _vm._v(\" \"),\n _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Scene Numbering: \")]\n ),\n _c(\n \"td\",\n [\n _c(\"state-switch\", {\n attrs: {\n theme: _vm.layout.themeName,\n state: _vm.show.config.scene\n },\n on: {\n click: function($event) {\n return _vm.toggleConfigOption(\n \"scene\"\n )\n }\n }\n })\n ],\n 1\n )\n ])\n ]\n )\n ]\n )\n ])\n ]\n )\n : _vm._e()\n ])\n ])\n ]\n )\n ])\n ]\n ),\n _vm._v(\" \"),\n _c(\n \"div\",\n {\n staticClass: \"shadow shadow-background\",\n attrs: { id: \"episodes-controll-background\" }\n },\n [\n _vm.show\n ? _c(\n \"div\",\n {\n staticClass: \"row\",\n attrs: { id: \"row-show-episodes-controls\" }\n },\n [\n _c(\n \"div\",\n {\n staticClass: \"col-md-12\",\n attrs: { id: \"col-show-episodes-controls\" }\n },\n [\n _vm.type === \"show\"\n ? _c(\"div\", { staticClass: \"row key\" }, [\n _c(\n \"div\",\n {\n ref: \"checkboxControls\",\n staticClass: \"col-lg-12\",\n attrs: { id: \"checkboxControls\" }\n },\n [\n _vm.show.seasons\n ? _c(\n \"div\",\n {\n staticClass: \"pull-left top-5\",\n attrs: { id: \"key-padding\" }\n },\n _vm._l(_vm.overviewStatus, function(\n status\n ) {\n return _c(\n \"label\",\n {\n key: status.id,\n attrs: { for: status.id }\n },\n [\n _c(\"span\", { class: status.id }, [\n _c(\"input\", {\n directives: [\n {\n name: \"model\",\n rawName: \"v-model\",\n value: status.checked,\n expression: \"status.checked\"\n }\n ],\n attrs: {\n type: \"checkbox\",\n id: status.id\n },\n domProps: {\n checked: Array.isArray(\n status.checked\n )\n ? _vm._i(\n status.checked,\n null\n ) > -1\n : status.checked\n },\n on: {\n change: [\n function($event) {\n var $$a = status.checked,\n $$el = $event.target,\n $$c = $$el.checked\n ? true\n : false\n if (Array.isArray($$a)) {\n var $$v = null,\n $$i = _vm._i($$a, $$v)\n if ($$el.checked) {\n $$i < 0 &&\n _vm.$set(\n status,\n \"checked\",\n $$a.concat([$$v])\n )\n } else {\n $$i > -1 &&\n _vm.$set(\n status,\n \"checked\",\n $$a\n .slice(0, $$i)\n .concat(\n $$a.slice(\n $$i + 1\n )\n )\n )\n }\n } else {\n _vm.$set(\n status,\n \"checked\",\n $$c\n )\n }\n },\n function($event) {\n return _vm.$emit(\n \"update-overview-status\",\n _vm.overviewStatus\n )\n }\n ]\n }\n }),\n _vm._v(\n \"\\n \" +\n _vm._s(status.name) +\n \": \"\n ),\n _c(\"b\", [\n _vm._v(\n _vm._s(\n _vm.episodeSummary[\n status.name\n ]\n )\n )\n ])\n ])\n ]\n )\n }),\n 0\n )\n : _vm._e(),\n _vm._v(\" \"),\n _c(\n \"div\",\n { staticClass: \"pull-lg-right top-5\" },\n [\n _c(\n \"select\",\n {\n directives: [\n {\n name: \"model\",\n rawName: \"v-model\",\n value: _vm.selectedStatus,\n expression: \"selectedStatus\"\n }\n ],\n staticClass:\n \"form-control form-control-inline input-sm-custom input-sm-smallfont\",\n attrs: { id: \"statusSelect\" },\n on: {\n change: function($event) {\n var $$selectedVal = Array.prototype.filter\n .call(\n $event.target.options,\n function(o) {\n return o.selected\n }\n )\n .map(function(o) {\n var val =\n \"_value\" in o\n ? o._value\n : o.value\n return val\n })\n _vm.selectedStatus = $event.target\n .multiple\n ? $$selectedVal\n : $$selectedVal[0]\n }\n }\n },\n [\n _c(\n \"option\",\n {\n domProps: {\n value: \"Change status to:\"\n }\n },\n [_vm._v(\"Change status to:\")]\n ),\n _vm._v(\" \"),\n _vm._l(\n _vm.changeStatusOptions,\n function(status) {\n return _c(\n \"option\",\n {\n key: status.key,\n domProps: {\n value: status.value\n }\n },\n [\n _vm._v(\n \"\\n \" +\n _vm._s(status.name) +\n \"\\n \"\n )\n ]\n )\n }\n )\n ],\n 2\n ),\n _vm._v(\" \"),\n _c(\n \"select\",\n {\n directives: [\n {\n name: \"model\",\n rawName: \"v-model\",\n value: _vm.selectedQuality,\n expression: \"selectedQuality\"\n }\n ],\n staticClass:\n \"form-control form-control-inline input-sm-custom input-sm-smallfont\",\n attrs: { id: \"qualitySelect\" },\n on: {\n change: function($event) {\n var $$selectedVal = Array.prototype.filter\n .call(\n $event.target.options,\n function(o) {\n return o.selected\n }\n )\n .map(function(o) {\n var val =\n \"_value\" in o\n ? o._value\n : o.value\n return val\n })\n _vm.selectedQuality = $event.target\n .multiple\n ? $$selectedVal\n : $$selectedVal[0]\n }\n }\n },\n [\n _c(\n \"option\",\n {\n domProps: {\n value: \"Change quality to:\"\n }\n },\n [_vm._v(\"Change quality to:\")]\n ),\n _vm._v(\" \"),\n _vm._l(_vm.qualities, function(\n quality\n ) {\n return _c(\n \"option\",\n {\n key: quality.key,\n domProps: { value: quality.value }\n },\n [\n _vm._v(\n \"\\n \" +\n _vm._s(quality.name) +\n \"\\n \"\n )\n ]\n )\n })\n ],\n 2\n ),\n _vm._v(\" \"),\n _c(\"input\", {\n attrs: {\n type: \"hidden\",\n id: \"series-slug\"\n },\n domProps: { value: _vm.show.id.slug }\n }),\n _vm._v(\" \"),\n _c(\"input\", {\n attrs: {\n type: \"hidden\",\n id: \"series-id\"\n },\n domProps: {\n value: _vm.show.id[_vm.show.indexer]\n }\n }),\n _vm._v(\" \"),\n _c(\"input\", {\n attrs: { type: \"hidden\", id: \"indexer\" },\n domProps: { value: _vm.show.indexer }\n }),\n _vm._v(\" \"),\n _c(\"input\", {\n staticClass: \"btn-medusa\",\n attrs: {\n type: \"button\",\n id: \"changeStatus\",\n value: \"Go\"\n },\n on: { click: _vm.changeStatusClicked }\n })\n ]\n )\n ]\n )\n ])\n : _c(\"div\")\n ]\n )\n ]\n )\n : _vm._e()\n ]\n )\n ],\n 2\n )\n}\nvar staticRenderFns = []\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack://slim/./src/components/show-header.vue?./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib/index.js??vue-loader-options"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"render\": () => (/* binding */ render),\n/* harmony export */ \"staticRenderFns\": () => (/* binding */ staticRenderFns)\n/* harmony export */ });\nvar render = function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\n \"div\",\n { staticClass: \"show-header-container\" },\n [\n _c(\"div\", { staticClass: \"row\" }, [\n _vm.show\n ? _c(\n \"div\",\n {\n staticClass: \"col-lg-12\",\n attrs: { id: \"showtitle\", \"data-showname\": _vm.show.title }\n },\n [\n _c(\"div\", [\n _c(\n \"h1\",\n {\n staticClass: \"title\",\n attrs: {\n \"data-indexer-name\": _vm.show.indexer,\n \"data-series-id\": _vm.show.id[_vm.show.indexer],\n id: \"scene_exception_\" + _vm.show.id[_vm.show.indexer]\n }\n },\n [\n _c(\n \"app-link\",\n {\n staticClass: \"snatchTitle\",\n attrs: {\n href:\n \"home/displayShow?showslug=\" + _vm.show.id.slug\n }\n },\n [_vm._v(_vm._s(_vm.show.title))]\n )\n ],\n 1\n )\n ]),\n _vm._v(\" \"),\n _vm.type === \"snatch-selection\"\n ? _c(\n \"div\",\n {\n staticClass: \"pull-right episode-info\",\n attrs: { id: \"show-specials-and-seasons\" }\n },\n [\n _c(\n \"span\",\n { staticClass: \"h2footer display-specials\" },\n [\n _vm._v(\n \"\\n Manual search for: Season \" +\n _vm._s(_vm.season)\n ),\n _vm.episode !== undefined &&\n _vm.manualSearchType !== \"season\"\n ? [_vm._v(\" Episode \" + _vm._s(_vm.episode))]\n : _vm._e()\n ],\n 2\n ),\n _vm._v(\" \"),\n _vm.manualSearchType !== \"season\" && _vm.episodeTitle\n ? _c(\"span\", [\n _vm._v(\n \"\\n \" +\n _vm._s(_vm.episodeTitle) +\n \"\\n \"\n )\n ])\n : _vm._e()\n ]\n )\n : _vm._e(),\n _vm._v(\" \"),\n _vm.type !== \"snatch-selection\" && _vm.seasons.length >= 1\n ? _c(\n \"div\",\n {\n staticClass: \"pull-right\",\n attrs: { id: \"show-specials-and-seasons\" }\n },\n [\n _vm.seasons.includes(0)\n ? _c(\n \"span\",\n { staticClass: \"h2footer display-specials\" },\n [\n _vm._v(\n \"\\n Display Specials: \"\n ),\n _c(\n \"a\",\n {\n staticClass: \"inner\",\n staticStyle: { cursor: \"pointer\" },\n on: {\n click: function($event) {\n $event.preventDefault()\n return _vm.toggleSpecials()\n }\n }\n },\n [\n _vm._v(\n _vm._s(\n _vm.displaySpecials ? \"Hide\" : \"Show\"\n )\n )\n ]\n )\n ]\n )\n : _vm._e(),\n _vm._v(\" \"),\n _c(\n \"div\",\n { staticClass: \"h2footer display-seasons clear\" },\n [\n _c(\n \"span\",\n [\n _vm.seasons.length >= 15\n ? _c(\n \"select\",\n {\n directives: [\n {\n name: \"model\",\n rawName: \"v-model\",\n value: _vm.jumpToSeason,\n expression: \"jumpToSeason\"\n }\n ],\n staticClass: \"form-control input-sm\",\n staticStyle: { position: \"relative\" },\n attrs: { id: \"seasonJump\" },\n on: {\n change: function($event) {\n var $$selectedVal = Array.prototype.filter\n .call(\n $event.target.options,\n function(o) {\n return o.selected\n }\n )\n .map(function(o) {\n var val =\n \"_value\" in o\n ? o._value\n : o.value\n return val\n })\n _vm.jumpToSeason = $event.target\n .multiple\n ? $$selectedVal\n : $$selectedVal[0]\n }\n }\n },\n [\n _c(\n \"option\",\n { attrs: { value: \"jump\" } },\n [_vm._v(\"Jump to Season\")]\n ),\n _vm._v(\" \"),\n _vm._l(_vm.seasons, function(\n seasonNumber\n ) {\n return _c(\n \"option\",\n {\n key:\n \"jumpToSeason-\" + seasonNumber,\n domProps: { value: seasonNumber }\n },\n [\n _vm._v(\n \"\\n \" +\n _vm._s(\n seasonNumber === 0\n ? \"Specials\"\n : \"Season \" + seasonNumber\n ) +\n \"\\n \"\n )\n ]\n )\n })\n ],\n 2\n )\n : _vm.seasons.length >= 1\n ? [\n _vm._v(\n \"\\n Season:\\n \"\n ),\n _vm._l(_vm.reverse(_vm.seasons), function(\n seasonNumber,\n index\n ) {\n return [\n _c(\n \"app-link\",\n {\n key:\n \"jumpToSeason-\" + seasonNumber,\n attrs: {\n href: \"#season-\" + seasonNumber\n },\n nativeOn: {\n click: function($event) {\n $event.preventDefault()\n _vm.jumpToSeason = seasonNumber\n }\n }\n },\n [\n _vm._v(\n \"\\n \" +\n _vm._s(\n seasonNumber === 0\n ? \"Specials\"\n : seasonNumber\n ) +\n \"\\n \"\n )\n ]\n ),\n _vm._v(\" \"),\n index !== _vm.seasons.length - 1\n ? _c(\n \"span\",\n {\n key: \"separator-\" + index,\n staticClass: \"separator\"\n },\n [_vm._v(\"| \")]\n )\n : _vm._e()\n ]\n })\n ]\n : _vm._e()\n ],\n 2\n )\n ]\n )\n ]\n )\n : _vm._e()\n ]\n )\n : _vm._e()\n ]),\n _vm._v(\" \"),\n _vm._l(_vm.activeShowQueueStatuses, function(queueItem) {\n return _c(\"div\", { key: queueItem.action, staticClass: \"row\" }, [\n _c(\"div\", { staticClass: \"alert alert-info\" }, [\n _vm._v(\"\\n \" + _vm._s(queueItem.message) + \"\\n \")\n ])\n ])\n }),\n _vm._v(\" \"),\n _c(\n \"div\",\n {\n staticClass: \"shadow shadow-background\",\n attrs: { id: \"summaryBackground\" }\n },\n [\n _c(\"div\", { staticClass: \"row\", attrs: { id: \"row-show-summary\" } }, [\n _c(\n \"div\",\n { staticClass: \"col-md-12\", attrs: { id: \"col-show-summary\" } },\n [\n _c(\"div\", { staticClass: \"show-poster-container\" }, [\n _c(\"div\", { staticClass: \"row\" }, [\n _c(\n \"div\",\n { staticClass: \"image-flex-container col-md-12\" },\n [\n _vm.show.id.slug\n ? _c(\"asset\", {\n attrs: {\n \"default-src\": \"images/poster.png\",\n \"show-slug\": _vm.show.id.slug,\n type: \"posterThumb\",\n cls: \"show-image shadow\",\n link: true\n }\n })\n : _vm._e()\n ],\n 1\n )\n ])\n ]),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"ver-spacer\" }),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"show-info-container\" }, [\n _c(\"div\", { staticClass: \"row\" }, [\n _c(\n \"div\",\n {\n staticClass:\n \"pull-right col-lg-3 col-md-3 hidden-sm hidden-xs\"\n },\n [\n _vm.show.id.slug\n ? _c(\"asset\", {\n attrs: {\n \"default-src\": \"images/banner.png\",\n \"show-slug\": _vm.show.id.slug,\n type: \"banner\",\n cls: \"show-banner pull-right shadow\",\n link: true\n }\n })\n : _vm._e()\n ],\n 1\n ),\n _vm._v(\" \"),\n _c(\n \"div\",\n {\n staticClass:\n \"pull-left col-lg-9 col-md-9 col-sm-12 col-xs-12\",\n attrs: { id: \"indexers\" }\n },\n [\n _vm.show.rating.imdb && _vm.show.rating.imdb.rating\n ? _c(\n \"span\",\n {\n staticClass: \"imdbstars\",\n attrs: {\n \"qtip-content\":\n _vm.show.rating.imdb.rating +\n \" / 10 Stars
    \" +\n _vm.show.rating.imdb.votes +\n \" Votes\"\n }\n },\n [\n _c(\"span\", {\n style: {\n width:\n Number(_vm.show.rating.imdb.rating) * 10 +\n \"%\"\n }\n })\n ]\n )\n : _vm._e(),\n _vm._v(\" \"),\n !_vm.show.imdbInfo || !_vm.show.imdbInfo.imdbId\n ? [\n _vm.show.year.start\n ? _c(\"span\", [\n _vm._v(\n \"(\" +\n _vm._s(_vm.show.year.start) +\n \") - \" +\n _vm._s(_vm.show.runtime) +\n \" minutes - \"\n )\n ])\n : _vm._e()\n ]\n : [\n _vm._l(_vm.show.countryCodes, function(country) {\n return _c(\"img\", {\n key: \"flag-\" + country,\n class: [\"country-flag\", \"flag-\" + country],\n staticStyle: {\n \"margin-left\": \"3px\",\n \"vertical-align\": \"middle\"\n },\n attrs: {\n src: \"images/blank.png\",\n width: \"16\",\n height: \"11\"\n }\n })\n }),\n _vm._v(\" \"),\n _vm.show.imdbInfo.year\n ? _c(\"span\", [\n _vm._v(\n \"\\n (\" +\n _vm._s(_vm.show.imdbInfo.year) +\n \") -\\n \"\n )\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _c(\"span\", [\n _vm._v(\n \"\\n \" +\n _vm._s(\n _vm.show.imdbInfo.runtimes ||\n _vm.show.runtime\n ) +\n \" minutes\\n \"\n )\n ])\n ],\n _vm._v(\" \"),\n _c(\n \"div\",\n { attrs: { id: \"indexer-wrapper\" } },\n [\n _vm.getShowIndexerUrl(_vm.show) &&\n _vm.indexerConfig[_vm.show.indexer].icon\n ? _c(\n \"app-link\",\n {\n attrs: {\n href: _vm.getShowIndexerUrl(_vm.show),\n title: _vm.getShowIndexerUrl(_vm.show)\n }\n },\n [\n _c(\"img\", {\n attrs: {\n id: \"stored-by-indexer\",\n src: \"images/star.png\"\n }\n }),\n _vm._v(\" \"),\n _c(\"img\", {\n staticStyle: {\n \"margin-top\": \"-1px\",\n \"vertical-align\": \"middle\"\n },\n attrs: {\n alt:\n _vm.indexerConfig[_vm.show.indexer]\n .name,\n height: \"16\",\n width: \"16\",\n src:\n \"images/\" +\n _vm.indexerConfig[_vm.show.indexer]\n .icon\n }\n })\n ]\n )\n : _vm._e()\n ],\n 1\n ),\n _vm._v(\" \"),\n _c(\"externals\", { attrs: { show: _vm.show } })\n ],\n 2\n ),\n _vm._v(\" \"),\n _c(\n \"div\",\n {\n staticClass:\n \"pull-left col-lg-9 col-md-9 col-sm-12 col-xs-12\",\n attrs: { id: \"tags\" }\n },\n [\n _vm.show.genres\n ? _c(\n \"ul\",\n { staticClass: \"tags\" },\n _vm._l(\n _vm.dedupeGenres(_vm.show.genres),\n function(genre) {\n return _c(\n \"app-link\",\n {\n key: genre.toString(),\n attrs: {\n href:\n \"https://trakt.tv/shows/popular/?genres=\" +\n genre.toLowerCase().replace(\" \", \"-\"),\n title:\n \"View other popular \" +\n genre +\n \" shows on trakt.tv\"\n }\n },\n [_c(\"li\", [_vm._v(_vm._s(genre))])]\n )\n }\n ),\n 1\n )\n : _c(\n \"ul\",\n { staticClass: \"tags\" },\n _vm._l(_vm.showGenres, function(genre) {\n return _c(\n \"app-link\",\n {\n key: genre.toString(),\n attrs: {\n href:\n \"https://www.imdb.com/search/title?count=100&title_type=tv_series&genres=\" +\n genre.toLowerCase().replace(\" \", \"-\"),\n title:\n \"View other popular \" +\n genre +\n \" shows on IMDB\"\n }\n },\n [_c(\"li\", [_vm._v(_vm._s(genre))])]\n )\n }),\n 1\n )\n ]\n )\n ]),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"row\" }, [\n _vm.configLoaded\n ? _c(\n \"div\",\n {\n ref: \"summary\",\n staticClass: \"col-md-12\",\n attrs: { id: \"summary\" }\n },\n [\n _c(\"div\", { staticClass: \"row\" }, [\n _c(\n \"div\",\n {\n staticClass:\n \"col-lg-9 col-md-8 col-sm-8 col-xs-12\",\n attrs: { id: \"show-summary\" }\n },\n [\n _c(\n \"table\",\n { staticClass: \"summaryTable pull-left\" },\n [\n _vm.show.plot\n ? _c(\"tr\", [\n _c(\n \"td\",\n {\n staticStyle: {\n \"padding-bottom\": \"15px\"\n },\n attrs: { colspan: \"2\" }\n },\n [\n _c(\"truncate\", {\n attrs: {\n \"action-class\":\n \"btn-medusa\",\n length: 250,\n clamp: \"show more\",\n less: \"show less\",\n text: _vm.show.plot\n },\n on: {\n toggle: function($event) {\n return _vm.$emit(\"reflow\")\n }\n }\n })\n ],\n 1\n )\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _vm.getQualityPreset({\n value: _vm.combinedQualities\n }) !== undefined\n ? _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Quality:\")]\n ),\n _vm._v(\" \"),\n _c(\n \"td\",\n [\n _c(\"quality-pill\", {\n attrs: {\n quality:\n _vm.combinedQualities\n }\n })\n ],\n 1\n )\n ])\n : [\n _vm.combineQualities(\n _vm.show.config.qualities.allowed\n ) > 0\n ? _c(\"tr\", [\n _c(\n \"td\",\n {\n staticClass: \"showLegend\"\n },\n [\n _vm._v(\n \"Allowed Qualities:\"\n )\n ]\n ),\n _vm._v(\" \"),\n _c(\n \"td\",\n [\n _vm._l(\n _vm.show.config\n .qualities.allowed,\n function(\n curQuality,\n index\n ) {\n return [\n _vm._v(\n \"\\n \" +\n _vm._s(\n index > 0\n ? \", \"\n : \"\"\n ) +\n \"\\n \"\n ),\n _c(\"quality-pill\", {\n key:\n \"allowed-\" +\n curQuality,\n attrs: {\n quality: curQuality\n }\n })\n ]\n }\n )\n ],\n 2\n )\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _vm.combineQualities(\n _vm.show.config.qualities\n .preferred\n ) > 0\n ? _c(\"tr\", [\n _c(\n \"td\",\n {\n staticClass: \"showLegend\"\n },\n [\n _vm._v(\n \"Preferred Qualities:\"\n )\n ]\n ),\n _vm._v(\" \"),\n _c(\n \"td\",\n [\n _vm._l(\n _vm.show.config\n .qualities.preferred,\n function(\n curQuality,\n index\n ) {\n return [\n _vm._v(\n \"\\n \" +\n _vm._s(\n index > 0\n ? \", \"\n : \"\"\n ) +\n \"\\n \"\n ),\n _c(\"quality-pill\", {\n key:\n \"preferred-\" +\n curQuality,\n attrs: {\n quality: curQuality\n }\n })\n ]\n }\n )\n ],\n 2\n )\n ])\n : _vm._e()\n ],\n _vm._v(\" \"),\n _vm.show.network && _vm.show.airs\n ? _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Originally Airs: \")]\n ),\n _c(\"td\", [\n _vm._v(_vm._s(_vm.show.airs)),\n !_vm.show.airsFormatValid\n ? _c(\n \"b\",\n {\n staticClass:\n \"invalid-value\"\n },\n [\n _vm._v(\n \" (invalid time format)\"\n )\n ]\n )\n : _vm._e(),\n _vm._v(\n \" on \" +\n _vm._s(_vm.show.network)\n )\n ])\n ])\n : _vm.show.network\n ? _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Originally Airs: \")]\n ),\n _c(\"td\", [\n _vm._v(_vm._s(_vm.show.network))\n ])\n ])\n : _vm.show.airs\n ? _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Originally Airs: \")]\n ),\n _c(\"td\", [\n _vm._v(_vm._s(_vm.show.airs)),\n !_vm.show.airsFormatValid\n ? _c(\n \"b\",\n {\n staticClass:\n \"invalid-value\"\n },\n [\n _vm._v(\n \" (invalid time format)\"\n )\n ]\n )\n : _vm._e()\n ])\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Show Status: \")]\n ),\n _c(\"td\", [\n _vm._v(_vm._s(_vm.show.status))\n ])\n ]),\n _vm._v(\" \"),\n _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Default EP Status: \")]\n ),\n _c(\"td\", [\n _vm._v(\n _vm._s(\n _vm.show.config\n .defaultEpisodeStatus\n )\n )\n ])\n ]),\n _vm._v(\" \"),\n _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [\n _c(\n \"span\",\n {\n class: {\n \"invalid-value\": !_vm.show\n .config.locationValid\n }\n },\n [_vm._v(\"Location: \")]\n )\n ]\n ),\n _c(\"td\", [\n _c(\n \"span\",\n {\n class: {\n \"invalid-value\": !_vm.show\n .config.locationValid\n }\n },\n [\n _vm._v(\n _vm._s(_vm.show.config.location)\n )\n ]\n ),\n _vm._v(\n _vm._s(\n _vm.show.config.locationValid\n ? \"\"\n : \" (Missing)\"\n )\n )\n ])\n ]),\n _vm._v(\" \"),\n _vm.show.config.aliases.filter(function(\n alias\n ) {\n return alias.season === -1\n }).length > 0\n ? _c(\"tr\", [\n _c(\n \"td\",\n {\n staticClass: \"showLegend\",\n staticStyle: {\n \"vertical-align\": \"top\"\n }\n },\n [_vm._v(\"Scene Name:\")]\n ),\n _vm._v(\" \"),\n _c(\"td\", [\n _vm._v(\n _vm._s(\n _vm.show.config.aliases\n .filter(function(alias) {\n return alias.season === -1\n })\n .map(function(alias) {\n return alias.title\n })\n .join(\", \")\n )\n )\n ])\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _vm.show.config.release.requiredWords\n .length +\n _vm.search.filters.required.length >\n 0\n ? _c(\"tr\", [\n _c(\n \"td\",\n {\n staticClass: \"showLegend\",\n staticStyle: {\n \"vertical-align\": \"top\"\n }\n },\n [\n _c(\n \"span\",\n {\n class: {\n required:\n _vm.type ===\n \"snatch-selection\"\n }\n },\n [_vm._v(\"Required Words: \")]\n )\n ]\n ),\n _vm._v(\" \"),\n _c(\"td\", [\n _vm.show.config.release\n .requiredWords.length\n ? _c(\n \"span\",\n {\n staticClass: \"break-word\"\n },\n [\n _vm._v(\n \"\\n \" +\n _vm._s(\n _vm.show.config.release.requiredWords.join(\n \", \"\n )\n ) +\n \"\\n \"\n )\n ]\n )\n : _vm._e(),\n _vm._v(\" \"),\n _vm.search.filters.required\n .length > 0\n ? _c(\n \"span\",\n {\n staticClass:\n \"break-word global-filter\"\n },\n [\n _c(\n \"app-link\",\n {\n attrs: {\n href:\n \"config/search/#searchfilters\"\n }\n },\n [\n _vm.show.config\n .release\n .requiredWords\n .length > 0\n ? [\n _vm.show.config\n .release\n .requiredWordsExclude\n ? _c(\"span\", [\n _vm._v(\n \" excluded from: \"\n )\n ])\n : _c(\"span\", [\n _vm._v(\n \"+ \"\n )\n ])\n ]\n : _vm._e(),\n _vm._v(\n \"\\n \" +\n _vm._s(\n _vm.search.filters.required.join(\n \", \"\n )\n ) +\n \"\\n \"\n )\n ],\n 2\n )\n ],\n 1\n )\n : _vm._e()\n ])\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _vm.show.config.release.ignoredWords\n .length +\n _vm.search.filters.ignored.length >\n 0\n ? _c(\"tr\", [\n _c(\n \"td\",\n {\n staticClass: \"showLegend\",\n staticStyle: {\n \"vertical-align\": \"top\"\n }\n },\n [\n _c(\n \"span\",\n {\n class: {\n ignored:\n _vm.type ===\n \"snatch-selection\"\n }\n },\n [_vm._v(\"Ignored Words: \")]\n )\n ]\n ),\n _vm._v(\" \"),\n _c(\"td\", [\n _vm.show.config.release\n .ignoredWords.length\n ? _c(\n \"span\",\n {\n staticClass: \"break-word\"\n },\n [\n _vm._v(\n \"\\n \" +\n _vm._s(\n _vm.show.config.release.ignoredWords.join(\n \", \"\n )\n ) +\n \"\\n \"\n )\n ]\n )\n : _vm._e(),\n _vm._v(\" \"),\n _vm.search.filters.ignored\n .length > 0\n ? _c(\n \"span\",\n {\n staticClass:\n \"break-word global-filter\"\n },\n [\n _c(\n \"app-link\",\n {\n attrs: {\n href:\n \"config/search/#searchfilters\"\n }\n },\n [\n _vm.show.config\n .release\n .ignoredWords\n .length > 0\n ? [\n _vm.show.config\n .release\n .ignoredWordsExclude\n ? _c(\"span\", [\n _vm._v(\n \" excluded from: \"\n )\n ])\n : _c(\"span\", [\n _vm._v(\n \"+ \"\n )\n ])\n ]\n : _vm._e(),\n _vm._v(\n \"\\n \" +\n _vm._s(\n _vm.search.filters.ignored.join(\n \", \"\n )\n ) +\n \"\\n \"\n )\n ],\n 2\n )\n ],\n 1\n )\n : _vm._e()\n ])\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _vm.search.filters.preferred.length > 0\n ? _c(\"tr\", [\n _c(\n \"td\",\n {\n staticClass: \"showLegend\",\n staticStyle: {\n \"vertical-align\": \"top\"\n }\n },\n [\n _c(\n \"span\",\n {\n class: {\n preferred:\n _vm.type ===\n \"snatch-selection\"\n }\n },\n [_vm._v(\"Preferred Words: \")]\n )\n ]\n ),\n _vm._v(\" \"),\n _c(\n \"td\",\n [\n _c(\n \"app-link\",\n {\n attrs: {\n href:\n \"config/search/#searchfilters\"\n }\n },\n [\n _c(\n \"span\",\n {\n staticClass:\n \"break-word\"\n },\n [\n _vm._v(\n _vm._s(\n _vm.search.filters.preferred.join(\n \", \"\n )\n )\n )\n ]\n )\n ]\n )\n ],\n 1\n )\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _vm.search.filters.undesired.length > 0\n ? _c(\"tr\", [\n _c(\n \"td\",\n {\n staticClass: \"showLegend\",\n staticStyle: {\n \"vertical-align\": \"top\"\n }\n },\n [\n _c(\n \"span\",\n {\n class: {\n undesired:\n _vm.type ===\n \"snatch-selection\"\n }\n },\n [_vm._v(\"Undesired Words: \")]\n )\n ]\n ),\n _vm._v(\" \"),\n _c(\n \"td\",\n [\n _c(\n \"app-link\",\n {\n attrs: {\n href:\n \"config/search/#searchfilters\"\n }\n },\n [\n _c(\n \"span\",\n {\n staticClass:\n \"break-word\"\n },\n [\n _vm._v(\n _vm._s(\n _vm.search.filters.undesired.join(\n \", \"\n )\n )\n )\n ]\n )\n ]\n )\n ],\n 1\n )\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _vm.show.config.release.whitelist &&\n _vm.show.config.release.whitelist.length >\n 0\n ? _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Wanted Groups:\")]\n ),\n _vm._v(\" \"),\n _c(\"td\", [\n _vm._v(\n _vm._s(\n _vm.show.config.release.whitelist.join(\n \", \"\n )\n )\n )\n ])\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _vm.show.config.release.blacklist &&\n _vm.show.config.release.blacklist.length >\n 0\n ? _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Unwanted Groups:\")]\n ),\n _vm._v(\" \"),\n _c(\"td\", [\n _vm._v(\n _vm._s(\n _vm.show.config.release.blacklist.join(\n \", \"\n )\n )\n )\n ])\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _vm.show.config.airdateOffset !== 0\n ? _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Daily search offset:\")]\n ),\n _vm._v(\" \"),\n _c(\"td\", [\n _vm._v(\n _vm._s(\n _vm.show.config.airdateOffset\n ) + \" hours\"\n )\n ])\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _vm.show.config.locationValid &&\n _vm.show.size > -1\n ? _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Size:\")]\n ),\n _vm._v(\" \"),\n _c(\"td\", [\n _vm._v(\n _vm._s(\n _vm.humanFileSize(\n _vm.show.size\n )\n )\n )\n ])\n ])\n : _vm._e()\n ],\n 2\n )\n ]\n ),\n _vm._v(\" \"),\n _c(\n \"div\",\n {\n staticClass:\n \"col-lg-3 col-md-4 col-sm-4 col-xs-12 pull-xs-left\",\n attrs: { id: \"show-status\" }\n },\n [\n _c(\n \"table\",\n {\n staticClass:\n \"pull-xs-left pull-md-right pull-sm-right pull-lg-right\"\n },\n [\n _vm.show.language\n ? _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Info Language:\")]\n ),\n _c(\"td\", [\n _c(\"img\", {\n attrs: {\n src:\n \"images/subtitles/flags/\" +\n _vm.getCountryISO2ToISO3(\n _vm.show.language\n ) +\n \".png\",\n width: \"16\",\n height: \"11\",\n alt: _vm.show.language,\n title: _vm.show.language,\n onError:\n \"this.onerror=null;this.src='images/flags/unknown.png';\"\n }\n })\n ])\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _vm.subtitles.enabled\n ? _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Subtitles: \")]\n ),\n _c(\n \"td\",\n [\n _c(\"state-switch\", {\n attrs: {\n theme: _vm.layout.themeName,\n state:\n _vm.show.config\n .subtitlesEnabled\n },\n on: {\n click: function($event) {\n return _vm.toggleConfigOption(\n \"subtitlesEnabled\"\n )\n }\n }\n })\n ],\n 1\n )\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Season Folders: \")]\n ),\n _c(\n \"td\",\n [\n _c(\"state-switch\", {\n attrs: {\n theme: _vm.layout.themeName,\n state:\n _vm.show.config\n .seasonFolders ||\n _vm.general.namingForceFolders\n }\n })\n ],\n 1\n )\n ]),\n _vm._v(\" \"),\n _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Paused: \")]\n ),\n _c(\n \"td\",\n [\n _c(\"state-switch\", {\n attrs: {\n theme: _vm.layout.themeName,\n state: _vm.show.config.paused\n },\n on: {\n click: function($event) {\n return _vm.toggleConfigOption(\n \"paused\"\n )\n }\n }\n })\n ],\n 1\n )\n ]),\n _vm._v(\" \"),\n _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Air-by-Date: \")]\n ),\n _c(\n \"td\",\n [\n _c(\"state-switch\", {\n attrs: {\n theme: _vm.layout.themeName,\n state: _vm.show.config.airByDate\n },\n on: {\n click: function($event) {\n return _vm.toggleConfigOption(\n \"airByDate\"\n )\n }\n }\n })\n ],\n 1\n )\n ]),\n _vm._v(\" \"),\n _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Sports: \")]\n ),\n _c(\n \"td\",\n [\n _c(\"state-switch\", {\n attrs: {\n theme: _vm.layout.themeName,\n state: _vm.show.config.sports\n },\n on: {\n click: function($event) {\n return _vm.toggleConfigOption(\n \"sports\"\n )\n }\n }\n })\n ],\n 1\n )\n ]),\n _vm._v(\" \"),\n _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Anime: \")]\n ),\n _c(\n \"td\",\n [\n _c(\"state-switch\", {\n attrs: {\n theme: _vm.layout.themeName,\n state: _vm.show.config.anime\n },\n on: {\n click: function($event) {\n return _vm.toggleConfigOption(\n \"anime\"\n )\n }\n }\n })\n ],\n 1\n )\n ]),\n _vm._v(\" \"),\n _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"DVD Order: \")]\n ),\n _c(\n \"td\",\n [\n _c(\"state-switch\", {\n attrs: {\n theme: _vm.layout.themeName,\n state: _vm.show.config.dvdOrder\n },\n on: {\n click: function($event) {\n return _vm.toggleConfigOption(\n \"dvdOrder\"\n )\n }\n }\n })\n ],\n 1\n )\n ]),\n _vm._v(\" \"),\n _c(\"tr\", [\n _c(\n \"td\",\n { staticClass: \"showLegend\" },\n [_vm._v(\"Scene Numbering: \")]\n ),\n _c(\n \"td\",\n [\n _c(\"state-switch\", {\n attrs: {\n theme: _vm.layout.themeName,\n state: _vm.show.config.scene\n },\n on: {\n click: function($event) {\n return _vm.toggleConfigOption(\n \"scene\"\n )\n }\n }\n })\n ],\n 1\n )\n ])\n ]\n )\n ]\n )\n ])\n ]\n )\n : _vm._e()\n ])\n ])\n ]\n )\n ])\n ]\n ),\n _vm._v(\" \"),\n _c(\n \"div\",\n {\n staticClass: \"shadow shadow-background\",\n attrs: { id: \"episodes-controll-background\" }\n },\n [\n _vm.show\n ? _c(\n \"div\",\n {\n staticClass: \"row\",\n attrs: { id: \"row-show-episodes-controls\" }\n },\n [\n _c(\n \"div\",\n {\n staticClass: \"col-md-12\",\n attrs: { id: \"col-show-episodes-controls\" }\n },\n [\n _vm.type === \"show\"\n ? _c(\"div\", { staticClass: \"row key\" }, [\n _c(\n \"div\",\n {\n ref: \"checkboxControls\",\n staticClass: \"col-lg-12\",\n attrs: { id: \"checkboxControls\" }\n },\n [\n _vm.show.seasons\n ? _c(\n \"div\",\n {\n staticClass: \"pull-left top-5\",\n attrs: { id: \"key-padding\" }\n },\n _vm._l(_vm.overviewStatus, function(\n status\n ) {\n return _c(\n \"label\",\n {\n key: status.id,\n attrs: { for: status.id }\n },\n [\n _c(\"span\", { class: status.id }, [\n _c(\"input\", {\n directives: [\n {\n name: \"model\",\n rawName: \"v-model\",\n value: status.checked,\n expression: \"status.checked\"\n }\n ],\n attrs: {\n type: \"checkbox\",\n id: status.id\n },\n domProps: {\n checked: Array.isArray(\n status.checked\n )\n ? _vm._i(\n status.checked,\n null\n ) > -1\n : status.checked\n },\n on: {\n change: [\n function($event) {\n var $$a = status.checked,\n $$el = $event.target,\n $$c = $$el.checked\n ? true\n : false\n if (Array.isArray($$a)) {\n var $$v = null,\n $$i = _vm._i($$a, $$v)\n if ($$el.checked) {\n $$i < 0 &&\n _vm.$set(\n status,\n \"checked\",\n $$a.concat([$$v])\n )\n } else {\n $$i > -1 &&\n _vm.$set(\n status,\n \"checked\",\n $$a\n .slice(0, $$i)\n .concat(\n $$a.slice(\n $$i + 1\n )\n )\n )\n }\n } else {\n _vm.$set(\n status,\n \"checked\",\n $$c\n )\n }\n },\n function($event) {\n return _vm.$emit(\n \"update-overview-status\",\n _vm.overviewStatus\n )\n }\n ]\n }\n }),\n _vm._v(\n \"\\n \" +\n _vm._s(status.name) +\n \": \"\n ),\n _c(\"b\", [\n _vm._v(\n _vm._s(\n _vm.episodeSummary[\n status.name\n ]\n )\n )\n ])\n ])\n ]\n )\n }),\n 0\n )\n : _vm._e(),\n _vm._v(\" \"),\n _c(\n \"div\",\n { staticClass: \"pull-lg-right top-5\" },\n [\n _c(\n \"select\",\n {\n directives: [\n {\n name: \"model\",\n rawName: \"v-model\",\n value: _vm.selectedStatus,\n expression: \"selectedStatus\"\n }\n ],\n staticClass:\n \"form-control form-control-inline input-sm-custom input-sm-smallfont\",\n attrs: { id: \"statusSelect\" },\n on: {\n change: function($event) {\n var $$selectedVal = Array.prototype.filter\n .call(\n $event.target.options,\n function(o) {\n return o.selected\n }\n )\n .map(function(o) {\n var val =\n \"_value\" in o\n ? o._value\n : o.value\n return val\n })\n _vm.selectedStatus = $event.target\n .multiple\n ? $$selectedVal\n : $$selectedVal[0]\n }\n }\n },\n [\n _c(\n \"option\",\n {\n domProps: {\n value: \"Change status to:\"\n }\n },\n [_vm._v(\"Change status to:\")]\n ),\n _vm._v(\" \"),\n _vm._l(\n _vm.changeStatusOptions,\n function(status) {\n return _c(\n \"option\",\n {\n key: status.key,\n domProps: {\n value: status.value\n }\n },\n [\n _vm._v(\n \"\\n \" +\n _vm._s(status.name) +\n \"\\n \"\n )\n ]\n )\n }\n )\n ],\n 2\n ),\n _vm._v(\" \"),\n _c(\n \"select\",\n {\n directives: [\n {\n name: \"model\",\n rawName: \"v-model\",\n value: _vm.selectedQuality,\n expression: \"selectedQuality\"\n }\n ],\n staticClass:\n \"form-control form-control-inline input-sm-custom input-sm-smallfont\",\n attrs: { id: \"qualitySelect\" },\n on: {\n change: function($event) {\n var $$selectedVal = Array.prototype.filter\n .call(\n $event.target.options,\n function(o) {\n return o.selected\n }\n )\n .map(function(o) {\n var val =\n \"_value\" in o\n ? o._value\n : o.value\n return val\n })\n _vm.selectedQuality = $event.target\n .multiple\n ? $$selectedVal\n : $$selectedVal[0]\n }\n }\n },\n [\n _c(\n \"option\",\n {\n domProps: {\n value: \"Change quality to:\"\n }\n },\n [_vm._v(\"Change quality to:\")]\n ),\n _vm._v(\" \"),\n _vm._l(_vm.qualities, function(\n quality\n ) {\n return _c(\n \"option\",\n {\n key: quality.key,\n domProps: { value: quality.value }\n },\n [\n _vm._v(\n \"\\n \" +\n _vm._s(quality.name) +\n \"\\n \"\n )\n ]\n )\n })\n ],\n 2\n ),\n _vm._v(\" \"),\n _c(\"input\", {\n attrs: {\n type: \"hidden\",\n id: \"series-slug\"\n },\n domProps: { value: _vm.show.id.slug }\n }),\n _vm._v(\" \"),\n _c(\"input\", {\n attrs: {\n type: \"hidden\",\n id: \"series-id\"\n },\n domProps: {\n value: _vm.show.id[_vm.show.indexer]\n }\n }),\n _vm._v(\" \"),\n _c(\"input\", {\n attrs: { type: \"hidden\", id: \"indexer\" },\n domProps: { value: _vm.show.indexer }\n }),\n _vm._v(\" \"),\n _c(\"input\", {\n staticClass: \"btn-medusa\",\n attrs: {\n type: \"button\",\n id: \"changeStatus\",\n value: \"Go\"\n },\n on: { click: _vm.changeStatusClicked }\n })\n ]\n )\n ]\n )\n ])\n : _c(\"div\")\n ]\n )\n ]\n )\n : _vm._e()\n ]\n )\n ],\n 2\n )\n}\nvar staticRenderFns = []\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack://slim/./src/components/show-header.vue?./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib/index.js??vue-loader-options"); /***/ }), @@ -8493,6 +8514,16 @@ eval("// style-loader: Adds some css to the DOM by adding a diff --git a/themes/dark/assets/js/medusa-runtime.js b/themes/dark/assets/js/medusa-runtime.js index d14f7b2b74..d9080cec05 100644 --- a/themes/dark/assets/js/medusa-runtime.js +++ b/themes/dark/assets/js/medusa-runtime.js @@ -2628,7 +2628,7 @@ eval("// Imports\nvar ___CSS_LOADER_API_NO_SOURCEMAP_IMPORT___ = __webpack_requi \****************************************************************************************************************************************************************************************************************************************************************************************************/ /***/ ((module, __unused_webpack_exports, __webpack_require__) => { -eval("// Imports\nvar ___CSS_LOADER_API_NO_SOURCEMAP_IMPORT___ = __webpack_require__(/*! ../../../node_modules/css-loader/dist/runtime/noSourceMaps.js */ \"./node_modules/css-loader/dist/runtime/noSourceMaps.js\");\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\nvar ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(___CSS_LOADER_API_NO_SOURCEMAP_IMPORT___);\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, \"\\n.show-container[data-v-54b453c4] {\\n display: inline-block;\\n margin: 4px;\\n border-width: 5px;\\n border-style: solid;\\n overflow: hidden;\\n box-shadow: 1px 1px 3px 0 rgba(0, 0, 0, 0.31);\\n}\\n.show-dlstats[data-v-54b453c4] {\\n font-size: 11px;\\n text-align: left;\\n display: block;\\n margin-left: 4px;\\n}\\n.show-quality[data-v-54b453c4] {\\n font-size: 11px;\\n text-align: right;\\n display: block;\\n margin-right: 4px;\\n}\\n.posterview[data-v-54b453c4] {\\n margin: 0 auto;\\n position: relative;\\n}\\n\\n/* Used by isotope scaling */\\n.show-image[data-v-54b453c4] {\\n max-width: 100%;\\n overflow: hidden;\\n border: 1px solid rgb(136, 136, 136);\\n}\\n.background-image img[data-v-54b453c4] {\\n width: 100%;\\n overflow: hidden;\\n}\\n.poster-overlay[data-v-54b453c4] {\\n position: absolute;\\n}\\n.show-container .ui-progressbar[data-v-54b453c4] {\\n height: 7px !important;\\n top: -2px;\\n}\\n.show-container .ui-corner-all[data-v-54b453c4],\\n.ui-corner-bottom[data-v-54b453c4],\\n.ui-corner-right[data-v-54b453c4],\\n.ui-corner-br[data-v-54b453c4] {\\n border-bottom-right-radius: 0;\\n}\\n.show-container .ui-corner-all[data-v-54b453c4],\\n.ui-corner-bottom[data-v-54b453c4],\\n.ui-corner-left[data-v-54b453c4],\\n.ui-corner-bl[data-v-54b453c4] {\\n border-bottom-left-radius: 0;\\n}\\n.show-container .ui-corner-all[data-v-54b453c4],\\n.ui-corner-top[data-v-54b453c4],\\n.ui-corner-right[data-v-54b453c4],\\n.ui-corner-tr[data-v-54b453c4] {\\n border-top-right-radius: 0;\\n}\\n.show-container .ui-corner-all[data-v-54b453c4],\\n.ui-corner-top[data-v-54b453c4],\\n.ui-corner-left[data-v-54b453c4],\\n.ui-corner-tl[data-v-54b453c4] {\\n border-top-left-radius: 0;\\n}\\n.show-container .ui-widget-content[data-v-54b453c4] {\\n border-top: 1px solid rgb(17, 17, 17);\\n border-bottom: 1px solid rgb(17, 17, 17);\\n border-left: 0;\\n border-right: 0;\\n}\\n.ui-progressbar .progress-20[data-v-54b453c4] {\\n border: none;\\n}\\n.show-container .progress-20[data-v-54b453c4],\\n.show-container .progress-40[data-v-54b453c4],\\n.show-container .progress-60[data-v-54b453c4],\\n.show-container .progress-80[data-v-54b453c4] {\\n border-radius: 0;\\n height: 7px;\\n}\\n.overlay-container[data-v-54b453c4] {\\n display: flex;\\n align-items: baseline;\\n}\\n\", \"\"]);\n// Exports\nmodule.exports = ___CSS_LOADER_EXPORT___;\n\n\n//# sourceURL=webpack://slim/./src/components/show-list/poster.vue?./node_modules/css-loader/dist/cjs.js??clonedRuleSet-3%5B0%5D.rules%5B0%5D.use%5B1%5D!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/vue-loader/lib/index.js??vue-loader-options"); +eval("// Imports\nvar ___CSS_LOADER_API_NO_SOURCEMAP_IMPORT___ = __webpack_require__(/*! ../../../node_modules/css-loader/dist/runtime/noSourceMaps.js */ \"./node_modules/css-loader/dist/runtime/noSourceMaps.js\");\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\nvar ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(___CSS_LOADER_API_NO_SOURCEMAP_IMPORT___);\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, \"\\n.show-container[data-v-54b453c4] {\\n display: inline-block;\\n margin: 4px;\\n border-width: 5px;\\n border-style: solid;\\n overflow: hidden;\\n box-shadow: 1px 1px 3px 0 rgba(0, 0, 0, 0.31);\\n}\\n.show-dlstats[data-v-54b453c4] {\\n font-size: 11px;\\n text-align: left;\\n display: block;\\n margin-left: 4px;\\n}\\n.show-quality[data-v-54b453c4] {\\n font-size: 11px;\\n text-align: right;\\n display: block;\\n margin-right: 4px;\\n}\\n.posterview[data-v-54b453c4] {\\n margin: 0 auto;\\n position: relative;\\n}\\n\\n/* Used by isotope scaling */\\n.show-image[data-v-54b453c4] {\\n max-width: 100%;\\n overflow: hidden;\\n border: 1px solid rgb(136, 136, 136);\\n}\\n.background-image img[data-v-54b453c4] {\\n width: 100%;\\n overflow: hidden;\\n}\\n.poster-overlay[data-v-54b453c4] {\\n position: absolute;\\n top: 0;\\n left: 0;\\n}\\n.show-container .ui-progressbar[data-v-54b453c4] {\\n height: 7px !important;\\n top: -2px;\\n}\\n.show-container .ui-corner-all[data-v-54b453c4],\\n.ui-corner-bottom[data-v-54b453c4],\\n.ui-corner-right[data-v-54b453c4],\\n.ui-corner-br[data-v-54b453c4] {\\n border-bottom-right-radius: 0;\\n}\\n.show-container .ui-corner-all[data-v-54b453c4],\\n.ui-corner-bottom[data-v-54b453c4],\\n.ui-corner-left[data-v-54b453c4],\\n.ui-corner-bl[data-v-54b453c4] {\\n border-bottom-left-radius: 0;\\n}\\n.show-container .ui-corner-all[data-v-54b453c4],\\n.ui-corner-top[data-v-54b453c4],\\n.ui-corner-right[data-v-54b453c4],\\n.ui-corner-tr[data-v-54b453c4] {\\n border-top-right-radius: 0;\\n}\\n.show-container .ui-corner-all[data-v-54b453c4],\\n.ui-corner-top[data-v-54b453c4],\\n.ui-corner-left[data-v-54b453c4],\\n.ui-corner-tl[data-v-54b453c4] {\\n border-top-left-radius: 0;\\n}\\n.show-container .ui-widget-content[data-v-54b453c4] {\\n border-top: 1px solid rgb(17, 17, 17);\\n border-bottom: 1px solid rgb(17, 17, 17);\\n border-left: 0;\\n border-right: 0;\\n}\\n.ui-progressbar .progress-20[data-v-54b453c4] {\\n border: none;\\n}\\n.show-container .progress-20[data-v-54b453c4],\\n.show-container .progress-40[data-v-54b453c4],\\n.show-container .progress-60[data-v-54b453c4],\\n.show-container .progress-80[data-v-54b453c4] {\\n border-radius: 0;\\n height: 7px;\\n}\\n.overlay-container[data-v-54b453c4] {\\n display: block;\\n overflow: hidden;\\n position: relative;\\n}\\n\", \"\"]);\n// Exports\nmodule.exports = ___CSS_LOADER_EXPORT___;\n\n\n//# sourceURL=webpack://slim/./src/components/show-list/poster.vue?./node_modules/css-loader/dist/cjs.js??clonedRuleSet-3%5B0%5D.rules%5B0%5D.use%5B1%5D!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/vue-loader/lib/index.js??vue-loader-options"); /***/ }), diff --git a/themes/light/assets/js/medusa-runtime.js b/themes/light/assets/js/medusa-runtime.js index d14f7b2b74..d9080cec05 100644 --- a/themes/light/assets/js/medusa-runtime.js +++ b/themes/light/assets/js/medusa-runtime.js @@ -2628,7 +2628,7 @@ eval("// Imports\nvar ___CSS_LOADER_API_NO_SOURCEMAP_IMPORT___ = __webpack_requi \****************************************************************************************************************************************************************************************************************************************************************************************************/ /***/ ((module, __unused_webpack_exports, __webpack_require__) => { -eval("// Imports\nvar ___CSS_LOADER_API_NO_SOURCEMAP_IMPORT___ = __webpack_require__(/*! ../../../node_modules/css-loader/dist/runtime/noSourceMaps.js */ \"./node_modules/css-loader/dist/runtime/noSourceMaps.js\");\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\nvar ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(___CSS_LOADER_API_NO_SOURCEMAP_IMPORT___);\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, \"\\n.show-container[data-v-54b453c4] {\\n display: inline-block;\\n margin: 4px;\\n border-width: 5px;\\n border-style: solid;\\n overflow: hidden;\\n box-shadow: 1px 1px 3px 0 rgba(0, 0, 0, 0.31);\\n}\\n.show-dlstats[data-v-54b453c4] {\\n font-size: 11px;\\n text-align: left;\\n display: block;\\n margin-left: 4px;\\n}\\n.show-quality[data-v-54b453c4] {\\n font-size: 11px;\\n text-align: right;\\n display: block;\\n margin-right: 4px;\\n}\\n.posterview[data-v-54b453c4] {\\n margin: 0 auto;\\n position: relative;\\n}\\n\\n/* Used by isotope scaling */\\n.show-image[data-v-54b453c4] {\\n max-width: 100%;\\n overflow: hidden;\\n border: 1px solid rgb(136, 136, 136);\\n}\\n.background-image img[data-v-54b453c4] {\\n width: 100%;\\n overflow: hidden;\\n}\\n.poster-overlay[data-v-54b453c4] {\\n position: absolute;\\n}\\n.show-container .ui-progressbar[data-v-54b453c4] {\\n height: 7px !important;\\n top: -2px;\\n}\\n.show-container .ui-corner-all[data-v-54b453c4],\\n.ui-corner-bottom[data-v-54b453c4],\\n.ui-corner-right[data-v-54b453c4],\\n.ui-corner-br[data-v-54b453c4] {\\n border-bottom-right-radius: 0;\\n}\\n.show-container .ui-corner-all[data-v-54b453c4],\\n.ui-corner-bottom[data-v-54b453c4],\\n.ui-corner-left[data-v-54b453c4],\\n.ui-corner-bl[data-v-54b453c4] {\\n border-bottom-left-radius: 0;\\n}\\n.show-container .ui-corner-all[data-v-54b453c4],\\n.ui-corner-top[data-v-54b453c4],\\n.ui-corner-right[data-v-54b453c4],\\n.ui-corner-tr[data-v-54b453c4] {\\n border-top-right-radius: 0;\\n}\\n.show-container .ui-corner-all[data-v-54b453c4],\\n.ui-corner-top[data-v-54b453c4],\\n.ui-corner-left[data-v-54b453c4],\\n.ui-corner-tl[data-v-54b453c4] {\\n border-top-left-radius: 0;\\n}\\n.show-container .ui-widget-content[data-v-54b453c4] {\\n border-top: 1px solid rgb(17, 17, 17);\\n border-bottom: 1px solid rgb(17, 17, 17);\\n border-left: 0;\\n border-right: 0;\\n}\\n.ui-progressbar .progress-20[data-v-54b453c4] {\\n border: none;\\n}\\n.show-container .progress-20[data-v-54b453c4],\\n.show-container .progress-40[data-v-54b453c4],\\n.show-container .progress-60[data-v-54b453c4],\\n.show-container .progress-80[data-v-54b453c4] {\\n border-radius: 0;\\n height: 7px;\\n}\\n.overlay-container[data-v-54b453c4] {\\n display: flex;\\n align-items: baseline;\\n}\\n\", \"\"]);\n// Exports\nmodule.exports = ___CSS_LOADER_EXPORT___;\n\n\n//# sourceURL=webpack://slim/./src/components/show-list/poster.vue?./node_modules/css-loader/dist/cjs.js??clonedRuleSet-3%5B0%5D.rules%5B0%5D.use%5B1%5D!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/vue-loader/lib/index.js??vue-loader-options"); +eval("// Imports\nvar ___CSS_LOADER_API_NO_SOURCEMAP_IMPORT___ = __webpack_require__(/*! ../../../node_modules/css-loader/dist/runtime/noSourceMaps.js */ \"./node_modules/css-loader/dist/runtime/noSourceMaps.js\");\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\nvar ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(___CSS_LOADER_API_NO_SOURCEMAP_IMPORT___);\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, \"\\n.show-container[data-v-54b453c4] {\\n display: inline-block;\\n margin: 4px;\\n border-width: 5px;\\n border-style: solid;\\n overflow: hidden;\\n box-shadow: 1px 1px 3px 0 rgba(0, 0, 0, 0.31);\\n}\\n.show-dlstats[data-v-54b453c4] {\\n font-size: 11px;\\n text-align: left;\\n display: block;\\n margin-left: 4px;\\n}\\n.show-quality[data-v-54b453c4] {\\n font-size: 11px;\\n text-align: right;\\n display: block;\\n margin-right: 4px;\\n}\\n.posterview[data-v-54b453c4] {\\n margin: 0 auto;\\n position: relative;\\n}\\n\\n/* Used by isotope scaling */\\n.show-image[data-v-54b453c4] {\\n max-width: 100%;\\n overflow: hidden;\\n border: 1px solid rgb(136, 136, 136);\\n}\\n.background-image img[data-v-54b453c4] {\\n width: 100%;\\n overflow: hidden;\\n}\\n.poster-overlay[data-v-54b453c4] {\\n position: absolute;\\n top: 0;\\n left: 0;\\n}\\n.show-container .ui-progressbar[data-v-54b453c4] {\\n height: 7px !important;\\n top: -2px;\\n}\\n.show-container .ui-corner-all[data-v-54b453c4],\\n.ui-corner-bottom[data-v-54b453c4],\\n.ui-corner-right[data-v-54b453c4],\\n.ui-corner-br[data-v-54b453c4] {\\n border-bottom-right-radius: 0;\\n}\\n.show-container .ui-corner-all[data-v-54b453c4],\\n.ui-corner-bottom[data-v-54b453c4],\\n.ui-corner-left[data-v-54b453c4],\\n.ui-corner-bl[data-v-54b453c4] {\\n border-bottom-left-radius: 0;\\n}\\n.show-container .ui-corner-all[data-v-54b453c4],\\n.ui-corner-top[data-v-54b453c4],\\n.ui-corner-right[data-v-54b453c4],\\n.ui-corner-tr[data-v-54b453c4] {\\n border-top-right-radius: 0;\\n}\\n.show-container .ui-corner-all[data-v-54b453c4],\\n.ui-corner-top[data-v-54b453c4],\\n.ui-corner-left[data-v-54b453c4],\\n.ui-corner-tl[data-v-54b453c4] {\\n border-top-left-radius: 0;\\n}\\n.show-container .ui-widget-content[data-v-54b453c4] {\\n border-top: 1px solid rgb(17, 17, 17);\\n border-bottom: 1px solid rgb(17, 17, 17);\\n border-left: 0;\\n border-right: 0;\\n}\\n.ui-progressbar .progress-20[data-v-54b453c4] {\\n border: none;\\n}\\n.show-container .progress-20[data-v-54b453c4],\\n.show-container .progress-40[data-v-54b453c4],\\n.show-container .progress-60[data-v-54b453c4],\\n.show-container .progress-80[data-v-54b453c4] {\\n border-radius: 0;\\n height: 7px;\\n}\\n.overlay-container[data-v-54b453c4] {\\n display: block;\\n overflow: hidden;\\n position: relative;\\n}\\n\", \"\"]);\n// Exports\nmodule.exports = ___CSS_LOADER_EXPORT___;\n\n\n//# sourceURL=webpack://slim/./src/components/show-list/poster.vue?./node_modules/css-loader/dist/cjs.js??clonedRuleSet-3%5B0%5D.rules%5B0%5D.use%5B1%5D!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/vue-loader/lib/index.js??vue-loader-options"); /***/ }), From 19e33639971bc66b62497f84dafd4929d28fd964 Mon Sep 17 00:00:00 2001 From: p0psicles Date: Fri, 25 Feb 2022 17:00:42 +0100 Subject: [PATCH 76/86] Improve the show season updates. * Limit the amount of info pulled for checking if we need a season update. * Only pull the season that we need. --- medusa/indexers/base.py | 1 + medusa/indexers/imdb/api.py | 24 ++++++++++++++++-------- medusa/queues/show_queue.py | 2 +- medusa/show/show.py | 6 ------ medusa/tv/series.py | 5 ++++- 5 files changed, 22 insertions(+), 16 deletions(-) diff --git a/medusa/indexers/base.py b/medusa/indexers/base.py index 286a79047c..960beb1527 100644 --- a/medusa/indexers/base.py +++ b/medusa/indexers/base.py @@ -88,6 +88,7 @@ def __init__(self, self.config['banners_enabled'] = banners self.config['image_type'] = image_type self.config['actors_enabled'] = actors + self.config['limit_seasons'] = [] if self.config['debug_enabled']: warnings.warn('The debug argument to tvdbv2_api.__init__ will be removed in the next version. ' diff --git a/medusa/indexers/imdb/api.py b/medusa/indexers/imdb/api.py index 6f14a06818..dfcd2414d6 100644 --- a/medusa/indexers/imdb/api.py +++ b/medusa/indexers/imdb/api.py @@ -85,7 +85,7 @@ def __init__(self, *args, **kwargs): # pylint: disable=too-many-locals,too-many ('seriesname', 'base.title'), ('summary', 'plot.outline.text'), ('firstaired', 'year'), - # ('poster', 'base.image.url'), + ('poster', 'base.image.url'), ('show_url', 'base.id'), ('firstaired', 'base.seriesStartYear'), ('rating', 'ratings.rating'), @@ -240,7 +240,7 @@ def _get_show_by_id(self, imdb_id): # pylint: disable=unused-argument return OrderedDict({'series': mapped_results}) - def _get_episodes(self, imdb_id, detailed=True, *args, **kwargs): # pylint: disable=unused-argument + def _get_episodes(self, imdb_id, detailed=True, aired_season=None, *args, **kwargs): # pylint: disable=unused-argument """ Get all the episodes for a show by imdb id @@ -249,13 +249,17 @@ def _get_episodes(self, imdb_id, detailed=True, *args, **kwargs): # pylint: dis """ # Parse episode data log.debug('Getting all episodes of {0}', imdb_id) + get_ep_only = kwargs.get('get_ep_only') + + if aired_season: + aired_season = [aired_season] if not isinstance(aired_season, list) else aired_season series_id = imdb_id imdb_id = ImdbIdentifier(imdb_id).imdb_id try: - if not self[imdb_id]: - self._get_show_data(imdb_id) + # if not get_ep_only and not self[ImdbIdentifier(imdb_id).series_id]: + # self._get_show_data(imdb_id) # results = self.imdb_api.get_title_episodes(imdb_id) results = self.imdb_api.get_title_episodes(ImdbIdentifier(imdb_id).imdb_id) @@ -274,6 +278,9 @@ def _get_episodes(self, imdb_id, detailed=True, *args, **kwargs): # pylint: dis absolute_number_counter = 1 for season in results.get('seasons'): + if aired_season and season.get('season') not in aired_season: + continue + for episode in season['episodes']: season_no, episode_no = episode.get('season'), episode.get('episode') @@ -296,7 +303,7 @@ def _get_episodes(self, imdb_id, detailed=True, *args, **kwargs): # pylint: dis self._set_item(series_id, season_no, episode_no, k, v) - if detailed and season.get('season'): + if detailed and season.get('season') and not get_ep_only: # Enrich episode for the current season. self._get_episodes_detailed(imdb_id, season['season']) @@ -304,7 +311,8 @@ def _get_episodes(self, imdb_id, detailed=True, *args, **kwargs): # pylint: dis self._enrich_episodes(imdb_id, season['season']) # Try to calculate the airs day of week - self._calc_airs_day_of_week(imdb_id) + if not get_ep_only: + self._calc_airs_day_of_week(imdb_id) def _calc_airs_day_of_week(self, imdb_id): series_id = ImdbIdentifier(imdb_id).series_id @@ -659,7 +667,7 @@ def _get_show_data(self, imdb_id, language='en'): # pylint: disable=too-many-br # get episode data if self.config['episodes_enabled']: - self._get_episodes(imdb_id) + self._get_episodes(imdb_id, aired_season=self.config['limit_seasons']) # Parse banners if self.config['banners_enabled']: @@ -709,7 +717,7 @@ def get_last_updated_seasons(self, show_list=None, cache=None, *args, **kwargs): total_updates = [] # A small api call to get the amount of known seasons - self._get_episodes(series_id, detailed=False) + self._get_episodes(series_id, detailed=False, get_ep_only=True) # Get all the seasons diff --git a/medusa/queues/show_queue.py b/medusa/queues/show_queue.py index 8f3694ff85..3fb89dd4e1 100644 --- a/medusa/queues/show_queue.py +++ b/medusa/queues/show_queue.py @@ -1052,7 +1052,7 @@ def run(self): try: # Let's make sure we refresh the indexer_api object attached to the show object. self.show.create_indexer() - self.show.load_from_indexer() + self.show.load_from_indexer(limit_seasons=self.seasons) except IndexerError as error: log.warning( '{id}: Unable to contact {indexer}. Aborting: {error_msg}', diff --git a/medusa/show/show.py b/medusa/show/show.py index 8bc999294b..92d3c70359 100644 --- a/medusa/show/show.py +++ b/medusa/show/show.py @@ -132,12 +132,6 @@ def find_by_id(series, indexer_id, series_id): except ValueError: indexer_id = indexer_name_to_id(indexer_id) - try: - if indexer_id != 10: # 10 = EXTERNAL_IMDB - series_id = int(series_id) - except ValueError: - log.warning('Invalid series id: {series_id}', {'series_id': series_id}) - if series_id is None or series is None or len(series) == 0: return None diff --git a/medusa/tv/series.py b/medusa/tv/series.py index 11517cdec5..65bf17b5d5 100644 --- a/medusa/tv/series.py +++ b/medusa/tv/series.py @@ -1584,7 +1584,7 @@ def _load_from_db(self): self.reset_dirty() return True - def load_from_indexer(self, tvapi=None): + def load_from_indexer(self, tvapi=None, limit_seasons=None): """Load show from indexer. :param tvapi: @@ -1600,6 +1600,9 @@ def load_from_indexer(self, tvapi=None): ) indexer_api = tvapi or self.indexer_api + if limit_seasons: + self.indexer_api.config['limit_seasons'] = limit_seasons + indexed_show = indexer_api[self.series_id] if getattr(indexed_show, 'firstaired', ''): From 6db36ff741297c30bf05cdda5e15e6db5f1835e3 Mon Sep 17 00:00:00 2001 From: p0psicles Date: Fri, 25 Feb 2022 17:05:19 +0100 Subject: [PATCH 77/86] Also applied to the other indexers --- medusa/indexers/tmdb/api.py | 2 +- medusa/indexers/tvdbv2/api.py | 2 +- medusa/indexers/tvmaze/api.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/medusa/indexers/tmdb/api.py b/medusa/indexers/tmdb/api.py index b58c200877..d496c85437 100644 --- a/medusa/indexers/tmdb/api.py +++ b/medusa/indexers/tmdb/api.py @@ -538,7 +538,7 @@ def _get_show_data(self, tmdb_id, language='en'): # get episode data if self.config['episodes_enabled']: - self._get_episodes(tmdb_id, specials=False, aired_season=None) + self._get_episodes(tmdb_id, specials=False, aired_season=self.config['limit_seasons']) # Parse banners if self.config['banners_enabled']: diff --git a/medusa/indexers/tvdbv2/api.py b/medusa/indexers/tvdbv2/api.py index d83274bb01..0123a60dbe 100644 --- a/medusa/indexers/tvdbv2/api.py +++ b/medusa/indexers/tvdbv2/api.py @@ -563,7 +563,7 @@ def _get_show_data(self, sid, language): # get episode data if self.config['episodes_enabled']: - self._get_episodes(sid, specials=False, aired_season=None) + self._get_episodes(sid, specials=False, aired_season=self.config['limit_seasons']) # Parse banners if self.config['banners_enabled']: diff --git a/medusa/indexers/tvmaze/api.py b/medusa/indexers/tvmaze/api.py index ff6133a446..dba31f7b58 100644 --- a/medusa/indexers/tvmaze/api.py +++ b/medusa/indexers/tvmaze/api.py @@ -219,7 +219,7 @@ def _get_show_by_id(self, tvmaze_id, request_language='en'): # pylint: disable= mapped_results = self._map_results(results, self.series_map) return OrderedDict({'series': mapped_results}) - def _get_episodes(self, tvmaze_id, specials=False, aired_season=None): # pylint: disable=unused-argument + def _get_episodes(self, tvmaze_id, specials=False, *args, **kwargs): # pylint: disable=unused-argument """ Get all the episodes for a show by tvmaze id. @@ -465,7 +465,7 @@ def _get_show_data(self, tvmaze_id, language='en'): # get episode data if self.config['episodes_enabled']: - self._get_episodes(tvmaze_id, specials=False, aired_season=None) + self._get_episodes(tvmaze_id, specials=False) # Parse banners if self.config['banners_enabled']: From aaefba027b3fa30dbb24ec111b0f20498805e685 Mon Sep 17 00:00:00 2001 From: p0psicles Date: Sat, 26 Feb 2022 10:18:06 +0100 Subject: [PATCH 78/86] Refactored the get_last_updated_seasons method. --- medusa/__main__.py | 2 +- medusa/indexers/imdb/api.py | 66 +++++++++++++++++++++---------------- 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/medusa/__main__.py b/medusa/__main__.py index 7080320ff4..ae74024910 100755 --- a/medusa/__main__.py +++ b/medusa/__main__.py @@ -428,7 +428,7 @@ def start(self, args): trim_history() # # Check for metadata indexer updates for shows (Disabled until we use api) - # app.show_update_scheduler.forceRun() + app.show_update_scheduler.forceRun() # Launch browser if app.LAUNCH_BROWSER and not (self.no_launch or self.run_as_daemon): diff --git a/medusa/indexers/imdb/api.py b/medusa/indexers/imdb/api.py index dfcd2414d6..f14bcb7b92 100644 --- a/medusa/indexers/imdb/api.py +++ b/medusa/indexers/imdb/api.py @@ -249,7 +249,6 @@ def _get_episodes(self, imdb_id, detailed=True, aired_season=None, *args, **kwar """ # Parse episode data log.debug('Getting all episodes of {0}', imdb_id) - get_ep_only = kwargs.get('get_ep_only') if aired_season: aired_season = [aired_season] if not isinstance(aired_season, list) else aired_season @@ -258,9 +257,6 @@ def _get_episodes(self, imdb_id, detailed=True, aired_season=None, *args, **kwar imdb_id = ImdbIdentifier(imdb_id).imdb_id try: - # if not get_ep_only and not self[ImdbIdentifier(imdb_id).series_id]: - # self._get_show_data(imdb_id) - # results = self.imdb_api.get_title_episodes(imdb_id) results = self.imdb_api.get_title_episodes(ImdbIdentifier(imdb_id).imdb_id) except (LookupError, IndexerShowNotFound) as error: @@ -303,7 +299,7 @@ def _get_episodes(self, imdb_id, detailed=True, aired_season=None, *args, **kwar self._set_item(series_id, season_no, episode_no, k, v) - if detailed and season.get('season') and not get_ep_only: + if detailed and season.get('season'): # Enrich episode for the current season. self._get_episodes_detailed(imdb_id, season['season']) @@ -311,8 +307,7 @@ def _get_episodes(self, imdb_id, detailed=True, aired_season=None, *args, **kwar self._enrich_episodes(imdb_id, season['season']) # Try to calculate the airs day of week - if not get_ep_only: - self._calc_airs_day_of_week(imdb_id) + self._calc_airs_day_of_week(imdb_id) def _calc_airs_day_of_week(self, imdb_id): series_id = ImdbIdentifier(imdb_id).series_id @@ -361,7 +356,6 @@ def _get_episodes_detailed(self, imdb_id, season): """ try: - # results = self.imdb_api.get_title_episodes(imdb_id) results = self.imdb_api.get_title_episodes_detailed(imdb_id=ImdbIdentifier(imdb_id).imdb_id, season=season) except LookupError as error: raise IndexerShowIncomplete( @@ -709,32 +703,48 @@ def get_last_updated_seasons(self, show_list=None, cache=None, *args, **kwargs): for series_id in show_list: series_obj = Show.find_by_id(app.showList, self.indexer, series_id) - all_episodes = series_obj.get_all_episodes() - - if not all_episodes: - continue + all_episodes_local = series_obj.get_all_episodes() total_updates = [] - + results = None # A small api call to get the amount of known seasons - self._get_episodes(series_id, detailed=False, get_ep_only=True) + try: + results = self.imdb_api.get_title_episodes(ImdbIdentifier(series_id).imdb_id) + except (LookupError, IndexerShowNotFound) as error: + raise IndexerShowIncomplete( + 'Show episode search exception, ' + 'could not get any episodes. Exception: {e!r}'.format( + e=error + ) + ) + except RequestException as error: + raise IndexerUnavailable('Error connecting to Imdb api. Caused by: {0!r}'.format(error)) + + if not results or not results.get('seasons'): + continue # Get all the seasons # Loop through seasons - for season in self[series_id]: + for season in results['seasons']: + season_number = season.get('season') + + # Imdb api gives back a season without the 'season' key. This season has special episodes. + # Dont know what this is, but skipping it. + if not season_number: + continue # Check if the season is already known in our local db. - season_episodes = [ep for ep in all_episodes if ep.season == season] - if not season_episodes: - if len(self[series_id][season]): - total_updates.append(season) - log.debug('{series}: Season {season} seems to be a new season. Adding it.', - {'series': series_obj.name, 'season': season}) - continue + local_season_episodes = [ep for ep in all_episodes_local if ep.season == season_number] + remote_season_episodes = season['episodes'] + if not local_season_episodes or len(remote_season_episodes) != len(local_season_episodes): + total_updates.append(season_number) + log.debug('{series}: Season {season} seems to be a new season. Adding it.', + {'series': series_obj.name, 'season': season_number}) + continue # Per season, get latest episode airdate - sorted_episodes = sorted(season_episodes, key=lambda x: x.airdate) + sorted_episodes = sorted(local_season_episodes, key=lambda x: x.airdate) # date_season_start = sorted_episodes[0].airdate date_season_last = sorted_episodes[-1].airdate @@ -744,20 +754,20 @@ def get_last_updated_seasons(self, show_list=None, cache=None, *args, **kwargs): update_interval = self._calc_update_interval( # date_season_start, date_season_last, - season_finished=bool(self[series_id].get(season + 1)) + season_finished=bool([s for s in results['seasons'] if s.get('season') == season_number +1]) ) - last_update = cache.get_last_update_season(self.indexer, series_id, season) + last_update = cache.get_last_update_season(self.indexer, series_id, season_number) if last_update < time() - update_interval: # This season should be updated. - total_updates.append(season) + total_updates.append(season_number) # Update last_update for this season. - cache.set_last_update_season(self.indexer, series_id, season) + cache.set_last_update_season(self.indexer, series_id, season_number) else: log.debug( '{series}: Season {season} seems to have been recently updated. Not scheduling a new refresh', - {'series': series_obj.name, 'season': season} + {'series': series_obj.name, 'season': season_number} ) show_season_updates[series_id] = list(set(total_updates)) From f68957b007f228a0b541ab68e59b72ffe3fc9ce0 Mon Sep 17 00:00:00 2001 From: p0psicles Date: Sat, 26 Feb 2022 10:18:28 +0100 Subject: [PATCH 79/86] comment --- medusa/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/medusa/__main__.py b/medusa/__main__.py index ae74024910..7080320ff4 100755 --- a/medusa/__main__.py +++ b/medusa/__main__.py @@ -428,7 +428,7 @@ def start(self, args): trim_history() # # Check for metadata indexer updates for shows (Disabled until we use api) - app.show_update_scheduler.forceRun() + # app.show_update_scheduler.forceRun() # Launch browser if app.LAUNCH_BROWSER and not (self.no_launch or self.run_as_daemon): From 5c273af185ce49778207b87e5a4201e9bda1745e Mon Sep 17 00:00:00 2001 From: p0psicles Date: Mon, 28 Feb 2022 19:39:43 +0100 Subject: [PATCH 80/86] Fix imdb id parse error --- medusa/providers/torrent/json/eztv.py | 1 - 1 file changed, 1 deletion(-) diff --git a/medusa/providers/torrent/json/eztv.py b/medusa/providers/torrent/json/eztv.py index f6b60050d5..adca6cc017 100644 --- a/medusa/providers/torrent/json/eztv.py +++ b/medusa/providers/torrent/json/eztv.py @@ -63,7 +63,6 @@ def search(self, search_strings, age=0, ep_obj=None, **kwargs): if mode != 'RSS': imdb_id = self.series.externals.get(mappings[10]) if imdb_id: - imdb_id = imdb_id[2:] # strip two tt's of id as they are not used search_params['imdb_id'] = imdb_id log.debug('Search string (IMDb ID): {imdb_id}', {'imdb_id': imdb_id}) else: From 1cc83c834d7dbb559ae6fed06e54f15b0e2d12bc Mon Sep 17 00:00:00 2001 From: p0psicles Date: Mon, 28 Feb 2022 19:54:23 +0100 Subject: [PATCH 81/86] Fix diverse imdb id mapping --- medusa/helpers/trakt.py | 8 ++++++-- medusa/providers/torrent/html/sdbits.py | 2 ++ medusa/providers/torrent/json/eztv.py | 2 ++ medusa/schedulers/trakt_checker.py | 5 +++-- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/medusa/helpers/trakt.py b/medusa/helpers/trakt.py index 9bd4b22491..64734192b3 100644 --- a/medusa/helpers/trakt.py +++ b/medusa/helpers/trakt.py @@ -3,6 +3,7 @@ import logging from medusa.helpers import get_title_without_year +from medusa.indexers.imdb.api import ImdbIdentifier from medusa.logger.adapters.style import BraceAdapter from requests.exceptions import RequestException @@ -70,8 +71,11 @@ def create_show_structure(show_obj): 'ids': {} } for valid_trakt_id in ['tvdb_id', 'trakt_id', 'tmdb_id', 'imdb_id']: - if show_obj.externals.get(valid_trakt_id): - show['ids'][valid_trakt_id[:-3]] = show_obj.externals.get(valid_trakt_id) + external = show_obj.externals.get(valid_trakt_id) + if external: + if valid_trakt_id == 'imdb_id': + external = ImdbIdentifier(external).imdb_id + show['ids'][valid_trakt_id[:-3]] = external return show diff --git a/medusa/providers/torrent/html/sdbits.py b/medusa/providers/torrent/html/sdbits.py index 177291f956..1f5f3eddbd 100644 --- a/medusa/providers/torrent/html/sdbits.py +++ b/medusa/providers/torrent/html/sdbits.py @@ -13,6 +13,7 @@ convert_size, try_int, ) +from medusa.indexers.imdb.api import ImdbIdentifier from medusa.indexers.utils import mappings from medusa.logger.adapters.style import BraceAdapter from medusa.providers.torrent.torrent_provider import TorrentProvider @@ -83,6 +84,7 @@ def search(self, search_strings, age=0, ep_obj=None, **kwargs): if mode != 'RSS': imdb_id = self.series.externals.get(mappings[10]) if imdb_id: + imdb_id = ImdbIdentifier(imdb_id).imdb_id search_params['imdb'] = imdb_id log.debug('Search string (IMDb ID): {imdb_id}', {'imdb_id': imdb_id}) diff --git a/medusa/providers/torrent/json/eztv.py b/medusa/providers/torrent/json/eztv.py index adca6cc017..cc218e19df 100644 --- a/medusa/providers/torrent/json/eztv.py +++ b/medusa/providers/torrent/json/eztv.py @@ -8,6 +8,7 @@ from medusa import tv from medusa.helper.common import convert_size +from medusa.indexers.imdb.api import ImdbIdentifier from medusa.indexers.utils import mappings from medusa.logger.adapters.style import BraceAdapter from medusa.providers.torrent.torrent_provider import TorrentProvider @@ -63,6 +64,7 @@ def search(self, search_strings, age=0, ep_obj=None, **kwargs): if mode != 'RSS': imdb_id = self.series.externals.get(mappings[10]) if imdb_id: + imdb_id = ImdbIdentifier(imdb_id).imdb_id search_params['imdb_id'] = imdb_id log.debug('Search string (IMDb ID): {imdb_id}', {'imdb_id': imdb_id}) else: diff --git a/medusa/schedulers/trakt_checker.py b/medusa/schedulers/trakt_checker.py index 9b48cafc3e..fee5d0325b 100644 --- a/medusa/schedulers/trakt_checker.py +++ b/medusa/schedulers/trakt_checker.py @@ -15,6 +15,7 @@ from medusa.helpers.externals import show_in_library from medusa.helpers.trakt import create_episode_structure, create_show_structure, get_trakt_user from medusa.indexers.config import EXTERNAL_IMDB, EXTERNAL_TRAKT, indexerConfig +from medusa.indexers.imdb.api import ImdbIdentifier from medusa.indexers.utils import get_trakt_indexer from medusa.logger.adapters.style import BraceAdapter from medusa.search.queue import BacklogQueueItem @@ -488,7 +489,7 @@ def remove_from_library(self): continue try: - trakt_show = tv.TVShow(str(trakt_id or show.imdb_id)) + trakt_show = tv.TVShow(str(trakt_id or ImdbIdentifier(show.imdb_id).imdb_id)) progress = trakt_show.progress except (TraktException, RequestException) as error: log.info("Unable to check if show '{show}' is ended/completed. Error: {error!r}", { @@ -683,7 +684,7 @@ def match_trakt_by_id(trakt_show, medusa_show): if trakt_supported_indexer and getattr(trakt_show, trakt_supported_indexer) == medusa_show.indexerid: return True # Try to match by imdb_id - if getattr(trakt_show, 'imdb') == medusa_show.imdb_id: + if getattr(trakt_show, 'imdb') == ImdbIdentifier(medusa_show.imdb_id).imdb_id: return True return False From e23ebf6ce84504865778879025681bc95b313aa8 Mon Sep 17 00:00:00 2001 From: p0psicles Date: Mon, 28 Feb 2022 19:58:30 +0100 Subject: [PATCH 82/86] Add login response check --- medusa/providers/torrent/html/morethantv.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/medusa/providers/torrent/html/morethantv.py b/medusa/providers/torrent/html/morethantv.py index 7bdefc3374..726f3bfb61 100644 --- a/medusa/providers/torrent/html/morethantv.py +++ b/medusa/providers/torrent/html/morethantv.py @@ -1,4 +1,4 @@ -# coding=utf-8 + # coding=utf-8 """Provider code for MoreThanTV.""" @@ -191,6 +191,10 @@ def login(self): # Get the login page, to retrieve the token response = self.session.get(self.urls['login']) + if not response: + log.warning('Unable to get login page') + return False + token = re.search(r'token".value="([^"]+)"', response.text) if not token: log.warning('Unable to get login token') From b3d7a8de4b73b72b0ee22ddcb1c06d5e44de39f7 Mon Sep 17 00:00:00 2001 From: p0psicles Date: Mon, 28 Feb 2022 20:24:04 +0100 Subject: [PATCH 83/86] Added imdb exception handling --- medusa/indexers/imdb/api.py | 79 +++++++++++++++++++++++++++++-------- 1 file changed, 63 insertions(+), 16 deletions(-) diff --git a/medusa/indexers/imdb/api.py b/medusa/indexers/imdb/api.py index f14bcb7b92..7dd7d77ec4 100644 --- a/medusa/indexers/imdb/api.py +++ b/medusa/indexers/imdb/api.py @@ -163,7 +163,16 @@ def _show_search(self, series): :return: A list of Show objects.series_map """ - results = self.imdb_api.search_for_title(series) + try: + results = self.imdb_api.search_for_title(series) + except LookupError as error: + raise IndexerShowNotFound('Could not get any results searching for {series} using indexer Imdb. Cause: {cause!r}'.format( + series=series, cause=error + )) + except (AttributeError, RequestException) as error: + raise IndexerUnavailable('Could not get any results searching for {series} using indexer Imdb. Cause: {cause!r}'.format( + series=series, cause=error + )) if results: return results @@ -186,7 +195,7 @@ def search(self, series): mapped_results.append(show_by_id['series']) return OrderedDict({'series': mapped_results})['series'] results = self._show_search(series) - except RequestException: + except IndexerShowNotFound: results = None if not results: @@ -204,9 +213,17 @@ def _get_show_by_id(self, imdb_id): # pylint: disable=unused-argument :return: An ordered dict with the show searched for. """ results = None - if imdb_id: - log.debug('Getting all show data for {0}', imdb_id) + log.debug('Getting all show data for {0}', imdb_id) + try: results = self.imdb_api.get_title(ImdbIdentifier(imdb_id).imdb_id) + except LookupError as error: + raise IndexerShowNotFound('Could not find show {imdb_id} using indexer Imdb. Cause: {cause!r}'.format( + imdb_id=imdb_id, cause=error + )) + except (AttributeError, RequestException) as error: + raise IndexerUnavailable('Could not find show {imdb_id} using indexer Imdb. Cause: {cause!r}'.format( + imdb_id=imdb_id, cause=error + )) if not results: return @@ -216,8 +233,19 @@ def _get_show_by_id(self, imdb_id): # pylint: disable=unused-argument if not mapped_results: return - # Get firstaired - releases = self.imdb_api.get_title_releases(ImdbIdentifier(imdb_id).imdb_id) + + try: + # Get firstaired + releases = self.imdb_api.get_title_releases(ImdbIdentifier(imdb_id).imdb_id) + except LookupError as error: + raise IndexerShowNotFound('Could not find show {imdb_id} using indexer Imdb. Cause: {cause!r}'.format( + imdb_id=imdb_id, cause=error + )) + except (AttributeError, RequestException) as error: + raise IndexerUnavailable('Could not get title releases for show {imdb_id} using indexer Imdb. Cause: {cause!r}'.format( + imdb_id=imdb_id, cause=error + )) + if releases.get('releases'): first_released = sorted([r['date'] for r in releases['releases']])[0] mapped_results['firstaired'] = first_released @@ -235,7 +263,7 @@ def _get_show_by_id(self, imdb_id): # pylint: disable=unused-argument if first_release: mapped_results['network'] = first_release[0]['company']['name'] - except LookupError: + except (AttributeError, LookupError, RequestException): log.info('No company data available for {0}, cant get a network', imdb_id) return OrderedDict({'series': mapped_results}) @@ -259,14 +287,14 @@ def _get_episodes(self, imdb_id, detailed=True, aired_season=None, *args, **kwar try: # results = self.imdb_api.get_title_episodes(imdb_id) results = self.imdb_api.get_title_episodes(ImdbIdentifier(imdb_id).imdb_id) - except (LookupError, IndexerShowNotFound) as error: + except LookupError as error: raise IndexerShowIncomplete( 'Show episode search exception, ' 'could not get any episodes. Exception: {e!r}'.format( e=error ) ) - except RequestException as error: + except (AttributeError, RequestException) as error: raise IndexerUnavailable('Error connecting to Imdb api. Caused by: {0!r}'.format(error)) if not results or not results.get('seasons'): @@ -357,7 +385,7 @@ def _get_episodes_detailed(self, imdb_id, season): try: results = self.imdb_api.get_title_episodes_detailed(imdb_id=ImdbIdentifier(imdb_id).imdb_id, season=season) - except LookupError as error: + except (AttributeError, LookupError, RequestException) as error: raise IndexerShowIncomplete( 'Show episode search exception, ' 'could not get any episodes. Exception: {e!r}'.format( @@ -446,7 +474,17 @@ def _parse_images(self, imdb_id, language='en'): """ log.debug('Getting show banners for {0}', imdb_id) - images = self.imdb_api.get_title_images(ImdbIdentifier(imdb_id).imdb_id) + try: + images = self.imdb_api.get_title_images(ImdbIdentifier(imdb_id).imdb_id) + except LookupError as error: + raise IndexerShowNotFound('Could not find show {imdb_id} using indexer Imdb. Cause: {cause!r}'.format( + imdb_id=imdb_id, cause=error + )) + except (AttributeError, RequestException) as error: + raise IndexerUnavailable('Could not get images for show {imdb_id} using indexer Imdb. Cause: {cause!r}'.format( + imdb_id=imdb_id, cause=error + )) + image_mapping = {'poster': 'poster', 'production_art': 'fanart'} # Removed 'still_frame': 'fanart', thumb_height = 640 @@ -616,7 +654,16 @@ def _parse_actors(self, imdb_id): """ log.debug('Getting actors for {0}', imdb_id) - actors = self.imdb_api.get_title_credits(ImdbIdentifier(imdb_id).imdb_id) + try: + actors = self.imdb_api.get_title_credits(ImdbIdentifier(imdb_id).imdb_id) + except LookupError as error: + raise IndexerShowNotFound('Could not find show {imdb_id} using indexer Imdb. Cause: {cause!r}'.format( + imdb_id=imdb_id, cause=error + )) + except (AttributeError, RequestException) as error: + raise IndexerUnavailable('Could not get actors for show {imdb_id} using indexer Imdb. Cause: {cause!r}'.format( + imdb_id=imdb_id, cause=error + )) if not actors.get('credits') or not actors['credits'].get('cast'): return @@ -710,14 +757,14 @@ def get_last_updated_seasons(self, show_list=None, cache=None, *args, **kwargs): # A small api call to get the amount of known seasons try: results = self.imdb_api.get_title_episodes(ImdbIdentifier(series_id).imdb_id) - except (LookupError, IndexerShowNotFound) as error: + except LookupError as error: raise IndexerShowIncomplete( 'Show episode search exception, ' - 'could not get any episodes. Exception: {e!r}'.format( - e=error + 'could not get any episodes. Exception: {error!r}'.format( + error=error ) ) - except RequestException as error: + except (AttributeError, RequestException) as error: raise IndexerUnavailable('Error connecting to Imdb api. Caused by: {0!r}'.format(error)) if not results or not results.get('seasons'): From 5e8096dfe7d82512826ff8196a0d38a7f4f3a461 Mon Sep 17 00:00:00 2001 From: p0psicles Date: Tue, 1 Mar 2022 13:59:27 +0100 Subject: [PATCH 84/86] Fix flake warnings --- medusa/databases/cache_db.py | 20 ++++---- medusa/databases/main_db.py | 2 - medusa/databases/recommended_db.py | 6 ++- medusa/indexers/base.py | 5 -- medusa/indexers/imdb/api.py | 75 ++++++++++++++++-------------- 5 files changed, 56 insertions(+), 52 deletions(-) diff --git a/medusa/databases/cache_db.py b/medusa/databases/cache_db.py index 44d9a02f63..00972be837 100644 --- a/medusa/databases/cache_db.py +++ b/medusa/databases/cache_db.py @@ -16,10 +16,14 @@ # Add new migrations at the bottom of the list # and subclass the previous migration. class InitialSchema(db.SchemaUpgrade): + """Cache.db initial schema class.""" + def test(self): + """Test db version.""" return self.hasTable('db_version') def execute(self): + """Execute.""" queries = [ ('CREATE TABLE lastUpdate (provider TEXT, time NUMERIC);',), ('CREATE TABLE lastSearch (provider TEXT, time NUMERIC);',), @@ -233,15 +237,15 @@ def execute(self): class AddSeasonUpdatesTable(RemoveSceneExceptionsTable): # pylint:disable=too-many-ancestors def test(self): - return self.hasTable("season_updates") + return self.hasTable('season_updates') def execute(self): self.connection.action( - '''CREATE TABLE "season_updates" ( - `season_updates_id` INTEGER, - `indexer` INTEGER NOT NULL, - `series_id` INTEGER NOT NULL, - `season` INTEGER, - `time` INTEGER, - PRIMARY KEY(season_updates_id))''' + """CREATE TABLE "season_updates" ( + `season_updates_id` INTEGER, + `indexer` INTEGER NOT NULL, + `series_id` INTEGER NOT NULL, + `season` INTEGER, + `time` INTEGER, + PRIMARY KEY(season_updates_id))""" ) diff --git a/medusa/databases/main_db.py b/medusa/databases/main_db.py index 49793b41ec..be3354fda9 100644 --- a/medusa/databases/main_db.py +++ b/medusa/databases/main_db.py @@ -8,11 +8,9 @@ import warnings from medusa import common, db, subtitles -from medusa.helper.common import dateTimeFormat, episode_num from medusa.databases import utils from medusa.helper.common import dateTimeFormat from medusa.indexers.config import STATUS_MAP -from medusa.indexers.imdb.api import ImdbIdentifier from medusa.logger.adapters.style import BraceAdapter from medusa.name_parser.parser import NameParser diff --git a/medusa/databases/recommended_db.py b/medusa/databases/recommended_db.py index 796b4b2daf..d0d33b887f 100644 --- a/medusa/databases/recommended_db.py +++ b/medusa/databases/recommended_db.py @@ -12,15 +12,19 @@ log.logger.addHandler(logging.NullHandler()) - class RecommendedSanityCheck(db.DBSanityCheck): + """Sanity check class.""" + def check(self): + """Check functions.""" self.remove_imdb_tt() def remove_imdb_tt(self): + """Remove tt from imdb id's.""" log.debug(u'Remove shows added with an incorrect imdb id.') self.connection.action("DELETE FROM shows WHERE source = 10 AND series_id like '%tt%'") + # Add new migrations at the bottom of the list # and subclass the previous migration. class InitialSchema(db.SchemaUpgrade): diff --git a/medusa/indexers/base.py b/medusa/indexers/base.py index 960beb1527..91d5d22ff1 100644 --- a/medusa/indexers/base.py +++ b/medusa/indexers/base.py @@ -450,15 +450,10 @@ def get_last_updated_series(self, *args, **kwargs): """Retrieve a list with updated shows.""" raise IndexerShowUpdatesNotSupported('Method get_last_updated_series not implemented by this indexer') - def get_last_updated_seasons(self, *args, **kwargs): """Retrieve a list with updated show seasons.""" raise IndexerSeasonUpdatesNotSupported('Method get_last_updated_series not implemented by this indexer') - def get_episodes_for_season(self, show_id, *args, **kwargs): - self._get_episodes(show_id, *args, **kwargs) - return self.shows[show_id] - class ShowContainer(dict): """Simple dict that holds a series of Show instances.""" diff --git a/medusa/indexers/imdb/api.py b/medusa/indexers/imdb/api.py index 7dd7d77ec4..6bd28e5fa6 100644 --- a/medusa/indexers/imdb/api.py +++ b/medusa/indexers/imdb/api.py @@ -1,13 +1,17 @@ # coding=utf-8 +"""Imdb indexer api module.""" from __future__ import unicode_literals +import locale +import logging +from collections import OrderedDict, namedtuple from datetime import datetime from itertools import chain -import logging -from collections import namedtuple, OrderedDict +from time import time + from imdbpie import imdbpie -import locale + from medusa import app from medusa.bs4_parser import BS4Parser from medusa.indexers.base import (Actor, Actors, BaseIndexer) @@ -16,18 +20,22 @@ ) from medusa.logger.adapters.style import BraceAdapter from medusa.show.show import Show -from six import integer_types, string_types, text_type -from time import time + from requests.exceptions import RequestException +from six import string_types, text_type + log = BraceAdapter(logging.getLogger(__name__)) log.logger.addHandler(logging.NullHandler()) class ImdbIdentifier(object): + """Imdb identifier class.""" + def __init__(self, imdb_id): """Initialize an identifier object. Can be used to get the full textual id e.a. 'tt3986523'. + Or the series_id: 3986523 """ self._imdb_id = None @@ -40,18 +48,22 @@ def _clean(self, imdb_id): @property def series_id(self): + """Return series id.""" return self._series_id @series_id.setter def series_id(self, value): + """Set series id.""" self._series_id = value @property def imdb_id(self): + """Return imdb id.""" return self._imdb_id @imdb_id.setter def imdb_id(self, value): + """Set imdb id.""" if isinstance(value, string_types) and 'tt' in value: self._imdb_id = self._clean(value) self.series_id = int(self._imdb_id.split('tt')[-1]) @@ -61,13 +73,15 @@ def imdb_id(self, value): class Imdb(BaseIndexer): - """Create easy-to-use interface to name of season/episode name + """Create easy-to-use interface to name of season/episode name. + >>> indexer_api = imdb() >>> indexer_api['Scrubs'][1][24]['episodename'] u'My Last Day' """ def __init__(self, *args, **kwargs): # pylint: disable=too-many-locals,too-many-arguments + """Imdb constructor.""" super(Imdb, self).__init__(*args, **kwargs) self.indexer = 10 @@ -157,12 +171,11 @@ def _map_results(self, imdb_response, key_mappings=None, list_separator='|'): def _show_search(self, series): """ - Uses the Imdb API to search for a show - :param series: The series name that's searched for as a string + Use the Imdb API to search for a show. + :param series: The series name that's searched for as a string :return: A list of Show objects.series_map """ - try: results = self.imdb_api.search_for_title(series) except LookupError as error: @@ -180,7 +193,7 @@ def _show_search(self, series): return None def search(self, series): - """This searches imdb.com for the series name + """Search imdb.com for the series name. :param series: the query for the series name :return: An ordered dict with the show searched for. In the format of OrderedDict{"series": [list of shows]} @@ -206,8 +219,7 @@ def search(self, series): return OrderedDict({'series': mapped_results})['series'] def _get_show_by_id(self, imdb_id): # pylint: disable=unused-argument - """ - Retrieve imdb show information by imdb id, or if no imdb id provided by passed external id. + """Retrieve imdb show information by imdb id, or if no imdb id provided by passed external id. :param imdb_id: The shows imdb id :return: An ordered dict with the show searched for. @@ -225,7 +237,7 @@ def _get_show_by_id(self, imdb_id): # pylint: disable=unused-argument imdb_id=imdb_id, cause=error )) - if not results: + if not results: return mapped_results = self._map_results(results, self.series_map) @@ -233,7 +245,6 @@ def _get_show_by_id(self, imdb_id): # pylint: disable=unused-argument if not mapped_results: return - try: # Get firstaired releases = self.imdb_api.get_title_releases(ImdbIdentifier(imdb_id).imdb_id) @@ -269,8 +280,7 @@ def _get_show_by_id(self, imdb_id): # pylint: disable=unused-argument return OrderedDict({'series': mapped_results}) def _get_episodes(self, imdb_id, detailed=True, aired_season=None, *args, **kwargs): # pylint: disable=unused-argument - """ - Get all the episodes for a show by imdb id + """Get all the episodes for a show by imdb id. :param imdb_id: Series imdb id. :return: An ordered dict with the show searched for. In the format of OrderedDict{"episode": [list of episodes]} @@ -312,7 +322,7 @@ def _get_episodes(self, imdb_id, detailed=True, aired_season=None, *args, **kwar log.debug('{0}: Found incomplete episode with season: {1!r} and episode: {2!r})', imdb_id, season_no, episode_no) continue # Skip to next episode - + if season_no > 0: episode['absolute_number'] = absolute_number_counter absolute_number_counter += 1 @@ -376,13 +386,11 @@ def _parse_date_with_local(date, template, locale_format='C', method='strptime') locale.setlocale(locale.LC_TIME, lc) def _get_episodes_detailed(self, imdb_id, season): - """ - Enrich the episodes with additional information for a specific season. + """Enrich the episodes with additional information for a specific season. :param imdb_id: imdb id including the `tt`. :param season: season passed as integer. """ - try: results = self.imdb_api.get_title_episodes_detailed(imdb_id=ImdbIdentifier(imdb_id).imdb_id, season=season) except (AttributeError, LookupError, RequestException) as error: @@ -413,14 +421,12 @@ def _get_episodes_detailed(self, imdb_id, season): self._set_item(series_id, season, episode['episodeNumber'], 'votes', episode['ratingCount']) def _enrich_episodes(self, imdb_id, season): - """ - Enrich the episodes with additional information for a specific season. + """Enrich the episodes with additional information for a specific season. For this we're making use of html scraping using beautiful soup. :param imdb_id: imdb id including the `tt`. :param season: season passed as integer. """ - episodes_url = 'http://www.imdb.com/title/{imdb_id}/episodes?season={season}' episodes = [] @@ -564,13 +570,10 @@ def _save_images(self, series_id, images, language='en'): res: resolution such as `1024x768`, `original`, etc id: the image id """ - # Get desired image types from images - image_types = 'banner', 'fanart', 'poster' - def by_aspect_ratio(image): w, h = image['bannertype2'].split('x') return int(w) / int(h) - + # Parse Posters and Banners (by aspect ratio) if images.get('poster'): # Flatten image_type[res][id].values() into list of values @@ -598,7 +601,7 @@ def by_aspect_ratio(image): img_url = highest_rated['_bannerpath'] log.debug( u'Selecting poster with the lowest aspect ratio (resolution={resolution})\n' - 'aspect ratio of {aspect_ratio} ', { + 'aspect ratio of {aspect_ratio} ', { 'resolution': highest_rated['bannertype2'], 'aspect_ratio': by_aspect_ratio(highest_rated) } @@ -610,13 +613,12 @@ def by_aspect_ratio(image): img_url = highest_rated['_bannerpath'] log.debug( u'Selecting poster with the lowest aspect ratio (resolution={resolution})\n' - 'aspect ratio of {aspect_ratio} ', { + 'aspect ratio of {aspect_ratio} ', { 'resolution': highest_rated['bannertype2'], 'aspect_ratio': by_aspect_ratio(highest_rated) } ) self._set_show_data(series_id, 'banner', img_url) - if images.get('fanart'): # Flatten image_type[res][id].values() into list of values @@ -637,7 +639,7 @@ def by_aspect_ratio(image): img_url = highest_rated['_bannerpath'] log.debug( u'Selecting poster with the lowest aspect ratio (resolution={resolution})\n' - 'aspect ratio of {aspect_ratio} ', { + 'aspect ratio of {aspect_ratio} ', { 'resolution': highest_rated['bannertype2'], 'aspect_ratio': by_aspect_ratio(highest_rated) } @@ -667,7 +669,7 @@ def _parse_actors(self, imdb_id): if not actors.get('credits') or not actors['credits'].get('cast'): return - + cur_actors = Actors() for order, cur_actor in enumerate(actors['credits']['cast'][:25]): save_actor = Actor() @@ -680,8 +682,9 @@ def _parse_actors(self, imdb_id): self._set_show_data(imdb_id, '_actors', cur_actors) def _get_show_data(self, imdb_id, language='en'): # pylint: disable=too-many-branches,too-many-statements,too-many-locals - """Takes a series ID, gets the epInfo URL and parses the imdb json response - into the shows dict in layout: + """Get show data by imdb id. + + Take a series ID, gets the epInfo URL and parses the imdb json response into the shows dict in a format: shows[series_id][season_number][episode_number] """ # Parse show information @@ -787,7 +790,7 @@ def get_last_updated_seasons(self, show_list=None, cache=None, *args, **kwargs): if not local_season_episodes or len(remote_season_episodes) != len(local_season_episodes): total_updates.append(season_number) log.debug('{series}: Season {season} seems to be a new season. Adding it.', - {'series': series_obj.name, 'season': season_number}) + {'series': series_obj.name, 'season': season_number}) continue # Per season, get latest episode airdate @@ -801,7 +804,7 @@ def get_last_updated_seasons(self, show_list=None, cache=None, *args, **kwargs): update_interval = self._calc_update_interval( # date_season_start, date_season_last, - season_finished=bool([s for s in results['seasons'] if s.get('season') == season_number +1]) + season_finished=bool([s for s in results['seasons'] if s.get('season') == season_number + 1]) ) last_update = cache.get_last_update_season(self.indexer, series_id, season_number) From 763289e41637289759120b65b177a24f00474d61 Mon Sep 17 00:00:00 2001 From: p0psicles Date: Tue, 1 Mar 2022 14:31:51 +0100 Subject: [PATCH 85/86] Refactor and fix flake --- medusa/__main__.py | 2 +- medusa/classes.py | 9 +-- medusa/indexers/imdb/exceptions.py | 54 ++++++----------- medusa/indexers/tmdb/api.py | 15 +++-- medusa/indexers/tmdb/exceptions.py | 56 ++++++----------- medusa/indexers/tvdbv2/api.py | 67 +++++++++++---------- medusa/indexers/tvdbv2/exceptions.py | 22 +++---- medusa/indexers/tvmaze/api.py | 6 +- medusa/indexers/tvmaze/exceptions.py | 54 ++++++----------- medusa/providers/torrent/html/morethantv.py | 3 +- medusa/schedulers/show_updater.py | 13 +++- setup.cfg | 1 + 12 files changed, 131 insertions(+), 171 deletions(-) diff --git a/medusa/__main__.py b/medusa/__main__.py index 7080320ff4..e3ba7ad89e 100755 --- a/medusa/__main__.py +++ b/medusa/__main__.py @@ -1202,7 +1202,7 @@ def initialize(self, console_logging=True): recommended_db_con = db.DBConnection('recommended.db') db.upgradeDatabase(recommended_db_con, recommended_db.InitialSchema) db.sanityCheckDatabase(recommended_db_con, recommended_db.RecommendedSanityCheck) - + # Performs a vacuum on cache.db logger.debug(u'Performing a vacuum on the CACHE database') cache_db_con.action('VACUUM') diff --git a/medusa/classes.py b/medusa/classes.py index e14bddcd27..d179749701 100644 --- a/medusa/classes.py +++ b/medusa/classes.py @@ -22,7 +22,7 @@ from datetime import datetime from dateutil import parser -from trans import trans + from medusa import app, ws from medusa.common import ( @@ -36,6 +36,8 @@ from six import itervalues +from trans import trans + log = BraceAdapter(logging.getLogger(__name__)) log.logger.addHandler(logging.NullHandler()) @@ -373,16 +375,15 @@ def searchterm_in_result(search_term, search_result): if norm_search_term in norm_result: return True - + # translates national characters into similar sounding latin characters # For ex. Физрук -> Fizruk search_term_alpha = trans(self.config['searchterm']) if search_term_alpha != search_term and search_term_alpha in norm_result: return True - - return False + return False # get all available shows if all_series: diff --git a/medusa/indexers/imdb/exceptions.py b/medusa/indexers/imdb/exceptions.py index 87df4b6b7c..ce7494e1c5 100644 --- a/medusa/indexers/imdb/exceptions.py +++ b/medusa/indexers/imdb/exceptions.py @@ -21,55 +21,37 @@ __author__ = 'p0psicles' __version__ = '1.0' -__all__ = ['imdb_error', 'imdb_userabort', 'imdb_shownotfound', 'imdb_showincomplete', - 'imdb_seasonnotfound', 'imdb_episodenotfound', 'imdb_attributenotfound'] +__all__ = ['ImdbException', 'ImdbError', 'ImdbUserAbort', 'ImdbShowNotFound', 'ImdbShowIncomplete', + 'ImdbSeasonNotFound', 'ImdbEpisodeNotFound', 'ImdbAttributeNotFound'] -class imdb_exception(Exception): - """Any exception generated by imdb_api - """ - pass +class ImdbException(Exception): + """Any exception generated by imdb_api.""" -class imdb_error(imdb_exception): - """An error with the indexer (Cannot connect, for example) - """ - pass +class ImdbError(ImdbException): + """An error with the indexer (Cannot connect, for example).""" -class imdb_userabort(imdb_exception): - """User aborted the interactive selection (via - the q command, ^c etc) - """ - pass +class ImdbUserAbort(ImdbException): + """User aborted the interactive selection (via the q command, ^c etc).""" -class imdb_shownotfound(imdb_exception): - """Show cannot be found on the indexer (non-existant show) - """ - pass +class ImdbShowNotFound(ImdbException): + """Show cannot be found on the indexer (non-existant show).""" -class imdb_showincomplete(imdb_exception): - """Show found but incomplete on the indexer (incomplete show) - """ - pass +class ImdbShowIncomplete(ImdbException): + """Show found but incomplete on the indexer (incomplete show).""" -class imdb_seasonnotfound(imdb_exception): - """Season cannot be found on indexer. - """ - pass +class ImdbSeasonNotFound(ImdbException): + """Season cannot be found on indexer.""" -class imdb_episodenotfound(imdb_exception): - """Episode cannot be found on the indexer - """ - pass +class ImdbEpisodeNotFound(ImdbException): + """Episode cannot be found on the indexer.""" -class imdb_attributenotfound(imdb_exception): - """Raised if an episode does not have the requested - attribute (such as a episode name) - """ - pass +class ImdbAttributeNotFound(ImdbException): + """Raised if an episode does not have the requested attribute (such as a episode name).""" diff --git a/medusa/indexers/tmdb/api.py b/medusa/indexers/tmdb/api.py index d496c85437..3da02a0edc 100644 --- a/medusa/indexers/tmdb/api.py +++ b/medusa/indexers/tmdb/api.py @@ -176,6 +176,7 @@ def _show_search(self, show, request_language='en'): def search(self, series): """Search TMDB (themoviedb.org) for the series name. + :param series: The query for the series name :return: An ordered dict with the show searched for. In the format of OrderedDict{"series": [list of shows]} """ @@ -220,7 +221,7 @@ def get_show_country_codes(self, tmdb_id): def _get_show_by_id(self, tmdb_id, request_language='en', extra_info=None): """Retrieve tmdb show information by tmdb id. - + :param tmdb_id: The show's tmdb id :param request_language: Language to get the show in :type request_language: string or unicode @@ -549,7 +550,7 @@ def _get_show_data(self, tmdb_id, language='en'): self._parse_actors(tmdb_id) return True - + def _get_series_season_updates(self, tmdb_id, start_date=None, end_date=None): """ Retrieve all updates (show,season,episode) from TMDB. @@ -675,10 +676,12 @@ def get_id_by_external(self, **kwargs): if result.get('tv_results') and result['tv_results'][0]: # Get the external id's for the passed shows id. externals = self.tmdb.TV(result['tv_results'][0]['id']).external_ids() - externals = {tmdb_external_id: external_value - for tmdb_external_id, external_value - in viewitems(externals) - if external_value and tmdb_external_id in wanted_externals} + externals = { + tmdb_external_id: external_value + for tmdb_external_id, external_value + in viewitems(externals) + if external_value and tmdb_external_id in wanted_externals + } externals['tmdb_id'] = result['tv_results'][0]['id'] if 'imdb_id' in externals: externals['imdb_id'] = ImdbIdentifier(externals['imdb_id']).series_id diff --git a/medusa/indexers/tmdb/exceptions.py b/medusa/indexers/tmdb/exceptions.py index b17df3d1cb..b30b130e95 100644 --- a/medusa/indexers/tmdb/exceptions.py +++ b/medusa/indexers/tmdb/exceptions.py @@ -16,61 +16,43 @@ # You should have received a copy of the GNU General Public License # along with Medusa. If not, see . -"""Custom exceptions used or raised by tvdbv2_api.""" +"""Custom exceptions used or raised by tmdb api.""" from __future__ import unicode_literals __author__ = 'p0psicles' __version__ = '1.0' -__all__ = ['tvdbv2_error', 'tvdbv2_userabort', 'tvdbv2_shownotfound', 'tvdbv2_showincomplete', - 'tvdbv2_seasonnotfound', 'tvdbv2_episodenotfound', 'tvdbv2_attributenotfound'] +__all__ = ['TmdbException', 'TmdbError', 'TmdbUserAbort', 'TmdbShowNotFound', 'TmdbShowIncomplete', + 'TmdbSeasonNotFound', 'TmdbEpisodeNotFound', 'TmdbAttributeNotFound'] -class tvdbv2_exception(Exception): - """Any exception generated by tvdbv2_api - """ - pass +class TmdbException(Exception): + """Any exception generated by tvdbv2_api.""" -class tvdbv2_error(tvdbv2_exception): - """An error with thetvdb.com (Cannot connect, for example) - """ - pass +class TmdbError(TmdbException): + """An error with thetvdb.com (Cannot connect, for example).""" -class tvdbv2_userabort(tvdbv2_exception): - """User aborted the interactive selection (via - the q command, ^c etc) - """ - pass +class TmdbUserAbort(TmdbException): + """User aborted the interactive selection (via the q command, ^c etc).""" -class tvdbv2_shownotfound(tvdbv2_exception): - """Show cannot be found on thetvdb.com (non-existant show) - """ - pass +class TmdbShowNotFound(TmdbException): + """Show cannot be found on thetvdb.com (non-existant show).""" -class tvdbv2_showincomplete(tvdbv2_exception): - """Show found but incomplete on thetvdb.com (incomplete show) - """ - pass +class TmdbShowIncomplete(TmdbException): + """Show found but incomplete on thetvdb.com (incomplete show).""" -class tvdbv2_seasonnotfound(tvdbv2_exception): - """Season cannot be found on thetvdb.com - """ - pass +class TmdbSeasonNotFound(TmdbException): + """Season cannot be found on thetvdb.com.""" -class tvdbv2_episodenotfound(tvdbv2_exception): - """Episode cannot be found on thetvdb.com - """ - pass +class TmdbEpisodeNotFound(TmdbException): + """Episode cannot be found on thetvdb.com.""" -class tvdbv2_attributenotfound(tvdbv2_exception): - """Raised if an episode does not have the requested - attribute (such as a episode name) - """ - pass +class TmdbAttributeNotFound(TmdbException): + """Raised if an episode does not have the requested attribute (such as a episode name).""" diff --git a/medusa/indexers/tvdbv2/api.py b/medusa/indexers/tvdbv2/api.py index 0123a60dbe..80f5b1d152 100644 --- a/medusa/indexers/tvdbv2/api.py +++ b/medusa/indexers/tvdbv2/api.py @@ -11,20 +11,17 @@ from medusa.helper.metadata import needs_metadata from medusa.indexers.base import (Actor, Actors, BaseIndexer) from medusa.indexers.exceptions import ( - IndexerAuthFailed, IndexerError, IndexerException, - IndexerShowIncomplete, IndexerShowNotFound, - IndexerShowNotFoundInLanguage, IndexerUnavailable + IndexerAuthFailed, IndexerError, + IndexerShowNotFound, IndexerShowNotFoundInLanguage, IndexerUnavailable ) from medusa.indexers.imdb.api import ImdbIdentifier -from medusa.indexers.ui import BaseUI, ConsoleUI from medusa.indexers.tvdbv2.fallback import PlexFallback from medusa.logger.adapters.style import BraceAdapter from medusa.show.show import Show from requests.compat import urljoin -from requests.exceptions import RequestException -from six import string_types, text_type, viewitems +from six import string_types, viewitems from tvdbapiv2 import ApiClient, EpisodesApi, SearchApi, SeriesApi, UpdatesApi from tvdbapiv2.exceptions import ApiException @@ -178,6 +175,7 @@ def search(self, series): @PlexFallback def _get_show_by_id(self, tvdbv2_id, request_language='en'): # pylint: disable=unused-argument """Retrieve tvdbv2 show information by tvdbv2 id, or if no tvdbv2 id provided by passed external id. + :param tvdbv2_id: The shows tvdbv2 id :return: An ordered dict with the show searched for. """ @@ -244,6 +242,7 @@ def _get_episodes_info(self, tvdb_id, episodes, season=None): @PlexFallback def _query_series(self, tvdb_id, specials=False, aired_season=None, full_info=False): """Query against episodes for the given series. + :param tvdb_id: tvdb series id. :param specials: enable/disable download of specials. Currently not used. :param aired_season: the episodes returned for a specific aired season. @@ -381,9 +380,9 @@ def _parse_episodes(self, tvdb_id, episode_data): self._set_item(tvdb_id, seas_no, ep_no, k, v) @PlexFallback - def _parse_images(self, sid): - """Parse images XML. - From http://thetvdb.com/api/[APIKEY]/series/[SERIES ID]/banners.xml + def _parse_images(self, tvdb_id): + """Fetch and parse images from api. + images are retrieved using t['show name]['_banners'], for example: >>> indexer_api = Tvdb(images = True) >>> indexer_api['scrubs']['_banners'].keys() @@ -405,16 +404,16 @@ def _parse_images(self, sid): search_for_image_type = self.config['image_type'] - log.debug('Getting show banners for {0}', sid) + log.debug('Getting show banners for {0}', tvdb_id) _images = {} # Let's get the different types of images available for this series try: series_images_count = self.config['session'].series_api.series_id_images_get( - sid, accept_language=self.config['language'] + tvdb_id, accept_language=self.config['language'] ) except ApiException as error: - log.info('Could not get image count for show ID: {0} with reason: {1}', sid, error.reason) + log.info('Could not get image count for show ID: {0} with reason: {1}', tvdb_id, error.reason) return for image_type, image_count in viewitems(self._map_results(series_images_count)): @@ -428,12 +427,12 @@ def _parse_images(self, sid): try: images = self.config['session'].series_api.series_id_images_query_get( - sid, key_type=image_type, accept_language=self.config['language'] + tvdb_id, key_type=image_type, accept_language=self.config['language'] ) except ApiException as error: log.debug( - 'Could not parse {image} for show ID: {sid}, with exception: {reason}', - {'image': image_type, 'sid': sid, 'reason': error.reason} + 'Could not parse {image} for show ID: {tvdb_id}, with exception: {reason}', + {'image': image_type, 'tvdb_id': tvdb_id, 'reason': error.reason} ) continue @@ -475,13 +474,13 @@ def _parse_images(self, sid): base_path[k] = v - self._save_images(sid, _images) - self._set_show_data(sid, '_banners', _images) + self._save_images(tvdb_id, _images) + self._set_show_data(tvdb_id, '_banners', _images) @PlexFallback - def _parse_actors(self, sid): - """Parser actors XML. - From http://thetvdb.com/api/[APIKEY]/series/[SERIES ID]/actors.xml + def _parse_actors(self, tvdb_id): + """Fetch and parse actors. + Actors are retrieved using t['show name]['_actors'], for example: >>> indexer_api = Tvdb(actors = True) >>> actors = indexer_api['scrubs']['_actors'] @@ -500,12 +499,12 @@ def _parse_actors(self, sid): Any key starting with an underscore has been processed (not the raw data from the XML) """ - log.debug('Getting actors for {0}', sid) + log.debug('Getting actors for {0}', tvdb_id) try: - actors = self.config['session'].series_api.series_id_actors_get(sid) + actors = self.config['session'].series_api.series_id_actors_get(tvdb_id) except ApiException as error: - log.info('Could not get actors for show ID: {0} with reason: {1}', sid, error.reason) + log.info('Could not get actors for show ID: {0} with reason: {1}', tvdb_id, error.reason) return if not actors or not actors.data: @@ -521,10 +520,11 @@ def _parse_actors(self, sid): new_actor['role'] = cur_actor.role new_actor['sortorder'] = 0 cur_actors.append(new_actor) - self._set_show_data(sid, '_actors', cur_actors) + self._set_show_data(tvdb_id, '_actors', cur_actors) + + def _get_show_data(self, tvdb_id, language): + """Get the show data using tvdb id.. - def _get_show_data(self, sid, language): - """Parse TheTVDB json response. Takes a series ID, gets the epInfo URL and parses the TheTVDB json response into the shows dict in layout: shows[series_id][season_number][episode_number] @@ -542,10 +542,10 @@ def _get_show_data(self, sid, language): get_show_in_language = self.config['language'] # Parse show information - log.debug('Getting all series data for {0}', sid) + log.debug('Getting all series data for {0}', tvdb_id) # Parse show information - series_info = self._get_show_by_id(sid, request_language=get_show_in_language) + series_info = self._get_show_by_id(tvdb_id, request_language=get_show_in_language) if not series_info: log.debug('Series result returned zero') @@ -556,22 +556,22 @@ def _get_show_data(self, sid, language): if v is not None: if v and k in ['banner', 'fanart', 'poster']: v = self.config['artwork_prefix'].format(image=v) - self._set_show_data(sid, k, v) + self._set_show_data(tvdb_id, k, v) # Create the externals structure - self._set_show_data(sid, 'externals', {'imdb_id': ImdbIdentifier(getattr(self[sid], 'imdb_id', None)).series_id}) + self._set_show_data(tvdb_id, 'externals', {'imdb_id': ImdbIdentifier(getattr(self[tvdb_id], 'imdb_id', None)).series_id}) # get episode data if self.config['episodes_enabled']: - self._get_episodes(sid, specials=False, aired_season=self.config['limit_seasons']) + self._get_episodes(tvdb_id, specials=False, aired_season=self.config['limit_seasons']) # Parse banners if self.config['banners_enabled']: - self._parse_images(sid) + self._parse_images(tvdb_id) # Parse actors if self.config['actors_enabled']: - self._parse_actors(sid) + self._parse_actors(tvdb_id) return True @@ -579,6 +579,7 @@ def _get_show_data(self, sid, language): @PlexFallback def get_last_updated_series(self, from_time, weeks=1, filter_show_list=None): """Retrieve a list with updated shows. + :param from_time: epoch timestamp, with the start date/time as int :param weeks: number of weeks to get updates for. :param filter_show_list: Optional list of show objects, to use for filtering the returned list. diff --git a/medusa/indexers/tvdbv2/exceptions.py b/medusa/indexers/tvdbv2/exceptions.py index 29d6531738..a43ffe8a3e 100644 --- a/medusa/indexers/tvdbv2/exceptions.py +++ b/medusa/indexers/tvdbv2/exceptions.py @@ -22,37 +22,37 @@ __author__ = 'p0psicles' __version__ = '1.0' -__all__ = ['Tvdb2Error', 'Tvdb2UserAbort', 'Tvdb2ShowNotFound', 'Tvdb2ShowIncomplete', - 'Tvdb2SeasonNotFound', 'Tvdb2EpisodeNotFound', 'Tvdb2AttributeNotFound'] +__all__ = ['TvdbError', 'TvdbUserAbort', 'TvdbShowNotFound', 'TvdbShowIncomplete', + 'TvdbSeasonNotFound', 'TvdbEpisodeNotFound', 'TvdbAttributeNotFound'] -class Tvdb2Exception(Exception): - """Any exception generated by Tvdb2Api.""" +class TvdbException(Exception): + """Any exception generated by TvdbApi.""" -class Tvdb2Error(Tvdb2Exception): +class TvdbError(TvdbException): """An error with thetvdb.com (Cannot connect, for example).""" -class Tvdb2UserAbort(Tvdb2Exception): +class TvdbUserAbort(TvdbException): """User aborted the interactive selection (via the q command, ^c etc).""" -class Tvdb2ShowNotFound(Tvdb2Exception): +class TvdbShowNotFound(TvdbException): """Show cannot be found on thetvdb.com (non-existant show).""" -class Tvdb2ShowIncomplete(Tvdb2Exception): +class TvdbShowIncomplete(TvdbException): """Show found but incomplete on thetvdb.com (incomplete show).""" -class Tvdb2SeasonNotFound(Tvdb2Exception): +class TvdbSeasonNotFound(TvdbException): """Season cannot be found on thetvdb.com.""" -class Tvdb2EpisodeNotFound(Tvdb2Exception): +class TvdbEpisodeNotFound(TvdbException): """Episode cannot be found on thetvdb.com.""" -class Tvdb2AttributeNotFound(Tvdb2Exception): +class TvdbAttributeNotFound(TvdbException): """Raised if an episode does not have the requested attribute (such as a episode name).""" diff --git a/medusa/indexers/tvmaze/api.py b/medusa/indexers/tvmaze/api.py index dba31f7b58..7277cff5af 100644 --- a/medusa/indexers/tvmaze/api.py +++ b/medusa/indexers/tvmaze/api.py @@ -1,4 +1,5 @@ # coding=utf-8 +"""Tvmaze indexer api module.""" from __future__ import unicode_literals @@ -7,13 +8,13 @@ from time import time from medusa.indexers.base import (Actor, Actors, BaseIndexer) -from medusa.indexers.imdb.api import ImdbIdentifier from medusa.indexers.exceptions import ( IndexerError, IndexerException, IndexerShowNotFound, IndexerUnavailable, ) +from medusa.indexers.imdb.api import ImdbIdentifier from medusa.logger.adapters.style import BraceAdapter from pytvmaze import TVMaze @@ -34,6 +35,7 @@ class TVmaze(BaseIndexer): """ def __init__(self, *args, **kwargs): # pylint: disable=too-many-locals,too-many-arguments + """Tvmaze api constructor.""" super(TVmaze, self).__init__(*args, **kwargs) # List of language from http://thetvmaze.com/api/0629B785CE550C8D/languages.xml @@ -380,7 +382,7 @@ def _parse_season_images(self, tvmaze_id): return _images def _parse_actors(self, tvmaze_id): - """Parsers actors XML, from http://thetvmaze.com/api/[APIKEY]/series/[SERIES ID]/actors.xml + """Parsers actors XML, from http://thetvmaze.com/api/[APIKEY]/series/[SERIES ID]/actors.xml. Actors are retrieved using t['show name]['_actors'], for example: >>> indexer_api = TVMaze(actors = True) diff --git a/medusa/indexers/tvmaze/exceptions.py b/medusa/indexers/tvmaze/exceptions.py index 0014e91d41..f0fa0c16bc 100644 --- a/medusa/indexers/tvmaze/exceptions.py +++ b/medusa/indexers/tvmaze/exceptions.py @@ -22,55 +22,37 @@ __author__ = 'p0psicles' __version__ = '1.0' -__all__ = ['tvdbv2_error', 'tvdbv2_userabort', 'tvdbv2_shownotfound', 'tvdbv2_showincomplete', - 'tvdbv2_seasonnotfound', 'tvdbv2_episodenotfound', 'tvdbv2_attributenotfound'] +__all__ = ['TvmazeException', 'TvmazeError', 'TvmazeUserAbort', 'TvmazeShowNotFound', 'TvmazeShowIncomplete', + 'TvmazeSeasonNotFound', 'TvmazeEpisodeNotFound', 'TvmazeAttributeNotFound'] -class tvmaze_exception(Exception): - """Any exception generated by tvmaze_api - """ - pass +class TvmazeException(Exception): + """Any exception generated by tvmaze_api.""" -class tvmaze_error(tvmaze_exception): - """An error with thetvdb.com (Cannot connect, for example) - """ - pass +class TvmazeError(TvmazeException): + """An error with thetvdb.com (Cannot connect, for example).""" -class tvmaze_userabort(tvmaze_exception): - """User aborted the interactive selection (via - the q command, ^c etc) - """ - pass +class TvmazeUserAbort(TvmazeException): + """User aborted the interactive selection (via the q command, ^c etc).""" -class tvmaze_shownotfound(tvmaze_exception): - """Show cannot be found on thetvdb.com (non-existant show) - """ - pass +class TvmazeShowNotFound(TvmazeException): + """Show cannot be found on thetvdb.com (non-existant show).""" -class tvmaze_showincomplete(tvmaze_exception): - """Show found but incomplete on thetvdb.com (incomplete show) - """ - pass +class TvmazeShowIncomplete(TvmazeException): + """Show found but incomplete on thetvdb.com (incomplete show).""" -class tvmaze_seasonnotfound(tvmaze_exception): - """Season cannot be found on thetvdb.com - """ - pass +class TvmazeSeasonNotFound(TvmazeException): + """Season cannot be found on thetvdb.com.""" -class tvmaze_episodenotfound(tvmaze_exception): - """Episode cannot be found on thetvdb.com - """ - pass +class TvmazeEpisodeNotFound(TvmazeException): + """Episode cannot be found on thetvdb.com.""" -class tvmaze_attributenotfound(tvmaze_exception): - """Raised if an episode does not have the requested - attribute (such as a episode name) - """ - pass +class TvmazeAttributeNotFound(TvmazeException): + """Raised if an episode does not have the requested attribute (such as a episode name).""" diff --git a/medusa/providers/torrent/html/morethantv.py b/medusa/providers/torrent/html/morethantv.py index 726f3bfb61..cec231d775 100644 --- a/medusa/providers/torrent/html/morethantv.py +++ b/medusa/providers/torrent/html/morethantv.py @@ -1,5 +1,4 @@ - # coding=utf-8 - +# coding=utf-8 """Provider code for MoreThanTV.""" from __future__ import unicode_literals diff --git a/medusa/schedulers/show_updater.py b/medusa/schedulers/show_updater.py index 94e639209a..f721283013 100644 --- a/medusa/schedulers/show_updater.py +++ b/medusa/schedulers/show_updater.py @@ -1,4 +1,5 @@ # coding=utf-8 +"""Show updater module.""" from __future__ import unicode_literals @@ -20,14 +21,17 @@ class ShowUpdater(object): + """Show updater class.""" + def __init__(self): + """Show updatere constructor.""" self.lock = threading.Lock() self.amActive = False self.session = MedusaSession() self.update_cache = UpdateCache() def run(self, force=False): - + """Start show updater.""" self.amActive = True refresh_shows = [] # A list of shows, that need to be refreshed season_updates = [] # A list of show seasons that have passed their next_update timestamp @@ -81,8 +85,8 @@ def run(self, force=False): ) except IndexerShowUpdatesNotSupported: logger.info('Could not get a list with updated shows from indexer {indexer_name},' - ' as this is not supported. Attempting a regular update for show: {show}', - indexer_name=indexer_name, show=show.name) + ' as this is not supported. Attempting a regular update for show: {show}', + indexer_name=indexer_name, show=show.name) show_updates_supported = False except IndexerUnavailable: logger.warning('Problem running show_updater, Indexer {indexer_name} seems to be having ' @@ -208,7 +212,10 @@ def run(self, force=False): class UpdateCache(db.DBConnection): + """Show updater update cache class.""" + def __init__(self): + """Show updater update cache constructor.""" super(UpdateCache, self).__init__('cache.db') def get_last_indexer_update(self, indexer): diff --git a/setup.cfg b/setup.cfg index 58a323210c..958417eb2f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -72,6 +72,7 @@ flake8-ignore = medusa/indexers/tvmaze/__init__.py D104 medusa/indexers/tvmaze/api.py D100 D102 D103 D202 D205 D400 D401 medusa/indexers/tvmaze/exceptions.py D200 D204 D205 D400 N801 + medusa/indexers/imdb/__init__.py D104 medusa/init/logconfig.py E305 medusa/logger/__init__.py D401 medusa/media/__init__.py D104 From 983f51b6bbd40678a4d6c9fb2622af2405a12541 Mon Sep 17 00:00:00 2001 From: p0psicles Date: Tue, 1 Mar 2022 14:41:38 +0100 Subject: [PATCH 86/86] Fix jest tests --- .../add-recommended.spec.js.snap | 88 ------------------- .../slim/test/specs/add-recommended.spec.js | 28 ------ 2 files changed, 116 deletions(-) delete mode 100644 themes-default/slim/test/specs/__snapshots__/add-recommended.spec.js.snap delete mode 100644 themes-default/slim/test/specs/add-recommended.spec.js diff --git a/themes-default/slim/test/specs/__snapshots__/add-recommended.spec.js.snap b/themes-default/slim/test/specs/__snapshots__/add-recommended.spec.js.snap deleted file mode 100644 index 58231725f5..0000000000 --- a/themes-default/slim/test/specs/__snapshots__/add-recommended.spec.js.snap +++ /dev/null @@ -1,88 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`AddRecommended.test.js renders 1`] = ` -
    - -
    -
    -
    - -
    -

    - Add From Trakt Lists -

    - -

    - For shows that you haven't downloaded yet, this option lets you choose from a show from one of the Trakt lists to add to Medusa . -

    -
    - - - -
    -
    -
    - -
    -

    - Add From IMDB's Popular Shows -

    - -

    - View IMDB's list of the most popular shows. This feature uses IMDB's MOVIEMeter algorithm to identify popular TV Shows. -

    -
    - - - -
    -
    -
    - -
    -

    - Add From Anidb's Hot Anime list -

    - -

    - View Anidb's list of the most popular anime shows. Anidb provides lists for Popular Anime, using the "Hot Anime" list. -

    -
    - -
    -`; diff --git a/themes-default/slim/test/specs/add-recommended.spec.js b/themes-default/slim/test/specs/add-recommended.spec.js deleted file mode 100644 index 814b331ef5..0000000000 --- a/themes-default/slim/test/specs/add-recommended.spec.js +++ /dev/null @@ -1,28 +0,0 @@ -import Vuex, { Store } from 'vuex'; -import VueRouter from 'vue-router'; -import { createLocalVue, shallowMount } from '@vue/test-utils'; -import { AppLink, AddRecommended } from '../../src/components'; -import fixtures from '../__fixtures__/common'; - -describe('AddRecommended.test.js', () => { - let wrapper; - - beforeEach(() => { - const localVue = createLocalVue(); - localVue.use(Vuex); - localVue.use(VueRouter); - localVue.component('app-link', AppLink); - - const { state } = fixtures; - const store = new Store({ state }); - - wrapper = shallowMount(AddRecommended, { - localVue, - store - }); - }); - - it('renders', () => { - expect(wrapper.element).toMatchSnapshot(); - }); -});