diff --git a/server/index.gohtml b/server/index.gohtml index d2acf8be..9267cbf8 100644 --- a/server/index.gohtml +++ b/server/index.gohtml @@ -67,12 +67,6 @@ curl -d "Backup successful 😀" ntfy.sh/mytopic -

- And another one using PUT (via curl -T): -

- - echo -en "\u26A0\uFE0F Unauthorized login" | curl -T- ntfy.sh/mytopic -

Here's an example in JS with fetch() (see full example):

@@ -82,6 +76,19 @@   body: 'Hello from the other side.'
}) +

+ There are more features related to publishing messages: You can set a + notification priority, a title, and tag messages. + Here's an example using all of them: +

+ + curl \
+   -H "Title: Unauthorized access detected" \
+   -H "Priority: urgent" \
+   -H "Tags: warn,skull" \
+   -d "Remote access to $(hostname) detected. Act right away." \
+   ntfy.sh/mytopic +

Subscribe to a topic

@@ -196,6 +203,43 @@ {"id":"Cm02DsxUHb","time":1637182643,"event":"message","topic":"mytopic2","message":"for topic 2"} +

Message priority (X-Priority, Priority, prio, or p)

+

+ All messages have a priority, which defines how your urgently your phone notifies you. You can set custom + notification sounds and vibration patterns on your phone to map to these priorities. +

+

+ The following priorities exist: 1 (min), 2 (low), 3 (default), + 4 (high), and 5 (max/urgent). You can set the priority with the + header X-Priority (or any of its aliases: Priority, prio, or p). Here are a few examples: +

+ + curl -H "X-Priority: urgent" -d "An urgent message" ntfy.sh/mytopic
+ curl -H "Priority: 2" -d "Low priority message" ntfy.sh/mytopic
+ curl -H p:4 -d "A high priority message" ntfy.sh/mytopic +
+ +

Notification title (X-Title, Title, ti, or t)

+

