Priorities, titles, tags

pull/26/head
Philipp Heckel 2021-11-27 16:12:08 -05:00
parent 7b8185c2a7
commit 1b8ebab5f3
4 changed files with 118 additions and 26 deletions

View File

@ -67,12 +67,6 @@
<code> <code>
curl -d "Backup successful 😀" <span class="ntfyUrl">ntfy.sh</span>/mytopic curl -d "Backup successful 😀" <span class="ntfyUrl">ntfy.sh</span>/mytopic
</code> </code>
<p class="smallMarginBottom">
And another one using PUT (via <tt>curl -T</tt>):
</p>
<code>
echo -en "\u26A0\uFE0F Unauthorized login" | curl -T- <span class="ntfyUrl">ntfy.sh</span>/mytopic
</code>
<p class="smallMarginBottom"> <p class="smallMarginBottom">
Here's an example in JS with <tt>fetch()</tt> (see <a href="https://github.com/binwiederhier/ntfy/tree/main/examples">full example</a>): Here's an example in JS with <tt>fetch()</tt> (see <a href="https://github.com/binwiederhier/ntfy/tree/main/examples">full example</a>):
</p> </p>
@ -82,6 +76,19 @@
&nbsp;&nbsp;body: 'Hello from the other side.'<br/> &nbsp;&nbsp;body: 'Hello from the other side.'<br/>
}) })
</code> </code>
<p class="smallMarginBottom">
There are <a href="#other-features">more features</a> related to publishing messages: You can set a
<a href="#priority">notification priority</a>, a <a href="#title">title</a>, and <a href="#tags">tag messages</a>.
Here's an example using all of them:
</p>
<code>
curl \<br/>
&nbsp;&nbsp;-H "Title: Unauthorized access detected" \<br/>
&nbsp;&nbsp;-H "Priority: urgent" \<br/>
&nbsp;&nbsp;-H "Tags: warn,skull" \<br/>
&nbsp;&nbsp;-d "Remote access to $(hostname) detected. Act right away." \<br/>
&nbsp;&nbsp;<span class="ntfyUrl">ntfy.sh</span>/mytopic
</code>
<h2 id="subscribe" class="anchor">Subscribe to a topic</h2> <h2 id="subscribe" class="anchor">Subscribe to a topic</h2>
<p> <p>
@ -196,6 +203,43 @@
{"id":"Cm02DsxUHb","time":1637182643,"event":"message","topic":"mytopic2","message":"for topic 2"} {"id":"Cm02DsxUHb","time":1637182643,"event":"message","topic":"mytopic2","message":"for topic 2"}
</code> </code>
<h3 id="priority" class="anchor">Message priority (<tt>X-Priority</tt>, <tt>Priority</tt>, <tt>prio</tt>, or <tt>p</tt>)</h3>
<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.
</p>
<p class="smallMarginBottom">
The following priorities exist: <tt>1</tt> (<tt>min</tt>), <tt>2</tt> (<tt>low</tt>), <tt>3</tt> (<tt>default</tt>),
<tt>4</tt> (<tt>high</tt>), and <tt>5</tt> (<tt>max</tt>/<tt>urgent</tt>). You can set the priority with the
header <tt>X-Priority</tt> (or any of its aliases: <tt>Priority</tt>, <tt>prio</tt>, or <tt>p</tt>). Here are a few examples:
</p>
<code>
curl -H "X-Priority: urgent" -d "An urgent message" <span class="ntfyUrl">ntfy.sh</span>/mytopic<br/>
curl -H "Priority: 2" -d "Low priority message" <span class="ntfyUrl">ntfy.sh</span>/mytopic<br/>
curl -H p:4 -d "A high priority message" <span class="ntfyUrl">ntfy.sh</span>/mytopic
</code>
<h3 id="title" class="anchor">Notification title (<tt>X-Title</tt>, <tt>Title</tt>, <tt>ti</tt>, or <tt>t</tt>)</h3>
<p class="smallMarginBottom">
The notification title is typically set to the topic short URL (e.g. <tt><span class="ntfyUrl">ntfy.sh</span>/mytopic</tt>.
To override it, you can set the <tt>X-Title</tt> header (or any of its aliases: <tt>Title</tt>, <tt>ti</tt>, or <tt>t</tt>).
</p>
<code>
curl -H "Title: Dogs are better than cats" -d "Oh my ..." <span class="ntfyUrl">ntfy.sh</span>/mytopic<br/>
</code>
<h3 id="tags" class="anchor">Tagging messages (<tt>X-Tags</tt>, <tt>Tags</tt>, or <tt>ta</tt>)</h3>
<p class="smallMarginBottom">
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 <tt>X-Tags</tt> header
(or any of its aliases: <tt>Tags</tt>, or <tt>ta</tt>). Use <a href="https://github.com/vdurmont/emoji-java/blob/master/EMOJIS.md">this reference</a>
to figure out what tags you can use to send emojis.
</p>
<code>
curl -H "Tags: warn,skull" -d "Unauthorized SSH access" <span class="ntfyUrl">ntfy.sh</span>/mytopic<br/>
curl -H tags:thumbsup -d "Backup successful" <span class="ntfyUrl">ntfy.sh</span>/mytopic<br/>
</code>
<h2 id="examples" class="anchor">Examples</h2> <h2 id="examples" class="anchor">Examples</h2>
<p> <p>
There are a million ways to use ntfy, but here are some inspirations. I try to collect 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 \<br/> rsync -a root@laptop /backups/laptop \<br/>
&nbsp;&nbsp;&& zfs snapshot ... \<br/> &nbsp;&nbsp;&& zfs snapshot ... \<br/>
&nbsp;&nbsp;&& curl -d "Laptop backup succeeded" <span class="ntfyUrl">ntfy.sh</span>/backups \<br/> &nbsp;&nbsp;&& curl -d "Laptop backup succeeded" <span class="ntfyUrl">ntfy.sh</span>/backups \<br/>
&nbsp;&nbsp;|| echo -en "\u26A0\uFE0F Laptop backup failed" | curl -sT- <span class="ntfyUrl">ntfy.sh</span>/backups &nbsp;&nbsp;|| curl -H tags:warn -H prio:high -d "Laptop backup failed" <span class="ntfyUrl">ntfy.sh</span>/backups
</code> </code>
<h3 id="example-web" class="anchor">Example: Server-sent messages in your web app</h3> <h3 id="example-web" class="anchor">Example: Server-sent messages in your web app</h3>
@ -240,7 +284,7 @@
<code> <code>
#!/bin/bash<br/> #!/bin/bash<br/>
if [ "${PAM_TYPE}" = "open_session" ]; then<br/> if [ "${PAM_TYPE}" = "open_session" ]; then<br/>
&nbsp;&nbsp;echo -en "\u26A0\uFE0F SSH login: ${PAM_USER} from ${PAM_RHOST}" | curl -T- <span class="ntfyUrl">ntfy.sh</span>/alerts<br/> &nbsp;&nbsp;curl -H tags:warn -d "SSH login: ${PAM_USER} from ${PAM_RHOST}" <span class="ntfyUrl">ntfy.sh</span>/alerts<br/>
fi fi
</code> </code>

View File

@ -18,11 +18,14 @@ const (
// message represents a message published to a topic // message represents a message published to a topic
type message struct { type message struct {
ID string `json:"id"` // Random message ID ID string `json:"id"` // Random message ID
Time int64 `json:"time"` // Unix time in seconds Time int64 `json:"time"` // Unix time in seconds
Event string `json:"event"` // One of the above Event string `json:"event"` // One of the above
Topic string `json:"topic"` Topic string `json:"topic"`
Message string `json:"message,omitempty"` 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 // 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 // newMessage creates a new message with the current timestamp
func newMessage(event, topic, msg string) *message { func newMessage(event, topic, msg string) *message {
return &message{ return &message{
ID: util.RandomString(messageIDLength), ID: util.RandomString(messageIDLength),
Time: time.Now().Unix(), Time: time.Now().Unix(),
Event: event, Event: event,
Topic: topic, Topic: topic,
Message: msg, Priority: 0,
Tags: nil,
Title: "",
Message: msg,
} }
} }

View File

@ -89,7 +89,7 @@ var (
indexTemplate = template.Must(template.New("index").Parse(indexSource)) indexTemplate = template.Must(template.New("index").Parse(indexSource))
//go:embed "example.html" //go:embed "example.html"
exampleSource string exampleSource string
//go:embed static //go:embed static
webStaticFs embed.FS webStaticFs embed.FS
@ -150,11 +150,14 @@ func createFirebaseSubscriber(conf *config.Config) (subscriber, error) {
_, err := msg.Send(context.Background(), &messaging.Message{ _, err := msg.Send(context.Background(), &messaging.Message{
Topic: m.Topic, Topic: m.Topic,
Data: map[string]string{ Data: map[string]string{
"id": m.ID, "id": m.ID,
"time": fmt.Sprintf("%d", m.Time), "time": fmt.Sprintf("%d", m.Time),
"event": m.Event, "event": m.Event,
"topic": m.Topic, "topic": m.Topic,
"message": m.Message, "priority": fmt.Sprintf("%d", m.Priority),
"tags": strings.Join(m.Tags, ","),
"title": m.Title,
"message": m.Message,
}, },
}) })
return err return err
@ -246,6 +249,10 @@ func (s *Server) handlePublish(w http.ResponseWriter, r *http.Request, v *visito
if m.Message == "" { if m.Message == "" {
return errHTTPBadRequest return errHTTPBadRequest
} }
title, priority, tags := parseHeaders(r.Header)
m.Title = title
m.Priority = priority
m.Tags = tags
if err := t.Publish(m); err != nil { if err := t.Publish(m); err != nil {
return err return err
} }
@ -262,6 +269,40 @@ func (s *Server) handlePublish(w http.ResponseWriter, r *http.Request, v *visito
return nil 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 { func (s *Server) handleSubscribeJSON(w http.ResponseWriter, r *http.Request, v *visitor) error {
encoder := func(msg *message) (string, error) { encoder := func(msg *message) (string, error) {
var buf bytes.Buffer var buf bytes.Buffer
@ -414,11 +455,11 @@ func (s *Server) topicFromID(id string) (*topic, error) {
return topics[0], nil return topics[0], nil
} }
func (s *Server) topicsFromIDs(ids... string) ([]*topic, error) { func (s *Server) topicsFromIDs(ids ...string) ([]*topic, error) {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
topics := make([]*topic, 0) topics := make([]*topic, 0)
for _, id := range ids { for _, id := range ids {
if _, ok := s.topics[id]; !ok { if _, ok := s.topics[id]; !ok {
if len(s.topics) >= s.config.GlobalTopicLimit { if len(s.topics) >= s.config.GlobalTopicLimit {
return nil, errHTTPTooManyRequests return nil, errHTTPTooManyRequests

View File

@ -69,6 +69,7 @@ code {
margin-top: 10px; margin-top: 10px;
margin-bottom: 20px; margin-bottom: 20px;
overflow-x: auto; overflow-x: auto;
white-space: nowrap;
} }
/* Lato font (OFL), https://fonts.google.com/specimen/Lato#about, /* Lato font (OFL), https://fonts.google.com/specimen/Lato#about,