diff --git a/config/config.yml.template b/config/config.yml.template index 8c97e60e..89284dac 100644 --- a/config/config.yml.template +++ b/config/config.yml.template @@ -34,9 +34,12 @@ settings: # Can be individually specified missing_only_released: false collection_minimum: 1 delete_below_minimum: true - notifiarr_collection_creation: false - notifiarr_collection_addition: false - notifiarr_collection_removing: false + error_webhooks: + run_start_webhooks: + run_end_webhooks: + collection_creation_webhooks: + collection_addition_webhooks: + collection_removing_webhooks: tvdb_language: eng plex: # Can be individually specified per library as well url: http://192.168.1.12:32400 @@ -78,7 +81,6 @@ omdb: apikey: ######## notifiarr: apikey: #################################### - error_notification: true trakt: client_id: ################################################################ client_secret: ################################################################ diff --git a/modules/builder.py b/modules/builder.py index 998b2181..4c58cfc0 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -78,15 +78,16 @@ summary_details = [ poster_details = ["url_poster", "tmdb_poster", "tmdb_profile", "tvdb_poster", "file_poster"] background_details = ["url_background", "tmdb_background", "tvdb_background", "file_background"] boolean_details = [ - "visible_library", "visible_home", "visible_shared", "show_filtered", "show_missing", "save_missing", "missing_only_released", - "delete_below_minimum", "notifiarr_collection_creation", "notifiarr_collection_addition", "notifiarr_collection_removing" + "visible_library", "visible_home", "visible_shared", "show_filtered", "show_missing", "save_missing", + "missing_only_released", "delete_below_minimum" ] string_details = ["sort_title", "content_rating", "name_mapping"] ignored_details = [ "smart_filter", "smart_label", "smart_url", "run_again", "schedule", "sync_mode", "template", "test", "tmdb_person", "build_collection", "collection_order", "collection_level", "validate_builders", "collection_name" ] -details = ["collection_mode", "collection_order", "collection_level", "collection_minimum", "label"] + boolean_details + string_details +notification_details = ["collection_creation_webhooks", "collection_addition_webhooks", "collection_removing_webhooks"] +details = ["collection_mode", "collection_order", "collection_level", "collection_minimum", "label"] + boolean_details + string_details + notification_details collectionless_details = ["collection_order", "plex_collectionless", "label", "label_sync_mode", "test"] + \ poster_details + background_details + summary_details + string_details item_details = ["item_label", "item_radarr_tag", "item_sonarr_tag", "item_overlay", "item_assets", "revert_overlay", "item_refresh"] + list(plex.item_advance_keys.keys()) @@ -176,9 +177,9 @@ class CollectionBuilder: "missing_only_released": self.library.missing_only_released, "create_asset_folders": self.library.create_asset_folders, "delete_below_minimum": self.library.delete_below_minimum, - "notifiarr_collection_creation": self.library.notifiarr_collection_creation, - "notifiarr_collection_addition": self.library.notifiarr_collection_addition, - "notifiarr_collection_removing": self.library.notifiarr_collection_removing, + "collection_creation_webhooks": self.library.collection_creation_webhooks, + "collection_addition_webhooks": self.library.collection_addition_webhooks, + "collection_removing_webhooks": self.library.collection_removing_webhooks, } self.item_details = {} self.radarr_details = {} @@ -193,8 +194,8 @@ class CollectionBuilder: self.filtered_keys = {} self.run_again_movies = [] self.run_again_shows = [] - self.notifiarr_additions = [] - self.notifiarr_removals = [] + self.notification_additions = [] + self.notification_removals = [] self.items = [] self.posters = {} self.backgrounds = {} @@ -551,7 +552,6 @@ class CollectionBuilder: elif not self.library.Sonarr and "sonarr" in method_name: raise Failed(f"Collection Error: {method_final} requires Sonarr to be configured") elif not self.library.Tautulli and "tautulli" in method_name: raise Failed(f"Collection Error: {method_final} requires Tautulli to be configured") elif not self.config.MyAnimeList and "mal" in method_name: raise Failed(f"Collection Error: {method_final} requires MyAnimeList to be configured") - elif not self.library.Notifiarr and "notifiarr" in method_name: raise Failed(f"Collection Error: {method_final} requires Notifiarr to be configured") 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") 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 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") @@ -709,6 +709,8 @@ class CollectionBuilder: self.details["label.sync"] = util.get_list(method_data) else: self.details[method_final] = util.get_list(method_data) + elif method_name in notification_details: + self.details[method_name] = util.parse(method_name, method_data, datatype="list") elif method_name in boolean_details: default = self.details[method_name] if method_name in self.details else None self.details[method_name] = util.parse(method_name, method_data, datatype="bool", default=default) @@ -1485,6 +1487,7 @@ class CollectionBuilder: def add_to_collection(self): name, collection_items = self.library.get_collection_name_and_items(self.obj if self.obj else self.name, self.smart_label_collection) total = len(self.rating_keys) + amount_added = 0 for i, item in enumerate(self.rating_keys, 1): try: current = self.fetch_item(item) @@ -1497,41 +1500,44 @@ class CollectionBuilder: self.plex_map[current.ratingKey] = None else: self.library.alter_collection(current, name, smart_label_collection=self.smart_label_collection) - if self.details["notifiarr_collection_addition"]: + amount_added += 1 + if self.details["collection_addition_webhooks"]: 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.notifiarr_additions.append({"title": current.title, "id": add_id}) + self.notification_additions.append({"title": current.title, "id": add_id}) util.print_end() logger.info("") logger.info(f"{total} {self.collection_level.capitalize()}{'s' if total > 1 else ''} Processed") + return amount_added def sync_collection(self): - count_removed = 0 + amount_removed = 0 for ratingKey, item in self.plex_map.items(): if item is not None: - if count_removed == 0: + if amount_removed == 0: logger.info("") util.separator(f"Removed from {self.name} Collection", space=False, border=False) logger.info("") self.library.reload(item) logger.info(f"{self.name} Collection | - | {self.item_title(item)}") self.library.alter_collection(item, self.name, smart_label_collection=self.smart_label_collection, add=False) - if self.details["notifiarr_collection_removing"]: + if self.details["collection_removing_webhooks"]: if self.library.is_movie and item.ratingKey in self.library.movie_rating_key_map: remove_id = self.library.movie_rating_key_map[item.ratingKey] elif self.library.is_show and item.ratingKey in self.library.show_rating_key_map: remove_id = self.library.show_rating_key_map[item.ratingKey] else: remove_id = None - self.notifiarr_removals.append({"title": item.title, "id": remove_id}) - count_removed += 1 - if count_removed > 0: + self.notification_removals.append({"title": item.title, "id": remove_id}) + amount_removed += 1 + if amount_removed > 0: logger.info("") - logger.info(f"{count_removed} {self.collection_level.capitalize()}{'s' if count_removed == 1 else ''} Removed") + logger.info(f"{amount_removed} {self.collection_level.capitalize()}{'s' if amount_removed == 1 else ''} Removed") + return amount_removed def check_tmdb_filter(self, item_id, is_movie, item=None, check_released=False): if self.tmdb_filters or check_released: @@ -1653,6 +1659,8 @@ class CollectionBuilder: return True def run_missing(self): + added_to_radarr = 0 + added_to_sonarr = 0 if len(self.missing_movies) > 0: missing_movies_with_names = [] for missing_id in self.missing_movies: @@ -1679,7 +1687,7 @@ class CollectionBuilder: if self.library.Radarr: if self.radarr_details["add"]: try: - self.library.Radarr.add_tmdb(missing_tmdb_ids, **self.radarr_details) + added_to_radarr += self.library.Radarr.add_tmdb(missing_tmdb_ids, **self.radarr_details) except Failed as e: logger.error(e) if "item_radarr_tag" in self.item_details: @@ -1714,7 +1722,7 @@ class CollectionBuilder: if self.library.Sonarr: if self.sonarr_details["add"]: try: - self.library.Sonarr.add_tvdb(missing_tvdb_ids, **self.sonarr_details) + added_to_sonarr += self.library.Sonarr.add_tvdb(missing_tvdb_ids, **self.sonarr_details) except Failed as e: logger.error(e) if "item_sonarr_tag" in self.item_details: @@ -1727,6 +1735,7 @@ class CollectionBuilder: if len(self.missing_parts) > 0 and self.library.is_show and self.details["save_missing"] is True: for missing in self.missing_parts: logger.info(f"{self.name} Collection | X | {missing}") + return added_to_radarr, added_to_sonarr def item_title(self, item): if self.collection_level == "season": @@ -2034,16 +2043,17 @@ class CollectionBuilder: def send_notifications(self): if self.obj and ( - (self.details["notifiarr_collection_creation"] and self.created) or - (self.details["notifiarr_collection_addition"] and len(self.notifiarr_additions) > 0) or - (self.details["notifiarr_collection_removing"] and len(self.notifiarr_removals) > 0) + (self.details["collection_creation_webhooks"] and self.created) or + (self.details["collection_addition_webhooks"] and len(self.notification_additions) > 0) or + (self.details["collection_removing_webhooks"] and len(self.notification_removals) > 0) ): self.obj.reload() - self.library.Notifiarr.plex_collection( + self.library.Webhooks.collection_hooks( + self.details["collection_creation_webhooks"] + self.details["collection_addition_webhooks"] + self.details["collection_removing_webhooks"], self.obj, created=self.created, - additions=self.notifiarr_additions, - removals=self.notifiarr_removals + additions=self.notification_additions, + removals=self.notification_removals ) def run_collections_again(self): @@ -2051,7 +2061,7 @@ class CollectionBuilder: name, collection_items = self.library.get_collection_name_and_items(self.obj, self.smart_label_collection) self.created = False rating_keys = [] - self.notifiarr_additions = [] + self.notification_additions = [] for mm in self.run_again_movies: if mm in self.library.movie_map: rating_keys.extend(self.library.movie_map[mm]) @@ -2077,7 +2087,7 @@ class CollectionBuilder: add_id = self.library.show_rating_key_map[current.ratingKey] else: add_id = None - self.notifiarr_additions.append({"title": current.title, "id": add_id}) + self.notification_additions.append({"title": current.title, "id": add_id}) self.send_notifications() logger.info(f"{len(rating_keys)} {self.collection_level.capitalize()}{'s' if len(rating_keys) > 1 else ''} Processed") diff --git a/modules/config.py b/modules/config.py index 30076691..9b29f1e6 100644 --- a/modules/config.py +++ b/modules/config.py @@ -10,7 +10,7 @@ from modules.icheckmovies import ICheckMovies from modules.imdb import IMDb from modules.letterboxd import Letterboxd from modules.mal import MyAnimeList -from modules.notifiarr import NotifiarrFactory +from modules.notifiarr import Notifiarr from modules.omdb import OMDb from modules.plex import Plex from modules.radarr import Radarr @@ -21,6 +21,7 @@ from modules.tmdb import TMDb from modules.trakt import Trakt from modules.tvdb import TVDb from modules.util import Failed +from modules.webhooks import Webhooks from retrying import retry from ruamel import yaml @@ -135,9 +136,9 @@ class Config: elif var_type == "path": if os.path.exists(os.path.abspath(data[attribute])): return data[attribute] else: message = f"Path {os.path.abspath(data[attribute])} does not exist" - elif var_type == "list": return util.get_list(data[attribute]) + elif var_type == "list": return util.get_list(data[attribute], split=False) elif var_type == "list_path": - temp_list = [p for p in util.get_list(data[attribute], split=True) if os.path.exists(os.path.abspath(p))] + temp_list = [p for p in util.get_list(data[attribute], split=False) if os.path.exists(os.path.abspath(p))] if len(temp_list) > 0: return temp_list else: message = "No Paths exist" elif var_type == "lower_list": return util.get_list(data[attribute], lower=True) @@ -190,9 +191,12 @@ class Config: "create_asset_folders": check_for_attribute(self.data, "create_asset_folders", parent="settings", var_type="bool", default=False), "collection_minimum": check_for_attribute(self.data, "collection_minimum", parent="settings", var_type="int", default=1), "delete_below_minimum": check_for_attribute(self.data, "delete_below_minimum", parent="settings", var_type="bool", default=False), - "notifiarr_collection_creation": check_for_attribute(self.data, "notifiarr_collection_creation", parent="settings", var_type="bool", default=False), - "notifiarr_collection_addition": check_for_attribute(self.data, "notifiarr_collection_addition", parent="settings", var_type="bool", default=False), - "notifiarr_collection_removing": check_for_attribute(self.data, "notifiarr_collection_removing", parent="settings", var_type="bool", default=False), + "error_webhooks": check_for_attribute(self.data, "error_webhooks", parent="settings", var_type="list", default_is_none=True), + "run_start_webhooks": check_for_attribute(self.data, "run_start_webhooks", parent="settings", var_type="list", default_is_none=True), + "run_end_webhooks": check_for_attribute(self.data, "run_end_webhooks", parent="settings", var_type="list", default_is_none=True), + "collection_creation_webhooks": check_for_attribute(self.data, "collection_creation_webhooks", parent="settings", var_type="list", default_is_none=True), + "collection_addition_webhooks": check_for_attribute(self.data, "collection_addition_webhooks", parent="settings", var_type="list", default_is_none=True), + "collection_removing_webhooks": check_for_attribute(self.data, "collection_removing_webhooks", parent="settings", var_type="list", default_is_none=True), "tvdb_language": check_for_attribute(self.data, "tvdb_language", parent="settings", default="default") } if self.general["cache"]: @@ -207,9 +211,8 @@ class Config: if "notifiarr" in self.data: logger.info("Connecting to Notifiarr...") try: - self.NotifiarrFactory = NotifiarrFactory(self, { + self.NotifiarrFactory = Notifiarr(self, { "apikey": check_for_attribute(self.data, "apikey", parent="notifiarr", throw=True), - "error_notification": check_for_attribute(self.data, "error_notification", parent="notifiarr", var_type="bool", default=True), "develop": check_for_attribute(self.data, "develop", parent="notifiarr", var_type="bool", default=False, do_print=False, save=False), "test": check_for_attribute(self.data, "test", parent="notifiarr", var_type="bool", default=False, do_print=False, save=False) }) @@ -219,6 +222,9 @@ class Config: else: logger.warning("notifiarr attribute not found") + self.Webhooks = Webhooks(self, self.general, notifiarr=self.NotifiarrFactory) + self.Webhooks.start_time_hooks(self.run_start_time) + self.errors = [] util.separator() @@ -389,9 +395,10 @@ class Config: params["create_asset_folders"] = check_for_attribute(lib, "create_asset_folders", parent="settings", var_type="bool", default=self.general["create_asset_folders"], do_print=False, save=False) params["collection_minimum"] = check_for_attribute(lib, "collection_minimum", parent="settings", var_type="int", default=self.general["collection_minimum"], do_print=False, save=False) params["delete_below_minimum"] = check_for_attribute(lib, "delete_below_minimum", parent="settings", var_type="bool", default=self.general["delete_below_minimum"], do_print=False, save=False) - params["notifiarr_collection_creation"] = check_for_attribute(lib, "notifiarr_collection_creation", parent="settings", var_type="bool", default=self.general["notifiarr_collection_creation"], do_print=False, save=False) - params["notifiarr_collection_addition"] = check_for_attribute(lib, "notifiarr_collection_addition", parent="settings", var_type="bool", default=self.general["notifiarr_collection_addition"], do_print=False, save=False) - params["notifiarr_collection_removing"] = check_for_attribute(lib, "notifiarr_collection_removing", parent="settings", var_type="bool", default=self.general["notifiarr_collection_removing"], do_print=False, save=False) + params["error_webhooks"] = check_for_attribute(lib, "error_webhooks", parent="settings", var_type="list", default=self.general["error_webhooks"], do_print=False, save=False) + params["collection_creation_webhooks"] = check_for_attribute(lib, "collection_creation_webhooks", parent="settings", var_type="list", default=self.general["collection_creation_webhooks"], do_print=False, save=False) + params["collection_addition_webhooks"] = check_for_attribute(lib, "collection_addition_webhooks", parent="settings", var_type="list", default=self.general["collection_addition_webhooks"], do_print=False, save=False) + params["collection_removing_webhooks"] = check_for_attribute(lib, "collection_removing_webhooks", parent="settings", var_type="list", default=self.general["collection_removing_webhooks"], do_print=False, save=False) params["assets_for_all"] = check_for_attribute(lib, "assets_for_all", var_type="bool", default=assets_for_all, save=False, do_print=lib and "assets_for_all" in lib) params["delete_unmanaged_collections"] = check_for_attribute(lib, "delete_unmanaged_collections", var_type="bool", default=False, save=False, do_print=lib and "delete_unmanaged_collections" in lib) @@ -545,7 +552,7 @@ class Config: logger.info("") logger.info(f"{display_name} library's Tautulli Connection {'Failed' if library.Tautulli is None else 'Successful'}") - library.Notifiarr = self.NotifiarrFactory.getNotifiarr(library) if self.NotifiarrFactory else None + library.Webhooks = Webhooks(self, {"error_webhooks": library.error_webhooks}, library=library, notifiarr=self.NotifiarrFactory) logger.info("") self.libraries.append(library) @@ -566,11 +573,8 @@ class Config: raise def notify(self, text, library=None, collection=None, critical=True): - if self.NotifiarrFactory: - if not isinstance(text, list): - text = [text] - for t in text: - self.NotifiarrFactory.error(t, library=library, collection=collection, critical=critical) + for error in util.get_list(text, split=False): + self.Webhooks.error_hooks(error, library=library, collection=collection, critical=critical) def get_html(self, url, headers=None, params=None): return html.fromstring(self.get(url, headers=headers, params=params).content) diff --git a/modules/library.py b/modules/library.py index 4335a630..5c47ce71 100644 --- a/modules/library.py +++ b/modules/library.py @@ -13,6 +13,7 @@ class Library(ABC): self.Radarr = None self.Sonarr = None self.Tautulli = None + self.Webhooks = None self.Notifiarr = None self.collections = [] self.metadatas = [] @@ -57,9 +58,10 @@ class Library(ABC): self.sonarr_add_all = params["sonarr_add_all"] self.collection_minimum = params["collection_minimum"] self.delete_below_minimum = params["delete_below_minimum"] - self.notifiarr_collection_creation = params["notifiarr_collection_creation"] - self.notifiarr_collection_addition = params["notifiarr_collection_addition"] - self.notifiarr_collection_removing = params["notifiarr_collection_removing"] + self.error_webhooks = params["error_webhooks"] + self.collection_creation_webhooks = params["collection_creation_webhooks"] + self.collection_addition_webhooks = params["collection_addition_webhooks"] + self.collection_removing_webhooks = params["collection_removing_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? @@ -182,6 +184,9 @@ class Library(ABC): self.config.Cache.update_image_map(item.ratingKey, f"{self.image_table_name}_backgrounds", item.art, background.compare) def notify(self, text, collection=None, critical=True): + for error in util.get_list(text, split=False): + self.Webhooks.error_hooks(error, library=self, collection=collection, critical=critical) + self.config.notify(text, library=self, collection=collection, critical=critical) @abstractmethod diff --git a/modules/notifiarr.py b/modules/notifiarr.py index 6a203704..1c0e8e40 100644 --- a/modules/notifiarr.py +++ b/modules/notifiarr.py @@ -7,70 +7,25 @@ logger = logging.getLogger("Plex Meta Manager") base_url = "https://notifiarr.com/api/v1/" dev_url = "https://dev.notifiarr.com/api/v1/" -class NotifiarrBase: - def __init__(self, config, apikey, develop, test, error_notification): - self.config = config - self.apikey = apikey - self.develop = develop - self.test = test - self.error_notification = error_notification - def _request(self, path, json=None, params=None): - url = f"{dev_url if self.develop else base_url}" + \ - ("notification/test" if self.test else f"{path}{self.apikey}") - logger.debug(url.replace(self.apikey, "APIKEY")) - response = self.config.get(url, json=json, params={"event": "pmm"} if self.test else params) +class Notifiarr: + def __init__(self, config, params): + self.config = config + self.apikey = params["apikey"] + self.develop = params["develop"] + self.test = params["test"] + url, _ = self.get_url("user/validate/") + response = self.config.get(url) response_json = response.json() - if self.develop or self.test: - logger.debug(json) - logger.debug("") - logger.debug(response_json) if response.status_code >= 400 or ("result" in response_json and response_json["result"] == "error"): + logger.debug(f"Response: {response_json}") raise Failed(f"({response.status_code} [{response.reason}]) {response_json}") - return response_json - - def error(self, text, library=None, collection=None, critical=True): - if self.error_notification: - json = {"error": str(text), "critical": critical} - if library: - json["server_name"] = library.PlexServer.friendlyName - json["library_name"] = library.name - if collection: - json["collection"] = str(collection) - self._request("notification/plex/", json=json, params={"event": "collections"}) - -class NotifiarrFactory(NotifiarrBase): - def __init__(self, config, params): - super().__init__(config, params["apikey"], params["develop"], params["test"], params["error_notification"]) - if not params["test"] and not self._request("user/validate/")["details"]["response"]: + if not params["test"] and not response_json["details"]["response"]: raise Failed("Notifiarr Error: Invalid apikey") - def getNotifiarr(self, library): - return Notifiarr(self.config, library, self.apikey, self.develop, self.test, self.error_notification) - -class Notifiarr(NotifiarrBase): - def __init__(self, config, library, apikey, develop, test, error_notification): - super().__init__(config, apikey, develop, test, error_notification) - self.library = library + def get_url(self, path): + url = f"{dev_url if self.develop else base_url}{'notification/test' if self.test else f'{path}{self.apikey}'}" + logger.debug(url.replace(self.apikey, "APIKEY")) + params = {"event": "pmm" if self.test else "collections"} + return url, params - def plex_collection(self, collection, created=False, additions=None, removals=None): - thumb = None - if collection.thumb and next((f for f in collection.fields if f.name == "thumb"), None): - thumb = self.config.get_image_encoded(f"{self.library.url}{collection.thumb}?X-Plex-Token={self.library.token}") - art = None - if collection.art and next((f for f in collection.fields if f.name == "art"), None): - art = self.config.get_image_encoded(f"{self.library.url}{collection.art}?X-Plex-Token={self.library.token}") - json = { - "server_name": self.library.PlexServer.friendlyName, - "library_name": self.library.name, - "type": "movie" if self.library.is_movie else "show", - "collection": collection.title, - "created": created, - "poster": thumb, - "background": art - } - if additions: - json["additions"] = additions - if removals: - json["removals"] = removals - self._request("notification/plex/", json=json, params={"event": "collections"}) diff --git a/modules/radarr.py b/modules/radarr.py index 7f704d20..426eba0c 100644 --- a/modules/radarr.py +++ b/modules/radarr.py @@ -61,6 +61,8 @@ class Radarr: for tmdb_id in invalid: logger.info(f"Invalid TMDb ID | {tmdb_id}") + return len(added) + def edit_tags(self, tmdb_ids, tags, apply_tags): logger.info("") logger.info(f"{apply_tags_translation[apply_tags].capitalize()} Radarr Tags: {tags}") diff --git a/modules/sonarr.py b/modules/sonarr.py index 2386e3f8..20154440 100644 --- a/modules/sonarr.py +++ b/modules/sonarr.py @@ -87,6 +87,8 @@ class Sonarr: logger.info("") logger.info(f"Invalid TVDb ID | {tvdb_id}") + return len(added) + def edit_tags(self, tvdb_ids, tags, apply_tags): logger.info("") logger.info(f"{apply_tags_translation[apply_tags].capitalize()} Sonarr Tags: {tags}") diff --git a/modules/util.py b/modules/util.py index 477d3291..0cf0f73a 100644 --- a/modules/util.py +++ b/modules/util.py @@ -304,7 +304,7 @@ def parse(attribute, data, datatype=None, methods=None, parent=None, default=Non value = data[methods[attribute]] if methods and attribute in methods else data if datatype == "list": - if methods and attribute in methods and data[methods[attribute]]: + if value: return [v for v in value if v] if isinstance(value, list) else [str(value)] return [] elif datatype == "dictlist": diff --git a/modules/webhooks.py b/modules/webhooks.py new file mode 100644 index 00000000..3a79f334 --- /dev/null +++ b/modules/webhooks.py @@ -0,0 +1,83 @@ +import logging + +from modules.util import Failed + +logger = logging.getLogger("Plex Meta Manager") + +class Webhooks: + def __init__(self, config, system_webhooks, library=None, notifiarr=None): + self.config = config + self.error_webhooks = system_webhooks["error_webhooks"] if "error_webhooks" in system_webhooks else [] + self.run_start_webhooks = system_webhooks["run_start_webhooks"] if "run_start_webhooks" in system_webhooks else [] + self.run_end_webhooks = system_webhooks["run_end_webhooks"] if "run_end_webhooks" in system_webhooks else [] + self.library = library + self.notifiarr = notifiarr + + def _request(self, webhooks, json): + if self.config.trace_mode: + logger.debug("") + logger.debug(f"JSON: {json}") + for webhook in webhooks: + if self.config.trace_mode: + logger.debug(f"Webhook: {webhook}") + if webhook == "notifiarr": + url, params = self.notifiarr.get_url("notification/plex/") + response = self.config.get(url, json=json, params=params) + else: + response = self.config.post(webhook, json=json) + response_json = response.json() + if self.config.trace_mode: + logger.debug(f"Response: {response_json}") + if response.status_code >= 400 or ("result" in response_json and response_json["result"] == "error"): + raise Failed(f"({response.status_code} [{response.reason}]) {response_json}") + + def start_time_hooks(self, start_time): + if self.run_start_webhooks: + self._request(self.run_start_webhooks, {"start_time": start_time}) + + def end_time_hooks(self, start_time, run_time, stats): + if self.run_end_webhooks: + self._request(self.run_end_webhooks, { + "start_time": start_time, + "run_time": run_time, + "collections_created": stats["created"], + "collections_modified": stats["modified"], + "collections_deleted": stats["deleted"], + "items_added": stats["added"], + "items_removed": stats["removed"], + "added_to_radarr": stats["radarr"], + "added_to_sonarr": stats["sonarr"], + }) + + def error_hooks(self, text, library=None, collection=None, critical=True): + if self.error_webhooks: + json = {"error": str(text), "critical": critical} + if library: + json["server_name"] = library.PlexServer.friendlyName + json["library_name"] = library.name + if collection: + json["collection"] = str(collection) + self._request(self.error_webhooks, json) + + def collection_hooks(self, webhooks, collection, created=False, additions=None, removals=None): + if self.library: + thumb = None + if collection.thumb and next((f for f in collection.fields if f.name == "thumb"), None): + thumb = self.config.get_image_encoded(f"{self.library.url}{collection.thumb}?X-Plex-Token={self.library.token}") + art = None + if collection.art and next((f for f in collection.fields if f.name == "art"), None): + art = self.config.get_image_encoded(f"{self.library.url}{collection.art}?X-Plex-Token={self.library.token}") + json = { + "server_name": self.library.PlexServer.friendlyName, + "library_name": self.library.name, + "type": "movie" if self.library.is_movie else "show", + "collection": collection.title, + "created": created, + "poster": thumb, + "background": art + } + if additions: + json["additions"] = additions + if removals: + json["removals"] = removals + self._request(webhooks, json) diff --git a/plex_meta_manager.py b/plex_meta_manager.py index 837f69cb..38b1429d 100644 --- a/plex_meta_manager.py +++ b/plex_meta_manager.py @@ -63,6 +63,7 @@ times = get_arg("PMM_TIME", args.times) divider = get_arg("PMM_DIVIDER", args.divider) screen_width = get_arg("PMM_WIDTH", args.width) config_file = get_arg("PMM_CONFIG", args.config) +stats = {} util.separating_character = divider[0] @@ -133,6 +134,9 @@ def start(attrs): if "time" not in attrs: attrs["time"] = start_time.strftime("%H:%M") util.separator(f"Starting {start_type}Run") + config = None + global stats + stats = {"created": 0, "modified": 0, "deleted": 0, "added": 0, "removed": 0, "radarr": 0, "sonarr": 0} try: config = Config(default_dir, attrs) except Exception as e: @@ -146,10 +150,14 @@ def start(attrs): util.print_stacktrace() util.print_multiline(e, critical=True) logger.info("") - util.separator(f"Finished {start_type}Run\nRun Time: {str(datetime.now() - start_time).split('.')[0]}") + run_time = str(datetime.now() - start_time).split('.')[0] + if config: + config.Webhooks.end_time_hooks(start_time, run_time, stats) + util.separator(f"Finished {start_type}Run\nRun Time: {run_time}") logger.removeHandler(file_handler) def update_libraries(config): + global stats for library in config.libraries: try: os.makedirs(os.path.join(default_dir, "logs", library.mapping_name, "collections"), exist_ok=True) @@ -460,6 +468,7 @@ def mass_metadata(config, library, items=None): logger.error(e) def run_collection(config, library, metadata, requested_collections): + global stats logger.info("") for mapping_name, collection_attrs in requested_collections.items(): collection_start = datetime.now() @@ -520,6 +529,8 @@ def run_collection(config, library, metadata, requested_collections): logger.info("") util.print_multiline(builder.smart_filter_details, info=True) + items_added = 0 + items_removed = 0 if not builder.smart_url: logger.info("") logger.info(f"Sync Mode: {'sync' if builder.sync else 'append'}") @@ -535,14 +546,18 @@ def run_collection(config, library, metadata, requested_collections): logger.info("") util.separator(f"Adding to {mapping_name} Collection", space=False, border=False) logger.info("") - builder.add_to_collection() + items_added = builder.add_to_collection() + stats["added"] += items_added + items_removed = 0 if builder.sync: - builder.sync_collection() + items_removed = builder.sync_collection() + stats["removed"] += items_removed elif len(builder.rating_keys) < builder.minimum and builder.build_collection: logger.info("") logger.info(f"Collection Minimum: {builder.minimum} not met for {mapping_name} Collection") if builder.details["delete_below_minimum"] and builder.obj: builder.delete_collection() + stats["deleted"] += 1 logger.info("") logger.info(f"Collection {builder.obj.title} deleted") @@ -551,12 +566,18 @@ def run_collection(config, library, metadata, requested_collections): logger.info("") util.separator(f"Missing from Library", space=False, border=False) logger.info("") - builder.run_missing() + radarr_add, sonarr_add = builder.run_missing() + stats["radarr"] += radarr_add + stats["sonarr"] += sonarr_add run_item_details = True if builder.build_collection: try: builder.load_collection() + if builder.created: + stats["created"] += 1 + elif items_added > 0 or items_removed > 0: + stats["modified"] += 1 except Failed: util.print_stacktrace() run_item_details = False