Initial commit
This commit is contained in:
98
internal/ntfy/client.go
Normal file
98
internal/ntfy/client.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package ntfy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
BaseURL string
|
||||
Token string
|
||||
HTTP *http.Client
|
||||
}
|
||||
|
||||
func New(baseURL string) *Client {
|
||||
return &Client{
|
||||
BaseURL: strings.TrimRight(baseURL, "/"),
|
||||
HTTP: &http.Client{Timeout: 15 * time.Second},
|
||||
}
|
||||
}
|
||||
|
||||
// NewWithToken returns a ntfy Client with bearer-token auth set. Use this
|
||||
// when the ntfy server requires authentication.
|
||||
func NewWithToken(baseURL, token string) *Client {
|
||||
c := New(baseURL)
|
||||
c.Token = strings.TrimSpace(token)
|
||||
return c
|
||||
}
|
||||
|
||||
type Notification struct {
|
||||
Topic string
|
||||
Title string
|
||||
Message string
|
||||
Priority string
|
||||
Tags []string
|
||||
Click string
|
||||
}
|
||||
|
||||
// Send publishes to ntfy using the topic-path + header style
|
||||
// (POST {base}/{topic} with metadata in HTTP headers and the message as the
|
||||
// raw body). This is the most broadly compatible ntfy publish method —
|
||||
// works on every ntfy version including self-hosted, and on any path layout
|
||||
// the server is mounted under.
|
||||
func (c *Client) Send(ctx context.Context, n Notification) error {
|
||||
if c.BaseURL == "" {
|
||||
return fmt.Errorf("ntfy base_url not configured")
|
||||
}
|
||||
if n.Topic == "" {
|
||||
return fmt.Errorf("ntfy topic required")
|
||||
}
|
||||
url := c.BaseURL + "/" + strings.TrimLeft(n.Topic, "/")
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, strings.NewReader(n.Message))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "text/plain; charset=utf-8")
|
||||
if c.Token != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+c.Token)
|
||||
}
|
||||
if n.Title != "" {
|
||||
req.Header.Set("Title", n.Title)
|
||||
}
|
||||
if n.Priority != "" {
|
||||
req.Header.Set("Priority", n.Priority)
|
||||
}
|
||||
if len(n.Tags) > 0 {
|
||||
req.Header.Set("Tags", strings.Join(n.Tags, ","))
|
||||
}
|
||||
if n.Click != "" {
|
||||
req.Header.Set("Click", n.Click)
|
||||
}
|
||||
tokenLen := len(c.Token)
|
||||
tokenPrefix := ""
|
||||
if tokenLen >= 4 {
|
||||
tokenPrefix = c.Token[:4]
|
||||
}
|
||||
slog.Info("ntfy publish",
|
||||
"url", url,
|
||||
"topic", n.Topic,
|
||||
"auth_header_set", c.Token != "",
|
||||
"token_prefix", tokenPrefix,
|
||||
"token_len", tokenLen,
|
||||
)
|
||||
resp, err := c.HTTP.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ntfy POST: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode >= 300 {
|
||||
b, _ := io.ReadAll(io.LimitReader(resp.Body, 1024))
|
||||
return fmt.Errorf("ntfy returned %d: %s", resp.StatusCode, string(b))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user