from json import JSONDecodeError from modules import util from modules.util import Failed, YAML logger = util.logger class Webhooks: def __init__(self, config, system_webhooks, library=None, notifiarr=None, gotify=None): self.config = config self.error_webhooks = system_webhooks["error"] if "error" in system_webhooks else [] self.version_webhooks = system_webhooks["version"] if "version" in system_webhooks else [] self.run_start_webhooks = system_webhooks["run_start"] if "run_start" in system_webhooks else [] self.run_end_webhooks = system_webhooks["run_end"] if "run_end" in system_webhooks else [] self.delete_webhooks = system_webhooks["delete"] if "delete" in system_webhooks else [] self.library = library self.notifiarr = notifiarr self.gotify = gotify def _request(self, webhooks, json): logger.trace("") logger.separator("Webhooks", space=False, border=False, trace=True) logger.trace("") json["library_mapping_name"] = self.library.mapping_name if self.library else "" logger.trace(f"JSON: {json}") for webhook in list(set(webhooks)): response = None logger.trace(f"Webhook: {webhook}") if webhook == "notifiarr": if self.notifiarr: for x in range(6): response = self.notifiarr.notification(json) if response.status_code < 500: break elif webhook == "gotify": if self.gotify: self.gotify.notification(json) else: if webhook.startswith("https://discord.com/api/webhooks"): json = self.discord(json) elif webhook.startswith("https://hooks.slack.com/services"): json = self.slack(json) response = self.config.post(webhook, json=json) if response is not None: try: response_json = response.json() logger.trace(f"Response: {response_json}") if webhook == "notifiarr" and self.notifiarr and response.status_code == 400: def remove_from_config(text, hook_cat): if response_json["details"]["response"] == text: yaml = YAML(self.config.config_path) changed = False if hook_cat in yaml.data and yaml.data["webhooks"][hook_cat]: if isinstance(yaml.data["webhooks"][hook_cat], list) and "notifiarr" in yaml.data["webhooks"][hook_cat]: changed = True yaml.data["webhooks"][hook_cat].pop("notifiarr") elif yaml.data["webhooks"][hook_cat] == "notifiarr": changed = True yaml.data["webhooks"][hook_cat] = None if changed: yaml.save() remove_from_config("PMM updated trigger is not enabled", "changes") remove_from_config("PMM created trigger is not enabled", "changes") remove_from_config("PMM deleted trigger is not enabled", "changes") remove_from_config("PMM failure trigger is not enabled", "error") remove_from_config("PMM start/complete trigger is not enabled", "run_start") remove_from_config("PMM start/complete trigger is not enabled", "run_end") remove_from_config("PMM app updates trigger is not enabled", "version") if "result" in response_json and response_json["result"] == "error" and "details" in response_json and "response" in response_json["details"]: raise Failed(f"Notifiarr Error: {response_json['details']['response']}") 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}") except JSONDecodeError: if response.status_code >= 400: raise Failed(f"({response.status_code} [{response.reason}])") def start_time_hooks(self, start_time): if self.run_start_webhooks: self._request(self.run_start_webhooks, {"event": "run_start", "start_time": start_time.strftime("%Y-%m-%d %H:%M:%S")}) def version_hooks(self, version, latest_version): if self.version_webhooks: notes = None if version[1] != latest_version[1]: notes = self.config.GitHub.latest_release_notes() elif version[2] and version[2] < latest_version[2]: notes = self.config.GitHub.get_commits(version[2], nightly=self.config.branch == "nightly") self._request(self.version_webhooks, {"event": "version", "current": version[0], "latest": latest_version[0], "notes": notes}) def end_time_hooks(self, start_time, end_time, run_time, stats): if self.run_end_webhooks: self._request(self.run_end_webhooks, { "event": "run_end", "start_time": start_time.strftime("%Y-%m-%d %H:%M:%S"), "end_time": end_time.strftime("%Y-%m-%d %H:%M:%S"), "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"], "names": stats["names"] }) def error_hooks(self, text, server=None, library=None, collection=None, playlist=None, critical=True): if self.error_webhooks: json = {"event": "error", "error": str(text), "critical": critical} if server: json["server_name"] = str(server) if library: json["library_name"] = str(library) if collection: json["collection"] = str(collection) if playlist: json["playlist"] = str(playlist) self._request(self.error_webhooks, json) def delete_hooks(self, message, server=None, library=None): if self.delete_webhooks: json = {"event": "delete", "message": str(message)} if server: json["server_name"] = str(server) if library: json["library_name"] = str(library) self._request(self.delete_webhooks, json) def collection_hooks(self, webhooks, collection, poster_url=None, background_url=None, created=False, additions=None, removals=None, radarr=None, sonarr=None, playlist=False): if self.library: thumb = None if not poster_url and 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 not playlist and not background_url and 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}") self._request(webhooks, { "event": "changes", "server_name": self.library.PlexServer.friendlyName, "library_name": self.library.name, "playlist" if playlist else "collection": collection.title, "created": created, "poster": thumb, "background": art, "poster_url": poster_url, "background_url": background_url, "additions": additions if additions else [], "removals": removals if removals else [], "radarr_adds": radarr if radarr else [], "sonarr_adds": sonarr if sonarr else [], }) def slack(self, json): if json["event"] == "run_end": title = ":white_check_mark: Plex Meta Manager Has Finished a Run" rows = [ [("*Start Time*", json["start_time"]), ("*End Time*", json["end_time"]), ("*Run Time*", json["run_time"])], [], [ (":heavy_plus_sign: *Collections Created*", str(json["collections_created"])), (":infinity: *Collections Modified*", str(json["collections_modified"])), (":heavy_minus_sign: *Collections Deleted*", str(json["collections_deleted"])) ] ] if json["added_to_radarr"] or json["added_to_sonarr"]: rows.append([]) if json["added_to_radarr"]: rows.append([("*Added To Radarr*", json['added_to_radarr'])]) if json["added_to_sonarr"]: rows.append([("*Added To Sonarr*", json['added_to_sonarr'])]) elif json["event"] == "run_start": title = ":information_source: Plex Meta Manager Has Started!" rows = [[("*Start Time*", json["start_time"])]] elif json["event"] == "version": title = "Plex Meta Manager Has a New Version Available" rows = [ [("*Current Version*", json["current"]), ("*Latest Version*", json["latest"])], [(json["notes"], )] ] else: rows = [] row1 = [] text = "" if "server_name" in json: row1.append(("*Server Name*", json["server_name"])) if "library_name" in json: row1.append(("*Library Name*", json["library_name"])) if "collection" in json: text = "Collection" row1.append(("*Collection Name*", json["collection"])) elif "playlist" in json: text = "Playlist" row1.append(("*Playlist Name*", json["playlist"])) if row1: rows.append(row1) if json["event"] == "delete": title = json["message"] elif "error" in json: title = f":warning: Plex Meta Manager Encountered {'a Critical' if json['critical'] else 'an'} Error" rows.append([]) rows.append([(json["error"], )]) else: title = f"{':heavy_plus_sign:' if json['created'] else ':infinity:'} A {text} has Been {'Created' if json['created'] else 'Modified'}!" def get_field_text(items_list): field_text = "" for i, item in enumerate(items_list, 1): if "tmdb_id" in item: field_text += f"\n{i}. [{item['title']}](https://www.themoviedb.org/movie/{item['tmdb_id']})" elif "tvdb_id" in item: field_text += f"\n{i}. [{item['title']}](https://www.thetvdb.com/dereferrer/series/{item['tvdb_id']})" else: field_text += f"\n{i}. {item['title']}" return field_text if json["additions"]: rows.append([]) rows.append([("*Items Added*", " ")]) rows.append([(get_field_text(json["additions"]), )]) if json["removals"]: rows.append([]) rows.append([("*Items Removed*", " ")]) rows.append([(get_field_text(json["removals"]), )]) new_json = { "text": title, "blocks": [{ "type": "header", "text": {"type": "plain_text", "text": title} }] } if rows: for row in rows: if row: if len(row[0]) == 1: section = {"type": "section", "text": {"type": "plain_text", "text": row[0][0]}} new_json["blocks"].append(section) else: section = {"type": "section", "fields": []} for col in row: section["fields"].append({"type": "mrkdwn", "text": col[0]}) section["fields"].append({"type": "plain_text", "text": col[1]}) new_json["blocks"].append(section) # noqa else: new_json["blocks"].append({"type": "divider"}) # noqa return new_json def discord(self, json): description = None rows = [] if json["event"] == "run_end": title = "Run Completed" rows = [ [("Start Time", json["start_time"]), ("End Time", json["end_time"]), ("Run Time", json["run_time"])], [("Collections", None)], [ ("Created", json["collections_created"] if json["collections_created"] else "0"), ("Modified", json["collections_modified"] if json["collections_modified"] else "0"), ("Deleted", json["collections_deleted"] if json["collections_deleted"] else "0") ] ] if json["added_to_radarr"]: rows.append([(f"{json['added_to_radarr']} Movies Added To Radarr", None)]) if json["added_to_sonarr"]: rows.append([(f"{json['added_to_sonarr']} Series Added To Sonarr", None)]) elif json["event"] == "run_start": title = "Run Started" description = json["start_time"] elif json["event"] == "version": title = "New Version Available" rows = [ [("Current", json["current"]), ("Latest", json["latest"])], [("New Commits", json["notes"])] ] else: row1 = [] text = "" if "server_name" in json: row1.append(("Server", json["server_name"])) if "library_name" in json: row1.append(("Library", json["library_name"])) if "collection" in json: text = "Collection" row1.append(("Collection", json["collection"])) elif "playlist" in json: text = "Playlist" row1.append(("Playlist", json["playlist"])) if row1: rows.append(row1) if json["event"] == "delete": title = json["message"] elif "error" in json: title = f"{'Critical ' if json['critical'] else ''}Error" rows.append([("Error Message", json["error"])]) else: title = f"{text} {'Created' if json['created'] else 'Modified'}" def get_field_text(items_list): field_text = "" for i, item in enumerate(items_list, 1): if "tmdb_id" in item: field_text += f"\n{i}. [{item['title']}](https://www.themoviedb.org/movie/{item['tmdb_id']})" elif "tvdb_id" in item: field_text += f"\n{i}. [{item['title']}](https://www.thetvdb.com/dereferrer/series/{item['tvdb_id']})" else: field_text += f"\n{i}. {item['title']}" return field_text if json["additions"]: rows.append([("Items Added", get_field_text(json["additions"]))]) if json["removals"]: rows.append([("Items Removed", get_field_text(json["removals"]))]) new_json = { "embeds": [ { "title": title, "color": 0x00bc8c } ], "username": "Metabot", "avatar_url": "https://github.com/meisnate12/Plex-Meta-Manager/raw/master/.github/pmm.png" } if description: new_json["embeds"][0]["description"] = description if rows: fields = [] for row in rows: for col in row: col_name, col_value = col field = {"name": col_name, "value": col_value if col_value else ""} if len(row) > 1: field["inline"] = True fields.append(field) new_json["embeds"][0]["fields"] = fields return new_json