+ The notification title is typically set to the topic short URL (e.g. ntfy.sh/mytopic. + To override it, you can set the X-Title header (or any of its aliases: Title, ti, or t). +

+ + curl -H "Title: Dogs are better than cats" -d "Oh my ..." ntfy.sh/mytopic
+
+ +

Tagging messages (X-Tags, Tags, or ta)

+

+ You can tag notifications with emojis (or other relevant strings). In the phone app, the tags will be converted + to emojis and prepended to the message or title in the notification. You can set tags with the X-Tags header + (or any of its aliases: Tags, or ta). Use this reference + to figure out what tags you can use to send emojis. +

+ + curl -H "Tags: warn,skull" -d "Unauthorized SSH access" ntfy.sh/mytopic
+ curl -H tags:thumbsup -d "Backup successful" ntfy.sh/mytopic
+
+

Examples

There are a million ways to use ntfy, but here are some inspirations. I try to collect @@ -213,7 +257,7 @@ rsync -a root@laptop /backups/laptop \
  && zfs snapshot ... \
  && curl -d "Laptop backup succeeded" ntfy.sh/backups \
-   || echo -en "\u26A0\uFE0F Laptop backup failed" | curl -sT- ntfy.sh/backups +   || curl -H tags:warn -H prio:high -d "Laptop backup failed" ntfy.sh/backups

Example: Server-sent messages in your web app

@@ -240,7 +284,7 @@ #!/bin/bash
if [ "${PAM_TYPE}" = "open_session" ]; then
-   echo -en "\u26A0\uFE0F SSH login: ${PAM_USER} from ${PAM_RHOST}" | curl -T- ntfy.sh/alerts
+   curl -H tags:warn -d "SSH login: ${PAM_USER} from ${PAM_RHOST}" ntfy.sh/alerts
fi
diff --git a/server/message.go b/server/message.go index 5b91a284..ad870e09 100644 --- a/server/message.go +++ b/server/message.go @@ -18,11 +18,14 @@ const ( // message represents a message published to a topic type message struct { - ID string `json:"id"` // Random message ID - Time int64 `json:"time"` // Unix time in seconds - Event string `json:"event"` // One of the above - Topic string `json:"topic"` - Message string `json:"message,omitempty"` + ID string `json:"id"` // Random message ID + Time int64 `json:"time"` // Unix time in seconds + Event string `json:"event"` // One of the above + Topic string `json:"topic"` + Priority int `json:"priority,omitempty"` + Tags []string `json:"tags,omitempty"` + Title string `json:"title,omitempty"` + Message string `json:"message,omitempty"` } // messageEncoder is a function that knows how to encode a message @@ -31,11 +34,14 @@ type messageEncoder func(msg *message) (string, error) // newMessage creates a new message with the current timestamp func newMessage(event, topic, msg string) *message { return &message{ - ID: util.RandomString(messageIDLength), - Time: time.Now().Unix(), - Event: event, - Topic: topic, - Message: msg, + ID: util.RandomString(messageIDLength), + Time: time.Now().Unix(), + Event: event, + Topic: topic, + Priority: 0, + Tags: nil, + Title: "", + Message: msg, } } diff --git a/server/server.go b/server/server.go index 3ccc86a9..08ca68ce 100644 --- a/server/server.go +++ b/server/server.go @@ -89,7 +89,7 @@ var ( indexTemplate = template.Must(template.New("index").Parse(indexSource)) //go:embed "example.html" - exampleSource string + exampleSource string //go:embed static webStaticFs embed.FS @@ -150,11 +150,14 @@ func createFirebaseSubscriber(conf *config.Config) (subscriber, error) { _, err := msg.Send(context.Background(), &messaging.Message{ Topic: m.Topic, Data: map[string]string{ - "id": m.ID, - "time": fmt.Sprintf("%d", m.Time), - "event": m.Event, - "topic": m.Topic, - "message": m.Message, + "id": m.ID, + "time": fmt.Sprintf("%d", m.Time), + "event": m.Event, + "topic": m.Topic, + "priority": fmt.Sprintf("%d", m.Priority), + "tags": strings.Join(m.Tags, ","), + "title": m.Title, + "message": m.Message, }, }) return err @@ -246,6 +249,10 @@ func (s *Server) handlePublish(w http.ResponseWriter, r *http.Request, v *visito if m.Message == "" { return errHTTPBadRequest } + title, priority, tags := parseHeaders(r.Header) + m.Title = title + m.Priority = priority + m.Tags = tags if err := t.Publish(m); err != nil { return err } @@ -262,6 +269,40 @@ func (s *Server) handlePublish(w http.ResponseWriter, r *http.Request, v *visito return nil } +func parseHeaders(header http.Header) (title string, priority int, tags []string) { + title = readHeader(header, "x-title", "title", "ti", "t") + priorityStr := readHeader(header, "x-priority", "priority", "prio", "p") + if priorityStr != "" { + switch strings.ToLower(priorityStr) { + case "1", "min": + priority = 1 + case "2", "low": + priority = 2 + case "4", "high": + priority = 4 + case "5", "max", "urgent": + priority = 5 + default: + priority = 3 + } + } + tagsStr := readHeader(header, "x-tags", "tags", "ta") + if tagsStr != "" { + tags = strings.Split(tagsStr, ",") + } + return title, priority, tags +} + +func readHeader(header http.Header, names ...string) string { + for _, name := range names { + value := header.Get(name) + if value != "" { + return value + } + } + return "" +} + func (s *Server) handleSubscribeJSON(w http.ResponseWriter, r *http.Request, v *visitor) error { encoder := func(msg *message) (string, error) { var buf bytes.Buffer @@ -414,11 +455,11 @@ func (s *Server) topicFromID(id string) (*topic, error) { return topics[0], nil } -func (s *Server) topicsFromIDs(ids... string) ([]*topic, error) { +func (s *Server) topicsFromIDs(ids ...string) ([]*topic, error) { s.mu.Lock() defer s.mu.Unlock() topics := make([]*topic, 0) - for _, id := range ids { + for _, id := range ids { if _, ok := s.topics[id]; !ok { if len(s.topics) >= s.config.GlobalTopicLimit { return nil, errHTTPTooManyRequests diff --git a/server/static/css/app.css b/server/static/css/app.css index bd0d4be3..e05193a8 100644 --- a/server/static/css/app.css +++ b/server/static/css/app.css @@ -69,6 +69,7 @@ code { margin-top: 10px; margin-bottom: 20px; overflow-x: auto; + white-space: nowrap; } /* Lato font (OFL), https://fonts.google.com/specimen/Lato#about,