Compare commits
14 Commits
v1.17.0
...
webhook-te
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96a12d98c9 | ||
|
|
53375ff559 | ||
|
|
53e08988e7 | ||
|
|
d0bbda555f | ||
|
|
207e990798 | ||
|
|
b0a07af28d | ||
|
|
1a8bac7ab1 | ||
|
|
dc03c13642 | ||
|
|
739b20583d | ||
|
|
10ccbc780b | ||
|
|
f971a36ec0 | ||
|
|
3699464947 | ||
|
|
3a3d1262ab | ||
|
|
395a97c0e5 |
@@ -59,12 +59,12 @@ nfpms:
|
|||||||
contents:
|
contents:
|
||||||
- src: server/server.yml
|
- src: server/server.yml
|
||||||
dst: /etc/ntfy/server.yml
|
dst: /etc/ntfy/server.yml
|
||||||
type: config
|
type: "config|noreplace"
|
||||||
- src: server/ntfy.service
|
- src: server/ntfy.service
|
||||||
dst: /lib/systemd/system/ntfy.service
|
dst: /lib/systemd/system/ntfy.service
|
||||||
- src: client/client.yml
|
- src: client/client.yml
|
||||||
dst: /etc/ntfy/client.yml
|
dst: /etc/ntfy/client.yml
|
||||||
type: config
|
type: "config|noreplace"
|
||||||
- src: client/ntfy-client.service
|
- src: client/ntfy-client.service
|
||||||
dst: /lib/systemd/system/ntfy-client.service
|
dst: /lib/systemd/system/ntfy-client.service
|
||||||
- dst: /var/cache/ntfy
|
- dst: /var/cache/ntfy
|
||||||
|
|||||||
8
Makefile
8
Makefile
@@ -149,6 +149,14 @@ release-check-tags:
|
|||||||
echo "ERROR: Must update docs/install.md with latest tag first.";\
|
echo "ERROR: Must update docs/install.md with latest tag first.";\
|
||||||
exit 1;\
|
exit 1;\
|
||||||
fi
|
fi
|
||||||
|
if grep -q XXXXX docs/releases.md; then\
|
||||||
|
echo "ERROR: Must update docs/releases.md, found XXXXX.";\
|
||||||
|
exit 1;\
|
||||||
|
fi
|
||||||
|
if ! grep -q $(LATEST_TAG) docs/releases.md; then\
|
||||||
|
echo "ERROR: Must update docs/releases.mdwith latest tag first.";\
|
||||||
|
exit 1;\
|
||||||
|
fi
|
||||||
|
|
||||||
release: build-deps release-check-tags check
|
release: build-deps release-check-tags check
|
||||||
goreleaser release --rm-dist --debug
|
goreleaser release --rm-dist --debug
|
||||||
|
|||||||
12
README.md
12
README.md
@@ -1,4 +1,4 @@
|
|||||||

|

