[20] Merge remote-tracking branch 'krstn420/gotify' into nightly

pull/1858/head
meisnate12 10 months ago
commit fe9d0749bd

@ -105,6 +105,9 @@ mdblist:
cache_expiration: 60 cache_expiration: 60
notifiarr: notifiarr:
apikey: #################################### apikey: ####################################
gotify:
url: http://192.168.1.12:80
apikey: ####################################
anidb: # Not required for AniDB builders unless you want mature content anidb: # Not required for AniDB builders unless you want mature content
username: ###### username: ######
password: ###### password: ######

@ -0,0 +1,31 @@
# Gotify Attributes
Configuring [Gotify](https://gotify.net/) is optional but can allow you to send the [webhooks](webhooks.md)
straight to gotify.
A `gotify` mapping is in the root of the config file.
Below is a `gotify` mapping example and the full set of attributes:
```yaml
gotify:
url: ####################################
apikey: ####################################
```
| Attribute | Allowed Values | Required |
|:----------|:-----------------------------------------|:------------------------------------------:|
| `url` | Gotify Server Url | :fontawesome-solid-circle-check:{ .green } |
| `apikey` | Gotify Application API Key | :fontawesome-solid-circle-check:{ .green } |
Once you have added the apikey your config.yml you have to add `gotify` to any [webhook](webhooks.md) to send that
notification to Gotify.
```yaml
webhooks:
error: gotify
version: gotify
run_start: gotify
run_end: gotify
changes: gotify
```

@ -24,6 +24,7 @@ requirements for setup that can be found by clicking the links within the table.
| [`tautulli`](tautulli.md) | :fontawesome-solid-circle-xmark:{ .red } | | [`tautulli`](tautulli.md) | :fontawesome-solid-circle-xmark:{ .red } |
| [`omdb`](omdb.md) | :fontawesome-solid-circle-xmark:{ .red } | | [`omdb`](omdb.md) | :fontawesome-solid-circle-xmark:{ .red } |
| [`notifiarr`](notifiarr.md) | :fontawesome-solid-circle-xmark:{ .red } | | [`notifiarr`](notifiarr.md) | :fontawesome-solid-circle-xmark:{ .red } |
| [`gotify`](gotify.md) | :fontawesome-solid-circle-xmark:{ .red } |
| [`anidb`](anidb.md) | :fontawesome-solid-circle-xmark:{ .red } | | [`anidb`](anidb.md) | :fontawesome-solid-circle-xmark:{ .red } |
| [`radarr`](radarr.md) | :fontawesome-solid-circle-xmark:{ .red } | | [`radarr`](radarr.md) | :fontawesome-solid-circle-xmark:{ .red } |
| [`sonarr`](sonarr.md) | :fontawesome-solid-circle-xmark:{ .red } | | [`sonarr`](sonarr.md) | :fontawesome-solid-circle-xmark:{ .red } |

@ -27,7 +27,7 @@ webhooks:
| [`changes`](#changes-notifications) | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } | | [`changes`](#changes-notifications) | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } | :fontawesome-solid-circle-check:{ .green } |
* Each Attribute can be either a webhook url as a string or a comma-separated list of webhooks urls. * Each Attribute can be either a webhook url as a string or a comma-separated list of webhooks urls.
* To send notifications to [Notifiarr](notifiarr.md) just add `notifiarr` to a webhook instead of the webhook url. * To send notifications to [Notifiarr](notifiarr.md) or [Gotify](gotify.md) just add `notifiarr` or `gotify` to a webhook instead of the webhook url.
## Error Notifications ## Error Notifications

@ -25,6 +25,9 @@
"notifiarr": { "notifiarr": {
"$ref": "#/definitions/notifiarr-api" "$ref": "#/definitions/notifiarr-api"
}, },
"gotify": {
"$ref": "#/definitions/gotify-api"
},
"anidb": { "anidb": {
"$ref": "#/definitions/anidb-api" "$ref": "#/definitions/anidb-api"
}, },
@ -283,6 +286,24 @@
], ],
"title": "notifiarr" "title": "notifiarr"
}, },
"gotify-api": {
"type": "object",
"additionalProperties": false,
"properties": {
"url": {
"type": "string"
},
"apikey": {
"type": "string"
}
},
"required": [
"url",
"apikey"
],
"title": "gotify"
},
"anidb-api": { "anidb-api": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
@ -1116,7 +1137,7 @@
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"patternProperties": { "patternProperties": {
"^(?!plex|tmdb|tautulli|webhooks|omdb|mdblist|notifiarr|anidb|radarr|sonarr|trakt|mal).+$": { "^(?!plex|tmdb|tautulli|webhooks|omdb|mdblist|notifiarr|gotify|anidb|radarr|sonarr|trakt|mal).+$": {
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"metadata_files": { "metadata_files": {

@ -472,6 +472,9 @@ mdblist:
cache_expiration: 60 cache_expiration: 60
notifiarr: notifiarr:
apikey: this-is-a-placeholder-string apikey: this-is-a-placeholder-string
gotify:
url: http://192.168.1.12:80
apikey: this-is-a-placeholder-string
anidb: # Not required for AniDB builders unless you want mature content anidb: # Not required for AniDB builders unless you want mature content
username: this-is-a-placeholder-string username: this-is-a-placeholder-string
password: this-is-a-placeholder-string password: this-is-a-placeholder-string

@ -178,6 +178,7 @@ nav:
- Radarr: config/radarr.md - Radarr: config/radarr.md
- Sonarr: config/sonarr.md - Sonarr: config/sonarr.md
- Notifiarr: config/notifiarr.md - Notifiarr: config/notifiarr.md
- Gotify: config/gotify.md
- Tautulli: config/tautulli.md - Tautulli: config/tautulli.md
- Github: config/github.md - Github: config/github.md
- MdbList: config/mdblist.md - MdbList: config/mdblist.md

@ -16,6 +16,7 @@ from modules.mal import MyAnimeList
from modules.meta import PlaylistFile from modules.meta import PlaylistFile
from modules.mojo import BoxOfficeMojo from modules.mojo import BoxOfficeMojo
from modules.notifiarr import Notifiarr from modules.notifiarr import Notifiarr
from modules.gotify import Gotify
from modules.omdb import OMDb from modules.omdb import OMDb
from modules.overlays import Overlays from modules.overlays import Overlays
from modules.plex import Plex from modules.plex import Plex
@ -289,6 +290,7 @@ class ConfigFile:
if "omdb" in self.data: self.data["omdb"] = self.data.pop("omdb") if "omdb" in self.data: self.data["omdb"] = self.data.pop("omdb")
if "mdblist" in self.data: self.data["mdblist"] = self.data.pop("mdblist") if "mdblist" in self.data: self.data["mdblist"] = self.data.pop("mdblist")
if "notifiarr" in self.data: self.data["notifiarr"] = self.data.pop("notifiarr") if "notifiarr" in self.data: self.data["notifiarr"] = self.data.pop("notifiarr")
if "gotify" in self.data: self.data["gotify"] = self.data.pop("gotify")
if "anidb" in self.data: self.data["anidb"] = self.data.pop("anidb") if "anidb" in self.data: self.data["anidb"] = self.data.pop("anidb")
if "radarr" in self.data: if "radarr" in self.data:
if "monitor" in self.data["radarr"] and isinstance(self.data["radarr"]["monitor"], bool): if "monitor" in self.data["radarr"] and isinstance(self.data["radarr"]["monitor"], bool):
@ -546,6 +548,42 @@ class ConfigFile:
logger.save_errors = True logger.save_errors = True
logger.separator() logger.separator()
self.GotifyFactory = None
if "gotify" in self.data:
logger.info("Connecting to Gotify...")
try:
self.GotifyFactory = Gotify(self, {"url": check_for_attribute(self.data, "url", parent="gotify", throw=True),
"apikey": check_for_attribute(self.data, "apikey", parent="gotify", throw=True)})
except Failed as e:
if str(e).endswith("is blank"):
logger.warning(e)
else:
logger.stacktrace()
logger.error(e)
logger.info(f"Gotify Connection {'Failed' if self.GotifyFactory is None else 'Successful'}")
else:
logger.info("gotify attribute not found")
self.webhooks = {
"error": check_for_attribute(self.data, "error", parent="webhooks", var_type="list", default_is_none=True),
"version": check_for_attribute(self.data, "version", 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),
"changes": check_for_attribute(self.data, "changes", parent="webhooks", var_type="list", default_is_none=True),
"delete": check_for_attribute(self.data, "delete", parent="webhooks", var_type="list", default_is_none=True)
}
self.Webhooks = Webhooks(self, self.webhooks, notifiarr=self.GotifyFactory)
try:
self.Webhooks.start_time_hooks(self.start_time)
if self.version[0] != "Unknown" and self.latest_version[0] != "Unknown" and self.version[1] != self.latest_version[1] or (self.version[2] and self.version[2] < self.latest_version[2]):
self.Webhooks.version_hooks(self.version, self.latest_version)
except Failed as e:
logger.stacktrace()
logger.error(f"Webhooks Error: {e}")
logger.save_errors = True
logger.separator()
try: try:
self.TMDb = None self.TMDb = None
if "tmdb" in self.data: if "tmdb" in self.data:
@ -1205,7 +1243,7 @@ class ConfigFile:
logger.info("") logger.info("")
logger.info(f"{display_name} library's Tautulli Connection {'Failed' if library.Tautulli is None else 'Successful'}") logger.info(f"{display_name} library's Tautulli Connection {'Failed' if library.Tautulli is None else 'Successful'}")
library.Webhooks = Webhooks(self, {}, library=library, notifiarr=self.NotifiarrFactory) library.Webhooks = Webhooks(self, {}, library=library, notifiarr=self.NotifiarrFactory or self.GotifyFactory,)
library.Overlays = Overlays(self, library) library.Overlays = Overlays(self, library)
logger.info("") logger.info("")

@ -0,0 +1,42 @@
from json import JSONDecodeError
from modules import util
from modules.util import Failed
from retrying import retry
logger = util.logger
class Gotify:
def __init__(self, config, params):
self.config = config
self.apikey = params["apikey"]
self.url = params["url"]
logger.secret(self.apikey)
try:
self.request(path="message")
except JSONDecodeError:
raise Failed("Gotify Error: Invalid JSON response received")
def notification(self, json):
return self.request(json=json)
@retry(stop_max_attempt_number=6, wait_fixed=10000, retry_on_exception=util.retry_if_not_failed)
def request(self, json=None, path="message"):
if not json:
json = {
"message": "Well hello there.",
"priority": 1,
"title": "This is first contact"
}
response = self.config.post(f"{self.url}{path}?token={self.apikey}", json=json)
try:
response_json = response.json()
except JSONDecodeError as e:
logger.error(response.content)
logger.debug(e)
raise e
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}")
if not response_json["id"]:
raise Failed("Gotify Error: Invalid apikey")
return response

@ -17,6 +17,7 @@ class Library(ABC):
self.Operations = Operations(config, self) self.Operations = Operations(config, self)
self.Overlays = None self.Overlays = None
self.Notifiarr = None self.Notifiarr = None
self.Gotify = None
self.collections = [] self.collections = []
self.collection_names = [] self.collection_names = []
self.metadatas = [] self.metadatas = []

@ -24,10 +24,14 @@ class Webhooks:
for webhook in list(set(webhooks)): for webhook in list(set(webhooks)):
response = None response = None
logger.trace(f"Webhook: {webhook}") logger.trace(f"Webhook: {webhook}")
if webhook == "notifiarr": if webhook == "notifiarr" or webhook == "gotify":
if self.notifiarr: if self.notifiarr:
for x in range(6): for x in range(6):
response = self.notifiarr.notification(json) if webhook == "gotify":
json = self.gotify(json)
response = self.notifiarr.notification(json)
else:
response = self.notifiarr.notification(json)
if response.status_code < 500: if response.status_code < 500:
break break
else: else:
@ -40,16 +44,17 @@ class Webhooks:
try: try:
response_json = response.json() response_json = response.json()
logger.trace(f"Response: {response_json}") logger.trace(f"Response: {response_json}")
if webhook == "notifiarr" and self.notifiarr and response.status_code == 400: if (webhook == "notifiarr" or webhook == "gotify") and self.notifiarr and response.status_code == 400:
def remove_from_config(text, hook_cat): def remove_from_config(text, hook_cat):
if response_json["details"]["response"] == text: if response_json["details"]["response"] == text:
yaml = YAML(self.config.config_path) yaml = YAML(self.config.config_path)
changed = False changed = False
if hook_cat in yaml.data and yaml.data["webhooks"][hook_cat]: 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]: if isinstance(yaml.data["webhooks"][hook_cat], list) and ("notifiarr" in yaml.data["webhooks"][hook_cat] or "gotify" in yaml.data["webhooks"][hook_cat]):
changed = True changed = True
yaml.data["webhooks"][hook_cat].pop("notifiarr") yaml.data["webhooks"][hook_cat].pop("notifiarr")
elif yaml.data["webhooks"][hook_cat] == "notifiarr": yaml.data["webhooks"][hook_cat].pop("gotify")
elif yaml.data["webhooks"][hook_cat] == "notifiarr" or yaml.data["webhooks"][hook_cat] == "gotify":
changed = True changed = True
yaml.data["webhooks"][hook_cat] = None yaml.data["webhooks"][hook_cat] = None
if changed: if changed:
@ -62,7 +67,7 @@ class Webhooks:
remove_from_config("PMM start/complete trigger is not enabled", "run_end") remove_from_config("PMM start/complete trigger is not enabled", "run_end")
remove_from_config("PMM app updates trigger is not enabled", "version") 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"]: 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']}") raise Failed(f"Notifiarr/Gotify Error: {response_json['details']['response']}")
if response.status_code >= 400 or ("result" in response_json and response_json["result"] == "error"): 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}") raise Failed(f"({response.status_code} [{response.reason}]) {response_json}")
except JSONDecodeError: except JSONDecodeError:
@ -326,3 +331,66 @@ class Webhooks:
fields.append(field) fields.append(field)
new_json["embeds"][0]["fields"] = fields new_json["embeds"][0]["fields"] = fields
return new_json return new_json
def gotify(self, json: dict):
message = ""
if json.get("event") == "run_end":
title = "Run Completed"
message = f"Start Time: {json['start_time']}\nEnd Time: {json['end_time']}\nRun Time: {json['run_time']}\nCollections Created: {json['collections_created']}\nCollections Modified: {json['collections_modified']}\nCollections Deleted: {json['collections_deleted']}"
if json.get("added_to_radarr"):
message = message + (f"{json['added_to_radarr']} Movies Added To Radarr\n", None)
if json.get("added_to_sonarr"):
message = message + (f"{json['added_to_sonarr']} Series Added To Sonarr\n", None)
elif json.get("event") == "run_start":
title = "Run Started"
message = json["start_time"]
elif json.get("event") == "version":
title = "New Version Available"
message = f"Current : {json['current']}\nLatest: {json['latest']}\nNew Commits: {json['notes']}"
else:
message1 = ""
text = ""
if "server_name" in json:
message1 = message1 + f"Server: {json['server_name']}\n"
if "library_name" in json:
message1 = message1 + f"Library: {json['library_name']}\n"
if "collection" in json:
text = "Collection"
message1 = message1 + f"Collection: {json['collection']}\n"
elif "playlist" in json:
text = "Playlist"
message1 = message1 + f"Playlist: {json['playlist']}\n"
if message1:
message1 = message1 + "\n"
if json["event"] == "delete":
title = json["message"]
elif "error" in json:
title = f"{'Critical ' if json['critical'] else ''}Error"
message = message + f"Error Message: {json['error']}\n"
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):
field_text += f"\n{i}. {item['title']}"
return field_text
if json["additions"]:
message = message + f"Items Added: { get_field_text(json['additions'])}\n"
if json["removals"]:
message = message + f"Items Removed: { get_field_text(json['removals'])}\n"
gotify_json = {
"message": "",
"priority": 1,
"title": ""
}
if message:
gotify_json["message"] = message
if title:
gotify_json["title"] = title
return gotify_json

Loading…
Cancel
Save