initial commit

This commit is contained in:
dries.k
2021-08-05 18:56:17 +02:00
commit 1520e4d9e7
56 changed files with 3820 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
from .api import GotifyApplication, GotifyClient
from .models import (
GotifyApplicationModel,
GotifyErrorModel,
GotifyHealthModel,
GotifyMessageModel,
GotifyPagedMessagesModel,
GotifyPagingModel,
GotifyVersionModel,
)

223
gotify_tray/gotify/api.py Normal file
View File

@@ -0,0 +1,223 @@
import logging
from typing import Callable, List, Optional, Union
import requests
from .listener import Listener
from .models import (
GotifyApplicationModel,
GotifyErrorModel,
GotifyHealthModel,
GotifyMessageModel,
GotifyPagedMessagesModel,
GotifyPagingModel,
GotifyVersionModel,
)
logger = logging.getLogger("logger")
class GotifySession(object):
def __init__(self, url: str, token: str):
self.url = url.rstrip("/")
self.session = requests.Session()
self.session.headers.update({"X-Gotify-Key": token})
def _get(self, endpoint: str = "/", **kwargs) -> requests.Response:
return self.session.get(self.url + endpoint, **kwargs)
def _post(self, endpoint: str = "/", **kwargs) -> requests.Response:
return self.session.post(self.url + endpoint, **kwargs)
def _put(self, endpoint: str = "/", **kwargs) -> requests.Response:
return self.session.put(self.url + endpoint, **kwargs)
def _delete(self, endpoint: str = "/", **kwargs) -> requests.Response:
return self.session.delete(self.url + endpoint, **kwargs)
# For sending messages
class GotifyApplication(GotifySession):
def __init__(self, url: str, application_token: str):
super(GotifyApplication, self).__init__(url, application_token)
def push(
self, title: str = "", message: str = "", priority: int = 0, extras: dict = None
) -> Union[GotifyMessageModel, GotifyErrorModel]:
response = self._post(
"/message",
json={
"title": title,
"message": message,
"priority": priority,
"extras": extras,
},
)
return (
GotifyMessageModel(response.json())
if response.ok
else GotifyErrorModel(response)
)
# For everything else
class GotifyClient(GotifySession):
def __init__(self, url: str, client_token: str):
super(GotifyClient, self).__init__(url, client_token)
self.hostname = self.url.lstrip("https://").lstrip("http://")
self.listener = Listener(self.hostname, client_token)
"""
Application
"""
def get_applications(self) -> Union[List[GotifyApplicationModel], GotifyErrorModel]:
response = self._get("/application")
return (
[GotifyApplicationModel(x) for x in response.json()]
if response.ok
else GotifyErrorModel(response)
)
def create_application(
self, name: str, description: str = ""
) -> Union[GotifyApplicationModel, GotifyErrorModel]:
response = self._post(
"/application", json={"name": name, "description": description}
)
return (
GotifyApplicationModel(response.json())
if response.ok
else GotifyErrorModel(response)
)
def update_application(
self, application_id: int, name: str, description: str = ""
) -> Union[GotifyApplicationModel, GotifyErrorModel]:
response = self._put(
f"/application/{application_id}",
json={"name": name, "description": description},
)
return (
GotifyApplicationModel(response.json())
if response.ok
else GotifyErrorModel(response)
)
def delete_application(self, application_id: int) -> bool:
return self._delete(f"/application/{application_id}").ok
def upload_application_image(
self, application_id: int, img_path: str
) -> Optional[GotifyApplicationModel]:
try:
with open(img_path, "rb") as f:
response = self._post(
f"/application/{application_id}/image", files={"file": f}
)
return response.json() if response.ok else None
except FileNotFoundError:
logger.error(
f"GotifyClient.upload_application_image: image '{img_path}' not found."
)
return None
"""
Message
"""
def get_application_messages(
self, application_id: int, limit: int = 100, since: int = None
) -> Union[GotifyPagedMessagesModel, GotifyErrorModel]:
response = self._get(
f"/application/{application_id}/message",
params={"limit": limit, "since": since},
)
if not response.ok:
return GotifyErrorModel(response)
j = response.json()
return GotifyPagedMessagesModel(
messages=[GotifyMessageModel(m) for m in j["messages"]],
paging=GotifyPagingModel(j["paging"]),
)
def delete_application_messages(self, application_id: int) -> bool:
return self._delete(f"/application/{application_id}/message").ok
def get_messages(
self, limit: int = 100, since: int = None
) -> Union[GotifyPagedMessagesModel, GotifyErrorModel]:
response = self._get("/message", params={"limit": limit, "since": since})
if not response.ok:
return GotifyErrorModel(response)
j = response.json()
return GotifyPagedMessagesModel(
messages=[GotifyMessageModel(m) for m in j["messages"]],
paging=GotifyPagingModel(j["paging"]),
)
def delete_messages(self) -> bool:
return self._delete("/message").ok
def delete_message(self, message_id: int) -> bool:
return self._delete(f"/message/{message_id}").ok
def listen(
self,
opened_callback: Callable[[], None] = None,
closed_callback: Callable[[int, str], None] = None,
new_message_callback: Callable[[GotifyMessageModel], None] = None,
error_callback: Callable[[Exception], None] = None,
):
def dummy(*args):
...
self.listener.opened.connect(lambda: self.opened_callback(opened_callback))
self.listener.closed.connect(closed_callback or dummy)
self.listener.new_message.connect(new_message_callback or dummy)
self.listener.error.connect(error_callback or dummy)
self.listener.start()
def opened_callback(self, user_callback: Callable[[], None] = None):
self.listener.reset_wait_time()
if user_callback:
user_callback()
def reconnect(self, increase_wait_time: bool = True):
if increase_wait_time:
self.listener.increase_wait_time()
self.listener.start()
def stop(self, reset_wait: bool = False):
if reset_wait:
self.listener.reset_wait_time()
self.listener.stop()
"""
Health
"""
def health(self) -> Union[GotifyHealthModel, GotifyErrorModel]:
response = self._get("/health")
return (
GotifyHealthModel(response.json())
if response.ok
else GotifyErrorModel(response)
)
"""
Version
"""
def version(self) -> Union[GotifyVersionModel, GotifyErrorModel]:
response = self._get("/version")
return (
GotifyVersionModel(response.json())
if response.ok
else GotifyErrorModel(response)
)

