Source code for flexget.plugins.input.letterboxd

import contextlib

from loguru import logger

from flexget import plugin
from flexget.entry import Entry
from flexget.event import event
from flexget.utils.cached_input import cached
from flexget.utils.requests import RequestException, Session, TimedLimiter
from flexget.utils.soup import get_soup

logger = logger.bind(name='letterboxd')

requests = Session(max_retries=5)
requests.add_domain_limiter(TimedLimiter('letterboxd.com', '1 seconds'))
base_url = 'http://letterboxd.com'

SLUGS = {
    'default': {'p_slug': '/%(user)s/list/%(list)s/', 'f_slug': 'data-film-slug'},
    'diary': {'p_slug': '/%(user)s/films/diary/', 'f_slug': 'data-film-slug'},
    'likes': {'p_slug': '/%(user)s/likes/films/', 'f_slug': 'data-film-link'},
    'rated': {'p_slug': '/%(user)s/films/ratings/', 'f_slug': 'data-film-slug'},
    'watched': {'p_slug': '/%(user)s/films/', 'f_slug': 'data-film-slug'},
    'watchlist': {'p_slug': '/%(user)s/watchlist/', 'f_slug': 'data-film-slug'},
}

SORT_BY = {
    'default': '',
    'added': 'by/added/',
    'length-ascending': 'by/shortest/',
    'length-descending': 'by/longest/',
    'name': 'by/name/',
    'popularity': 'by/popular/',
    'rating-ascending': 'by/rating-lowest/',
    'rating-descending': 'by/rating/',
    'release-ascending': 'by/release-earliest/',
    'release-descending': 'by/release/',
}


[docs] class Letterboxd: schema = { 'type': 'object', 'properties': { 'username': {'type': 'string'}, 'list': {'type': 'string'}, 'sort_by': {'type': 'string', 'enum': list(SORT_BY.keys()), 'default': 'default'}, 'max_results': { 'type': 'integer', 'deprecated': True, 'deprecationMessage': '`limit` plugin should be used instead of letterboxd `max_results` option', }, }, 'required': ['username', 'list'], 'additionalProperties': False, }
[docs] def build_config(self, config): config['list'] = config['list'].lower().replace(' ', '-') list_key = config['list'] if list_key not in list(SLUGS.keys()): list_key = 'default' config['p_slug'] = SLUGS[list_key]['p_slug'] % { 'user': config['username'], 'list': config['list'], } config['f_slug'] = SLUGS[list_key]['f_slug'] config['sort_by'] = SORT_BY[config['sort_by']] return config
[docs] def tmdb_lookup(self, search): if not search: logger.warning("Can't search tmdb, no tmdb_id") return None try: tmdb = plugin.get('api_tmdb', self).lookup(tmdb_id=search) except LookupError as e: logger.warning('Error searching tmdb: {}', e) return None return { 'title': f'{tmdb.name} ({tmdb.year})', 'imdb_id': tmdb.imdb_id, 'tmdb_id': tmdb.id, 'movie_name': tmdb.name, 'movie_year': tmdb.year, }
[docs] def parse_film(self, film, config): url = base_url + '/film/' + film.get(config['f_slug']) soup = get_soup(requests.get(url).content) result = self.tmdb_lookup(soup.find(attrs={'data-tmdb-id': True}).get('data-tmdb-id')) if not result: return None entry = Entry(result) entry['url'] = url entry['letterboxd_list'] = '{} ({})'.format(config['list'], config['username']) with contextlib.suppress(AttributeError): entry['letterboxd_score'] = float(soup.find(itemprop='average').get('content')) if config['list'] == 'diary': entry['letterboxd_uscore'] = int( film.find_next(attrs={'data-rating': True}).get('data-rating') ) elif config['list'] == 'rated': entry['letterboxd_uscore'] = int(film.find_next(itemprop='rating').get('content')) return entry
@cached('letterboxd', persist='2 hours') def on_task_input(self, task, config=None): config = self.build_config(config) url = base_url + config['p_slug'] + config['sort_by'] max_results = config.get('max_results', 1) rcount = 0 next_page = '' logger.verbose('Looking for films in Letterboxd list: {}', url) while next_page is not None and rcount < max_results: try: page = requests.get(url).content except RequestException as e: raise plugin.PluginError(f'Error retrieving list from Letterboxd: {e}') soup = get_soup(page) for film in soup.find_all(attrs={config['f_slug']: True}): if rcount < max_results: result = self.parse_film(film, config) if not result: continue yield result if 'max_results' in config: rcount += 1 next_page = soup.select_one('.paginate-nextprev .next') if next_page is not None: next_page = next_page.get('href') if next_page is not None: url = base_url + next_page
[docs] @event('plugin.register') def register_plugin(): plugin.register(Letterboxd, 'letterboxd', api_ver=2)