diff --git a/internal/api/api.go b/internal/api/api.go index 77022aa..3b6ccac 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -8,8 +8,10 @@ import ( "log/slog" "code.jhot.me/jhot/hats/internal/nats" + "code.jhot.me/jhot/hats/internal/ntfy" "code.jhot.me/jhot/hats/pkg/config" "code.jhot.me/jhot/hats/pkg/homeassistant" + ntfyPkg "code.jhot.me/jhot/hats/pkg/ntfy" "github.com/go-chi/chi/middleware" "github.com/go-chi/chi/v5" "github.com/go-chi/render" @@ -48,6 +50,8 @@ func Listen(parentLogger *slog.Logger) { router.Post("/api/schedule/{scheduleName}", createScheduleHandler) router.Delete("/api/schedule/{scheduleName}", deleteScheduleHandler) + router.Post("/api/ntfy", postNtfyHandler) + server = http.Server{ Addr: ":8888", Handler: router, @@ -244,3 +248,20 @@ func deleteScheduleHandler(w http.ResponseWriter, r *http.Request) { schedule.Cancel() render.PlainText(w, r, "OK") } + +// NTFY + +func postNtfyHandler(w http.ResponseWriter, r *http.Request) { + data := &ntfyPkg.Message{} + if err := render.DecodeJSON(r.Body, data); err != nil { + http.Error(w, "Unable to parse message data", http.StatusNotAcceptable) + return + } + + if err := ntfy.Send(*data); err != nil { + http.Error(w, "Unable to send message", http.StatusBadRequest) + return + } + + render.PlainText(w, r, "OK") +} diff --git a/internal/ntfy/client.go b/internal/ntfy/client.go new file mode 100644 index 0000000..3c1f8c6 --- /dev/null +++ b/internal/ntfy/client.go @@ -0,0 +1,28 @@ +package ntfy + +import ( + "fmt" + + "code.jhot.me/jhot/hats/pkg/config" + ntfyPkg "code.jhot.me/jhot/hats/pkg/ntfy" + "github.com/go-resty/resty/v2" +) + +var ( + ntfyClient *resty.Client +) + +func InitClient(cfg *config.HatsConfig) { + ntfyClient = resty.New().SetBaseURL(cfg.NtfyHost) + if cfg.NtfyToken != "" { + ntfyClient.SetHeader("Authorization", fmt.Sprintf("Bearer %s", cfg.NtfyToken)) + } +} + +func Send(data ntfyPkg.Message) error { + resp, err := ntfyClient.R().SetBody(data).Post("") + if err == nil && !resp.IsSuccess() { + err = fmt.Errorf("%d status code received: %s", resp.StatusCode(), resp.String()) + } + return err +} diff --git a/main.go b/main.go index e22d9af..3a76373 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ import ( "code.jhot.me/jhot/hats/internal/api" "code.jhot.me/jhot/hats/internal/homeassistant" "code.jhot.me/jhot/hats/internal/nats" + "code.jhot.me/jhot/hats/internal/ntfy" "code.jhot.me/jhot/hats/pkg/config" ) @@ -53,6 +54,8 @@ func main() { } defer homeassistant.CloseSubscription() + ntfy.InitClient(cfg) + api.Listen(logger) defer api.Close() diff --git a/pkg/client/client.go b/pkg/client/client.go index 3dd271f..c3be2af 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -5,6 +5,7 @@ import ( "code.jhot.me/jhot/hats/internal/api" ha "code.jhot.me/jhot/hats/pkg/homeassistant" + "code.jhot.me/jhot/hats/pkg/ntfy" "github.com/go-resty/resty/v2" ) @@ -178,3 +179,11 @@ func (c *HatsClient) DeleteSchedule(name string) error { } return err } + +func (c *HatsClient) SendNtfyMessage(data ntfy.Message) error { + resp, err := c.client.R().SetBody(data).Post("api/ntfy") + if err == nil && !resp.IsSuccess() { + err = fmt.Errorf("%d status code received: %s", resp.StatusCode(), resp.String()) + } + return err +} diff --git a/pkg/config/config.go b/pkg/config/config.go index 165b057..fc2ab7b 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -25,6 +25,9 @@ type HatsConfig struct { HatsHost string HatsPort string HatsSecure bool + + NtfyHost string + NtfyToken string } func FromEnvironment() *HatsConfig { @@ -39,6 +42,8 @@ func FromEnvironment() *HatsConfig { NatsClientName: util.GetEnvWithDefault("NATS_CLIENT_NAME", "hats"), HatsHost: util.GetEnvWithDefault("HATS_HOST", "hats"), HatsPort: util.GetEnvWithDefault("HATS_PORT", "8888"), + NtfyHost: util.GetEnvWithDefault("NTFY_HOST", "https://ntfy.sh"), + NtfyToken: util.GetEnvWithDefault("NTFY_TOKEN", ""), } config.HomeAssistantSecure, _ = strconv.ParseBool(util.GetEnvWithDefault("HASS_SECURE", "false")) diff --git a/pkg/ntfy/structs.go b/pkg/ntfy/structs.go new file mode 100644 index 0000000..4dc3eae --- /dev/null +++ b/pkg/ntfy/structs.go @@ -0,0 +1,26 @@ +package ntfy + +type Message struct { + Topic string `json:"topic"` // Required + Message string `json:"message,omitempty"` + Markdown bool `json:"markdown"` + Title string `json:"title"` // Required + Tags []string `json:"tags,omitempty"` // https://docs.ntfy.sh/publish/#tags-emojis + Priority int `json:"priority,omitempty"` // 1-5 + Delay string `json:"delay,omitempty"` // Duration i.e. "5s" + Icon string `json:"icon,omitempty"` // URL + Attach string `json:"attach,omitempty"` // URL + Filename string `json:"filename,omitempty"` + Click string `json:"click,omitempty"` // URL + Actions []struct { + Action string `json:"action"` // "view", "broadcast", or "http" + Label string `json:"label"` + URL string `json:"url,omitempty"` + Method string `json:"method,omitempty"` + Headers string `json:"headers,omitempty"` + Body string `json:"body,omitempty"` + Intent string `json:"intent,omitempty"` + Clear bool `json:"clear,omitempty"` + Extras map[string]string `json:"extras,omitempty"` + } `json:"actions,omitempty"` +}