View File

@@ -0,0 +1,67 @@
import json
import time
import websocket
from PyQt6 import QtCore
from .models import GotifyMessageModel, GotifyErrorModel
class Listener(QtCore.QThread):
new_message = QtCore.pyqtSignal(GotifyMessageModel)
error = QtCore.pyqtSignal(Exception)
opened = QtCore.pyqtSignal()
closed = QtCore.pyqtSignal(int, str)
def __init__(self, hostname: str, client_token: str):
super(Listener, self).__init__()
self.hostname = hostname
self.client_token = client_token
self.ws = websocket.WebSocketApp(
f"wss://{self.hostname}/stream?token={self.client_token}",
on_message=self._on_message,
on_error=self._on_error,
on_open=self._on_open,
on_close=self._on_close,
)
self.wait_time = 0
self.running = False
def reset_wait_time(self):
self.wait_time = 0
def increase_wait_time(self):
if self.wait_time == 0:
self.wait_time = 1
else:
self.wait_time = min(self.wait_time * 2, 10 * 60)
def _on_message(self, ws: websocket.WebSocketApp, message: str):
self.new_message.emit(GotifyMessageModel(json.loads(message)))
def _on_error(self, ws: websocket.WebSocketApp, error: Exception):
self.error.emit(error)
def _on_open(self, ws: websocket.WebSocketApp):
self.opened.emit()
def _on_close(
self, ws: websocket.WebSocketApp, close_status_code: int, close_msg: str
):
self.closed.emit(close_status_code, close_msg)
def stop(self):
self.ws.close()
self.running = False
def run(self):
self.running = True
try:
time.sleep(self.wait_time)
self.ws.run_forever()
finally:
self.running = False

View File

@@ -0,0 +1,91 @@
import datetime
import logging
from typing import List, Optional
import requests
logger = logging.getLogger("logger")
try:
local_timezone = datetime.datetime.utcnow().astimezone().tzinfo
except Exception as e:
logger.error(f"gotify.models.local_timezone error: {e}")
local_timezone = None
class AttributeDict(dict):
def __init__(self, *args, **kwargs):
super(AttributeDict, self).__init__(*args, **kwargs)
self.__dict__ = self
class GotifyApplicationModel(AttributeDict):
description: str
id: int
image: str
internal: bool
name: str
token: str
class GotifyPagingModel(AttributeDict):
limit: int
next: Optional[str] = None
since: int
size: int
class GotifyMessageModel(AttributeDict):
appid: int
date: datetime.datetime
extras: Optional[dict] = None
id: int
message: str
priority: Optional[int] = None
title: Optional[str] = None
def __init__(self, d: dict, *args, **kwargs):
d.update(
{
"date": datetime.datetime.fromisoformat(
d["date"].split(".")[0] + ".000000+00:00"
).astimezone(local_timezone)
}
)
super(GotifyMessageModel, self).__init__(d, *args, **kwargs)
class GotifyPagedMessagesModel(AttributeDict):
messages: List[GotifyMessageModel]
paging: GotifyPagingModel
class GotifyHealthModel(AttributeDict):
database: str
health: str
class GotifyVersionModel(AttributeDict):
buildDate: str
commit: str
version: str
class GotifyErrorModel(AttributeDict):
error: str
errorCode: int
errorDescription: str
def __init__(self, response: requests.Response, *args, **kwargs):
try:
j = response.json()
except ValueError:
j = {
"error": "unknown",
"errorCode": response.status_code,
"errorDescription": "",
}
super(GotifyErrorModel, self).__init__(j, *args, **kwargs)