[1] Add Letterboxd Dynamic Collections (#2098)

This commit is contained in:
meisnate12 2024-06-05 14:44:33 -04:00 committed by GitHub Action
parent de2e1852c5
commit e4c5ef60ab
5 changed files with 107 additions and 31 deletions

View file

@ -1,32 +1,14 @@
# Requirements Update (requirements will need to be reinstalled)
Updated PlexAPI requirement to 4.15.13
Update lxml requirement to 5.2.2
Update requests requirement to 2.32.3
Update schedule requirement to 1.2.2
Update setuptools requirement to 70.0.0
# Removed Features
# New Features
Checks requirement versions to print a message if one needs to be updated
Added the `mass_added_at_update` operation to mass set the Added At field of Movies and Shows.
Add automated Anime Aggregations for AniDB matching
Added `total_runtime` as a special text overlay variable.
Added `top_tamil`, `top_telugu`, `top_malayalam`, `trending_india`, `trending_tamil`, and `trending_telugu` as options for `imdb_chart`
Adds the `sort_by` attribute to `imdb_list`
Added [`letterboxd_user_lists`](https://kometa.wiki/en/latest/files/dynamic_types/#letterboxd-user-lists) Dynamic Collection Type
# Updates
Changed the `overlay_artwork_filetype` Setting to accept `webp_lossy` and `webp_lossless` while the old attribute `webp` will be treated as `webp_lossy`.
# Defaults
Added Letterboxd Default [Collections](https://kometa.wiki/en/latest/defaults/chart/letterboxd/) and [Ribbon](https://kometa.wiki/en/latest/defaults/overlays/ribbon/)
# Bug Fixes
Fixes #2034 `anilist_userlist` `score` attribute wasn't being validated correctly
Fixes #1367 Error when trying to symlink the logs folder
Fixes #2028 TMDb IDs were being ignored on the report
Fixes a bug when parsing a comma-separated string of ints
Fixes `imdb_chart` only getting 25 results
Fixes `imdb_list` not returning items
Various other Minor Fixes

View file

@ -1 +1 @@
2.0.2
2.0.2-build1

View file

@ -222,6 +222,64 @@ requirements of creating the collection.
ending: latest
```
??? blank "`letterboxd_user_lists` - Collections based on the Lists of Letterboxd Users.<a class="headerlink" href="#letterboxd-user-lists" title="Permanent link"></a>"
<div id="letterboxd-user-lists" />Creates collections for each of the Letterboxd lists that the user has created.
<hr style="margin: 0px;">
**`type` Value:** `letterboxd_user_lists`
**`data` Value:** [Dictionary](../kometa/yaml.md#dictionaries) of Attributes
??? blank "`username` - Determines the Usernames to scan for lists.<a class="headerlink" href="#letterboxd-user-lists-username" title="Permanent link"></a>"
<div id="letterboxd-user-lists-username" />This determines which Usernames are scanned.
**Allowed Values:** Username or list of Usernames
??? blank "`sort_by` - Determines the sort that the lists are returned.<a class="headerlink" href="#letterboxd-user-lists-sort-by" title="Permanent link"></a>"
<div id="letterboxd-user-lists-sort-by" />Determines the sort that the lists are returned.
**Allowed Values:** `updated`, `name`, `popularity`, `newest`, `oldest`
**Default:** `updated`
??? blank "`limit` - Determines the number of lists to create collections for.<a class="headerlink" href="#letterboxd-user-lists-limit" title="Permanent link"></a>"
<div id="letterboxd-user-lists-limit" />Determines the number of lists to create collections for. (`0` is all lists)
**Allowed Values:** Number 0 or greater
**Default:** `0`
**Valid Library Types:** Movies
**Key Values:** Letterboxd List URL
**Key Name Value:** Letterboxd List Title
**Default `title_format`:** `<<key_name>>`
??? tip "Default Template (click to expand)"
```yaml
default_template:
letterboxd_list_details: <<value>>
```
???+ example "Example"
```yaml
dynamic_collections:
Letterboxd User Lists: # This name is the mapping name
type: letterboxd_user_lists
data:
username: thebigpictures
limit: 5
```
??? blank "`trakt_user_lists` - Collections based on Trakt Lists by users.<a class="headerlink" href="#trakt-user-lists" title="Permanent link"></a>"
<div id="trakt-user-lists" />Creates collections for each of the Trakt lists for the specified users. Use `me` to

View file

@ -4,19 +4,30 @@ from modules.util import Failed
logger = util.logger
sort_options = {
"name": "by/name/",
"popularity": "by/popular/",
"newest": "by/newest/",
"oldest": "by/oldest/",
"updated": ""
}
builders = ["letterboxd_list", "letterboxd_list_details"]
base_url = "https://letterboxd.com"
class Letterboxd:
def __init__(self, requests, cache):
def __init__(self, requests, cache=None):
self.requests = requests
self.cache = cache
def _request(self, url, language, xpath=None):
logger.trace(f"URL: {url}")
response = self.requests.get_html(url, language=language)
return response.xpath(xpath) if xpath else response
def _parse_page(self, list_url, language):
if "ajax" not in list_url:
list_url = list_url.replace("https://letterboxd.com/films", "https://letterboxd.com/films/ajax")
logger.trace(f"URL: {list_url}")
response = self.requests.get_html(list_url, language=language)
response = self._request(list_url, language)
letterboxd_ids = response.xpath("//li[contains(@class, 'poster-container') or contains(@class, 'film-detail')]/div/@data-film-id")
items = []
for letterboxd_id in letterboxd_ids:
@ -44,19 +55,25 @@ class Letterboxd:
return items
def _tmdb(self, letterboxd_url, language):
logger.trace(f"URL: {letterboxd_url}")
response = self.requests.get_html(letterboxd_url, language=language)
ids = response.xpath("//a[@data-track-action='TMDb']/@href")
ids = self._request(letterboxd_url, language, "//a[@data-track-action='TMDb']/@href")
if len(ids) > 0 and ids[0]:
if "themoviedb.org/movie" in ids[0]:
return util.regex_first_int(ids[0], "TMDb Movie ID")
raise Failed(f"Letterboxd Error: TMDb Movie ID not found in {ids[0]}")
raise Failed(f"Letterboxd Error: TMDb Movie ID not found at {letterboxd_url}")
def get_user_lists(self, username, sort, language):
next_page = [f"/{username}/lists/{sort_options[sort]}"]
lists = []
while next_page:
response = self._request(f"{base_url}{next_page[0]}", language)
sections = response.xpath("//div[@class='film-list-summary']/h2/a")
lists.extend([(f"{base_url}{s.xpath('@href')[0]}", s.xpath("text()")[0]) for s in sections])
next_page = response.xpath("//div[@class='pagination']/div/a[@class='next']/@href")
return lists
def get_list_description(self, list_url, language):
logger.trace(f"URL: {list_url}")
response = self.requests.get_html(list_url, language=language)
descriptions = response.xpath("//meta[@name='description']/@content")
descriptions = self._request(f"{list_url}", language, xpath="//meta[@name='description']/@content")
if len(descriptions) > 0 and len(descriptions[0]) > 0 and "About this list: " in descriptions[0]:
return str(descriptions[0]).split("About this list: ")[1]
return None

View file

@ -1,6 +1,6 @@
import math, operator, os, re
from datetime import datetime
from modules import plex, ergast, util
from modules import plex, ergast, util, letterboxd
from modules.request import quote
from modules.util import Failed, NotScheduled
from plexapi.exceptions import NotFound, BadRequest
@ -13,7 +13,7 @@ ms_auto = [
"trakt_liked_lists", "trakt_people_list", "subtitle_language", "audio_language", "resolution", "decade", "imdb_awards"
]
auto = {
"Movie": ["tmdb_collection", "edition", "country", "director", "producer", "writer"] + all_auto + ms_auto,
"Movie": ["tmdb_collection", "edition", "country", "director", "producer", "writer", "letterboxd_user_lists"] + all_auto + ms_auto,
"Show": ["network", "origin_country", "episode_year"] + all_auto + ms_auto,
"Artist": ["mood", "style", "country", "album_genre", "album_mood", "album_style", "track_mood"] + all_auto,
"Video": ["country", "content_rating"] + all_auto
@ -33,6 +33,7 @@ default_templates = {
"tmdb_collection": {"tmdb_collection_details": "<<value>>", "minimum_items": 2},
"trakt_user_lists": {"trakt_list_details": "<<value>>"},
"trakt_liked_lists": {"trakt_list_details": "<<value>>"},
"letterboxd_user_lists": {"letterboxd_list_details": "<<value>>"},
"tmdb_popular_people": {"tmdb_person": "<<value>>", "plex_search": {"all": {"actor": "tmdb"}}},
"trakt_people_list": {"tmdb_person": "<<value>>", "plex_search": {"all": {"actor": "tmdb"}}}
}
@ -1096,6 +1097,24 @@ class MetadataFile(DataFile):
auto_list[k] = v
elif auto_type == "trakt_liked_lists":
_check_dict(self.config.Trakt.all_liked_lists())
elif auto_type == "letterboxd_user_lists":
dynamic_data = util.parse("Config", "data", dynamic, parent=map_name, methods=methods, datatype="dict")
if "data" in self.temp_vars:
temp_data = util.parse("Config", "data", self.temp_vars["data"], datatype="dict")
for k, v in temp_data.items():
dynamic_data[k] = v
letter_methods = {am.lower(): am for am in dynamic_data}
users = util.parse("Config", "username", dynamic_data, parent=f"{map_name} data", methods=letter_methods, datatype="strlist")
sort = util.parse("Config", "sort_by", dynamic_data, parent=f"{map_name} data", methods=letter_methods, options=letterboxd.sort_options, default="updated")
limit = util.parse("Config", "limit", dynamic_data, parent=f"{map_name} data", methods=letter_methods, datatype="int", minimum=0, default=0)
final = {}
for user in users:
out = self.config.Letterboxd.get_user_lists(user, sort, self.language)
if limit != 0:
out = out[:limit]
for url, name in out:
final[url] = name
_check_dict(final)
elif auto_type == "tmdb_popular_people":
if "data" in self.temp_vars:
dynamic_data = util.parse("Config", "data", self.temp_vars["data"], datatype="int", minimum=1)