package main import ( "encoding/json" "errors" "fmt" "io" "io/ioutil" "log" "net/http" "net/url" "os" "path" "github.com/godbus/dbus/v5" "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 } 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) < 3 { fmt.Println("Usage: gotify-dektop ") os.Exit(1) } return os.Args[1], os.Args[2] } func main() { hostname, token := parseArgs() log.Printf("Starting on: %s", hostname) u := url.URL{Scheme: "wss", Host: hostname, Path: "/stream"} param := url.Values{} param.Add("token", token) dbusConn, err := dbus.SessionBus() 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 { log.Printf("Message: %s | %s\n", pushMessage.Title, pushMessage.Message) go sendNotification(dbusConn, pushMessage, hostname, token) } } }