|
||||||
|
|
||||||
# ntfy.sh | Send push notifications to your phone or desktop via PUT/POST
|
# ntfy.sh | Send push notifications to your phone or desktop via PUT/POST
|
||||||
[](https://github.com/binwiederhier/ntfy/releases/latest)
|
[](https://github.com/binwiederhier/ntfy/releases/latest)
|
||||||
@@ -18,11 +18,11 @@ I run a free version of it at **[ntfy.sh](https://ntfy.sh)**, and there's an [op
|
|||||||
too.
|
too.
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<img src="server/static/img/screenshot-curl.png" height="180">
|
<img src="web/public/static/img/screenshot-curl.png" height="180">
|
||||||
<img src="server/static/img/screenshot-web-detail.png" height="180">
|
<img src="web/public/static/img/screenshot-web-detail.png" height="180">
|
||||||
<img src="server/static/img/screenshot-phone-main.jpg" height="180">
|
<img src="web/public/static/img/screenshot-phone-main.jpg" height="180">
|
||||||
<img src="server/static/img/screenshot-phone-detail.jpg" height="180">
|
<img src="web/public/static/img/screenshot-phone-detail.jpg" height="180">
|
||||||
<img src="server/static/img/screenshot-phone-notification.jpg" height="180">
|
<img src="web/public/static/img/screenshot-phone-notification.jpg" height="180">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## **[Documentation](https://ntfy.sh/docs/)**
|
## **[Documentation](https://ntfy.sh/docs/)**
|
||||||
|
|||||||
@@ -30,9 +30,6 @@ func New() *cli.App {
|
|||||||
Reader: os.Stdin,
|
Reader: os.Stdin,
|
||||||
Writer: os.Stdout,
|
Writer: os.Stdout,
|
||||||
ErrWriter: os.Stderr,
|
ErrWriter: os.Stderr,
|
||||||
Action: execMainApp,
|
|
||||||
Before: initConfigFileInputSource("config", flagsServe), // DEPRECATED, see deprecation notice
|
|
||||||
Flags: flagsServe, // DEPRECATED, see deprecation notice
|
|
||||||
Commands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
// Server commands
|
// Server commands
|
||||||
cmdServe,
|
cmdServe,
|
||||||
@@ -46,12 +43,6 @@ func New() *cli.App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func execMainApp(c *cli.Context) error {
|
|
||||||
fmt.Fprintln(c.App.ErrWriter, "\x1b[1;33mDeprecation notice: Please run the server using 'ntfy serve'; see 'ntfy -h' for help.\x1b[0m")
|
|
||||||
fmt.Fprintln(c.App.ErrWriter, "\x1b[1;33mThis way of running the server will be removed March 2022. See https://ntfy.sh/docs/deprecations/ for details.\x1b[0m")
|
|
||||||
return execServe(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// initConfigFileInputSource is like altsrc.InitInputSourceWithContext and altsrc.NewYamlSourceFromFlagFunc, but checks
|
// initConfigFileInputSource is like altsrc.InitInputSourceWithContext and altsrc.NewYamlSourceFromFlagFunc, but checks
|
||||||
// if the config flag is exists and only loads it if it does. If the flag is set and the file exists, it fails.
|
// if the config flag is exists and only loads it if it does. If the flag is set and the file exists, it fails.
|
||||||
func initConfigFileInputSource(configFlag string, flags []cli.Flag) cli.BeforeFunc {
|
func initConfigFileInputSource(configFlag string, flags []cli.Flag) cli.BeforeFunc {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ var flagsServe = []cli.Flag{
|
|||||||
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "keepalive-interval", Aliases: []string{"k"}, EnvVars: []string{"NTFY_KEEPALIVE_INTERVAL"}, Value: server.DefaultKeepaliveInterval, Usage: "interval of keepalive messages"}),
|
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "keepalive-interval", Aliases: []string{"k"}, EnvVars: []string{"NTFY_KEEPALIVE_INTERVAL"}, Value: server.DefaultKeepaliveInterval, Usage: "interval of keepalive messages"}),
|
||||||
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "manager-interval", Aliases: []string{"m"}, EnvVars: []string{"NTFY_MANAGER_INTERVAL"}, Value: server.DefaultManagerInterval, Usage: "interval of for message pruning and stats printing"}),
|
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "manager-interval", Aliases: []string{"m"}, EnvVars: []string{"NTFY_MANAGER_INTERVAL"}, Value: server.DefaultManagerInterval, Usage: "interval of for message pruning and stats printing"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-root", EnvVars: []string{"NTFY_WEB_ROOT"}, Value: "app", Usage: "sets web root to landing page (home) or web app (app)"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-root", EnvVars: []string{"NTFY_WEB_ROOT"}, Value: "app", Usage: "sets web root to landing page (home) or web app (app)"}),
|
||||||
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "message-size-limit", Aliases: []string{"M"}, EnvVars: []string{"NTFY_MESSAGE_SIZE_LIMIT"}, DefaultText: "4K", Usage: "size limit of messages before they are treated as attachments (e.g. 4K, 64K)"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-addr", EnvVars: []string{"NTFY_SMTP_SENDER_ADDR"}, Usage: "SMTP server address (host:port) for outgoing emails"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-addr", EnvVars: []string{"NTFY_SMTP_SENDER_ADDR"}, Usage: "SMTP server address (host:port) for outgoing emails"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-user", EnvVars: []string{"NTFY_SMTP_SENDER_USER"}, Usage: "SMTP user (if e-mail sending is enabled)"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-user", EnvVars: []string{"NTFY_SMTP_SENDER_USER"}, Usage: "SMTP user (if e-mail sending is enabled)"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-pass", EnvVars: []string{"NTFY_SMTP_SENDER_PASS"}, Usage: "SMTP password (if e-mail sending is enabled)"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-pass", EnvVars: []string{"NTFY_SMTP_SENDER_PASS"}, Usage: "SMTP password (if e-mail sending is enabled)"}),
|
||||||
@@ -95,6 +96,7 @@ func execServe(c *cli.Context) error {
|
|||||||
keepaliveInterval := c.Duration("keepalive-interval")
|
keepaliveInterval := c.Duration("keepalive-interval")
|
||||||
managerInterval := c.Duration("manager-interval")
|
managerInterval := c.Duration("manager-interval")
|
||||||
webRoot := c.String("web-root")
|
webRoot := c.String("web-root")
|
||||||
|
messageSizeLimitStr := c.String("message-size-limit")
|
||||||
smtpSenderAddr := c.String("smtp-sender-addr")
|
smtpSenderAddr := c.String("smtp-sender-addr")
|
||||||
smtpSenderUser := c.String("smtp-sender-user")
|
smtpSenderUser := c.String("smtp-sender-user")
|
||||||
smtpSenderPass := c.String("smtp-sender-pass")
|
smtpSenderPass := c.String("smtp-sender-pass")
|
||||||
@@ -171,6 +173,12 @@ func execServe(c *cli.Context) error {
|
|||||||
} else if visitorAttachmentDailyBandwidthLimit > math.MaxInt {
|
} else if visitorAttachmentDailyBandwidthLimit > math.MaxInt {
|
||||||
return fmt.Errorf("config option visitor-attachment-daily-bandwidth-limit must be lower than %d", math.MaxInt)
|
return fmt.Errorf("config option visitor-attachment-daily-bandwidth-limit must be lower than %d", math.MaxInt)
|
||||||
}
|
}
|
||||||
|
messageSizeLimit, err := parseSize(messageSizeLimitStr, server.DefaultMessageLengthLimit)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if messageSizeLimit > server.MaxMessageLengthLimit {
|
||||||
|
return fmt.Errorf("config option message-size-limit must be lower than %d", server.MaxMessageLengthLimit)
|
||||||
|
}
|
||||||
|
|
||||||
// Resolve hosts
|
// Resolve hosts
|
||||||
visitorRequestLimitExemptIPs := make([]string, 0)
|
visitorRequestLimitExemptIPs := make([]string, 0)
|
||||||
@@ -206,6 +214,7 @@ func execServe(c *cli.Context) error {
|
|||||||
conf.KeepaliveInterval = keepaliveInterval
|
conf.KeepaliveInterval = keepaliveInterval
|
||||||
conf.ManagerInterval = managerInterval
|
conf.ManagerInterval = managerInterval
|
||||||
conf.WebRootIsApp = webRootIsApp
|
conf.WebRootIsApp = webRootIsApp
|
||||||
|
conf.MessageLimit = int(messageSizeLimit)
|
||||||
conf.SMTPSenderAddr = smtpSenderAddr
|
conf.SMTPSenderAddr = smtpSenderAddr
|
||||||
conf.SMTPSenderUser = smtpSenderUser
|
conf.SMTPSenderUser = smtpSenderUser
|
||||||
conf.SMTPSenderPass = smtpSenderPass
|
conf.SMTPSenderPass = smtpSenderPass
|
||||||
|
|||||||
@@ -399,8 +399,10 @@ HTTP challenge. I've found [this guide](https://nandovieira.com/using-lets-encry
|
|||||||
be incredibly helpful.
|
be incredibly helpful.
|
||||||
|
|
||||||
### nginx/Apache2/caddy
|
### nginx/Apache2/caddy
|
||||||
For your convenience, here's a working config that'll help configure things behind a proxy. In this
|
For your convenience, here's a working config that'll help configure things behind a proxy. Be sure to **enable WebSockets**
|
||||||
example, ntfy runs on `:2586` and we proxy traffic to it. We also redirect HTTP to HTTPS for GET requests against a topic
|
by forwarding the `Connection` and `Upgrade` headers accordingly.
|
||||||
|
|
||||||
|
In this example, ntfy runs on `:2586` and we proxy traffic to it. We also redirect HTTP to HTTPS for GET requests against a topic
|
||||||
or the root domain:
|
or the root domain:
|
||||||
|
|
||||||
=== "nginx (/etc/nginx/sites-*/ntfy)"
|
=== "nginx (/etc/nginx/sites-*/ntfy)"
|
||||||
|
|||||||
@@ -4,16 +4,24 @@ This page is used to list deprecation notices for ntfy. Deprecated commands and
|
|||||||
|
|
||||||
## Active deprecations
|
## Active deprecations
|
||||||
|
|
||||||
### Android app: Using `since=<timestamp>` instead of `since=<id>`
|
### Android app: WebSockets will become the default connection protocol
|
||||||
> since 2022-02-27
|
> since 2022-03-13, behavior will change in **June 2022**
|
||||||
|
|
||||||
|
In future versions of the Android app, instant delivery connections and connections to self-hosted servers will
|
||||||
|
be using the WebSockets protocol. This potentially requires [configuration changes in your proxy](https://ntfy.sh/docs/config/#nginxapache2caddy).
|
||||||
|
|
||||||
|
### Android app: Using `since=<timestamp>` instead of `since=<id>`
|
||||||
|
> since 2022-02-27, behavior will change in **May 2022**
|
||||||
|
|
||||||
In about 3 months, the Android app will start using `since=<id>` instead of `since=<timestamp>`, which means that it will
|
In about 3 months, the Android app will start using `since=<id>` instead of `since=<timestamp>`, which means that it will
|
||||||
not work with servers older than v1.16.0 anymore. This is to simplify handling of deduplication in the Android app.
|
not work with servers older than v1.16.0 anymore. This is to simplify handling of deduplication in the Android app.
|
||||||
|
|
||||||
The `since=<timestamp>` endpoint will continue to work. This is merely a notice that the Android app behavior will change.
|
The `since=<timestamp>` endpoint will continue to work. This is merely a notice that the Android app behavior will change.
|
||||||
|
|
||||||
|
## Previous deprecations
|
||||||
|
|
||||||
### Running server via `ntfy` (instead of `ntfy serve`)
|
### Running server via `ntfy` (instead of `ntfy serve`)
|
||||||
> since 2021-12-17
|
> deprecated 2021-12-17, behavior changed with v1.10.0
|
||||||
|
|
||||||
As more commands are added to the `ntfy` CLI tool, using just `ntfy` to run the server is not practical
|
As more commands are added to the `ntfy` CLI tool, using just `ntfy` to run the server is not practical
|
||||||
anymore. Please use `ntfy serve` instead. This also applies to Docker images, as they can also execute more than
|
anymore. Please use `ntfy serve` instead. This also applies to Docker images, as they can also execute more than
|
||||||
|
|||||||
@@ -16,6 +16,27 @@ rsync -a root@laptop /backups/laptop \
|
|||||||
|| curl -H tags:warning -H prio:high -d "Laptop backup failed" ntfy.sh/backups
|
|| curl -H tags:warning -H prio:high -d "Laptop backup failed" ntfy.sh/backups
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Low disk space alerts
|
||||||
|
Here's a simple cronjob that I use to alert me when the disk space on the root disk is running low. It's simple, but
|
||||||
|
effective.
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
mingigs=10
|
||||||
|
avail=$(df | awk '$6 == "/" && $4 < '$mingigs' * 1024*1024 { print $4/1024/1024 }')
|
||||||
|
topicurl=https://ntfy.sh/mytopic
|
||||||
|
|
||||||
|
if [ -n "$avail" ]; then
|
||||||
|
curl \
|
||||||
|
-d "Only $avail GB available on the root disk. Better clean that up." \
|
||||||
|
-H "Title: Low disk space alert on $(hostname)" \
|
||||||
|
-H "Priority: high" \
|
||||||
|
-H "Tags: warning,cd" \
|
||||||
|
$topicurl
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
## Server-sent messages in your web app
|
## Server-sent messages in your web app
|
||||||
Just as you can [subscribe to topics in the Web UI](subscribe/web.md), you can use ntfy in your own
|
Just as you can [subscribe to topics in the Web UI](subscribe/web.md), you can use ntfy in your own
|
||||||
web application. Check out the <a href="/example.html">live example</a> or just look the source of this page.
|
web application. Check out the <a href="/example.html">live example</a> or just look the source of this page.
|
||||||
@@ -93,3 +114,13 @@ Or, if you only want to send notifications using shoutrrr:
|
|||||||
```
|
```
|
||||||
shoutrrr send -u "generic+https://ntfy.sh/my_watchtower_topic?title=WatchtowerUpdates" -m "testMessage"
|
shoutrrr send -u "generic+https://ntfy.sh/my_watchtower_topic?title=WatchtowerUpdates" -m "testMessage"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Random cronjobs
|
||||||
|
Alright, here's one for the history books. I desperately want the `github.com/ntfy` organization, but all my tickets with
|
||||||
|
GitHub have been hopeless. In case it ever becomes available, I want to know immediately.
|
||||||
|
|
||||||
|
``` cron
|
||||||
|
# Check github/ntfy user
|
||||||
|
*/6 * * * * if curl -s https://api.github.com/users/ntfy | grep "Not Found"; then curl -d "github.com/ntfy is available" -H "Tags: tada" -H "Prio: high" ntfy.sh/my-alerts; fi
|
||||||
|
~
|
||||||
|
```
|
||||||
|
|||||||
@@ -33,10 +33,11 @@ If you do not care for Firebase, I suggest you install the [F-Droid version](htt
|
|||||||
of the app and [self-host your own ntfy server](install.md).
|
of the app and [self-host your own ntfy server](install.md).
|
||||||
|
|
||||||
## How much battery does the Android app use?
|
## How much battery does the Android app use?
|
||||||
If you use the ntfy.sh server and you don't use the [instant delivery](subscribe/phone.md#instant-delivery) feature,
|
If you use the ntfy.sh server, and you don't use the [instant delivery](subscribe/phone.md#instant-delivery) feature,
|
||||||
the Android app uses no additional battery, since Firebase Cloud Messaging (FCM) is used. If you use your own server,
|
the Android app uses no additional battery, since Firebase Cloud Messaging (FCM) is used. If you use your own server,
|
||||||
or you use *instant delivery*, the app has to maintain a constant connection to the server, which consumes about 4% of
|
or you use *instant delivery*, the app has to maintain a constant connection to the server, which consumes about 0-1% of
|
||||||
battery in 17h of use (on my phone). I use it, and it makes no difference to me.
|
battery in 17h of use (on my phone). There has been a ton of testing and improvement around this. I think it's pretty
|
||||||
|
decent now.
|
||||||
|
|
||||||
## What is instant delivery?
|
## What is instant delivery?
|
||||||
[Instant delivery](subscribe/phone.md#instant-delivery) is a feature in the Android app. If turned on, the app maintains a constant connection to the
|
[Instant delivery](subscribe/phone.md#instant-delivery) is a feature in the Android app. If turned on, the app maintains a constant connection to the
|
||||||
|
|||||||
@@ -26,21 +26,21 @@ deb/rpm packages.
|
|||||||
|
|
||||||
=== "x86_64/amd64"
|
=== "x86_64/amd64"
|
||||||
```bash
|
```bash
|
||||||
wget https://github.com/binwiederhier/ntfy/releases/download/v1.17.0/ntfy_1.17.0_linux_x86_64.tar.gz
|
wget https://github.com/binwiederhier/ntfy/releases/download/v1.17.1/ntfy_1.17.1_linux_x86_64.tar.gz
|
||||||
sudo tar -C /usr/bin -zxf ntfy_*.tar.gz ntfy
|
sudo tar -C /usr/bin -zxf ntfy_*.tar.gz ntfy
|
||||||
sudo ./ntfy serve
|
sudo ./ntfy serve
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "armv7/armhf"
|
=== "armv7/armhf"
|
||||||
```bash
|
```bash
|
||||||
wget https://github.com/binwiederhier/ntfy/releases/download/v1.17.0/ntfy_1.17.0_linux_armv7.tar.gz
|
wget https://github.com/binwiederhier/ntfy/releases/download/v1.17.1/ntfy_1.17.1_linux_armv7.tar.gz
|
||||||
sudo tar -C /usr/bin -zxf ntfy_*.tar.gz ntfy
|
sudo tar -C /usr/bin -zxf ntfy_*.tar.gz ntfy
|
||||||
sudo ./ntfy serve
|
sudo ./ntfy serve
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "arm64"
|
=== "arm64"
|
||||||
```bash
|
```bash
|
||||||
wget https://github.com/binwiederhier/ntfy/releases/download/v1.17.0/ntfy_1.17.0_linux_arm64.tar.gz
|
wget https://github.com/binwiederhier/ntfy/releases/download/v1.17.1/ntfy_1.17.1_linux_arm64.tar.gz
|
||||||
sudo tar -C /usr/bin -zxf ntfy_*.tar.gz ntfy
|
sudo tar -C /usr/bin -zxf ntfy_*.tar.gz ntfy
|
||||||
sudo ./ntfy serve
|
sudo ./ntfy serve
|
||||||
```
|
```
|
||||||
@@ -88,7 +88,7 @@ Manually installing the .deb file:
|
|||||||
|
|
||||||
=== "x86_64/amd64"
|
=== "x86_64/amd64"
|
||||||
```bash
|
```bash
|
||||||
wget https://github.com/binwiederhier/ntfy/releases/download/v1.17.0/ntfy_1.17.0_linux_amd64.deb
|
wget https://github.com/binwiederhier/ntfy/releases/download/v1.17.1/ntfy_1.17.1_linux_amd64.deb
|
||||||
sudo dpkg -i ntfy_*.deb
|
sudo dpkg -i ntfy_*.deb
|
||||||
sudo systemctl enable ntfy
|
sudo systemctl enable ntfy
|
||||||
sudo systemctl start ntfy
|
sudo systemctl start ntfy
|
||||||
@@ -96,7 +96,7 @@ Manually installing the .deb file:
|
|||||||
|
|
||||||
=== "armv7/armhf"
|
=== "armv7/armhf"
|
||||||
```bash
|
```bash
|
||||||
wget https://github.com/binwiederhier/ntfy/releases/download/v1.17.0/ntfy_1.17.0_linux_armv7.deb
|
wget https://github.com/binwiederhier/ntfy/releases/download/v1.17.1/ntfy_1.17.1_linux_armv7.deb
|
||||||
sudo dpkg -i ntfy_*.deb
|
sudo dpkg -i ntfy_*.deb
|
||||||
sudo systemctl enable ntfy
|
sudo systemctl enable ntfy
|
||||||
sudo systemctl start ntfy
|
sudo systemctl start ntfy
|
||||||
@@ -104,7 +104,7 @@ Manually installing the .deb file:
|
|||||||
|
|
||||||
=== "arm64"
|
=== "arm64"
|
||||||
```bash
|
```bash
|
||||||
wget https://github.com/binwiederhier/ntfy/releases/download/v1.17.0/ntfy_1.17.0_linux_arm64.deb
|
wget https://github.com/binwiederhier/ntfy/releases/download/v1.17.1/ntfy_1.17.1_linux_arm64.deb
|
||||||
sudo dpkg -i ntfy_*.deb
|
sudo dpkg -i ntfy_*.deb
|
||||||
sudo systemctl enable ntfy
|
sudo systemctl enable ntfy
|
||||||
sudo systemctl start ntfy
|
sudo systemctl start ntfy
|
||||||
@@ -114,21 +114,21 @@ Manually installing the .deb file:
|
|||||||
|
|
||||||
=== "x86_64/amd64"
|
=== "x86_64/amd64"
|
||||||
```bash
|
```bash
|
||||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v1.17.0/ntfy_1.17.0_linux_amd64.rpm
|
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v1.17.1/ntfy_1.17.1_linux_amd64.rpm
|
||||||
sudo systemctl enable ntfy
|
sudo systemctl enable ntfy
|
||||||
sudo systemctl start ntfy
|
sudo systemctl start ntfy
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "armv7/armhf"
|
=== "armv7/armhf"
|
||||||
```bash
|
```bash
|
||||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v1.17.0/ntfy_1.17.0_linux_armv7.rpm
|
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v1.17.1/ntfy_1.17.1_linux_armv7.rpm
|
||||||
sudo systemctl enable ntfy
|
sudo systemctl enable ntfy
|
||||||
sudo systemctl start ntfy
|
sudo systemctl start ntfy
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "arm64"
|
=== "arm64"
|
||||||
```bash
|
```bash
|
||||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v1.17.0/ntfy_1.17.0_linux_arm64.rpm
|
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v1.17.1/ntfy_1.17.1_linux_arm64.rpm
|
||||||
sudo systemctl enable ntfy
|
sudo systemctl enable ntfy
|
||||||
sudo systemctl start ntfy
|
sudo systemctl start ntfy
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -2,6 +2,36 @@
|
|||||||
Binaries for all releases can be found on the GitHub releases pages for the [ntfy server](https://github.com/binwiederhier/ntfy/releases)
|
Binaries for all releases can be found on the GitHub releases pages for the [ntfy server](https://github.com/binwiederhier/ntfy/releases)
|
||||||
and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/releases).
|
and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/releases).
|
||||||
|
|
||||||
|
## ntfy server v1.18.0
|
||||||
|
Released XXXXXXXXXXXXXXX
|
||||||
|
|
||||||
|
**Bug fixes:**
|
||||||
|
|
||||||
|
* rpm: do not overwrite server.yaml on package upgrade (#166, thanks @waclaw66 for reporting)
|
||||||
|
|
||||||
|
**Deprecations:**
|
||||||
|
|
||||||
|
* Removed the ability to run server as `ntfy serve` as per [deprecation](https://ntfy.sh/docs/deprecations)
|
||||||
|
|
||||||
|
## ntfy Android app v1.10.0
|
||||||
|
Released XXXXXXXXXXXXXXX
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
|
||||||
|
* Support for UnifiedPush 2.0 specification (bytes messages, #130)
|
||||||
|
* Export/import settings and subscriptions (#115, thanks @cmeis for reporting)
|
||||||
|
|
||||||
|
**Bug fixes:**
|
||||||
|
|
||||||
|
* Display locale-specific times, with AM/PM or 24h format (#140, thanks @hl2guide for reporting)
|
||||||
|
|
||||||
|
## ntfy server v1.17.1
|
||||||
|
Released Mar 12, 2022
|
||||||
|
|
||||||
|
**Bug fixes:**
|
||||||
|
|
||||||
|
* Replace `crypto.subtle` with `hashCode` to errors with Brave/FF-Windows (#157, thanks for reporting @arminus)
|
||||||
|
|
||||||
## ntfy server v1.17.0
|
## ntfy server v1.17.0
|
||||||
Released Mar 11, 2022
|
Released Mar 11, 2022
|
||||||
|
|
||||||
|
|||||||
@@ -130,19 +130,21 @@ notification popups:
|
|||||||
|
|
||||||
Here's a list of extras you can access. Most likely, you'll want to filter for `topic` and react on `message`:
|
Here's a list of extras you can access. Most likely, you'll want to filter for `topic` and react on `message`:
|
||||||
|
|
||||||
| Extra name | Type | Example | Description |
|
| Extra name | Type | Example | Description |
|
||||||
|---|---|---|---|
|
|-----------------|------------------------------|--------------------|------------------------------------------------------------------------------------|
|
||||||
| `id` | *string* | `bP8dMjO8ig` | Randomly chosen message identifier (likely not very useful for task automation) |
|
| `id` | *String* | `bP8dMjO8ig` | Randomly chosen message identifier (likely not very useful for task automation) |
|
||||||
| `base_url` | *string* | `https://ntfy.sh` | Root URL of the ntfy server this message came from |
|
| `base_url` | *String* | `https://ntfy.sh` | Root URL of the ntfy server this message came from |
|
||||||
| `topic` ❤️ | *string* | `mytopic` | Topic name; **you'll likely want to filter for a specific topic** |
|
| `topic` ❤️ | *String* | `mytopic` | Topic name; **you'll likely want to filter for a specific topic** |
|
||||||
| `muted` | *bool* | `true` | Indicates whether the subscription was muted in the app |
|
| `muted` | *Boolean* | `true` | Indicates whether the subscription was muted in the app |
|
||||||
| `muted_str` | *string (`true` or `false`)* | `true` | Same as `muted`, but as string `true` or `false` |
|
| `muted_str` | *String (`true` or `false`)* | `true` | Same as `muted`, but as string `true` or `false` |
|
||||||
| `time` | *int* | `1635528741` | Message date time, as Unix time stamp |
|
| `time` | *Int* | `1635528741` | Message date time, as Unix time stamp |
|
||||||
| `title` | *string* | `Some title` | Message [title](../publish.md#message-title); may be empty if not set |
|
| `title` | *String* | `Some title` | Message [title](../publish.md#message-title); may be empty if not set |
|
||||||
| `message` ❤️ | *string* | `Some message` | Message body; **this is likely what you're interested in** |
|
| `message` ❤️ | *String* | `Some message` | Message body; **this is likely what you're interested in** |
|
||||||
| `tags` | *string* | `tag1,tag2,..` | Comma-separated list of [tags](../publish.md#tags-emojis) |
|
| `message_bytes` | *ByteArray* | `(binary data)` | Message body as binary data |
|
||||||
| `tags_map` | *string* | `0=tag1,1=tag2,..` | Map of tags to make it easier to map first, second, ... tag |
|
| `encoding`️ | *String* | - | Message encoding (empty or "base64") |
|
||||||
| `priority` | *int (between 1-5)* | `4` | Message [priority](../publish.md#message-priority) with 1=min, 3=default and 5=max |
|
| `tags` | *String* | `tag1,tag2,..` | Comma-separated list of [tags](../publish.md#tags-emojis) |
|
||||||
|
| `tags_map` | *String* | `0=tag1,1=tag2,..` | Map of tags to make it easier to map first, second, ... tag |
|
||||||
|
| `priority` | *Int (between 1-5)* | `4` | Message [priority](../publish.md#message-priority) with 1=min, 3=default and 5=max |
|
||||||
|
|
||||||
#### Send messages using intents
|
#### Send messages using intents
|
||||||
To send messages from other apps (such as [MacroDroid](https://play.google.com/store/apps/details?id=com.arlosoft.macrodroid)
|
To send messages from other apps (such as [MacroDroid](https://play.google.com/store/apps/details?id=com.arlosoft.macrodroid)
|
||||||
@@ -164,14 +166,14 @@ Here's what that looks like:
|
|||||||
|
|
||||||
The following intent extras are supported when for the intent with the `io.heckel.ntfy.SEND_MESSAGE` action:
|
The following intent extras are supported when for the intent with the `io.heckel.ntfy.SEND_MESSAGE` action:
|
||||||
|
|
||||||
| Extra name | Required | Type | Example | Description |
|
| Extra name | Required | Type | Example | Description |
|
||||||
|---|---|---|---|---|
|
|--------------|----------|-------------------------------|-------------------|------------------------------------------------------------------------------------|
|
||||||
| `base_url` | - | *string* | `https://ntfy.sh` | Root URL of the ntfy server this message came from, defaults to `https://ntfy.sh` |
|
| `base_url` | - | *String* | `https://ntfy.sh` | Root URL of the ntfy server this message came from, defaults to `https://ntfy.sh` |
|
||||||
| `topic` ❤️ | ✔ | *string* | `mytopic` | Topic name; **you must set this** |
|
| `topic` ❤️ | ✔ | *String* | `mytopic` | Topic name; **you must set this** |
|
||||||
| `title` | - | *string* | `Some title` | Message [title](../publish.md#message-title); may be empty if not set |
|
| `title` | - | *String* | `Some title` | Message [title](../publish.md#message-title); may be empty if not set |
|
||||||
| `message` ❤️ | ✔ | *string* | `Some message` | Message body; **you must set this** |
|
| `message` ❤️ | ✔ | *String* | `Some message` | Message body; **you must set this** |
|
||||||
| `tags` | - | *string* | `tag1,tag2,..` | Comma-separated list of [tags](../publish.md#tags-emojis) |
|
| `tags` | - | *String* | `tag1,tag2,..` | Comma-separated list of [tags](../publish.md#tags-emojis) |
|
||||||
| `priority` | - | *string or int (between 1-5)* | `4` | Message [priority](../publish.md#message-priority) with 1=min, 3=default and 5=max |
|
| `priority` | - | *String or Int (between 1-5)* | `4` | Message [priority](../publish.md#message-priority) with 1=min, 3=default and 5=max |
|
||||||
|
|
||||||
## iPhone/iOS
|
## iPhone/iOS
|
||||||
I almost feel devious for putting the *Download on the App Store* button on this page. Currently, there is no iOS app
|
I almost feel devious for putting the *Download on the App Store* button on this page. Currently, there is no iOS app
|
||||||
|
|||||||
3
go.mod
3
go.mod
@@ -14,6 +14,7 @@ require (
|
|||||||
github.com/mattn/go-sqlite3 v1.14.11
|
github.com/mattn/go-sqlite3 v1.14.11
|
||||||
github.com/olebedev/when v0.0.0-20211212231525-59bd4edcf9d6
|
github.com/olebedev/when v0.0.0-20211212231525-59bd4edcf9d6
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.0
|
||||||
|
github.com/tidwall/gjson v1.14.0
|
||||||
github.com/urfave/cli/v2 v2.3.0
|
github.com/urfave/cli/v2 v2.3.0
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
||||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
|
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
|
||||||
@@ -38,6 +39,8 @@ require (
|
|||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
|
github.com/tidwall/pretty v1.2.0 // indirect
|
||||||
go.opencensus.io v0.23.0 // indirect
|
go.opencensus.io v0.23.0 // indirect
|
||||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d // indirect
|
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d // indirect
|
||||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27 // indirect
|
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27 // indirect
|
||||||
|
|||||||
6
go.sum
6
go.sum
@@ -223,6 +223,12 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
|||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/tidwall/gjson v1.14.0 h1:6aeJ0bzojgWLa82gDQHcx3S0Lr/O51I9bJ5nv6JFx5w=
|
||||||
|
github.com/tidwall/gjson v1.14.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
|
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||||
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
||||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ const (
|
|||||||
// - total topic limit: max number of topics overall
|
// - total topic limit: max number of topics overall
|
||||||
// - various attachment limits
|
// - various attachment limits
|
||||||
const (
|
const (
|
||||||
DefaultMessageLengthLimit = 4096 // Bytes
|
DefaultMessageLengthLimit = 4096 // Bytes
|
||||||
|
MaxMessageLengthLimit = 16 * 1024 * 1024 // 16 MB, sanity size
|
||||||
DefaultTotalTopicLimit = 15000
|
DefaultTotalTopicLimit = 15000
|
||||||
DefaultAttachmentTotalSizeLimit = int64(5 * 1024 * 1024 * 1024) // 5 GB
|
DefaultAttachmentTotalSizeLimit = int64(5 * 1024 * 1024 * 1024) // 5 GB
|
||||||
DefaultAttachmentFileSizeLimit = int64(15 * 1024 * 1024) // 15 MB
|
DefaultAttachmentFileSizeLimit = int64(15 * 1024 * 1024) // 15 MB
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/emersion/go-smtp"
|
"github.com/emersion/go-smtp"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
"heckel.io/ntfy/auth"
|
"heckel.io/ntfy/auth"
|
||||||
"heckel.io/ntfy/util"
|
"heckel.io/ntfy/util"
|
||||||
@@ -397,11 +398,11 @@ func (s *Server) handlePublish(w http.ResponseWriter, r *http.Request, v *visito
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
m := newDefaultMessage(t.ID, "")
|
m := newDefaultMessage(t.ID, "")
|
||||||
cache, firebase, email, unifiedpush, err := s.parsePublishParams(r, v, m)
|
cache, firebase, email, template, unifiedpush, err := s.parsePublishParams(r, v, m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := s.handlePublishBody(r, v, m, body, unifiedpush); err != nil {
|
if err := s.handlePublishBody(r, v, m, body, template, unifiedpush); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if m.Message == "" {
|
if m.Message == "" {
|
||||||
@@ -443,7 +444,7 @@ func (s *Server) handlePublish(w http.ResponseWriter, r *http.Request, v *visito
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) parsePublishParams(r *http.Request, v *visitor, m *message) (cache bool, firebase bool, email string, unifiedpush bool, err error) {
|
func (s *Server) parsePublishParams(r *http.Request, v *visitor, m *message) (cache bool, firebase bool, email string, template string, unifiedpush bool, err error) {
|
||||||
cache = readBoolParam(r, true, "x-cache", "cache")
|
cache = readBoolParam(r, true, "x-cache", "cache")
|
||||||
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")
|
||||||
@@ -458,7 +459,7 @@ func (s *Server) parsePublishParams(r *http.Request, v *visitor, m *message) (ca
|
|||||||
}
|
}
|
||||||
if attach != "" {
|
if attach != "" {
|
||||||
if !attachURLRegex.MatchString(attach) {
|
if !attachURLRegex.MatchString(attach) {
|
||||||
return false, false, "", false, errHTTPBadRequestAttachmentURLInvalid
|
return false, false, "", "", false, errHTTPBadRequestAttachmentURLInvalid
|
||||||
}
|
}
|
||||||
m.Attachment.URL = attach
|
m.Attachment.URL = attach
|
||||||
if m.Attachment.Name == "" {
|
if m.Attachment.Name == "" {
|
||||||
@@ -477,11 +478,11 @@ func (s *Server) parsePublishParams(r *http.Request, v *visitor, m *message) (ca
|
|||||||
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 {
|
||||||
return false, false, "", false, errHTTPTooManyRequestsLimitEmails
|
return false, false, "", "", false, errHTTPTooManyRequestsLimitEmails
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if s.mailer == nil && email != "" {
|
if s.mailer == nil && email != "" {
|
||||||
return false, false, "", false, errHTTPBadRequestEmailDisabled
|
return false, false, "", "", false, errHTTPBadRequestEmailDisabled
|
||||||
}
|
}
|
||||||
messageStr := strings.ReplaceAll(readParam(r, "x-message", "message", "m"), "\\n", "\n")
|
messageStr := strings.ReplaceAll(readParam(r, "x-message", "message", "m"), "\\n", "\n")
|
||||||
if messageStr != "" {
|
if messageStr != "" {
|
||||||
@@ -489,7 +490,7 @@ func (s *Server) parsePublishParams(r *http.Request, v *visitor, m *message) (ca
|
|||||||
}
|
}
|
||||||
m.Priority, err = util.ParsePriority(readParam(r, "x-priority", "priority", "prio", "p"))
|
m.Priority, err = util.ParsePriority(readParam(r, "x-priority", "priority", "prio", "p"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, false, "", false, errHTTPBadRequestPriorityInvalid
|
return false, false, "", "", false, errHTTPBadRequestPriorityInvalid
|
||||||
}
|
}
|
||||||
tagsStr := readParam(r, "x-tags", "tags", "tag", "ta")
|
tagsStr := readParam(r, "x-tags", "tags", "tag", "ta")
|
||||||
if tagsStr != "" {
|
if tagsStr != "" {
|
||||||
@@ -501,27 +502,33 @@ func (s *Server) parsePublishParams(r *http.Request, v *visitor, m *message) (ca
|
|||||||
delayStr := readParam(r, "x-delay", "delay", "x-at", "at", "x-in", "in")
|
delayStr := readParam(r, "x-delay", "delay", "x-at", "at", "x-in", "in")
|
||||||
if delayStr != "" {
|
if delayStr != "" {
|
||||||
if !cache {
|
if !cache {
|
||||||
return false, false, "", false, errHTTPBadRequestDelayNoCache
|
return false, false, "", "", false, errHTTPBadRequestDelayNoCache
|
||||||
}
|
}
|
||||||
if email != "" {
|
if email != "" {
|
||||||
return false, false, "", false, errHTTPBadRequestDelayNoEmail // we cannot store the email address (yet)
|
return false, false, "", "", false, errHTTPBadRequestDelayNoEmail // we cannot store the email address (yet)
|
||||||
}
|
}
|
||||||
delay, err := util.ParseFutureTime(delayStr, time.Now())
|
delay, err := util.ParseFutureTime(delayStr, time.Now())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, false, "", false, errHTTPBadRequestDelayCannotParse
|
return false, false, "", "", false, errHTTPBadRequestDelayCannotParse
|
||||||
} else if delay.Unix() < time.Now().Add(s.config.MinDelay).Unix() {
|
} else if delay.Unix() < time.Now().Add(s.config.MinDelay).Unix() {
|
||||||
return false, false, "", false, errHTTPBadRequestDelayTooSmall
|
return false, false, "", "", false, errHTTPBadRequestDelayTooSmall
|
||||||
} else if delay.Unix() > time.Now().Add(s.config.MaxDelay).Unix() {
|
} else if delay.Unix() > time.Now().Add(s.config.MaxDelay).Unix() {
|
||||||
return false, false, "", false, errHTTPBadRequestDelayTooLarge
|
return false, false, "", "", false, errHTTPBadRequestDelayTooLarge
|
||||||
}
|
}
|
||||||
m.Time = delay.Unix()
|
m.Time = delay.Unix()
|
||||||
}
|
}
|
||||||
|
template = readParam(r, "x-template", "template", "tpl")
|
||||||
|
if template != "" {
|
||||||
|
if template != "json" {
|
||||||
|
return false, false, "", "", false, errors.New("invalid template")
|
||||||
|
}
|
||||||
|
}
|
||||||
unifiedpush = readBoolParam(r, false, "x-unifiedpush", "unifiedpush", "up") // see GET too!
|
unifiedpush = readBoolParam(r, false, "x-unifiedpush", "unifiedpush", "up") // see GET too!
|
||||||
if unifiedpush {
|
if unifiedpush {
|
||||||
firebase = false
|
firebase = false
|
||||||
unifiedpush = true
|
unifiedpush = true
|
||||||
}
|
}
|
||||||
return cache, firebase, email, unifiedpush, nil
|
return cache, firebase, email, template, unifiedpush, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// handlePublishBody consumes the PUT/POST body and decides whether the body is an attachment or the message.
|
// handlePublishBody consumes the PUT/POST body and decides whether the body is an attachment or the message.
|
||||||
@@ -536,15 +543,15 @@ func (s *Server) parsePublishParams(r *http.Request, v *visitor, m *message) (ca
|
|||||||
// If file.txt is <= 4096 (message limit) and valid UTF-8, treat it as a message
|
// If file.txt is <= 4096 (message limit) and valid UTF-8, treat it as a message
|
||||||
// 5. curl -T file.txt ntfy.sh/mytopic
|
// 5. curl -T file.txt ntfy.sh/mytopic
|
||||||
// If file.txt is > message limit, treat it as an attachment
|
// If file.txt is > message limit, treat it as an attachment
|
||||||
func (s *Server) handlePublishBody(r *http.Request, v *visitor, m *message, body *util.PeakedReadCloser, unifiedpush bool) error {
|
func (s *Server) handlePublishBody(r *http.Request, v *visitor, m *message, body *util.PeakedReadCloser, template string, unifiedpush bool) error {
|
||||||
if unifiedpush {
|
if unifiedpush {
|
||||||
return s.handleBodyAsMessageAutoDetect(m, body) // Case 1
|
return s.handleBodyAsMessageAutoDetect(m, body) // Case 1
|
||||||
} else if m.Attachment != nil && m.Attachment.URL != "" {
|
} else if m.Attachment != nil && m.Attachment.URL != "" {
|
||||||
return s.handleBodyAsTextMessage(m, body) // Case 2
|
return s.handleBodyAsTextMessage(m, body, template) // Case 2
|
||||||
} else if m.Attachment != nil && m.Attachment.Name != "" {
|
} else if m.Attachment != nil && m.Attachment.Name != "" {
|
||||||
return s.handleBodyAsAttachment(r, v, m, body) // Case 3
|
return s.handleBodyAsAttachment(r, v, m, body) // Case 3
|
||||||
} else if !body.LimitReached && utf8.Valid(body.PeakedBytes) {
|
} else if !body.LimitReached && utf8.Valid(body.PeakedBytes) {
|
||||||
return s.handleBodyAsTextMessage(m, body) // Case 4
|
return s.handleBodyAsTextMessage(m, body, template) // Case 4
|
||||||
}
|
}
|
||||||
return s.handleBodyAsAttachment(r, v, m, body) // Case 5
|
return s.handleBodyAsAttachment(r, v, m, body) // Case 5
|
||||||
}
|
}
|
||||||
@@ -559,12 +566,33 @@ func (s *Server) handleBodyAsMessageAutoDetect(m *message, body *util.PeakedRead
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleBodyAsTextMessage(m *message, body *util.PeakedReadCloser) error {
|
func (s *Server) handleBodyAsTextMessage(m *message, body *util.PeakedReadCloser, template string) error {
|
||||||
if !utf8.Valid(body.PeakedBytes) {
|
if !utf8.Valid(body.PeakedBytes) {
|
||||||
return errHTTPBadRequestMessageNotUTF8
|
return errHTTPBadRequestMessageNotUTF8
|
||||||
}
|
}
|
||||||
if len(body.PeakedBytes) > 0 { // Empty body should not override message (publish via GET!)
|
if len(body.PeakedBytes) > 0 { // Empty body should not override message (publish via GET!)
|
||||||
m.Message = strings.TrimSpace(string(body.PeakedBytes)) // Truncates the message to the peak limit if required
|
peakedBody := strings.TrimSpace(string(body.PeakedBytes)) // Truncates the message to the peak limit if required
|
||||||
|
if template == "json" && gjson.Valid(peakedBody) {
|
||||||
|
r := regexp.MustCompile(`\${([^}]+)}`)
|
||||||
|
matches := r.FindAllStringSubmatch(m.Message, -1)
|
||||||
|
for _, v := range matches {
|
||||||
|
query := v[1]
|
||||||
|
result := gjson.Get(peakedBody, query)
|
||||||
|
if result.Exists() {
|
||||||
|
m.Message = strings.ReplaceAll(m.Message, fmt.Sprintf("${%s}", query), result.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
matches = r.FindAllStringSubmatch(m.Title, -1)
|
||||||
|
for _, v := range matches {
|
||||||
|
query := v[1]
|
||||||
|
result := gjson.Get(peakedBody, query)
|
||||||
|
if result.Exists() {
|
||||||
|
m.Title = strings.ReplaceAll(m.Title, fmt.Sprintf("${%s}", query), result.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m.Message = peakedBody
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if m.Attachment != nil && m.Attachment.Name != "" && m.Message == "" {
|
if m.Attachment != nil && m.Attachment.Name != "" && m.Message == "" {
|
||||||
m.Message = fmt.Sprintf(defaultAttachmentMessage, m.Attachment.Name)
|
m.Message = fmt.Sprintf(defaultAttachmentMessage, m.Attachment.Name)
|
||||||
|
|||||||
@@ -870,7 +870,7 @@ func TestServer_PublishAttachment(t *testing.T) {
|
|||||||
require.Equal(t, "attachment.txt", msg.Attachment.Name)
|
require.Equal(t, "attachment.txt", msg.Attachment.Name)
|
||||||
require.Equal(t, "text/plain; charset=utf-8", msg.Attachment.Type)
|
require.Equal(t, "text/plain; charset=utf-8", msg.Attachment.Type)
|
||||||
require.Equal(t, int64(5000), msg.Attachment.Size)
|
require.Equal(t, int64(5000), msg.Attachment.Size)
|
||||||
require.GreaterOrEqual(t, msg.Attachment.Expires, time.Now().Add(3*time.Hour).Unix())
|
require.GreaterOrEqual(t, msg.Attachment.Expires, time.Now().Add(179*time.Minute).Unix()) // Almost 3 hours
|
||||||
require.Contains(t, msg.Attachment.URL, "http://127.0.0.1:12345/file/")
|
require.Contains(t, msg.Attachment.URL, "http://127.0.0.1:12345/file/")
|
||||||
require.Equal(t, "", msg.Attachment.Owner) // Should never be returned
|
require.Equal(t, "", msg.Attachment.Owner) // Should never be returned
|
||||||
require.FileExists(t, filepath.Join(s.config.AttachmentCacheDir, msg.ID))
|
require.FileExists(t, filepath.Join(s.config.AttachmentCacheDir, msg.ID))
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Connection from "./Connection";
|
import Connection from "./Connection";
|
||||||
import {sha256} from "./utils";
|
import {hashCode} from "./utils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The connection manager keeps track of active connections (WebSocket connections, see Connection).
|
* The connection manager keeps track of active connections (WebSocket connections, see Connection).
|
||||||
@@ -108,10 +108,9 @@ class ConnectionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const makeConnectionId = async (subscription, user) => {
|
const makeConnectionId = async (subscription, user) => {
|
||||||
const hash = (user)
|
return (user)
|
||||||
? await sha256(`${subscription.id}|${user.username}|${user.password}`)
|
? hashCode(`${subscription.id}|${user.username}|${user.password}`)
|
||||||
: await sha256(`${subscription.id}`);
|
: hashCode(`${subscription.id}`);
|
||||||
return hash.substring(0, 10);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const connectionManager = new ConnectionManager();
|
const connectionManager = new ConnectionManager();
|
||||||
|
|||||||
@@ -115,10 +115,15 @@ export const shuffle = (arr) => {
|
|||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://jameshfisher.com/2017/10/30/web-cryptography-api-hello-world/
|
/** Non-cryptographic hash function, see https://stackoverflow.com/a/8831937/1440785 */
|
||||||
export const sha256 = async (s) => {
|
export const hashCode = async (s) => {
|
||||||
const buf = await crypto.subtle.digest("SHA-256", new TextEncoder("utf-8").encode(s));
|
let hash = 0;
|
||||||
return Array.prototype.map.call(new Uint8Array(buf), x=>(('00'+x.toString(16)).slice(-2))).join('');
|
for (let i = 0; i < s.length; i++) {
|
||||||
|
const char = s.charCodeAt(i);
|
||||||
|
hash = ((hash<<5)-hash)+char;
|
||||||
|
hash = hash & hash; // Convert to 32bit integer
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const formatShortDateTime = (timestamp) => {
|
export const formatShortDateTime = (timestamp) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user