finish organizing

pull/351/head
meisnate12 3 years ago
parent fcb52d3b9c
commit a2fad0a170

@ -118,8 +118,8 @@ numbered_builders = [
"trakt_watched", "trakt_watched",
"trakt_collected" "trakt_collected"
] ]
smart_collection_invalid = ["collection_order"] smart_invalid = ["collection_order"]
smart_url_collection_invalid = [ smart_url_invalid = [
"run_again", "sync_mode", "show_filtered", "show_missing", "save_missing", "smart_label", "run_again", "sync_mode", "show_filtered", "show_missing", "save_missing", "smart_label",
"radarr_add", "radarr_folder", "radarr_monitor", "radarr_availability", "radarr_add", "radarr_folder", "radarr_monitor", "radarr_availability",
"radarr_quality", "radarr_tag", "radarr_search", "radarr_quality", "radarr_tag", "radarr_search",
@ -163,18 +163,12 @@ ignored_details = [
"tmdb_person", "tmdb_person",
"build_collection" "build_collection"
] ]
details = [ details = ["collection_mode", "collection_order", "label"] + boolean_details + string_details
"collection_mode", "collection_order", "label"
] + boolean_details + string_details
collectionless_details = [ collectionless_details = [
"collection_order", "plex_collectionless", "collection_order", "plex_collectionless",
"label", "label_sync_mode", "test" "label", "label_sync_mode", "test"
] + poster_details + background_details + summary_details + string_details ] + poster_details + background_details + summary_details + string_details
item_details = [ item_details = ["item_label", "item_radarr_tag", "item_sonarr_tag", "item_overlay"] + list(plex.item_advance_keys.keys())
"item_label", "item_radarr_tag", "item_sonarr_tag", "item_overlay",
"item_episode_sorting", "item_keep_episodes", "item_delete_episodes", "item_season_display",
"item_episode_ordering", "item_metadata_language", "item_use_original_title"
]
radarr_details = ["radarr_add", "radarr_folder", "radarr_monitor", "radarr_search", "radarr_availability", "radarr_quality", "radarr_tag"] radarr_details = ["radarr_add", "radarr_folder", "radarr_monitor", "radarr_search", "radarr_availability", "radarr_quality", "radarr_tag"]
sonarr_details = ["sonarr_add", "sonarr_folder", "sonarr_monitor", "sonarr_language", "sonarr_series", "sonarr_quality", "sonarr_season", "sonarr_search", "sonarr_cutoff_search", "sonarr_tag"] sonarr_details = ["sonarr_add", "sonarr_folder", "sonarr_monitor", "sonarr_language", "sonarr_series", "sonarr_quality", "sonarr_season", "sonarr_search", "sonarr_cutoff_search", "sonarr_tag"]
all_filters = [ all_filters = [
@ -541,339 +535,46 @@ class CollectionBuilder:
self.smart = self.smart_url or self.smart_label_collection self.smart = self.smart_url or self.smart_label_collection
for method_key, method_data in self.data.items(): for method_key, method_data in self.data.items():
if method_key.lower() in ignored_details: method_name, method_mod, method_final = self._split(method_key)
if method_name in ignored_details:
continue continue
logger.info("") logger.debug("")
logger.info(f"Validating Method: {method_key}") logger.debug(f"Validating Method: {method_key}")
if "trakt" in method_key.lower() and not self.config.Trakt: raise Failed(f"Collection Error: {method_key} requires Trakt to be configured")
elif "radarr" in method_key.lower() and not self.library.Radarr: raise Failed(f"Collection Error: {method_key} requires Radarr to be configured")
elif "sonarr" in method_key.lower() and not self.library.Sonarr: raise Failed(f"Collection Error: {method_key} requires Sonarr to be configured")
elif "tautulli" in method_key.lower() and not self.library.Tautulli: raise Failed(f"Collection Error: {method_key} requires Tautulli to be configured")
elif "mal" in method_key.lower() and not self.config.MyAnimeList: raise Failed(f"Collection Error: {method_key} requires MyAnimeList to be configured")
elif method_data is not None:
logger.debug(f"Value: {method_data}") logger.debug(f"Value: {method_data}")
method_name, method_mod, method_final = self._split(method_key) if method_data is None and method_name in all_builders + plex.searches: raise Failed(f"Collection Error: {method_final} attribute is blank")
if method_name in show_only_builders and self.library.is_movie: elif method_data is None: logger.warning(f"Collection Warning: {method_final} attribute is blank")
raise Failed(f"Collection Error: {method_name} attribute only works for show libraries") elif not self.config.Trakt and "trakt" in method_name: raise Failed(f"Collection Error: {method_final} requires Trakt to be configured")
elif method_name in movie_only_builders and self.library.is_show: elif not self.library.Radarr and "radarr" in method_name: raise Failed(f"Collection Error: {method_final} requires Radarr to be configured")
raise Failed(f"Collection Error: {method_name} attribute only works for movie libraries") elif not self.library.Sonarr and "sonarr" in method_name: raise Failed(f"Collection Error: {method_final} requires Sonarr to be configured")
elif method_name in plex.movie_only_searches and self.library.is_show: elif not self.library.Tautulli and "tautulli" in method_name: raise Failed(f"Collection Error: {method_final} requires Tautulli to be configured")
raise Failed(f"Collection Error: {method_name} plex search only works for movie libraries") elif not self.config.MyAnimeList and "mal" in method_name: raise Failed(f"Collection Error: {method_final} requires MyAnimeList to be configured")
elif method_name in plex.show_only_searches and self.library.is_movie: elif self.library.is_movie and method_name in show_only_builders: raise Failed(f"Collection Error: {method_final} attribute only works for show libraries")
raise Failed(f"Collection Error: {method_name} plex search only works for show libraries") elif self.library.is_show and method_name in movie_only_builders: raise Failed(f"Collection Error: {method_final} attribute only works for movie libraries")
elif method_name in smart_collection_invalid and self.smart: elif self.library.is_show and method_name in plex.movie_only_searches: raise Failed(f"Collection Error: {method_final} plex search only works for movie libraries")
raise Failed(f"Collection Error: {method_name} attribute only works with normal collections") elif self.library.is_movie and method_name in plex.show_only_searches: raise Failed(f"Collection Error: {method_final} plex search only works for show libraries")
elif method_name not in collectionless_details and self.collectionless: elif self.smart and method_name in smart_invalid: raise Failed(f"Collection Error: {method_final} attribute only works with normal collections")
raise Failed(f"Collection Error: {method_name} attribute does not work for Collectionless collection") elif self.collectionless and method_name not in collectionless_details: raise Failed(f"Collection Error: {method_final} attribute does not work for Collectionless collection")
elif self.smart_url and (method_name in all_builders or method_name in smart_url_collection_invalid): elif self.smart_url and method_name in all_builders + smart_url_invalid: raise Failed(f"Collection Error: {method_final} builder not allowed when using smart_filter")
raise Failed(f"Collection Error: {method_name} builder not allowed when using smart_filter") elif method_name in summary_details: self._summary(method_name, method_data)
elif method_name in summary_details: elif method_name in poster_details: self._poster(method_name, method_data)
self._summary(method_name, method_data) elif method_name in background_details: self._background(method_name, method_data)
elif method_name in poster_details: elif method_name in details: self._details(method_name, method_data, method_final, methods)
self._poster(method_name, method_data) elif method_name in item_details: self._item_details(method_name, method_data, method_mod, method_final, methods)
elif method_name in background_details: elif method_name in radarr_details: self._radarr(method_name, method_data)
self._background(method_name, method_data) elif method_name in sonarr_details: self._sonarr(method_name, method_data)
elif method_name in details: elif method_name in anidb.builders: self._anidb(method_name, method_data)
self._details(method_name, method_data, method_final, methods) elif method_name in anilist.builders: self._anilist(method_name, method_data)
elif method_name in details: elif method_name in icheckmovies.builders: self._icheckmovies(method_name, method_data)
self._details(method_name, method_data, method_final, methods) elif method_name in letterboxd.builders: self._letterboxd(method_name, method_data)
elif method_name in item_details: elif method_name in imdb.builders: self._imdb(method_name, method_data)
self._item_details(method_name, method_data, method_mod, method_final, methods) elif method_name in mal.builders: self._mal(method_name, method_data)
elif method_name in radarr_details: elif method_name in plex.builders or method_final in plex.searches: self._plex(method_name, method_data)
self._radarr(method_name, method_data) elif method_name in tautulli.builders: self._tautulli(method_name, method_data)
elif method_name in sonarr_details: elif method_name in tmdb.builders: self._tmdb(method_name, method_data)
self._sonarr(method_name, method_data) elif method_name in trakt.builders: self._trakt(method_name, method_data)
elif method_name in anidb.builders: elif method_name in tvdb.builders: self._tvdb(method_name, method_data)
self._anidb(method_name, method_data) elif method_name == "filters": self._filters(method_name, method_data)
elif method_name in anilist.builders: else: raise Failed(f"Collection Error: {method_final} attribute not supported")
self._anilist(method_name, method_data)
elif method_name in icheckmovies.builders:
self._icheckmovies(method_name, method_data)
elif method_final in plex.searches:
self.methods.append(("plex_search", self.build_filter("plex_search", {"any": {method_name: method_data}})))
elif method_name == "plex_all":
self.methods.append((method_name, True))
elif method_name == "mal_id":
for mal_id in util.get_int_list(method_data, "MyAnimeList ID"):
self.methods.append((method_name, mal_id))
elif method_name == "trakt_list":
for trakt_list in self.config.Trakt.validate_trakt(method_data, self.library.is_movie):
self.methods.append((method_name, trakt_list))
elif method_name == "trakt_list_details":
trakt_lists = self.config.Trakt.validate_trakt(method_data, self.library.is_movie)
self.summaries[method_name] = self.config.Trakt.list_description(trakt_lists[0])
for trakt_list in trakt_lists:
self.methods.append((method_name[:-8], trakt_list))
elif method_name in ["trakt_watchlist", "trakt_collection"]:
for trakt_list in self.config.Trakt.validate_trakt(method_data, self.library.is_movie, trakt_type=method_name[6:]):
self.methods.append((method_name, trakt_list))
elif method_name == "imdb_list":
for imdb_dict in self.config.IMDb.validate_imdb_lists(method_data, self.language):
self.methods.append((method_name, imdb_dict))
elif method_name == "letterboxd_list":
for letterboxd_list in self.config.Letterboxd.validate_letterboxd_lists(method_data, self.language):
self.methods.append((method_name, letterboxd_list))
elif method_name == "letterboxd_list_details":
letterboxd_lists = self.config.Letterboxd.validate_letterboxd_lists(method_data, self.language)
for letterboxd_list in letterboxd_lists:
self.methods.append((method_name[:-8], letterboxd_list))
self.summaries[method_name] = self.config.Letterboxd.get_list_description(letterboxd_lists[0], self.language)
elif method_name in dictionary_builders:
for dict_data in util.get_list(method_data):
if isinstance(dict_data, dict):
def get_int(parent, int_method, data_in, methods_in, default_in, minimum=1, maximum=None):
if int_method not in methods_in:
logger.warning(f"Collection Warning: {parent} {int_method} attribute not found using {default_in} as default")
elif not data_in[methods_in[int_method]]:
logger.warning(f"Collection Warning: {parent} {methods_in[int_method]} attribute is blank using {default_in} as default")
elif isinstance(data_in[methods_in[int_method]], int) and data_in[methods_in[int_method]] >= minimum:
if maximum is None or data_in[methods_in[int_method]] <= maximum:
return data_in[methods_in[int_method]]
else:
logger.warning(f"Collection Warning: {parent} {methods_in[int_method]} attribute {data_in[methods_in[int_method]]} invalid must an integer <= {maximum} using {default_in} as default")
else:
logger.warning(f"Collection Warning: {parent} {methods_in[int_method]} attribute {data_in[methods_in[int_method]]} invalid must an integer >= {minimum} using {default_in} as default")
return default_in
if method_name == "filters":
validate = True
if "validate" in dict_data:
if dict_data["validate"] is None:
raise Failed("Collection Error: validate filter attribute is blank")
if not isinstance(dict_data["validate"], bool):
raise Failed("Collection Error: validate filter attribute must be either true or false")
validate = dict_data["validate"]
for filter_method, filter_data in dict_data.items():
filter_attr, modifier, filter_final = self._split(filter_method)
if filter_final not in all_filters:
raise Failed(f"Collection Error: {filter_final} is not a valid filter attribute")
elif filter_final in movie_only_filters and self.library.is_show:
raise Failed(f"Collection Error: {filter_final} filter attribute only works for movie libraries")
elif filter_final in show_only_filters and self.library.is_movie:
raise Failed(f"Collection Error: {filter_final} filter attribute only works for show libraries")
elif filter_final is None:
raise Failed(f"Collection Error: {filter_final} filter attribute is blank")
else:
self.filters.append((filter_final, self.validate_attribute(filter_attr, modifier, f"{filter_final} filter", filter_data, validate)))
elif method_name == "plex_collectionless":
new_dictionary = {}
dict_methods = {dm.lower(): dm for dm in dict_data}
prefix_list = []
if "exclude_prefix" in dict_methods and dict_data[dict_methods["exclude_prefix"]]:
if isinstance(dict_data[dict_methods["exclude_prefix"]], list):
prefix_list.extend([exclude for exclude in dict_data[dict_methods["exclude_prefix"]] if exclude])
else:
prefix_list.append(str(dict_data[dict_methods["exclude_prefix"]]))
exact_list = []
if "exclude" in dict_methods and dict_data[dict_methods["exclude"]]:
if isinstance(dict_data[dict_methods["exclude"]], list):
exact_list.extend([exclude for exclude in dict_data[dict_methods["exclude"]] if exclude])
else:
exact_list.append(str(dict_data[dict_methods["exclude"]]))
if len(prefix_list) == 0 and len(exact_list) == 0:
raise Failed("Collection Error: you must have at least one exclusion")
exact_list.append(self.name)
new_dictionary["exclude_prefix"] = prefix_list
new_dictionary["exclude"] = exact_list
self.methods.append((method_name, new_dictionary))
elif method_name == "plex_search":
self.methods.append((method_name, self.build_filter("plex_search", dict_data)))
elif method_name == "tmdb_discover":
new_dictionary = {"limit": 100}
for discover_name, discover_data in dict_data.items():
discover_final = discover_name.lower()
if discover_data:
if (self.library.is_movie and discover_final in tmdb.discover_movie) or (self.library.is_show and discover_final in tmdb.discover_tv):
if discover_final == "language":
if re.compile("([a-z]{2})-([A-Z]{2})").match(str(discover_data)):
new_dictionary[discover_final] = str(discover_data)
else:
raise Failed(f"Collection Error: {method_name} attribute {discover_final}: {discover_data} must match pattern ([a-z]{{2}})-([A-Z]{{2}}) e.g. en-US")
elif discover_final == "region":
if re.compile("^[A-Z]{2}$").match(str(discover_data)):
new_dictionary[discover_final] = str(discover_data)
else:
raise Failed(f"Collection Error: {method_name} attribute {discover_final}: {discover_data} must match pattern ^[A-Z]{{2}}$ e.g. US")
elif discover_final == "sort_by":
if (self.library.is_movie and discover_data in tmdb.discover_movie_sort) or (self.library.is_show and discover_data in tmdb.discover_tv_sort):
new_dictionary[discover_final] = discover_data
else:
raise Failed(f"Collection Error: {method_name} attribute {discover_final}: {discover_data} is invalid")
elif discover_final == "certification_country":
if "certification" in dict_data or "certification.lte" in dict_data or "certification.gte" in dict_data:
new_dictionary[discover_final] = discover_data
else:
raise Failed(f"Collection Error: {method_name} attribute {discover_final}: must be used with either certification, certification.lte, or certification.gte")
elif discover_final in ["certification", "certification.lte", "certification.gte"]:
if "certification_country" in dict_data:
new_dictionary[discover_final] = discover_data
else:
raise Failed(f"Collection Error: {method_name} attribute {discover_final}: must be used with certification_country")
elif discover_final in ["include_adult", "include_null_first_air_dates", "screened_theatrically"]:
if discover_data is True:
new_dictionary[discover_final] = discover_data
elif discover_final in tmdb.discover_dates:
new_dictionary[discover_final] = util.validate_date(discover_data, f"{method_name} attribute {discover_final}", return_as="%m/%d/%Y")
elif discover_final in ["primary_release_year", "year", "first_air_date_year"]:
new_dictionary[discover_final] = util.check_number(discover_data, f"{method_name} attribute {discover_final}", minimum=1800, maximum=self.current_year + 1)
elif discover_final in ["vote_count.gte", "vote_count.lte", "vote_average.gte", "vote_average.lte", "with_runtime.gte", "with_runtime.lte"]:
new_dictionary[discover_final] = util.check_number(discover_data, f"{method_name} attribute {discover_final}", minimum=1)
elif discover_final in ["with_cast", "with_crew", "with_people", "with_companies", "with_networks", "with_genres", "without_genres", "with_keywords", "without_keywords", "with_original_language", "timezone"]:
new_dictionary[discover_final] = discover_data
else:
raise Failed(f"Collection Error: {method_name} attribute {discover_final} not supported")
elif discover_final == "limit":
if isinstance(discover_data, int) and discover_data > 0:
new_dictionary[discover_final] = discover_data
else:
raise Failed(f"Collection Error: {method_name} attribute {discover_final}: must be a valid number greater then 0")
else:
raise Failed(f"Collection Error: {method_name} attribute {discover_final} not supported")
else:
raise Failed(f"Collection Error: {method_name} parameter {discover_final} is blank")
if len(new_dictionary) > 1:
self.methods.append((method_name, new_dictionary))
else:
raise Failed(f"Collection Error: {method_name} had no valid fields")
elif "tautulli" in method_name:
new_dictionary = {}
if method_name == "tautulli_popular":
new_dictionary["list_type"] = "popular"
elif method_name == "tautulli_watched":
new_dictionary["list_type"] = "watched"
else:
raise Failed(f"Collection Error: {method_name} attribute not supported")
dict_methods = {dm.lower(): dm for dm in dict_data}
new_dictionary["list_days"] = get_int(method_name, "list_days", dict_data, dict_methods, 30)
new_dictionary["list_size"] = get_int(method_name, "list_size", dict_data, dict_methods, 10)
new_dictionary["list_buffer"] = get_int(method_name, "list_buffer", dict_data, dict_methods, 20)
self.methods.append((method_name, new_dictionary))
elif method_name == "mal_season":
new_dictionary = {"sort_by": "anime_num_list_users"}
dict_methods = {dm.lower(): dm for dm in dict_data}
if "sort_by" not in dict_methods:
logger.warning("Collection Warning: mal_season sort_by attribute not found using members as default")
elif not dict_data[dict_methods["sort_by"]]:
logger.warning("Collection Warning: mal_season sort_by attribute is blank using members as default")
elif dict_data[dict_methods["sort_by"]] not in mal.season_sort:
logger.warning(f"Collection Warning: mal_season sort_by attribute {dict_data[dict_methods['sort_by']]} invalid must be either 'members' or 'score' using members as default")
else:
new_dictionary["sort_by"] = mal.season_sort[dict_data[dict_methods["sort_by"]]]
if self.current_time.month in [1, 2, 3]: new_dictionary["season"] = "winter"
elif self.current_time.month in [4, 5, 6]: new_dictionary["season"] = "spring"
elif self.current_time.month in [7, 8, 9]: new_dictionary["season"] = "summer"
elif self.current_time.month in [10, 11, 12]: new_dictionary["season"] = "fall"
if "season" not in dict_methods:
logger.warning(f"Collection Warning: mal_season season attribute not found using the current season: {new_dictionary['season']} as default")
elif not dict_data[dict_methods["season"]]:
logger.warning(f"Collection Warning: mal_season season attribute is blank using the current season: {new_dictionary['season']} as default")
elif dict_data[dict_methods["season"]] not in util.pretty_seasons:
logger.warning(f"Collection Warning: mal_season season attribute {dict_data[dict_methods['season']]} invalid must be either 'winter', 'spring', 'summer' or 'fall' using the current season: {new_dictionary['season']} as default")
else:
new_dictionary["season"] = dict_data[dict_methods["season"]]
new_dictionary["year"] = get_int(method_name, "year", dict_data, dict_methods, self.current_time.year, minimum=1917, maximum=self.current_time.year + 1)
new_dictionary["limit"] = get_int(method_name, "limit", dict_data, dict_methods, 100, maximum=500)
self.methods.append((method_name, new_dictionary))
elif method_name == "mal_userlist":
new_dictionary = {"status": "all", "sort_by": "list_score"}
dict_methods = {dm.lower(): dm for dm in dict_data}
if "username" not in dict_methods:
raise Failed("Collection Error: mal_userlist username attribute is required")
elif not dict_data[dict_methods["username"]]:
raise Failed("Collection Error: mal_userlist username attribute is blank")
else:
new_dictionary["username"] = dict_data[dict_methods["username"]]
if "status" not in dict_methods:
logger.warning("Collection Warning: mal_season status attribute not found using all as default")
elif not dict_data[dict_methods["status"]]:
logger.warning("Collection Warning: mal_season status attribute is blank using all as default")
elif dict_data[dict_methods["status"]] not in mal.userlist_status:
logger.warning(f"Collection Warning: mal_season status attribute {dict_data[dict_methods['status']]} invalid must be either 'all', 'watching', 'completed', 'on_hold', 'dropped' or 'plan_to_watch' using all as default")
else:
new_dictionary["status"] = mal.userlist_status[dict_data[dict_methods["status"]]]
if "sort_by" not in dict_methods:
logger.warning("Collection Warning: mal_season sort_by attribute not found using score as default")
elif not dict_data[dict_methods["sort_by"]]:
logger.warning("Collection Warning: mal_season sort_by attribute is blank using score as default")
elif dict_data[dict_methods["sort_by"]] not in mal.userlist_sort:
logger.warning(f"Collection Warning: mal_season sort_by attribute {dict_data[dict_methods['sort_by']]} invalid must be either 'score', 'last_updated', 'title' or 'start_date' using score as default")
else:
new_dictionary["sort_by"] = mal.userlist_sort[dict_data[dict_methods["sort_by"]]]
new_dictionary["limit"] = get_int(method_name, "limit", dict_data, dict_methods, 100, maximum=1000)
self.methods.append((method_name, new_dictionary))
else:
raise Failed(f"Collection Error: {method_name} attribute is not a dictionary: {dict_data}")
elif method_name in numbered_builders:
list_count = util.regex_first_int(method_data, "List Size", default=10)
if list_count < 1:
logger.warning(f"Collection Warning: {method_name} must be an integer greater then 0 defaulting to 10")
list_count = 10
self.methods.append((method_name, list_count))
elif "tvdb" in method_name:
values = util.get_list(method_data)
if method_name[-8:] == "_details":
if method_name == "tvdb_movie_details":
item = self.config.TVDb.get_movie(self.language, values[0])
if hasattr(item, "description") and item.description:
self.summaries[method_name] = item.description
if hasattr(item, "background_path") and item.background_path:
self.backgrounds[method_name] = f"{self.config.TMDb.image_url}{item.background_path}"
if hasattr(item, "poster_path") and item.poster_path:
self.posters[method_name] = f"{self.config.TMDb.image_url}{item.poster_path}"
elif method_name == "tvdb_show_details":
item = self.config.TVDb.get_series(self.language, values[0])
if hasattr(item, "description") and item.description:
self.summaries[method_name] = item.description
if hasattr(item, "background_path") and item.background_path:
self.backgrounds[method_name] = f"{self.config.TMDb.image_url}{item.background_path}"
if hasattr(item, "poster_path") and item.poster_path:
self.posters[method_name] = f"{self.config.TMDb.image_url}{item.poster_path}"
elif method_name == "tvdb_list_details":
self.summaries[method_name] = self.config.TVDb.get_list_description(values[0], self.language)
for value in values:
self.methods.append((method_name[:-8] if method_name[-8:] == "_details" else method_name, value))
elif method_name in tmdb.builders:
values = self.config.TMDb.validate_tmdb_ids(method_data, method_name)
if method_name[-8:] == "_details":
if method_name in ["tmdb_collection_details", "tmdb_movie_details", "tmdb_show_details"]:
item = self.config.TMDb.get_movie_show_or_collection(values[0], self.library.is_movie)
if hasattr(item, "overview") and item.overview:
self.summaries[method_name] = item.overview
if hasattr(item, "backdrop_path") and item.backdrop_path:
self.backgrounds[method_name] = f"{self.config.TMDb.image_url}{item.backdrop_path}"
if hasattr(item, "poster_path") and item.poster_path:
self.posters[method_name] = f"{self.config.TMDb.image_url}{item.poster_path}"
elif method_name in ["tmdb_actor_details", "tmdb_crew_details", "tmdb_director_details", "tmdb_producer_details", "tmdb_writer_details"]:
item = self.config.TMDb.get_person(values[0])
if hasattr(item, "biography") and item.biography:
self.summaries[method_name] = item.biography
if hasattr(item, "profile_path") and item.profile_path:
self.posters[method_name] = f"{self.config.TMDb.image_url}{item.profile_path}"
else:
item = self.config.TMDb.get_list(values[0])
if hasattr(item, "description") and item.description:
self.summaries[method_name] = item.description
for value in values:
self.methods.append((method_name[:-8] if method_name[-8:] == "_details" else method_name, value))
elif method_name in all_builders:
for value in util.get_list(method_data):
self.methods.append((method_name, value))
elif method_name not in ignored_details:
raise Failed(f"Collection Error: {method_name} attribute not supported")
elif method_key.lower() in all_builders or method_key.lower() in method_alias or method_key.lower() in plex.searches:
raise Failed(f"Collection Error: {method_key} attribute is blank")
else:
logger.warning(f"Collection Warning: {method_key} attribute is blank")
if self.add_to_radarr is None: if self.add_to_radarr is None:
self.add_to_radarr = self.library.Radarr.add if self.library.Radarr else False self.add_to_radarr = self.library.Radarr.add if self.library.Radarr else False
@ -942,7 +643,7 @@ class CollectionBuilder:
url_slug = self.config.TMDb.get_person(util.regex_first_int(method_data, 'TMDb Person ID')).profile_path url_slug = self.config.TMDb.get_person(util.regex_first_int(method_data, 'TMDb Person ID')).profile_path
self.posters[method_name] = f"{self.config.TMDb.image_url}{url_slug}" self.posters[method_name] = f"{self.config.TMDb.image_url}{url_slug}"
elif method_name == "tvdb_poster": elif method_name == "tvdb_poster":
self.posters[method_name] = f"{self.config.TVDb.get_movie_or_series(method_data, self.language, self.library.is_movie).poster_path}" self.posters[method_name] = f"{self.config.TVDb.get_item(method_data, self.language, self.library.is_movie).poster_path}"
elif method_name == "file_poster": elif method_name == "file_poster":
if os.path.exists(method_data): if os.path.exists(method_data):
self.posters[method_name] = os.path.abspath(method_data) self.posters[method_name] = os.path.abspath(method_data)
@ -956,7 +657,7 @@ class CollectionBuilder:
url_slug = self.config.TMDb.get_movie_show_or_collection(util.regex_first_int(method_data, 'TMDb ID'), self.library.is_movie).poster_path url_slug = self.config.TMDb.get_movie_show_or_collection(util.regex_first_int(method_data, 'TMDb ID'), self.library.is_movie).poster_path
self.backgrounds[method_name] = f"{self.config.TMDb.image_url}{url_slug}" self.backgrounds[method_name] = f"{self.config.TMDb.image_url}{url_slug}"
elif method_name == "tvdb_background": elif method_name == "tvdb_background":
self.posters[method_name] = f"{self.config.TVDb.get_movie_or_series(method_data, self.language, self.library.is_movie).background_path}" self.posters[method_name] = f"{self.config.TVDb.get_item(method_data, self.language, self.library.is_movie).background_path}"
elif method_name == "file_background": elif method_name == "file_background":
if os.path.exists(method_data): if os.path.exists(method_data):
self.backgrounds[method_name] = os.path.abspath(method_data) self.backgrounds[method_name] = os.path.abspath(method_data)
@ -1070,7 +771,7 @@ class CollectionBuilder:
def _anidb(self, method_name, method_data): def _anidb(self, method_name, method_data):
if method_name == "anidb_popular": if method_name == "anidb_popular":
self.methods.append((method_name, util.parse_int(method_name, method_data, 30, maximum=30))) self.methods.append((method_name, util.parse_int(method_name, method_data, default=30, maximum=30)))
elif method_name in ["anidb_id", "anidb_relation"]: elif method_name in ["anidb_id", "anidb_relation"]:
for anidb_id in self.config.AniDB.validate_anidb_ids(method_data, self.language): for anidb_id in self.config.AniDB.validate_anidb_ids(method_data, self.language):
self.methods.append((method_name, anidb_id)) self.methods.append((method_name, anidb_id))
@ -1091,7 +792,7 @@ class CollectionBuilder:
for anilist_id in self.config.AniList.validate_anilist_ids(method_data, studio=method_name == "anilist_studio"): for anilist_id in self.config.AniList.validate_anilist_ids(method_data, studio=method_name == "anilist_studio"):
self.methods.append((method_name, anilist_id)) self.methods.append((method_name, anilist_id))
elif method_name in ["anilist_popular", "anilist_top_rated"]: elif method_name in ["anilist_popular", "anilist_top_rated"]:
self.methods.append((method_name, util.parse_int(method_name, method_data, 10))) self.methods.append((method_name, util.parse_int(method_name, method_data)))
elif method_name in ["anilist_season", "anilist_genre", "anilist_tag"]: elif method_name in ["anilist_season", "anilist_genre", "anilist_tag"]:
for dict_data, dict_methods in util.validate_dict_list(method_name, method_data): for dict_data, dict_methods in util.validate_dict_list(method_name, method_data):
new_dictionary = {} new_dictionary = {}
@ -1118,6 +819,215 @@ class CollectionBuilder:
if method_name.endswith("_details"): if method_name.endswith("_details"):
self.summaries[method_name] = self.config.ICheckMovies.get_list_description(icheckmovies_lists[0], self.language) self.summaries[method_name] = self.config.ICheckMovies.get_list_description(icheckmovies_lists[0], self.language)
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"):
self.methods.append((method_name, value))
else:
raise Failed(f"Collection Error: imdb_id {value} must begin with tt")
elif method_name == "imdb_list":
for imdb_dict in self.config.IMDb.validate_imdb_lists(method_data, self.language):
self.methods.append((method_name, imdb_dict))
def _letterboxd(self, method_name, method_data):
if method_name.startswith("letterboxd_list"):
letterboxd_lists = self.config.Letterboxd.validate_letterboxd_lists(method_data, self.language)
for letterboxd_list in letterboxd_lists:
self.methods.append(("letterboxd_list", letterboxd_list))
if method_name.endswith("_details"):
self.summaries[method_name] = self.config.Letterboxd.get_list_description(letterboxd_lists[0], self.language)
def _mal(self, method_name, method_data):
if method_name == "mal_id":
for mal_id in util.get_int_list(method_data, "MyAnimeList ID"):
self.methods.append((method_name, mal_id))
elif method_name in ["mal_all", "mal_airing", "mal_upcoming", "mal_tv", "mal_ova", "mal_movie", "mal_special", "mal_popular", "mal_favorite", "mal_suggested"]:
self.methods.append((method_name, util.parse_int(method_name, method_data)))
elif method_name in ["mal_season", "mal_userlist"]:
for dict_data, dict_methods in util.validate_dict_list(method_name, method_data):
new_dictionary = {}
if method_name == "mal_season":
if self.current_time.month in [1, 2, 3]: new_dictionary["season"] = "winter"
elif self.current_time.month in [4, 5, 6]: new_dictionary["season"] = "spring"
elif self.current_time.month in [7, 8, 9]: new_dictionary["season"] = "summer"
elif self.current_time.month in [10, 11, 12]: new_dictionary["season"] = "fall"
new_dictionary["season"] = util.parse_from_dict(method_name, "season", dict_data, dict_methods, default=new_dictionary["season"], options=["winter", "spring", "summer", "fall"])
new_dictionary["sort_by"] = util.parse_from_dict(method_name, "sort_by", dict_data, dict_methods, default="members", options=mal.season_sort_options, translation=mal.season_sort_translation)
new_dictionary["year"] = util.parse_int_from_dict(method_name, "year", dict_data, dict_methods, self.current_time.year, minimum=1917, maximum=self.current_time.year + 1)
new_dictionary["limit"] = util.parse_int_from_dict(method_name, "limit", dict_data, dict_methods, 100, maximum=500)
elif method_name == "mal_userlist":
new_dictionary["username"] = util.parse_from_dict(method_name, "username", dict_data, dict_methods)
new_dictionary["status"] = util.parse_from_dict(method_name, "status", dict_data, dict_methods, default="all", options=mal.userlist_status)
new_dictionary["sort_by"] = util.parse_from_dict(method_name, "sort_by", dict_data, dict_methods, default="score", options=mal.userlist_sort_options, translation=mal.userlist_sort_translation)
new_dictionary["limit"] = util.parse_int_from_dict(method_name, "limit", dict_data, dict_methods, 100, maximum=1000)
self.methods.append((method_name, new_dictionary))
def _plex(self, method_name, method_data):
if method_name == "plex_all":
self.methods.append((method_name, True))
elif method_name in ["plex_search", "plex_collectionless"]:
for dict_data, dict_methods in util.validate_dict_list(method_name, method_data):
new_dictionary = {}
if method_name == "plex_search":
new_dictionary = self.build_filter("plex_search", dict_data)
elif method_name == "plex_collectionless":
prefix_list = util.parse_list("exclude_prefix", dict_data, dict_methods)
exact_list = util.parse_list("exclude", dict_data, dict_methods)
if len(prefix_list) == 0 and len(exact_list) == 0:
raise Failed("Collection Error: you must have at least one exclusion")
exact_list.append(self.name)
new_dictionary["exclude_prefix"] = prefix_list
new_dictionary["exclude"] = exact_list
self.methods.append((method_name, new_dictionary))
else:
self.methods.append(("plex_search", self.build_filter("plex_search", {"any": {method_name: method_data}})))
def _tautulli(self, method_name, method_data):
for dict_data, dict_methods in util.validate_dict_list(method_name, method_data):
self.methods.append((method_name, {
"list_type": "popular" if method_name == "tautulli_popular" else "watched",
"list_days": util.parse_int_from_dict(method_name, "list_days", dict_data, dict_methods, 30),
"list_size": util.parse_int_from_dict(method_name, "list_size", dict_data, dict_methods, 10),
"list_buffer": util.parse_int_from_dict(method_name, "list_buffer", dict_data, dict_methods, 20)
}))
def _tmdb(self, method_name, method_data):
if method_name == "tmdb_discover":
for dict_data, dict_methods in util.validate_dict_list(method_name, method_data):
new_dictionary = {"limit": 100}
for discover_name, discover_data in dict_data.items():
discover_final = discover_name.lower()
if discover_data:
if (self.library.is_movie and discover_final in tmdb.discover_movie) or (self.library.is_show and discover_final in tmdb.discover_tv):
if discover_final == "language":
if re.compile("([a-z]{2})-([A-Z]{2})").match(str(discover_data)):
new_dictionary[discover_final] = str(discover_data)
else:
raise Failed(f"Collection Error: {method_name} attribute {discover_final}: {discover_data} must match pattern ([a-z]{{2}})-([A-Z]{{2}}) e.g. en-US")
elif discover_final == "region":
if re.compile("^[A-Z]{2}$").match(str(discover_data)):
new_dictionary[discover_final] = str(discover_data)
else:
raise Failed(f"Collection Error: {method_name} attribute {discover_final}: {discover_data} must match pattern ^[A-Z]{{2}}$ e.g. US")
elif discover_final == "sort_by":
if (self.library.is_movie and discover_data in tmdb.discover_movie_sort) or (self.library.is_show and discover_data in tmdb.discover_tv_sort):
new_dictionary[discover_final] = discover_data
else:
raise Failed(f"Collection Error: {method_name} attribute {discover_final}: {discover_data} is invalid")
elif discover_final == "certification_country":
if "certification" in dict_data or "certification.lte" in dict_data or "certification.gte" in dict_data:
new_dictionary[discover_final] = discover_data
else:
raise Failed(f"Collection Error: {method_name} attribute {discover_final}: must be used with either certification, certification.lte, or certification.gte")
elif discover_final in ["certification", "certification.lte", "certification.gte"]:
if "certification_country" in dict_data:
new_dictionary[discover_final] = discover_data
else:
raise Failed(f"Collection Error: {method_name} attribute {discover_final}: must be used with certification_country")
elif discover_final in ["include_adult", "include_null_first_air_dates", "screened_theatrically"]:
if discover_data is True:
new_dictionary[discover_final] = discover_data
elif discover_final in tmdb.discover_dates:
new_dictionary[discover_final] = util.validate_date(discover_data, f"{method_name} attribute {discover_final}", return_as="%m/%d/%Y")
elif discover_final in ["primary_release_year", "year", "first_air_date_year"]:
new_dictionary[discover_final] = util.check_number(discover_data, f"{method_name} attribute {discover_final}", minimum=1800, maximum=self.current_year + 1)
elif discover_final in ["vote_count.gte", "vote_count.lte", "vote_average.gte", "vote_average.lte", "with_runtime.gte", "with_runtime.lte"]:
new_dictionary[discover_final] = util.check_number(discover_data, f"{method_name} attribute {discover_final}", minimum=1)
elif discover_final in ["with_cast", "with_crew", "with_people", "with_companies", "with_networks", "with_genres", "without_genres", "with_keywords", "without_keywords", "with_original_language", "timezone"]:
new_dictionary[discover_final] = discover_data
else:
raise Failed(f"Collection Error: {method_name} attribute {discover_final} not supported")
elif discover_final == "limit":
if isinstance(discover_data, int) and discover_data > 0:
new_dictionary[discover_final] = discover_data
else:
raise Failed(f"Collection Error: {method_name} attribute {discover_final}: must be a valid number greater then 0")
else:
raise Failed(f"Collection Error: {method_name} attribute {discover_final} not supported")
else:
raise Failed(f"Collection Error: {method_name} parameter {discover_final} is blank")
if len(new_dictionary) > 1:
self.methods.append((method_name, new_dictionary))
else:
raise Failed(f"Collection Error: {method_name} had no valid fields")
elif method_name in ["tmdb_popular", "tmdb_top_rated", "tmdb_now_playing", "tmdb_trending_daily", "tmdb_trending_weekly"]:
self.methods.append((method_name, util.parse_int(method_name, method_data)))
else:
values = self.config.TMDb.validate_tmdb_ids(method_data, method_name)
if method_name.endswith("_details"):
if method_name.beginswith(("tmdb_collection", "tmdb_movie", "tmdb_show")):
item = self.config.TMDb.get_movie_show_or_collection(values[0], self.library.is_movie)
if hasattr(item, "overview") and item.overview:
self.summaries[method_name] = item.overview
if hasattr(item, "backdrop_path") and item.backdrop_path:
self.backgrounds[method_name] = f"{self.config.TMDb.image_url}{item.backdrop_path}"
if hasattr(item, "poster_path") and item.poster_path:
self.posters[method_name] = f"{self.config.TMDb.image_url}{item.poster_path}"
elif method_name.beginswith(("tmdb_actor", "tmdb_crew", "tmdb_director", "tmdb_producer", "tmdb_writer")):
item = self.config.TMDb.get_person(values[0])
if hasattr(item, "biography") and item.biography:
self.summaries[method_name] = item.biography
if hasattr(item, "profile_path") and item.profile_path:
self.posters[method_name] = f"{self.config.TMDb.image_url}{item.profile_path}"
elif method_name.beginswith("tmdb_list"):
item = self.config.TMDb.get_list(values[0])
if hasattr(item, "description") and item.description:
self.summaries[method_name] = item.description
for value in values:
self.methods.append((method_name[:-8] if method_name.endswith("_details") else method_name, value))
def _trakt(self, method_name, method_data):
if method_name.startswith("trakt_list"):
trakt_lists = self.config.Trakt.validate_trakt(method_data, self.library.is_movie)
for trakt_list in trakt_lists:
self.methods.append(("trakt_list", trakt_list))
if method_name.endswith("_details"):
self.summaries[method_name] = self.config.Trakt.list_description(trakt_lists[0])
elif method_name in ["trakt_trending", "trakt_popular", "trakt_recommended", "trakt_watched", "trakt_collected"]:
self.methods.append((method_name, util.parse_int(method_name, method_data)))
elif method_name in ["trakt_watchlist", "trakt_collection"]:
for trakt_list in self.config.Trakt.validate_trakt(method_data, self.library.is_movie, trakt_type=method_name[6:]):
self.methods.append((method_name, trakt_list))
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")):
item = self.config.TVDb.get_item(self.language, values[0], method_name.startswith("tvdb_movie"))
if hasattr(item, "description") and item.description:
self.summaries[method_name] = item.description
if hasattr(item, "background_path") and item.background_path:
self.backgrounds[method_name] = f"{self.config.TMDb.image_url}{item.background_path}"
if hasattr(item, "poster_path") and item.poster_path:
self.posters[method_name] = f"{self.config.TMDb.image_url}{item.poster_path}"
elif method_name.startswith("tvdb_list"):
self.summaries[method_name] = self.config.TVDb.get_list_description(values[0], self.language)
for value in values:
self.methods.append((method_name[:-8] if method_name.endswith("_details") else method_name, value))
def _filters(self, method_name, method_data):
for dict_data, dict_methods in util.validate_dict_list(method_name, method_data):
validate = True
if "validate" in dict_data:
if dict_data["validate"] is None:
raise Failed("Collection Error: validate filter attribute is blank")
if not isinstance(dict_data["validate"], bool):
raise Failed("Collection Error: validate filter attribute must be either true or false")
validate = dict_data["validate"]
for filter_method, filter_data in dict_data.items():
filter_attr, modifier, filter_final = self._split(filter_method)
if filter_final not in all_filters:
raise Failed(f"Collection Error: {filter_final} is not a valid filter attribute")
elif filter_final in movie_only_filters and self.library.is_show:
raise Failed(f"Collection Error: {filter_final} filter attribute only works for movie libraries")
elif filter_final in show_only_filters and self.library.is_movie:
raise Failed(f"Collection Error: {filter_final} filter attribute only works for show libraries")
elif filter_final is None:
raise Failed(f"Collection Error: {filter_final} filter attribute is blank")
else:
self.filters.append((filter_final, self.validate_attribute(filter_attr, modifier, f"{filter_final} filter", filter_data, validate)))
def collect_rating_keys(self): def collect_rating_keys(self):
filtered_keys = {} filtered_keys = {}
name = self.obj.title if self.obj else self.name name = self.obj.title if self.obj else self.name

@ -31,12 +31,13 @@ mal_ranked_name = {
"mal_popular": "bypopularity", "mal_popular": "bypopularity",
"mal_favorite": "favorite" "mal_favorite": "favorite"
} }
season_sort = { season_sort_translation = {
"anime_score": "anime_score", "anime_score": "anime_score",
"anime_num_list_users": "anime_num_list_users", "anime_num_list_users": "anime_num_list_users",
"score": "anime_score", "score": "anime_score",
"members": "anime_num_list_users" "members": "anime_num_list_users"
} }
season_sort_options = ["score", "members"]
pretty_names = { pretty_names = {
"anime_score": "Score", "anime_score": "Score",
"anime_num_list_users": "Members", "anime_num_list_users": "Members",
@ -51,7 +52,7 @@ pretty_names = {
"dropped": "Dropped", "dropped": "Dropped",
"plan_to_watch": "Plan to Watch" "plan_to_watch": "Plan to Watch"
} }
userlist_sort = { userlist_sort_translation = {
"score": "list_score", "score": "list_score",
"list_score": "list_score", "list_score": "list_score",
"last_updated": "list_updated_at", "last_updated": "list_updated_at",
@ -62,6 +63,7 @@ userlist_sort = {
"start_date": "anime_start_date", "start_date": "anime_start_date",
"anime_start_date": "anime_start_date" "anime_start_date": "anime_start_date"
} }
userlist_sort_options = ["score", "last_updated", "title", "start_date"]
userlist_status = [ userlist_status = [
"all", "all",
"watching", "watching",

@ -87,7 +87,7 @@ class TVDb:
def __init__(self, config): def __init__(self, config):
self.config = config self.config = config
def get_movie_or_series(self, language, tvdb_url, is_movie): def get_item(self, language, tvdb_url, is_movie):
return self.get_movie(language, tvdb_url) if is_movie else self.get_series(language, tvdb_url) return self.get_movie(language, tvdb_url) if is_movie else self.get_series(language, tvdb_url)
def get_series(self, language, tvdb_url): def get_series(self, language, tvdb_url):

@ -458,21 +458,24 @@ def parse_int(method, data, default=10, minimum=1, maximum=None):
return list_count return list_count
return default return default
def parse_from_dict(parent, method, data, methods, default=None, options=None): def parse_from_dict(parent, method, data, methods, default=None, options=None, translation=None):
message = "" message = ""
if options is None and translation is not None:
options = [o for o in translation]
if method not in methods: if method not in methods:
message = f"Collection Warning: {parent} {method} attribute not found" message = f"{parent} {method} attribute not found"
elif data[methods[method]] is None: elif data[methods[method]] is None:
message = f"Collection Warning: {parent} {method} attribute is blank" message = f"{parent} {method} attribute is blank"
elif options is not None and str(data[methods[method]]).lower() not in options: elif (translation is not None and str(data[methods[method]]).lower() not in translation) or \
message = f"Collection Warning: {parent} {method} attribute {data[methods[method]]} must be in {options}" (options is not None and translation is None and str(data[methods[method]]).lower() not in options):
message = f"{parent} {method} attribute {data[methods[method]]} must be in {options}"
else: else:
return data[methods[method]] return translation[data[methods[method]]] if translation is not None else data[methods[method]]
if default is None: if default is None:
raise Failed(message) raise Failed(f"Collection Error: {message}")
else: else:
logger.warning(f"{message} using {default} as default") logger.warning(f"Collection Warning: {message} using {default} as default")
return default return translation[default] if translation is not None else default
def parse_int_from_dict(parent, method, data, methods, default, minimum=1, maximum=None): def parse_int_from_dict(parent, method, data, methods, default, minimum=1, maximum=None):
if method not in methods: if method not in methods:
@ -486,3 +489,8 @@ def parse_int_from_dict(parent, method, data, methods, default, minimum=1, maxim
else: else:
return data[methods[method]] return data[methods[method]]
return default return default
def parse_list(method, data, methods):
if method in methods and data[methods[method]]:
return [i for i in data[methods[method]] if i] if isinstance(data[methods[method]], list) else [str(data[methods[method]])]
return []
Loading…
Cancel
Save