mirror of
https://github.com/binwiederhier/ntfy.git
synced 2024-12-14 11:47:33 +00:00
More docs
This commit is contained in:
parent
c5ec3b48b4
commit
a779434bab
7 changed files with 1776 additions and 1045 deletions
838
docs/publish.md
838
docs/publish.md
|
@ -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)"
|
||||
```
|
||||
|
@ -899,40 +901,29 @@ 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,107 +1361,510 @@ 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:
|
||||
|
||||
=== "`view` action"
|
||||
``` json
|
||||
{
|
||||
"action": "view",
|
||||
"label": "Open bing.com",
|
||||
"url": "https://bing.com"
|
||||
}
|
||||
=== "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
|
||||
```
|
||||
|
||||
=== "`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"
|
||||
}
|
||||
}
|
||||
=== "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` action"
|
||||
``` json
|
||||
{
|
||||
"action": "http",
|
||||
"label": "Take picture",
|
||||
"method": "POST",
|
||||
"url": "https://homecam.lan/capture",
|
||||
"headers": {
|
||||
"Authorization": "..."
|
||||
},
|
||||
"body": "this is a message"
|
||||
}
|
||||
=== "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?
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
=== "Open a website"
|
||||
``` json
|
||||
{
|
||||
"action": "view",
|
||||
"label": "Open bing.com",
|
||||
"url": "https://bing.com"
|
||||
}
|
||||
=== "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..'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
=== "Open location in Google Maps"
|
||||
``` json
|
||||
{
|
||||
"action": "view",
|
||||
"label": "Show map",
|
||||
"url": "geo:0,0?q=1600+Amphitheatre+Parkway,+Mountain+View,+California"
|
||||
}
|
||||
=== "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)
|
||||
```
|
||||
|
||||
=== "Open a ntfy topic (deep link)"
|
||||
``` json
|
||||
=== "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
|
||||
|
||||
{
|
||||
"action": "view",
|
||||
"label": "Show stats",
|
||||
"url": "ntfy://ntfy.sh/stats"
|
||||
"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"
|
||||
}
|
||||
}
|
||||
=== "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.."
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
=== "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"
|
||||
}
|
||||
=== "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)
|
||||
```
|
||||
|
||||
=== "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
|
||||
```
|
||||
|
||||
=== "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.."
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
)
|
||||
```
|
||||
|
||||
=== "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.."
|
||||
]
|
||||
]
|
||||
]
|
||||
])
|
||||
]
|
||||
]));
|
||||
```
|
||||
|
||||
The `http` action supports the following fields:
|
||||
|
||||
| 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
|
||||
to a Zabbix alert or a transaction that you'd like to provide the deep-link for. Tapping the notification will open
|
||||
|
@ -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
27
go.mod
|
@ -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
35
go.sum
|
@ -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=
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
1869
web/package-lock.json
generated
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue