From 29cc04f6c47c186feb4846d16e1fd62a6118d0f4 Mon Sep 17 00:00:00 2001 From: meisnate12 Date: Sun, 26 Dec 2021 10:48:52 -0500 Subject: [PATCH] version 1.14.0 --- VERSION | 2 +- modules/builder.py | 93 ++++++++++++++++++++++++-------------------- modules/config.py | 14 ++++--- modules/library.py | 2 +- plex_meta_manager.py | 14 +++---- 5 files changed, 68 insertions(+), 57 deletions(-) diff --git a/VERSION b/VERSION index 9e1df7d0..cd99d386 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.13.3-develop1222 \ No newline at end of file +1.14.0 \ No newline at end of file diff --git a/modules/builder.py b/modules/builder.py index 8aa2e0f0..86b48e3d 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -42,7 +42,8 @@ method_alias = { "minimum_tag_percentage": "min_tag_percent", "minimumtagrank": "min_tag_percent", "minimum_tag_rank": "min_tag_percent", "anilist_tag": "anilist_search", "anilist_genre": "anilist_search", "anilist_season": "anilist_search", "mal_producer": "mal_studio", "mal_licensor": "mal_studio", - "trakt_recommended": "trakt_recommended_weekly", "trakt_watched": "trakt_watched_weekly", "trakt_collected": "trakt_collected_weekly" + "trakt_recommended": "trakt_recommended_weekly", "trakt_watched": "trakt_watched_weekly", "trakt_collected": "trakt_collected_weekly", + "collection_changes_webhooks": "changes_webhooks" } filter_translation = { "actor": "actors", @@ -85,10 +86,11 @@ boolean_details = [ scheduled_boolean = ["visible_library", "visible_home", "visible_shared"] string_details = ["sort_title", "content_rating", "name_mapping"] ignored_details = [ - "smart_filter", "smart_label", "smart_url", "run_again", "schedule", "sync_mode", "template", "test", "delete_not_scheduled", - "tmdb_person", "build_collection", "collection_order", "collection_level", "validate_builders", "collection_name", "sort_by", "libraries", "sync_to_users" + "smart_filter", "smart_label", "smart_url", "run_again", "schedule", "sync_mode", "template", "test", + "delete_not_scheduled", "tmdb_person", "build_collection", "collection_order", "collection_level", + "validate_builders", "sort_by", "libraries", "sync_to_users", "collection_name", "playlist_name", "name" ] -details = ["ignore_ids", "ignore_imdb_ids", "server_preroll", "collection_changes_webhooks", "collection_mode", +details = ["ignore_ids", "ignore_imdb_ids", "server_preroll", "changes_webhooks", "collection_mode", "collection_minimum", "label"] + boolean_details + scheduled_boolean + string_details collectionless_details = ["collection_order", "plex_collectionless", "label", "label_sync_mode", "test"] + \ poster_details + background_details + summary_details + string_details @@ -101,7 +103,7 @@ sonarr_details = [ "sonarr_quality", "sonarr_season", "sonarr_search", "sonarr_cutoff_search", "sonarr_tag" ] parts_collection_valid = [ - "plex_search", "trakt_list", "trakt_list_details", "collection_mode", "label", "visible_library", "collection_changes_webhooks" + "plex_search", "trakt_list", "trakt_list_details", "collection_mode", "label", "visible_library", "changes_webhooks" "visible_home", "visible_shared", "show_missing", "save_missing", "missing_only_released", "server_preroll", "item_lock_background", "item_lock_poster", "item_lock_title", "item_refresh", "imdb_list" ] + summary_details + poster_details + background_details + string_details @@ -168,13 +170,13 @@ custom_sort_builders = [ "mal_popular", "mal_favorite", "mal_suggested", "mal_userlist", "mal_season", "mal_genre", "mal_studio" ] playlist_attributes = [ - "playlist_name", "filters", "name_mapping", "show_filtered", "show_missing", "save_missing", + "filters", "name_mapping", "show_filtered", "show_missing", "save_missing", "missing_only_released", "only_filter_missing", "delete_below_minimum", "ignore_ids", "ignore_imdb_ids", - "server_preroll", "collection_changes_webhooks", "collection_minimum" -] + custom_sort_builders + summary_details + poster_details + background_details + radarr_details + sonarr_details + "server_preroll", "changes_webhooks", "collection_minimum", +] + custom_sort_builders + summary_details + poster_details + radarr_details + sonarr_details class CollectionBuilder: - def __init__(self, config, library, metadata, name, no_missing, data, playlist=False): + def __init__(self, config, library, metadata, name, no_missing, data, playlist=False, valid_users=None): self.config = config self.library = library self.metadata = metadata @@ -182,6 +184,7 @@ class CollectionBuilder: self.no_missing = no_missing self.data = data self.playlist = playlist + self.valid_users = valid_users self.language = self.library.Plex.language self.details = { "show_filtered": self.library.show_filtered, @@ -194,7 +197,7 @@ class CollectionBuilder: "create_asset_folders": self.library.create_asset_folders, "delete_below_minimum": self.library.delete_below_minimum, "delete_not_scheduled": self.library.delete_not_scheduled, - "collection_changes_webhooks": self.library.collection_changes_webhooks + "changes_webhooks": self.library.changes_webhooks } self.item_details = {} self.radarr_details = {} @@ -232,13 +235,21 @@ class CollectionBuilder: methods = {m.lower(): m for m in self.data} - if f"{self.type}_name" in methods: + if "name" in methods: + name = self.data[methods["name"]] + elif f"{self.type}_name" in methods: + logger.warning(f"Config Warning: Running {self.type}_name as name") + name = self.data[methods[f"{self.type}_name"]] + else: + name = None + + if name: logger.debug("") - logger.debug(f"Validating Method: {self.type}_name") - if not self.data[methods[f"{self.type}_name"]]: - raise Failed(f"{self.Type} Error: {self.type}_name attribute is blank") - logger.debug(f"Value: {self.data[methods['{}_name'.format(self.type)]]}") - self.name = self.data[methods[f"{self.type}_name"]] + logger.debug("Validating Method: name") + if not name: + raise Failed(f"{self.Type} Error: name attribute is blank") + logger.debug(f"Value: {name}") + self.name = name else: self.name = self.mapping_name @@ -269,13 +280,13 @@ class CollectionBuilder: suffix = "" if self.details["delete_not_scheduled"]: try: - self.obj = self.library.get_collection(self.name) - self.delete_collection() + self.obj = self.library.get_playlist(self.name) if self.playlist else self.library.get_collection(self.name) + util.print_multiline(self.delete()) self.deleted = True suffix = f" and was deleted" except Failed: suffix = f" and could not be found to delete" - raise NotScheduled(f"{e}\n\nCollection {self.name} not scheduled to run{suffix}") + raise NotScheduled(f"{e}\n\n{self.Type} {self.name} not scheduled to run{suffix}") self.collectionless = "plex_collectionless" in methods and not self.playlist @@ -674,7 +685,7 @@ class CollectionBuilder: self.details["label.sync"] = util.get_list(method_data) if method_data else [] else: self.details[method_final] = util.get_list(method_data) if method_data else [] - elif method_name == "collection_changes_webhooks": + elif method_name == "changes_webhooks": self.details[method_name] = self._parse(method_name, method_data, datatype="list") elif method_name in scheduled_boolean: if isinstance(method_data, bool): @@ -1570,7 +1581,7 @@ class CollectionBuilder: else: self.library.alter_collection(item, name, smart_label_collection=self.smart_label_collection) amount_added += 1 - if self.details["collection_changes_webhooks"]: + if self.details["changes_webhooks"]: if item.ratingKey in self.library.movie_rating_key_map: add_id = self.library.movie_rating_key_map[item.ratingKey] elif item.ratingKey in self.library.show_rating_key_map: @@ -1605,7 +1616,7 @@ class CollectionBuilder: else: self.library.alter_collection(item, self.name, smart_label_collection=self.smart_label_collection, add=False) amount_removed += 1 - if self.details["collection_changes_webhooks"]: + if self.details["changes_webhooks"]: if item.ratingKey in self.library.movie_rating_key_map: remove_id = self.library.movie_rating_key_map[item.ratingKey] elif item.ratingKey in self.library.show_rating_key_map: @@ -1950,10 +1961,6 @@ class CollectionBuilder: os.remove(og_image) self.config.Cache.update_image_map(item.ratingKey, self.library.image_table_name, "", "") - def delete_collection(self): - if self.obj: - self.library.query(self.obj.delete) - def load_collection(self): if not self.obj and self.smart_url: self.library.create_smart_collection(self.name, self.smart_type_key, self.smart_url) @@ -2141,25 +2148,27 @@ class CollectionBuilder: user_playlist = user_server.playlist(title) user_playlist.delete() - def delete_playlist(self, users): + def delete(self): + output = "" if self.obj: self.library.query(self.obj.delete) - logger.info("") - logger.info(f"Playlist {self.obj.title} deleted") - if users: - for user in users: - try: - self.delete_user_playlist(self.obj.title, user) - logger.info(f"Playlist {self.obj.title} deleted on User {user}") - except NotFound: - logger.error(f"Playlist {self.obj.title} not found on User {user}") - - def sync_playlist(self, users): - if self.obj and users: + output = f"{self.Type} {self.obj.title} deleted" + if self.playlist: + if self.valid_users: + for user in self.valid_users: + try: + self.delete_user_playlist(self.obj.title, user) + output += f"\nPlaylist {self.obj.title} deleted on User {user}" + except NotFound: + output += f"\nPlaylist {self.obj.title} not found on User {user}" + return output + + def sync_playlist(self): + if self.obj and self.valid_users: logger.info("") util.separator(f"Syncing Playlist to Users", space=False, border=False) logger.info("") - for user in users: + for user in self.valid_users: try: self.delete_user_playlist(self.obj.title, user) except NotFound: @@ -2168,12 +2177,12 @@ class CollectionBuilder: logger.info(f"Playlist: {self.name} synced to {user}") def send_notifications(self, playlist=False): - if self.obj and self.details["collection_changes_webhooks"] and \ + if self.obj and self.details["changes_webhooks"] and \ (self.created or len(self.notification_additions) > 0 or len(self.notification_removals) > 0): self.obj.reload() try: self.library.Webhooks.collection_hooks( - self.details["collection_changes_webhooks"], + self.details["changes_webhooks"], 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, diff --git a/modules/config.py b/modules/config.py index a30c5e3c..5951b037 100644 --- a/modules/config.py +++ b/modules/config.py @@ -33,7 +33,7 @@ sync_modes = {"append": "Only Add Items to the Collection or Playlist", "sync": mass_update_options = {"tmdb": "Use TMDb Metadata", "omdb": "Use IMDb Metadata through OMDb"} class ConfigFile: - def __init__(self, default_dir, attrs, read_only=False): + def __init__(self, default_dir, attrs, read_only): logger.info("Locating config...") config_file = attrs["config_file"] if config_file and os.path.exists(config_file): self.config_path = os.path.abspath(config_file) @@ -94,13 +94,14 @@ class ConfigFile: hooks("collection_creation") hooks("collection_addition") hooks("collection_removal") - new_config["libraries"][library]["webhooks"]["collection_changes"] = changes if changes else None + hooks("collection_changes") + new_config["libraries"][library]["webhooks"]["changes"] = changes if changes else None if "libraries" in new_config: new_config["libraries"] = new_config.pop("libraries") if "playlists" in new_config: new_config["playlists"] = new_config.pop("playlists") if "settings" in new_config: new_config["settings"] = new_config.pop("settings") if "webhooks" in new_config: temp = new_config.pop("webhooks") - if "collection_changes" not in temp: + if "changes" not in temp: changes = [] def hooks(attr): if attr in temp: @@ -110,7 +111,8 @@ class ConfigFile: hooks("collection_creation") hooks("collection_addition") hooks("collection_removal") - temp["collection_changes"] = changes if changes else None + hooks("collection_changes") + temp["changes"] = changes if changes else None new_config["webhooks"] = temp if "plex" in new_config: new_config["plex"] = new_config.pop("plex") if "tmdb" in new_config: new_config["tmdb"] = new_config.pop("tmdb") @@ -252,7 +254,7 @@ class ConfigFile: "error": check_for_attribute(self.data, "error", parent="webhooks", var_type="list", default_is_none=True), "run_start": check_for_attribute(self.data, "run_start", parent="webhooks", var_type="list", default_is_none=True), "run_end": check_for_attribute(self.data, "run_end", parent="webhooks", var_type="list", default_is_none=True), - "collection_changes": check_for_attribute(self.data, "collection_changes", parent="webhooks", var_type="list", default_is_none=True) + "changes": check_for_attribute(self.data, "changes", parent="webhooks", var_type="list", default_is_none=True) } if self.general["cache"]: util.separator() @@ -534,7 +536,7 @@ class ConfigFile: params["ignore_imdb_ids"] = check_for_attribute(lib, "ignore_imdb_ids", parent="settings", var_type="list", default_is_none=True, do_print=False, save=False) params["ignore_imdb_ids"].extend([i for i in self.general["ignore_imdb_ids"] if i not in params["ignore_imdb_ids"]]) params["error_webhooks"] = check_for_attribute(lib, "error", parent="webhooks", var_type="list", default=self.webhooks["error"], do_print=False, save=False, default_is_none=True) - params["collection_changes_webhooks"] = check_for_attribute(lib, "collection_changes", parent="webhooks", var_type="list", default=self.webhooks["collection_changes"], do_print=False, save=False, default_is_none=True) + params["changes_webhooks"] = check_for_attribute(lib, "changes", parent="webhooks", var_type="list", default=self.webhooks["changes"], do_print=False, save=False, default_is_none=True) params["assets_for_all"] = check_for_attribute(lib, "assets_for_all", parent="settings", var_type="bool", default=self.general["assets_for_all"], do_print=False, save=False) params["mass_genre_update"] = check_for_attribute(lib, "mass_genre_update", test_list=mass_update_options, default_is_none=True, save=False, do_print=False) params["mass_audience_rating_update"] = check_for_attribute(lib, "mass_audience_rating_update", test_list=mass_update_options, default_is_none=True, save=False, do_print=False) diff --git a/modules/library.py b/modules/library.py index c5b172d3..d68f2069 100644 --- a/modules/library.py +++ b/modules/library.py @@ -74,7 +74,7 @@ class Library(ABC): self.tmdb_collections = params["tmdb_collections"] self.genre_mapper = params["genre_mapper"] self.error_webhooks = params["error_webhooks"] - self.collection_changes_webhooks = params["collection_changes_webhooks"] + self.changes_webhooks = params["changes_webhooks"] self.split_duplicates = params["split_duplicates"] # TODO: Here or just in Plex? self.clean_bundles = params["plex"]["clean_bundles"] # TODO: Here or just in Plex? self.empty_trash = params["plex"]["empty_trash"] # TODO: Here or just in Plex? diff --git a/plex_meta_manager.py b/plex_meta_manager.py index f7fa27c8..993c2ea8 100644 --- a/plex_meta_manager.py +++ b/plex_meta_manager.py @@ -34,7 +34,7 @@ parser.add_argument("-rc", "-cl", "--collection", "--collections", "--run-collec parser.add_argument("-rl", "-l", "--library", "--libraries", "--run-library", "--run-libraries", dest="libraries", help="Process only specified libraries (comma-separated list)", type=str) parser.add_argument("-nc", "--no-countdown", dest="no_countdown", help="Run without displaying the countdown", action="store_true", default=False) parser.add_argument("-nm", "--no-missing", dest="no_missing", help="Run without running the missing section", action="store_true", default=False) -parser.add_argument("-ro", "--read-only-config", dest="read_only_config", help="Config must be read only", action="store_true", default=False) +parser.add_argument("-ro", "--read-only-config", dest="read_only_config", help="Run without writing to the config", action="store_true", default=False) parser.add_argument("-d", "--divider", dest="divider", help="Character that divides the sections (Default: '=')", default="=", type=str) parser.add_argument("-w", "--width", dest="width", help="Screen Width (Default: 100)", default=100, type=int) args = parser.parse_args() @@ -721,10 +721,9 @@ def run_collection(config, library, metadata, requested_collections): logger.info(f"Collection Minimum: {builder.minimum} not met for {mapping_name} Collection") valid = False if builder.details["delete_below_minimum"] and builder.obj: - builder.delete_collection() - builder.deleted = True logger.info("") - logger.info(f"Collection {builder.obj.title} deleted") + util.print_multiline(builder.delete(), info=True) + builder.deleted = True if builder.do_missing and (len(builder.missing_movies) > 0 or len(builder.missing_shows) > 0): if builder.details["show_missing"] is True: @@ -875,7 +874,7 @@ def run_playlists(config): util.separator(f"Validating {mapping_name} Attributes", space=False, border=False) builder = CollectionBuilder(config, pl_libraries[0], playlist_file, mapping_name, no_missing, - playlist_attrs, playlist=True) + playlist_attrs, playlist=True, valid_users=valid_users) logger.info("") util.separator(f"Running {mapping_name} Playlist", space=False, border=False) @@ -1043,7 +1042,8 @@ def run_playlists(config): logger.info(f"Playlist Minimum: {builder.minimum} not met for {mapping_name} Playlist") valid = False if builder.details["delete_below_minimum"] and builder.obj: - builder.delete_playlist(valid_users) + logger.info("") + util.print_multiline(builder.delete(), info=True) builder.deleted = True if builder.do_missing and (len(builder.missing_movies) > 0 or len(builder.missing_shows) > 0): @@ -1086,7 +1086,7 @@ def run_playlists(config): builder.sort_collection() if valid: - builder.sync_playlist(valid_users) + builder.sync_playlist() builder.send_notifications(playlist=True)