Plex-Meta-Manager/modules/builder.py

3686 lines
234 KiB
Python
Raw Normal View History

import os, re, time
2023-02-05 19:10:08 +00:00
from arrapi import ArrException
2024-01-15 21:38:54 +00:00
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
2024-03-06 16:31:44 +00:00
from modules import anidb, anilist, icheckmovies, imdb, letterboxd, mal, mojo, plex, radarr, reciperr, sonarr, tautulli, tmdb, trakt, tvdb, mdblist, util
2022-11-11 16:59:39 +00:00
from modules.util import Failed, FilterFailed, NonExisting, NotScheduled, NotScheduledRange, Deleted
from modules.overlay import Overlay
2024-04-22 14:20:12 +00:00
from modules.poster import KometaImage
from plexapi.audio import Artist, Album, Track
2023-02-27 20:10:06 +00:00
from plexapi.exceptions import NotFound
2021-08-22 15:54:33 +00:00
from plexapi.video import Movie, Show, Season, Episode
2022-05-31 15:37:55 +00:00
from requests.exceptions import ConnectionError
2021-05-05 18:01:41 +00:00
from urllib.parse import quote
2021-02-20 05:41:45 +00:00
logger = util.logger
2021-02-20 05:41:45 +00:00
advance_new_agent = ["item_metadata_language", "item_use_original_title"]
advance_show = ["item_episode_sorting", "item_keep_episodes", "item_delete_episodes", "item_season_display", "item_episode_sorting"]
2024-03-06 16:31:44 +00:00
all_builders = anidb.builders + anilist.builders + icheckmovies.builders + imdb.builders + \
2024-01-15 21:38:54 +00:00
letterboxd.builders + mal.builders + mojo.builders + plex.builders + reciperr.builders + tautulli.builders + \
tmdb.builders + trakt.builders + tvdb.builders + mdblist.builders + radarr.builders + sonarr.builders
show_only_builders = [
"tmdb_network", "tmdb_show", "tmdb_show_details", "tvdb_show", "tvdb_show_details", "tmdb_airing_today",
2022-07-26 19:58:53 +00:00
"tmdb_on_the_air", "builder_level", "item_tmdb_season_titles", "sonarr_all", "sonarr_taglist"
]
2021-03-30 05:50:53 +00:00
movie_only_builders = [
2021-08-01 01:23:17 +00:00
"letterboxd_list", "letterboxd_list_details", "icheckmovies_list", "icheckmovies_list_details", "stevenlu_popular",
2022-09-20 02:46:52 +00:00
"tmdb_collection", "tmdb_collection_details", "tmdb_movie", "tmdb_movie_details", "tmdb_now_playing", "item_edition",
2024-01-15 21:38:54 +00:00
"tvdb_movie", "tvdb_movie_details", "tmdb_upcoming", "trakt_boxoffice", "reciperr_list", "radarr_all", "radarr_taglist",
"mojo_world", "mojo_domestic", "mojo_international", "mojo_record", "mojo_all_time", "mojo_never"
]
music_only_builders = ["item_album_sorting"]
2021-06-22 20:28:12 +00:00
summary_details = [
"summary", "tmdb_summary", "tmdb_description", "tmdb_biography", "tvdb_summary",
2021-07-03 19:17:05 +00:00
"tvdb_description", "trakt_description", "letterboxd_description", "icheckmovies_description"
2021-06-22 20:28:12 +00:00
]
2021-07-23 19:44:21 +00:00
poster_details = ["url_poster", "tmdb_poster", "tmdb_profile", "tvdb_poster", "file_poster"]
background_details = ["url_background", "tmdb_background", "tvdb_background", "file_background"]
boolean_details = [
"show_filtered", "show_missing", "save_report", "missing_only_released", "only_filter_missing",
2022-02-02 16:28:25 +00:00
"delete_below_minimum", "asset_folders", "create_asset_folders"
]
2021-12-19 05:41:58 +00:00
scheduled_boolean = ["visible_library", "visible_home", "visible_shared"]
2021-07-23 19:44:21 +00:00
string_details = ["sort_title", "content_rating", "name_mapping"]
2021-08-22 15:54:33 +00:00
ignored_details = [
2022-09-20 19:56:03 +00:00
"smart_filter", "smart_label", "smart_url", "run_again", "schedule", "sync_mode", "template", "variables", "test", "suppress_overlays",
2024-04-22 14:20:12 +00:00
"delete_not_scheduled", "tmdb_person", "build_collection", "collection_order", "builder_level", "overlay", "kometa_poster",
2023-04-01 19:02:32 +00:00
"validate_builders", "libraries", "sync_to_users", "exclude_users", "collection_name", "playlist_name", "name", "limit",
"blank_collection", "allowed_library_types", "run_definition", "delete_playlist", "ignore_blank_results", "only_run_on_create",
2023-04-18 15:37:05 +00:00
"delete_collections_named", "tmdb_person_offset", "append_label", "key_name", "translation_key", "translation_prefix", "tmdb_birthday"
2021-08-22 15:54:33 +00:00
]
2022-03-23 18:43:50 +00:00
details = [
2023-04-01 19:02:32 +00:00
"ignore_ids", "ignore_imdb_ids", "server_preroll", "changes_webhooks", "collection_filtering", "collection_mode", "url_theme",
2022-05-17 13:35:37 +00:00
"file_theme", "minimum_items", "label", "album_sorting", "cache_builders", "tmdb_region", "default_percent"
2022-03-23 18:43:50 +00:00
] + boolean_details + scheduled_boolean + string_details
2023-03-01 15:14:20 +00:00
collectionless_details = ["collection_order", "plex_collectionless", "label", "label_sync_mode", "test", "item_label"] + \
2023-03-20 21:03:24 +00:00
poster_details + background_details + summary_details + string_details + all_builders
2022-01-26 14:43:58 +00:00
item_false_details = ["item_lock_background", "item_lock_poster", "item_lock_title"]
2022-05-26 16:16:04 +00:00
item_bool_details = ["item_tmdb_season_titles", "revert_overlay", "item_assets", "item_refresh"] + item_false_details
2022-09-20 02:46:52 +00:00
item_details = ["non_item_remove_label", "item_label", "item_genre", "item_edition", "item_radarr_tag", "item_sonarr_tag", "item_refresh_delay"] + item_bool_details + list(plex.item_advance_keys.keys())
none_details = ["label.sync", "item_label.sync", "item_genre.sync", "radarr_taglist", "sonarr_taglist", "item_edition"]
2024-02-02 20:41:44 +00:00
none_builders = ["radarr_tag_list", "sonarr_taglist"]
radarr_details = [
"radarr_add_missing", "radarr_add_existing", "radarr_upgrade_existing", "radarr_monitor_existing", "radarr_folder", "radarr_monitor",
"radarr_search", "radarr_availability", "radarr_quality", "radarr_tag", "item_radarr_tag", "radarr_ignore_cache",
]
2021-08-22 15:54:33 +00:00
sonarr_details = [
"sonarr_add_missing", "sonarr_add_existing", "sonarr_upgrade_existing", "sonarr_monitor_existing", "sonarr_folder", "sonarr_monitor", "sonarr_language",
"sonarr_series", "sonarr_quality", "sonarr_season", "sonarr_search", "sonarr_cutoff_search", "sonarr_tag", "item_sonarr_tag", "sonarr_ignore_cache"
2021-08-22 15:54:33 +00:00
]
2022-01-25 07:45:31 +00:00
album_details = ["non_item_remove_label", "item_label", "item_album_sorting"]
sub_filters = [
"filepath", "audio_track_title", "subtitle_track_title", "resolution", "audio_language", "subtitle_language", "has_dolby_vision",
2022-09-19 15:38:32 +00:00
"channels", "height", "width", "aspect", "audio_codec", "audio_profile", "video_codec", "video_profile", "versions"
]
filters_by_type = {
"movie_show_season_episode_artist_album_track": ["title", "summary", "collection", "has_collection", "added", "last_played", "user_rating", "plays", "filepath", "label", "audio_track_title", "subtitle_track_title", "versions"],
"movie_show_season_episode_album_track": ["year"],
"movie_show_season_episode_artist_album": ["has_overlay"],
"movie_show_season_episode": ["resolution", "audio_language", "subtitle_language", "has_dolby_vision", "channels", "height", "width", "aspect", "audio_codec", "audio_profile", "video_codec", "video_profile"],
"movie_show_episode_album": ["release", "critic_rating", "history"],
"movie_show_episode_track": ["duration"],
"movie_show_artist_album": ["genre"],
"movie_show_episode": ["actor", "content_rating", "audience_rating"],
2023-04-18 14:55:32 +00:00
"movie_show": ["studio", "original_language", "tmdb_vote_count", "tmdb_vote_average", "tmdb_year", "tmdb_genre", "tmdb_title", "tmdb_keyword", "imdb_keyword"],
"movie_episode": ["director", "producer", "writer"],
"movie_artist": ["country"],
"show_artist": ["folder"],
"show_season": ["episodes"],
"artist_album": ["tracks"],
"movie": ["edition", "has_edition", "stinger_rating", "has_stinger"],
2024-04-03 12:45:43 +00:00
"show": ["seasons", "tmdb_status", "tmdb_type", "origin_country", "network", "first_episode_aired", "last_episode_aired", "last_episode_aired_or_never", "tvdb_title", "tvdb_status", "tvdb_genre"],
"artist": ["albums"],
"album": ["record_label"]
}
filters = {
"movie": [item for check, sub in filters_by_type.items() for item in sub if "movie" in check],
"show": [item for check, sub in filters_by_type.items() for item in sub if "show" in check],
"season": [item for check, sub in filters_by_type.items() for item in sub if "season" in check],
"episode": [item for check, sub in filters_by_type.items() for item in sub if "episode" in check],
"artist": [item for check, sub in filters_by_type.items() for item in sub if "artist" in check],
"album": [item for check, sub in filters_by_type.items() for item in sub if "album" in check],
"track": [item for check, sub in filters_by_type.items() for item in sub if "track" in check]
}
tmdb_filters = [
2023-04-18 14:55:32 +00:00
"original_language", "origin_country", "tmdb_vote_count", "tmdb_vote_average", "tmdb_year", "tmdb_keyword", "tmdb_genre",
"first_episode_aired", "last_episode_aired", "last_episode_aired_or_never", "tmdb_status", "tmdb_type", "tmdb_title"
]
2024-04-03 12:45:43 +00:00
tvdb_filters = ["tvdb_title", "tvdb_status", "tvdb_genre"]
2023-01-27 15:16:00 +00:00
imdb_filters = ["imdb_keyword"]
2022-09-21 14:54:38 +00:00
string_filters = [
"title", "summary", "studio", "edition", "record_label", "folder", "filepath", "audio_track_title", "subtitle_track_title", "tmdb_title",
2024-04-03 12:45:43 +00:00
"audio_codec", "audio_profile", "video_codec", "video_profile", "tvdb_title", "tvdb_status"
2022-09-21 14:54:38 +00:00
]
string_modifiers = ["", ".not", ".is", ".isnot", ".begins", ".ends", ".regex"]
tag_filters = [
"actor", "collection", "content_rating", "country", "director", "network", "genre", "label", "producer", "year",
2024-04-03 12:45:43 +00:00
"origin_country", "writer", "resolution", "audio_language", "subtitle_language", "tmdb_keyword", "tmdb_genre", "imdb_keyword", "tvdb_genre"
2021-03-30 05:50:53 +00:00
]
2022-04-20 16:03:08 +00:00
tag_modifiers = ["", ".not", ".regex", ".count_gt", ".count_gte", ".count_lt", ".count_lte"]
boolean_filters = ["has_collection", "has_edition", "has_overlay", "has_dolby_vision", "has_stinger"]
date_filters = ["release", "added", "last_played", "first_episode_aired", "last_episode_aired", "last_episode_aired_or_never"]
date_modifiers = ["", ".not", ".before", ".after", ".regex"]
number_filters = [
2023-04-18 14:55:32 +00:00
"year", "tmdb_year", "critic_rating", "audience_rating", "user_rating", "tmdb_vote_count", "tmdb_vote_average", "plays", "duration",
"channels", "height", "width", "aspect", "versions", "stinger_rating"]
number_modifiers = ["", ".not", ".gt", ".gte", ".lt", ".lte"]
special_filters = [
"history", "episodes", "seasons", "albums", "tracks", "original_language", "original_language.not",
"tmdb_status", "tmdb_status.not", "tmdb_type", "tmdb_type.not"
]
all_filters = boolean_filters + special_filters + \
[f"{f}{m}" for f in string_filters for m in string_modifiers] + \
[f"{f}{m}" for f in tag_filters for m in tag_modifiers] + \
[f"{f}{m}" for f in date_filters for m in date_modifiers] + \
[f"{f}{m}" for f in number_filters for m in number_modifiers]
date_attributes = plex.date_attributes + ["first_episode_aired", "last_episode_aired", "last_episode_aired_or_never"]
2022-04-20 16:03:08 +00:00
year_attributes = plex.year_attributes + ["tmdb_year"]
2023-02-24 16:05:52 +00:00
number_attributes = plex.number_attributes + ["channels", "height", "width", "tmdb_vote_count"]
2022-09-21 14:54:38 +00:00
tag_attributes = plex.tag_attributes
string_attributes = plex.string_attributes + string_filters
2023-04-18 14:55:32 +00:00
float_attributes = plex.float_attributes + ["aspect", "tmdb_vote_average"]
boolean_attributes = plex.boolean_attributes + boolean_filters
2022-07-26 19:58:53 +00:00
smart_invalid = ["collection_order", "builder_level"]
2022-04-08 18:29:38 +00:00
smart_only = ["collection_filtering"]
smart_url_invalid = ["filters", "run_again", "sync_mode", "show_filtered", "show_missing", "save_report", "smart_label"] + radarr_details + sonarr_details
2021-07-29 02:26:39 +00:00
custom_sort_builders = [
2022-09-08 20:11:11 +00:00
"plex_search", "plex_watchlist", "plex_pilots", "tmdb_list", "tmdb_popular", "tmdb_now_playing", "tmdb_top_rated",
2022-03-29 20:17:20 +00:00
"tmdb_trending_daily", "tmdb_trending_weekly", "tmdb_discover", "reciperr_list", "trakt_chart", "trakt_userlist",
"tvdb_list", "imdb_chart", "imdb_list", "imdb_award", "imdb_search", "imdb_watchlist", "stevenlu_popular", "anidb_popular",
2023-12-11 21:34:50 +00:00
"tmdb_upcoming", "tmdb_airing_today", "tmdb_on_the_air", "trakt_list", "trakt_watchlist", "trakt_collection",
"trakt_trending", "trakt_popular", "trakt_boxoffice", "trakt_collected_daily", "trakt_collected_weekly",
"trakt_collected_monthly", "trakt_collected_yearly", "trakt_collected_all", "trakt_recommendations",
"trakt_recommended_personal", "trakt_recommended_daily", "trakt_recommended_weekly", "trakt_recommended_monthly",
"trakt_recommended_yearly", "trakt_recommended_all", "trakt_watched_daily", "trakt_watched_weekly",
"trakt_watched_monthly", "trakt_watched_yearly", "trakt_watched_all",
2024-03-06 16:31:44 +00:00
"tautulli_popular", "tautulli_watched", "mdblist_list", "letterboxd_list", "icheckmovies_list",
"anilist_top_rated", "anilist_popular", "anilist_trending", "anilist_search", "anilist_userlist",
2022-05-03 18:44:19 +00:00
"mal_all", "mal_airing", "mal_upcoming", "mal_tv", "mal_movie", "mal_ova", "mal_special", "mal_search",
2024-01-15 21:38:54 +00:00
"mal_popular", "mal_favorite", "mal_suggested", "mal_userlist", "mal_season", "mal_genre", "mal_studio",
"mojo_world", "mojo_domestic", "mojo_international", "mojo_record", "mojo_all_time", "mojo_never"
2021-07-29 02:26:39 +00:00
]
2022-01-24 22:36:40 +00:00
episode_parts_only = ["plex_pilots"]
2022-04-20 12:56:07 +00:00
overlay_only = ["overlay", "suppress_overlays"]
2022-04-18 18:16:39 +00:00
overlay_attributes = [
2022-07-19 20:33:57 +00:00
"filters", "limit", "show_missing", "save_report", "missing_only_released", "minimum_items", "cache_builders", "tmdb_region", "default_percent"
2022-04-18 18:16:39 +00:00
] + all_builders + overlay_only
parts_collection_valid = [
2022-04-08 18:29:38 +00:00
"filters", "plex_all", "plex_search", "trakt_list", "trakt_list_details", "collection_filtering", "collection_mode", "label", "visible_library", "limit",
"visible_home", "visible_shared", "show_missing", "save_report", "missing_only_released", "server_preroll", "changes_webhooks",
2023-12-11 21:34:50 +00:00
"item_lock_background", "item_lock_poster", "item_lock_title", "item_refresh", "item_refresh_delay", "imdb_list", "imdb_search",
2024-01-03 21:57:07 +00:00
"cache_builders", "url_theme", "file_theme", "item_label", "default_percent", "non_item_remove_label"
2022-01-24 22:36:40 +00:00
] + episode_parts_only + summary_details + poster_details + background_details + string_details
2021-12-17 14:24:46 +00:00
playlist_attributes = [
"filters", "name_mapping", "show_filtered", "show_missing", "save_report", "allowed_library_types", "run_definition",
2021-12-17 14:24:46 +00:00
"missing_only_released", "only_filter_missing", "delete_below_minimum", "ignore_ids", "ignore_imdb_ids",
2022-05-17 13:35:37 +00:00
"server_preroll", "changes_webhooks", "minimum_items", "cache_builders", "default_percent"
2021-12-26 15:48:52 +00:00
] + custom_sort_builders + summary_details + poster_details + radarr_details + sonarr_details
music_attributes = [
2022-04-18 18:16:39 +00:00
"non_item_remove_label", "item_label", "collection_filtering", "item_lock_background", "item_lock_poster", "item_lock_title",
2022-05-26 16:16:04 +00:00
"item_assets", "item_refresh", "item_refresh_delay", "plex_search", "plex_all", "filters"
] + details + summary_details + poster_details + background_details
2021-03-30 05:50:53 +00:00
2021-02-20 05:41:45 +00:00
class CollectionBuilder:
def __init__(self, config, metadata, name, data, library=None, overlay=None, extra=None):
2021-02-20 05:41:45 +00:00
self.config = config
2021-05-07 15:29:29 +00:00
self.metadata = metadata
2021-09-13 00:05:38 +00:00
self.mapping_name = name
2021-02-20 05:41:45 +00:00
self.data = data
2022-02-08 22:57:36 +00:00
self.library = library
self.libraries = []
2023-03-30 19:51:04 +00:00
self.summaries = {}
2022-02-08 22:57:36 +00:00
self.playlist = library is None
2022-04-18 18:16:39 +00:00
self.overlay = overlay
2022-02-08 22:57:36 +00:00
methods = {m.lower(): m for m in self.data}
2022-04-18 18:16:39 +00:00
if self.playlist:
self.type = "playlist"
elif self.overlay:
self.type = "overlay"
else:
self.type = "collection"
2022-02-08 22:57:36 +00:00
self.Type = self.type.capitalize()
logger.separator(f"{self.mapping_name} {self.Type}{f' in {self.library.name}' if self.library else ''}")
logger.info("")
if extra:
logger.info(extra)
logger.info("")
2022-07-14 19:37:10 +00:00
if f"{self.type}_name" in methods:
2022-02-08 22:57:36 +00:00
logger.warning(f"Config Warning: Running {self.type}_name as name")
2022-07-14 19:37:10 +00:00
self.data["name"] = self.data[methods[f"{self.type}_name"]]
methods["name"] = "name"
2022-02-08 22:57:36 +00:00
if "template" in methods:
2022-12-02 17:27:27 +00:00
logger.separator(f"Building Definition From Templates", space=False, border=False)
logger.debug("")
named_templates = []
for original_variables in util.get_list(self.data[methods["template"]], split=False):
if not isinstance(original_variables, dict):
raise Failed(f"{self.Type} Error: template attribute is not a dictionary")
elif "name" not in original_variables:
raise Failed(f"{self.Type} Error: template sub-attribute name is required")
elif not original_variables["name"]:
raise Failed(f"{self.Type} Error: template sub-attribute name cannot be blank")
named_templates.append(original_variables["name"])
2022-12-06 16:34:31 +00:00
logger.debug(f"Templates Called: {', '.join(named_templates)}")
2022-12-02 17:27:27 +00:00
logger.debug("")
2022-09-20 21:29:57 +00:00
new_variables = {}
2022-09-20 19:56:03 +00:00
if "variables" in methods:
logger.debug("")
logger.debug("Validating Method: variables")
if not isinstance(self.data[methods["variables"]], dict):
raise Failed(f"{self.Type} Error: variables must be a dictionary (key: value pairs)")
logger.trace(self.data[methods["variables"]])
2022-09-20 21:29:57 +00:00
new_variables = self.data[methods["variables"]]
2022-07-14 19:37:10 +00:00
name = self.data[methods["name"]] if "name" in methods else None
2022-09-20 21:29:57 +00:00
new_attributes = self.metadata.apply_template(name, self.mapping_name, self.data, self.data[methods["template"]], new_variables)
2022-02-08 22:57:36 +00:00
for attr in new_attributes:
if attr.lower() not in methods:
self.data[attr] = new_attributes[attr]
methods[attr.lower()] = attr
2022-07-18 15:44:16 +00:00
logger.separator(f"Validating {self.mapping_name} Attributes", space=False, border=False)
2023-03-30 19:51:04 +00:00
self.builder_language = self.metadata.language
if "language" in methods:
logger.debug("")
logger.debug("Validating Method: language")
if not self.data[methods["language"]]:
raise Failed(f"{self.Type} Error: language attribute is blank")
logger.debug(f"Value: {self.data[methods['language']]}")
if str(self.data[methods["language"]]).lower() not in self.config.GitHub.translation_keys:
logger.warning(f"Config Error: Language: {str(self.data[methods['language']]).lower()} Not Found using {self.builder_language}. Options: {', '.join(self.config.GitHub.translation_keys)}")
else:
self.builder_language = str(self.data[methods["language"]]).lower()
self.name = None
2023-04-01 19:02:32 +00:00
if "name" in methods:
logger.debug("")
logger.debug("Validating Method: name")
if not self.data[methods["name"]]:
raise Failed(f"{self.Type} Error: name attribute is blank")
logger.debug(f"Value: {self.data[methods['name']]}")
self.name = str(self.data[methods["name"]])
english = None
translations = None
self.limit = 0
if "limit" in methods:
logger.debug("")
logger.debug("Validating Method: limit")
if not self.data[methods["limit"]]:
raise Failed(f"{self.Type} Error: limit attribute is blank")
self.limit = util.parse(self.Type, "limit", self.data[methods["limit"]], datatype="int", minimum=1)
en_key = None
trans_key = None
if "key_name" in methods:
2023-03-30 19:51:04 +00:00
english = self.config.GitHub.translation_yaml("en")
translations = self.config.GitHub.translation_yaml(self.builder_language)
logger.debug("")
2023-04-01 19:02:32 +00:00
logger.debug("Validating Method: key_name")
if not self.data[methods["key_name"]]:
raise Failed(f"{self.Type} Error: key_name attribute is blank")
en_key = str(self.data[methods["key_name"]])
trans_key = en_key
if self.builder_language != "en":
key_name_key = None
for k, v in english["key_names"].items():
if en_key == v:
key_name_key = k
break
if key_name_key and key_name_key in translations["key_names"]:
trans_key = translations["key_names"][key_name_key]
logger.debug(f"Value: {trans_key}")
2023-04-18 17:50:39 +00:00
self.key_name = trans_key
2023-04-01 19:02:32 +00:00
en_name = None
en_summary = None
trans_name = None
trans_summary = None
if "translation_key" in methods:
if not english:
english = self.config.GitHub.translation_yaml("en")
if not translations:
translations = self.config.GitHub.translation_yaml(self.builder_language)
logger.debug("")
2023-03-30 19:51:04 +00:00
logger.debug("Validating Method: translation_key")
if not self.data[methods["translation_key"]]:
raise Failed(f"{self.Type} Error: translation_key attribute is blank")
logger.debug(f"Value: {self.data[methods['translation_key']]}")
translation_key = str(self.data[methods["translation_key"]])
if translation_key not in english["collections"]:
raise Failed(f"{self.Type} Error: translation_key: {translation_key} is invalid")
2023-04-01 19:02:32 +00:00
en_name = english["collections"][translation_key]["name"]
en_summary = english["collections"][translation_key]["summary"]
if translation_key in translations["collections"]:
if "name" in translations["collections"][translation_key]:
trans_name = translations["collections"][translation_key]["name"]
if "summary" in translations["collections"][translation_key]:
trans_summary = translations["collections"][translation_key]["summary"]
2023-04-10 01:20:05 +00:00
if "translation_prefix" in methods and self.data[methods["translation_prefix"]]:
2023-03-30 19:51:04 +00:00
logger.debug("")
2023-04-01 19:02:32 +00:00
logger.debug("Validating Method: translation_prefix")
logger.debug(f"Value: {self.data[methods['translation_prefix']]}")
en_name = f"{self.data[methods['translation_prefix']]}{en_name}"
trans_name = f"{self.data[methods['translation_prefix']]}{trans_name}"
if self.name or en_name or trans_name:
if not english:
english = self.config.GitHub.translation_yaml("en")
if not translations:
translations = self.config.GitHub.translation_yaml(self.builder_language)
2023-03-30 19:51:04 +00:00
lib_type = self.library.type.lower() if self.library else "item"
en_vars = {k: v[lib_type] for k, v in english["variables"].items() if lib_type in v and v[lib_type]}
2023-04-01 19:02:32 +00:00
trans_vars = {k: v[lib_type] for k, v in translations["variables"].items() if lib_type in v and v[lib_type]}
2023-03-30 19:51:04 +00:00
for k, v in en_vars.items():
2023-04-01 19:02:32 +00:00
if k not in trans_vars:
trans_vars[k] = v
2023-03-30 19:51:04 +00:00
2023-04-01 19:02:32 +00:00
def apply_vars(input_str, var_set, var_key, var_limit):
2023-03-30 19:51:04 +00:00
input_str = str(input_str)
2023-04-01 19:02:32 +00:00
if "<<library_type>>" in input_str:
input_str = input_str.replace("<<library_type>>", "<<library_translation>>")
if "<<library_typeU>>" in input_str:
input_str = input_str.replace("<<library_typeU>>", "<<library_translationU>>")
2023-03-30 19:51:04 +00:00
for ik, iv in var_set.items():
if f"<<{ik}>>" in input_str:
2023-03-31 22:19:06 +00:00
input_str = input_str.replace(f"<<{ik}>>", str(iv))
2023-03-30 19:51:04 +00:00
if f"<<{ik}U>>" in input_str:
input_str = input_str.replace(f"<<{ik}U>>", str(iv).capitalize())
2023-04-01 19:02:32 +00:00
if var_key and "<<key_name>>" in input_str:
input_str = input_str.replace("<<key_name>>", str(var_key))
if var_limit and "<<limit>>" in input_str:
input_str = input_str.replace("<<limit>>", str(var_limit))
2023-03-30 19:51:04 +00:00
return input_str
2023-04-01 19:02:32 +00:00
if self.name:
self.name = apply_vars(self.name, trans_vars, trans_key, self.limit)
if en_name:
en_name = apply_vars(en_name, en_vars, en_key, self.limit)
if trans_name:
trans_name = apply_vars(trans_name, trans_vars, trans_key, self.limit)
if en_summary:
en_summary = apply_vars(en_summary, en_vars, en_key, self.limit)
if trans_summary:
trans_summary = apply_vars(trans_summary, trans_vars, trans_key, self.limit)
delete_cols = []
if (self.name and self.name != en_name) or (not self.name and en_name != trans_name):
delete_cols.append(en_name)
if self.name and self.name != trans_name and en_name != trans_name:
2023-04-01 19:02:32 +00:00
delete_cols.append(trans_name)
if delete_cols:
2023-03-30 19:51:04 +00:00
if "delete_collections_named" not in methods:
2023-04-01 19:02:32 +00:00
self.data["delete_collections_named"] = delete_cols
2023-03-30 19:51:04 +00:00
methods["delete_collections_named"] = "delete_collections_named"
2023-04-01 19:02:32 +00:00
elif not self.data[methods["delete_collections_named"]]:
self.data[methods["delete_collections_named"]] = delete_cols
elif not isinstance(self.data[methods["delete_collections_named"]], list):
if self.data[methods["delete_collections_named"]] not in delete_cols:
delete_cols.append(self.data[methods["delete_collections_named"]])
2023-04-01 19:02:32 +00:00
self.data[methods["delete_collections_named"]] = delete_cols
else:
self.data[methods["delete_collections_named"]].extend([d for d in delete_cols if d not in self.data[methods["delete_collections_named"]]])
2023-03-30 19:51:04 +00:00
2023-04-01 19:02:32 +00:00
if not self.name:
self.name = trans_name if trans_name else en_name
logger.info(self.name)
if en_summary or trans_summary:
self.summaries["translation"] = trans_summary if trans_summary else en_summary
2023-03-30 19:51:04 +00:00
if not self.name:
2022-07-15 21:03:04 +00:00
self.name = self.mapping_name
2022-07-14 19:37:10 +00:00
if self.library and self.name not in self.library.collections:
self.library.collections.append(self.name)
2022-12-21 16:30:11 +00:00
if self.playlist:
if "libraries" not in methods:
raise Failed("Playlist Error: libraries attribute is required")
logger.debug("")
logger.debug("Validating Method: libraries")
if not self.data[methods["libraries"]]:
raise Failed(f"{self.Type} Error: libraries attribute is blank")
logger.debug(f"Value: {self.data[methods['libraries']]}")
for pl_library in util.get_list(self.data[methods["libraries"]]):
if str(pl_library) not in config.library_map:
raise Failed(f"Playlist Error: Library: {pl_library} not defined")
self.libraries.append(config.library_map[pl_library])
self.library = self.libraries[0]
try:
self.obj = self.library.get_playlist(self.name) if self.playlist else self.library.get_collection(self.name, force_search=True)
except Failed:
self.obj = None
self.only_run_on_create = False
2023-02-21 20:31:09 +00:00
if "only_run_on_create" in methods and not self.playlist:
logger.debug("")
logger.debug("Validating Method: only_run_on_create")
logger.debug(f"Value: {data[methods['only_run_on_create']]}")
self.only_run_on_create = util.parse(self.Type, "only_run_on_create", self.data, datatype="bool", methods=methods, default=False)
if self.obj and self.only_run_on_create:
raise NotScheduled("Skipped because only_run_on_create is True and the collection already exists")
if "allowed_library_types" in methods and "run_definition" not in methods:
logger.warning(f"{self.Type} Warning: allowed_library_types will run as run_definition")
methods["run_definition"] = methods["allowed_library_types"]
if "run_definition" in methods:
2022-05-12 15:04:52 +00:00
logger.debug("")
logger.debug("Validating Method: run_definition")
if self.data[methods["run_definition"]] is None:
raise NotScheduled("Skipped because run_definition has no value")
logger.debug(f"Value: {self.data[methods['run_definition']]}")
valid_options = ["true", "false"] + plex.library_types
for library_type in util.get_list(self.data[methods["run_definition"]], lower=True):
if library_type not in valid_options:
raise Failed(f"{self.Type} Error: {library_type} is invalid. Options: true, false, {', '.join(plex.library_types)}")
elif library_type == "false":
raise NotScheduled(f"Skipped because run_definition is false")
elif library_type != "true" and self.library and library_type != self.library.Plex.type:
raise NotScheduled(f"Skipped because run_definition library_type: {library_type} doesn't match")
2022-05-12 15:04:52 +00:00
2022-07-26 19:58:53 +00:00
if self.playlist: self.builder_level = "item"
elif self.library.is_show: self.builder_level = "show"
elif self.library.is_music: self.builder_level = "artist"
else: self.builder_level = "movie"
level = None
2023-09-29 17:12:01 +00:00
for level_attr in ["builder_level", "collection_level", "overlay_level"]:
2022-07-26 19:58:53 +00:00
if level_attr in methods:
level = self.data[methods[level_attr]]
if level_attr != "builder_level":
logger.warning(f"Collection Warning: {level_attr} attribute will run as builder_level")
break
2022-08-23 15:30:53 +00:00
if level and not self.playlist and not self.library.is_movie:
2022-07-26 18:30:40 +00:00
logger.debug("")
2022-07-26 19:58:53 +00:00
logger.debug("Validating Method: builder_level")
2022-07-26 18:30:40 +00:00
if level is None:
2022-07-26 19:58:53 +00:00
logger.error(f"{self.Type} Error: builder_level attribute is blank")
2022-07-26 18:30:40 +00:00
else:
logger.debug(f"Value: {level}")
level = level.lower()
2022-07-26 19:58:53 +00:00
if (self.library.is_show and level in plex.builder_level_show_options) or (self.library.is_music and level in plex.builder_level_music_options):
self.builder_level = level
2022-07-26 18:30:40 +00:00
elif (self.library.is_show and level != "show") or (self.library.is_music and level != "artist"):
if self.library.is_show:
options = "\n\tseason (Collection at the Season Level)\n\tepisode (Collection at the Episode Level)"
else:
options = "\n\talbum (Collection at the Album Level)\n\ttrack (Collection at the Track Level)"
2022-07-26 19:58:53 +00:00
raise Failed(f"{self.Type} Error: {self.data[methods['builder_level']]} builder_level invalid{options}")
self.parts_collection = self.builder_level in plex.builder_level_options
2022-07-26 18:30:40 +00:00
self.posters = {}
self.backgrounds = {}
2024-04-22 14:20:12 +00:00
if not self.overlay and "kometa_poster" in methods:
logger.debug("")
2024-04-22 14:20:12 +00:00
logger.debug("Validating Method: kometa_poster")
if self.data[methods["kometa_poster"]] is None:
logger.error(f"{self.Type} Error: kometa_poster attribute is blank")
logger.debug(f"Value: {data[methods['kometa_poster']]}")
try:
2024-04-22 14:20:12 +00:00
self.posters["kometa_poster"] = KometaImage(self.config, self.data[methods["kometa_poster"]], "kometa_poster", playlist=self.playlist)
except Failed as e:
logger.error(e)
2022-04-18 18:16:39 +00:00
if self.overlay:
if "overlay" in methods:
2022-05-15 03:06:32 +00:00
overlay_data = data[methods["overlay"]]
2022-04-18 18:16:39 +00:00
else:
2022-05-15 03:06:32 +00:00
overlay_data = str(self.mapping_name)
logger.warning(f"{self.Type} Warning: No overlay attribute using mapping name {self.mapping_name} as the overlay name")
2022-05-15 03:06:32 +00:00
suppress = []
2022-04-20 12:56:07 +00:00
if "suppress_overlays" in methods:
logger.debug("")
2022-04-20 12:56:07 +00:00
logger.debug("Validating Method: suppress_overlays")
logger.debug(f"Value: {data[methods['suppress_overlays']]}")
if data[methods["suppress_overlays"]]:
2022-05-15 03:06:32 +00:00
suppress = util.get_list(data[methods["suppress_overlays"]])
else:
2022-05-15 03:06:32 +00:00
logger.error(f"Overlay Error: suppress_overlays attribute is blank")
2022-10-28 23:32:48 +00:00
self.overlay = Overlay(config, library, metadata, str(self.mapping_name), overlay_data, suppress, self.builder_level)
self.sync_to_users = None
2022-12-22 16:38:09 +00:00
self.exclude_users = None
self.valid_users = []
2022-02-08 22:57:36 +00:00
if self.playlist:
if "sync_to_users" in methods or "sync_to_user" in methods:
s_attr = f"sync_to_user{'s' if 'sync_to_users' in methods else ''}"
logger.debug("")
logger.debug(f"Validating Method: {s_attr}")
logger.debug(f"Value: {self.data[methods[s_attr]]}")
self.sync_to_users = self.data[methods[s_attr]]
else:
self.sync_to_users = config.general["playlist_sync_to_users"]
2022-12-22 16:38:09 +00:00
logger.warning(f"Playlist Warning: sync_to_users attribute not found defaulting to playlist_sync_to_users: {self.sync_to_users}")
if "exclude_users" in methods or "exclude_user" in methods:
s_attr = f"exclude_user{'s' if 'exclude_users' in methods else ''}"
logger.debug("")
logger.debug(f"Validating Method: {s_attr}")
logger.debug(f"Value: {self.data[methods[s_attr]]}")
self.exclude_users = self.data[methods[s_attr]]
elif config.general["playlist_exclude_users"]:
self.exclude_users = config.general["playlist_exclude_users"]
logger.warning(f"Playlist Warning: exclude_users attribute not found defaulting to playlist_exclude_users: {self.exclude_users}")
2024-02-28 20:40:17 +00:00
plex_users = self.library.users + [self.library.account.username]
2022-12-22 16:38:09 +00:00
2022-12-22 20:40:10 +00:00
self.exclude_users = util.get_list(self.exclude_users) if self.exclude_users else []
2022-12-22 16:38:09 +00:00
for user in self.exclude_users:
if user not in plex_users:
raise Failed(f"Playlist Error: User: {user} not found in plex\nOptions: {plex_users}")
if self.sync_to_users:
if str(self.sync_to_users) == "all":
2022-12-22 16:38:09 +00:00
self.valid_users = [p for p in plex_users if p not in self.exclude_users]
else:
2023-05-10 13:32:18 +00:00
user_list = self.sync_to_users if isinstance(self.sync_to_users, list) else util.get_list(self.sync_to_users)
for user in user_list:
2022-05-12 15:04:52 +00:00
if user not in plex_users:
raise Failed(f"Playlist Error: User: {user} not found in plex\nOptions: {plex_users}")
2022-12-22 16:38:09 +00:00
if user not in self.exclude_users:
self.valid_users.append(user)
if "delete_playlist" in methods:
logger.debug("")
2022-05-27 15:09:46 +00:00
logger.debug("Validating Method: delete_playlist")
logger.debug(f"Value: {data[methods['delete_playlist']]}")
if util.parse(self.Type, "delete_playlist", self.data, datatype="bool", methods=methods, default=False):
2024-02-28 20:40:17 +00:00
for getter in [self.library.get_playlist, self.library.get_playlist_from_users]:
try:
self.obj = getter(self.name)
break
except Failed as e:
error = e
else:
logger.error(error)
2022-05-28 16:01:51 +00:00
raise Deleted(self.delete())
2022-02-08 22:57:36 +00:00
else:
self.libraries.append(self.library)
2022-04-29 13:36:07 +00:00
self.asset_directory = metadata.asset_directory if metadata.asset_directory else self.library.asset_directory
2021-07-21 20:59:27 +00:00
self.language = self.library.Plex.language
2021-02-20 06:41:40 +00:00
self.details = {
2021-05-26 14:45:33 +00:00
"show_filtered": self.library.show_filtered,
2021-12-17 00:16:08 +00:00
"show_options": self.library.show_options,
2021-05-26 14:45:33 +00:00
"show_missing": self.library.show_missing,
"save_report": self.library.save_report,
2021-08-16 05:41:04 +00:00
"missing_only_released": self.library.missing_only_released,
2022-12-02 15:01:52 +00:00
"only_filter_missing": False if self.overlay else self.library.only_filter_missing,
"asset_folders": self.library.asset_folders,
"create_asset_folders": self.library.create_asset_folders,
2021-10-04 17:51:32 +00:00
"delete_below_minimum": self.library.delete_below_minimum,
"delete_not_scheduled": self.library.delete_not_scheduled,
2022-02-02 15:38:38 +00:00
"changes_webhooks": self.library.changes_webhooks,
2022-02-02 16:28:25 +00:00
"cache_builders": 0
2021-02-20 06:41:40 +00:00
}
2022-04-20 21:30:31 +00:00
if self.library.mass_collection_mode:
self.details["collection_mode"] = self.library.mass_collection_mode
self.item_details = {}
2021-08-16 05:41:04 +00:00
self.radarr_details = {}
self.sonarr_details = {}
2021-03-01 20:59:10 +00:00
self.missing_movies = []
self.missing_shows = []
2021-08-22 15:54:33 +00:00
self.missing_parts = []
self.added_to_radarr = []
self.added_to_sonarr = []
2021-07-29 02:26:39 +00:00
self.builders = []
2021-02-20 05:41:45 +00:00
self.filters = []
2022-10-24 06:17:49 +00:00
self.has_tmdb_filters = False
2023-01-28 02:59:43 +00:00
self.has_imdb_filters = False
2022-11-07 17:05:21 +00:00
self.found_items = []
self.filtered_items = []
2021-08-09 15:50:41 +00:00
self.filtered_keys = {}
self.run_again_movies = []
self.run_again_shows = []
2021-11-03 14:36:11 +00:00
self.notification_additions = []
self.notification_removals = []
2021-09-15 03:16:59 +00:00
self.items = []
2022-02-09 02:31:29 +00:00
self.remove_item_map = {}
2021-02-26 18:15:05 +00:00
self.schedule = ""
2022-02-09 02:31:29 +00:00
self.beginning_count = 0
self.default_percent = 50
self.minimum = self.library.minimum_items
2022-03-23 18:43:50 +00:00
self.tmdb_region = None
self.ignore_ids = [i for i in self.library.ignore_ids]
self.ignore_imdb_ids = [i for i in self.library.ignore_imdb_ids]
self.server_preroll = None
2021-05-27 17:40:35 +00:00
self.current_time = datetime.now()
self.current_year = self.current_time.year
2022-03-08 02:37:40 +00:00
self.url_theme = None
self.file_theme = None
self.sync_to_trakt_list = None
2022-05-09 15:22:41 +00:00
self.sync_missing_to_trakt_list = False
self.collection_poster = None
self.collection_background = None
2021-10-04 17:51:32 +00:00
self.exists = False
self.non_existing = False
2021-10-04 17:51:32 +00:00
self.created = False
self.deleted = False
2021-03-21 23:00:10 +00:00
2022-02-08 22:57:36 +00:00
if self.playlist:
server_check = None
for pl_library in self.libraries:
if server_check:
if pl_library.PlexServer.machineIdentifier != server_check:
raise Failed("Playlist Error: All defined libraries must be on the same server")
else:
server_check = pl_library.PlexServer.machineIdentifier
2021-12-26 15:48:52 +00:00
2022-11-11 16:59:39 +00:00
self.ignore_blank_results = False
if "ignore_blank_results" in methods and not self.playlist:
logger.debug("")
logger.debug("Validating Method: ignore_blank_results")
logger.debug(f"Value: {data[methods['ignore_blank_results']]}")
self.ignore_blank_results = util.parse(self.Type, "ignore_blank_results", self.data, datatype="bool", methods=methods, default=False)
2022-11-11 16:59:39 +00:00
2022-11-01 18:20:00 +00:00
self.smart_filter_details = ""
2022-11-19 20:17:43 +00:00
self.smart_label_url = None
2022-11-01 18:20:00 +00:00
self.smart_label = {"sort_by": "random", "all": {"label": [self.name]}}
self.smart_label_collection = False
if "smart_label" in methods and not self.playlist and not self.overlay and not self.library.is_music:
logger.debug("")
logger.debug("Validating Method: smart_label")
self.smart_label_collection = True
if not self.data[methods["smart_label"]]:
logger.warning(f"{self.Type} Error: smart_label attribute is blank defaulting to random")
else:
logger.debug(f"Value: {self.data[methods['smart_label']]}")
if isinstance(self.data[methods["smart_label"]], dict):
_data, replaced = util.replace_label(self.name, self.data[methods["smart_label"]])
if not replaced:
raise Failed("Config Error: <<smart_label>> not found in the smart_label attribute data")
self.smart_label = _data
elif (self.library.is_movie and str(self.data[methods["smart_label"]]).lower() in plex.movie_sorts) \
or (self.library.is_show and str(self.data[methods["smart_label"]]).lower() in plex.show_sorts):
self.smart_label["sort_by"] = str(self.data[methods["smart_label"]]).lower()
else:
logger.warning(f"{self.Type} Error: smart_label attribute: {self.data[methods['smart_label']]} is invalid defaulting to random")
if self.smart_label_collection and self.library.smart_label_check(self.name):
2022-11-11 16:59:39 +00:00
try:
2022-11-19 20:17:43 +00:00
_, self.smart_filter_details, self.smart_label_url = self.build_filter("smart_label", self.smart_label, default_sort="random")
2022-11-11 16:59:39 +00:00
except FilterFailed as e:
if self.ignore_blank_results:
raise
else:
raise Failed(str(e))
2022-04-18 18:16:39 +00:00
if "delete_not_scheduled" in methods and not self.overlay:
logger.debug("")
logger.debug("Validating Method: delete_not_scheduled")
logger.debug(f"Value: {data[methods['delete_not_scheduled']]}")
2022-01-28 18:36:21 +00:00
self.details["delete_not_scheduled"] = util.parse(self.Type, "delete_not_scheduled", self.data, datatype="bool", methods=methods, default=False)
2022-04-25 20:18:04 +00:00
if "schedule" in methods and not self.config.requested_collections and not self.overlay:
2021-08-05 14:59:45 +00:00
logger.debug("")
logger.debug("Validating Method: schedule")
2021-05-24 15:23:21 +00:00
if not self.data[methods["schedule"]]:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: schedule attribute is blank")
2021-05-24 15:23:21 +00:00
else:
logger.debug(f"Value: {self.data[methods['schedule']]}")
2022-01-09 00:45:42 +00:00
err = None
2021-12-18 03:02:24 +00:00
try:
2022-04-25 20:18:04 +00:00
util.schedule_check("schedule", self.data[methods["schedule"]], self.current_time, self.config.run_hour)
except NonExisting as e:
self.non_existing = str(e)
2022-01-06 19:16:12 +00:00
except NotScheduledRange as e:
2022-01-09 00:45:42 +00:00
err = e
2021-12-18 03:02:24 +00:00
except NotScheduled as e:
2022-01-06 19:16:12 +00:00
if not self.config.ignore_schedules:
2022-01-09 00:45:42 +00:00
err = e
if err:
suffix = ""
if self.details["delete_not_scheduled"]:
try:
self.obj = self.library.get_playlist(self.name) if self.playlist else self.library.get_collection(self.name, force_search=True)
logger.info(self.delete())
self.deleted = True
suffix = f" and was deleted"
except Failed:
suffix = f" and could not be found to delete"
2022-01-09 00:45:42 +00:00
raise NotScheduled(f"{err}\n\n{self.Type} {self.name} not scheduled to run{suffix}")
2021-02-20 05:41:45 +00:00
2023-10-16 20:04:46 +00:00
if "delete_collections_named" in methods and not self.overlay and not self.playlist:
logger.debug("")
logger.debug("Validating Method: delete_collections_named")
logger.debug(f"Value: {data[methods['delete_collections_named']]}")
for del_col in util.parse(self.Type, "delete_collections_named", self.data, datatype="strlist", methods=methods):
try:
del_obj = self.library.get_collection(del_col, force_search=True)
self.library.delete(del_obj)
logger.info(f"Collection: {del_obj.title} deleted")
except Failed as e:
if str(e).startswith("Plex Error: Failed to delete"):
logger.error(e)
2022-04-18 18:16:39 +00:00
self.collectionless = "plex_collectionless" in methods and not self.playlist and not self.overlay
2021-07-30 19:19:43 +00:00
self.validate_builders = True
2022-04-18 18:16:39 +00:00
if "validate_builders" in methods and not self.overlay:
2021-08-05 14:59:45 +00:00
logger.debug("")
logger.debug("Validating Method: validate_builders")
logger.debug(f"Value: {data[methods['validate_builders']]}")
2022-01-28 18:36:21 +00:00
self.validate_builders = util.parse(self.Type, "validate_builders", self.data, datatype="bool", methods=methods, default=True)
2021-07-30 19:19:43 +00:00
2021-05-24 15:23:21 +00:00
self.run_again = False
2022-04-18 18:16:39 +00:00
if "run_again" in methods and not self.overlay:
2021-08-05 14:59:45 +00:00
logger.debug("")
logger.debug("Validating Method: run_again")
logger.debug(f"Value: {data[methods['run_again']]}")
2022-01-28 18:36:21 +00:00
self.run_again = util.parse(self.Type, "run_again", self.data, datatype="bool", methods=methods, default=False)
2021-07-30 19:19:43 +00:00
2022-04-18 18:16:39 +00:00
self.build_collection = False if self.overlay else True
if "build_collection" in methods and not self.playlist and not self.overlay:
2021-08-05 14:59:45 +00:00
logger.debug("")
logger.debug("Validating Method: build_collection")
logger.debug(f"Value: {data[methods['build_collection']]}")
2022-01-28 18:36:21 +00:00
self.build_collection = util.parse(self.Type, "build_collection", self.data, datatype="bool", methods=methods, default=True)
2021-05-24 15:23:21 +00:00
2022-01-27 21:24:50 +00:00
self.blank_collection = False
2022-04-18 18:16:39 +00:00
if "blank_collection" in methods and not self.playlist and not self.overlay:
2022-01-27 21:24:50 +00:00
logger.debug("")
logger.debug("Validating Method: blank_collection")
logger.debug(f"Value: {data[methods['blank_collection']]}")
2022-01-28 18:36:21 +00:00
self.blank_collection = util.parse(self.Type, "blank_collection", self.data, datatype="bool", methods=methods, default=False)
2022-01-27 21:24:50 +00:00
2024-01-07 16:48:07 +00:00
self.sync = self.library.sync_mode == "sync" and self.type != "overlay"
2022-04-18 18:16:39 +00:00
if "sync_mode" in methods and not self.overlay:
2021-08-05 14:59:45 +00:00
logger.debug("")
logger.debug("Validating Method: sync_mode")
2021-05-24 15:23:21 +00:00
if not self.data[methods["sync_mode"]]:
logger.warning(f"Collection Warning: sync_mode attribute is blank using general: {self.library.sync_mode}")
else:
logger.debug(f"Value: {self.data[methods['sync_mode']]}")
if self.data[methods["sync_mode"]].lower() not in ["append", "sync"]:
logger.warning(f"Collection Warning: {self.data[methods['sync_mode']]} sync_mode invalid using general: {self.library.sync_mode}")
else:
self.sync = self.data[methods["sync_mode"]].lower() == "sync"
2023-02-13 23:13:54 +00:00
self.tmdb_person_offset = 0
2023-02-07 16:50:18 +00:00
if "tmdb_person_offset" in methods:
logger.debug("")
logger.debug("Validating Method: tmdb_person_offset")
logger.debug(f"Value: {data[methods['tmdb_person_offset']]}")
self.tmdb_person_offset = util.parse(self.Type, "tmdb_person_offset", self.data, datatype="int", methods=methods, default=0, minimum=0)
2023-04-18 15:37:05 +00:00
self.tmdb_birthday = None
if "tmdb_birthday" in methods:
logger.debug("")
logger.debug("Validating Method: tmdb_birthday")
logger.debug(f"Value: {data[methods['tmdb_birthday']]}")
if not self.data[methods["tmdb_birthday"]]:
raise Failed(f"{self.Type} Error: tmdb_birthday attribute is blank")
parsed_birthday = util.parse(self.Type, "tmdb_birthday", self.data, datatype="dict", methods=methods)
parsed_methods = {m.lower(): m for m in parsed_birthday}
self.tmdb_birthday = {
"before": util.parse(self.Type, "before", parsed_birthday, datatype="int", methods=parsed_methods, minimum=0, default=0),
2023-04-18 17:50:39 +00:00
"after": util.parse(self.Type, "after", parsed_birthday, datatype="int", methods=parsed_methods, minimum=0, default=0),
"this_month": util.parse(self.Type, "this_month", parsed_birthday, datatype="bool", methods=parsed_methods, default=False)
2023-04-18 15:37:05 +00:00
}
first_person = None
self.tmdb_person_birthday = None
2021-05-05 18:01:41 +00:00
if "tmdb_person" in methods:
2021-08-05 14:59:45 +00:00
logger.debug("")
logger.debug("Validating Method: tmdb_person")
2021-05-24 15:23:21 +00:00
if not self.data[methods["tmdb_person"]]:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: tmdb_person attribute is blank")
2021-05-24 15:23:21 +00:00
else:
logger.debug(f"Value: {self.data[methods['tmdb_person']]}")
2021-05-05 18:01:41 +00:00
valid_names = []
2022-04-05 18:46:00 +00:00
for tmdb_person in util.get_list(self.data[methods["tmdb_person"]]):
try:
2023-04-18 15:37:05 +00:00
if not first_person:
first_person = tmdb_person
2022-04-05 18:46:00 +00:00
person = self.config.TMDb.get_person(util.regex_first_int(tmdb_person, "TMDb Person ID"))
valid_names.append(person.name)
if person.biography:
self.summaries["tmdb_person"] = person.biography
if person.profile_url:
self.posters["tmdb_person"] = person.profile_url
2023-04-18 15:37:05 +00:00
if person.birthday and not self.tmdb_person_birthday:
self.tmdb_person_birthday = person.birthday
2022-04-05 18:46:00 +00:00
except Failed as e:
if str(e).startswith("TMDb Error"):
logger.error(e)
else:
try:
results = self.config.TMDb.search_people(tmdb_person)
if results:
2023-02-07 16:50:18 +00:00
result_index = len(results) - 1 if self.tmdb_person_offset >= len(results) else self.tmdb_person_offset
2022-07-15 13:56:54 +00:00
valid_names.append(tmdb_person)
2023-02-07 16:50:18 +00:00
if results[result_index].biography:
self.summaries["tmdb_person"] = results[result_index].biography
if results[result_index].profile_url:
self.posters["tmdb_person"] = results[result_index].profile_url
2023-04-18 15:37:05 +00:00
if results[result_index].birthday and not self.tmdb_person_birthday:
self.tmdb_person_birthday = results[result_index].birthday
2022-04-05 18:46:00 +00:00
except Failed as ee:
logger.error(ee)
2021-05-05 18:01:41 +00:00
if len(valid_names) > 0:
self.details["tmdb_person"] = valid_names
else:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: No valid TMDb Person IDs in {self.data[methods['tmdb_person']]}")
2021-05-05 18:01:41 +00:00
2023-04-18 15:37:05 +00:00
if self.tmdb_birthday:
if "tmdb_person" not in methods:
raise NotScheduled("Skipped because tmdb_person is required when using tmdb_birthday")
if not self.tmdb_person_birthday:
raise NotScheduled(f"Skipped because No Birthday was found for {first_person}")
now = datetime(self.current_time.year, self.current_time.month, self.current_time.day)
try:
delta = datetime(now.year, self.tmdb_person_birthday.month, self.tmdb_person_birthday.day)
except ValueError:
delta = datetime(now.year, self.tmdb_person_birthday.month, 28)
before_delta = delta
after_delta = delta
if delta < now:
try:
before_delta = datetime(now.year + 1, self.tmdb_person_birthday.month, self.tmdb_person_birthday.day)
except ValueError:
before_delta = datetime(now.year + 1, self.tmdb_person_birthday.month, 28)
elif delta > now:
try:
after_delta = datetime(now.year - 1, self.tmdb_person_birthday.month, self.tmdb_person_birthday.day)
except ValueError:
after_delta = datetime(now.year - 1, self.tmdb_person_birthday.month, 28)
days_after = (now - after_delta).days
days_before = (before_delta - now).days
2023-04-18 17:50:39 +00:00
msg = ""
if self.tmdb_birthday["this_month"]:
if now.month != self.tmdb_person_birthday.month:
msg = f"Skipped because Birthday Month: {self.tmdb_person_birthday.month} is not {now.month}"
elif days_before > self.tmdb_birthday["before"] and days_after > self.tmdb_birthday["after"]:
msg = f"Skipped because days until {self.tmdb_person_birthday.month}/{self.tmdb_person_birthday.day}: {days_before} > {self.tmdb_birthday['before']} and days after {self.tmdb_person_birthday.month}/{self.tmdb_person_birthday.day}: {days_after} > {self.tmdb_birthday['after']}"
if msg:
2023-04-18 15:37:05 +00:00
suffix = ""
if self.details["delete_not_scheduled"]:
try:
self.obj = self.library.get_playlist(self.name) if self.playlist else self.library.get_collection(self.name, force_search=True)
logger.info(self.delete())
self.deleted = True
suffix = f" and was deleted"
except Failed:
suffix = f" and could not be found to delete"
2023-04-18 17:50:39 +00:00
raise NotScheduled(f"{msg}{suffix}")
2023-04-18 15:37:05 +00:00
self.smart_url = None
2021-05-05 18:01:41 +00:00
self.smart_type_key = None
2022-04-18 18:16:39 +00:00
if "smart_url" in methods and not self.playlist and not self.overlay:
2021-08-05 14:59:45 +00:00
logger.debug("")
logger.debug("Validating Method: smart_url")
2021-05-24 15:23:21 +00:00
if not self.data[methods["smart_url"]]:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: smart_url attribute is blank")
2021-05-24 15:23:21 +00:00
else:
logger.debug(f"Value: {self.data[methods['smart_url']]}")
try:
2021-05-26 14:45:33 +00:00
self.smart_url, self.smart_type_key = self.library.get_smart_filter_from_uri(self.data[methods["smart_url"]])
except ValueError:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: smart_url is incorrectly formatted")
2022-04-18 18:16:39 +00:00
if "smart_filter" in methods and not self.playlist and not self.overlay:
2022-11-11 16:59:39 +00:00
try:
self.smart_type_key, self.smart_filter_details, self.smart_url = self.build_filter("smart_filter", self.data[methods["smart_filter"]], display=True, default_sort="random")
except FilterFailed as e:
if self.ignore_blank_results:
raise
else:
raise Failed(str(e))
2021-05-05 18:01:41 +00:00
2022-02-19 14:52:24 +00:00
if self.collectionless:
for x in ["smart_label", "smart_filter", "smart_url"]:
if x in methods:
self.collectionless = False
2021-05-05 18:01:41 +00:00
logger.info("")
2022-02-19 14:52:24 +00:00
logger.warning(f"{self.Type} Error: {x} is not compatible with plex_collectionless removing plex_collectionless")
if self.run_again and self.smart_url:
self.run_again = False
logger.info("")
logger.warning(f"{self.Type} Error: smart_filter is not compatible with run_again removing run_again")
if self.smart_url and self.smart_label_collection:
raise Failed(f"{self.Type} Error: smart_filter is not compatible with smart_label")
2021-05-05 18:01:41 +00:00
if self.parts_collection and "smart_url" in methods:
2022-07-26 19:58:53 +00:00
raise Failed(f"{self.Type} Error: smart_url is not compatible with builder_level: {self.builder_level}")
2021-05-05 18:01:41 +00:00
self.smart = self.smart_url or self.smart_label_collection
2021-02-20 05:41:45 +00:00
2022-01-07 16:09:12 +00:00
test_sort = None
2022-01-11 01:24:42 +00:00
if "collection_order" in methods and not self.playlist and self.build_collection:
2022-01-07 16:09:12 +00:00
if self.data[methods["collection_order"]] is None:
raise Failed(f"{self.Type} Warning: collection_order attribute is blank")
else:
test_sort = self.data[methods["collection_order"]]
2022-05-27 15:09:46 +00:00
elif "collection_order" not in methods and not self.playlist and not self.blank_collection and self.build_collection and self.library.default_collection_order and not self.smart:
2022-01-07 16:09:12 +00:00
test_sort = self.library.default_collection_order
2024-01-05 21:06:39 +00:00
logger.info("")
2022-05-27 15:09:46 +00:00
logger.warning(f"{self.Type} Warning: collection_order not found using library default_collection_order: {test_sort}")
self.custom_sort = "custom" if self.playlist else None
2022-01-07 16:09:12 +00:00
if test_sort:
2022-01-02 04:23:47 +00:00
if self.smart:
raise Failed(f"{self.Type} Error: collection_order does not work with Smart Collections")
logger.debug("")
logger.debug("Validating Method: collection_order")
2022-01-07 16:09:12 +00:00
logger.debug(f"Value: {test_sort}")
if test_sort in plex.collection_order_options + ["custom.asc", "custom.desc"]:
self.details["collection_order"] = test_sort.split(".")[0]
if test_sort.startswith("custom") and self.build_collection:
self.custom_sort = test_sort
2022-01-02 04:23:47 +00:00
else:
sort_type = self.builder_level
if sort_type == "item":
if self.library.is_show:
sort_type = "show"
elif self.library.is_music:
sort_type = "artist"
else:
sort_type = "movie"
_, _, sorts = plex.sort_types[sort_type]
if not isinstance(test_sort, list):
test_sort = [test_sort]
self.custom_sort = []
for ts in test_sort:
if ts not in sorts:
raise Failed(f"{self.Type} Error: collection_order: {ts} is invalid. Options: {', '.join(sorts)}")
self.custom_sort.append(ts)
2022-12-23 15:28:52 +00:00
if test_sort not in plex.collection_order_options + ["custom.asc", "custom.desc"] and not self.custom_sort:
2024-04-22 14:20:12 +00:00
raise Failed(f"{self.Type} Error: {test_sort} collection_order invalid\n\trelease (Order Collection by release dates)\n\talpha (Order Collection Alphabetically)\n\tcustom.asc/custom.desc (Custom Order Collection)\n\tOther sorting options can be found at https://github.com/Kometa-Team/Kometa/wiki/Smart-Builders#sort-options")
2022-01-02 04:23:47 +00:00
2022-11-19 20:17:43 +00:00
if self.smart:
self.custom_sort = None
2022-01-02 04:23:47 +00:00
for method_key, method_data in self.data.items():
if method_key.lower() in ignored_details:
continue
logger.debug("")
method_name, method_mod, method_final = self.library.split(method_key)
2021-07-23 18:45:49 +00:00
if method_name in ignored_details:
2021-05-24 15:23:21 +00:00
continue
2021-07-23 18:45:49 +00:00
logger.debug(f"Validating Method: {method_key}")
logger.debug(f"Value: {method_data}")
2021-07-30 19:19:43 +00:00
try:
2024-02-02 20:41:44 +00:00
if method_data is None and method_name in all_builders + plex.searches and method_final not in none_builders:
raise Failed(f"{self.Type} Error: {method_final} attribute is blank")
elif method_data is None and method_final not in none_details:
logger.warning(f"Collection Warning: {method_final} attribute is blank")
elif self.playlist and method_name not in playlist_attributes:
raise Failed(f"{self.Type} Error: {method_final} attribute not allowed when using playlists")
elif not self.config.Trakt and "trakt" in method_name:
raise Failed(f"{self.Type} Error: {method_final} requires Trakt to be configured")
elif not self.library.Radarr and "radarr" in method_name:
2022-01-04 16:46:56 +00:00
logger.error(f"{self.Type} Error: {method_final} requires Radarr to be configured")
elif not self.library.Sonarr and "sonarr" in method_name:
2022-01-04 16:46:56 +00:00
logger.error(f"{self.Type} Error: {method_final} requires Sonarr to be configured")
elif not self.library.Tautulli and "tautulli" in method_name:
raise Failed(f"{self.Type} Error: {method_final} requires Tautulli to be configured")
elif not self.config.MyAnimeList and "mal" in method_name:
raise Failed(f"{self.Type} Error: {method_final} requires MyAnimeList to be configured")
elif self.library.is_movie and method_name in show_only_builders:
raise Failed(f"{self.Type} Error: {method_final} attribute only allowed for show libraries")
elif self.library.is_show and method_name in movie_only_builders:
raise Failed(f"{self.Type} Error: {method_final} attribute only allowed for movie libraries")
elif self.library.is_show and method_name in plex.movie_only_searches:
raise Failed(f"{self.Type} Error: {method_final} plex search only allowed for movie libraries")
elif self.library.is_movie and method_name in plex.show_only_searches:
raise Failed(f"{self.Type} Error: {method_final} plex search only allowed for show libraries")
elif self.library.is_music and method_name not in music_attributes:
raise Failed(f"{self.Type} Error: {method_final} attribute not allowed for music libraries")
2022-07-26 19:58:53 +00:00
elif self.library.is_music and method_name in album_details and self.builder_level != "album":
raise Failed(f"{self.Type} Error: {method_final} attribute only allowed for album collections")
elif not self.library.is_music and method_name in music_only_builders:
raise Failed(f"{self.Type} Error: {method_final} attribute only allowed for music libraries")
2022-07-26 19:58:53 +00:00
elif not self.playlist and self.builder_level != "episode" and method_name in episode_parts_only:
2022-01-24 22:36:40 +00:00
raise Failed(f"{self.Type} Error: {method_final} attribute only allowed with Collection Level: episode")
elif self.parts_collection and method_name not in parts_collection_valid:
2022-07-26 19:58:53 +00:00
raise Failed(f"{self.Type} Error: {method_final} attribute not allowed with Collection Level: {self.builder_level.capitalize()}")
elif self.smart and method_name in smart_invalid:
raise Failed(f"{self.Type} Error: {method_final} attribute only allowed with normal collections")
2022-04-08 18:29:38 +00:00
elif not self.smart and method_name in smart_only:
raise Failed(f"{self.Type} Error: {method_final} attribute only allowed with smart collections")
elif self.collectionless and method_name not in collectionless_details:
raise Failed(f"{self.Type} Error: {method_final} attribute not allowed for Collectionless collection")
elif self.smart_url and method_name in all_builders + smart_url_invalid:
raise Failed(f"{self.Type} Error: {method_final} builder not allowed when using smart_filter")
2022-04-18 18:16:39 +00:00
elif not self.overlay and method_name in overlay_only:
raise Failed(f"{self.Type} Error: {method_final} attribute only allowed in an overlay file")
elif self.overlay and method_name not in overlay_attributes:
raise Failed(f"{self.Type} Error: {method_final} attribute not allowed in an overlay file")
elif method_name in summary_details:
self._summary(method_name, method_data)
elif method_name in poster_details:
self._poster(method_name, method_data)
elif method_name in background_details:
self._background(method_name, method_data)
elif method_name in details:
self._details(method_name, method_data, method_final, methods)
elif method_name in item_details:
self._item_details(method_name, method_data, method_mod, method_final, methods)
elif method_name in radarr_details or method_name in radarr.builders:
self._radarr(method_name, method_data)
elif method_name in sonarr_details or method_name in sonarr.builders:
self._sonarr(method_name, method_data)
elif method_name in anidb.builders:
self._anidb(method_name, method_data)
elif method_name in anilist.builders:
self._anilist(method_name, method_data)
elif method_name in icheckmovies.builders:
self._icheckmovies(method_name, method_data)
elif method_name in letterboxd.builders:
self._letterboxd(method_name, method_data)
elif method_name in imdb.builders:
self._imdb(method_name, method_data)
elif method_name in mal.builders:
self._mal(method_name, method_data)
2024-01-15 21:38:54 +00:00
elif method_name in mojo.builders:
self._mojo(method_name, method_data)
elif method_name in plex.builders or method_final in plex.searches:
self._plex(method_name, method_data)
2022-03-19 05:16:25 +00:00
elif method_name in reciperr.builders:
self._reciperr(method_name, method_data)
elif method_name in tautulli.builders:
self._tautulli(method_name, method_data)
elif method_name in tmdb.builders:
self._tmdb(method_name, method_data)
2022-05-09 15:22:41 +00:00
elif method_name in trakt.builders or method_name in ["sync_to_trakt_list", "sync_missing_to_trakt_list"]:
self._trakt(method_name, method_data)
elif method_name in tvdb.builders:
self._tvdb(method_name, method_data)
2022-01-15 22:40:59 +00:00
elif method_name in mdblist.builders:
self._mdblist(method_name, method_data)
elif method_name == "filters":
self._filters(method_name, method_data)
else:
raise Failed(f"{self.Type} Error: {method_final} attribute not supported")
2021-07-30 19:19:43 +00:00
except Failed as e:
if self.validate_builders:
raise
else:
logger.error(e)
2021-02-20 05:41:45 +00:00
2023-02-27 20:10:06 +00:00
if "append_label" in methods and not self.playlist and not self.overlay:
logger.debug("")
logger.debug("Validating Method: append_label")
logger.debug(f"Value: {data[methods['append_label']]}")
append_labels = util.get_list(data[methods["append_label"]])
if "label.sync" in self.details:
self.details["label.sync"].extend(append_labels)
elif "label" in self.details:
self.details["label"].extend(append_labels)
else:
self.details["label"] = append_labels
2022-01-27 21:24:50 +00:00
if not self.server_preroll and not self.smart_url and not self.blank_collection and len(self.builders) == 0:
2022-01-24 19:33:14 +00:00
raise Failed(f"{self.Type} Error: No builders were found")
2022-01-27 21:24:50 +00:00
if self.blank_collection and len(self.builders) > 0:
raise Failed(f"{self.Type} Error: No builders allowed with blank_collection")
if not isinstance(self.custom_sort, list) and self.custom_sort and (len(self.builders) > 1 or self.builders[0][0] not in custom_sort_builders):
2022-02-08 22:57:36 +00:00
raise Failed(f"{self.Type} Error: " + ('Playlists' if self.playlist else 'collection_order: custom') +
2021-12-14 05:51:36 +00:00
(f" can only be used with a single builder per {self.type}" if len(self.builders) > 1 else f" cannot be used with {self.builders[0][0]}"))
2021-07-29 02:26:39 +00:00
2021-12-30 17:13:01 +00:00
if "add_missing" not in self.radarr_details:
self.radarr_details["add_missing"] = self.library.Radarr.add_missing if self.library.Radarr else False
2021-08-16 05:41:04 +00:00
if "add_existing" not in self.radarr_details:
self.radarr_details["add_existing"] = self.library.Radarr.add_existing if self.library.Radarr else False
2021-12-30 17:13:01 +00:00
if "add_missing" not in self.sonarr_details:
self.sonarr_details["add_missing"] = self.library.Sonarr.add_missing if self.library.Sonarr else False
2021-08-16 05:41:04 +00:00
if "add_existing" not in self.sonarr_details:
self.sonarr_details["add_existing"] = self.library.Sonarr.add_existing if self.library.Sonarr else False
2022-11-01 18:20:00 +00:00
if self.smart_url or self.collectionless or self.library.is_music:
2021-12-30 17:13:01 +00:00
self.radarr_details["add_missing"] = False
2021-08-16 05:41:04 +00:00
self.radarr_details["add_existing"] = False
2021-12-30 17:13:01 +00:00
self.sonarr_details["add_missing"] = False
2021-08-16 05:41:04 +00:00
self.sonarr_details["add_existing"] = False
2021-02-20 05:41:45 +00:00
2022-01-28 16:11:09 +00:00
if (self.radarr_details["add_existing"] or self.sonarr_details["add_existing"]) and not self.parts_collection:
2021-11-22 20:47:26 +00:00
self.item_details["add_existing"] = True
2021-03-16 15:49:05 +00:00
if self.collectionless:
self.details["collection_mode"] = "hide"
self.sync = True
2022-07-04 06:49:52 +00:00
if self.smart_url:
self.sync = False
2022-10-21 21:09:36 +00:00
self.do_report = not self.config.no_report and (self.details["save_report"])
self.do_missing = not self.config.no_missing and (self.details["show_missing"] or self.do_report
2022-04-13 04:30:59 +00:00
or (self.library.Radarr and self.radarr_details["add_missing"])
or (self.library.Sonarr and self.sonarr_details["add_missing"]))
if self.build_collection:
if self.obj and ((self.smart and not self.obj.smart) or (not self.smart and self.obj.smart)):
logger.info("")
logger.error(f"{self.Type} Error: Converting {self.obj.title} to a {'smart' if self.smart else 'normal'} collection")
self.library.delete(self.obj)
2022-11-28 22:00:54 +00:00
self.obj = None
2022-11-19 20:17:43 +00:00
if self.smart:
check_url = self.smart_url if self.smart_url else self.smart_label_url
2023-10-10 19:57:10 +00:00
if self.obj:
if check_url != self.library.smart_filter(self.obj):
self.library.update_smart_collection(self.obj, check_url)
2023-12-26 22:07:38 +00:00
logger.info(f"Metadata: Smart Collection updated to {check_url}")
2023-10-11 20:03:12 +00:00
self.beginning_count = len(self.library.fetchItems(check_url)) if check_url else 0
2021-10-04 17:51:32 +00:00
if self.obj:
self.exists = True
2022-02-21 20:56:38 +00:00
if self.sync or self.playlist:
2023-10-11 20:03:12 +00:00
self.remove_item_map = {i.ratingKey: i for i in self.library.get_collection_items(self.obj, self.smart_label_collection)}
if not self.smart:
self.beginning_count = len(self.remove_item_map) if self.playlist else self.obj.childCount
else:
2021-07-12 02:28:52 +00:00
self.obj = None
2023-06-05 18:29:59 +00:00
if self.sync:
logger.warning(f"{self.Type} Error: Sync Mode can only be append when using build_collection: false")
self.sync = False
self.run_again = False
if self.non_existing is not False and self.obj:
raise NotScheduled(self.non_existing)
2021-05-24 03:38:46 +00:00
logger.info("")
logger.info("Validation Successful")
2021-05-12 14:25:48 +00:00
2021-07-21 20:59:27 +00:00
def _summary(self, method_name, method_data):
if method_name == "summary":
2023-04-18 17:50:39 +00:00
self.summaries[method_name] = str(method_data).replace("<<key_name>>", self.key_name) if self.key_name else method_data
2021-07-21 20:59:27 +00:00
elif method_name == "tmdb_summary":
self.summaries[method_name] = self.config.TMDb.get_movie_show_or_collection(util.regex_first_int(method_data, "TMDb ID"), self.library.is_movie).overview
elif method_name == "tmdb_description":
self.summaries[method_name] = self.config.TMDb.get_list(util.regex_first_int(method_data, "TMDb List ID")).description
elif method_name == "tmdb_biography":
self.summaries[method_name] = self.config.TMDb.get_person(util.regex_first_int(method_data, "TMDb Person ID")).biography
elif method_name == "tvdb_summary":
2022-05-05 22:05:16 +00:00
self.summaries[method_name] = self.config.TVDb.get_tvdb_obj(method_data, is_movie=self.library.is_movie).summary
2021-07-21 20:59:27 +00:00
elif method_name == "tvdb_description":
2022-12-19 21:36:51 +00:00
summary, _ = self.config.TVDb.get_list_description(method_data)
if summary:
self.summaries[method_name] = summary
2021-07-21 20:59:27 +00:00
elif method_name == "trakt_description":
2023-09-01 14:11:00 +00:00
try:
self.summaries[method_name] = self.config.Trakt.list_description(self.config.Trakt.validate_list(method_data)[0])
except Failed as e:
logger.error(f"Trakt Error: List description not found: {e}")
2021-07-21 20:59:27 +00:00
elif method_name == "letterboxd_description":
self.summaries[method_name] = self.config.Letterboxd.get_list_description(method_data, self.language)
elif method_name == "icheckmovies_description":
self.summaries[method_name] = self.config.ICheckMovies.get_list_description(method_data, self.language)
def _poster(self, method_name, method_data):
if method_name == "url_poster":
2022-05-31 15:37:55 +00:00
try:
if not method_data.startswith("https://theposterdb.com/api/assets/"):
image_response = self.config.get(method_data, headers=util.header())
if image_response.status_code >= 400 or image_response.headers["Content-Type"] not in util.image_content_types:
raise ConnectionError
2022-05-21 23:20:31 +00:00
self.posters[method_name] = method_data
2022-05-31 15:37:55 +00:00
except ConnectionError:
logger.warning(f"{self.Type} Warning: No Poster Found at {method_data}")
2022-12-19 21:36:51 +00:00
elif method_name == "tmdb_list_poster":
self.posters[method_name] = self.config.TMDb.get_list(util.regex_first_int(method_data, "TMDb List ID")).poster_url
elif method_name == "tvdb_list_poster":
_, poster = self.config.TVDb.get_list_description(method_data)
if poster:
self.posters[method_name] = poster
2021-07-21 20:59:27 +00:00
elif method_name == "tmdb_poster":
2022-01-21 16:34:19 +00:00
self.posters[method_name] = self.config.TMDb.get_movie_show_or_collection(util.regex_first_int(method_data, 'TMDb ID'), self.library.is_movie).poster_url
2021-07-21 20:59:27 +00:00
elif method_name == "tmdb_profile":
2022-01-21 16:34:19 +00:00
self.posters[method_name] = self.config.TMDb.get_person(util.regex_first_int(method_data, 'TMDb Person ID')).profile_url
2021-07-21 20:59:27 +00:00
elif method_name == "tvdb_poster":
2022-05-05 22:05:16 +00:00
self.posters[method_name] = f"{self.config.TVDb.get_tvdb_obj(method_data, is_movie=self.library.is_movie).poster_url}"
2021-07-21 20:59:27 +00:00
elif method_name == "file_poster":
2022-01-11 01:23:56 +00:00
if os.path.exists(os.path.abspath(method_data)):
2021-07-21 20:59:27 +00:00
self.posters[method_name] = os.path.abspath(method_data)
else:
2022-01-11 01:23:56 +00:00
logger.error(f"{self.Type} Error: Poster Path Does Not Exist: {os.path.abspath(method_data)}")
2021-07-21 20:59:27 +00:00
def _background(self, method_name, method_data):
if method_name == "url_background":
2022-05-31 15:37:55 +00:00
try:
image_response = self.config.get(method_data, headers=util.header())
if image_response.status_code >= 400 or image_response.headers["Content-Type"] not in util.image_content_types:
2022-05-31 15:37:55 +00:00
raise ConnectionError
2022-05-21 23:20:31 +00:00
self.backgrounds[method_name] = method_data
2022-05-31 15:37:55 +00:00
except ConnectionError:
logger.warning(f"{self.Type} Warning: No Background Found at {method_data}")
2021-07-21 20:59:27 +00:00
elif method_name == "tmdb_background":
2022-01-21 16:34:19 +00:00
self.backgrounds[method_name] = self.config.TMDb.get_movie_show_or_collection(util.regex_first_int(method_data, 'TMDb ID'), self.library.is_movie).backdrop_url
2021-07-21 20:59:27 +00:00
elif method_name == "tvdb_background":
2022-05-05 22:05:16 +00:00
self.posters[method_name] = f"{self.config.TVDb.get_tvdb_obj(method_data, is_movie=self.library.is_movie).background_url}"
2021-07-21 20:59:27 +00:00
elif method_name == "file_background":
2022-01-11 01:23:56 +00:00
if os.path.exists(os.path.abspath(method_data)):
2021-07-21 20:59:27 +00:00
self.backgrounds[method_name] = os.path.abspath(method_data)
else:
2022-01-11 01:23:56 +00:00
logger.error(f"{self.Type} Error: Background Path Does Not Exist: {os.path.abspath(method_data)}")
2021-07-21 20:59:27 +00:00
def _details(self, method_name, method_data, method_final, methods):
2022-03-06 06:09:03 +00:00
if method_name == "url_theme":
self.url_theme = method_data
elif method_name == "file_theme":
if os.path.exists(os.path.abspath(method_data)):
self.file_theme = os.path.abspath(method_data)
else:
logger.error(f"{self.Type} Error: Theme Path Does Not Exist: {os.path.abspath(method_data)}")
2022-03-23 18:43:50 +00:00
elif method_name == "tmdb_region":
2022-03-24 19:28:23 +00:00
self.tmdb_region = util.parse(self.Type, method_name, method_data, options=self.config.TMDb.iso_3166_1)
2022-03-08 02:37:40 +00:00
elif method_name == "collection_mode":
2022-04-20 21:30:31 +00:00
try:
self.details[method_name] = util.check_collection_mode(method_data)
except Failed as e:
logger.error(e)
2022-04-08 18:29:38 +00:00
elif method_name == "collection_filtering":
if method_data and str(method_data).lower() in plex.collection_filtering_options:
self.details[method_name] = str(method_data).lower()
else:
logger.error(f"Config Error: {method_data} collection_filtering invalid\n\tadmin (Always the server admin user)\n\tuser (User currently viewing the content)")
elif method_name == "minimum_items":
2022-01-28 18:36:21 +00:00
self.minimum = util.parse(self.Type, method_name, method_data, datatype="int", minimum=1)
2022-02-02 16:28:25 +00:00
elif method_name == "cache_builders":
self.details[method_name] = util.parse(self.Type, method_name, method_data, datatype="int", minimum=0)
2022-05-17 13:35:37 +00:00
elif method_name == "default_percent":
self.default_percent = util.parse(self.Type, method_name, method_data, datatype="int", minimum=1, maximum=100)
elif method_name == "server_preroll":
2022-01-28 18:36:21 +00:00
self.server_preroll = util.parse(self.Type, method_name, method_data)
elif method_name == "ignore_ids":
2022-01-28 18:36:21 +00:00
self.ignore_ids.extend(util.parse(self.Type, method_name, method_data, datatype="intlist"))
elif method_name == "ignore_imdb_ids":
2022-01-28 18:36:21 +00:00
self.ignore_imdb_ids.extend(util.parse(self.Type, method_name, method_data, datatype="list"))
2021-07-21 20:59:27 +00:00
elif method_name == "label":
if "label" in methods and "label.sync" in methods:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: Cannot use label and label.sync together")
2021-07-21 20:59:27 +00:00
if "label.remove" in methods and "label.sync" in methods:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: Cannot use label.remove and label.sync together")
2021-07-21 20:59:27 +00:00
if method_final == "label" and "label_sync_mode" in methods and self.data[methods["label_sync_mode"]] == "sync":
2021-12-03 04:23:22 +00:00
self.details["label.sync"] = util.get_list(method_data) if method_data else []
2021-07-21 20:59:27 +00:00
else:
2021-12-03 04:23:22 +00:00
self.details[method_final] = util.get_list(method_data) if method_data else []
2021-12-26 15:48:52 +00:00
elif method_name == "changes_webhooks":
2022-04-14 13:29:54 +00:00
self.details[method_name] = util.parse(self.Type, method_name, method_data, datatype="list") if method_data else None
2021-12-19 05:41:58 +00:00
elif method_name in scheduled_boolean:
if isinstance(method_data, bool):
self.details[method_name] = method_data
elif isinstance(method_data, (int, float)):
self.details[method_name] = method_data > 0
elif str(method_data).lower() in ["t", "true"]:
self.details[method_name] = True
elif str(method_data).lower() in ["f", "false"]:
self.details[method_name] = False
else:
2021-12-19 05:41:58 +00:00
try:
2022-01-28 18:36:21 +00:00
util.schedule_check(method_name, util.parse(self.Type, method_name, method_data), self.current_time, self.config.run_hour)
2021-12-19 05:41:58 +00:00
self.details[method_name] = True
except NotScheduled:
self.details[method_name] = False
2021-07-21 20:59:27 +00:00
elif method_name in boolean_details:
default = self.details[method_name] if method_name in self.details else None
2022-01-28 18:36:21 +00:00
self.details[method_name] = util.parse(self.Type, method_name, method_data, datatype="bool", default=default)
2021-07-21 20:59:27 +00:00
elif method_name in string_details:
self.details[method_name] = str(method_data)
def _item_details(self, method_name, method_data, method_mod, method_final, methods):
if method_name == "item_label":
if "item_label" in methods and "item_label.sync" in methods:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: Cannot use item_label and item_label.sync together")
2021-07-21 20:59:27 +00:00
if "item_label.remove" in methods and "item_label.sync" in methods:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: Cannot use item_label.remove and item_label.sync together")
2021-12-03 04:23:22 +00:00
self.item_details[method_final] = util.get_list(method_data) if method_data else []
2022-08-31 13:49:52 +00:00
if method_name == "item_genre":
if "item_genre" in methods and "item_genre.sync" in methods:
raise Failed(f"{self.Type} Error: Cannot use item_genre and item_genre.sync together")
if "item_genre.remove" in methods and "item_genre.sync" in methods:
raise Failed(f"{self.Type} Error: Cannot use item_genre.remove and item_genre.sync together")
self.item_details[method_final] = util.get_list(method_data) if method_data else []
2022-09-20 02:46:52 +00:00
elif method_name == "item_edition":
2023-04-28 03:43:26 +00:00
self.item_details[method_final] = str(method_data) if method_data else "" # noqa
2022-01-25 01:46:48 +00:00
elif method_name == "non_item_remove_label":
if not method_data:
raise Failed(f"{self.Type} Error: non_item_remove_label is blank")
self.item_details[method_final] = util.get_list(method_data)
2021-07-21 20:59:27 +00:00
elif method_name in ["item_radarr_tag", "item_sonarr_tag"]:
if method_name in methods and f"{method_name}.sync" in methods:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: Cannot use {method_name} and {method_name}.sync together")
2021-07-21 20:59:27 +00:00
if f"{method_name}.remove" in methods and f"{method_name}.sync" in methods:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: Cannot use {method_name}.remove and {method_name}.sync together")
2021-07-21 20:59:27 +00:00
if method_name in methods and f"{method_name}.remove" in methods:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: Cannot use {method_name} and {method_name}.remove together")
2021-12-20 14:47:50 +00:00
self.item_details[method_name] = util.get_list(method_data, lower=True)
2021-07-21 20:59:27 +00:00
self.item_details["apply_tags"] = method_mod[1:] if method_mod else ""
2022-01-11 18:51:49 +00:00
elif method_name == "item_refresh_delay":
2022-01-28 18:36:21 +00:00
self.item_details[method_name] = util.parse(self.Type, method_name, method_data, datatype="int", default=0, minimum=0)
elif method_name in item_bool_details:
2022-01-28 18:36:21 +00:00
if util.parse(self.Type, method_name, method_data, datatype="bool", default=False):
2021-09-15 03:16:59 +00:00
self.item_details[method_name] = True
2022-01-26 14:43:58 +00:00
elif method_name in item_false_details:
self.item_details[method_name] = False
2021-07-21 20:59:27 +00:00
elif method_name in plex.item_advance_keys:
key, options = plex.item_advance_keys[method_name]
if method_name in advance_new_agent and self.library.agent not in plex.new_plex_agents:
logger.error(f"Metadata Error: {method_name} attribute only works for with the New Plex Movie Agent and New Plex TV Agent")
2021-07-21 20:59:27 +00:00
elif method_name in advance_show and not self.library.is_show:
logger.error(f"Metadata Error: {method_name} attribute only works for show libraries")
elif str(method_data).lower() not in options:
logger.error(f"Metadata Error: {method_data} {method_name} attribute invalid")
else:
2023-04-28 03:43:26 +00:00
self.item_details[method_name] = str(method_data).lower() # noqa
2021-07-21 20:59:27 +00:00
def _radarr(self, method_name, method_data):
if method_name in ["radarr_add_missing", "radarr_add_existing", "radarr_upgrade_existing", "radarr_monitor_existing", "radarr_search", "radarr_monitor", "radarr_ignore_cache"]:
2022-01-28 18:36:21 +00:00
self.radarr_details[method_name[7:]] = util.parse(self.Type, method_name, method_data, datatype="bool")
2021-07-21 20:59:27 +00:00
elif method_name == "radarr_folder":
2021-08-16 05:41:04 +00:00
self.radarr_details["folder"] = method_data
2021-07-21 20:59:27 +00:00
elif method_name == "radarr_availability":
if str(method_data).lower() in radarr.availability_translation:
2021-08-16 05:41:04 +00:00
self.radarr_details["availability"] = str(method_data).lower()
2021-07-21 20:59:27 +00:00
else:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: {method_name} attribute must be either announced, cinemas, released or db")
2021-07-21 20:59:27 +00:00
elif method_name == "radarr_quality":
2021-08-16 05:41:04 +00:00
self.radarr_details["quality"] = method_data
2021-07-21 20:59:27 +00:00
elif method_name == "radarr_tag":
2021-12-20 14:47:50 +00:00
self.radarr_details["tag"] = util.get_list(method_data, lower=True)
elif method_name == "radarr_taglist":
self.builders.append((method_name, util.get_list(method_data, lower=True)))
elif method_name == "radarr_all":
self.builders.append((method_name, True))
2021-07-21 20:59:27 +00:00
def _sonarr(self, method_name, method_data):
if method_name in ["sonarr_add_missing", "sonarr_add_existing", "sonarr_upgrade_existing", "sonarr_monitor_existing", "sonarr_season", "sonarr_search", "sonarr_cutoff_search", "sonarr_ignore_cache"]:
2022-01-28 18:36:21 +00:00
self.sonarr_details[method_name[7:]] = util.parse(self.Type, method_name, method_data, datatype="bool")
2021-10-19 04:23:52 +00:00
elif method_name in ["sonarr_folder", "sonarr_quality", "sonarr_language"]:
self.sonarr_details[method_name[7:]] = method_data
2021-07-21 20:59:27 +00:00
elif method_name == "sonarr_monitor":
if str(method_data).lower() in sonarr.monitor_translation:
2021-08-16 05:41:04 +00:00
self.sonarr_details["monitor"] = str(method_data).lower()
2021-07-21 20:59:27 +00:00
else:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: {method_name} attribute must be either all, future, missing, existing, pilot, first, latest or none")
2021-07-21 20:59:27 +00:00
elif method_name == "sonarr_series":
2021-12-13 14:28:46 +00:00
if str(method_data).lower() in sonarr.series_types:
2021-08-16 05:41:04 +00:00
self.sonarr_details["series"] = str(method_data).lower()
2021-07-21 20:59:27 +00:00
else:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: {method_name} attribute must be either standard, daily, or anime")
2021-07-21 20:59:27 +00:00
elif method_name == "sonarr_tag":
2021-12-20 14:47:50 +00:00
self.sonarr_details["tag"] = util.get_list(method_data, lower=True)
elif method_name == "sonarr_taglist":
self.builders.append((method_name, util.get_list(method_data, lower=True)))
elif method_name == "sonarr_all":
self.builders.append((method_name, True))
2021-07-21 20:59:27 +00:00
2021-07-22 21:00:45 +00:00
def _anidb(self, method_name, method_data):
if method_name == "anidb_popular":
2022-01-28 18:36:21 +00:00
self.builders.append((method_name, util.parse(self.Type, method_name, method_data, datatype="int", default=30, maximum=30)))
2021-07-22 21:00:45 +00:00
elif method_name in ["anidb_id", "anidb_relation"]:
2022-03-15 00:11:16 +00:00
for anidb_id in self.config.AniDB.validate_anidb_ids(method_data):
2021-07-29 02:26:39 +00:00
self.builders.append((method_name, anidb_id))
2021-07-22 21:00:45 +00:00
elif method_name == "anidb_tag":
2022-02-13 22:47:08 +00:00
for dict_data in util.parse(self.Type, method_name, method_data, datatype="listdict"):
dict_methods = {dm.lower(): dm for dm in dict_data}
2021-07-22 21:00:45 +00:00
new_dictionary = {}
if "tag" not in dict_methods:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: anidb_tag tag attribute is required")
2021-07-22 21:00:45 +00:00
elif not dict_data[dict_methods["tag"]]:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: anidb_tag tag attribute is blank")
2021-07-22 21:00:45 +00:00
else:
2021-08-28 05:17:14 +00:00
new_dictionary["tag"] = util.regex_first_int(dict_data[dict_methods["tag"]], "AniDB Tag ID")
2022-01-28 18:36:21 +00:00
new_dictionary["limit"] = util.parse(self.Type, "limit", dict_data, datatype="int", methods=dict_methods, default=0, parent=method_name, minimum=0)
2021-07-29 02:26:39 +00:00
self.builders.append((method_name, new_dictionary))
2021-07-22 21:00:45 +00:00
def _anilist(self, method_name, method_data):
if method_name in ["anilist_id", "anilist_relations", "anilist_studio"]:
for anilist_id in self.config.AniList.validate_anilist_ids(method_data, studio=method_name == "anilist_studio"):
2021-07-29 02:26:39 +00:00
self.builders.append((method_name, anilist_id))
elif method_name in ["anilist_popular", "anilist_trending", "anilist_top_rated"]:
2022-01-28 18:36:21 +00:00
self.builders.append((method_name, util.parse(self.Type, method_name, method_data, datatype="int", default=10)))
elif method_name == "anilist_userlist":
for dict_data in util.parse(self.Type, method_name, method_data, datatype="listdict"):
dict_methods = {dm.lower(): dm for dm in dict_data}
2023-11-08 22:00:47 +00:00
new_dictionary = {
"username": util.parse(self.Type, "username", dict_data, methods=dict_methods, parent=method_name),
2022-03-14 02:49:55 +00:00
"list_name": util.parse(self.Type, "list_name", dict_data, methods=dict_methods, parent=method_name),
"sort_by": util.parse(self.Type, "sort_by", dict_data, methods=dict_methods, parent=method_name, default="score", options=anilist.userlist_sort_options),
2023-11-08 22:00:47 +00:00
}
score_dict = {}
for search_method, search_data in dict_data.items():
search_attr, modifier = os.path.splitext(str(search_method).lower())
if search_attr == "score" and modifier in ["gt", "gte", "lt", "lte"]:
score = util.parse(self.Type, search_method, dict_data, datatype="int", default=-1, minimum=0, maximum=10, parent=method_name)
if score > -1:
score_dict[modifier] = score
elif search_attr not in ["username", "list_name", "sort_by"]:
raise Failed(f"{self.Type} Error: {method_name} {search_method} attribute not supported")
new_dictionary["score"] = score_dict
self.builders.append((method_name, self.config.AniList.validate_userlist(new_dictionary)))
2021-08-16 05:41:04 +00:00
elif method_name == "anilist_search":
if self.current_time.month in [12, 1, 2]: current_season = "winter"
elif self.current_time.month in [3, 4, 5]: current_season = "spring"
elif self.current_time.month in [6, 7, 8]: current_season = "summer"
else: current_season = "fall"
2021-12-01 15:17:19 +00:00
default_year = self.current_year + 1 if self.current_time.month == 12 else self.current_year
2022-02-13 22:47:08 +00:00
for dict_data in util.parse(self.Type, method_name, method_data, datatype="listdict"):
dict_methods = {dm.lower(): dm for dm in dict_data}
2021-07-22 21:00:45 +00:00
new_dictionary = {}
2021-08-16 05:41:04 +00:00
for search_method, search_data in dict_data.items():
2023-12-07 19:49:32 +00:00
lower_method = str(search_method).lower()
search_attr, modifier = os.path.splitext(lower_method)
if lower_method not in anilist.searches:
2022-01-12 16:12:38 +00:00
raise Failed(f"{self.Type} Error: {method_name} {search_method} attribute not supported")
2021-08-16 05:41:04 +00:00
elif search_attr == "season":
2022-01-28 18:36:21 +00:00
new_dictionary[search_attr] = util.parse(self.Type, search_attr, search_data, parent=method_name, default=current_season, options=util.seasons)
2022-03-03 14:43:00 +00:00
if new_dictionary[search_attr] == "current":
new_dictionary[search_attr] = current_season
2021-08-16 05:41:04 +00:00
if "year" not in dict_methods:
2021-12-01 15:17:19 +00:00
logger.warning(f"Collection Warning: {method_name} year attribute not found using this year: {default_year} by default")
new_dictionary["year"] = default_year
2021-08-16 05:41:04 +00:00
elif search_attr == "year":
2022-01-28 18:36:21 +00:00
new_dictionary[search_attr] = util.parse(self.Type, search_attr, search_data, datatype="int", parent=method_name, default=default_year, minimum=1917, maximum=default_year + 1)
2021-08-17 00:07:35 +00:00
elif search_data is None:
2022-01-12 16:12:38 +00:00
raise Failed(f"{self.Type} Error: {method_name} {search_method} attribute is blank")
2021-08-16 05:41:04 +00:00
elif search_attr == "adult":
2022-01-28 18:36:21 +00:00
new_dictionary[search_attr] = util.parse(self.Type, search_attr, search_data, datatype="bool", parent=method_name)
elif search_attr == "country":
2022-01-28 18:36:21 +00:00
new_dictionary[search_attr] = util.parse(self.Type, search_attr, search_data, options=anilist.country_codes, parent=method_name)
elif search_attr == "source":
2022-01-28 18:36:21 +00:00
new_dictionary[search_attr] = util.parse(self.Type, search_attr, search_data, options=anilist.media_source, parent=method_name)
2021-08-16 05:41:04 +00:00
elif search_attr in ["episodes", "duration", "score", "popularity"]:
2023-12-07 19:49:32 +00:00
new_dictionary[lower_method] = util.parse(self.Type, search_method, search_data, datatype="int", parent=method_name)
2021-08-16 05:41:04 +00:00
elif search_attr in ["format", "status", "genre", "tag", "tag_category"]:
2023-12-07 19:49:32 +00:00
new_dictionary[lower_method] = self.config.AniList.validate(search_attr.replace("_", " ").title(), util.parse(self.Type, search_method, search_data))
2021-08-16 05:41:04 +00:00
elif search_attr in ["start", "end"]:
2023-12-07 19:49:32 +00:00
new_dictionary[search_attr] = util.parse(self.Type, search_attr, search_data, datatype="date", parent=method_name, date_return="%m/%d/%Y")
2021-08-16 05:41:04 +00:00
elif search_attr == "min_tag_percent":
2022-01-28 18:36:21 +00:00
new_dictionary[search_attr] = util.parse(self.Type, search_attr, search_data, datatype="int", parent=method_name, minimum=0, maximum=100)
2021-08-16 05:41:04 +00:00
elif search_attr == "search":
new_dictionary[search_attr] = str(search_data)
2023-12-07 19:49:32 +00:00
elif lower_method not in ["sort_by", "limit"]:
2022-01-12 16:12:38 +00:00
raise Failed(f"{self.Type} Error: {method_name} {search_method} attribute not supported")
2021-08-16 13:02:52 +00:00
if len(new_dictionary) == 0:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: {method_name} must have at least one valid search option")
2022-01-28 18:36:21 +00:00
new_dictionary["sort_by"] = util.parse(self.Type, "sort_by", dict_data, methods=dict_methods, parent=method_name, default="score", options=anilist.sort_options)
new_dictionary["limit"] = util.parse(self.Type, "limit", dict_data, datatype="int", methods=dict_methods, default=0, parent=method_name)
2021-07-29 02:26:39 +00:00
self.builders.append((method_name, new_dictionary))
2021-07-22 21:00:45 +00:00
def _icheckmovies(self, method_name, method_data):
if method_name.startswith("icheckmovies_list"):
icheckmovies_lists = self.config.ICheckMovies.validate_icheckmovies_lists(method_data, self.language)
for icheckmovies_list in icheckmovies_lists:
2021-07-29 02:26:39 +00:00
self.builders.append(("icheckmovies_list", icheckmovies_list))
2021-07-22 21:00:45 +00:00
if method_name.endswith("_details"):
self.summaries[method_name] = self.config.ICheckMovies.get_list_description(icheckmovies_lists[0], self.language)
2021-07-23 18:45:49 +00:00
def _imdb(self, method_name, method_data):
if method_name == "imdb_id":
for value in util.get_list(method_data):
if str(value).startswith("tt"):
2021-07-29 02:26:39 +00:00
self.builders.append((method_name, value))
2021-07-23 18:45:49 +00:00
else:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: imdb_id {value} must begin with tt")
2021-07-23 18:45:49 +00:00
elif method_name == "imdb_list":
2023-12-28 19:55:46 +00:00
try:
for imdb_dict in self.config.IMDb.validate_imdb_lists(self.Type, method_data, self.language):
self.builders.append((method_name, imdb_dict))
except Failed as e:
logger.error(e)
2021-12-10 08:13:26 +00:00
elif method_name == "imdb_chart":
for value in util.get_list(method_data):
if value in imdb.movie_charts and not self.library.is_movie:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: chart: {value} does not work with show libraries")
2021-12-10 08:13:26 +00:00
elif value in imdb.show_charts and self.library.is_movie:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: chart: {value} does not work with movie libraries")
2023-01-03 22:07:05 +00:00
elif value in imdb.movie_charts or value in imdb.show_charts:
2021-12-10 08:13:26 +00:00
self.builders.append((method_name, value))
2021-12-10 08:17:24 +00:00
else:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: chart: {value} is invalid options are {[i for i in imdb.charts]}")
2022-08-29 19:07:01 +00:00
elif method_name == "imdb_watchlist":
for imdb_user in self.config.IMDb.validate_imdb_watchlists(self.Type, method_data, self.language):
self.builders.append((method_name, imdb_user))
2023-12-12 21:24:37 +00:00
elif method_name == "imdb_award":
for dict_data in util.parse(self.Type, method_name, method_data, datatype="listdict"):
dict_methods = {dm.lower(): dm for dm in dict_data}
event_id = util.parse(self.Type, "event_id", dict_data, parent=method_name, methods=dict_methods, regex=(r"(ev\d+)", "ev0000003"))
2024-01-01 23:19:29 +00:00
git_event, year_options = self.config.IMDb.get_event_years(event_id)
2023-12-12 21:24:37 +00:00
if not year_options:
raise Failed(f"{self.Type} Error: imdb_award event_id attribute: No event found at {imdb.base_url}/event/{event_id}")
2024-01-01 23:19:29 +00:00
if "event_year" not in dict_methods:
raise Failed(f"{self.Type} Error: imdb_award event_year attribute not found")
og_year = dict_data[dict_methods["event_year"]]
if not og_year:
raise Failed(f"{self.Type} Error: imdb_award event_year attribute is blank")
2024-01-05 21:06:39 +00:00
if og_year in ["all", "latest"]:
event_year = og_year
2024-01-01 23:19:29 +00:00
elif not isinstance(og_year, list) and "-" in str(og_year) and len(str(og_year)) > 7:
try:
min_year, max_year = og_year.split("-")
min_year = int(min_year)
max_year = int(max_year) if max_year != "current" else None
event_year = []
for option in year_options:
check = int(option.split("-")[0] if "-" in option else option)
if check >= min_year and (max_year is None or check <= max_year):
event_year.append(option)
except ValueError:
raise Failed(f"{self.Type} Error: imdb_award event_year attribute invalid: {og_year}")
else:
event_year = util.parse(self.Type, "event_year", og_year, parent=method_name, datatype="strlist", options=year_options)
2024-01-03 19:06:05 +00:00
if (event_year == "all" or len(event_year) > 1) and not git_event:
2024-01-02 13:51:40 +00:00
raise Failed(f"{self.Type} Error: Only specific events work when using multiple years. Event Options: [{', '.join([k for k in self.config.IMDb.events_validation])}]")
2024-01-01 23:45:01 +00:00
award_filters = []
if "award_filter" in dict_methods:
if not dict_data[dict_methods["award_filter"]]:
raise Failed(f"{self.Type} Error: imdb_award award_filter attribute is blank")
award_filters = util.parse(self.Type, "award_filter", dict_data[dict_methods["award_filter"]], datatype="lowerlist")
category_filters = []
if "category_filter" in dict_methods:
if not dict_data[dict_methods["category_filter"]]:
raise Failed(f"{self.Type} Error: imdb_award category_filter attribute is blank")
category_filters = util.parse(self.Type, "category_filter", dict_data[dict_methods["category_filter"]], datatype="lowerlist")
2023-12-12 21:24:37 +00:00
final_category = []
final_awards = []
if award_filters or category_filters:
2024-01-05 21:06:39 +00:00
award_names, category_names = self.config.IMDb.get_award_names(event_id, year_options[0] if event_year == "latest" else event_year)
2023-12-12 21:24:37 +00:00
lower_award = {a.lower(): a for a in award_names if a}
for award_filter in award_filters:
if award_filter in lower_award:
final_awards.append(lower_award[award_filter])
else:
raise Failed(f"{self.Type} Error: imdb_award award_filter attribute invalid: {award_filter} must be in in [{', '.join([v for _, v in lower_award.items()])}]")
lower_category = {c.lower(): c for c in category_names if c}
for category_filter in category_filters:
if category_filter in lower_category:
final_category.append(lower_category[category_filter])
else:
raise Failed(f"{self.Type} Error: imdb_award category_filter attribute invalid: {category_filter} must be in in [{', '.join([v for _, v in lower_category.items()])}]")
self.builders.append((method_name, {
"event_id": event_id, "event_year": event_year, "award_filter": final_awards if final_awards else None, "category_filter": final_category if final_category else None,
"winning": util.parse(self.Type, "winning", dict_data, parent=method_name, methods=dict_methods, datatype="bool", default=False)
}))
2023-12-07 19:49:32 +00:00
elif method_name == "imdb_search":
for dict_data in util.parse(self.Type, method_name, method_data, datatype="listdict"):
dict_methods = {dm.lower(): dm for dm in dict_data}
new_dictionary = {"limit": util.parse(self.Type, "limit", dict_data, datatype="int", methods=dict_methods, minimum=0, default=100, parent=method_name)}
for search_method, search_data in dict_data.items():
lower_method = str(search_method).lower()
search_attr, modifier = os.path.splitext(lower_method)
if search_data is None:
raise Failed(f"{self.Type} Error: {method_name} {search_method} attribute is blank")
elif lower_method not in imdb.imdb_search_attributes:
raise Failed(f"{self.Type} Error: {method_name} {search_method} attribute not supported")
elif search_attr == "sort_by":
new_dictionary[lower_method] = util.parse(self.Type, search_method, search_data, parent=method_name, options=imdb.sort_options)
elif search_attr == "title":
new_dictionary[lower_method] = util.parse(self.Type, search_method, search_data, parent=method_name)
elif search_attr == "type":
new_dictionary[lower_method] = util.parse(self.Type, search_method, search_data, datatype="lowerlist", parent=method_name, options=imdb.title_type_options)
elif search_attr == "topic":
new_dictionary[lower_method] = util.parse(self.Type, search_method, search_data, datatype="lowerlist", parent=method_name, options=imdb.topic_options)
2023-12-07 19:49:32 +00:00
elif search_attr == "release":
new_dictionary[lower_method] = util.parse(self.Type, search_method, search_data, datatype="date", parent=method_name, date_return="%Y-%m-%d")
elif search_attr == "rating":
new_dictionary[lower_method] = util.parse(self.Type, search_method, search_data, datatype="float", parent=method_name, minimum=0.1, maximum=10)
elif search_attr in ["votes", "imdb_top", "imdb_bottom", "popularity", "runtime"]:
new_dictionary[lower_method] = util.parse(self.Type, search_method, search_data, datatype="int", parent=method_name, minimum=0)
elif search_attr == "genre":
new_dictionary[lower_method] = util.parse(self.Type, search_method, search_data, datatype="lowerlist", parent=method_name, options=imdb.genre_options)
elif search_attr == "event":
events = []
for event in util.parse(self.Type, search_method, search_data, datatype="lowerlist", parent=method_name):
if event in imdb.event_options:
events.append(event)
else:
res = re.search(r'(ev\d+)', event)
if res:
events.append(res.group(1))
else:
2024-01-03 21:57:07 +00:00
raise Failed(f"{method_name} {search_method} attribute: {search_data} must match pattern ev\\d+ e.g. ev0000292 or be one of {', '.join([e for e in imdb.event_options])}")
2023-12-07 19:49:32 +00:00
if events:
new_dictionary[lower_method] = events
elif search_attr == "company":
companies = []
for company in util.parse(self.Type, search_method, search_data, datatype="lowerlist", parent=method_name):
if company in imdb.company_options:
companies.append(company)
else:
res = re.search(r'(co\d+)', company)
if res:
companies.append(res.group(1))
else:
2024-01-03 21:57:07 +00:00
raise Failed(f"{method_name} {search_method} attribute: {search_data} must match pattern co\\d+ e.g. co0098836 or be one of {', '.join([e for e in imdb.company_options])}")
2023-12-07 19:49:32 +00:00
if companies:
new_dictionary[lower_method] = companies
elif search_attr == "content_rating":
final_list = []
for content in util.get_list(search_data):
if content:
final_dict = {"region": "US", "rating": None}
if not isinstance(content, dict):
final_dict["rating"] = str(content)
else:
if "rating" not in content or not content["rating"]:
raise Failed(f"{method_name} {search_method} attribute: rating attribute is required")
final_dict["rating"] = str(content["rating"])
if "region" not in content or not content["region"]:
logger.warning(f"{method_name} {search_method} attribute: region attribute not found defaulting to 'US'")
elif len(str(content["region"])) != 2:
logger.warning(f"{method_name} {search_method} attribute: region attribute: {str(content['region'])} must be only 2 characters defaulting to 'US'")
else:
final_dict["region"] = str(content["region"]).upper()
final_list.append(final_dict)
if final_list:
new_dictionary[lower_method] = final_list
elif search_attr == "country":
countries = []
for country in util.parse(self.Type, search_method, search_data, datatype="upperlist", parent=method_name):
if country:
if len(str(country)) != 2:
raise Failed(f"{method_name} {search_method} attribute: {country} must be only 2 characters i.e. 'US'")
countries.append(str(country))
if countries:
new_dictionary[lower_method] = countries
elif search_attr in ["keyword", "language", "alternate_version", "crazy_credit", "location", "goof", "plot", "quote", "soundtrack", "trivia"]:
2023-12-07 19:49:32 +00:00
new_dictionary[lower_method] = util.parse(self.Type, search_method, search_data, datatype="lowerlist", parent=method_name)
elif search_attr == "cast":
casts = []
for cast in util.parse(self.Type, search_method, search_data, datatype="lowerlist", parent=method_name):
res = re.search(r'(nm\d+)', cast)
if res:
casts.append(res.group(1))
else:
2024-01-03 21:57:07 +00:00
raise Failed(f"{method_name} {search_method} attribute: {search_data} must match pattern nm\\d+ e.g. nm00988366")
2023-12-07 19:49:32 +00:00
if casts:
new_dictionary[lower_method] = casts
elif search_attr == "series":
series = []
for show in util.parse(self.Type, search_method, search_data, datatype="lowerlist", parent=method_name):
res = re.search(r'(tt\d+)', show)
if res:
series.append(res.group(1))
else:
2024-01-03 21:57:07 +00:00
raise Failed(f"{method_name} {search_method} attribute: {search_data} must match pattern tt\\d+ e.g. tt00988366")
2023-12-07 19:49:32 +00:00
if series:
new_dictionary[lower_method] = series
elif search_attr == "list":
lists = []
for new_list in util.parse(self.Type, search_method, search_data, datatype="lowerlist", parent=method_name):
res = re.search(r'(ls\d+)', new_list)
if res:
lists.append(res.group(1))
else:
2024-01-03 21:57:07 +00:00
raise Failed(f"{method_name} {search_method} attribute: {search_data} must match pattern ls\\d+ e.g. ls000024621")
2023-12-07 19:49:32 +00:00
if lists:
new_dictionary[lower_method] = lists
elif search_attr == "adult":
if util.parse(self.Type, search_method, search_data, datatype="bool", parent=method_name):
new_dictionary[lower_method] = True
2023-12-08 19:15:41 +00:00
elif search_attr != "limit":
2023-12-07 19:49:32 +00:00
raise Failed(f"{self.Type} Error: {method_name} {search_method} attribute not supported")
if len(new_dictionary) > 1:
self.builders.append((method_name, new_dictionary))
else:
raise Failed(f"{self.Type} Error: {method_name} had no valid fields")
2021-07-23 18:45:49 +00:00
def _letterboxd(self, method_name, method_data):
if method_name.startswith("letterboxd_list"):
2022-03-31 06:23:48 +00:00
letterboxd_lists = self.config.Letterboxd.validate_letterboxd_lists(self.Type, method_data, self.language)
2021-07-23 18:45:49 +00:00
for letterboxd_list in letterboxd_lists:
2021-07-29 02:26:39 +00:00
self.builders.append(("letterboxd_list", letterboxd_list))
2021-07-23 18:45:49 +00:00
if method_name.endswith("_details"):
2022-03-31 06:23:48 +00:00
self.summaries[method_name] = self.config.Letterboxd.get_list_description(letterboxd_lists[0]["url"], self.language)
2021-07-23 18:45:49 +00:00
def _mal(self, method_name, method_data):
if method_name == "mal_id":
for mal_id in util.get_int_list(method_data, "MyAnimeList ID"):
2021-07-29 02:26:39 +00:00
self.builders.append((method_name, mal_id))
2021-07-23 18:45:49 +00:00
elif method_name in ["mal_all", "mal_airing", "mal_upcoming", "mal_tv", "mal_ova", "mal_movie", "mal_special", "mal_popular", "mal_favorite", "mal_suggested"]:
2022-01-28 18:36:21 +00:00
self.builders.append((method_name, util.parse(self.Type, method_name, method_data, datatype="int", default=10, maximum=100 if method_name == "mal_suggested" else 500)))
2022-05-03 18:44:19 +00:00
elif method_name in ["mal_season", "mal_userlist", "mal_search"]:
2022-02-13 22:47:08 +00:00
for dict_data in util.parse(self.Type, method_name, method_data, datatype="listdict"):
dict_methods = {dm.lower(): dm for dm in dict_data}
2021-07-23 18:45:49 +00:00
if method_name == "mal_season":
if self.current_time.month in [1, 2, 3]: default_season = "winter"
elif self.current_time.month in [4, 5, 6]: default_season = "spring"
elif self.current_time.month in [7, 8, 9]: default_season = "summer"
else: default_season = "fall"
2022-03-03 14:43:00 +00:00
season = util.parse(self.Type, "season", dict_data, methods=dict_methods, parent=method_name, default=default_season, options=util.seasons)
if season == "current":
season = default_season
self.builders.append((method_name, {
2022-03-03 14:43:00 +00:00
"season": season,
2022-01-28 18:36:21 +00:00
"sort_by": util.parse(self.Type, "sort_by", dict_data, methods=dict_methods, parent=method_name, default="members", options=mal.season_sort_options, translation=mal.season_sort_translation),
"year": util.parse(self.Type, "year", dict_data, datatype="int", methods=dict_methods, default=self.current_year, parent=method_name, minimum=1917, maximum=self.current_year + 1),
"limit": util.parse(self.Type, "limit", dict_data, datatype="int", methods=dict_methods, default=100, parent=method_name, maximum=500),
"starting_only": util.parse(self.Type, "starting_only", dict_data, datatype="bool", methods=dict_methods, default=False, parent=method_name)
}))
2021-07-23 18:45:49 +00:00
elif method_name == "mal_userlist":
self.builders.append((method_name, {
2022-01-28 18:36:21 +00:00
"username": util.parse(self.Type, "username", dict_data, methods=dict_methods, parent=method_name),
"status": util.parse(self.Type, "status", dict_data, methods=dict_methods, parent=method_name, default="all", options=mal.userlist_status),
"sort_by": util.parse(self.Type, "sort_by", dict_data, methods=dict_methods, parent=method_name, default="score", options=mal.userlist_sort_options, translation=mal.userlist_sort_translation),
"limit": util.parse(self.Type, "limit", dict_data, datatype="int", methods=dict_methods, default=100, parent=method_name, maximum=1000)
}))
2022-05-03 18:44:19 +00:00
elif method_name == "mal_search":
final_attributes = {}
final_text = "MyAnimeList Search"
if "sort_by" in dict_methods:
sort = util.parse(self.Type, "sort_by", dict_data, methods=dict_methods, parent=method_name, options=mal.search_combos)
sort_type, sort_direction = sort.split(".")
final_text += f"\nSorted By: {sort}"
final_attributes["order_by"] = sort_type
final_attributes["sort"] = sort_direction
limit = 0
if "limit" in dict_methods:
limit = util.parse(self.Type, "limit", dict_data, datatype="int", default=0, methods=dict_methods, parent=method_name)
final_text += f"\nLimit: {limit if limit else 'None'}"
if "query" in dict_methods:
final_attributes["q"] = util.parse(self.Type, "query", dict_data, methods=dict_methods, parent=method_name)
final_text += f"\nQuery: {final_attributes['q']}"
if "prefix" in dict_methods:
final_attributes["letter"] = util.parse(self.Type, "prefix", dict_data, methods=dict_methods, parent=method_name)
final_text += f"\nPrefix: {final_attributes['letter']}"
if "type" in dict_methods:
type_list = util.parse(self.Type, "type", dict_data, datatype="commalist", methods=dict_methods, parent=method_name, options=mal.search_types)
final_attributes["type"] = ",".join(type_list)
final_text += f"\nType: {' or '.join(type_list)}"
if "status" in dict_methods:
final_attributes["status"] = util.parse(self.Type, "status", dict_data, methods=dict_methods, parent=method_name, options=mal.search_status)
final_text += f"\nStatus: {final_attributes['status']}"
if "genre" in dict_methods:
2022-12-13 17:15:50 +00:00
genre_str = str(util.parse(self.Type, "genre", dict_data, methods=dict_methods, parent=method_name))
2023-01-24 19:07:42 +00:00
out_text, out_ints = util.parse_and_or(self.Type, 'Genre', genre_str, self.config.MyAnimeList.genres)
final_text += f"\nGenre: {out_text}"
final_attributes["genres"] = out_ints
2022-05-03 18:44:19 +00:00
if "genre.not" in dict_methods:
2022-12-13 17:15:50 +00:00
genre_str = str(util.parse(self.Type, "genre.not", dict_data, methods=dict_methods, parent=method_name))
2023-01-24 19:07:42 +00:00
out_text, out_ints = util.parse_and_or(self.Type, 'Genre', genre_str, self.config.MyAnimeList.genres)
final_text += f"\nNot Genre: {out_text}"
final_attributes["genres_exclude"] = out_ints
2022-05-03 18:44:19 +00:00
if "studio" in dict_methods:
2022-12-13 17:15:50 +00:00
studio_str = str(util.parse(self.Type, "studio", dict_data, methods=dict_methods, parent=method_name))
2023-01-24 19:07:42 +00:00
out_text, out_ints = util.parse_and_or(self.Type, 'Studio', studio_str, self.config.MyAnimeList.studios)
final_text += f"\nStudio: {out_text}"
final_attributes["producers"] = out_ints
2022-05-03 18:44:19 +00:00
if "content_rating" in dict_methods:
final_attributes["rating"] = util.parse(self.Type, "content_rating", dict_data, methods=dict_methods, parent=method_name, options=mal.search_ratings)
final_text += f"\nContent Rating: {final_attributes['rating']}"
if "score.gte" in dict_methods:
final_attributes["min_score"] = util.parse(self.Type, "score.gte", dict_data, datatype="float", methods=dict_methods, parent=method_name, minimum=0, maximum=10)
final_text += f"\nScore Greater Than or Equal: {final_attributes['min_score']}"
elif "score.gt" in dict_methods:
original_score = util.parse(self.Type, "score.gt", dict_data, datatype="float", methods=dict_methods, parent=method_name, minimum=0, maximum=10)
final_attributes["min_score"] = original_score + 0.01
final_text += f"\nScore Greater Than: {original_score}"
if "score.lte" in dict_methods:
final_attributes["max_score"] = util.parse(self.Type, "score.lte", dict_data, datatype="float", methods=dict_methods, parent=method_name, minimum=0, maximum=10)
final_text += f"\nScore Less Than or Equal: {final_attributes['max_score']}"
elif "score.lt" in dict_methods:
original_score = util.parse(self.Type, "score.lt", dict_data, datatype="float", methods=dict_methods, parent=method_name, minimum=0, maximum=10)
final_attributes["max_score"] = original_score - 0.01
final_text += f"\nScore Less Than: {original_score}"
if "min_score" in final_attributes and "max_score" in final_attributes and final_attributes["max_score"] <= final_attributes["min_score"]:
2022-05-11 15:55:07 +00:00
raise Failed(f"{self.Type} Error: mal_search score.lte/score.lt attribute must be greater than score.gte/score.gt")
2022-05-03 18:44:19 +00:00
if "sfw" in dict_methods:
sfw = util.parse(self.Type, "sfw", dict_data, datatype="bool", methods=dict_methods, parent=method_name)
if sfw:
final_attributes["sfw"] = 1
final_text += f"\nSafe for Work: {final_attributes['sfw']}"
2022-05-03 18:44:19 +00:00
if not final_attributes:
raise Failed(f"{self.Type} Error: no mal_search attributes found")
self.builders.append((method_name, (final_attributes, final_text, limit)))
2021-08-17 00:07:35 +00:00
elif method_name in ["mal_genre", "mal_studio"]:
2022-12-07 19:44:15 +00:00
logger.warning(f"Config Warning: {method_name} will run as a mal_search")
item_list = util.parse(self.Type, method_name[4:], method_data, datatype="commalist")
all_items = self.config.MyAnimeList.genres if method_name == "mal_genre" else self.config.MyAnimeList.studios
final_items = [str(all_items[i]) for i in item_list if i in all_items]
final_text = f"MyAnimeList Search\n{method_name[4:].capitalize()}: {' or '.join([str(all_items[i]) for i in final_items])}"
self.builders.append(("mal_search", ({"genres" if method_name == "mal_genre" else "producers": ",".join(final_items)}, final_text, 0)))
2021-07-23 18:45:49 +00:00
2024-01-15 21:38:54 +00:00
def _mojo(self, method_name, method_data):
for dict_data in util.parse(self.Type, method_name, method_data, datatype="listdict"):
dict_methods = {dm.lower(): dm for dm in dict_data}
final = {}
if method_name == "mojo_record":
final["chart"] = util.parse(self.Type, "chart", dict_data, methods=dict_methods, parent=method_name, options=mojo.top_options)
elif method_name == "mojo_world":
if "year" not in dict_methods:
raise Failed(f"{self.Type} Error: {method_name} year attribute not found")
og_year = dict_data[dict_methods["year"]]
if not og_year:
raise Failed(f"{self.Type} Error: {method_name} year attribute is blank")
if og_year == "current":
final["year"] = str(self.current_year) # noqa
elif str(og_year).startswith("current-"):
try:
final["year"] = str(self.current_year - int(og_year.split("-")[1])) # noqa
if final["year"] not in mojo.year_options:
raise Failed(f"{self.Type} Error: {method_name} year attribute final value must be 1977 or greater: {og_year}")
except ValueError:
raise Failed(f"{self.Type} Error: {method_name} year attribute invalid: {og_year}")
else:
final["year"] = util.parse(self.Type, "year", dict_data, methods=dict_methods, parent=method_name, options=mojo.year_options)
elif method_name == "mojo_all_time":
final["chart"] = util.parse(self.Type, "chart", dict_data, methods=dict_methods, parent=method_name, options=mojo.chart_options)
final["content_rating_filter"] = util.parse(self.Type, "content_rating_filter", dict_data, methods=dict_methods, parent=method_name, options=mojo.content_rating_options) if "content_rating_filter" in dict_methods else None
elif method_name == "mojo_never":
final["chart"] = util.parse(self.Type, "chart", dict_data, methods=dict_methods, parent=method_name, default="domestic", options=self.config.BoxOfficeMojo.never_options)
final["never"] = str(util.parse(self.Type, "never", dict_data, methods=dict_methods, parent=method_name, default="1", options=mojo.never_in_options)) if "never" in dict_methods else "1"
elif method_name in ["mojo_domestic", "mojo_international"]:
dome = method_name == "mojo_domestic"
final["range"] = util.parse(self.Type, "range", dict_data, methods=dict_methods, parent=method_name, options=mojo.dome_range_options if dome else mojo.intl_range_options)
if not dome:
final["chart"] = util.parse(self.Type, "chart", dict_data, methods=dict_methods, parent=method_name, default="international", options=self.config.BoxOfficeMojo.intl_options)
chart_date = self.current_time
if final["range"] != "daily":
_m = "range_data" if final["range"] == "yearly" and "year" not in dict_methods and "range_data" in dict_methods else "year"
if _m not in dict_methods:
raise Failed(f"{self.Type} Error: {method_name} {_m} attribute not found")
og_year = dict_data[dict_methods[_m]]
if not og_year:
raise Failed(f"{self.Type} Error: {method_name} {_m} attribute is blank")
if str(og_year).startswith("current-"):
try:
chart_date = self.current_time - relativedelta(years=int(og_year.split("-")[1]))
except ValueError:
raise Failed(f"{self.Type} Error: {method_name} {_m} attribute invalid: {og_year}")
else:
_y = util.parse(self.Type, _m, dict_data, methods=dict_methods, parent=method_name, default="current", options=mojo.year_options)
if _y != "current":
chart_date = self.current_time - relativedelta(years=self.current_time.year - _y)
if final["range"] != "yearly":
if "range_data" not in dict_methods:
raise Failed(f"{self.Type} Error: {method_name} range_data attribute not found")
og_data = dict_data[dict_methods["range_data"]]
if not og_data:
raise Failed(f"{self.Type} Error: {method_name} range_data attribute is blank")
if final["range"] == "holiday":
final["range_data"] = util.parse(self.Type, "range_data", dict_data, methods=dict_methods, parent=method_name, options=mojo.holiday_options)
elif final["range"] == "daily":
if og_data == "current":
final["range_data"] = datetime.strftime(self.current_time, "%Y-%m-%d") # noqa
elif str(og_data).startswith("current-"):
try:
final["range_data"] = datetime.strftime(self.current_time - timedelta(days=int(og_data.split("-")[1])), "%Y-%m-%d") # noqa
except ValueError:
raise Failed(f"{self.Type} Error: {method_name} range_data attribute invalid: {og_data}")
else:
final["range_data"] = util.parse(self.Type, "range_data", dict_data, methods=dict_methods, parent=method_name, default="current", datatype="date", date_return="%Y-%m-%d")
if final["range_data"] == "current":
final["range_data"] = datetime.strftime(self.current_time, "%Y-%m-%d") # noqa
elif final["range"] in ["weekend", "weekly"]:
if str(og_data).startswith("current-"):
try:
final_date = chart_date - timedelta(weeks=int(og_data.split("-")[1]))
final_iso = final_date.isocalendar()
final["range_data"] = final_iso.week
final["year"] = final_iso.year
except ValueError:
raise Failed(f"{self.Type} Error: {method_name} range_data attribute invalid: {og_data}")
else:
_v = util.parse(self.Type, "range_data", dict_data, methods=dict_methods, parent=method_name, default="current", options=["current"] + [str(i) for i in range(1, 54)])
current_iso = chart_date.isocalendar()
final["range_data"] = current_iso.week if _v == "current" else _v
final["year"] = current_iso.year
elif final["range"] == "monthly":
if str(og_data).startswith("current-"):
try:
final_date = chart_date - relativedelta(months=int(og_data.split("-")[1]))
final["range_data"] = final_date.month
final["year"] = final_date.year
except ValueError:
raise Failed(f"{self.Type} Error: {method_name} range_data attribute invalid: {og_data}")
else:
_v = util.parse(self.Type, "range_data", dict_data, methods=dict_methods, parent=method_name, default="current", options=["current"] + util.lower_months)
final["range_data"] = chart_date.month if _v == "current" else util.lower_months[_v]
elif final["range"] == "quarterly":
if str(og_data).startswith("current-"):
try:
final_date = chart_date - relativedelta(months=int(og_data.split("-")[1]) * 3)
final["range_data"] = mojo.quarters[final_date.month]
final["year"] = final_date.year
except ValueError:
raise Failed(f"{self.Type} Error: {method_name} range_data attribute invalid: {og_data}")
else:
_v = util.parse(self.Type, "range_data", dict_data, methods=dict_methods, parent=method_name, default="current", options=mojo.quarter_options)
final["range_data"] = mojo.quarters[chart_date.month] if _v == "current" else _v
elif final["range"] == "season":
_v = util.parse(self.Type, "range_data", dict_data, methods=dict_methods, parent=method_name, default="current", options=mojo.season_options)
final["range_data"] = mojo.seasons[chart_date.month] if _v == "current" else _v
else:
final["range_data"] = chart_date.year
if "year" not in final:
final["year"] = chart_date.year
if final["year"] < 1977:
raise Failed(f"{self.Type} Error: {method_name} attribute final date value must be on year 1977 or greater: {final['year']}")
final["limit"] = util.parse(self.Type, "limit", dict_data, methods=dict_methods, parent=method_name, default=0, datatype="int", maximum=1000) if "limit" in dict_methods else 0
self.builders.append((method_name, final))
2021-07-23 18:45:49 +00:00
def _plex(self, method_name, method_data):
2022-01-24 22:36:40 +00:00
if method_name in ["plex_all", "plex_pilots"]:
2022-07-26 19:58:53 +00:00
self.builders.append((method_name, self.builder_level))
2022-09-08 20:11:11 +00:00
elif method_name == "plex_watchlist":
if method_data not in plex.watchlist_sorts:
logger.warning(f"{self.Type} Warning: Watchlist sort: {method_data} invalid defaulting to added.asc")
self.builders.append((method_name, method_data if method_data in plex.watchlist_sorts else "added.asc"))
2021-07-23 18:45:49 +00:00
elif method_name in ["plex_search", "plex_collectionless"]:
2022-02-13 22:47:08 +00:00
for dict_data in util.parse(self.Type, method_name, method_data, datatype="listdict"):
dict_methods = {dm.lower(): dm for dm in dict_data}
2021-07-23 18:45:49 +00:00
if method_name == "plex_search":
2022-11-11 16:59:39 +00:00
try:
self.builders.append((method_name, self.build_filter("plex_search", dict_data)))
except FilterFailed as e:
if self.ignore_blank_results:
raise
else:
raise Failed(str(e))
2021-07-23 18:45:49 +00:00
elif method_name == "plex_collectionless":
2022-02-01 03:19:49 +00:00
prefix_list = util.parse(self.Type, "exclude_prefix", dict_data, datatype="list", methods=dict_methods) if "exclude_prefix" in dict_methods else []
exact_list = util.parse(self.Type, "exclude", dict_data, datatype="list", methods=dict_methods) if "exclude" in dict_methods else []
2021-07-23 18:45:49 +00:00
if len(prefix_list) == 0 and len(exact_list) == 0:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: you must have at least one exclusion")
2021-07-23 18:45:49 +00:00
exact_list.append(self.name)
self.builders.append((method_name, {"exclude_prefix": prefix_list, "exclude": exact_list}))
2021-07-23 18:45:49 +00:00
else:
2022-11-11 16:59:39 +00:00
try:
self.builders.append(("plex_search", self.build_filter("plex_search", {"any": {method_name: method_data}})))
except FilterFailed as e:
if self.ignore_blank_results:
raise
else:
raise Failed(str(e))
2021-07-23 18:45:49 +00:00
2022-03-19 05:16:25 +00:00
def _reciperr(self, method_name, method_data):
if method_name == "reciperr_list":
for reciperr_list in self.config.Reciperr.validate_list(method_data):
self.builders.append((method_name, reciperr_list))
elif method_name == "stevenlu_popular":
self.builders.append((method_name, util.parse(self.Type, method_name, method_data, "bool")))
2021-08-01 01:23:17 +00:00
2022-01-15 22:40:59 +00:00
def _mdblist(self, method_name, method_data):
2022-03-03 14:43:00 +00:00
for mdb_dict in self.config.Mdblist.validate_mdblist_lists(self.Type, method_data):
2022-01-24 09:20:07 +00:00
self.builders.append((method_name, mdb_dict))
2022-01-15 22:40:59 +00:00
2021-07-23 18:45:49 +00:00
def _tautulli(self, method_name, method_data):
2022-02-13 22:47:08 +00:00
for dict_data in util.parse(self.Type, method_name, method_data, datatype="listdict"):
dict_methods = {dm.lower(): dm for dm in dict_data}
final_dict = {
2021-07-23 18:45:49 +00:00
"list_type": "popular" if method_name == "tautulli_popular" else "watched",
2022-01-28 18:36:21 +00:00
"list_days": util.parse(self.Type, "list_days", dict_data, datatype="int", methods=dict_methods, default=30, parent=method_name),
"list_size": util.parse(self.Type, "list_size", dict_data, datatype="int", methods=dict_methods, default=10, parent=method_name),
"list_minimum": util.parse(self.Type, "list_minimum", dict_data, datatype="int", methods=dict_methods, default=0, parent=method_name)
}
2023-04-28 03:43:26 +00:00
buff = final_dict["list_size"] * 3
if self.library.Tautulli.has_section:
2023-04-28 03:43:26 +00:00
buff = 0
elif "list_buffer" in dict_methods:
2023-04-28 03:43:26 +00:00
buff = util.parse(self.Type, "list_buffer", dict_data, datatype="int", methods=dict_methods, default=buff, parent=method_name)
final_dict["list_buffer"] = buff
self.builders.append((method_name, final_dict))
2021-07-23 18:45:49 +00:00
def _tmdb(self, method_name, method_data):
if method_name == "tmdb_discover":
2022-02-13 22:47:08 +00:00
for dict_data in util.parse(self.Type, method_name, method_data, datatype="listdict"):
dict_methods = {dm.lower(): dm for dm in dict_data}
2022-01-28 18:36:21 +00:00
new_dictionary = {"limit": util.parse(self.Type, "limit", dict_data, datatype="int", methods=dict_methods, default=100, parent=method_name)}
2021-08-14 22:59:35 +00:00
for discover_method, discover_data in dict_data.items():
2023-12-07 19:49:32 +00:00
lower_method = str(discover_method).lower()
discover_attr, modifier = os.path.splitext(lower_method)
2021-08-14 22:59:35 +00:00
if discover_data is None:
2022-01-12 16:12:38 +00:00
raise Failed(f"{self.Type} Error: {method_name} {discover_method} attribute is blank")
2023-12-07 19:49:32 +00:00
elif discover_method.lower() not in tmdb.discover_all:
2022-01-12 16:12:38 +00:00
raise Failed(f"{self.Type} Error: {method_name} {discover_method} attribute not supported")
2021-08-14 22:59:35 +00:00
elif self.library.is_movie and discover_attr in tmdb.discover_tv_only:
2022-01-12 16:12:38 +00:00
raise Failed(f"{self.Type} Error: {method_name} {discover_method} attribute only works for show libraries")
2021-08-14 22:59:35 +00:00
elif self.library.is_show and discover_attr in tmdb.discover_movie_only:
2022-01-12 16:12:38 +00:00
raise Failed(f"{self.Type} Error: {method_name} {discover_method} attribute only works for movie libraries")
2022-03-21 17:40:32 +00:00
elif discover_attr == "region":
2024-03-19 20:31:25 +00:00
new_dictionary[discover_attr] = util.parse(self.Type, discover_method, discover_data.upper(), parent=method_name, regex=("^[A-Z]{2}$", "US"))
2022-01-24 06:42:56 +00:00
elif discover_attr == "sort_by":
2021-08-14 22:59:35 +00:00
options = tmdb.discover_movie_sort if self.library.is_movie else tmdb.discover_tv_sort
2023-12-07 19:49:32 +00:00
new_dictionary[lower_method] = util.parse(self.Type, discover_method, discover_data, parent=method_name, options=options)
2021-08-14 22:59:35 +00:00
elif discover_attr == "certification_country":
if "certification" in dict_data or "certification.lte" in dict_data or "certification.gte" in dict_data:
2023-12-07 19:49:32 +00:00
new_dictionary[lower_method] = discover_data
2021-07-23 18:45:49 +00:00
else:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: {method_name} {discover_attr} attribute: must be used with either certification, certification.lte, or certification.gte")
2021-08-14 22:59:35 +00:00
elif discover_attr == "certification":
if "certification_country" in dict_data:
2023-12-07 19:49:32 +00:00
new_dictionary[lower_method] = discover_data
2021-08-14 22:59:35 +00:00
else:
2022-01-12 16:12:38 +00:00
raise Failed(f"{self.Type} Error: {method_name} {discover_method} attribute: must be used with certification_country")
2021-11-26 08:24:36 +00:00
elif discover_attr == "watch_region":
if "with_watch_providers" in dict_data or "without_watch_providers" in dict_data or "with_watch_monetization_types" in dict_data:
2024-03-19 20:31:25 +00:00
new_dictionary[lower_method] = discover_data.upper()
2021-11-26 08:24:36 +00:00
else:
2022-01-12 16:12:38 +00:00
raise Failed(f"{self.Type} Error: {method_name} {discover_method} attribute: must be used with either with_watch_providers, without_watch_providers, or with_watch_monetization_types")
2021-11-26 08:24:36 +00:00
elif discover_attr == "with_watch_monetization_types":
if "watch_region" in dict_data:
2023-12-07 19:49:32 +00:00
new_dictionary[lower_method] = util.parse(self.Type, discover_method, discover_data, parent=method_name, options=tmdb.discover_monetization_types)
2021-11-26 08:24:36 +00:00
else:
2022-01-12 16:12:38 +00:00
raise Failed(f"{self.Type} Error: {method_name} {discover_method} attribute: must be used with watch_region")
elif discover_attr in tmdb.discover_booleans:
2023-12-07 19:49:32 +00:00
new_dictionary[lower_method] = util.parse(self.Type, discover_method, discover_data, datatype="bool", parent=method_name)
elif discover_attr == "vote_average":
2023-12-07 19:49:32 +00:00
new_dictionary[lower_method] = util.parse(self.Type, discover_method, discover_data, datatype="float", parent=method_name)
2021-11-26 08:24:36 +00:00
elif discover_attr == "with_status":
2023-12-07 19:49:32 +00:00
new_dictionary[lower_method] = util.parse(self.Type, discover_method, discover_data, datatype="int", parent=method_name, minimum=0, maximum=5)
2021-11-26 08:24:36 +00:00
elif discover_attr == "with_type":
2023-12-07 19:49:32 +00:00
new_dictionary[lower_method] = util.parse(self.Type, discover_method, discover_data, datatype="int", parent=method_name, minimum=0, maximum=6)
2022-03-21 17:40:32 +00:00
elif discover_attr in tmdb.discover_dates:
2023-12-07 19:49:32 +00:00
new_dictionary[lower_method] = util.parse(self.Type, discover_method, discover_data, datatype="date", parent=method_name, date_return="%m/%d/%Y")
elif discover_attr in tmdb.discover_years:
2023-12-07 19:49:32 +00:00
new_dictionary[lower_method] = util.parse(self.Type, discover_method, discover_data, datatype="int", parent=method_name, minimum=1800, maximum=self.current_year + 1)
elif discover_attr in tmdb.discover_ints:
2023-12-07 19:49:32 +00:00
new_dictionary[lower_method] = util.parse(self.Type, discover_method, discover_data, datatype="int", parent=method_name)
2022-03-21 17:40:32 +00:00
elif discover_attr in tmdb.discover_strings:
2023-12-07 19:49:32 +00:00
new_dictionary[lower_method] = discover_data
2021-08-14 22:59:35 +00:00
elif discover_attr != "limit":
2022-01-12 16:12:38 +00:00
raise Failed(f"{self.Type} Error: {method_name} {discover_method} attribute not supported")
2021-07-23 18:45:49 +00:00
if len(new_dictionary) > 1:
2021-07-29 02:26:39 +00:00
self.builders.append((method_name, new_dictionary))
2021-07-23 18:45:49 +00:00
else:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: {method_name} had no valid fields")
elif method_name in tmdb.int_builders:
2022-01-28 18:36:21 +00:00
self.builders.append((method_name, util.parse(self.Type, method_name, method_data, datatype="int", default=10)))
2021-07-23 18:45:49 +00:00
else:
values = self.config.TMDb.validate_tmdb_ids(method_data, method_name)
if method_name in tmdb.details_builders:
2021-07-26 15:46:36 +00:00
if method_name.startswith(("tmdb_collection", "tmdb_movie", "tmdb_show")):
2021-07-23 18:45:49 +00:00
item = self.config.TMDb.get_movie_show_or_collection(values[0], self.library.is_movie)
2022-01-21 16:34:19 +00:00
if item.overview:
2021-07-23 18:45:49 +00:00
self.summaries[method_name] = item.overview
2022-01-21 16:34:19 +00:00
if item.backdrop_url:
self.backgrounds[method_name] = item.backdrop_url
2022-04-30 21:58:46 +00:00
if item.poster_url:
2022-01-21 16:34:19 +00:00
self.posters[method_name] = item.poster_url
2021-07-26 15:46:36 +00:00
elif method_name.startswith(("tmdb_actor", "tmdb_crew", "tmdb_director", "tmdb_producer", "tmdb_writer")):
2021-07-23 18:45:49 +00:00
item = self.config.TMDb.get_person(values[0])
2022-01-21 16:34:19 +00:00
if item.biography:
2021-07-23 18:45:49 +00:00
self.summaries[method_name] = item.biography
2022-01-21 16:34:19 +00:00
if item.profile_path:
self.posters[method_name] = item.profile_url
2021-07-26 15:46:36 +00:00
elif method_name.startswith("tmdb_list"):
2021-07-23 18:45:49 +00:00
item = self.config.TMDb.get_list(values[0])
2022-01-21 16:34:19 +00:00
if item.description:
2021-07-23 18:45:49 +00:00
self.summaries[method_name] = item.description
2022-12-19 21:36:51 +00:00
if item.poster_url:
self.posters[method_name] = item.poster_url
2021-07-23 18:45:49 +00:00
for value in values:
self.builders.append((method_name[:-8] if method_name in tmdb.details_builders else method_name, value))
2021-07-23 18:45:49 +00:00
def _trakt(self, method_name, method_data):
if method_name.startswith("trakt_list"):
2022-03-27 06:26:08 +00:00
trakt_lists = self.config.Trakt.validate_list(method_data)
2021-07-23 18:45:49 +00:00
for trakt_list in trakt_lists:
2021-07-29 02:26:39 +00:00
self.builders.append(("trakt_list", trakt_list))
2021-07-23 18:45:49 +00:00
if method_name.endswith("_details"):
2023-09-01 14:11:00 +00:00
try:
self.summaries[method_name] = self.config.Trakt.list_description(trakt_lists[0])
except Failed as e:
logger.error(f"Trakt Error: List description not found: {e}")
2021-11-13 23:51:12 +00:00
elif method_name == "trakt_boxoffice":
2022-01-28 18:36:21 +00:00
if util.parse(self.Type, method_name, method_data, datatype="bool", default=False):
2021-11-13 23:51:12 +00:00
self.builders.append((method_name, 10))
else:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: {method_name} must be set to true")
2022-03-27 06:26:08 +00:00
elif method_name == "trakt_recommendations":
self.builders.append((method_name, util.parse(self.Type, method_name, method_data, datatype="int", default=10, maximum=100)))
elif method_name == "sync_to_trakt_list":
if method_data not in self.config.Trakt.slugs:
raise Failed(f"{self.Type} Error: {method_data} invalid. Options {', '.join(self.config.Trakt.slugs)}")
self.sync_to_trakt_list = method_data
2022-05-09 15:22:41 +00:00
elif method_name == "sync_missing_to_trakt_list":
self.sync_missing_to_trakt_list = util.parse(self.Type, method_name, method_data, datatype="bool", default=False)
2021-11-13 23:51:12 +00:00
elif method_name in trakt.builders:
2022-03-27 06:26:08 +00:00
if method_name in ["trakt_chart", "trakt_userlist"]:
trakt_dicts = method_data
final_method = method_name
elif method_name in ["trakt_watchlist", "trakt_collection"]:
trakt_dicts = []
for trakt_user in util.get_list(method_data, split=False):
2024-01-24 18:41:53 +00:00
trakt_dicts.append({"userlist": method_name[6:], "user": trakt_user})
2022-03-27 06:26:08 +00:00
final_method = "trakt_userlist"
else:
terms = method_name.split("_")
trakt_dicts = {
"chart": terms[1],
"limit": util.parse(self.Type, method_name, method_data, datatype="int", default=10),
2022-03-27 06:26:08 +00:00
"time_period": terms[2] if len(terms) > 2 else None
}
final_method = "trakt_chart"
2022-03-28 19:30:46 +00:00
if method_name != final_method:
logger.warning(f"{self.Type} Warning: {method_name} will run as {final_method}")
2024-02-28 20:40:17 +00:00
for trakt_dict in self.config.Trakt.validate_chart(self.Type, final_method, trakt_dicts, self.library.is_movie):
2022-03-29 19:16:05 +00:00
self.builders.append((final_method, trakt_dict))
2021-07-23 18:45:49 +00:00
def _tvdb(self, method_name, method_data):
values = util.get_list(method_data)
if method_name.endswith("_details"):
if method_name.startswith(("tvdb_movie", "tvdb_show")):
2022-05-05 22:05:16 +00:00
item = self.config.TVDb.get_tvdb_obj(values[0], is_movie=method_name.startswith("tvdb_movie"))
2024-04-03 13:13:28 +00:00
if item.summary:
self.summaries[method_name] = item.summary
2022-05-05 22:05:16 +00:00
if item.background_url:
self.backgrounds[method_name] = item.background_url
if item.poster_url:
self.posters[method_name] = item.poster_url
2021-07-23 18:45:49 +00:00
elif method_name.startswith("tvdb_list"):
2022-12-19 21:36:51 +00:00
description, poster = self.config.TVDb.get_list_description(values[0])
if description:
self.summaries[method_name] = description
if poster:
self.posters[method_name] = poster
2021-07-23 18:45:49 +00:00
for value in values:
2021-07-29 02:26:39 +00:00
self.builders.append((method_name[:-8] if method_name.endswith("_details") else method_name, value))
2021-07-23 18:45:49 +00:00
def _filters(self, method_name, method_data):
2022-10-03 19:32:50 +00:00
for dict_data in util.parse(self.Type, method_name, method_data, datatype="listdict"):
dict_methods = {dm.lower(): dm for dm in dict_data}
current_filters = []
validate = True
if "validate" in dict_methods:
if dict_data[dict_methods["validate"]] is None:
raise Failed(f"{self.Type} Error: validate filter attribute is blank")
if not isinstance(dict_data[dict_methods["validate"]], bool):
raise Failed(f"{self.Type} Error: validate filter attribute must be either true or false")
validate = dict_data.pop(dict_methods["validate"])
for filter_method, filter_data in dict_data.items():
filter_attr, modifier, filter_final = self.library.split(filter_method)
message = None
if filter_final not in all_filters:
message = f"{self.Type} Error: {filter_final} is not a valid filter attribute"
elif self.builder_level in filters and filter_attr not in filters[self.builder_level]:
message = f"{self.Type} Error: {filter_final} is not a valid {self.builder_level} filter attribute"
elif filter_final is None:
message = f"{self.Type} Error: {filter_final} filter attribute is blank"
2021-07-23 18:45:49 +00:00
else:
2023-02-01 19:51:22 +00:00
try:
final_data = self.validate_attribute(filter_attr, modifier, f"{filter_final} filter", filter_data, validate)
except FilterFailed as e:
raise Failed(e)
2022-10-24 06:17:49 +00:00
if self.builder_level in ["show", "season", "artist", "album"] and filter_attr in sub_filters:
2022-10-03 19:32:50 +00:00
current_filters.append(("episodes" if self.builder_level in ["show", "season"] else "tracks", {filter_final: final_data, "percentage": self.default_percent}))
else:
current_filters.append((filter_final, final_data))
if message:
if validate:
raise Failed(message)
else:
logger.error(message)
if current_filters:
self.filters.append(current_filters)
2023-04-17 01:39:36 +00:00
self.has_tmdb_filters = any([str(k).split(".")[0] in tmdb_filters for f in self.filters for k, v in f])
self.has_imdb_filters = any([str(k).split(".")[0] in imdb_filters for f in self.filters for k, v in f])
2021-07-23 18:45:49 +00:00
2021-12-14 05:51:36 +00:00
def gather_ids(self, method, value):
2022-02-02 15:38:38 +00:00
expired = None
list_key = None
if self.config.Cache and self.details["cache_builders"]:
list_key, expired = self.config.Cache.query_list_cache(f"{self.library.type}:{method}", str(value), self.details["cache_builders"])
2022-02-02 15:38:38 +00:00
if list_key and expired is False:
2022-02-03 20:21:52 +00:00
logger.info(f"Builder: {method} loaded from Cache")
2022-02-02 15:38:38 +00:00
return self.config.Cache.query_list_ids(list_key)
2021-12-14 05:51:36 +00:00
if "plex" in method:
2022-09-08 20:11:11 +00:00
ids = self.library.get_rating_keys(method, value, self.playlist)
2021-12-14 05:51:36 +00:00
elif "tautulli" in method:
ids = self.library.Tautulli.get_rating_keys(value, self.playlist)
2021-12-14 05:51:36 +00:00
elif "anidb" in method:
2022-03-15 00:11:16 +00:00
anidb_ids = self.config.AniDB.get_anidb_ids(method, value)
2022-02-02 15:38:38 +00:00
ids = self.config.Convert.anidb_to_ids(anidb_ids, self.library)
2021-12-14 05:51:36 +00:00
elif "anilist" in method:
anilist_ids = self.config.AniList.get_anilist_ids(method, value)
2022-02-02 15:38:38 +00:00
ids = self.config.Convert.anilist_to_ids(anilist_ids, self.library)
2021-12-14 05:51:36 +00:00
elif "mal" in method:
mal_ids = self.config.MyAnimeList.get_mal_ids(method, value)
2022-02-02 15:38:38 +00:00
ids = self.config.Convert.myanimelist_to_ids(mal_ids, self.library)
2021-12-14 05:51:36 +00:00
elif "tvdb" in method:
2022-02-02 15:38:38 +00:00
ids = self.config.TVDb.get_tvdb_ids(method, value)
2021-12-14 05:51:36 +00:00
elif "imdb" in method:
2022-02-02 15:38:38 +00:00
ids = self.config.IMDb.get_imdb_ids(method, value, self.language)
2021-12-14 05:51:36 +00:00
elif "icheckmovies" in method:
2022-03-19 05:16:25 +00:00
ids = self.config.ICheckMovies.get_imdb_ids(method, value, self.language)
2021-12-14 05:51:36 +00:00
elif "letterboxd" in method:
2022-02-02 15:38:38 +00:00
ids = self.config.Letterboxd.get_tmdb_ids(method, value, self.language)
2022-03-19 05:16:25 +00:00
elif "reciperr" in method or "stevenlu" in method:
ids = self.config.Reciperr.get_imdb_ids(method, value)
2024-01-15 21:38:54 +00:00
elif "mojo" in method:
ids = self.config.BoxOfficeMojo.get_imdb_ids(method, value)
2022-01-15 22:40:59 +00:00
elif "mdblist" in method:
2022-08-09 05:35:21 +00:00
ids = self.config.Mdblist.get_tmdb_ids(method, value, self.library.is_movie if not self.playlist else None)
2021-12-14 05:51:36 +00:00
elif "tmdb" in method:
2022-03-23 18:43:50 +00:00
ids = self.config.TMDb.get_tmdb_ids(method, value, self.library.is_movie, self.tmdb_region)
2021-12-14 05:51:36 +00:00
elif "trakt" in method:
2022-02-02 15:38:38 +00:00
ids = self.config.Trakt.get_trakt_ids(method, value, self.library.is_movie)
elif "radarr" in method:
ids = self.library.Radarr.get_tmdb_ids(method, value)
elif "sonarr" in method:
ids = self.library.Sonarr.get_tvdb_ids(method, value)
2021-12-14 05:51:36 +00:00
else:
2022-02-02 15:38:38 +00:00
ids = []
2021-12-14 05:51:36 +00:00
logger.error(f"{self.Type} Error: {method} method not supported")
2022-02-02 15:38:38 +00:00
if self.config.Cache and self.details["cache_builders"] and ids:
if list_key:
self.config.Cache.delete_list_ids(list_key)
list_key = self.config.Cache.update_list_cache(f"{self.library.type}:{method}", str(value), expired, self.details["cache_builders"])
2022-02-02 15:38:38 +00:00
self.config.Cache.update_list_ids(list_key, ids)
return ids
2021-12-14 05:51:36 +00:00
2022-02-08 22:57:36 +00:00
def filter_and_save_items(self, ids):
items = []
if len(ids) > 0:
total_ids = len(ids)
2021-07-21 17:40:05 +00:00
logger.debug("")
logger.debug(f"{total_ids} IDs Found")
logger.trace(f"IDs: {ids}")
2022-02-08 22:57:36 +00:00
logger.debug("")
for i, input_data in enumerate(ids, 1):
input_id, id_type = input_data
logger.ghost(f"Parsing ID {i}/{total_ids}")
2022-02-08 22:57:36 +00:00
rating_keys = []
if id_type == "ratingKey":
rating_keys = int(input_id)
elif id_type == "imdb":
if input_id not in self.ignore_imdb_ids:
found = False
for pl_library in self.libraries:
if input_id in pl_library.imdb_map:
found = True
rating_keys = pl_library.imdb_map[input_id]
break
2022-07-26 19:58:53 +00:00
if not found and (self.builder_level == "episode" or self.playlist or self.do_missing):
try:
_id, tmdb_type = self.config.Convert.imdb_to_tmdb(input_id, fail=True)
2022-07-26 19:58:53 +00:00
if tmdb_type == "episode" and (self.builder_level == "episode" or self.playlist):
2022-01-06 06:25:23 +00:00
try:
2022-02-08 22:57:36 +00:00
tmdb_id, season_num, episode_num = _id.split("_")
tvdb_id = self.config.Convert.tmdb_to_tvdb(tmdb_id, fail=True)
tvdb_id = int(tvdb_id)
except Failed as e:
try:
if not self.config.OMDb:
raise Failed("")
if self.config.OMDb.limit:
raise Failed(" and OMDb limit reached.")
omdb_item = self.config.OMDb.get_omdb(input_id)
tvdb_id = omdb_item.series_id
season_num = omdb_item.season_num
episode_num = omdb_item.episode_num
if not tvdb_id or not season_num or not episode_num:
raise Failed(f" and OMDb metadata lookup Failed for IMDb ID: {input_id}")
except Failed as ee:
logger.error(f"{e}{ee}")
continue
for pl_library in self.libraries:
if tvdb_id in pl_library.show_map:
found = True
2023-02-27 19:54:52 +00:00
show_item = pl_library.fetch_item(pl_library.show_map[tvdb_id][0])
2022-02-08 22:57:36 +00:00
try:
items.append(show_item.episode(season=int(season_num), episode=int(episode_num)))
except NotFound:
self.missing_parts.append(f"{show_item.title} Season: {season_num} Episode: {episode_num} Missing")
break
if not found and tvdb_id not in self.missing_shows and self.do_missing:
self.missing_shows.append(tvdb_id)
elif tmdb_type == "movie" and self.do_missing and _id not in self.missing_movies:
self.missing_movies.append(_id)
2022-09-25 18:43:58 +00:00
elif tmdb_type in ["show", "episode"] and self.do_missing:
if tmdb_type == "episode":
tmdb_id, _, _ = _id.split("_")
else:
tmdb_id = _id
tvdb_id = self.config.Convert.tmdb_to_tvdb(tmdb_id, fail=True)
2022-02-08 22:57:36 +00:00
if tvdb_id not in self.missing_shows:
self.missing_shows.append(tvdb_id)
except Failed as e:
logger.warning(e)
continue
elif id_type == "tmdb" and not self.parts_collection:
input_id = int(input_id)
if input_id not in self.ignore_ids:
found = False
for pl_library in self.libraries:
if input_id in pl_library.movie_map:
found = True
rating_keys = pl_library.movie_map[input_id]
break
if not found and input_id not in self.missing_movies:
self.missing_movies.append(input_id)
2022-07-26 19:58:53 +00:00
elif id_type == "tvdb_season" and (self.builder_level == "season" or self.playlist):
2022-02-08 22:57:36 +00:00
tvdb_id, season_num = input_id.split("_")
tvdb_id = int(tvdb_id)
found = False
for pl_library in self.libraries:
if tvdb_id in pl_library.show_map:
found = True
2023-02-27 19:54:52 +00:00
show_item = pl_library.fetch_item(pl_library.show_map[tvdb_id][0])
2022-02-08 22:57:36 +00:00
try:
season_obj = show_item.season(season=int(season_num))
if self.playlist:
items.extend(season_obj.episodes())
else:
items.append(season_obj)
except NotFound:
self.missing_parts.append(f"{show_item.title} Season: {season_num} Missing")
break
if not found and tvdb_id not in self.missing_shows:
self.missing_shows.append(tvdb_id)
2022-07-26 19:58:53 +00:00
elif id_type == "tvdb_episode" and (self.builder_level == "episode" or self.playlist):
2022-02-08 22:57:36 +00:00
tvdb_id, season_num, episode_num = input_id.split("_")
tvdb_id = int(tvdb_id)
found = False
for pl_library in self.libraries:
if tvdb_id in pl_library.show_map:
found = True
2023-02-27 19:54:52 +00:00
show_item = pl_library.fetch_item(pl_library.show_map[tvdb_id][0])
2021-12-14 05:51:36 +00:00
try:
2021-12-15 15:50:20 +00:00
items.append(show_item.episode(season=int(season_num), episode=int(episode_num)))
2021-12-14 05:51:36 +00:00
except NotFound:
self.missing_parts.append(f"{show_item.title} Season: {season_num} Episode: {episode_num} Missing")
2022-02-08 22:57:36 +00:00
if not found and tvdb_id not in self.missing_shows and self.do_missing:
self.missing_shows.append(tvdb_id)
elif id_type in ["tvdb", "tmdb_show", "tvdb_season", "tvdb_episode"]:
tvdb_season = None
2022-09-26 13:59:54 +00:00
if id_type == "tmdb_show":
try:
tvdb_id = self.config.Convert.tmdb_to_tvdb(input_id, fail=True)
except Failed as e:
logger.warning(e)
continue
elif id_type == "tvdb_season":
tvdb_id, tvdb_season = input_id.split("_")
2022-09-26 13:59:54 +00:00
tvdb_id = int(tvdb_id)
tvdb_season = int(tvdb_season)
2022-09-26 13:59:54 +00:00
elif id_type == "tvdb_episode":
tvdb_id, _, _ = input_id.split("_")
tvdb_id = int(tvdb_id)
else:
tvdb_id = int(input_id)
if tvdb_id not in self.ignore_ids:
found_keys = None
2022-09-26 13:59:54 +00:00
for pl_library in self.libraries:
if tvdb_id in pl_library.show_map:
found_keys = pl_library.show_map[tvdb_id]
2022-09-26 13:59:54 +00:00
break
if not found_keys and tvdb_id not in self.missing_shows:
2022-09-26 13:59:54 +00:00
self.missing_shows.append(tvdb_id)
if found_keys:
if self.parts_collection:
rating_keys = []
for rk in found_keys:
try:
2023-02-27 19:54:52 +00:00
item = self.library.fetch_item(rk)
if self.builder_level == "episode" and isinstance(item, Show):
if tvdb_season is not None:
item = item.season(season=tvdb_season)
rating_keys.extend([k.ratingKey for k in item.episodes()])
elif self.builder_level == "season" and isinstance(item, Show):
rating_keys.extend([k.ratingKey for k in item.seasons()])
except Failed as e:
logger.error(e)
else:
rating_keys = found_keys
2022-02-08 22:57:36 +00:00
else:
continue
2021-12-15 15:50:20 +00:00
2022-02-08 22:57:36 +00:00
if not isinstance(rating_keys, list):
rating_keys = [rating_keys]
for rk in rating_keys:
try:
2023-02-27 19:54:52 +00:00
item = self.library.fetch_item(rk)
2022-02-08 22:57:36 +00:00
if self.playlist and isinstance(item, (Show, Season)):
items.extend(item.episodes())
2022-07-28 04:51:01 +00:00
elif self.builder_level == "movie" and not isinstance(item, Movie):
logger.info(f"Item: {item} is not an Movie")
elif self.builder_level == "show" and not isinstance(item, Show):
logger.info(f"Item: {item} is not an Show")
elif self.builder_level == "episode" and not isinstance(item, Episode):
logger.info(f"Item: {item} is not an Episode")
elif self.builder_level == "season" and not isinstance(item, Season):
logger.info(f"Item: {item} is not a Season")
elif self.builder_level == "artist" and not isinstance(item, Artist):
logger.info(f"Item: {item} is not an Artist")
elif self.builder_level == "album" and not isinstance(item, Album):
logger.info(f"Item: {item} is not an Album")
elif self.builder_level == "track" and not isinstance(item, Track):
logger.info(f"Item: {item} is not a Track")
2022-02-08 22:57:36 +00:00
else:
items.append(item)
except Failed as e:
logger.error(e)
logger.exorcise()
2022-02-08 22:57:36 +00:00
if not items:
return None
2021-12-15 15:50:20 +00:00
name = self.obj.title if self.obj else self.name
total = len(items)
max_length = len(str(total))
2022-10-24 06:17:49 +00:00
if self.filters and self.details["show_filtered"] is True:
2021-12-15 15:50:20 +00:00
logger.info("")
logger.info("Filtering Builders:")
filtered_items = []
2021-12-15 15:50:20 +00:00
for i, item in enumerate(items, 1):
if not isinstance(item, (Movie, Show, Season, Episode, Artist, Album, Track)):
logger.error(f"{self.Type} Error: Item: {item} is an invalid type")
2021-12-15 15:50:20 +00:00
continue
2022-11-07 17:05:21 +00:00
if item not in self.found_items:
2021-12-15 15:50:20 +00:00
if item.ratingKey in self.filtered_keys:
if self.details["show_filtered"] is True:
logger.info(f"{name} {self.Type} | X | {self.filtered_keys[item.ratingKey]}")
else:
2021-12-17 14:24:46 +00:00
current_title = util.item_title(item)
2021-12-15 15:50:20 +00:00
if self.check_filters(item, f"{(' ' * (max_length - len(str(i))))}{i}/{total}"):
2022-11-07 17:05:21 +00:00
self.found_items.append(item)
2021-12-15 15:50:20 +00:00
else:
filtered_items.append(item)
2021-12-15 15:50:20 +00:00
self.filtered_keys[item.ratingKey] = current_title
if self.details["show_filtered"] is True:
logger.info(f"{name} {self.Type} | X | {current_title}")
2022-10-21 21:09:36 +00:00
if self.do_report and filtered_items:
self.library.add_filtered(self.name, [(i.title, self.library.get_id_from_maps(i.ratingKey)) for i in filtered_items], self.library.is_movie)
2021-08-05 14:59:45 +00:00
def build_filter(self, method, plex_filter, display=False, default_sort=None):
2022-02-13 18:28:53 +00:00
if display:
2021-05-29 02:33:55 +00:00
logger.info("")
logger.info(f"Validating Method: {method}")
if plex_filter is None:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: {method} attribute is blank")
2021-05-29 02:33:55 +00:00
if not isinstance(plex_filter, dict):
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: {method} must be a dictionary: {plex_filter}")
2022-02-13 18:28:53 +00:00
if display:
2021-05-29 02:33:55 +00:00
logger.debug(f"Value: {plex_filter}")
filter_alias = {m.lower(): m for m in plex_filter}
if "any" in filter_alias and "all" in filter_alias:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: Cannot have more then one base")
2021-05-29 02:33:55 +00:00
2022-07-26 19:58:53 +00:00
if self.builder_level == "item":
2022-09-13 18:47:24 +00:00
if "type" in filter_alias:
if plex_filter[filter_alias["type"]] is None:
raise Failed(f"{self.Type} Error: type attribute is blank")
if plex_filter[filter_alias["type"]] not in plex.sort_types:
raise Failed(f"{self.Type} Error: type: {plex_filter[filter_alias['type']]} is invalid. Options: {', '.join(plex.sort_types)}")
sort_type = plex_filter[filter_alias["type"]]
elif self.library.is_show:
sort_type = "show"
elif self.library.is_music:
sort_type = "artist"
else:
sort_type = "movie"
2021-05-29 02:33:55 +00:00
else:
2022-07-26 19:58:53 +00:00
sort_type = self.builder_level
2021-05-29 02:33:55 +00:00
ms = method.split("_")
filter_details = f"{ms[0].capitalize()} {sort_type.capitalize()} {ms[1].capitalize()}\n"
2022-04-27 23:32:48 +00:00
type_default_sort, type_key, sorts = plex.sort_types[sort_type]
2021-05-29 02:33:55 +00:00
sort = []
2021-05-29 02:33:55 +00:00
if "sort_by" in filter_alias:
test_sorts = plex_filter[filter_alias["sort_by"]]
if test_sorts is None:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: sort_by attribute is blank")
if not isinstance(test_sorts, list):
test_sorts = [test_sorts]
for test_sort in test_sorts:
if test_sort not in sorts:
raise Failed(f"{self.Type} Error: sort_by: {test_sort} is invalid. Options: {', '.join(sorts)}")
sort.append(test_sort)
if not sort:
sort.append(default_sort if default_sort else type_default_sort)
2021-05-29 02:33:55 +00:00
filter_details += f"Sort By: {sort}\n"
limit = None
if "limit" in filter_alias:
if plex_filter[filter_alias["limit"]] is None:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: limit attribute is blank")
2022-03-22 20:06:59 +00:00
elif str(plex_filter[filter_alias["limit"]]).lower() == "all":
filter_details += "Limit: all\n"
else:
2022-08-24 13:00:34 +00:00
try:
if int(plex_filter[filter_alias["limit"]]) < 1:
raise ValueError
else:
limit = int(plex_filter[filter_alias["limit"]])
filter_details += f"Limit: {limit}\n"
except ValueError:
raise Failed(f"{self.Type} Error: limit attribute must be an integer greater than 0")
2021-05-29 02:33:55 +00:00
validate = True
if "validate" in filter_alias:
if plex_filter[filter_alias["validate"]] is None:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: validate attribute is blank")
2021-05-29 02:33:55 +00:00
if not isinstance(plex_filter[filter_alias["validate"]], bool):
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: validate attribute must be either true or false")
2021-05-29 02:33:55 +00:00
validate = plex_filter[filter_alias["validate"]]
filter_details += f"Validate: {validate}\n"
def _filter(filter_dict, is_all=True, level=1):
output = ""
2022-02-13 18:28:53 +00:00
display_out = f"\n{' ' * level}Match {'all' if is_all else 'any'} of the following:"
2021-05-29 02:33:55 +00:00
level += 1
indent = f"\n{' ' * level}"
conjunction = f"{'and' if is_all else 'or'}=1&"
for _key, _data in filter_dict.items():
attr, modifier, final_attr = self.library.split(_key)
2021-05-29 02:33:55 +00:00
def build_url_arg(arg, mod=None, arg_s=None, mod_s=None):
arg_key = plex.search_translation[attr] if attr in plex.search_translation else attr
2021-07-26 18:52:32 +00:00
arg_key = plex.show_translation[arg_key] if self.library.is_show and arg_key in plex.show_translation else arg_key
2021-05-29 02:33:55 +00:00
if mod is None:
mod = plex.modifier_translation[modifier] if modifier in plex.modifier_translation else modifier
if arg_s is None:
arg_s = arg
if attr in plex.string_attributes and modifier in ["", ".not"]:
2021-05-29 02:33:55 +00:00
mod_s = "does not contain" if modifier == ".not" else "contains"
elif mod_s is None:
2021-08-14 22:59:35 +00:00
mod_s = util.mod_displays[modifier]
2021-05-29 02:33:55 +00:00
param_s = plex.search_display[attr] if attr in plex.search_display else attr.title().replace('_', ' ')
display_line = f"{indent}{param_s} {mod_s} {arg_s}"
return f"{arg_key}{mod}={arg}&", display_line
2022-01-23 19:40:16 +00:00
error = None
2021-07-14 14:47:20 +00:00
if final_attr not in plex.searches and not final_attr.startswith(("any", "all")):
2022-01-23 19:40:16 +00:00
error = f"{self.Type} Error: {final_attr} is not a valid {method} attribute"
elif self.library.is_show and final_attr in plex.movie_only_searches:
2022-01-23 19:40:16 +00:00
error = f"{self.Type} Error: {final_attr} {method} attribute only works for movie libraries"
elif self.library.is_movie and final_attr in plex.show_only_searches:
2022-01-23 19:40:16 +00:00
error = f"{self.Type} Error: {final_attr} {method} attribute only works for show libraries"
elif self.library.is_music and final_attr not in plex.music_searches + ["all", "any"]:
2022-01-23 19:40:16 +00:00
error = f"{self.Type} Error: {final_attr} {method} attribute does not work for music libraries"
elif not self.library.is_music and final_attr in plex.music_searches:
2022-01-23 19:40:16 +00:00
error = f"{self.Type} Error: {final_attr} {method} attribute only works for music libraries"
2022-06-22 07:19:15 +00:00
elif _data is not False and _data != 0 and not _data:
2022-01-23 19:40:16 +00:00
error = f"{self.Type} Error: {final_attr} {method} attribute is blank"
2021-05-29 02:33:55 +00:00
else:
2022-01-23 19:40:16 +00:00
if final_attr.startswith(("any", "all")):
dicts = util.get_list(_data)
2021-05-29 02:33:55 +00:00
results = ""
display_add = ""
2022-01-23 19:40:16 +00:00
for dict_data in dicts:
if not isinstance(dict_data, dict):
2022-01-27 14:55:18 +00:00
raise Failed(f"{self.Type} Error: {attr} must be either a dictionary or list of dictionaries")
2022-01-23 19:40:16 +00:00
inside_filter, inside_display = _filter(dict_data, is_all=attr == "all", level=level)
if len(inside_filter) > 0:
display_add += inside_display
results += f"{conjunction if len(results) > 0 else ''}push=1&{inside_filter}pop=1&"
else:
2022-04-20 16:03:08 +00:00
validation = self.validate_attribute(attr, modifier, final_attr, _data, validate, plex_search=True)
2022-06-21 14:07:44 +00:00
if validation is not False and validation != 0 and not validation:
2022-01-23 19:40:16 +00:00
continue
elif attr in plex.date_attributes and modifier in ["", ".not"]:
last_text = "is not in the last" if modifier == ".not" else "is in the last"
last_mod = "%3E%3E" if modifier == "" else "%3C%3C"
search_mod = validation[-1]
if search_mod == "o":
validation = f"{validation[:-1]}mon"
results, display_add = build_url_arg(f"-{validation}", mod=last_mod, arg_s=f"{validation} {plex.date_sub_mods[search_mod]}", mod_s=last_text)
2022-01-23 19:40:16 +00:00
elif attr == "duration" and modifier in [".gt", ".gte", ".lt", ".lte"]:
results, display_add = build_url_arg(validation * 60000)
2022-06-26 19:08:17 +00:00
elif modifier == ".rated":
2022-06-27 18:33:54 +00:00
results, display_add = build_url_arg(-1, mod="!" if validation else "", arg_s="Rated", mod_s="is" if validation else "is not")
2022-01-23 19:40:16 +00:00
elif attr in plex.boolean_attributes:
bool_mod = "" if validation else "!"
bool_arg = "true" if validation else "false"
results, display_add = build_url_arg(1, mod=bool_mod, arg_s=bool_arg, mod_s="is")
2022-04-20 16:03:08 +00:00
elif (attr in plex.tag_attributes + plex.string_attributes + plex.year_attributes) and modifier in ["", ".is", ".isnot", ".not", ".begins", ".ends", ".regex"]:
2022-01-23 19:40:16 +00:00
results = ""
display_add = ""
for og_value, result in validation:
built_arg = build_url_arg(quote(str(result)) if attr in plex.string_attributes else result, arg_s=og_value)
display_add += built_arg[1]
results += f"{conjunction if len(results) > 0 else ''}{built_arg[0]}"
else:
results, display_add = build_url_arg(validation)
2022-02-13 18:28:53 +00:00
display_out += display_add
2022-01-23 19:40:16 +00:00
output += f"{conjunction if len(output) > 0 else ''}{results}"
if error:
if validate:
raise Failed(error)
2021-05-29 02:33:55 +00:00
else:
2022-01-23 19:40:16 +00:00
logger.error(error)
continue
2022-02-13 18:28:53 +00:00
return output, display_out
2021-05-29 02:33:55 +00:00
if "any" not in filter_alias and "all" not in filter_alias:
base_dict = {}
2021-05-30 02:19:38 +00:00
any_dicts = []
2021-05-29 02:33:55 +00:00
for alias_key, alias_value in filter_alias.items():
_, _, final = self.library.split(alias_key)
2021-06-22 14:54:11 +00:00
if final in plex.and_searches:
2021-05-30 02:19:38 +00:00
base_dict[alias_value[:-4]] = plex_filter[alias_value]
2021-06-22 14:54:11 +00:00
elif final in plex.or_searches:
2021-05-30 02:19:38 +00:00
any_dicts.append({alias_value: plex_filter[alias_value]})
2021-06-22 14:54:11 +00:00
elif final in plex.searches:
2021-05-29 02:33:55 +00:00
base_dict[alias_value] = plex_filter[alias_value]
2021-05-30 02:19:38 +00:00
if len(any_dicts) > 0:
base_dict["any"] = any_dicts
2021-05-29 02:33:55 +00:00
base_all = True
if len(base_dict) == 0:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: Must have either any or all as a base for {method}")
2021-05-29 02:33:55 +00:00
else:
base = "all" if "all" in filter_alias else "any"
base_all = base == "all"
if plex_filter[filter_alias[base]] is None:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: {base} attribute is blank")
2021-05-29 02:33:55 +00:00
if not isinstance(plex_filter[filter_alias[base]], dict):
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: {base} must be a dictionary: {plex_filter[filter_alias[base]]}")
2021-05-29 02:33:55 +00:00
base_dict = plex_filter[filter_alias[base]]
built_filter, filter_text = _filter(base_dict, is_all=base_all)
filter_details = f"{filter_details}Filter:{filter_text}"
if len(built_filter) > 0:
final_filter = built_filter[:-1] if base_all else f"push=1&{built_filter}pop=1"
filter_url = f"?type={type_key}&{f'limit={limit}&' if limit else ''}sort={'%2C'.join([sorts[s] for s in sort])}&{final_filter}"
2021-05-29 02:33:55 +00:00
else:
2022-11-11 16:59:39 +00:00
raise FilterFailed(f"{self.Type} Error: No Plex Filter Created")
2021-05-29 02:33:55 +00:00
2022-08-04 13:34:02 +00:00
if display:
logger.debug(f"Smart URL: {filter_url}")
2021-05-29 02:33:55 +00:00
return type_key, filter_details, filter_url
2022-04-20 16:03:08 +00:00
def validate_attribute(self, attribute, modifier, final, data, validate, plex_search=False):
2021-05-27 17:40:35 +00:00
def smart_pair(list_to_pair):
2022-04-20 16:03:08 +00:00
return [(t, t) for t in list_to_pair] if plex_search else list_to_pair
if attribute in tag_attributes and modifier in [".regex"]:
2022-06-01 20:01:40 +00:00
_, names = self.library.get_search_choices(attribute, title=not plex_search, name_pairs=True)
valid_list = []
used = []
for reg in util.validate_regex(data, self.Type, validate=validate):
for name, key in names:
if name not in used and re.compile(reg).search(name):
used.append(name)
valid_list.append((name, key) if plex_search else name)
2022-06-01 20:01:40 +00:00
if not valid_list:
error = f"Plex Error: {attribute}: No matches found with regex pattern {data}"
if self.details["show_options"]:
error += f"\nOptions: {names}"
if validate:
raise Failed(error)
else:
logger.error(error)
return valid_list
elif modifier == ".regex":
2022-04-20 16:03:08 +00:00
return util.validate_regex(data, self.Type, validate=validate)
2022-09-21 14:54:38 +00:00
elif attribute in string_attributes and modifier in ["", ".not", ".is", ".isnot", ".begins", ".ends"]:
2021-05-27 17:40:35 +00:00
return smart_pair(util.get_list(data, split=False))
2023-01-29 00:07:04 +00:00
elif attribute in year_attributes and modifier in ["", ".not", ".gt", ".gte", ".lt", ".lte"]:
if modifier in ["", ".not"]:
final_years = []
values = util.get_list(data)
for value in values:
if str(value).startswith("current_year"):
year_values = str(value).split("-")
try:
final_years.append(datetime.now().year - (0 if len(year_values) == 1 else int(year_values[1].strip())))
except ValueError:
raise Failed(f"{self.Type} Error: {final} attribute modifier invalid '{year_values[1]}'")
else:
final_years.append(util.parse(self.Type, final, value, datatype="int"))
return smart_pair(final_years)
else:
if str(data).startswith("current_year"):
year_values = str(data).split("-")
try:
return datetime.now().year - (0 if len(year_values) == 1 else int(year_values[1].strip()))
except ValueError:
raise Failed(f"{self.Type} Error: {final} attribute modifier invalid '{year_values[1]}'")
return util.parse(self.Type, final, data, datatype="int", minimum=0)
2023-01-27 15:16:00 +00:00
elif (attribute in number_attributes and modifier in ["", ".not", ".gt", ".gte", ".lt", ".lte"]) \
or (attribute in tag_attributes and modifier in [".count_gt", ".count_gte", ".count_lt", ".count_lte"]):
return util.parse(self.Type, final, data, datatype="int", minimum=0)
2022-03-11 14:10:23 +00:00
elif attribute == "origin_country":
return util.get_list(data, upper=True)
elif attribute in ["original_language", "tmdb_keyword"]:
2021-05-28 23:18:28 +00:00
return util.get_list(data, lower=True)
2024-04-03 14:49:18 +00:00
elif attribute in ["tmdb_genre", "tvdb_genre"]:
2021-05-28 23:18:28 +00:00
return util.get_list(data)
2021-06-03 04:20:49 +00:00
elif attribute == "history":
try:
2022-01-28 18:36:21 +00:00
return util.parse(self.Type, final, data, datatype="int", maximum=30)
2021-06-03 04:20:49 +00:00
except Failed:
if str(data).lower() in ["day", "month"]:
return data.lower()
2022-04-20 16:03:08 +00:00
else:
raise Failed(f"{self.Type} Error: history attribute invalid: {data} must be a number between 1-30, day, or month")
2022-01-28 21:20:09 +00:00
elif attribute == "tmdb_type":
2022-10-24 06:17:49 +00:00
return util.parse(self.Type, final, data, datatype="commalist", options=[v for k, v in tmdb.discover_types.items()])
2022-01-28 21:20:09 +00:00
elif attribute == "tmdb_status":
2022-10-24 06:17:49 +00:00
return util.parse(self.Type, final, data, datatype="commalist", options=[v for k, v in tmdb.discover_status.items()])
2023-01-27 15:16:00 +00:00
elif attribute == "imdb_keyword":
new_dictionary = {"minimum_votes": 0, "minimum_relevant": 0, "minimum_percentage": 0}
if isinstance(data, dict) and "keyword" not in data:
raise Failed(f"{self.Type} Error: imdb_keyword requires the keyword attribute")
elif isinstance(data, dict):
dict_methods = {dm.lower(): dm for dm in data}
2023-01-27 18:33:22 +00:00
new_dictionary["keywords"] = util.parse(self.Type, "keyword", data, methods=dict_methods, parent=attribute, datatype="lowerlist")
2023-01-27 15:16:00 +00:00
new_dictionary["minimum_votes"] = util.parse(self.Type, "minimum_votes", data, methods=dict_methods, parent=attribute, datatype="int", minimum=0)
new_dictionary["minimum_relevant"] = util.parse(self.Type, "minimum_relevant", data, methods=dict_methods, parent=attribute, datatype="int", minimum=0)
new_dictionary["minimum_percentage"] = util.parse(self.Type, "minimum_percentage", data, methods=dict_methods, parent=attribute, datatype="int", minimum=0, maximum=100)
else:
2023-01-27 18:33:22 +00:00
new_dictionary["keywords"] = util.parse(self.Type, final, data, datatype="lowerlist")
2023-01-27 15:16:00 +00:00
return new_dictionary
elif attribute in tag_attributes and modifier in ["", ".not"]:
2021-05-28 23:18:28 +00:00
if attribute in plex.tmdb_attributes:
2021-05-27 17:40:35 +00:00
final_values = []
for value in util.get_list(data):
if value.lower() == "tmdb" and "tmdb_person" in self.details:
for name in self.details["tmdb_person"]:
final_values.append(name)
else:
final_values.append(value)
else:
2022-05-09 15:22:41 +00:00
final_values = util.get_list(data, trim=False)
2022-04-20 16:03:08 +00:00
search_choices, names = self.library.get_search_choices(attribute, title=not plex_search)
2021-06-16 03:20:45 +00:00
valid_list = []
2022-08-04 20:47:54 +00:00
for fvalue in final_values:
if str(fvalue) in search_choices or str(fvalue).lower() in search_choices:
valid_value = search_choices[str(fvalue) if str(fvalue) in search_choices else str(fvalue).lower()]
valid_list.append((fvalue, valid_value) if plex_search else valid_value)
2021-05-27 17:40:35 +00:00
else:
2022-04-06 02:14:32 +00:00
actor_id = None
if attribute in ["actor", "director", "producer", "writer"]:
2022-08-04 20:47:54 +00:00
actor_id = self.library.get_actor_id(fvalue)
2022-04-06 02:14:32 +00:00
if actor_id:
2022-04-20 16:03:08 +00:00
if plex_search:
2022-08-04 20:47:54 +00:00
valid_list.append((fvalue, actor_id))
2022-04-06 02:14:32 +00:00
else:
valid_list.append(actor_id)
if not actor_id:
2022-08-04 20:47:54 +00:00
error = f"Plex Error: {attribute}: {fvalue} not found"
2022-04-06 02:14:32 +00:00
if self.details["show_options"]:
error += f"\nOptions: {names}"
if validate:
2022-11-15 21:30:46 +00:00
raise FilterFailed(error)
2022-11-11 16:59:39 +00:00
elif not self.ignore_blank_results:
2022-04-06 02:14:32 +00:00
logger.error(error)
2021-06-16 03:20:45 +00:00
return valid_list
2022-04-20 16:03:08 +00:00
elif attribute in date_attributes and modifier in [".before", ".after"]:
2023-12-07 19:49:32 +00:00
try:
return util.validate_date(datetime.now() if data == "today" else data, return_as="%Y-%m-%d")
except Failed as e:
raise Failed(f"{self.Type} Error: {final}: {e}")
elif attribute in date_attributes and modifier in ["", ".not"]:
2022-06-09 15:27:42 +00:00
search_mod = "d"
if plex_search and data and str(data)[-1] in ["s", "m", "h", "d", "w", "o", "y"]:
search_mod = str(data)[-1]
data = str(data)[:-1]
search_data = util.parse(self.Type, final, data, datatype="int", minimum=0)
return f"{search_data}{search_mod}" if plex_search else search_data
elif attribute in float_attributes and modifier in ["", ".not", ".gt", ".gte", ".lt", ".lte"]:
2022-02-02 15:37:16 +00:00
return util.parse(self.Type, final, data, datatype="float", minimum=0, maximum=None if attribute == "duration" else 10)
2022-06-26 19:08:17 +00:00
elif attribute in boolean_attributes or (attribute in float_attributes and modifier in [".rated"]):
2022-01-28 18:36:21 +00:00
return util.parse(self.Type, attribute, data, datatype="bool")
elif attribute in ["seasons", "episodes", "albums", "tracks"]:
if isinstance(data, dict) and data:
percentage = self.default_percent
if "percentage" in data:
if data["percentage"] is None:
logger.warning(f"{self.Type} Warning: percentage filter attribute is blank using {self.default_percent} as default")
else:
maybe = util.check_num(data["percentage"])
if maybe < 0 or maybe > 100:
logger.warning(f"{self.Type} Warning: percentage filter attribute must be a number 0-100 using {self.default_percent} as default")
else:
percentage = maybe
final_filters = {"percentage": percentage}
for filter_method, filter_data in data.items():
filter_attr, filter_modifier, filter_final = self.library.split(filter_method)
message = None
2022-04-27 23:40:11 +00:00
if filter_final == "percentage":
continue
if filter_final not in all_filters:
message = f"{self.Type} Error: {filter_final} is not a valid filter attribute"
elif filter_attr not in filters[attribute[:-1]] or filter_attr in ["seasons", "episodes", "albums", "tracks"]:
message = f"{self.Type} Error: {filter_final} is not a valid {attribute[:-1]} filter attribute"
elif filter_final is None:
message = f"{self.Type} Error: {filter_final} filter attribute is blank"
2022-04-27 23:40:11 +00:00
else:
final_filters[filter_final] = self.validate_attribute(filter_attr, filter_modifier, f"{attribute} {filter_final} filter", filter_data, validate)
if message:
if validate:
raise Failed(message)
else:
logger.error(message)
if not final_filters:
raise Failed(f"{self.Type} Error: no filters found under {attribute}")
return final_filters
else:
raise Failed(f"{self.Type} Error: {final} attribute must be a dictionary")
2021-05-27 17:40:35 +00:00
else:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: {final} attribute not supported")
2021-05-27 17:40:35 +00:00
2021-05-26 14:45:33 +00:00
def add_to_collection(self):
2021-12-30 00:37:31 +00:00
logger.info("")
logger.separator(f"Adding to {self.name} {self.Type}", space=False, border=False)
2021-12-30 00:37:31 +00:00
logger.info("")
2021-05-12 14:25:48 +00:00
name, collection_items = self.library.get_collection_name_and_items(self.obj if self.obj else self.name, self.smart_label_collection)
2022-11-07 17:05:21 +00:00
total = self.limit if self.limit and len(self.found_items) > self.limit else len(self.found_items)
2021-12-30 00:37:31 +00:00
spacing = len(str(total)) * 2 + 1
2021-11-03 14:36:11 +00:00
amount_added = 0
2021-12-30 21:29:16 +00:00
amount_unchanged = 0
items_added = []
2022-11-07 17:05:21 +00:00
for i, item in enumerate(self.found_items, 1):
if self.limit and amount_added + self.beginning_count - len([r for _, r in self.remove_item_map.items() if r is not None]) >= self.limit:
logger.info(f"{self.Type} Limit reached")
2022-11-07 17:05:21 +00:00
self.found_items = self.found_items[:i - 1]
break
2021-12-17 14:24:46 +00:00
current_operation = "=" if item in collection_items else "+"
2021-12-30 00:37:31 +00:00
number_text = f"{i}/{total}"
logger.info(f"{number_text:>{spacing}} | {name} {self.Type} | {current_operation} | {util.item_title(item)}")
2021-12-17 14:24:46 +00:00
if item in collection_items:
2022-02-09 02:31:29 +00:00
self.remove_item_map[item.ratingKey] = None
2021-12-30 21:29:16 +00:00
amount_unchanged += 1
else:
items_added.append(item)
2021-11-03 14:36:11 +00:00
amount_added += 1
2021-12-26 15:48:52 +00:00
if self.details["changes_webhooks"]:
self.notification_additions.append(util.item_set(item, self.library.get_id_from_maps(item.ratingKey)))
if self.playlist and items_added and not self.obj:
self.obj = self.library.create_playlist(self.name, items_added)
2021-12-17 14:24:46 +00:00
logger.info("")
logger.info(f"Playlist: {self.name} created")
elif self.playlist and items_added:
self.obj.addItems(items_added)
2023-11-13 21:19:59 +00:00
elif items_added:
self.library.alter_collection(items_added, name, smart_label_collection=self.smart_label_collection)
2022-10-21 21:09:36 +00:00
if self.do_report and items_added:
self.library.add_additions(self.name, [(i.title, self.library.get_id_from_maps(i.ratingKey)) for i in items_added], self.library.is_movie)
logger.exorcise()
2021-05-24 03:38:46 +00:00
logger.info("")
2022-08-09 05:35:21 +00:00
item_label = f"{self.builder_level.capitalize()}{'s' if total > 1 else ''}"
logger.info(f"{total} {item_label} Processed {amount_added} {item_label} Added")
2021-12-30 21:29:16 +00:00
return amount_added, amount_unchanged
2021-02-20 05:41:45 +00:00
2021-10-20 19:58:25 +00:00
def sync_collection(self):
2021-11-03 14:36:11 +00:00
amount_removed = 0
items_removed = []
2022-02-09 02:31:29 +00:00
items = [item for _, item in self.remove_item_map.items() if item is not None]
2021-12-30 00:37:31 +00:00
if items:
logger.info("")
logger.separator(f"Removed from {self.name} {self.Type}", space=False, border=False)
2021-12-30 00:37:31 +00:00
logger.info("")
total = len(items)
spacing = len(str(total)) * 2 + 1
for i, item in enumerate(items, 1):
number_text = f"{i}/{total}"
logger.info(f"{number_text:>{spacing}} | {self.name} {self.Type} | - | {util.item_title(item)}")
items_removed.append(item)
2021-12-17 14:24:46 +00:00
amount_removed += 1
2021-12-26 15:48:52 +00:00
if self.details["changes_webhooks"]:
self.notification_removals.append(util.item_set(item, self.library.get_id_from_maps(item.ratingKey)))
if self.playlist and items_removed:
2023-10-16 20:04:46 +00:00
self.library._reload(self.obj)
self.obj.removeItems(items_removed)
2023-11-13 21:19:59 +00:00
elif items_removed:
self.library.alter_collection(items_removed, self.name, smart_label_collection=self.smart_label_collection, add=False)
2022-10-21 21:09:36 +00:00
if self.do_report and items_removed:
self.library.add_removed(self.name, [(i.title, self.library.get_id_from_maps(i.ratingKey)) for i in items_removed], self.library.is_movie)
2021-10-20 19:58:25 +00:00
logger.info("")
2022-07-26 19:58:53 +00:00
logger.info(f"{amount_removed} {self.builder_level.capitalize()}{'s' if amount_removed == 1 else ''} Removed")
2021-11-03 14:36:11 +00:00
return amount_removed
2021-10-20 19:58:25 +00:00
2022-10-24 06:17:49 +00:00
def check_tmdb_filters(self, tmdb_item, filters_in, is_movie):
for filter_method, filter_data in filters_in:
filter_attr, modifier, filter_final = self.library.split(filter_method)
if self.config.TMDb.item_filter(tmdb_item, filter_attr, modifier, filter_final, filter_data, is_movie, self.current_time) is False:
2022-10-03 19:32:50 +00:00
return False
return True
2024-04-03 12:45:43 +00:00
def check_tvdb_filters(self, tvdb_item, filters_in):
for filter_method, filter_data in filters_in:
filter_attr, modifier, filter_final = self.library.split(filter_method)
if self.config.TVDb.item_filter(tvdb_item, filter_attr, modifier, filter_final, filter_data) is False:
return False
return True
2023-01-27 15:16:00 +00:00
def check_imdb_filters(self, imdb_info, filters_in):
for filter_method, filter_data in filters_in:
filter_attr, modifier, filter_final = self.library.split(filter_method)
if self.config.IMDb.item_filter(imdb_info, filter_attr, modifier, filter_final, filter_data) is False:
return False
return True
2022-10-24 06:17:49 +00:00
def check_missing_filters(self, item_id, is_movie, tmdb_item=None, check_released=False):
2023-01-27 15:16:00 +00:00
imdb_info = None
if self.has_tmdb_filters or self.has_imdb_filters or check_released:
2021-08-07 06:01:21 +00:00
try:
2022-10-24 06:17:49 +00:00
if tmdb_item is None:
if is_movie:
2022-10-24 06:17:49 +00:00
tmdb_item = self.config.TMDb.get_movie(item_id, ignore_cache=True)
else:
2022-10-24 06:17:49 +00:00
tmdb_item = self.config.TMDb.get_show(self.config.Convert.tvdb_to_tmdb(item_id, fail=True), ignore_cache=True)
2021-08-07 06:01:21 +00:00
except Failed:
return False
2023-01-27 15:16:00 +00:00
if self.has_imdb_filters and tmdb_item and tmdb_item.imdb_id:
try:
2023-07-10 15:34:02 +00:00
imdb_info = self.config.IMDb.keywords(tmdb_item.imdb_id, self.language)
2023-01-27 15:16:00 +00:00
except Failed as e:
logger.error(e)
return False
2022-10-24 21:01:58 +00:00
if check_released:
date_to_check = tmdb_item.release_date if is_movie else tmdb_item.first_air_date
if not date_to_check or date_to_check > self.current_time:
return False
final_return = True
2023-01-27 15:16:00 +00:00
if self.has_tmdb_filters or self.has_imdb_filters:
2022-10-24 21:01:58 +00:00
final_return = False
2022-10-24 06:17:49 +00:00
for filter_list in self.filters:
2023-01-27 15:16:00 +00:00
tmdb_f = []
imdb_f = []
for k, v in filter_list:
if k.split(".")[0] in tmdb_filters:
tmdb_f.append((k, v))
elif k.split(".")[0] in imdb_filters:
imdb_f.append((k, v))
2022-10-24 06:17:49 +00:00
or_result = True
2023-01-27 15:16:00 +00:00
if tmdb_f:
if not tmdb_item or self.check_tmdb_filters(tmdb_item, tmdb_f, is_movie) is False:
or_result = False
if imdb_f:
if not imdb_info and self.check_imdb_filters(imdb_info, imdb_f) is False:
or_result = False
2022-10-24 06:17:49 +00:00
if or_result:
final_return = True
2022-10-03 19:32:50 +00:00
return final_return
2021-08-07 06:01:21 +00:00
def check_filters(self, item, display):
2022-10-03 19:32:50 +00:00
final_return = True
2022-10-24 06:17:49 +00:00
if self.filters and not self.details["only_filter_missing"]:
logger.ghost(f"Filtering {display} {item.title}")
item = self.library.reload(item)
2022-10-03 19:32:50 +00:00
final_return = False
2022-10-24 06:17:49 +00:00
tmdb_item = None
2024-04-03 12:45:43 +00:00
tvdb_item = None
2023-01-27 15:16:00 +00:00
imdb_info = None
2022-10-03 19:32:50 +00:00
for filter_list in self.filters:
2022-12-28 17:30:16 +00:00
tmdb_f = []
2024-04-03 12:45:43 +00:00
tvdb_f = []
2023-01-27 15:16:00 +00:00
imdb_f = []
2022-12-28 17:30:16 +00:00
plex_f = []
for k, v in filter_list:
if k.split(".")[0] in tmdb_filters:
tmdb_f.append((k, v))
2024-04-03 12:45:43 +00:00
elif k.split(".")[0] in tvdb_filters:
tvdb_f.append((k, v))
2023-01-27 15:16:00 +00:00
elif k.split(".")[0] in imdb_filters:
imdb_f.append((k, v))
2022-12-28 17:30:16 +00:00
else:
plex_f.append((k, v))
2022-10-24 06:17:49 +00:00
or_result = True
if tmdb_f:
if not tmdb_item and isinstance(item, (Movie, Show)):
if item.ratingKey not in self.library.movie_rating_key_map and item.ratingKey not in self.library.show_rating_key_map:
logger.warning(f"Filter Error: No {'TMDb' if self.library.is_movie else 'TVDb'} ID found for {item.title}")
or_result = False
else:
try:
if item.ratingKey in self.library.movie_rating_key_map:
tmdb_item = self.config.TMDb.get_movie(self.library.movie_rating_key_map[item.ratingKey], ignore_cache=True)
else:
tmdb_item = self.config.TMDb.get_show(self.config.Convert.tvdb_to_tmdb(self.library.show_rating_key_map[item.ratingKey], fail=True), ignore_cache=True)
2023-01-27 15:16:00 +00:00
except Failed as e:
logger.error(e)
2022-10-24 06:17:49 +00:00
or_result = False
if not tmdb_item or self.check_tmdb_filters(tmdb_item, tmdb_f, item.ratingKey in self.library.movie_rating_key_map) is False:
or_result = False
2024-04-03 12:45:43 +00:00
if tvdb_f:
if not tvdb_item and isinstance(item, Show):
if item.ratingKey not in self.library.show_rating_key_map:
logger.warning(f"Filter Error: No TVDb ID found for {item.title}")
or_result = False
else:
try:
tvdb_item = self.config.TVDb.get_tvdb_obj(self.library.show_rating_key_map[item.ratingKey])
except Failed as e:
logger.error(e)
or_result = False
if not tvdb_item or self.check_tvdb_filters(tvdb_item, tvdb_f) is False:
or_result = False
2023-01-27 15:16:00 +00:00
if imdb_f:
if not imdb_info and isinstance(item, (Movie, Show)):
if item.ratingKey not in self.library.imdb_rating_key_map:
logger.warning(f"Filter Error: No IMDb ID found for {item.title}")
or_result = False
else:
try:
2023-07-10 15:34:02 +00:00
imdb_info = self.config.IMDb.keywords(self.library.imdb_rating_key_map[item.ratingKey], self.language)
2023-01-27 15:16:00 +00:00
except Failed as e:
logger.error(e)
or_result = False
if not imdb_info or self.check_imdb_filters(imdb_info, imdb_f) is False:
or_result = False
2022-10-24 06:17:49 +00:00
if plex_f and self.library.check_filters(item, plex_f, self.current_time) is False:
or_result = False
if or_result:
2022-10-03 19:32:50 +00:00
final_return = True
return final_return
def display_filters(self):
2022-10-24 06:17:49 +00:00
if self.filters:
2022-10-03 19:32:50 +00:00
for filter_list in self.filters:
logger.info("")
for filter_key, filter_value in filter_list:
logger.info(f"Collection Filter {filter_key}: {filter_value}")
2021-05-26 14:58:04 +00:00
def run_missing(self):
2021-11-03 14:36:11 +00:00
added_to_radarr = 0
added_to_sonarr = 0
if len(self.missing_movies) > 0:
2021-12-30 00:37:31 +00:00
if self.details["show_missing"] is True:
logger.info("")
2022-02-21 20:56:38 +00:00
logger.separator(f"Missing Movies from Library: {self.library.name}", space=False, border=False)
2021-12-30 00:37:31 +00:00
logger.info("")
2021-05-12 14:25:48 +00:00
missing_movies_with_names = []
filtered_movies_with_names = []
for missing_id in self.missing_movies:
2021-05-12 14:25:48 +00:00
try:
movie = self.config.TMDb.get_movie(missing_id)
except Failed as e:
logger.error(e)
continue
2022-01-21 16:34:19 +00:00
current_title = f"{movie.title} ({movie.release_date.year})" if movie.release_date else movie.title
2022-10-24 06:17:49 +00:00
if self.check_missing_filters(missing_id, True, tmdb_item=movie, check_released=self.details["missing_only_released"]):
missing_movies_with_names.append((current_title, missing_id))
2021-05-12 14:25:48 +00:00
if self.details["show_missing"] is True:
2021-12-17 14:24:46 +00:00
logger.info(f"{self.name} {self.Type} | ? | {current_title} (TMDb: {missing_id})")
2021-08-06 17:46:13 +00:00
else:
filtered_movies_with_names.append((current_title, missing_id))
2021-08-06 17:46:13 +00:00
if self.details["show_filtered"] is True and self.details["show_missing"] is True:
2021-12-17 14:24:46 +00:00
logger.info(f"{self.name} {self.Type} | X | {current_title} (TMDb: {missing_id})")
2021-05-24 03:38:46 +00:00
logger.info("")
2021-05-12 14:25:48 +00:00
logger.info(f"{len(missing_movies_with_names)} Movie{'s' if len(missing_movies_with_names) > 1 else ''} Missing")
if len(missing_movies_with_names) > 0:
2022-10-21 21:09:36 +00:00
if self.do_report:
self.library.add_missing(self.name, missing_movies_with_names, True)
2021-12-30 17:13:01 +00:00
if self.run_again or (self.library.Radarr and (self.radarr_details["add_missing"] or "item_radarr_tag" in self.item_details)):
missing_tmdb_ids = [missing_id for title, missing_id in missing_movies_with_names]
if self.library.Radarr:
2021-12-30 17:13:01 +00:00
if self.radarr_details["add_missing"]:
try:
added = self.library.Radarr.add_tmdb(missing_tmdb_ids, **self.radarr_details)
2022-01-26 14:22:16 +00:00
self.added_to_radarr.extend([{"title": movie.title, "id": movie.tmdbId} for movie in added])
added_to_radarr += len(added)
except Failed as e:
logger.error(e)
2023-02-05 19:10:08 +00:00
except ArrException as e:
logger.stacktrace()
logger.error(f"Arr Error: {e}")
if "item_radarr_tag" in self.item_details:
try:
self.library.Radarr.edit_tags(missing_tmdb_ids, self.item_details["item_radarr_tag"], self.item_details["apply_tags"])
except Failed as e:
logger.error(e)
2023-02-05 19:10:08 +00:00
except ArrException as e:
logger.stacktrace()
logger.error(f"Arr Error: {e}")
if self.run_again:
self.run_again_movies.extend(missing_tmdb_ids)
2022-10-21 21:09:36 +00:00
if len(filtered_movies_with_names) > 0 and self.do_report:
self.library.add_filtered(self.name, filtered_movies_with_names, True)
if len(self.missing_shows) > 0 and self.library.is_show:
2021-12-30 00:37:31 +00:00
if self.details["show_missing"] is True:
logger.info("")
logger.separator(f"Missing Shows from Library: {self.name}", space=False, border=False)
2021-12-30 00:37:31 +00:00
logger.info("")
2021-05-12 14:25:48 +00:00
missing_shows_with_names = []
filtered_shows_with_names = []
for missing_id in self.missing_shows:
2021-05-12 14:25:48 +00:00
try:
2022-05-05 22:05:16 +00:00
title = self.config.TVDb.get_tvdb_obj(missing_id).title
2021-05-12 14:25:48 +00:00
except Failed as e:
logger.error(e)
continue
2022-10-24 06:17:49 +00:00
if self.check_missing_filters(missing_id, False, check_released=self.details["missing_only_released"]):
2022-05-05 22:05:16 +00:00
missing_shows_with_names.append((title, missing_id))
2021-05-12 14:25:48 +00:00
if self.details["show_missing"] is True:
2022-05-05 22:05:16 +00:00
logger.info(f"{self.name} {self.Type} | ? | {title} (TVDb: {missing_id})")
2021-08-06 17:46:13 +00:00
else:
filtered_shows_with_names.append((title, missing_id))
2021-08-06 17:46:13 +00:00
if self.details["show_filtered"] is True and self.details["show_missing"] is True:
2022-05-05 22:05:16 +00:00
logger.info(f"{self.name} {self.Type} | X | {title} (TVDb: {missing_id})")
2021-05-24 03:38:46 +00:00
logger.info("")
2021-05-12 14:25:48 +00:00
logger.info(f"{len(missing_shows_with_names)} Show{'s' if len(missing_shows_with_names) > 1 else ''} Missing")
if len(missing_shows_with_names) > 0:
2022-10-21 21:09:36 +00:00
if self.do_report:
self.library.add_missing(self.name, missing_shows_with_names, False)
2021-12-30 17:13:01 +00:00
if self.run_again or (self.library.Sonarr and (self.sonarr_details["add_missing"] or "item_sonarr_tag" in self.item_details)):
missing_tvdb_ids = [missing_id for title, missing_id in missing_shows_with_names]
if self.library.Sonarr:
2021-12-30 17:13:01 +00:00
if self.sonarr_details["add_missing"]:
try:
added = self.library.Sonarr.add_tvdb(missing_tvdb_ids, **self.sonarr_details)
2022-01-26 14:22:16 +00:00
self.added_to_sonarr.extend([{"title": show.title, "id": show.tvdbId} for show in added])
added_to_sonarr += len(added)
except Failed as e:
logger.error(e)
2023-02-05 19:10:08 +00:00
except ArrException as e:
logger.stacktrace()
logger.error(f"Arr Error: {e}")
if "item_sonarr_tag" in self.item_details:
try:
self.library.Sonarr.edit_tags(missing_tvdb_ids, self.item_details["item_sonarr_tag"], self.item_details["apply_tags"])
except Failed as e:
logger.error(e)
2023-02-05 19:10:08 +00:00
except ArrException as e:
logger.stacktrace()
logger.error(f"Arr Error: {e}")
if self.run_again:
self.run_again_shows.extend(missing_tvdb_ids)
2022-10-21 21:09:36 +00:00
if len(filtered_shows_with_names) > 0 and self.do_report:
self.library.add_filtered(self.name, filtered_shows_with_names, False)
if len(self.missing_parts) > 0 and self.library.is_show:
if self.details["show_missing"] is True:
for missing in self.missing_parts:
logger.info(f"{self.name} {self.Type} | ? | {missing}")
2022-10-21 21:09:36 +00:00
if self.do_report:
self.library.add_missing(self.name, self.missing_parts, False)
2021-11-03 14:36:11 +00:00
return added_to_radarr, added_to_sonarr
2021-08-22 15:54:33 +00:00
2021-09-15 03:16:59 +00:00
def load_collection_items(self):
if self.build_collection and self.obj:
self.items = self.library.get_collection_items(self.obj, self.smart_label_collection)
elif not self.build_collection:
2021-08-05 18:55:39 +00:00
logger.info("")
logger.separator(f"Items Found for {self.name} {self.Type}", space=False, border=False)
2021-08-05 18:55:39 +00:00
logger.info("")
2022-11-07 17:05:21 +00:00
self.items = self.found_items
2021-09-15 03:16:59 +00:00
if not self.items:
2021-12-17 14:24:46 +00:00
raise Failed(f"Plex Error: No {self.Type} items found")
2021-09-15 03:16:59 +00:00
def update_item_details(self):
2021-08-05 18:55:39 +00:00
logger.info("")
2023-12-26 22:07:38 +00:00
logger.separator(f"Updating Metadata of the Items in {self.name} {self.Type}", space=False, border=False)
2021-08-05 18:55:39 +00:00
logger.info("")
2021-08-22 15:55:37 +00:00
2021-09-15 03:16:59 +00:00
add_tags = self.item_details["item_label"] if "item_label" in self.item_details else None
remove_tags = self.item_details["item_label.remove"] if "item_label.remove" in self.item_details else None
sync_tags = self.item_details["item_label.sync"] if "item_label.sync" in self.item_details else None
2022-09-17 21:10:57 +00:00
add_genres = self.item_details["item_genre"] if "item_genre" in self.item_details else None
remove_genres = self.item_details["item_genre.remove"] if "item_genre.remove" in self.item_details else None
sync_genres = self.item_details["item_genre.sync"] if "item_genre.sync" in self.item_details else None
2022-08-31 13:49:52 +00:00
2022-01-25 01:46:48 +00:00
if "non_item_remove_label" in self.item_details:
2022-01-26 21:16:46 +00:00
rk_compare = [item.ratingKey for item in self.items]
2022-07-26 19:58:53 +00:00
for non_item in self.library.search(label=self.item_details["non_item_remove_label"], libtype=self.builder_level):
2022-05-10 13:56:29 +00:00
if non_item.ratingKey not in rk_compare:
self.library.edit_tags("label", non_item, remove_tags=self.item_details["non_item_remove_label"])
2022-01-25 01:46:48 +00:00
tmdb_paths = []
tvdb_paths = []
2021-09-15 03:16:59 +00:00
for item in self.items:
2023-02-27 18:37:23 +00:00
item = self.library.reload(item)
2022-06-26 16:31:22 +00:00
current_labels = [la.tag for la in self.library.item_labels(item)]
if "item_assets" in self.item_details and self.asset_directory and "Overlay" not in current_labels:
self.library.find_and_upload_assets(item, current_labels, asset_directory=self.asset_directory)
self.library.edit_tags("label", item, add_tags=add_tags, remove_tags=remove_tags, sync_tags=sync_tags)
2022-08-31 13:49:52 +00:00
self.library.edit_tags("genre", item, add_tags=add_genres, remove_tags=remove_genres, sync_tags=sync_genres)
2022-09-20 02:46:52 +00:00
if "item_edition" in self.item_details and item.editionTitle != self.item_details["item_edition"]:
self.library.query_data(item.editEditionTitle, self.item_details["item_edition"])
logger.info(f"{item.title[:25]:<25} | Edition | {self.item_details['item_edition']}")
2023-07-22 05:46:49 +00:00
path = None
2023-08-10 21:01:40 +00:00
if "item_radarr_tag" in self.item_details or self.radarr_details["add_existing"] or "item_sonarr_tag" in self.item_details or self.sonarr_details["add_existing"]:
if item.locations:
if self.library.is_movie:
path = os.path.dirname(str(item.locations[0]))
elif self.library.is_show:
path = str(item.locations[0])
if not path:
logger.error(f"Plex Error: No location found for {item.title}: {item.locations}")
2023-07-26 12:23:52 +00:00
if path and self.library.Radarr and item.ratingKey in self.library.movie_rating_key_map:
2021-12-09 15:23:51 +00:00
path = path.replace(self.library.Radarr.plex_path, self.library.Radarr.radarr_path)
path = path[:-1] if path.endswith(('/', '\\')) else path
tmdb_paths.append((self.library.movie_rating_key_map[item.ratingKey], path))
2023-07-26 12:23:52 +00:00
if path and self.library.Sonarr and item.ratingKey in self.library.show_rating_key_map:
2021-12-09 15:23:51 +00:00
path = path.replace(self.library.Sonarr.plex_path, self.library.Sonarr.sonarr_path)
path = path[:-1] if path.endswith(('/', '\\')) else path
tvdb_paths.append((self.library.show_rating_key_map[item.ratingKey], path))
2022-04-20 21:30:31 +00:00
if any([mn in plex.item_advance_keys for mn in self.item_details]) and hasattr(item, "preferences"):
advance_edits = {}
2022-01-24 22:36:40 +00:00
prefs = [p.id for p in item.preferences()]
for method_name, method_data in self.item_details.items():
if method_name in plex.item_advance_keys:
key, options = plex.item_advance_keys[method_name]
if key in prefs and getattr(item, key) != options[method_data]:
advance_edits[key] = options[method_data]
2022-05-09 20:40:39 +00:00
if advance_edits:
2023-12-26 22:07:38 +00:00
logger.debug(f"Metadata Update: {advance_edits}")
2022-05-09 20:40:39 +00:00
if self.library.edit_advance(item, advance_edits):
2023-12-26 22:07:38 +00:00
logger.info(f"{item.title} Advanced Metadata Update Successful")
2022-05-09 20:40:39 +00:00
else:
2023-12-26 22:07:38 +00:00
logger.error(f"{item.title} Advanced Metadata Update Failed")
2021-11-23 19:45:41 +00:00
if "item_tmdb_season_titles" in self.item_details and item.ratingKey in self.library.show_rating_key_map:
try:
tmdb_id = self.config.Convert.tvdb_to_tmdb(self.library.show_rating_key_map[item.ratingKey])
2022-01-21 16:34:19 +00:00
names = {s.season_number: s.name for s in self.config.TMDb.get_show(tmdb_id).seasons}
for season in self.library.query(item.seasons):
2022-03-25 20:13:51 +00:00
if season.index in names and season.title != names[season.index]:
season.editTitle(names[season.index])
except Failed as e:
logger.error(e)
2021-11-23 19:45:41 +00:00
# Locking should come before refreshing since refreshing can change metadata (i.e. if specified to both lock
# background/poster and also refreshing, assume that the item background/poster should be kept)
2021-11-21 00:15:45 +00:00
if "item_lock_background" in self.item_details:
self.library.query(item.lockArt if self.item_details["item_lock_background"] else item.unlockArt)
if "item_lock_poster" in self.item_details:
self.library.query(item.lockPoster if self.item_details["item_lock_poster"] else item.unlockPoster)
2021-11-23 19:45:41 +00:00
if "item_lock_title" in self.item_details:
self.library.edit_query(item, {"title.locked": 1 if self.item_details["item_lock_title"] else 0})
if "item_refresh" in self.item_details:
2022-01-11 19:37:48 +00:00
delay = self.item_details["item_refresh_delay"] if "item_refresh_delay" in self.item_details else self.library.item_refresh_delay
if delay > 0:
time.sleep(delay)
self.library.query(item.refresh)
2021-11-29 14:11:23 +00:00
if self.library.Radarr and tmdb_paths:
2023-04-09 06:46:27 +00:00
try:
if "item_radarr_tag" in self.item_details:
self.library.Radarr.edit_tags([t[0] if isinstance(t, tuple) else t for t in tmdb_paths], self.item_details["item_radarr_tag"], self.item_details["apply_tags"])
if self.radarr_details["add_existing"]:
added = self.library.Radarr.add_tmdb(tmdb_paths, **self.radarr_details)
self.added_to_radarr.extend([{"title": movie.title, "id": movie.tmdbId} for movie in added])
except Failed as e:
logger.error(e)
except ArrException as e:
logger.stacktrace()
logger.error(f"Arr Error: {e}")
2021-11-29 14:11:23 +00:00
if self.library.Sonarr and tvdb_paths:
2023-04-09 06:46:27 +00:00
try:
if "item_sonarr_tag" in self.item_details:
self.library.Sonarr.edit_tags([t[0] if isinstance(t, tuple) else t for t in tvdb_paths], self.item_details["item_sonarr_tag"], self.item_details["apply_tags"])
if self.sonarr_details["add_existing"]:
added = self.library.Sonarr.add_tvdb(tvdb_paths, **self.sonarr_details)
self.added_to_sonarr.extend([{"title": show.title, "id": show.tvdbId} for show in added])
except Failed as e:
logger.error(e)
except ArrException as e:
logger.stacktrace()
logger.error(f"Arr Error: {e}")
2021-09-15 03:16:59 +00:00
def load_collection(self):
2022-04-18 18:16:39 +00:00
if self.obj is None and self.smart_url:
self.library.create_smart_collection(self.name, self.smart_type_key, self.smart_url, self.ignore_blank_results)
2022-08-04 20:47:54 +00:00
logger.debug(f"Smart Collection Created: {self.smart_url}")
2022-04-18 18:16:39 +00:00
elif self.obj is None and self.blank_collection:
2022-01-27 21:24:50 +00:00
self.library.create_blank_collection(self.name)
2021-05-17 14:35:47 +00:00
elif self.smart_label_collection:
2021-05-16 22:06:51 +00:00
try:
2022-02-13 18:28:53 +00:00
if not self.library.smart_label_check(self.name):
raise Failed
smart_type, _, self.smart_url = self.build_filter("smart_label", self.smart_label, default_sort="random")
2021-05-17 14:35:47 +00:00
if not self.obj:
self.library.create_smart_collection(self.name, smart_type, self.smart_url, self.ignore_blank_results)
2021-05-16 22:06:51 +00:00
except Failed:
2021-12-13 07:30:19 +00:00
raise Failed(f"{self.Type} Error: Label: {self.name} was not added to any items in the Library")
self.obj = self.library.get_playlist(self.name) if self.playlist else self.library.get_collection(self.name, force_search=True)
2021-10-04 17:51:32 +00:00
if not self.exists:
self.created = True
2021-05-12 14:25:48 +00:00
2021-09-15 03:16:59 +00:00
def update_details(self):
2022-05-04 16:27:17 +00:00
updated_details = []
2021-09-15 03:16:59 +00:00
logger.info("")
2023-12-26 22:07:38 +00:00
logger.separator(f"Updating Metadata of {self.name} {self.Type}", space=False, border=False)
2021-09-15 03:16:59 +00:00
logger.info("")
2022-05-04 16:27:17 +00:00
if "summary" in self.summaries: summary = ("summary", self.summaries["summary"])
2023-03-30 19:51:04 +00:00
elif "translation" in self.summaries: summary = ("translation", self.summaries["translation"])
2022-05-04 16:27:17 +00:00
elif "tmdb_description" in self.summaries: summary = ("tmdb_description", self.summaries["tmdb_description"])
2022-12-19 21:36:51 +00:00
elif "tvdb_description" in self.summaries: summary = ("tvdb_description", self.summaries["tvdb_description"])
2022-05-04 16:27:17 +00:00
elif "letterboxd_description" in self.summaries: summary = ("letterboxd_description", self.summaries["letterboxd_description"])
elif "tmdb_summary" in self.summaries: summary = ("tmdb_summary", self.summaries["tmdb_summary"])
elif "tvdb_summary" in self.summaries: summary = ("tvdb_summary", self.summaries["tvdb_summary"])
elif "tmdb_biography" in self.summaries: summary = ("tmdb_biography", self.summaries["tmdb_biography"])
elif "tmdb_person" in self.summaries: summary = ("tmdb_person", self.summaries["tmdb_person"])
elif "tmdb_collection_details" in self.summaries: summary = ("tmdb_collection_details", self.summaries["tmdb_collection_details"])
elif "trakt_list_details" in self.summaries: summary = ("trakt_list_details", self.summaries["trakt_list_details"])
elif "tmdb_list_details" in self.summaries: summary = ("tmdb_list_details", self.summaries["tmdb_list_details"])
2022-12-19 21:36:51 +00:00
elif "tvdb_list_details" in self.summaries: summary = ("tvdb_list_details", self.summaries["tvdb_list_details"])
2022-05-04 16:27:17 +00:00
elif "letterboxd_list_details" in self.summaries: summary = ("letterboxd_list_details", self.summaries["letterboxd_list_details"])
elif "icheckmovies_list_details" in self.summaries: summary = ("icheckmovies_list_details", self.summaries["icheckmovies_list_details"])
elif "tmdb_actor_details" in self.summaries: summary = ("tmdb_actor_details", self.summaries["tmdb_actor_details"])
elif "tmdb_crew_details" in self.summaries: summary = ("tmdb_crew_details", self.summaries["tmdb_crew_details"])
elif "tmdb_director_details" in self.summaries: summary = ("tmdb_director_details", self.summaries["tmdb_director_details"])
elif "tmdb_producer_details" in self.summaries: summary = ("tmdb_producer_details", self.summaries["tmdb_producer_details"])
elif "tmdb_writer_details" in self.summaries: summary = ("tmdb_writer_details", self.summaries["tmdb_writer_details"])
elif "tmdb_movie_details" in self.summaries: summary = ("tmdb_movie_details", self.summaries["tmdb_movie_details"])
elif "tvdb_movie_details" in self.summaries: summary = ("tvdb_movie_details", self.summaries["tvdb_movie_details"])
elif "tvdb_show_details" in self.summaries: summary = ("tvdb_show_details", self.summaries["tvdb_show_details"])
elif "tmdb_show_details" in self.summaries: summary = ("tmdb_show_details", self.summaries["tmdb_show_details"])
2024-01-03 16:56:55 +00:00
else: summary = (None, None)
2021-03-01 04:16:08 +00:00
2022-03-25 20:13:51 +00:00
if self.playlist:
2024-01-03 16:56:55 +00:00
if summary[1]:
2022-06-28 20:34:20 +00:00
if str(summary[1]) != str(self.obj.summary):
try:
2024-01-10 18:56:26 +00:00
self.obj.editSummary(str(summary[1]))
2022-06-28 20:34:20 +00:00
logger.info(f"Summary ({summary[0]}) | {summary[1]:<25}")
2023-12-26 22:07:38 +00:00
logger.info("Metadata: Update Completed")
2022-06-28 20:34:20 +00:00
updated_details.append("Metadata")
except NotFound:
2023-12-26 22:07:38 +00:00
logger.error("Metadata: Failed to Update Please delete the collection and run again")
2022-06-28 20:34:20 +00:00
logger.info("")
else:
2023-10-16 20:04:46 +00:00
self.library._reload(self.obj)
2023-02-21 20:32:08 +00:00
#self.obj.batchEdits()
2022-03-25 20:13:51 +00:00
batch_display = "Collection Metadata Edits"
2024-01-03 16:56:55 +00:00
if summary[1] and str(summary[1]) != str(self.obj.summary):
2022-05-04 16:27:17 +00:00
self.obj.editSummary(summary[1])
batch_display += f"\nSummary ({summary[0]}) | {summary[1]:<25}"
2022-03-25 20:13:51 +00:00
2023-03-31 14:42:06 +00:00
if "sort_title" in self.details:
new_sort_title = str(self.details["sort_title"])
if "<<title>>" in new_sort_title:
title = self.name
for op in ["The ", "A ", "An "]:
if title.startswith(f"{op} "):
2023-04-01 19:02:32 +00:00
title = f"{title[len(op):].strip()}, {op.strip()}"
2023-03-31 14:42:06 +00:00
break
2023-04-01 19:02:32 +00:00
new_sort_title = new_sort_title.replace("<<title>>", title)
2023-03-31 14:42:06 +00:00
if new_sort_title != str(self.obj.titleSort):
2023-04-01 19:02:32 +00:00
self.obj.editSortTitle(new_sort_title)
batch_display += f"\nSort Title | {new_sort_title}"
2022-03-25 20:13:51 +00:00
if "content_rating" in self.details and str(self.details["content_rating"]) != str(self.obj.contentRating):
self.obj.editContentRating(self.details["content_rating"])
batch_display += f"\nContent Rating | {self.details['content_rating']}"
2022-06-08 13:29:56 +00:00
add_tags = self.details["label"] if "label" in self.details else []
2022-03-25 20:13:51 +00:00
remove_tags = self.details["label.remove"] if "label.remove" in self.details else None
sync_tags = self.details["label.sync"] if "label.sync" in self.details else None
2022-06-08 13:29:56 +00:00
if sync_tags:
2024-04-22 14:20:12 +00:00
sync_tags.append("Kometa")
2022-06-08 13:29:56 +00:00
else:
2024-04-22 14:20:12 +00:00
add_tags.append("Kometa")
2022-07-06 18:38:26 +00:00
tag_results = self.library.edit_tags('label', self.obj, add_tags=add_tags, remove_tags=remove_tags, sync_tags=sync_tags, do_print=False)
2022-05-06 13:32:37 +00:00
if tag_results:
batch_display += f"\n{tag_results}"
2022-03-25 20:13:51 +00:00
logger.info(batch_display)
if len(batch_display) > 25:
try:
2023-02-21 20:32:08 +00:00
#self.obj.saveEdits()
2023-12-26 22:07:38 +00:00
logger.info("Metadata: Update Completed")
2022-05-04 16:27:17 +00:00
updated_details.append("Metadata")
2022-03-25 20:13:51 +00:00
except NotFound:
2023-12-26 22:07:38 +00:00
logger.error("Metadata: Failed to Update Please delete the collection and run again")
2022-03-25 20:13:51 +00:00
logger.info("")
2021-03-01 04:16:08 +00:00
2022-05-04 16:27:17 +00:00
advance_update = False
2022-03-25 20:13:51 +00:00
if "collection_mode" in self.details:
2022-07-15 13:57:27 +00:00
if (self.blank_collection and self.created) or int(self.obj.collectionMode) not in plex.collection_mode_keys \
2022-05-04 16:27:17 +00:00
or plex.collection_mode_keys[int(self.obj.collectionMode)] != self.details["collection_mode"]:
2022-07-15 13:57:27 +00:00
if self.blank_collection and self.created:
2022-07-18 18:14:32 +00:00
self.library.collection_mode_query(self.obj, "hide")
logger.info(f"Collection Mode | hide")
2022-07-26 18:30:40 +00:00
self.library.collection_mode_query(self.obj, "default")
logger.info(f"Collection Mode | default")
2022-05-04 16:27:17 +00:00
self.library.collection_mode_query(self.obj, self.details["collection_mode"])
logger.info(f"Collection Mode | {self.details['collection_mode']}")
advance_update = True
2021-02-20 05:41:45 +00:00
2022-04-08 18:29:38 +00:00
if "collection_filtering" in self.details:
2022-10-26 06:56:12 +00:00
try:
self.library.edit_query(self.obj, {"collectionFilterBasedOnUser": 0 if self.details["collection_filtering"] == "admin" else 1}, advanced=True)
advance_update = True
except NotFound:
logger.error("Collection Error: collection_filtering requires a more recent version of Plex Media Server")
2022-04-08 18:29:38 +00:00
2022-03-25 20:13:51 +00:00
if "collection_order" in self.details:
2022-05-04 16:27:17 +00:00
if int(self.obj.collectionSort) not in plex.collection_order_keys \
2022-03-25 20:13:51 +00:00
or plex.collection_order_keys[int(self.obj.collectionSort)] != self.details["collection_order"]:
self.library.collection_order_query(self.obj, self.details["collection_order"])
logger.info(f"Collection Order | {self.details['collection_order']}")
2022-05-04 16:27:17 +00:00
advance_update = True
2021-06-30 15:07:02 +00:00
2022-03-25 20:13:51 +00:00
if "visible_library" in self.details or "visible_home" in self.details or "visible_shared" in self.details:
visibility = self.library.collection_visibility(self.obj)
visible_library = None
visible_home = None
visible_shared = None
2021-06-30 15:07:02 +00:00
2022-03-25 20:13:51 +00:00
if "visible_library" in self.details and self.details["visible_library"] != visibility["library"]:
visible_library = self.details["visible_library"]
2021-06-30 15:07:02 +00:00
2022-03-25 20:13:51 +00:00
if "visible_home" in self.details and self.details["visible_home"] != visibility["home"]:
visible_home = self.details["visible_home"]
2021-06-30 15:07:02 +00:00
2022-03-25 20:13:51 +00:00
if "visible_shared" in self.details and self.details["visible_shared"] != visibility["shared"]:
visible_shared = self.details["visible_shared"]
2021-06-30 15:07:02 +00:00
2022-03-25 20:13:51 +00:00
if visible_library is not None or visible_home is not None or visible_shared is not None:
self.library.collection_visibility_update(self.obj, visibility=visibility, library=visible_library, home=visible_home, shared=visible_shared)
2022-05-04 16:27:17 +00:00
advance_update = True
2022-03-25 20:13:51 +00:00
logger.info("Collection Visibility Updated")
2021-05-20 19:26:56 +00:00
2022-05-04 16:27:17 +00:00
if advance_update and "Metadata" not in updated_details:
updated_details.append("Metadata")
asset_location = None
2022-04-21 05:35:07 +00:00
if self.asset_directory:
2021-02-20 05:41:45 +00:00
name_mapping = self.name
if "name_mapping" in self.details:
if self.details["name_mapping"]: name_mapping = self.details["name_mapping"]
2021-12-13 07:30:19 +00:00
else: logger.error(f"{self.Type} Error: name_mapping attribute is blank")
2022-04-25 22:55:59 +00:00
try:
asset_poster, asset_background, asset_location, _ = self.library.find_item_assets(name_mapping, asset_directory=self.asset_directory)
if asset_poster:
self.posters["asset_directory"] = asset_poster
if asset_background:
self.backgrounds["asset_directory"] = asset_background
2022-04-25 22:55:59 +00:00
except Failed as e:
if self.library.asset_folders and (self.library.show_missing_assets or self.library.create_asset_folders):
2022-04-27 15:11:10 +00:00
logger.warning(e)
2023-03-04 20:20:52 +00:00
if self.mapping_name in self.library.collection_images or self.name in self.library.collection_images:
style_data = self.library.collection_images[self.mapping_name if self.mapping_name in self.library.collection_images else self.name]
if style_data and "url_poster" in style_data and style_data["url_poster"]:
self.posters["style_data"] = style_data["url_poster"]
elif style_data and "tpdb_poster" in style_data and style_data["tpdb_poster"]:
self.posters["style_data"] = f"https://theposterdb.com/api/assets/{style_data['tpdb_poster']}"
if style_data and "url_background" in style_data and style_data["url_background"]:
self.backgrounds["style_data"] = style_data["url_background"]
elif style_data and "tpdb_background" in style_data and style_data["tpdb_background"]:
self.backgrounds["style_data"] = f"https://theposterdb.com/api/assets/{style_data['tpdb_background']}"
2021-02-21 08:13:07 +00:00
self.collection_poster = util.pick_image(self.obj.title, self.posters, self.library.prioritize_assets, self.library.download_url_assets, asset_location)
self.collection_background = util.pick_image(self.obj.title, self.backgrounds, self.library.prioritize_assets, self.library.download_url_assets, asset_location, is_poster=False)
2021-03-01 20:59:10 +00:00
clean_temp = False
2024-04-22 14:20:12 +00:00
if isinstance(self.collection_poster, KometaImage):
clean_temp = True
item_vars = {"title": self.name, "titleU": self.name.upper(), "titleL": self.name.lower()}
self.collection_poster = self.collection_poster.save(item_vars)
if self.collection_poster or self.collection_background:
2022-05-04 16:27:17 +00:00
pu, bu = self.library.upload_images(self.obj, poster=self.collection_poster, background=self.collection_background)
if pu or bu:
updated_details.append("Image")
2021-06-12 15:29:17 +00:00
if clean_temp:
code_base = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
posters_dir = os.path.join(code_base, "defaults", "posters")
for filename in os.listdir(posters_dir):
if "temp" in filename:
os.remove(os.path.join(posters_dir, filename))
2022-05-04 16:27:17 +00:00
if self.url_theme: # TODO: cache theme path to not constantly upload
2022-03-19 18:29:03 +00:00
self.library.upload_theme(self.obj, url=self.url_theme)
2022-03-06 06:09:03 +00:00
elif self.file_theme:
2022-03-19 18:29:03 +00:00
self.library.upload_theme(self.obj, filepath=self.file_theme)
2022-05-04 16:27:17 +00:00
return updated_details
2022-03-06 06:09:03 +00:00
2021-07-29 13:36:30 +00:00
def sort_collection(self):
2021-09-15 03:16:59 +00:00
logger.info("")
logger.separator(f"Sorting {self.name} {self.Type}", space=False, border=False)
2021-09-15 03:16:59 +00:00
logger.info("")
if not isinstance(self.custom_sort, list):
2022-11-07 17:05:21 +00:00
items = self.found_items
if self.custom_sort == "custom.desc":
items = items[::-1]
else:
2022-03-27 20:59:14 +00:00
plex_search = {"sort_by": self.custom_sort}
2022-07-26 19:58:53 +00:00
if self.builder_level in ["season", "episode"]:
plex_search["type"] = f"{self.builder_level}s"
plex_search["any"] = {f"{self.builder_level}_collection": [self.name]} # noqa
2022-03-27 20:59:14 +00:00
else:
plex_search["any"] = {"collection": [self.name]}
2022-11-11 16:59:39 +00:00
try:
search_data = self.build_filter("plex_search", plex_search)
except FilterFailed as e:
if self.ignore_blank_results:
raise
else:
raise Failed(str(e))
2023-06-06 14:57:40 +00:00
items = self.library.fetchItems(search_data[2])
2021-07-29 13:36:30 +00:00
previous = None
2022-11-30 21:12:52 +00:00
sort_edit = False
2022-03-28 19:30:46 +00:00
for i, item in enumerate(items, 0):
2022-11-12 16:42:53 +00:00
try:
if len(self.items) <= i or item.ratingKey != self.items[i].ratingKey:
text = f"after {util.item_title(previous)}" if previous else "to the beginning"
self.library.moveItem(self.obj, item, previous)
logger.info(f"Moving {util.item_title(item)} {text}")
2022-11-30 21:12:52 +00:00
sort_edit = True
2022-11-12 16:42:53 +00:00
previous = item
except Failed:
logger.error(f"Failed to Move {util.item_title(item)}")
2022-11-30 21:12:52 +00:00
sort_edit = True
if not sort_edit:
logger.info("No Sorting Required")
2021-12-17 14:24:46 +00:00
def sync_trakt_list(self):
logger.info("")
logger.separator(f"Syncing {self.name} {self.Type} to Trakt List {self.sync_to_trakt_list}", space=False, border=False)
logger.info("")
if self.obj:
2023-10-16 20:04:46 +00:00
self.library._reload(self.obj)
self.load_collection_items()
current_ids = []
for item in self.items:
for pl_library in self.libraries:
new_id = None
if isinstance(item, Movie) and item.ratingKey in pl_library.movie_rating_key_map:
new_id = (pl_library.movie_rating_key_map[item.ratingKey], "tmdb")
elif isinstance(item, Show) and item.ratingKey in pl_library.show_rating_key_map:
new_id = (pl_library.show_rating_key_map[item.ratingKey], "tvdb")
elif isinstance(item, Season) and item.parentRatingKey in pl_library.show_rating_key_map:
new_id = (f"{pl_library.show_rating_key_map[item.parentRatingKey]}_{item.seasonNumber}", "tvdb_season")
elif isinstance(item, Episode) and item.grandparentRatingKey in pl_library.show_rating_key_map:
new_id = (f"{pl_library.show_rating_key_map[item.grandparentRatingKey]}_{item.seasonNumber}_{item.episodeNumber}", "tvdb_episode")
if new_id:
current_ids.append(new_id)
break
2022-05-09 15:22:41 +00:00
if self.sync_missing_to_trakt_list:
current_ids.extend([(mm, "tmdb") for mm in self.missing_movies])
current_ids.extend([(ms, "tvdb") for ms in self.missing_shows])
self.config.Trakt.sync_list(self.sync_to_trakt_list, current_ids)
2021-12-26 15:48:52 +00:00
def delete(self):
title = self.obj.title if self.obj else self.name
2022-05-28 16:01:51 +00:00
if self.playlist:
output = f"Deleting {self.Type} {title}"
2022-05-28 16:01:51 +00:00
elif self.obj:
output = f"{self.Type} {self.obj.title} deleted"
2022-11-01 18:20:00 +00:00
if self.smart_label_collection:
for item in self.library.search(label=self.name, libtype=self.builder_level):
self.library.edit_tags("label", item, remove_tags=self.name)
2022-05-28 16:01:51 +00:00
else:
output = ""
if self.playlist:
2022-05-28 16:01:51 +00:00
for user in self.valid_users:
2024-02-28 20:40:17 +00:00
try:
if user == self.library.account.username:
_ = self.library.get_playlist(title) # Verify if this playlist exists in Admin to avoid log confusion
self.library.delete(self.obj)
2024-02-28 20:40:17 +00:00
else:
self.library.delete_user_playlist(title, user)
2024-02-28 20:40:17 +00:00
output += f"\nPlaylist deleted on User {user}"
except Failed:
output += f"\nPlaylist not found on User {user}"
elif self.obj:
self.library.delete(self.obj)
2021-12-26 15:48:52 +00:00
return output
def sync_playlist(self):
if self.obj and self.valid_users:
2021-12-23 17:02:07 +00:00
logger.info("")
logger.separator(f"Syncing Playlist to Users", space=False, border=False)
2021-12-23 17:02:07 +00:00
logger.info("")
2021-12-26 15:48:52 +00:00
for user in self.valid_users:
2021-12-23 17:02:07 +00:00
try:
self.library.delete_user_playlist(self.obj.title, user)
2022-11-28 22:00:54 +00:00
except Failed:
2021-12-23 17:02:07 +00:00
pass
if user != self.library.account.username:
2024-02-28 20:40:17 +00:00
self.obj.copyToUser(user).editSummary(summary=self.obj.summary).reload()
logger.info(f"Playlist: {self.name} synced to {user}")
def exclude_admin_from_playlist(self):
if self.obj and (self.exclude_users is not None and self.library.account.username in self.exclude_users):
logger.info("")
logger.separator(f"Excluding Admin from Playlist", space=False, border=False)
logger.info("")
try:
self.library.delete(self.obj)
2024-02-28 20:40:17 +00:00
logger.info(f"Playlist: {self.name} deleted on User {self.library.account.username}")
except Failed:
2024-02-28 20:40:17 +00:00
logger.info(f"Playlist: {self.name} not found on User {self.library.account.username}")
2021-12-23 17:02:07 +00:00
2021-12-17 14:24:46 +00:00
def send_notifications(self, playlist=False):
2021-12-26 15:48:52 +00:00
if self.obj and self.details["changes_webhooks"] and \
2021-12-07 06:29:41 +00:00
(self.created or len(self.notification_additions) > 0 or len(self.notification_removals) > 0):
2023-10-16 20:04:46 +00:00
self.library._reload(self.obj)
2021-11-28 07:14:35 +00:00
try:
self.library.Webhooks.collection_hooks(
2021-12-26 15:48:52 +00:00
self.details["changes_webhooks"],
2021-11-28 07:14:35 +00:00
self.obj,
poster_url=self.collection_poster.location if self.collection_poster and self.collection_poster.is_url else None,
background_url=self.collection_background.location if self.collection_background and self.collection_background.is_url else None,
2021-11-28 07:14:35 +00:00
created=self.created,
additions=self.notification_additions,
2021-12-17 14:24:46 +00:00
removals=self.notification_removals,
radarr=self.added_to_radarr,
sonarr=self.added_to_sonarr,
2021-12-17 14:24:46 +00:00
playlist=playlist
2021-11-28 07:14:35 +00:00
)
except Failed as e:
logger.stacktrace()
2021-11-28 07:14:35 +00:00
logger.error(f"Webhooks Error: {e}")
2021-10-04 17:51:32 +00:00
2021-05-26 14:45:33 +00:00
def run_collections_again(self):
self.obj = self.library.get_collection(self.name, force_search=True)
2021-05-12 14:25:48 +00:00
name, collection_items = self.library.get_collection_name_and_items(self.obj, self.smart_label_collection)
2021-10-04 17:51:32 +00:00
self.created = False
rating_keys = []
2021-12-30 21:29:16 +00:00
amount_added = 0
2021-11-03 14:36:11 +00:00
self.notification_additions = []
self.added_to_radarr = []
self.added_to_sonarr = []
for mm in self.run_again_movies:
2021-05-26 14:45:33 +00:00
if mm in self.library.movie_map:
rating_keys.extend(self.library.movie_map[mm])
2021-03-05 16:04:28 +00:00
if self.library.is_show:
for sm in self.run_again_shows:
2021-05-26 14:45:33 +00:00
if sm in self.library.show_map:
rating_keys.extend(self.library.show_map[sm])
2021-03-01 20:59:10 +00:00
if len(rating_keys) > 0:
for rating_key in rating_keys:
try:
2023-02-27 19:54:52 +00:00
current = self.library.fetch_item(int(rating_key))
except Failed as e:
logger.error(e)
2021-03-01 20:59:10 +00:00
continue
if current in collection_items:
2021-12-17 14:24:46 +00:00
logger.info(f"{name} {self.Type} | = | {util.item_title(current)}")
2021-03-01 20:59:10 +00:00
else:
2021-09-30 20:55:29 +00:00
self.library.alter_collection(current, name, smart_label_collection=self.smart_label_collection)
2021-12-30 21:29:16 +00:00
amount_added += 1
2021-12-17 14:24:46 +00:00
logger.info(f"{name} {self.Type} | + | {util.item_title(current)}")
2021-10-04 17:51:32 +00:00
if self.library.is_movie and current.ratingKey in self.library.movie_rating_key_map:
add_id = self.library.movie_rating_key_map[current.ratingKey]
elif self.library.is_show and current.ratingKey in self.library.show_rating_key_map:
add_id = self.library.show_rating_key_map[current.ratingKey]
else:
add_id = None
self.notification_additions.append(util.item_set(current, add_id))
2021-10-04 17:51:32 +00:00
self.send_notifications()
2022-07-26 19:58:53 +00:00
logger.info(f"{len(rating_keys)} {self.builder_level.capitalize()}{'s' if len(rating_keys) > 1 else ''} Processed")
2021-03-01 20:59:10 +00:00
if len(self.run_again_movies) > 0:
2021-03-01 20:59:10 +00:00
logger.info("")
for missing_id in self.run_again_movies:
2021-05-26 14:45:33 +00:00
if missing_id not in self.library.movie_map:
2021-03-01 20:59:10 +00:00
try:
movie = self.config.TMDb.get_movie(missing_id)
except Failed as e:
logger.error(e)
continue
if self.details["show_missing"] is True:
2022-01-21 16:34:19 +00:00
current_title = f"{movie.title} ({movie.release_date.year})" if movie.release_date else movie.title
2021-12-17 14:24:46 +00:00
logger.info(f"{name} {self.Type} | ? | {current_title} (TMDb: {missing_id})")
2021-03-01 20:59:10 +00:00
logger.info("")
logger.info(f"{len(self.run_again_movies)} Movie{'s' if len(self.run_again_movies) > 1 else ''} Missing")
2021-03-01 20:59:10 +00:00
if len(self.run_again_shows) > 0 and self.library.is_show:
2021-03-01 20:59:10 +00:00
logger.info("")
for missing_id in self.run_again_shows:
2021-05-26 14:45:33 +00:00
if missing_id not in self.library.show_map:
2021-03-01 20:59:10 +00:00
try:
2022-05-05 22:05:16 +00:00
title = self.config.TVDb.get_tvdb_obj(missing_id).title
2021-03-01 20:59:10 +00:00
except Failed as e:
logger.error(e)
continue
if self.details["show_missing"] is True:
2021-12-17 14:24:46 +00:00
logger.info(f"{name} {self.Type} | ? | {title} (TVDb: {missing_id})")
logger.info(f"{len(self.run_again_shows)} Show{'s' if len(self.run_again_shows) > 1 else ''} Missing")
2021-12-30 21:29:16 +00:00
2022-01-25 07:45:31 +00:00
return amount_added