gotify-desktop/main.go
2020-10-09 23:08:07 +02:00

199 lines
4.2 KiB
Go

package main
import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"path"
"github.com/godbus/dbus"
"github.com/gorilla/websocket"
)
type messageType struct {
ID int `json:"id"`
AppID int `json:"appid"`
Message string `json:"message"`
Title string `json:"title"`
Priority int `json:"priority"`
// Date time.Time `json:"date"` // Ignore that for now
}
type applicationType struct {
Description string `json:"description"`
ID int `json:"id"`
Image string `json:"image"`
Name string `json:"name"`
Token string `json:"token"`
}
func listenToPushEvents(url string, c chan messageType) {
defer close(c)
connection, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
log.Printf("Dial error: %s", err.Error())
}
defer connection.Close()
for {
var result messageType
err := connection.ReadJSON(&result)
if err != nil {
log.Printf("Connection error: %s", err.Error())
return
}
log.Printf("Message: %s %s\n", result.Title, result.Message)
c <- result
}
}
func sendNotification(dbusConn *dbus.Conn, message messageType, hostname, token string) error {
image := getAppImage(hostname, token, message.AppID)
// https://specifications.freedesktop.org/notification-spec/notification-spec-latest.html#urgency-levels
var urgencyByte int
if message.Priority <= 3 {
urgencyByte = 0
} else if message.Priority <= 7 {
urgencyByte = 1
} else {
urgencyByte = 2
}
obj := dbusConn.Object("org.freedesktop.Notifications", "/org/freedesktop/Notifications")
call := obj.Call("org.freedesktop.Notifications.Notify", 0, "gotify", uint32(0), "", message.Title, message.Message, []string{},
map[string]dbus.Variant{"urgency": dbus.MakeVariant(byte(urgencyByte)), "image-path": dbus.MakeVariant(image)}, int32(-1))
return call.Err
}
func getAppImage(hostname, token string, appID int) string {
cacheDir := getImageDir()
image := path.Join(cacheDir, fmt.Sprint(appID))
// Check if you need to download the image
_, err := os.Stat(image)
if os.IsNotExist(err) {
// File does not exist
err := downloadAppImage(hostname, token, cacheDir, appID)
if err != nil {
return ""
}
}
return image
}
func getImageDir() string {
cacheDir := os.Getenv("XDG_CACHE_HOME")
if cacheDir == "" {
cacheDir = path.Join(os.Getenv("HOME"), ".cache/gotify-desktop")
} else {
cacheDir = path.Join(cacheDir, "gotify-desktop")
}
return cacheDir
}
func downloadAppImage(hostname string, token string, cacheDir string, appID int) error {
u, err := getAppImageURL(hostname, token, appID)
if err != nil {
return err
}
os.MkdirAll(cacheDir, 0755) // rwx r-x r-x
resp, err := http.Get(u)
if err != nil {
return err
}
defer resp.Body.Close()
out, err := os.Create(path.Join(cacheDir, fmt.Sprint(appID)))
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, resp.Body)
return err
}
func getAppImageURL(hostname string, token string, appID int) (string, error) {
u := url.URL{Scheme: "https", Host: hostname, Path: "/application"}
param := url.Values{}
param.Add("token", token)
resp, err := http.Get(u.String() + "?" + param.Encode())
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
var result []applicationType
json.Unmarshal(body, &result)
for i := 0; i < len(result); i++ {
if result[i].ID == appID {
u := url.URL{Scheme: "https", Host: hostname, Path: result[i].Image}
return u.String() + "?" + param.Encode(), nil
}
}
return "", errors.New("")
}
func parseArgs() (string, string) {
if len(os.Args) < 2 {
fmt.Println("Usage: gotify-dektop <hostname> <token>")
os.Exit(1)
}
return os.Args[1], os.Args[2]
}
func main() {
hostname, token := parseArgs()
u := url.URL{Scheme: "wss", Host: hostname, Path: "/stream"}
param := url.Values{}
param.Add("token", token)
dbusConn, err := dbus.ConnectSessionBus()
if err != nil {
log.Printf("Dbus error: %s", err.Error())
os.Exit(1)
}
for {
push := make(chan messageType)
go listenToPushEvents(u.String()+"?"+param.Encode(), push)
for pushMessage := range push {
go sendNotification(dbusConn, pushMessage, hostname, token)
}
}
}