notification icons
parent
cbcd0e3f0d
commit
d519fd999b
|
@ -47,6 +47,7 @@ type Message struct { // TODO combine with server.message
|
||||||
Priority int
|
Priority int
|
||||||
Tags []string
|
Tags []string
|
||||||
Click string
|
Click string
|
||||||
|
Icon *Icon
|
||||||
Attachment *Attachment
|
Attachment *Attachment
|
||||||
|
|
||||||
// Additional fields
|
// Additional fields
|
||||||
|
@ -65,6 +66,13 @@ type Attachment struct {
|
||||||
Owner string `json:"-"` // IP address of uploader, used for rate limiting
|
Owner string `json:"-"` // IP address of uploader, used for rate limiting
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Icon represents a message icon
|
||||||
|
type Icon struct {
|
||||||
|
Url string `json:"url"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Size int64 `json:"size,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type subscription struct {
|
type subscription struct {
|
||||||
ID string
|
ID string
|
||||||
topicURL string
|
topicURL string
|
||||||
|
|
|
@ -56,6 +56,11 @@ func WithClick(url string) PublishOption {
|
||||||
return WithHeader("X-Click", url)
|
return WithHeader("X-Click", url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithIcon makes the notification use the given URL as its icon
|
||||||
|
func WithIcon(icon string) PublishOption {
|
||||||
|
return WithHeader("X-Icon", icon)
|
||||||
|
}
|
||||||
|
|
||||||
// WithActions adds custom user actions to the notification. The value can be either a JSON array or the
|
// WithActions adds custom user actions to the notification. The value can be either a JSON array or the
|
||||||
// simple format definition. See https://ntfy.sh/docs/publish/#action-buttons for details.
|
// simple format definition. See https://ntfy.sh/docs/publish/#action-buttons for details.
|
||||||
func WithActions(value string) PublishOption {
|
func WithActions(value string) PublishOption {
|
||||||
|
|
|
@ -28,6 +28,7 @@ var flagsPublish = append(
|
||||||
&cli.StringFlag{Name: "tags", Aliases: []string{"tag", "T"}, EnvVars: []string{"NTFY_TAGS"}, Usage: "comma separated list of tags and emojis"},
|
&cli.StringFlag{Name: "tags", Aliases: []string{"tag", "T"}, EnvVars: []string{"NTFY_TAGS"}, Usage: "comma separated list of tags and emojis"},
|
||||||
&cli.StringFlag{Name: "delay", Aliases: []string{"at", "in", "D"}, EnvVars: []string{"NTFY_DELAY"}, Usage: "delay/schedule message"},
|
&cli.StringFlag{Name: "delay", Aliases: []string{"at", "in", "D"}, EnvVars: []string{"NTFY_DELAY"}, Usage: "delay/schedule message"},
|
||||||
&cli.StringFlag{Name: "click", Aliases: []string{"U"}, EnvVars: []string{"NTFY_CLICK"}, Usage: "URL to open when notification is clicked"},
|
&cli.StringFlag{Name: "click", Aliases: []string{"U"}, EnvVars: []string{"NTFY_CLICK"}, Usage: "URL to open when notification is clicked"},
|
||||||
|
&cli.StringFlag{Name: "icon", Aliases: []string{"i"}, EnvVars: []string{"NTFY_ICON"}, Usage: "URL to use as notification icon"},
|
||||||
&cli.StringFlag{Name: "actions", Aliases: []string{"A"}, EnvVars: []string{"NTFY_ACTIONS"}, Usage: "actions JSON array or simple definition"},
|
&cli.StringFlag{Name: "actions", Aliases: []string{"A"}, EnvVars: []string{"NTFY_ACTIONS"}, Usage: "actions JSON array or simple definition"},
|
||||||
&cli.StringFlag{Name: "attach", Aliases: []string{"a"}, EnvVars: []string{"NTFY_ATTACH"}, Usage: "URL to send as an external attachment"},
|
&cli.StringFlag{Name: "attach", Aliases: []string{"a"}, EnvVars: []string{"NTFY_ATTACH"}, Usage: "URL to send as an external attachment"},
|
||||||
&cli.StringFlag{Name: "filename", Aliases: []string{"name", "n"}, EnvVars: []string{"NTFY_FILENAME"}, Usage: "filename for the attachment"},
|
&cli.StringFlag{Name: "filename", Aliases: []string{"name", "n"}, EnvVars: []string{"NTFY_FILENAME"}, Usage: "filename for the attachment"},
|
||||||
|
@ -64,6 +65,7 @@ Examples:
|
||||||
ntfy pub --at=8:30am delayed_topic Laterzz # Send message at 8:30am
|
ntfy pub --at=8:30am delayed_topic Laterzz # Send message at 8:30am
|
||||||
ntfy pub -e phil@example.com alerts 'App is down!' # Also send email to phil@example.com
|
ntfy pub -e phil@example.com alerts 'App is down!' # Also send email to phil@example.com
|
||||||
ntfy pub --click="https://reddit.com" redd 'New msg' # Opens Reddit when notification is clicked
|
ntfy pub --click="https://reddit.com" redd 'New msg' # Opens Reddit when notification is clicked
|
||||||
|
ntfy pub --icon="http://some.tld/icon.png" 'Icon!' # Send notification with custom icon
|
||||||
ntfy pub --attach="http://some.tld/file.zip" files # Send ZIP archive from URL as attachment
|
ntfy pub --attach="http://some.tld/file.zip" files # Send ZIP archive from URL as attachment
|
||||||
ntfy pub --file=flower.jpg flowers 'Nice!' # Send image.jpg as attachment
|
ntfy pub --file=flower.jpg flowers 'Nice!' # Send image.jpg as attachment
|
||||||
ntfy pub -u phil:mypass secret Psst # Publish with username/password
|
ntfy pub -u phil:mypass secret Psst # Publish with username/password
|
||||||
|
@ -90,6 +92,7 @@ func execPublish(c *cli.Context) error {
|
||||||
tags := c.String("tags")
|
tags := c.String("tags")
|
||||||
delay := c.String("delay")
|
delay := c.String("delay")
|
||||||
click := c.String("click")
|
click := c.String("click")
|
||||||
|
icon := c.String("icon")
|
||||||
actions := c.String("actions")
|
actions := c.String("actions")
|
||||||
attach := c.String("attach")
|
attach := c.String("attach")
|
||||||
filename := c.String("filename")
|
filename := c.String("filename")
|
||||||
|
@ -120,6 +123,9 @@ func execPublish(c *cli.Context) error {
|
||||||
if click != "" {
|
if click != "" {
|
||||||
options = append(options, client.WithClick(click))
|
options = append(options, client.WithClick(click))
|
||||||
}
|
}
|
||||||
|
if icon != "" {
|
||||||
|
options = append(options, client.WithIcon(icon))
|
||||||
|
}
|
||||||
if actions != "" {
|
if actions != "" {
|
||||||
options = append(options, client.WithActions(strings.ReplaceAll(actions, "\n", " ")))
|
options = append(options, client.WithActions(strings.ReplaceAll(actions, "\n", " ")))
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,7 @@ func TestCLI_Publish_All_The_Things(t *testing.T) {
|
||||||
"--tags", "tag1,tag2",
|
"--tags", "tag1,tag2",
|
||||||
// No --delay, --email
|
// No --delay, --email
|
||||||
"--click", "https://ntfy.sh",
|
"--click", "https://ntfy.sh",
|
||||||
|
"--icon", "https://ntfy.sh/static/img/ntfy.png",
|
||||||
"--attach", "https://f-droid.org/F-Droid.apk",
|
"--attach", "https://f-droid.org/F-Droid.apk",
|
||||||
"--filename", "fdroid.apk",
|
"--filename", "fdroid.apk",
|
||||||
"--no-cache",
|
"--no-cache",
|
||||||
|
|
|
@ -2349,6 +2349,84 @@ Here's an example showing how to attach an APK file:
|
||||||
<figcaption>File attachment sent from an external URL</figcaption>
|
<figcaption>File attachment sent from an external URL</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
|
|
||||||
|
## Icons
|
||||||
|
_Supported on:_ :material-android:
|
||||||
|
|
||||||
|
You can include an icon that will appear next to the text of the notification. Simply pass the `X-Icon` header or query
|
||||||
|
parameter (or its alias `Icon`) to specify the URL that the icon is located at. The client will automatically download
|
||||||
|
the icon (up to 300KB) and show it in the notification. Only jpeg and png images are supported at this time.
|
||||||
|
|
||||||
|
Here's an example showing how to include an icon:
|
||||||
|
|
||||||
|
=== "Command line (curl)"
|
||||||
|
```
|
||||||
|
curl \
|
||||||
|
-X POST \
|
||||||
|
-H "Icon: https://ntfy.sh/docs/static/img/ntfy.png" \
|
||||||
|
ntfy.sh/customIcons
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "ntfy CLI"
|
||||||
|
```
|
||||||
|
ntfy publish \
|
||||||
|
--icon="https://ntfy.sh/docs/static/img/ntfy.png" \
|
||||||
|
customIcons
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "HTTP"
|
||||||
|
``` http
|
||||||
|
POST /customIcons HTTP/1.1
|
||||||
|
Host: ntfy.sh
|
||||||
|
Icon: https://ntfy.sh/docs/static/img/ntfy.png
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "JavaScript"
|
||||||
|
``` javascript
|
||||||
|
fetch('https://ntfy.sh/customIcons', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Icon': 'https://ntfy.sh/docs/static/img/ntfy.png' }
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Go"
|
||||||
|
``` go
|
||||||
|
req, _ := http.NewRequest("POST", "https://ntfy.sh/customIcons", file)
|
||||||
|
req.Header.Set("Icon", "https://ntfy.sh/docs/static/img/ntfy.png")
|
||||||
|
http.DefaultClient.Do(req)
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "PowerShell"
|
||||||
|
``` powershell
|
||||||
|
$uri = "https://ntfy.sh/customIcons"
|
||||||
|
$headers = @{ Icon="https://ntfy.sh/docs/static/img/ntfy.png" }
|
||||||
|
Invoke-RestMethod -Method 'Post' -Uri $uri -Headers $headers -UseBasicParsing
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Python"
|
||||||
|
``` python
|
||||||
|
requests.put("https://ntfy.sh/customIcons",
|
||||||
|
headers={ "Icon": "https://ntfy.sh/docs/static/img/ntfy.png" })
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "PHP"
|
||||||
|
``` php-inline
|
||||||
|
file_get_contents('https://ntfy.sh/customIcons', false, stream_context_create([
|
||||||
|
'http' => [
|
||||||
|
'method' => 'PUT',
|
||||||
|
'header' =>
|
||||||
|
"Content-Type: text/plain\r\n" . // Does not matter
|
||||||
|
"Icon: https://ntfy.sh/docs/static/img/ntfy.png",
|
||||||
|
]
|
||||||
|
]));
|
||||||
|
```
|
||||||
|
|
||||||
|
Here's an example of how it will look on Android:
|
||||||
|
|
||||||
|
<figure markdown>
|
||||||
|
![file attachment](static/img/android-screenshot-icon.png){ width=500 }
|
||||||
|
<figcaption>Custom icon from an external URL</figcaption>
|
||||||
|
</figure>
|
||||||
|
|
||||||
## E-mail notifications
|
## E-mail notifications
|
||||||
_Supported on:_ :material-android: :material-apple: :material-firefox:
|
_Supported on:_ :material-android: :material-apple: :material-firefox:
|
||||||
|
|
||||||
|
@ -2804,6 +2882,7 @@ and can be passed as **HTTP headers** or **query parameters in the URL**. They a
|
||||||
| `X-Actions` | `Actions`, `Action` | JSON array or short format of [user actions](#action-buttons) |
|
| `X-Actions` | `Actions`, `Action` | JSON array or short format of [user actions](#action-buttons) |
|
||||||
| `X-Click` | `Click` | URL to open when [notification is clicked](#click-action) |
|
| `X-Click` | `Click` | URL to open when [notification is clicked](#click-action) |
|
||||||
| `X-Attach` | `Attach`, `a` | URL to send as an [attachment](#attachments), as an alternative to PUT/POST-ing an attachment |
|
| `X-Attach` | `Attach`, `a` | URL to send as an [attachment](#attachments), as an alternative to PUT/POST-ing an attachment |
|
||||||
|
| `X-Icon` | `Icon` | URL to use as notification [icon](#icons) |
|
||||||
| `X-Filename` | `Filename`, `file`, `f` | Optional [attachment](#attachments) filename, as it appears in the client |
|
| `X-Filename` | `Filename`, `file`, `f` | Optional [attachment](#attachments) filename, as it appears in the client |
|
||||||
| `X-Email` | `X-E-Mail`, `Email`, `E-Mail`, `mail`, `e` | E-mail address for [e-mail notifications](#e-mail-notifications) |
|
| `X-Email` | `X-E-Mail`, `Email`, `E-Mail`, `mail`, `e` | E-mail address for [e-mail notifications](#e-mail-notifications) |
|
||||||
| `X-Cache` | `Cache` | Allows disabling [message caching](#message-caching) |
|
| `X-Cache` | `Cache` | Allows disabling [message caching](#message-caching) |
|
||||||
|
|
|
@ -13,6 +13,7 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
|
||||||
* Polling is now done with `since=<id>` API, which makes deduping easier ([#165](https://github.com/binwiederhier/ntfy/issues/165))
|
* Polling is now done with `since=<id>` API, which makes deduping easier ([#165](https://github.com/binwiederhier/ntfy/issues/165))
|
||||||
* Turned JSON stream deprecation banner into "Use WebSockets" banner (no ticket)
|
* Turned JSON stream deprecation banner into "Use WebSockets" banner (no ticket)
|
||||||
* Move action buttons in notification cards ([#236](https://github.com/binwiederhier/ntfy/issues/236), thanks to [@wunter8](https://github.com/wunter8))
|
* Move action buttons in notification cards ([#236](https://github.com/binwiederhier/ntfy/issues/236), thanks to [@wunter8](https://github.com/wunter8))
|
||||||
|
* Icons can be set for each individual notification ([#126](https://github.com/binwiederhier/ntfy/issues/126), thanks to [@wunter8](https://github.com/wunter8))
|
||||||
|
|
||||||
**Bugs:**
|
**Bugs:**
|
||||||
|
|
||||||
|
@ -41,12 +42,12 @@ Thank you to [@wunter8](https://github.com/wunter8) for proactively picking up s
|
||||||
* `ntfy user` commands don't work with `auth_file` but works with `auth-file` ([#344](https://github.com/binwiederhier/ntfy/issues/344), thanks to [@Histalek](https://github.com/Histalek) for reporting)
|
* `ntfy user` commands don't work with `auth_file` but works with `auth-file` ([#344](https://github.com/binwiederhier/ntfy/issues/344), thanks to [@Histalek](https://github.com/Histalek) for reporting)
|
||||||
* Ignore new draft HTTP `Priority` header ([#351](https://github.com/binwiederhier/ntfy/issues/351), thanks to [@ksurl](https://github.com/ksurl) for reporting)
|
* Ignore new draft HTTP `Priority` header ([#351](https://github.com/binwiederhier/ntfy/issues/351), thanks to [@ksurl](https://github.com/ksurl) for reporting)
|
||||||
* Delete expired attachments based on mod time instead of DB entry to avoid races (no ticket)
|
* Delete expired attachments based on mod time instead of DB entry to avoid races (no ticket)
|
||||||
|
* Icons can be set for each individual notification ([#126](https://github.com/binwiederhier/ntfy/issues/126), thanks to [@wunter8](https://github.com/wunter8))
|
||||||
|
|
||||||
**Documentation:**
|
**Documentation:**
|
||||||
|
|
||||||
* Fix some PowerShell publish docs ([#345](https://github.com/binwiederhier/ntfy/pull/345), thanks to [@noahpeltier](https://github.com/noahpeltier))
|
* Fix some PowerShell publish docs ([#345](https://github.com/binwiederhier/ntfy/pull/345), thanks to [@noahpeltier](https://github.com/noahpeltier))
|
||||||
|
|
||||||
-->
|
|
||||||
|
|
||||||
## ntfy server v1.27.2
|
## ntfy server v1.27.2
|
||||||
Released June 23, 2022
|
Released June 23, 2022
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
|
@ -52,6 +52,7 @@ var (
|
||||||
errHTTPBadRequestActionsInvalid = &errHTTP{40018, http.StatusBadRequest, "invalid request: actions invalid", "https://ntfy.sh/docs/publish/#action-buttons"}
|
errHTTPBadRequestActionsInvalid = &errHTTP{40018, http.StatusBadRequest, "invalid request: actions invalid", "https://ntfy.sh/docs/publish/#action-buttons"}
|
||||||
errHTTPBadRequestMatrixMessageInvalid = &errHTTP{40019, http.StatusBadRequest, "invalid request: Matrix JSON invalid", "https://ntfy.sh/docs/publish/#matrix-gateway"}
|
errHTTPBadRequestMatrixMessageInvalid = &errHTTP{40019, http.StatusBadRequest, "invalid request: Matrix JSON invalid", "https://ntfy.sh/docs/publish/#matrix-gateway"}
|
||||||
errHTTPBadRequestMatrixPushkeyBaseURLMismatch = &errHTTP{40020, http.StatusBadRequest, "invalid request: push key must be prefixed with base URL", "https://ntfy.sh/docs/publish/#matrix-gateway"}
|
errHTTPBadRequestMatrixPushkeyBaseURLMismatch = &errHTTP{40020, http.StatusBadRequest, "invalid request: push key must be prefixed with base URL", "https://ntfy.sh/docs/publish/#matrix-gateway"}
|
||||||
|
errHTTPBadRequestIconURLInvalid = &errHTTP{40021, http.StatusBadRequest, "invalid request: icon URL is invalid", "https://ntfy.sh/docs/publish/#icons"}
|
||||||
errHTTPNotFound = &errHTTP{40401, http.StatusNotFound, "page not found", ""}
|
errHTTPNotFound = &errHTTP{40401, http.StatusNotFound, "page not found", ""}
|
||||||
errHTTPUnauthorized = &errHTTP{40101, http.StatusUnauthorized, "unauthorized", "https://ntfy.sh/docs/publish/#authentication"}
|
errHTTPUnauthorized = &errHTTP{40101, http.StatusUnauthorized, "unauthorized", "https://ntfy.sh/docs/publish/#authentication"}
|
||||||
errHTTPForbidden = &errHTTP{40301, http.StatusForbidden, "forbidden", "https://ntfy.sh/docs/publish/#authentication"}
|
errHTTPForbidden = &errHTTP{40301, http.StatusForbidden, "forbidden", "https://ntfy.sh/docs/publish/#authentication"}
|
||||||
|
|
|
@ -38,44 +38,47 @@ const (
|
||||||
attachment_url TEXT NOT NULL,
|
attachment_url TEXT NOT NULL,
|
||||||
sender TEXT NOT NULL,
|
sender TEXT NOT NULL,
|
||||||
encoding TEXT NOT NULL,
|
encoding TEXT NOT NULL,
|
||||||
published INT NOT NULL
|
published INT NOT NULL,
|
||||||
|
icon_url TEXT NOT NULL,
|
||||||
|
icon_type TEXT NOT NULL,
|
||||||
|
icon_size INT NOT NULL
|
||||||
);
|
);
|
||||||
CREATE INDEX IF NOT EXISTS idx_mid ON messages (mid);
|
CREATE INDEX IF NOT EXISTS idx_mid ON messages (mid);
|
||||||
CREATE INDEX IF NOT EXISTS idx_topic ON messages (topic);
|
CREATE INDEX IF NOT EXISTS idx_topic ON messages (topic);
|
||||||
COMMIT;
|
COMMIT;
|
||||||
`
|
`
|
||||||
insertMessageQuery = `
|
insertMessageQuery = `
|
||||||
INSERT INTO messages (mid, time, topic, message, title, priority, tags, click, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, encoding, published)
|
INSERT INTO messages (mid, time, topic, message, title, priority, tags, click, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, encoding, published, icon_url, icon_type, icon_size)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
`
|
`
|
||||||
pruneMessagesQuery = `DELETE FROM messages WHERE time < ? AND published = 1`
|
pruneMessagesQuery = `DELETE FROM messages WHERE time < ? AND published = 1`
|
||||||
selectRowIDFromMessageID = `SELECT id FROM messages WHERE mid = ?` // Do not include topic, see #336 and TestServer_PollSinceID_MultipleTopics
|
selectRowIDFromMessageID = `SELECT id FROM messages WHERE mid = ?` // Do not include topic, see #336 and TestServer_PollSinceID_MultipleTopics
|
||||||
selectMessagesSinceTimeQuery = `
|
selectMessagesSinceTimeQuery = `
|
||||||
SELECT mid, time, topic, message, title, priority, tags, click, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, encoding
|
SELECT mid, time, topic, message, title, priority, tags, click, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, encoding, icon_url, icon_type, icon_size
|
||||||
FROM messages
|
FROM messages
|
||||||
WHERE topic = ? AND time >= ? AND published = 1
|
WHERE topic = ? AND time >= ? AND published = 1
|
||||||
ORDER BY time, id
|
ORDER BY time, id
|
||||||
`
|
`
|
||||||
selectMessagesSinceTimeIncludeScheduledQuery = `
|
selectMessagesSinceTimeIncludeScheduledQuery = `
|
||||||
SELECT mid, time, topic, message, title, priority, tags, click, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, encoding
|
SELECT mid, time, topic, message, title, priority, tags, click, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, encoding, icon_url, icon_type, icon_size
|
||||||
FROM messages
|
FROM messages
|
||||||
WHERE topic = ? AND time >= ?
|
WHERE topic = ? AND time >= ?
|
||||||
ORDER BY time, id
|
ORDER BY time, id
|
||||||
`
|
`
|
||||||
selectMessagesSinceIDQuery = `
|
selectMessagesSinceIDQuery = `
|
||||||
SELECT mid, time, topic, message, title, priority, tags, click, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, encoding
|
SELECT mid, time, topic, message, title, priority, tags, click, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, encoding, icon_url, icon_type, icon_size
|
||||||
FROM messages
|
FROM messages
|
||||||
WHERE topic = ? AND id > ? AND published = 1
|
WHERE topic = ? AND id > ? AND published = 1
|
||||||
ORDER BY time, id
|
ORDER BY time, id
|
||||||
`
|
`
|
||||||
selectMessagesSinceIDIncludeScheduledQuery = `
|
selectMessagesSinceIDIncludeScheduledQuery = `
|
||||||
SELECT mid, time, topic, message, title, priority, tags, click, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, encoding
|
SELECT mid, time, topic, message, title, priority, tags, click, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, encoding, icon_url, icon_type, icon_size
|
||||||
FROM messages
|
FROM messages
|
||||||
WHERE topic = ? AND (id > ? OR published = 0)
|
WHERE topic = ? AND (id > ? OR published = 0)
|
||||||
ORDER BY time, id
|
ORDER BY time, id
|
||||||
`
|
`
|
||||||
selectMessagesDueQuery = `
|
selectMessagesDueQuery = `
|
||||||
SELECT mid, time, topic, message, title, priority, tags, click, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, encoding
|
SELECT mid, time, topic, message, title, priority, tags, click, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, encoding, icon_url, icon_type, icon_size
|
||||||
FROM messages
|
FROM messages
|
||||||
WHERE time <= ? AND published = 0
|
WHERE time <= ? AND published = 0
|
||||||
ORDER BY time, id
|
ORDER BY time, id
|
||||||
|
@ -89,7 +92,7 @@ const (
|
||||||
|
|
||||||
// Schema management queries
|
// Schema management queries
|
||||||
const (
|
const (
|
||||||
currentSchemaVersion = 7
|
currentSchemaVersion = 8
|
||||||
createSchemaVersionTableQuery = `
|
createSchemaVersionTableQuery = `
|
||||||
CREATE TABLE IF NOT EXISTS schemaVersion (
|
CREATE TABLE IF NOT EXISTS schemaVersion (
|
||||||
id INT PRIMARY KEY,
|
id INT PRIMARY KEY,
|
||||||
|
@ -177,6 +180,13 @@ const (
|
||||||
migrate6To7AlterMessagesTableQuery = `
|
migrate6To7AlterMessagesTableQuery = `
|
||||||
ALTER TABLE messages RENAME COLUMN attachment_owner TO sender;
|
ALTER TABLE messages RENAME COLUMN attachment_owner TO sender;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
// 7 -> 8
|
||||||
|
migrate7To8AlterMessagesTableQuery = `
|
||||||
|
ALTER TABLE messages ADD COLUMN icon_url TEXT NOT NULL DEFAULT('');
|
||||||
|
ALTER TABLE messages ADD COLUMN icon_type TEXT NOT NULL DEFAULT('');
|
||||||
|
ALTER TABLE messages ADD COLUMN icon_size INT NOT NULL DEFAULT('0');
|
||||||
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
type messageCache struct {
|
type messageCache struct {
|
||||||
|
@ -248,6 +258,13 @@ func (c *messageCache) addMessages(ms []*message) error {
|
||||||
attachmentExpires = m.Attachment.Expires
|
attachmentExpires = m.Attachment.Expires
|
||||||
attachmentURL = m.Attachment.URL
|
attachmentURL = m.Attachment.URL
|
||||||
}
|
}
|
||||||
|
var iconURL, iconType string
|
||||||
|
var iconSize int64
|
||||||
|
if m.Icon != nil {
|
||||||
|
iconURL = m.Icon.URL
|
||||||
|
iconType = m.Icon.Type
|
||||||
|
iconSize = m.Icon.Size
|
||||||
|
}
|
||||||
var actionsStr string
|
var actionsStr string
|
||||||
if len(m.Actions) > 0 {
|
if len(m.Actions) > 0 {
|
||||||
actionsBytes, err := json.Marshal(m.Actions)
|
actionsBytes, err := json.Marshal(m.Actions)
|
||||||
|
@ -275,6 +292,9 @@ func (c *messageCache) addMessages(ms []*message) error {
|
||||||
m.Sender,
|
m.Sender,
|
||||||
m.Encoding,
|
m.Encoding,
|
||||||
published,
|
published,
|
||||||
|
iconURL,
|
||||||
|
iconType,
|
||||||
|
iconSize,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -412,9 +432,9 @@ func readMessages(rows *sql.Rows) ([]*message, error) {
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
messages := make([]*message, 0)
|
messages := make([]*message, 0)
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var timestamp, attachmentSize, attachmentExpires int64
|
var timestamp, attachmentSize, attachmentExpires, iconSize int64
|
||||||
var priority int
|
var priority int
|
||||||
var id, topic, msg, title, tagsStr, click, actionsStr, attachmentName, attachmentType, attachmentURL, sender, encoding string
|
var id, topic, msg, title, tagsStr, click, actionsStr, attachmentName, attachmentType, attachmentURL, sender, encoding, iconURL, iconType string
|
||||||
err := rows.Scan(
|
err := rows.Scan(
|
||||||
&id,
|
&id,
|
||||||
×tamp,
|
×tamp,
|
||||||
|
@ -432,6 +452,9 @@ func readMessages(rows *sql.Rows) ([]*message, error) {
|
||||||
&attachmentURL,
|
&attachmentURL,
|
||||||
&sender,
|
&sender,
|
||||||
&encoding,
|
&encoding,
|
||||||
|
&iconURL,
|
||||||
|
&iconType,
|
||||||
|
&iconSize,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -456,6 +479,14 @@ func readMessages(rows *sql.Rows) ([]*message, error) {
|
||||||
URL: attachmentURL,
|
URL: attachmentURL,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var ico *icon
|
||||||
|
if iconURL != "" {
|
||||||
|
ico = &icon{
|
||||||
|
URL: iconURL,
|
||||||
|
Type: iconType,
|
||||||
|
Size: iconSize,
|
||||||
|
}
|
||||||
|
}
|
||||||
messages = append(messages, &message{
|
messages = append(messages, &message{
|
||||||
ID: id,
|
ID: id,
|
||||||
Time: timestamp,
|
Time: timestamp,
|
||||||
|
@ -466,6 +497,7 @@ func readMessages(rows *sql.Rows) ([]*message, error) {
|
||||||
Priority: priority,
|
Priority: priority,
|
||||||
Tags: tags,
|
Tags: tags,
|
||||||
Click: click,
|
Click: click,
|
||||||
|
Icon: ico,
|
||||||
Actions: actions,
|
Actions: actions,
|
||||||
Attachment: att,
|
Attachment: att,
|
||||||
Sender: sender,
|
Sender: sender,
|
||||||
|
@ -524,6 +556,8 @@ func setupCacheDB(db *sql.DB, startupQueries string) error {
|
||||||
return migrateFrom5(db)
|
return migrateFrom5(db)
|
||||||
} else if schemaVersion == 6 {
|
} else if schemaVersion == 6 {
|
||||||
return migrateFrom6(db)
|
return migrateFrom6(db)
|
||||||
|
} else if schemaVersion == 7 {
|
||||||
|
return migrateFrom7(db)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("unexpected schema version found: %d", schemaVersion)
|
return fmt.Errorf("unexpected schema version found: %d", schemaVersion)
|
||||||
}
|
}
|
||||||
|
@ -618,5 +652,16 @@ func migrateFrom6(db *sql.DB) error {
|
||||||
if _, err := db.Exec(updateSchemaVersion, 7); err != nil {
|
if _, err := db.Exec(updateSchemaVersion, 7); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
return migrateFrom7(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
func migrateFrom7(db *sql.DB) error {
|
||||||
|
log.Info("Migrating cache database schema: from 7 to 8")
|
||||||
|
if _, err := db.Exec(migrate7To8AlterMessagesTableQuery); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := db.Exec(updateSchemaVersion, 8); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil // Update this when a new version is added
|
return nil // Update this when a new version is added
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,7 @@ var (
|
||||||
fileRegex = regexp.MustCompile(`^/file/([-_A-Za-z0-9]{1,64})(?:\.[A-Za-z0-9]{1,16})?$`)
|
fileRegex = regexp.MustCompile(`^/file/([-_A-Za-z0-9]{1,64})(?:\.[A-Za-z0-9]{1,16})?$`)
|
||||||
disallowedTopics = []string{"docs", "static", "file", "app", "settings"} // If updated, also update in Android app
|
disallowedTopics = []string{"docs", "static", "file", "app", "settings"} // If updated, also update in Android app
|
||||||
attachURLRegex = regexp.MustCompile(`^https?://`)
|
attachURLRegex = regexp.MustCompile(`^https?://`)
|
||||||
|
iconURLRegex = regexp.MustCompile(`^https?://`)
|
||||||
|
|
||||||
//go:embed site
|
//go:embed site
|
||||||
webFs embed.FS
|
webFs embed.FS
|
||||||
|
@ -568,6 +569,7 @@ func (s *Server) parsePublishParams(r *http.Request, v *visitor, m *message) (ca
|
||||||
firebase = readBoolParam(r, true, "x-firebase", "firebase")
|
firebase = readBoolParam(r, true, "x-firebase", "firebase")
|
||||||
m.Title = readParam(r, "x-title", "title", "t")
|
m.Title = readParam(r, "x-title", "title", "t")
|
||||||
m.Click = readParam(r, "x-click", "click")
|
m.Click = readParam(r, "x-click", "click")
|
||||||
|
ico := readParam(r, "x-icon", "icon")
|
||||||
filename := readParam(r, "x-filename", "filename", "file", "f")
|
filename := readParam(r, "x-filename", "filename", "file", "f")
|
||||||
attach := readParam(r, "x-attach", "attach", "a")
|
attach := readParam(r, "x-attach", "attach", "a")
|
||||||
if attach != "" || filename != "" {
|
if attach != "" || filename != "" {
|
||||||
|
@ -594,6 +596,13 @@ func (s *Server) parsePublishParams(r *http.Request, v *visitor, m *message) (ca
|
||||||
m.Attachment.Name = "attachment"
|
m.Attachment.Name = "attachment"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ico != "" {
|
||||||
|
m.Icon = &icon{}
|
||||||
|
if !iconURLRegex.MatchString(ico) {
|
||||||
|
return false, false, "", false, errHTTPBadRequestIconURLInvalid
|
||||||
|
}
|
||||||
|
m.Icon.URL = ico
|
||||||
|
}
|
||||||
email = readParam(r, "x-email", "x-e-mail", "email", "e-mail", "mail", "e")
|
email = readParam(r, "x-email", "x-e-mail", "email", "e-mail", "mail", "e")
|
||||||
if email != "" {
|
if email != "" {
|
||||||
if err := v.EmailAllowed(); err != nil {
|
if err := v.EmailAllowed(); err != nil {
|
||||||
|
@ -1336,6 +1345,9 @@ func (s *Server) transformBodyJSON(next handleFunc) handleFunc {
|
||||||
if m.Click != "" {
|
if m.Click != "" {
|
||||||
r.Header.Set("X-Click", m.Click)
|
r.Header.Set("X-Click", m.Click)
|
||||||
}
|
}
|
||||||
|
if m.Icon != "" {
|
||||||
|
r.Header.Set("X-Icon", m.Icon)
|
||||||
|
}
|
||||||
if len(m.Actions) > 0 {
|
if len(m.Actions) > 0 {
|
||||||
actionsStr, err := json.Marshal(m.Actions)
|
actionsStr, err := json.Marshal(m.Actions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -166,6 +166,11 @@ func toFirebaseMessage(m *message, auther auth.Auther) (*messaging.Message, erro
|
||||||
data["attachment_expires"] = fmt.Sprintf("%d", m.Attachment.Expires)
|
data["attachment_expires"] = fmt.Sprintf("%d", m.Attachment.Expires)
|
||||||
data["attachment_url"] = m.Attachment.URL
|
data["attachment_url"] = m.Attachment.URL
|
||||||
}
|
}
|
||||||
|
if m.Icon != nil {
|
||||||
|
data["icon_url"] = m.Icon.URL
|
||||||
|
data["icon_type"] = m.Icon.Type
|
||||||
|
data["icon_size"] = fmt.Sprintf("%d", m.Icon.Size)
|
||||||
|
}
|
||||||
apnsConfig = createAPNSAlertConfig(m, data)
|
apnsConfig = createAPNSAlertConfig(m, data)
|
||||||
} else {
|
} else {
|
||||||
// If anonymous read for a topic is not allowed, we cannot send the message along
|
// If anonymous read for a topic is not allowed, we cannot send the message along
|
||||||
|
|
|
@ -123,6 +123,11 @@ func TestToFirebaseMessage_Message_Normal_Allowed(t *testing.T) {
|
||||||
m.Priority = 4
|
m.Priority = 4
|
||||||
m.Tags = []string{"tag 1", "tag2"}
|
m.Tags = []string{"tag 1", "tag2"}
|
||||||
m.Click = "https://google.com"
|
m.Click = "https://google.com"
|
||||||
|
m.Icon = &icon{
|
||||||
|
URL: "https://ntfy.sh/static/img/ntfy.png",
|
||||||
|
Type: "image/jpeg",
|
||||||
|
Size: 4567,
|
||||||
|
}
|
||||||
m.Title = "some title"
|
m.Title = "some title"
|
||||||
m.Actions = []*action{
|
m.Actions = []*action{
|
||||||
{
|
{
|
||||||
|
@ -173,6 +178,9 @@ func TestToFirebaseMessage_Message_Normal_Allowed(t *testing.T) {
|
||||||
"priority": "4",
|
"priority": "4",
|
||||||
"tags": strings.Join(m.Tags, ","),
|
"tags": strings.Join(m.Tags, ","),
|
||||||
"click": "https://google.com",
|
"click": "https://google.com",
|
||||||
|
"icon_url": "https://ntfy.sh/static/img/ntfy.png",
|
||||||
|
"icon_type": "image/jpeg",
|
||||||
|
"icon_size": "4567",
|
||||||
"title": "some title",
|
"title": "some title",
|
||||||
"message": "this is a message",
|
"message": "this is a message",
|
||||||
"actions": `[{"id":"123","action":"view","label":"Open page","clear":true,"url":"https://ntfy.sh"},{"id":"456","action":"http","label":"Close door","clear":false,"url":"https://door.com/close","method":"PUT","headers":{"really":"yes"}}]`,
|
"actions": `[{"id":"123","action":"view","label":"Open page","clear":true,"url":"https://ntfy.sh"},{"id":"456","action":"http","label":"Close door","clear":false,"url":"https://door.com/close","method":"PUT","headers":{"really":"yes"}}]`,
|
||||||
|
@ -193,6 +201,9 @@ func TestToFirebaseMessage_Message_Normal_Allowed(t *testing.T) {
|
||||||
"priority": "4",
|
"priority": "4",
|
||||||
"tags": strings.Join(m.Tags, ","),
|
"tags": strings.Join(m.Tags, ","),
|
||||||
"click": "https://google.com",
|
"click": "https://google.com",
|
||||||
|
"icon_url": "https://ntfy.sh/static/img/ntfy.png",
|
||||||
|
"icon_type": "image/jpeg",
|
||||||
|
"icon_size": "4567",
|
||||||
"title": "some title",
|
"title": "some title",
|
||||||
"message": "this is a message",
|
"message": "this is a message",
|
||||||
"actions": `[{"id":"123","action":"view","label":"Open page","clear":true,"url":"https://ntfy.sh"},{"id":"456","action":"http","label":"Close door","clear":false,"url":"https://door.com/close","method":"PUT","headers":{"really":"yes"}}]`,
|
"actions": `[{"id":"123","action":"view","label":"Open page","clear":true,"url":"https://ntfy.sh"},{"id":"456","action":"http","label":"Close door","clear":false,"url":"https://door.com/close","method":"PUT","headers":{"really":"yes"}}]`,
|
||||||
|
|
|
@ -1046,7 +1046,7 @@ func TestServer_PublishAsJSON(t *testing.T) {
|
||||||
s := newTestServer(t, newTestConfig(t))
|
s := newTestServer(t, newTestConfig(t))
|
||||||
body := `{"topic":"mytopic","message":"A message","title":"a title\nwith lines","tags":["tag1","tag 2"],` +
|
body := `{"topic":"mytopic","message":"A message","title":"a title\nwith lines","tags":["tag1","tag 2"],` +
|
||||||
`"not-a-thing":"ok", "attach":"http://google.com","filename":"google.pdf", "click":"http://ntfy.sh","priority":4,` +
|
`"not-a-thing":"ok", "attach":"http://google.com","filename":"google.pdf", "click":"http://ntfy.sh","priority":4,` +
|
||||||
`"delay":"30min"}`
|
`"icon":"https://ntfy.sh/static/img/ntfy.png", "delay":"30min"}`
|
||||||
response := request(t, s, "PUT", "/", body, nil)
|
response := request(t, s, "PUT", "/", body, nil)
|
||||||
require.Equal(t, 200, response.Code)
|
require.Equal(t, 200, response.Code)
|
||||||
|
|
||||||
|
@ -1058,6 +1058,8 @@ func TestServer_PublishAsJSON(t *testing.T) {
|
||||||
require.Equal(t, "http://google.com", m.Attachment.URL)
|
require.Equal(t, "http://google.com", m.Attachment.URL)
|
||||||
require.Equal(t, "google.pdf", m.Attachment.Name)
|
require.Equal(t, "google.pdf", m.Attachment.Name)
|
||||||
require.Equal(t, "http://ntfy.sh", m.Click)
|
require.Equal(t, "http://ntfy.sh", m.Click)
|
||||||
|
require.Equal(t, "https://ntfy.sh/static/img/ntfy.png", m.Icon.URL)
|
||||||
|
|
||||||
require.Equal(t, 4, m.Priority)
|
require.Equal(t, 4, m.Priority)
|
||||||
require.True(t, m.Time > time.Now().Unix()+29*60)
|
require.True(t, m.Time > time.Now().Unix()+29*60)
|
||||||
require.True(t, m.Time < time.Now().Unix()+31*60)
|
require.True(t, m.Time < time.Now().Unix()+31*60)
|
||||||
|
|
|
@ -31,6 +31,7 @@ type message struct {
|
||||||
Click string `json:"click,omitempty"`
|
Click string `json:"click,omitempty"`
|
||||||
Actions []*action `json:"actions,omitempty"`
|
Actions []*action `json:"actions,omitempty"`
|
||||||
Attachment *attachment `json:"attachment,omitempty"`
|
Attachment *attachment `json:"attachment,omitempty"`
|
||||||
|
Icon *icon `json:"icon,omitempty"`
|
||||||
PollID string `json:"poll_id,omitempty"`
|
PollID string `json:"poll_id,omitempty"`
|
||||||
Sender string `json:"-"` // IP address of uploader, used for rate limiting
|
Sender string `json:"-"` // IP address of uploader, used for rate limiting
|
||||||
Encoding string `json:"encoding,omitempty"` // empty for raw UTF-8, or "base64" for encoded bytes
|
Encoding string `json:"encoding,omitempty"` // empty for raw UTF-8, or "base64" for encoded bytes
|
||||||
|
@ -44,6 +45,12 @@ type attachment struct {
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type icon struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Size int64 `json:"size,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type action struct {
|
type action struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Action string `json:"action"` // "view", "broadcast", or "http"
|
Action string `json:"action"` // "view", "broadcast", or "http"
|
||||||
|
@ -74,6 +81,7 @@ type publishMessage struct {
|
||||||
Click string `json:"click"`
|
Click string `json:"click"`
|
||||||
Actions []action `json:"actions"`
|
Actions []action `json:"actions"`
|
||||||
Attach string `json:"attach"`
|
Attach string `json:"attach"`
|
||||||
|
Icon string `json:"icon"`
|
||||||
Filename string `json:"filename"`
|
Filename string `json:"filename"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Delay string `json:"delay"`
|
Delay string `json:"delay"`
|
||||||
|
|
Loading…
Reference in New Issue