class FileListCookie(Base): __tablename__ = 'filelist_cookie' username = Column(Unicode, primary_key=True) _cookie = Column('cookie', Unicode) cookie = json_synonym('_cookie') expires = Column(DateTime)
class Variables(Base): __tablename__ = 'variables' id = Column(Integer, primary_key=True) _variables = Column('variables', Unicode) variables = json_synonym('_variables') added = Column(DateTime, default=datetime.now)
class PassThePopcornCookie(Base): __tablename__ = 'passthepopcorn_cookie' username = Column(Unicode, primary_key=True) _cookie = Column('cookie', Unicode) cookie = json_synonym('_cookie') expires = Column(DateTime)
class MoreThanTVCookie(Base): __tablename__ = 'morethantv_cookie' username = Column(Unicode, primary_key=True) _cookie = Column('cookie', Unicode) cookie = json_synonym('_cookie') expires = Column(DateTime)
class HEBitsCookies(Base): __tablename__ = 'hebits_cookies' user_name: str = Column(Unicode, primary_key=True) _cookies = Column('cookies', Unicode) cookies: dict = json_synonym('_cookies') expires: datetime = Column(DateTime)
class AlphaRatioCookie(Base): __tablename__ = 'alpharatio_cookie' username = Column(Unicode, primary_key=True) _cookie = Column('cookie', Unicode) cookie = json_synonym('_cookie') expires = Column(DateTime)
class Secrets(Base): __tablename__ = 'secrets' id = Column(Integer, primary_key=True) _secrets = Column('secrets', Unicode) secrets = json_synonym('_secrets') added = Column(DateTime, default=datetime.now)
class GazelleSession(Base): __tablename__ = 'gazelle_session' username = Column(Unicode, primary_key=True) base_url = Column(String, primary_key=True) authkey = Column(String) passkey = Column(String) _cookies = Column('cookie', Unicode) cookies = json_synonym('_cookies') expires = Column(DateTime)
class IMDBListUser(Base): __tablename__ = "imdb_list_user" user_id = Column(String, primary_key=True) user_name = Column(Unicode) _cookies = Column('cookies', Unicode) cookies = json_synonym('_cookies') lists = relation('IMDBListList', backref='imdb_user', cascade='all, delete, delete-orphan') def __init__(self, user_name, user_id, cookies): self.user_name = user_name self.user_id = user_id self.cookies = cookies
class SimpleKeyValue(Base): __tablename__ = 'simple_persistence' id = Column(Integer, primary_key=True) task = Column('feed', String) plugin = Column(String) key = Column(String) _json = Column('json', Unicode) value = json_synonym('_json') added = Column(DateTime, default=datetime.now()) def __init__(self, task, plugin, key, value): self.task = task self.plugin = plugin self.key = key self.value = value def __repr__(self): return "<SimpleKeyValue('%s','%s','%s')>" % (self.task, self.key, self.value)
class TMDBConfig(Base): __tablename__ = 'tmdb_configuration' id = Column(Integer, primary_key=True) _configuration = Column('configuration', Unicode) configuration = json_synonym('_configuration') updated = Column(DateTime, default=datetime.now, nullable=False) def __init__(self): try: configuration = tmdb_request('configuration') except requests.RequestException as e: raise LookupError('Error updating data from tmdb: %s' % e) self.configuration = configuration @property def expired(self): if self.updated < datetime.now() - timedelta(days=5): return True return False
class TVMazeSeries(Base): __tablename__ = 'tvmaze_series' tvmaze_id = Column(Integer, primary_key=True) status = Column(Unicode) rating = Column(Float) genres = relation(TVMazeGenre, secondary=genres_table) weight = Column(Integer) updated = Column(DateTime) # last time show was updated at tvmaze name = Column(Unicode) language = Column(Unicode) _schedule = Column('schedule', Unicode) schedule = json_synonym('_schedule') url = Column(String) original_image = Column(String) medium_image = Column(String) tvdb_id = Column(Integer) tvrage_id = Column(Integer) premiered = Column(DateTime) year = Column(Integer) summary = Column(Unicode) webchannel = Column(String) runtime = Column(Integer) show_type = Column(String) network = Column(Unicode) episodes = relation('TVMazeEpisodes', order_by='TVMazeEpisodes.season_number', cascade='all, delete, delete-orphan', backref='series') last_update = Column(DateTime) # last time we updated the db for the show def __init__(self, series, session): self.tvmaze_id = series['id'] self.update(series, session) def to_dict(self): return { 'tvmaze_id': self.tvmaze_id, 'status': self.status, 'rating': self.rating, 'genres': [genre.name for genre in self.genres], 'weight': self.weight, 'updated': self.updated, 'name': self.name, 'language': self.language, 'schedule': self.schedule, 'url': self.url, 'original_image': self.original_image, 'medium_image': self.medium_image, 'tvdb_id': self.tvdb_id, 'tvrage_id': self.tvrage_id, 'premiered': self.premiered, 'year': self.year, 'summary': self.summary, 'webchannel': self.webchannel, 'runtime': self.runtime, 'show_type': self.show_type, 'network': self.network, 'last_update': self.last_update } def update(self, series, session): self.status = series['status'] self.rating = series['rating']['average'] self.weight = series['weight'] self.updated = datetime.fromtimestamp(series['updated']) self.name = series['name'] self.language = series['language'] self.schedule = series['schedule'] self.url = series['url'] self.original_image = series.get('image').get( 'original') if series.get('image') else None self.medium_image = series.get('image').get('medium') if series.get( 'image') else None self.tvdb_id = series['externals'].get('thetvdb') self.tvrage_id = series['externals'].get('tvrage') self.premiered = parser.parse( series.get('premiered'), ignoretz=True) if series.get('premiered') else None self.year = int( series.get('premiered')[:4]) if series.get('premiered') else None self.summary = series['summary'] self.webchannel = series.get('web_channel')['name'] if series.get( 'web_channel') else None self.runtime = series['runtime'] self.show_type = series['type'] self.network = series.get('network')['name'] if series.get( 'network') else None self.last_update = datetime.now() self.genres[:] = get_db_genres(series['genres'], session) def __repr__(self): return '<TVMazeSeries(title=%s,id=%s,last_update=%s)>' % ( self.name, self.tvmaze_id, self.last_update) def __str__(self): return self.name @property def expired(self): if not self.last_update: log.debug('no last update attribute, series set for update') return True time_dif = datetime.now() - self.last_update expiration = time_dif.days > UPDATE_INTERVAL return expiration
class TVDBSeries(Base): __tablename__ = "tvdb_series" id = Column(Integer, primary_key=True, autoincrement=False) last_updated = Column(Integer) expired = Column(Boolean) name = Column(Unicode) language = Column(Unicode) rating = Column(Float) status = Column(Unicode) runtime = Column(Integer) airs_time = Column(Unicode) airs_dayofweek = Column(Unicode) content_rating = Column(Unicode) network = Column(Unicode) overview = Column(Unicode) imdb_id = Column(Unicode) zap2it_id = Column(Unicode) _banner = Column('banner', Unicode) _first_aired = Column('first_aired', DateTime) first_aired = text_date_synonym('_first_aired') _aliases = Column('aliases', Unicode) aliases = json_synonym('_aliases') _actors = Column('actors', Unicode) actors_list = json_synonym('_actors') _posters = Column('posters', Unicode) posters_list = json_synonym('_posters') _genres = relation('TVDBGenre', secondary=genres_table) genres = association_proxy('_genres', 'name') episodes = relation('TVDBEpisode', backref='series', cascade='all, delete, delete-orphan') def __init__(self, tvdb_id): """ Looks up movie on tvdb and creates a new database model for it. These instances should only be added to a session via `session.merge`. """ self.id = tvdb_id try: series = TVDBRequest().get('series/%s' % self.id) except requests.RequestException as e: raise LookupError('Error updating data from tvdb: %s' % e) self.id = series['id'] self.language = 'en' self.last_updated = series['lastUpdated'] self.name = series['seriesName'] self.rating = float( series['siteRating']) if series['siteRating'] else 0.0 self.status = series['status'] self.runtime = int(series['runtime']) if series['runtime'] else 0 self.airs_time = series['airsTime'] self.airs_dayofweek = series['airsDayOfWeek'] self.content_rating = series['rating'] self.network = series['network'] self.overview = series['overview'] self.imdb_id = series['imdbId'] self.zap2it_id = series['zap2itId'] self.first_aired = series['firstAired'] self.expired = False self.aliases = series['aliases'] self._banner = series['banner'] self._genres = [TVDBGenre( id=name) for name in series['genre']] if series['genre'] else [] # Actors and Posters are lazy populated self._actors = None self._posters = None def __repr__(self): return '<TVDBSeries name=%s,tvdb_id=%s>' % (self.name, self.id) @property def banner(self): if self._banner: return TVDBRequest.BANNER_URL + self._banner @property def actors(self): return self.get_actors() @property def posters(self): return self.get_posters() def get_actors(self): if not self._actors: log.debug('Looking up actors for series %s' % self.name) try: actors_query = TVDBRequest().get('series/%s/actors' % self.id) self.actors_list = [a['name'] for a in actors_query ] if actors_query else [] except requests.RequestException as e: if None is not e.response and e.response.status_code == 404: self.actors_list = [] else: raise LookupError('Error updating actors from tvdb: %s' % e) return self.actors_list def get_posters(self): if not self._posters: log.debug('Getting top 5 posters for series %s' % self.name) try: poster_query = TVDBRequest().get('series/%s/images/query' % self.id, keyType='poster') self.posters_list = [p['fileName'] for p in poster_query[:5] ] if poster_query else [] except requests.RequestException as e: if None is not e.response and e.response.status_code == 404: self.posters_list = [] else: raise LookupError('Error updating posters from tvdb: %s' % e) return [TVDBRequest.BANNER_URL + p for p in self.posters_list] def to_dict(self): return { 'tvdb_id': self.id, 'last_updated': datetime.fromtimestamp( self.last_updated).strftime('%Y-%m-%d %H:%M:%S'), 'expired': self.expired, 'series_name': self.name, 'language': self.language, 'rating': self.rating, 'status': self.status, 'runtime': self.runtime, 'airs_time': self.airs_time, 'airs_dayofweek': self.airs_dayofweek, 'content_rating': self.content_rating, 'network': self.network, 'overview': self.overview, 'imdb_id': self.imdb_id, 'zap2it_id': self.zap2it_id, 'banner': self.banner, 'posters': self.posters, 'genres': [g for g in self.genres], 'actors': self.actors, 'first_aired': self.first_aired, }
class TVDBSeriesSearchResult(Base): """ This table will hold a single result that results from the /search/series endpoint, which return a series with a minimal set of parameters. """ __tablename__ = 'tvdb_series_search_results' id = Column(Integer, primary_key=True, autoincrement=False) lookup_term = Column(Unicode) name = Column(Unicode) status = Column(Unicode) network = Column(Unicode) overview = Column(Unicode) _banner = Column('banner', Unicode) _first_aired = Column('first_aired', DateTime) first_aired = text_date_synonym('_first_aired') _aliases = Column('aliases', Unicode) aliases = json_synonym('_aliases') created_at = Column(DateTime) search_name = Column(Unicode) def __init__(self, series, lookup_term=None): self.lookup_term = lookup_term self.id = series['id'] self.name = series['seriesName'] self.first_aired = series['firstAired'] self.network = series['network'] self.overview = series['overview'] self.status = series['status'] self._banner = series['banner'] self.aliases = series['aliases'] self.created_at = datetime.now() @property def banner(self): if self._banner: return TVDBRequest.BANNER_URL + self._banner def to_dict(self): return { 'aliases': [a for a in self.aliases], 'banner': self.banner, 'first_aired': self.first_aired, 'tvdb_id': self.id, 'network': self.network, 'overview': self.overview, 'series_name': self.name, 'status': self.status, } @property def expired(self): logger.debug('checking series {} for expiration', self.original_name) if datetime.now() - self.created_at >= timedelta( days=SEARCH_RESULT_EXPIRATION_DAYS): logger.debug('series {} is expires, should re-fetch', self.original_name) return True logger.debug('series {} is not expired', self.original_name) return False
class TraktMovie(Base): __tablename__ = 'trakt_movies' id = Column(Integer, primary_key=True, autoincrement=False) title = Column(Unicode) year = Column(Integer) slug = Column(Unicode) imdb_id = Column(Unicode) tmdb_id = Column(Integer) tagline = Column(Unicode) overview = Column(Unicode) released = Column(Date) runtime = Column(Integer) rating = Column(Integer) votes = Column(Integer) trailer = Column(Unicode) homepage = Column(Unicode) language = Column(Unicode) updated_at = Column(DateTime) cached_at = Column(DateTime) _translations = relation(TraktMovieTranslation, backref='movie') _translation_languages = Column('translation_languages', Unicode) translation_languages = json_synonym('_translation_languages') genres = relation(TraktGenre, secondary=movie_genres_table) _actors = relation(TraktActor, secondary=movie_actors_table) def __init__(self, trakt_movie, session): super().__init__() self.update(trakt_movie, session) def to_dict(self): return { "id": self.id, "title": self.title, "year": self.year, "slug": self.slug, "imdb_id": self.imdb_id, "tmdb_id": self.tmdb_id, "tagline": self.tagline, "overview": self.overview, "released": self.released, "runtime": self.runtime, "rating": self.rating, "votes": self.votes, "language": self.language, "homepage": self.homepage, "trailer": self.trailer, "genres": [g.name for g in self.genres], "updated_at": self.updated_at, "cached_at": self.cached_at, } def update(self, trakt_movie, session): """Updates this record from the trakt media object `trakt_movie` returned by the trakt api.""" if self.id and self.id != trakt_movie['ids']['trakt']: raise Exception( 'Tried to update db movie with different movie data') elif not self.id: self.id = trakt_movie['ids']['trakt'] self.slug = trakt_movie['ids']['slug'] self.imdb_id = trakt_movie['ids']['imdb'] self.tmdb_id = trakt_movie['ids']['tmdb'] for col in [ 'title', 'overview', 'runtime', 'rating', 'votes', 'language', 'tagline', 'year', 'trailer', 'homepage', ]: setattr(self, col, trakt_movie.get(col)) if trakt_movie.get('released'): self.released = dateutil_parse(trakt_movie.get('released'), ignoretz=True).date() self.updated_at = dateutil_parse(trakt_movie.get('updated_at'), ignoretz=True) self.genres = [ TraktGenre(name=g.replace(' ', '-')) for g in trakt_movie.get('genres', []) ] self.cached_at = datetime.now() self.translation_languages = trakt_movie.get('available_translations', []) @property def expired(self): """ :return: True if movie details are considered to be expired, ie. need of update """ # TODO stolen from imdb plugin, maybe there's a better way? if self.updated_at is None: logger.debug('updated_at is None: {}', self) return True refresh_interval = 2 if self.year: # Make sure age is not negative age = max((datetime.now().year - self.year), 0) refresh_interval += age * 5 logger.debug('movie `{}` age {} expires in {} days', self.title, age, refresh_interval) return self.cached_at < datetime.now() - timedelta( days=refresh_interval) @property def translations(self): if not self._translations: self._translations = get_translations(self.id, 'movie') return self._translations @property def actors(self): if not self._actors: self._actors[:] = get_db_actors(self.id, 'movie') return self._actors
class TraktShow(Base): __tablename__ = 'trakt_shows' id = Column(Integer, primary_key=True, autoincrement=False) title = Column(Unicode) year = Column(Integer) slug = Column(Unicode) tvdb_id = Column(Integer) imdb_id = Column(Unicode) tmdb_id = Column(Integer) tvrage_id = Column(Unicode) overview = Column(Unicode) first_aired = Column(DateTime) air_day = Column(Unicode) air_time = Column(Time) timezone = Column(Unicode) runtime = Column(Integer) certification = Column(Unicode) network = Column(Unicode) country = Column(Unicode) status = Column(String) rating = Column(Integer) votes = Column(Integer) language = Column(Unicode) homepage = Column(Unicode) trailer = Column(Unicode) aired_episodes = Column(Integer) _translations = relation(TraktShowTranslation) _translation_languages = Column('translation_languages', Unicode) translation_languages = json_synonym('_translation_languages') episodes = relation(TraktEpisode, backref='show', cascade='all, delete, delete-orphan', lazy='dynamic') seasons = relation(TraktSeason, backref='show', cascade='all, delete, delete-orphan', lazy='dynamic') genres = relation(TraktGenre, secondary=show_genres_table) _actors = relation(TraktActor, secondary=show_actors_table) updated_at = Column(DateTime) cached_at = Column(DateTime) def to_dict(self): return { "id": self.id, "title": self.title, "year": self.year, "slug": self.slug, "tvdb_id": self.tvdb_id, "imdb_id": self.imdb_id, "tmdb_id": self.tmdb_id, "tvrage_id": self.tvrage_id, "overview": self.overview, "first_aired": self.first_aired, "air_day": self.air_day, "air_time": self.air_time.strftime("%H:%M") if self.air_time else None, "timezone": self.timezone, "runtime": self.runtime, "certification": self.certification, "network": self.network, "country": self.country, "status": self.status, "rating": self.rating, "votes": self.votes, "language": self.language, "homepage": self.homepage, "number_of_aired_episodes": self.aired_episodes, "genres": [g.name for g in self.genres], "updated_at": self.updated_at, "cached_at": self.cached_at, } def __init__(self, trakt_show, session): super().__init__() self.update(trakt_show, session) def update(self, trakt_show, session): """Updates this record from the trakt media object `trakt_show` returned by the trakt api.""" if self.id and self.id != trakt_show['ids']['trakt']: raise Exception('Tried to update db show with different show data') elif not self.id: self.id = trakt_show['ids']['trakt'] self.slug = trakt_show['ids']['slug'] self.imdb_id = trakt_show['ids']['imdb'] self.tmdb_id = trakt_show['ids']['tmdb'] self.tvrage_id = trakt_show['ids']['tvrage'] self.tvdb_id = trakt_show['ids']['tvdb'] if trakt_show.get('airs'): airs = trakt_show.get('airs') self.air_day = airs.get('day') self.timezone = airs.get('timezone') if airs.get('time'): self.air_time = datetime.strptime(airs.get('time'), '%H:%M').time() else: self.air_time = None if trakt_show.get('first_aired'): self.first_aired = dateutil_parse(trakt_show.get('first_aired'), ignoretz=True) else: self.first_aired = None self.updated_at = dateutil_parse(trakt_show.get('updated_at'), ignoretz=True) for col in [ 'overview', 'runtime', 'rating', 'votes', 'language', 'title', 'year', 'runtime', 'certification', 'network', 'country', 'status', 'aired_episodes', 'trailer', 'homepage', ]: setattr(self, col, trakt_show.get(col)) # Sometimes genres and translations are None but we really do want a list, hence the "or []" self.genres = [ TraktGenre(name=g.replace(' ', '-')) for g in trakt_show.get('genres') or [] ] self.cached_at = datetime.now() self.translation_languages = trakt_show.get( 'available_translations') or [] def get_episode(self, season, number, session, only_cached=False): # TODO: Does series data being expired mean all episode data should be refreshed? episode = (self.episodes.filter(TraktEpisode.season == season).filter( TraktEpisode.number == number).first()) if not episode or self.expired: url = get_api_url('shows', self.id, 'seasons', season, 'episodes', number, '?extended=full') if only_cached: raise LookupError('Episode %s %s not found in cache' % (season, number)) logger.debug( 'Episode {} {} not found in cache, looking up from trakt.', season, number) try: data = get_session().get(url).json() except requests.RequestException: raise LookupError('Error Retrieving Trakt url: %s' % url) if not data: raise LookupError('No data in response from trakt %s' % url) episode = self.episodes.filter( TraktEpisode.id == data['ids']['trakt']).first() if episode: episode.update(data, session) else: episode = TraktEpisode(data, session) self.episodes.append(episode) session.commit() return episode def get_season(self, number, session, only_cached=False): # TODO: Does series data being expired mean all season data should be refreshed? season = self.seasons.filter(TraktSeason.number == number).first() if not season or self.expired: url = get_api_url('shows', self.id, 'seasons', '?extended=full') if only_cached: raise LookupError('Season %s not found in cache' % number) logger.debug( 'Season {} not found in cache, looking up from trakt.', number) try: ses = get_session() data = ses.get(url).json() except requests.RequestException: raise LookupError('Error Retrieving Trakt url: %s' % url) if not data: raise LookupError('No data in response from trakt %s' % url) # We fetch all seasons for the given show because we barely get any data otherwise for season_result in data: db_season = self.seasons.filter( TraktSeason.id == season_result['ids']['trakt']).first() if db_season: db_season.update(season_result, session) else: db_season = TraktSeason(season_result, session) self.seasons.append(db_season) if number == season_result['number']: season = db_season if not season: raise LookupError('Season %s not found for show %s' % (number, self.title)) session.commit() return season @property def expired(self): """ :return: True if show details are considered to be expired, ie. need of update """ # TODO stolen from imdb plugin, maybe there's a better way? if self.cached_at is None: logger.debug('cached_at is None: {}', self) return True refresh_interval = 2 # if show has been cancelled or ended, then it is unlikely to be updated often if self.year and (self.status == 'ended' or self.status == 'canceled'): # Make sure age is not negative age = max((datetime.now().year - self.year), 0) refresh_interval += age * 5 logger.debug('show `{}` age {} expires in {} days', self.title, age, refresh_interval) return self.cached_at < datetime.now() - timedelta( days=refresh_interval) @property def translations(self): if not self._translations: self._translations = get_translations(self.id, 'show') return self._translations @property def actors(self): if not self._actors: self._actors[:] = get_db_actors(self.id, 'show') return self._actors def __repr__(self): return '<name=%s, id=%s>' % (self.title, self.id)