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

This commit is contained in:
meisnate12 2024-01-26 00:13:00 -05:00
commit fe9d0749bd
11 changed files with 218 additions and 9 deletions

View file

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

31
docs/config/gotify.md Normal file
View file

@ -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
```

View file

@ -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 } |
| [`omdb`](omdb.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 } |
| [`radarr`](radarr.md) | :fontawesome-solid-circle-xmark:{ .red } |
| [`sonarr`](sonarr.md) | :fontawesome-solid-circle-xmark:{ .red } |

View file

@ -27,7 +27,7 @@ webhooks:
| [`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.
* 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

View file

@ -25,6 +25,9 @@
"notifiarr": {
"$ref": "#/definitions/notifiarr-api"
},
"gotify": {
"$ref": "#/definitions/gotify-api"
},
"anidb": {
"$ref": "#/definitions/anidb-api"
},
@ -283,6 +286,24 @@
],
"title": "notifiarr"
},
"gotify-api": {
"type": "object",
"additionalProperties": false,
"properties": {
"url": {
"type": "string"
},
"apikey": {
"type": "string"
}
},
"required": [
"url",
"apikey"
],
"title": "gotify"
},
"anidb-api": {
"type": "object",
"additionalProperties": false,
@ -1116,7 +1137,7 @@
"type": "object",
"additionalProperties": false,
"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,
"properties": {
"metadata_files": {

View file

@ -472,6 +472,9 @@ mdblist:
cache_expiration: 60
notifiarr:
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
username: this-is-a-placeholder-string
password: this-is-a-placeholder-string

View file

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

View file

@ -16,6 +16,7 @@ from modules.mal import MyAnimeList
from modules.meta import PlaylistFile
from modules.mojo import BoxOfficeMojo
from modules.notifiarr import Notifiarr
from modules.gotify import Gotify
from modules.omdb import OMDb
from modules.overlays import Overlays
from modules.plex import Plex
@ -289,6 +290,7 @@ class ConfigFile:
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 "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 "radarr" in self.data:
if "monitor" in self.data["radarr"] and isinstance(self.data["radarr"]["monitor"], bool):
@ -546,6 +548,42 @@ class ConfigFile:
logger.save_errors = True
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:
self.TMDb = None
if "tmdb" in self.data:
@ -1205,7 +1243,7 @@ class ConfigFile:
logger.info("")
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)
logger.info("")

42
modules/gotify.py Normal file
View file

@ -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

View file

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

View file

@ -24,10 +24,14 @@ class Webhooks:
for webhook in list(set(webhooks)):
response = None
logger.trace(f"Webhook: {webhook}")
if webhook == "notifiarr":
if webhook == "notifiarr" or webhook == "gotify":
if self.notifiarr:
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:
break
else:
@ -40,16 +44,17 @@ class Webhooks:
try:
response_json = 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):
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]:
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
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
yaml.data["webhooks"][hook_cat] = None
if changed:
@ -62,7 +67,7 @@ class Webhooks:
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']}")
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"):
raise Failed(f"({response.status_code} [{response.reason}]) {response_json}")
except JSONDecodeError:
@ -326,3 +331,66 @@ class Webhooks:
fields.append(field)
new_json["embeds"][0]["fields"] = fields
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