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
+
@@ -196,6 +203,43 @@ {"id":"Cm02DsxUHb","time":1637182643,"event":"message","topic":"mytopic2","message":"for topic 2"} +
+ 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
+
+
+ + 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
+
+
+ + 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
+
+
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
#!/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,