diff --git a/client/client.go b/client/client.go index a670760..86a7a4e 100644 --- a/client/client.go +++ b/client/client.go @@ -34,12 +34,12 @@ type Message struct { Event string Time int64 Topic string + BaseURL string + TopicURL string Message string Title string Priority int Tags []string - BaseURL string - TopicURL string Raw string } @@ -73,7 +73,23 @@ func (c *Client) Publish(topicURL, message string, options ...PublishOption) err return err } -func (c *Client) Subscribe(topicURL string) { +func (c *Client) Poll(topicURL string, options ...SubscribeOption) ([]*Message, error) { + ctx := context.Background() + messages := make([]*Message, 0) + msgChan := make(chan *Message) + errChan := make(chan error) + go func() { + err := performSubscribeRequest(ctx, msgChan, topicURL, options...) + close(msgChan) + errChan <- err + }() + for m := range msgChan { + messages = append(messages, m) + } + return messages, <-errChan +} + +func (c *Client) Subscribe(topicURL string, options ...SubscribeOption) { c.mu.Lock() defer c.mu.Unlock() if _, ok := c.subscriptions[topicURL]; ok { @@ -81,7 +97,7 @@ func (c *Client) Subscribe(topicURL string) { } ctx, cancel := context.WithCancel(context.Background()) c.subscriptions[topicURL] = &subscription{cancel} - go handleConnectionLoop(ctx, c.Messages, topicURL) + go handleSubscribeConnLoop(ctx, c.Messages, topicURL, options...) } func (c *Client) Unsubscribe(topicURL string) { @@ -95,25 +111,30 @@ func (c *Client) Unsubscribe(topicURL string) { return } -func handleConnectionLoop(ctx context.Context, msgChan chan *Message, topicURL string) { +func handleSubscribeConnLoop(ctx context.Context, msgChan chan *Message, topicURL string, options ...SubscribeOption) { for { - if err := handleConnection(ctx, msgChan, topicURL); err != nil { - log.Printf("connection to %s failed: %s", topicURL, err.Error()) + if err := performSubscribeRequest(ctx, msgChan, topicURL, options...); err != nil { + log.Printf("Connection to %s failed: %s", topicURL, err.Error()) } select { case <-ctx.Done(): - log.Printf("connection to %s exited", topicURL) + log.Printf("Connection to %s exited", topicURL) return case <-time.After(5 * time.Second): } } } -func handleConnection(ctx context.Context, msgChan chan *Message, topicURL string) error { +func performSubscribeRequest(ctx context.Context, msgChan chan *Message, topicURL string, options ...SubscribeOption) error { req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/json", topicURL), nil) if err != nil { return err } + for _, option := range options { + if err := option(req); err != nil { + return err + } + } resp, err := http.DefaultClient.Do(req) if err != nil { return err diff --git a/client/options.go b/client/options.go index 8812e4d..62f79d0 100644 --- a/client/options.go +++ b/client/options.go @@ -4,42 +4,24 @@ import ( "net/http" ) -type PublishOption func(r *http.Request) error +type RequestOption func(r *http.Request) error +type PublishOption = RequestOption +type SubscribeOption = RequestOption func WithTitle(title string) PublishOption { - return func(r *http.Request) error { - if title != "" { - r.Header.Set("X-Title", title) - } - return nil - } + return WithHeader("X-Title", title) } func WithPriority(priority string) PublishOption { - return func(r *http.Request) error { - if priority != "" { - r.Header.Set("X-Priority", priority) - } - return nil - } + return WithHeader("X-Priority", priority) } func WithTags(tags string) PublishOption { - return func(r *http.Request) error { - if tags != "" { - r.Header.Set("X-Tags", tags) - } - return nil - } + return WithHeader("X-Tags", tags) } func WithDelay(delay string) PublishOption { - return func(r *http.Request) error { - if delay != "" { - r.Header.Set("X-Delay", delay) - } - return nil - } + return WithHeader("X-Delay", delay) } func WithNoCache() PublishOption { @@ -50,20 +32,32 @@ func WithNoFirebase() PublishOption { return WithHeader("X-Firebase", "no") } -func WithHeader(header, value string) PublishOption { +func WithSince(since string) SubscribeOption { + return WithQueryParam("since", since) +} + +func WithPoll() SubscribeOption { + return WithQueryParam("poll", "1") +} + +func WithScheduled() SubscribeOption { + return WithQueryParam("scheduled", "1") +} + +func WithHeader(header, value string) RequestOption { return func(r *http.Request) error { - r.Header.Set(header, value) + if value != "" { + r.Header.Set(header, value) + } return nil } } -type SubscribeOption func(r *http.Request) error - -func WithSince(since string) PublishOption { +func WithQueryParam(param, value string) RequestOption { return func(r *http.Request) error { - if since != "" { + if value != "" { q := r.URL.Query() - q.Add("since", since) + q.Add(param, value) r.URL.RawQuery = q.Encode() } return nil diff --git a/cmd/app.go b/cmd/app.go index e7234e0..9d99710 100644 --- a/cmd/app.go +++ b/cmd/app.go @@ -36,7 +36,7 @@ func New() *cli.App { func execMainApp(c *cli.Context) error { log.Printf("\x1b[1;33mDeprecation notice: Please run the server using 'ntfy serve'; see 'ntfy -h' for help.\x1b[0m") - log.Printf("\x1b[1;33mThis way of running the server will be removed Feb 2022.\x1b[0m") + log.Printf("\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) } diff --git a/cmd/publish.go b/cmd/publish.go index 274fee0..3db8146 100644 --- a/cmd/publish.go +++ b/cmd/publish.go @@ -9,7 +9,7 @@ import ( var cmdPublish = &cli.Command{ Name: "publish", - Aliases: []string{"pub", "send"}, + Aliases: []string{"pub", "send", "push"}, Usage: "Send message via a ntfy server", UsageText: "ntfy send [OPTIONS..] TOPIC MESSAGE", Action: execPublish, diff --git a/cmd/subscribe.go b/cmd/subscribe.go index 0b03b69..b2d2fc6 100644 --- a/cmd/subscribe.go +++ b/cmd/subscribe.go @@ -21,6 +21,8 @@ var cmdSubscribe = &cli.Command{ Flags: []cli.Flag{ &cli.StringFlag{Name: "exec", Aliases: []string{"e"}, Usage: "execute command for each message event"}, &cli.StringFlag{Name: "since", Aliases: []string{"s"}, Usage: "return events since (Unix timestamp, or all)"}, + &cli.BoolFlag{Name: "poll", Aliases: []string{"p"}, Usage: "return events and exit, do not listen for new events"}, + &cli.BoolFlag{Name: "scheduled", Aliases: []string{"sched", "S"}, Usage: "also return scheduled/delayed events"}, }, Description: `(THIS COMMAND IS INCUBATING. IT MAY CHANGE WITHOUT NOTICE.) @@ -45,7 +47,8 @@ are passed to the command as environment variables: Examples: ntfy subscribe mytopic # Prints JSON for incoming messages to stdout ntfy sub home.lan/backups alerts # Subscribe to two different topics - ntfy sub --exec='notify-send "$m"' mytopic # Execute command for incoming messages' + ntfy sub --exec='notify-send "$m"' mytopic # Execute command for incoming messages + ntfy sub --exec=/my/script topic1 topic2 # Subscribe to two topics and execute command for each message `, } @@ -56,11 +59,37 @@ func execSubscribe(c *cli.Context) error { log.Printf("\x1b[1;33mThis command is incubating. The interface may change without notice.\x1b[0m") cl := client.DefaultClient command := c.String("exec") - for _, topic := range c.Args().Slice() { - cl.Subscribe(expandTopicURL(topic)) + since := c.String("since") + poll := c.Bool("poll") + scheduled := c.Bool("scheduled") + topics := c.Args().Slice() + var options []client.SubscribeOption + if since != "" { + options = append(options, client.WithSince(since)) } - for m := range cl.Messages { - _ = dispatchMessage(c, command, m) + if poll { + options = append(options, client.WithPoll()) + } + if scheduled { + options = append(options, client.WithScheduled()) + } + if poll { + for _, topic := range topics { + messages, err := cl.Poll(expandTopicURL(topic), options...) + if err != nil { + return err + } + for _, m := range messages { + _ = dispatchMessage(c, command, m) + } + } + } else { + for _, topic := range topics { + cl.Subscribe(expandTopicURL(topic), options...) + } + for m := range cl.Messages { + _ = dispatchMessage(c, command, m) + } } return nil } @@ -77,11 +106,9 @@ func execCommand(c *cli.Context, command string, m *client.Message) error { if m.Event == client.OpenEvent { log.Printf("[%s] Connection opened, subscribed to topic", collapseTopicURL(m.TopicURL)) } else if m.Event == client.MessageEvent { - go func() { - if err := runCommandInternal(c, command, m); err != nil { - log.Printf("[%s] Command failed: %s", collapseTopicURL(m.TopicURL), err.Error()) - } - }() + if err := runCommandInternal(c, command, m); err != nil { + log.Printf("[%s] Command failed: %s", collapseTopicURL(m.TopicURL), err.Error()) + } } return nil } diff --git a/docs/deprecations.md b/docs/deprecations.md new file mode 100644 index 0000000..9ecb60e --- /dev/null +++ b/docs/deprecations.md @@ -0,0 +1,25 @@ +# Deprecation notices +This page is used to list deprecation notices for ntfy. Deprecated commands and options will be +**removed after ~3 months** from the time they were deprecated. + +## Active deprecations + +### Running server via `ntfy` (instead of `ntfy serve`) +> since 2021-12-17 + +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 +just the server. + +=== "Before" + ``` + $ ntfy + 2021/12/17 08:16:01 Listening on :80/http + ``` + +=== "After" + ``` + $ ntfy serve + 2021/12/17 08:16:01 Listening on :80/http + ``` + diff --git a/docs/install.md b/docs/install.md index dede928..c9ee08e 100644 --- a/docs/install.md +++ b/docs/install.md @@ -12,7 +12,7 @@ We support amd64, armv7 and arm64. 1. Install ntfy using one of the methods described below 2. Then (optionally) edit `/etc/ntfy/config.yml` (see [configuration](config.md)) -3. Then just run it with `ntfy` (or `systemctl start ntfy` when using the deb/rpm). +3. Then just run it with `ntfy serve` (or `systemctl start ntfy` when using the deb/rpm). ## Binaries and packages Please check out the [releases page](https://github.com/binwiederhier/ntfy/releases) for binaries and @@ -22,21 +22,21 @@ deb/rpm packages. ```bash wget https://github.com/binwiederhier/ntfy/releases/download/v1.7.0/ntfy_1.7.0_linux_x86_64.tar.gz sudo tar -C /usr/bin -zxf ntfy_*.tar.gz ntfy - sudo ./ntfy + sudo ./ntfy serve ``` === "armv7/armhf" ```bash wget https://github.com/binwiederhier/ntfy/releases/download/v1.7.0/ntfy_1.7.0_linux_armv7.tar.gz sudo tar -C /usr/bin -zxf ntfy_*.tar.gz ntfy - sudo ./ntfy + sudo ./ntfy serve ``` === "arm64" ```bash wget https://github.com/binwiederhier/ntfy/releases/download/v1.7.0/ntfy_1.7.0_linux_arm64.tar.gz sudo tar -C /usr/bin -zxf ntfy_*.tar.gz ntfy - sudo ./ntfy + sudo ./ntfy serve ``` ## Debian/Ubuntu repository @@ -132,12 +132,12 @@ The [ntfy image](https://hub.docker.com/r/binwiederhier/ntfy) is available for a straight forward to use. The server exposes its web UI and the API on port 80, so you need to expose that in Docker. To use the persistent -[message cache](config.md#message-cache), you also need to map a volume to `/var/cache/ntfy`. To change other settings, you should map `/etc/ntfy`, -so you can edit `/etc/ntfy/config.yml`. +[message cache](config.md#message-cache), you also need to map a volume to `/var/cache/ntfy`. To change other settings, +you should map `/etc/ntfy`, so you can edit `/etc/ntfy/config.yml`. Basic usage (no cache or additional config): ``` -docker run -p 80:80 -it binwiederhier/ntfy +docker run -p 80:80 -it binwiederhier/ntfy serve ``` With persistent cache (configured as command line arguments): @@ -147,7 +147,8 @@ docker run \ -p 80:80 \ -it \ binwiederhier/ntfy \ - --cache-file /var/cache/ntfy/cache.db + --cache-file /var/cache/ntfy/cache.db \ + serve ``` With other config options (configured via `/etc/ntfy/config.yml`, see [configuration](config.md) for details): @@ -156,7 +157,8 @@ docker run \ -v /etc/ntfy:/etc/ntfy \ -p 80:80 \ -it \ - binwiederhier/ntfy + binwiederhier/ntfy \ + serve ``` ## Go diff --git a/docs/subscribe/cli.md b/docs/subscribe/cli.md new file mode 100644 index 0000000..3e578a8 --- /dev/null +++ b/docs/subscribe/cli.md @@ -0,0 +1,3 @@ +# Subscribe via CLI + +XXXXXXXXXxxx diff --git a/mkdocs.yml b/mkdocs.yml index 6758aea..fdd5979 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,11 +1,11 @@ site_dir: server/docs site_name: ntfy site_url: https://ntfy.sh -site_description: simple HTTP-based pub-sub +site_description: Send push notifications to your phone via PUT/POST copyright: Made with ❤️ by Philipp C. Heckel repo_name: binwiederhier/ntfy repo_url: https://github.com/binwiederhier/ntfy -edit_uri: edit/main/docs/ +edit_uri: blob/main/docs/ theme: name: material @@ -31,7 +31,6 @@ theme: - search.highlight - search.share - navigation.sections - # - navigation.instant - toc.integrate - content.tabs.link extra: @@ -75,6 +74,7 @@ nav: - "Subscribing": - "From your phone": subscribe/phone.md - "From the Web UI": subscribe/web.md + - "Using the CLI": subscribe/cli.md - "Using the API": subscribe/api.md - "Self-hosting": - "Installation": install.md @@ -83,6 +83,7 @@ nav: - "FAQs": faq.md - "Examples": examples.md - "Emojis 🥳 🎉": emojis.md + - "Deprecation notices": deprecations.md - "Development": develop.md - "Privacy policy": privacy.md