More docs

This commit is contained in:
Philipp Heckel 2022-04-21 09:58:28 -04:00
parent c5ec3b48b4
commit a779434bab
7 changed files with 1776 additions and 1045 deletions

View file

@ -821,11 +821,13 @@ Here's an example of what that a notification with actions can look like:
<figcaption>XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</figcaption>
</figure>
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`).
You can set up to three user actions in your notifications, using either of the following methods:
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**:
* In the `X-Actions` header, using the **simple format**
* As a **JSON array** in the `actions` key, when [publishing as JSON](#publish-as-json)
Using the `X-Actions` header (or any of its aliases: `Actions`, `Action`) and the **simple format** (details see below), you
can create the above notification like this.
=== "Command line (curl)"
```
@ -900,39 +902,28 @@ this. This format is much **easier to write, but less powerful**:
]));
```
The `X-Actions` header (including above-mentioned aliases) supports the following formats:
Here's the generic definition of the simple format:
=== "Simple format (long)"
```
X-Actions: action=<action>, label=<label>, param1=..., param2=..., ...
```
Simple format examples:
```
X-Actions: action=view, label=Play video, url=https://www.youtube.com/watch?v=EmL3lS0-Sr8
X-Actions: action=broadcast, label=Turn of flashlight, extras.cmd=flashlight-on
X-Actions: action=http, label=Change temperature, url=https://api.nest.com/device/XZ1D2, body=target_temp_f=65
action=<action1>, label=<label1>, paramN=...[; action=<action2>, label=<label2>, ...]
```
=== "Simple format (short)"
```
Actions: <action>, <label>, param1=..., param2=..., ...
<action1>, <label1>, paramN=...[; <action2>, <label2>, ...]
```
An `action` is either [`view`](#open-websiteapp), [`broadcast`](#send-android-broadcast), or [`http`](#send-http-request),
and the `label` defines the button text. The other parameters depend on the action itself. Please refer to this table
for all available parameters:
`action=` and `label=` are optional in all actions, and `url=` is optional in the `view` and `http` action.
| Field | Required | Type | Example | Applies to action | Description |
|-----------|----------|--------------------------------|-----------------------|-------------------|--------------------------------------------------------------|
| `action` | ✔️ | *view, broadcast, or http* | `view` | *all actions* | Action type |
| `label` | ✔️ | *string* | `Turn on light` | *all actions* | Label of the action button in the notification |
| `url` | - | *URL* | `https://example.com` | `view`, `http` | URL to open or send a HTTP request to |
| `method` | - | *HTTP method (GET, POST, ...)* | `GET` | `http` | HTTP method to use for HTTP request (**default is `POST`**!) |
| `headers` | - | *HTTP method (GET, POST, ...)* | `GET` | `http` | HTTP method to use for HTTP request (**default is `POST`**!) |
| `method` | - | *HTTP method (GET, POST, ...)* | `GET` | `http` | HTTP method to use for HTTP request (**default is `POST`**!) |
Simple format examples:
```
http, Change temp, https://api.nest.com/XZ1D2, body=target_temp=65
action=view, label=Open site, url=https://ntfy.sh; action=broadcast, label=Turn off, extras.cmd=turn-off
```
Alternatively, you can define actions as **JSON array** (details see below), and pass them as part of the JSON body
Alternatively, the same actions can be defined as **JSON array** (details see below), if the notification is defined as part of the JSON body
(see [publish as JSON](#publish-as-json)):
=== "Command line (curl)"
@ -1127,15 +1118,241 @@ Alternatively, you can define actions as **JSON array** (details see below), and
]));
```
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXx
### Open website/app
The `view` action opens a website or app when the action button is tapped, e.g. a browser, a Google Maps location, or
even a deep link into Twitter or a show ntfy topic.
XXXXXXXXXXXXXXXXXXx
Examples:
* `http://` or `https://` will open your browser (or an app if it registered for a URL)
* `mailto:` links will open your mail app, e.g. `mailto:phil@example.com`
* `geo:` links will open Google Maps, e.g. `geo:0,0?q=1600+Amphitheatre+Parkway,+Mountain+View,+CA`
* `ntfy://` links will open ntfy (see [ntfy:// links](subscribe/phone.md#ntfy-links)), e.g. `ntfy://ntfy.sh/stats`
* ...
Here's an example using the simple format:
=== "Command line (curl)"
```
curl \
-d "You left the house. Turn down the A/C?" \
-H "Actions: view, Open portal, https://home.nest.com/" \
ntfy.sh/myhome
```
=== "ntfy CLI"
```
ntfy publish \
--actions="view, Open portal, https://home.nest.com/" \
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/
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/'
}
})
```
=== "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.DefaultClient.Do(req)
```
=== "PowerShell"
``` powershell
$uri = "https://ntfy.sh/myhome"
$headers = @{ Actions="view, Open portal, https://home.nest.com/" }
$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/" })
```
=== "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/",
'content' => 'You left the house. Turn down the A/C?'
]
]));
```
And the same example using [JSON publishing](#publish-as-json):
=== "Command line (curl)"
```
curl ntfy.sh \
-d '{
"topic": "myhome",
"message": "You left the house. Turn down the A/C?",
"actions": [
{
"action": "view",
"label": "Open portal",
"url": "https://home.nest.com/"
}
]
}'
```
=== "ntfy CLI"
```
ntfy publish \
--actions '[
{
"action": "view",
"label": "Open portal",
"url": "https://home.nest.com/"
}
]' \
myhome \
"You left the house. Turn down the A/C?"
```
=== "HTTP"
``` http
POST / HTTP/1.1
Host: ntfy.sh
{
"topic": "myhome",
"message": "You left the house. Turn down the A/C?",
"actions": [
{
"action": "view",
"label": "Open portal",
"url": "https://home.nest.com/"
}
]
}
```
=== "JavaScript"
``` javascript
fetch('https://ntfy.sh', {
method: 'POST',
body: JSON.stringify({
topic: "myhome",
message": "You left the house. Turn down the A/C?",
actions: [
{
action: "view",
label: "Open portal",
url: "https://home.nest.com/"
}
]
})
})
```
=== "Go"
``` go
// You should probably use json.Marshal() instead and make a proper struct,
// but for the sake of the example, this is easier.
body := `{
"topic": "myhome",
"message": "You left the house. Turn down the A/C?",
"actions": [
{
"action": "view",
"label": "Open portal",
"url": "https://home.nest.com/"
}
]
}`
req, _ := http.NewRequest("POST", "https://ntfy.sh/", strings.NewReader(body))
http.DefaultClient.Do(req)
```
=== "PowerShell"
``` powershell
$uri = "https://ntfy.sh"
$body = @{
"topic"="myhome"
"message"="You left the house. Turn down the A/C?"
"actions"=@(
@{
"action"="view"
"label"="Open portal"
"url"="https://home.nest.com/"
}
)
} | ConvertTo-Json
Invoke-RestMethod -Method 'Post' -Uri $uri -Body $body -ContentType "application/json" -UseBasicParsing
```
=== "Python"
``` python
requests.post("https://ntfy.sh/",
data=json.dumps({
"topic": "myhome",
"message": "You left the house. Turn down the A/C?",
"actions": [
{
"action": "view",
"label": "Open portal",
"url": "https://home.nest.com/"
}
]
})
)
```
=== "PHP"
``` php-inline
file_get_contents('https://ntfy.sh/', false, stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/json",
'content' => json_encode([
"topic": "myhome",
"message": "You left the house. Turn down the A/C?",
"actions": [
[
"action": "view",
"label": "Open portal",
"url": "https://home.nest.com/"
]
]
])
]
]));
```
The `view` action supports the following fields:
| Field | Required | Type | Example | Description |
|----------|----------|----------|-----------------------|------------------------------------------------|
| `action` | ✔️ | *string* | `view` | Action type (**must be `view`**) |
| `label` | ✔️ | *string* | `Turn on light` | Label of the action button in the notification |
| `url` | ✔️ | *URL* | `https://example.com` | URL to open when action is tapped |
### Send Android broadcast
The `broadcast` action sends an [Android broadcast](https://developer.android.com/guide/components/broadcasts) intent
@ -1144,106 +1361,509 @@ or [Tasker](https://play.google.com/store/apps/details?id=net.dinglisch.android.
you can do everything your phone is capable of. Examples include taking pictures, launching/killing apps, change device
settings, write/read files, etc.
XXXXXXXXXXXXXXxx
Here's an example using the simple format:
=== "Command line (curl)"
```
curl \
-d "Your wife requested you send a picture of yourself." \
-H "Actions: broadcast, Take picture, extras.cmd=pic, extras.camera=front" \
ntfy.sh/wifey
```
=== "ntfy CLI"
```
ntfy publish \
--actions="broadcast, Take picture, extras.cmd=pic, extras.camera=front" \
wifey \
"Your wife requested you send a picture of yourself."
```
=== "HTTP"
``` http
POST /wifey HTTP/1.1
Host: ntfy.sh
Actions: broadcast, Take picture, extras.cmd=pic, extras.camera=front
Your wife requested you send a picture of yourself.
```
=== "JavaScript"
``` javascript
fetch('https://ntfy.sh/wifey', {
method: 'POST',
body: 'Your wife requested you send a picture of yourself.',
headers: {
'Actions': 'broadcast, Take picture, extras.cmd=pic, extras.camera=front'
}
})
```
=== "Go"
``` go
req, _ := http.NewRequest("POST", "https://ntfy.sh/wifey", strings.NewReader("Your wife requested you send a picture of yourself."))
req.Header.Set("Actions", "broadcast, Take picture, extras.cmd=pic, extras.camera=front")
http.DefaultClient.Do(req)
```
=== "PowerShell"
``` powershell
$uri = "https://ntfy.sh/wifey"
$headers = @{ Actions="broadcast, Take picture, extras.cmd=pic, extras.camera=front" }
$body = "Your wife requested you send a picture of yourself."
Invoke-RestMethod -Method 'Post' -Uri $uri -Headers $headers -Body $body -UseBasicParsing
```
=== "Python"
``` python
requests.post("https://ntfy.sh/wifey",
data="Your wife requested you send a picture of yourself.",
headers={ "Actions": "broadcast, Take picture, extras.cmd=pic, extras.camera=front" })
```
=== "PHP"
``` php-inline
file_get_contents('https://ntfy.sh/wifey', false, stream_context_create([
'http' => [
'method' => 'POST',
'header' =>
"Content-Type: text/plain\r\n" .
"Actions: broadcast, Take picture, extras.cmd=pic, extras.camera=front",
'content' => 'Your wife requested you send a picture of yourself.'
]
]));
```
And the same example using [JSON publishing](#publish-as-json):
=== "Command line (curl)"
```
curl ntfy.sh \
-d '{
"topic": "wifey",
"message": "Your wife requested you send a picture of yourself.",
"actions": [
{
"action": "broadcast",
"label": "Take picture",
"extras": {
"cmd": "pic",
"camera": "front"
}
}
]
}'
```
=== "ntfy CLI"
```
ntfy publish \
--actions '[
{
"action": "broadcast",
"label": "Take picture",
"extras": {
"cmd": "pic",
"camera": "front"
}
}
]' \
wifey \
"Your wife requested you send a picture of yourself."
```
=== "HTTP"
``` http
POST / HTTP/1.1
Host: ntfy.sh
{
"topic": "wifey",
"message": "Your wife requested you send a picture of yourself.",
"actions": [
{
"action": "broadcast",
"label": "Take picture",
"extras": {
"cmd": "pic",
"camera": "front"
}
}
]
}
```
=== "JavaScript"
``` javascript
fetch('https://ntfy.sh', {
method: 'POST',
body: JSON.stringify({
topic: "wifey",
message": "Your wife requested you send a picture of yourself.",
actions: [
{
"action": "broadcast",
"label": "Take picture",
"extras": {
"cmd": "pic",
"camera": "front"
}
}
]
})
})
```
=== "Go"
``` go
// You should probably use json.Marshal() instead and make a proper struct,
// but for the sake of the example, this is easier.
body := `{
"topic": "wifey",
"message": "Your wife requested you send a picture of yourself.",
"actions": [
{
"action": "broadcast",
"label": "Take picture",
"extras": {
"cmd": "pic",
"camera": "front"
}
}
]
}`
req, _ := http.NewRequest("POST", "https://ntfy.sh/", strings.NewReader(body))
http.DefaultClient.Do(req)
```
=== "PowerShell"
``` powershell
$uri = "https://ntfy.sh"
$body = @{
"topic"="wifey"
"message"="Your wife requested you send a picture of yourself."
"actions"=@(
@{
"action"="broadcast"
"label"="Take picture"
"extras"=@{
"cmd"="pic"
"camera"="front"
}
}
)
} | ConvertTo-Json
Invoke-RestMethod -Method 'Post' -Uri $uri -Body $body -ContentType "application/json" -UseBasicParsing
```
=== "Python"
``` python
requests.post("https://ntfy.sh/",
data=json.dumps({
"topic": "wifey",
"message": "Your wife requested you send a picture of yourself.",
"actions": [
{
"action": "broadcast",
"label": "Take picture",
"extras": {
"cmd": "pic",
"camera": "front"
}
}
]
})
)
```
=== "PHP"
``` php-inline
file_get_contents('https://ntfy.sh/', false, stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/json",
'content' => json_encode([
"topic": "wifey",
"message": "Your wife requested you send a picture of yourself.",
"actions": [
[
"action": "broadcast",
"label": "Take picture",
"extras": [
"cmd": "pic",
"camera": "front"
]
]
])
]
]));
```
The `broadcast` action supports the following fields:
| Field | Required | Type | Example | Description |
|----------|----------|------------------|-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `action` | ✔️ | *string* | `broadcast` | Action type (**must be `broadcast`**) |
| `label` | ✔️ | *string* | `Turn on light` | Label of the action button in the notification |
| `intent` | - | *string* | `com.example.AN_INTENT` | Android intent name, **default is `io.heckel.ntfy.USER_ACTION`** |
| `extras` | - | *map of strings* | *see above* | Android intent extras. Currently, only string extras are supported. When publishing as JSON, extras are passed as a map. When the simple format is used, use `extras.<param>=<value>`. |
### Send HTTP request
The `http` action sends a HTTP POST/GET/PUT request when the action button is tapped. You can use this to trigger REST APIs
for whatever systems you have, e.g. opening the garage door, or turning on/off lights.
XXXXXXXXXXXXXXXXXXXXx
Here's an example using the simple format:
=== "Command line (curl)"
```
curl \
-d "Garage door has been open for 15 minutes. Close it?" \
-H "Actions: http, Cloor door, https://mygarage.lan/close, headers.Authorization=Bearer zAzsx1sk.." \
ntfy.sh/myhome
```
=== "ntfy CLI"
```
ntfy publish \
--actions="http, Cloor door, https://mygarage.lan/close, headers.Authorization=Bearer zAzsx1sk.." \
myhome \
"Garage door has been open for 15 minutes. Close it?"
```
=== "HTTP"
``` http
POST /myhome HTTP/1.1
Host: ntfy.sh
Actions: http, Cloor door, https://mygarage.lan/close, headers.Authorization=Bearer zAzsx1sk..
Garage door has been open for 15 minutes. Close it?
```
=== "JavaScript"
``` javascript
fetch('https://ntfy.sh/myhome', {
method: 'POST',
body: 'Garage door has been open for 15 minutes. Close it?',
headers: {
'Actions': 'http, Cloor door, https://mygarage.lan/close, headers.Authorization=Bearer zAzsx1sk..'
}
})
```
=== "Go"
``` go
req, _ := http.NewRequest("POST", "https://ntfy.sh/myhome", strings.NewReader("Garage door has been open for 15 minutes. Close it?"))
req.Header.Set("Actions", "http, Cloor door, https://mygarage.lan/close, headers.Authorization=Bearer zAzsx1sk..")
http.DefaultClient.Do(req)
```
=== "PowerShell"
``` powershell
$uri = "https://ntfy.sh/myhome"
$headers = @{ Actions="http, Cloor door, https://mygarage.lan/close, headers.Authorization=Bearer zAzsx1sk.." }
$body = "Garage door has been open for 15 minutes. Close it?"
Invoke-RestMethod -Method 'Post' -Uri $uri -Headers $headers -Body $body -UseBasicParsing
```
=== "Python"
``` python
requests.post("https://ntfy.sh/myhome",
data="Garage door has been open for 15 minutes. Close it?",
headers={ "Actions": "http, Cloor door, https://mygarage.lan/close, headers.Authorization=Bearer zAzsx1sk.." })
```
=== "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: http, Cloor door, https://mygarage.lan/close, headers.Authorization=Bearer zAzsx1sk..",
'content' => 'Garage door has been open for 15 minutes. Close it?'
]
]));
```
And the same example using [JSON publishing](#publish-as-json):
=== "Command line (curl)"
```
curl ntfy.sh \
-d '{
"topic": "myhome",
"message": "Garage door has been open for 15 minutes. Close it?",
"actions": [
{
"action": "http",
"label": "Close door",
"url": "https://mygarage.lan/close",
"headers": {
"Authorization": "Bearer zAzsx1sk.."
}
}
]
}'
```
=== "ntfy CLI"
```
ntfy publish \
--actions '[
{
"action": "http",
"label": "Close door",
"url": "https://mygarage.lan/close",
"headers": {
"Authorization": "Bearer zAzsx1sk.."
}
}
]' \
myhome \
"Garage door has been open for 15 minutes. Close it?"
```
=== "HTTP"
``` http
POST / HTTP/1.1
Host: ntfy.sh
=== "`view` action"
``` json
{
"action": "view",
"label": "Open bing.com",
"url": "https://bing.com"
"topic": "myhome",
"message": "Garage door has been open for 15 minutes. Close it?",
"actions": [
{
"action": "http",
"label": "Close door",
"url": "https://mygarage.lan/close",
"headers": {
"Authorization": "Bearer zAzsx1sk.."
}
}
]
}
```
=== "`broadcast` action"
``` json
{
"action": "broadcast",
"label": "Send broadcast",
"intent": "io.heckel.ntfy.USER_ACTION",
"extras": {
"param": "this is a param",
"anotherparam": "this is another one"
}
}
=== "JavaScript"
``` javascript
fetch('https://ntfy.sh', {
method: 'POST',
body: JSON.stringify({
topic: "myhome",
message": "Garage door has been open for 15 minutes. Close it?",
actions: [
{
"action": "http",
"label": "Close door",
"url": "https://mygarage.lan/close",
"headers": {
"Authorization": "Bearer zAzsx1sk.."
}
}
]
})
})
```
=== "`http` action"
``` json
{
"action": "http",
"label": "Take picture",
"method": "POST",
"url": "https://homecam.lan/capture",
"headers": {
"Authorization": "..."
},
"body": "this is a message"
}
=== "Go"
``` go
// You should probably use json.Marshal() instead and make a proper struct,
// but for the sake of the example, this is easier.
body := `{
"topic": "myhome",
"message": "Garage door has been open for 15 minutes. Close it?",
"actions": [
{
"action": "http",
"label": "Close door",
"url": "https://mygarage.lan/close",
"headers": {
"Authorization": "Bearer zAzsx1sk.."
}
}
]
}`
req, _ := http.NewRequest("POST", "https://ntfy.sh/", strings.NewReader(body))
http.DefaultClient.Do(req)
```
Examples:
=== "Open a website"
``` json
{
"action": "view",
"label": "Open bing.com",
"url": "https://bing.com"
}
=== "PowerShell"
``` powershell
$uri = "https://ntfy.sh"
$body = @{
"topic"="myhome"
"message"="Garage door has been open for 15 minutes. Close it?"
"actions"=@(
@{
"action"="http",
"label"="Close door"
"url"="https://mygarage.lan/close"
"headers"=@{
"Authorization"="Bearer zAzsx1sk.."
}
}
}
)
} | ConvertTo-Json
Invoke-RestMethod -Method 'Post' -Uri $uri -Body $body -ContentType "application/json" -UseBasicParsing
```
=== "Open location in Google Maps"
``` json
{
"action": "view",
"label": "Show map",
"url": "geo:0,0?q=1600+Amphitheatre+Parkway,+Mountain+View,+California"
}
=== "Python"
``` python
requests.post("https://ntfy.sh/",
data=json.dumps({
"topic": "myhome",
"message": "Garage door has been open for 15 minutes. Close it?",
"actions": [
{
"action": "http",
"label": "Close door",
"url": "https://mygarage.lan/close",
"headers": {
"Authorization": "Bearer zAzsx1sk.."
}
}
]
})
)
```
=== "Open a ntfy topic (deep link)"
``` json
{
"action": "view",
"label": "Show stats",
"url": "ntfy://ntfy.sh/stats"
}
=== "PHP"
``` php-inline
file_get_contents('https://ntfy.sh/', false, stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/json",
'content' => json_encode([
"topic": "myhome",
"message": "Garage door has been open for 15 minutes. Close it?",
"actions": [
[
"action": "http",
"label": "Close door",
"url": "https://mygarage.lan/close",
"headers": [
"Authorization": "Bearer zAzsx1sk.."
]
]
]
])
]
]));
```
=== "Send broadcast"
``` json
{
"action": "broadcast",
"label": "Send broadcast",
"intent": "my.custom.intent",
"extras": {
"message": "whats up, hello"
}
}
```
The `http` action supports the following fields:
=== "Send a ntfy message"
``` json
{
"action": "http",
"label": "Send message",
"method": "POST",
"url": "http://ntfy.example.com/mytopic",
"headers": {
"Title": "another message",
"Tags": "tag1, tag2"
},
"body": "this is a message"
}
```
| Field | Required | Type | Example | Description |
|-----------|----------|--------------------|---------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------|
| `action` | ✔️ | *string* | `http` | Action type (**must be `http`**) |
| `label` | ✔️ | *string* | `Open garage door` | Label of the action button in the notification |
| `url` | ✔️ | *string* | `https://ntfy.sh/mytopic` | URL to which the HTTP request will be sent |
| `method` | - | *GET/POST/PUT/...* | `GET` | HTTP method to use for request, **default is POST (!)** |
| `headers` | - | *map of strings* | *see above* | HTTP headers to pass in request. When publishing as JSON, headers are passed as a map. When the simple format is used, use `headers.<header1>=<value>`. |
| `method` | - | *string* | `some body, somebody?` | HTTP body |
## Click action
You can define which URL to open when a notification is clicked. This may be useful if your notification is related
@ -1257,9 +1877,9 @@ by another app, the responsible app may open.
Examples:
* `http://` or `https://` will open your browser (or an app if it registered for a URL)
* `mailto:` links will open your mail app
* `geo:` links will open Google Maps (or your maps application)
* `ntfy://` links will open ntfy (see [ntfy:// links](subscribe/phone.md#ntfy-links))
* `mailto:` links will open your mail app, e.g. `mailto:phil@example.com`
* `geo:` links will open Google Maps, e.g. `geo:0,0?q=1600+Amphitheatre+Parkway,+Mountain+View,+CA`
* `ntfy://` links will open ntfy (see [ntfy:// links](subscribe/phone.md#ntfy-links)), e.g. `ntfy://ntfy.sh/stats`
* ...
Here's an example that will open Reddit when the notification is clicked:

27
go.mod
View file

@ -4,7 +4,7 @@ go 1.17
require (
cloud.google.com/go/firestore v1.6.1 // indirect
cloud.google.com/go/storage v1.21.0 // indirect
cloud.google.com/go/storage v1.22.0 // indirect
firebase.google.com/go v3.13.0+incompatible
github.com/BurntSushi/toml v1.1.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
@ -15,20 +15,20 @@ require (
github.com/olebedev/when v0.0.0-20211212231525-59bd4edcf9d6
github.com/stretchr/testify v1.7.0
github.com/urfave/cli/v2 v2.4.0
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a // indirect
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65
google.golang.org/api v0.74.0
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171
golang.org/x/time v0.0.0-20220411224347-583f2d630306
google.golang.org/api v0.75.0
gopkg.in/yaml.v2 v2.4.0
)
require github.com/pkg/errors v0.9.1
require (
cloud.google.com/go v0.100.2 // indirect
cloud.google.com/go/compute v1.5.0 // indirect
cloud.google.com/go v0.101.0 // indirect
cloud.google.com/go/compute v1.6.0 // indirect
cloud.google.com/go/iam v0.3.0 // indirect
github.com/AlekSi/pointer v1.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
@ -36,16 +36,17 @@ require (
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.7 // indirect
github.com/googleapis/gax-go/v2 v2.2.0 // indirect
github.com/googleapis/gax-go/v2 v2.3.0 // indirect
github.com/googleapis/go-type-adapters v1.0.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
go.opencensus.io v0.23.0 // indirect
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b // indirect
golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12 // indirect
golang.org/x/net v0.0.0-20220420153159-1850ba15e1be // indirect
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220405205423-9d709892a2bf // indirect
google.golang.org/genproto v0.0.0-20220420195807-44278fea765b // indirect
google.golang.org/grpc v1.45.0 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect

35
go.sum
View file

@ -29,6 +29,8 @@ cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2Z
cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U=
cloud.google.com/go v0.100.2 h1:t9Iw5QH5v4XtlEQaCtUY7x6sCABps8sW0acw7e2WQ6Y=
cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
cloud.google.com/go v0.101.0 h1:g+LL+JvpvdyGtcaD2xw2mSByE/6F9s471eJSoaysM84=
cloud.google.com/go v0.101.0/go.mod h1:hEiddgDb77jDQ+I80tURYNJEnuwPzFU8awCFFRLKjW0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
@ -40,6 +42,8 @@ cloud.google.com/go/compute v1.2.0/go.mod h1:xlogom/6gr8RJGBe7nT2eGsQYAFUbbv8dbC
cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
cloud.google.com/go/compute v1.5.0 h1:b1zWmYuuHz7gO9kDcM/EpHGr06UgsYNRpNJzI2kFiLM=
cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
cloud.google.com/go/compute v1.6.0 h1:XdQIN5mdPTSBVwSIVDuY5e8ZzVAccsHvD3qTEz4zIps=
cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.6.1 h1:8rBq3zRjnHx8UtBvaOWqBB1xq9jH6/wltfQLlTMh2Fw=
@ -58,6 +62,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.21.0 h1:HwnT2u2D309SFDHQII6m18HlrCi3jAXhUMTLOWXYH14=
cloud.google.com/go/storage v1.21.0/go.mod h1:XmRlxkgPjlBONznT2dDUU/5XlpU2OjMnKuqnZI01LAA=
cloud.google.com/go/storage v1.22.0 h1:NUV0NNp9nkBuW66BFRLuMgldN60C57ET3dhbwLIYio8=
cloud.google.com/go/storage v1.22.0/go.mod h1:GbaLEoMqbVm6sx3Z0R++gSiBlgMv6yUi2q1DeGFKQgE=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
firebase.google.com/go v3.13.0+incompatible h1:3TdYC3DDi6aHn20qoRkxwGqNgdjtblwVAyRLQwGn/+4=
firebase.google.com/go v3.13.0+incompatible/go.mod h1:xlah6XbEyW6tbfSklcfe5FHJIwjt8toICdV5Wh9ptHs=
@ -166,6 +172,8 @@ github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIG
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ=
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw=
github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
@ -188,6 +196,10 @@ github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pf
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
github.com/googleapis/gax-go/v2 v2.2.0 h1:s7jOdKSaksJVOxE0Y/S32otcfiP+UQ0cL8/GTKaONwE=
github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
github.com/googleapis/gax-go/v2 v2.3.0 h1:nRJtk3y8Fm770D42QV6T90ZnvFZyk7agSo3Q+Z9p3WI=
github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
github.com/googleapis/go-type-adapters v1.0.0 h1:9XdMn+d/G57qq1s8dNc5IesGCXHf6V2HZ2JwRxfA2tA=
github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
@ -248,6 +260,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o=
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -325,6 +339,9 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b h1:vI32FkLJNAWtGD4BwkThwEy6XS7ZLLMHkSkYfF8M0W0=
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220420153159-1850ba15e1be h1:yx80W7nvY5ySWpaU8UWaj5o9e23YgO9BRhQol7Lc+JI=
golang.org/x/net v0.0.0-20220420153159-1850ba15e1be/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -345,6 +362,8 @@ golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a h1:qfl7ob3DIEs3Ml9oLuPwY2N04gymzAW04WsUQHIClgM=
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 h1:OSnWWcOd/CtWQC2cYSBgbTSJv3ciqd8r54ySIW2y3RE=
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -414,9 +433,13 @@ golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12 h1:QyVthZKMsyaQwBTJE04jdNN0Pp5Fn9Qga0mrgxyERQM=
golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8=
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -432,6 +455,8 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs=
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w=
golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@ -488,6 +513,8 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U=
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@ -528,6 +555,8 @@ google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/S
google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
google.golang.org/api v0.74.0 h1:ExR2D+5TYIrMphWgs5JCgwRhEDlPDXXrLwHHMgPHTXE=
google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
google.golang.org/api v0.75.0 h1:0AYh/ae6l9TDUvIQrDw5QRpM100P6oHgD+o3dYHMzJg=
google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@ -575,6 +604,7 @@ google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
@ -613,6 +643,11 @@ google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
google.golang.org/genproto v0.0.0-20220405205423-9d709892a2bf h1:JTjwKJX9erVpsw17w+OIPP7iAgEkN/r8urhWSunEDTs=
google.golang.org/genproto v0.0.0-20220405205423-9d709892a2bf/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220420195807-44278fea765b h1:5zvsLqz9A1TKTeI6AhjJH/Vkaw0GGBs+D3GkvUUqNO0=
google.golang.org/genproto v0.0.0-20220420195807-44278fea765b/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=

View file

@ -90,7 +90,7 @@ const (
// Schema management queries
const (
currentSchemaVersion = 5
currentSchemaVersion = 6
createSchemaVersionTableQuery = `
CREATE TABLE IF NOT EXISTS schemaVersion (
id INT PRIMARY KEY,
@ -168,6 +168,11 @@ const (
ALTER TABLE messages_new RENAME TO messages;
COMMIT;
`
// 5 -> 6
migrate5To6AlterMessagesTableQuery = `
ALTER TABLE messages ADD COLUMN actions TEXT NOT NULL DEFAULT('');
`
)
type messageCache struct {
@ -509,6 +514,8 @@ func setupCacheDB(db *sql.DB) error {
return migrateFrom3(db)
} else if schemaVersion == 4 {
return migrateFrom4(db)
} else if schemaVersion == 5 {
return migrateFrom5(db)
}
return fmt.Errorf("unexpected schema version found: %d", schemaVersion)
}
@ -581,5 +588,16 @@ func migrateFrom4(db *sql.DB) error {
if _, err := db.Exec(updateSchemaVersion, 5); err != nil {
return err
}
return migrateFrom5(db)
}
func migrateFrom5(db *sql.DB) error {
log.Print("Migrating cache database schema: from 5 to 6")
if _, err := db.Exec(migrate5To6AlterMessagesTableQuery); err != nil {
return err
}
if _, err := db.Exec(updateSchemaVersion, 6); err != nil {
return err
}
return nil // Update this when a new version is added
}

View file

@ -3,7 +3,6 @@ package server
import (
"encoding/json"
"fmt"
"github.com/pkg/errors"
"heckel.io/ntfy/util"
"net/http"
"strings"
@ -96,7 +95,10 @@ func parseActionsFromSimple(s string) ([]*action, error) {
actions := make([]*action, 0)
rawActions := util.SplitNoEmpty(s, ";")
for _, rawAction := range rawActions {
newAction := &action{}
newAction := &action{
Headers: make(map[string]string),
Extras: make(map[string]string),
}
parts := util.SplitNoEmpty(rawAction, ",")
if len(parts) < 3 {
return nil, fmt.Errorf("cannot parse action: action requires at least keys 'action', 'label' and one parameter: %s", rawAction)
@ -109,6 +111,10 @@ func parseActionsFromSimple(s string) ([]*action, error) {
newAction.Label = value
} else if key == "" && util.InStringList([]string{"view", "http"}, newAction.Action) && i == 2 {
newAction.URL = value
} else if strings.HasPrefix(key, "headers.") {
newAction.Headers[strings.TrimPrefix(key, "headers.")] = value
} else if strings.HasPrefix(key, "extras.") {
newAction.Extras[strings.TrimPrefix(key, "extras.")] = value
} else if key != "" {
switch strings.ToLower(key) {
case "action":
@ -122,10 +128,10 @@ func parseActionsFromSimple(s string) ([]*action, error) {
case "body":
newAction.Body = value
default:
return nil, errors.Errorf("cannot parse action: key '%s' not supported, please use JSON format instead", part)
return nil, fmt.Errorf("cannot parse action: key '%s' not supported, please use JSON format instead", part)
}
} else {
return nil, errors.Errorf("cannot parse action: unknown phrase '%s'", part)
return nil, fmt.Errorf("cannot parse action: unknown phrase '%s'", part)
}
}
actions = append(actions, newAction)

View file

@ -61,4 +61,22 @@ func TestParseActions(t *testing.T) {
require.Equal(t, "https://door.lan/open", actions[0].URL)
require.Equal(t, "PUT", actions[0].Method)
require.Equal(t, "this is a body", actions[0].Body)
actions, err = parseActions("action=broadcast, label=Do a thing, extras.command=some command, extras.some_param=a parameter")
require.Nil(t, err)
require.Equal(t, 1, len(actions))
require.Equal(t, "broadcast", actions[0].Action)
require.Equal(t, "Do a thing", actions[0].Label)
require.Equal(t, 2, len(actions[0].Extras))
require.Equal(t, "some command", actions[0].Extras["command"])
require.Equal(t, "a parameter", actions[0].Extras["some_param"])
actions, err = parseActions("action=http, label=Send request, url=http://example.com, method=GET, headers.Content-Type=application/json, headers.Authorization=Basic sdasffsf")
require.Nil(t, err)
require.Equal(t, 1, len(actions))
require.Equal(t, "http", actions[0].Action)
require.Equal(t, "Send request", actions[0].Label)
require.Equal(t, 2, len(actions[0].Headers))
require.Equal(t, "application/json", actions[0].Headers["Content-Type"])
require.Equal(t, "Basic sdasffsf", actions[0].Headers["Authorization"])
}

1869
web/package-lock.json generated

File diff suppressed because it is too large Load diff