diff --git a/Makefile b/Makefile index 1894075c..a5513bad 100644 --- a/Makefile +++ b/Makefile @@ -206,7 +206,7 @@ release-check-tags: # Installing targets install-amd64: remove-binary - sudo cp -a dist/ntfy_amd64_linux_amd64/ntfy /usr/bin/ntfy + sudo cp -a dist/ntfy_amd64_linux_amd64_v1/ntfy /usr/bin/ntfy install-armv6: remove-binary sudo cp -a dist/ntfy_armv6_linux_armv6/ntfy /usr/bin/ntfy diff --git a/client/options.go b/client/options.go index 1d3b7ac2..7d599699 100644 --- a/client/options.go +++ b/client/options.go @@ -56,6 +56,12 @@ func WithClick(url string) PublishOption { return WithHeader("X-Click", url) } +// WithActions adds custom user actions to the notification. The value can be either a JSON array or the +// simple format definition. See https://ntfy.sh/docs/publish/#action-buttons for details. +func WithActions(value string) PublishOption { + return WithHeader("X-Actions", value) +} + // WithAttach sets a URL that will be used by the client to download an attachment func WithAttach(attach string) PublishOption { return WithHeader("X-Attach", attach) diff --git a/cmd/publish.go b/cmd/publish.go index 6a4c7f0b..e210308a 100644 --- a/cmd/publish.go +++ b/cmd/publish.go @@ -26,6 +26,7 @@ var cmdPublish = &cli.Command{ &cli.StringFlag{Name: "tags", Aliases: []string{"tag", "T"}, EnvVars: []string{"NTFY_TAGS"}, Usage: "comma separated list of tags and emojis"}, &cli.StringFlag{Name: "delay", Aliases: []string{"at", "in", "D"}, EnvVars: []string{"NTFY_DELAY"}, Usage: "delay/schedule message"}, &cli.StringFlag{Name: "click", Aliases: []string{"U"}, EnvVars: []string{"NTFY_CLICK"}, Usage: "URL to open when notification is clicked"}, + &cli.StringFlag{Name: "actions", Aliases: []string{"A"}, EnvVars: []string{"NTFY_ACTIONS"}, Usage: "actions JSON array or simple definition"}, &cli.StringFlag{Name: "attach", Aliases: []string{"a"}, EnvVars: []string{"NTFY_ATTACH"}, Usage: "URL to send as an external attachment"}, &cli.StringFlag{Name: "filename", Aliases: []string{"name", "n"}, EnvVars: []string{"NTFY_FILENAME"}, Usage: "filename for the attachment"}, &cli.StringFlag{Name: "file", Aliases: []string{"f"}, EnvVars: []string{"NTFY_FILE"}, Usage: "file to upload as an attachment"}, @@ -72,6 +73,7 @@ func execPublish(c *cli.Context) error { tags := c.String("tags") delay := c.String("delay") click := c.String("click") + actions := c.String("actions") attach := c.String("attach") filename := c.String("filename") file := c.String("file") @@ -112,6 +114,9 @@ func execPublish(c *cli.Context) error { if click != "" { options = append(options, client.WithClick(click)) } + if actions != "" { + options = append(options, client.WithActions(strings.ReplaceAll(actions, "\n", " "))) + } if attach != "" { options = append(options, client.WithAttach(attach)) } diff --git a/docs/publish.md b/docs/publish.md index 54d2642c..2948c4e4 100644 --- a/docs/publish.md +++ b/docs/publish.md @@ -789,21 +789,21 @@ The JSON message format closely mirrors the format of the message you can consum (see [JSON message format](subscribe/api.md#json-message-format) for details), but is not exactly identical. Here's an overview of all the supported fields: -| Field | Required | Type | Example | Description | -|------------|----------|----------------------------------|---------------------------------------|-----------------------------------------------------------------------| -| `topic` | ✔️ | *string* | `topic1` | Target topic name | -| `message` | - | *string* | `Some message` | Message body; set to `triggered` if empty or not passed | -| `title` | - | *string* | `Some title` | Message [title](#message-title) | -| `tags` | - | *string array* | `["tag1","tag2"]` | List of [tags](#tags-emojis) that may or not map to emojis | -| `priority` | - | *int (one of: 1, 2, 3, 4, or 5)* | `4` | Message [priority](#message-priority) with 1=min, 3=default and 5=max | -| `actions` | - | *JSON array* | *(see [user actions](#user-actions))* | Custom [user action buttons](#user-actions) for notifications | -| `click` | - | *URL* | `https://example.com` | Website opened when notification is [clicked](#click-action) | -| `attach` | - | *URL* | `https://example.com/file.jpg` | URL of an attachment, see [attach via URL](#attach-file-from-url) | -| `filename` | - | *string* | `file.jpg` | File name of the attachment | -| `delay` | - | *string* | `30min`, `9am` | Timestamp or duration for delayed delivery | -| `email` | - | *e-mail address* | `phil@example.com` | E-mail address for e-mail notifications | +| Field | Required | Type | Example | Description | +|------------|----------|----------------------------------|-------------------------------------------|-----------------------------------------------------------------------| +| `topic` | ✔️ | *string* | `topic1` | Target topic name | +| `message` | - | *string* | `Some message` | Message body; set to `triggered` if empty or not passed | +| `title` | - | *string* | `Some title` | Message [title](#message-title) | +| `tags` | - | *string array* | `["tag1","tag2"]` | List of [tags](#tags-emojis) that may or not map to emojis | +| `priority` | - | *int (one of: 1, 2, 3, 4, or 5)* | `4` | Message [priority](#message-priority) with 1=min, 3=default and 5=max | +| `actions` | - | *JSON array* | *(see [actiom buttons](#action-buttons))* | Custom [user action buttons](#action-buttons) for notifications | +| `click` | - | *URL* | `https://example.com` | Website opened when notification is [clicked](#click-action) | +| `attach` | - | *URL* | `https://example.com/file.jpg` | URL of an attachment, see [attach via URL](#attach-file-from-url) | +| `filename` | - | *string* | `file.jpg` | File name of the attachment | +| `delay` | - | *string* | `30min`, `9am` | Timestamp or duration for delayed delivery | +| `email` | - | *e-mail address* | `phil@example.com` | E-mail address for e-mail notifications | -## User actions +## Action buttons You can add action buttons to notifications to allow yourself to react to a notification directly. This is incredibly useful and has countless applications. As of today, the following actions are supported: @@ -812,6 +812,9 @@ useful and has countless applications. As of today, the following actions are su when the action button is tapped * [`http`](#send-http-request): Sends HTTP POST/GET/PUT request when the action button is tapped +To define the user actions, you can either pass the `actions` field as part of the JSON body (if you're +[publishing via JSON](#publish-as-json)), or use the `X-Actions` header (or any of its aliases: `Actions`, `Action`). + Here's an example of what that a notification with actions can look like:
@@ -819,12 +822,91 @@ Here's an example of what that a notification with actions can look like:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+Using the `X-Actions` header and the **simple format** (details see below), you can create the above notification like +this. This format is much easier to write, but less powerful: + +=== "Command line (curl)" + ``` + curl \ + -d "You left the house. Turn down the A/C?" \ + -H "Actions: view, Open portal, https://home.nest.com/; \ + http, Turn down, https://api.nest.com/device/XZ1D2, body=target_temp_f=65" \ + ntfy.sh/myhome + ``` + +=== "ntfy CLI" + ``` + ntfy publish \ + --actions="view, Open portal, https://home.nest.com/; \ + http, Turn down, https://api.nest.com/device/XZ1D2, body=target_temp_f=65" \ + myhome \ + "You left the house. Turn down the A/C?" + ``` + +=== "HTTP" + ``` http + POST /myhome HTTP/1.1 + Host: ntfy.sh + Actions: view, Open portal, https://home.nest.com/; http, Turn down, https://api.nest.com/device/XZ1D2, body=target_temp_f=65 + + You left the house. Turn down the A/C? + ``` + +=== "JavaScript" + ``` javascript + fetch('https://ntfy.sh/myhome', { + method: 'POST', + body: 'You left the house. Turn down the A/C?', + headers: { + 'Actions': 'view, Open portal, https://home.nest.com/; http, Turn down, https://api.nest.com/device/XZ1D2, body=target_temp_f=65' + } + }) + ``` + +=== "Go" + ``` go + req, _ := http.NewRequest("POST", "https://ntfy.sh/myhome", strings.NewReader("You left the house. Turn down the A/C?")) + req.Header.Set("Actions", "view, Open portal, https://home.nest.com/; http, Turn down, https://api.nest.com/device/XZ1D2, body=target_temp_f=65") + http.DefaultClient.Do(req) + ``` + +=== "PowerShell" + ``` powershell + $uri = "https://ntfy.sh/myhome" + $headers = @{ Actions="view, Open portal, https://home.nest.com/; http, Turn down, https://api.nest.com/device/XZ1D2, body=target_temp_f=65" } + $body = "You left the house. Turn down the A/C?" + Invoke-RestMethod -Method 'Post' -Uri $uri -Headers $headers -Body $body -UseBasicParsing + ``` + +=== "Python" + ``` python + requests.post("https://ntfy.sh/myhome", + data="You left the house. Turn down the A/C?", + headers={ "Actions": "view, Open portal, https://home.nest.com/; http, Turn down, https://api.nest.com/device/XZ1D2, body=target_temp_f=65" }) + ``` + +=== "PHP" + ``` php-inline + file_get_contents('https://ntfy.sh/reddit_alerts', false, stream_context_create([ + 'http' => [ + 'method' => 'POST', + 'header' => + "Content-Type: text/plain\r\n" . + "Actions: view, Open portal, https://home.nest.com/; http, Turn down, https://api.nest.com/device/XZ1D2, body=target_temp_f=65", + 'content' => 'You left the house. Turn down the A/C?' + ] + ])); + ``` + +Alternatively, you can define actions as **JSON array** (details see below), and pass them as part of the JSON body +(see [publish as JSON](#publish-as-json)): + === "Command line (curl)" ``` curl ntfy.sh \ -d '{ "topic": "myhome", - "message": "You seem to have left the house. Want to turn down the A/C?", + "message": "You left the house. Turn down the A/C?", "actions": [ { "action": "view", @@ -834,39 +916,54 @@ Here's an example of what that a notification with actions can look like: { "action": "http", "label": "Turn down", - "method": "POST", - "url": "https://developer-api.nest.com/devices/thermostats/XZA124D", - "headers": { - "Authorization": "Bearer ...", - "Content-Type": "application/json" - }, - "body": "{\"target_temperature_f\": 65}" - }, - { - "action": "broadcast", - "label": "Enter deep sleep 💤", - "extras": { - "command": "deepsleep" - } + "url": "https://api.nest.com/device/XZ1D2", + "body": "target_temp_f=65" } ] }' ``` +=== "ntfy CLI" + ``` + ntfy publish \ + --actions '[ + { + "action": "view", + "label": "Open portal", + "url": "https://home.nest.com/" + }, + { + "action": "http", + "label": "Turn down", + "url": "https://api.nest.com/device/XZ1D2", + "body": "target_temp_f=65" + } + ]' \ + myhome \ + "You left the house. Turn down the A/C?" + ``` + === "HTTP" ``` http POST / HTTP/1.1 Host: ntfy.sh { - "topic": "mytopic", - "message": "Disk space is low at 5.1 GB", - "title": "Low disk space alert", - "tags": ["warning","cd"], - "priority": 4, - "attach": "https://filesrv.lan/space.jpg", - "filename": "diskspace.jpg", - "click": "https://homecamera.lan/xasds1h2xsSsa/" + "topic": "myhome", + "message": "You left the house. Turn down the A/C?", + "actions": [ + { + "action": "view", + "label": "Open portal", + "url": "https://home.nest.com/" + }, + { + "action": "http", + "label": "Turn down", + "url": "https://api.nest.com/device/XZ1D2", + "body": "target_temp_f=65" + } + ] } ``` @@ -875,14 +972,21 @@ Here's an example of what that a notification with actions can look like: fetch('https://ntfy.sh', { method: 'POST', body: JSON.stringify({ - "topic": "mytopic", - "message": "Disk space is low at 5.1 GB", - "title": "Low disk space alert", - "tags": ["warning","cd"], - "priority": 4, - "attach": "https://filesrv.lan/space.jpg", - "filename": "diskspace.jpg", - "click": "https://homecamera.lan/xasds1h2xsSsa/" + topic: "myhome", + message": "You left the house. Turn down the A/C?", + actions: [ + { + action: "view", + label: "Open portal", + url: "https://home.nest.com/" + }, + { + action: "http", + label: "Turn down", + url: "https://api.nest.com/device/XZ1D2", + body: "target_temp_f=65" + } + ] }) }) ``` @@ -890,18 +994,24 @@ Here's an example of what that a notification with actions can look like: === "Go" ``` go // You should probably use json.Marshal() instead and make a proper struct, - // or even just use req.Header.Set() like in the other examples, but for the - // sake of the example, this is easier. + // but for the sake of the example, this is easier. body := `{ - "topic": "mytopic", - "message": "Disk space is low at 5.1 GB", - "title": "Low disk space alert", - "tags": ["warning","cd"], - "priority": 4, - "attach": "https://filesrv.lan/space.jpg", - "filename": "diskspace.jpg", - "click": "https://homecamera.lan/xasds1h2xsSsa/" + "topic": "myhome", + "message": "You left the house. Turn down the A/C?", + "actions": [ + { + "action": "view", + "label": "Open portal", + "url": "https://home.nest.com/" + }, + { + "action": "http", + "label": "Turn down", + "url": "https://api.nest.com/device/XZ1D2", + "body": "target_temp_f=65" + } + ] }` req, _ := http.NewRequest("POST", "https://ntfy.sh/", strings.NewReader(body)) http.DefaultClient.Do(req) @@ -911,15 +1021,22 @@ Here's an example of what that a notification with actions can look like: ``` powershell $uri = "https://ntfy.sh" $body = @{ - "topic"="powershell" - "title"="Low disk space alert" - "message"="Disk space is low at 5.1 GB" - "priority"=4 - "attach"="https://filesrv.lan/space.jpg" - "filename"="diskspace.jpg" - "tags"=@("warning","cd") - "click"= "https://homecamera.lan/xasds1h2xsSsa/" - } | ConvertTo-Json + "topic"="myhome" + "message"="You left the house. Turn down the A/C?" + "actions"=@( + @{ + "action"="view" + "label"="Open portal" + "url"="https://home.nest.com/" + }, + @{ + "action"="http", + "label"="Turn down" + "url"="https://api.nest.com/device/XZ1D2" + "body"="target_temp_f=65" + } + ) + } | ConvertTo-Json Invoke-RestMethod -Method 'Post' -Uri $uri -Body $body -ContentType "application/json" -UseBasicParsing ``` @@ -927,14 +1044,21 @@ Here's an example of what that a notification with actions can look like: ``` python requests.post("https://ntfy.sh/", data=json.dumps({ - "topic": "mytopic", - "message": "Disk space is low at 5.1 GB", - "title": "Low disk space alert", - "tags": ["warning","cd"], - "priority": 4, - "attach": "https://filesrv.lan/space.jpg", - "filename": "diskspace.jpg", - "click": "https://homecamera.lan/xasds1h2xsSsa/" + "topic": "myhome", + "message": "You left the house. Turn down the A/C?", + "actions": [ + { + "action": "view", + "label": "Open portal", + "url": "https://home.nest.com/" + }, + { + "action": "http", + "label": "Turn down", + "url": "https://api.nest.com/device/XZ1D2", + "body": "target_temp_f=65" + } + ] }) ) ``` @@ -946,31 +1070,57 @@ Here's an example of what that a notification with actions can look like: 'method' => 'POST', 'header' => "Content-Type: application/json", 'content' => json_encode([ - "topic": "mytopic", - "message": "Disk space is low at 5.1 GB", - "title": "Low disk space alert", - "tags": ["warning","cd"], - "priority": 4, - "attach": "https://filesrv.lan/space.jpg", - "filename": "diskspace.jpg", - "click": "https://homecamera.lan/xasds1h2xsSsa/" + "topic": "myhome", + "message": "You left the house. Turn down the A/C?", + "actions": [ + [ + "action": "view", + "label": "Open portal", + "url": "https://home.nest.com/" + ], + [ + "action": "http", + "label": "Turn down", + "url": "https://api.nest.com/device/XZ1D2", + "headers": [ + "Authorization": "Bearer ..." + ], + "body": "target_temp_f=65" + ] + ] ]) ] ])); ``` +**Simple format syntax:** + +Generally, the `X-Actions` header is formatted like this: +``` +Actions: ,