From 8ea4ab0950f7720f23e54b54fc685f6c7fefced7 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Thu, 4 Mar 2021 15:05:51 -0500 Subject: [PATCH] Add Letterboxd Support --- modules/builder.py | 3 +++ modules/config.py | 2 ++ modules/letterboxd.py | 54 +++++++++++++++++++++++++++++++++++++++++++ modules/util.py | 3 +++ 4 files changed, 62 insertions(+) create mode 100644 modules/letterboxd.py diff --git a/modules/builder.py b/modules/builder.py index 76ba01b5..42c49e90 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -327,6 +327,8 @@ class CollectionBuilder: list_count = 0 new_list.append({"url": imdb_url, "limit": list_count}) self.methods.append((method_name, new_list)) + elif method_name == "letterboxd_list": + self.methods.append((method_name, util.get_list(data[m], split=False))) elif method_name in util.dictionary_lists: if isinstance(data[m], dict): def get_int(parent, method, data_in, default_in, minimum=1, maximum=None): @@ -648,6 +650,7 @@ class CollectionBuilder: elif "mal" in method: items_found += check_map(self.config.MyAnimeList.get_items(method, value)) elif "tvdb" in method: items_found += check_map(self.config.TVDb.get_items(method, value, self.library.Plex.language)) elif "imdb" in method: items_found += check_map(self.config.IMDb.get_items(self.config, method, value, self.library.Plex.language)) + elif "letterboxd" in method: items_found += check_map(self.config.Letterboxd.get_items(method, value, self.library.Plex.language)) elif "tmdb" in method: items_found += check_map(self.config.TMDb.get_items(method, value, self.library.is_movie)) elif "trakt" in method: items_found += check_map(self.config.Trakt.get_items(method, value, self.library.is_movie)) else: logger.error(f"Collection Error: {method} method not supported") diff --git a/modules/config.py b/modules/config.py index bd21035f..6e0d9bc5 100644 --- a/modules/config.py +++ b/modules/config.py @@ -4,6 +4,7 @@ from modules.anidb import AniDBAPI from modules.builder import CollectionBuilder from modules.cache import Cache from modules.imdb import IMDbAPI +from modules.letterboxd import LetterboxdAPI from modules.mal import MyAnimeListAPI from modules.mal import MyAnimeListIDList from modules.plex import PlexAPI @@ -209,6 +210,7 @@ class Config: self.TVDb = TVDbAPI(self, Cache=self.Cache, TMDb=self.TMDb, Trakt=self.Trakt) self.IMDb = IMDbAPI(Cache=self.Cache, TMDb=self.TMDb, Trakt=self.Trakt, TVDb=self.TVDb) if self.TMDb or self.Trakt else None self.AniDB = AniDBAPI(Cache=self.Cache, TMDb=self.TMDb, Trakt=self.Trakt) + self.Letterboxd = LetterboxdAPI() util.separator() diff --git a/modules/letterboxd.py b/modules/letterboxd.py new file mode 100644 index 00000000..5671878d --- /dev/null +++ b/modules/letterboxd.py @@ -0,0 +1,54 @@ +import logging, math, re, requests +from lxml import html +from modules import util +from modules.util import Failed +from retrying import retry + +logger = logging.getLogger("Plex Meta Manager") + +class LetterboxdAPI: + def __init__(self): + self.url = "https://letterboxd.com" + + @retry(stop_max_attempt_number=6, wait_fixed=10000) + def send_request(self, url, header): + return html.fromstring(requests.get(url, headers=header).content) + + def parse_list_for_slugs(self, list_url, language): + response = self.send_request(list_url, header={"Accept-Language": language, "User-Agent": "Mozilla/5.0 x64"}) + slugs = response.xpath("//div[@class='poster film-poster really-lazy-load']/@data-film-slug") + next_url = response.xpath("//a[@class='next']/@href") + if len(next_url) > 0: + slugs.extend(self.parse_list_for_slugs(f"{self.url}{next_url[0]}", language)) + return slugs + + def get_tmdb_from_slug(self, slug, language): + return self.get_tmdb(f"{self.url}{slug}", language) + + def get_tmdb(self, letterboxd_url, language): + response = self.send_request(letterboxd_url, header={"Accept-Language": language, "User-Agent": "Mozilla/5.0 x64"}) + ids = response.xpath("//body/@data-tmdb-id") + if len(ids) > 0: + return int(ids[0]) + raise Failed(f"Letterboxd Error: TMDb ID not found at {letterboxd_url}") + + def get_items(self, method, data, language, status_message=True): + pretty = util.pretty_names[method] if method in util.pretty_names else method + movie_ids = [] + if status_message: + logger.info(f"Processing {pretty}: {data}") + slugs = self.parse_list_for_slugs(data, language) + total_slugs = len(slugs) + if total_slugs == 0: + raise Failed(f"Letterboxd Error: No List Items found in {data}") + length = 0 + for i, slug in enumerate(slugs, 1): + length = util.print_return(length, f"Finding TMDb ID {i}/{total_slugs}") + try: + movie_ids.append(self.get_tmdb(slug, language)) + except Failed as e: + logger.error(e) + util.print_end(length, f"Processed {total_slugs} TMDb IDs") + if status_message: + logger.debug(f"TMDb IDs Found: {movie_ids}") + return movie_ids, [] diff --git a/modules/util.py b/modules/util.py index 4ddfc1d2..53ec05b1 100644 --- a/modules/util.py +++ b/modules/util.py @@ -97,6 +97,7 @@ pretty_names = { "anidb_popular": "AniDB Popular", "imdb_list": "IMDb List", "imdb_id": "IMDb ID", + "letterboxd_list": "Letterboxd List", "mal_id": "MyAnimeList ID", "mal_all": "MyAnimeList All", "mal_airing": "MyAnimeList Airing", @@ -214,6 +215,7 @@ all_lists = [ "anidb_popular", "imdb_list", "imdb_id", + "letterboxd_list", "mal_id", "mal_all", "mal_airing", @@ -309,6 +311,7 @@ show_only_lists = [ "tvdb_show" ] movie_only_lists = [ + "letterboxd_list", "tmdb_collection", "tmdb_collection_details", "tmdb_movie",