diff --git a/client/client.go b/client/client.go index 0103a1da..0a1022c2 100644 --- a/client/client.go +++ b/client/client.go @@ -6,6 +6,7 @@ import ( "context" "encoding/json" "fmt" + "io" "log" "net/http" "strings" @@ -20,6 +21,10 @@ const ( OpenEvent = "open" ) +const ( + maxResponseBytes = 4096 +) + // Client is the ntfy client that can be used to publish and subscribe to ntfy topics type Client struct { Messages chan *Message @@ -63,22 +68,31 @@ func New(config *Config) *Client { // // To pass title, priority and tags, check out WithTitle, WithPriority, WithTagsList, WithDelay, WithNoCache, // WithNoFirebase, and the generic WithHeader. -func (c *Client) Publish(topic, message string, options ...PublishOption) error { +func (c *Client) Publish(topic, message string, options ...PublishOption) (*Message, error) { topicURL := c.expandTopicURL(topic) req, _ := http.NewRequest("POST", topicURL, strings.NewReader(message)) for _, option := range options { if err := option(req); err != nil { - return err + return nil, err } } resp, err := http.DefaultClient.Do(req) if err != nil { - return err + return nil, err } + defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return fmt.Errorf("unexpected response %d from server", resp.StatusCode) + return nil, fmt.Errorf("unexpected response %d from server", resp.StatusCode) } - return err + b, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseBytes)) + if err != nil { + return nil, err + } + m, err := toMessage(string(b), topicURL) + if err != nil { + return nil, err + } + return m, nil } // Poll queries a topic for all (or a limited set) of messages. Unlike Subscribe, this method only polls for @@ -192,14 +206,21 @@ func performSubscribeRequest(ctx context.Context, msgChan chan *Message, topicUR defer resp.Body.Close() scanner := bufio.NewScanner(resp.Body) for scanner.Scan() { - var m *Message - line := scanner.Text() - if err := json.NewDecoder(strings.NewReader(line)).Decode(&m); err != nil { + m, err := toMessage(scanner.Text(), topicURL) + if err != nil { return err } - m.TopicURL = topicURL - m.Raw = line msgChan <- m } return nil } + +func toMessage(s, topicURL string) (*Message, error) { + var m *Message + if err := json.NewDecoder(strings.NewReader(s)).Decode(&m); err != nil { + return nil, err + } + m.TopicURL = topicURL + m.Raw = s + return m, nil +} diff --git a/cmd/publish.go b/cmd/publish.go index 4d64a347..3dd15dda 100644 --- a/cmd/publish.go +++ b/cmd/publish.go @@ -2,6 +2,7 @@ package cmd import ( "errors" + "fmt" "github.com/urfave/cli/v2" "heckel.io/ntfy/client" "strings" @@ -9,17 +10,19 @@ import ( var cmdPublish = &cli.Command{ Name: "publish", - Aliases: []string{"pub", "send", "push", "trigger"}, + Aliases: []string{"pub", "send", "trigger"}, Usage: "Send message via a ntfy server", - UsageText: "ntfy send [OPTIONS..] TOPIC MESSAGE", + UsageText: "ntfy send [OPTIONS..] TOPIC [MESSAGE]", Action: execPublish, Flags: []cli.Flag{ + &cli.StringFlag{Name: "config", Aliases: []string{"c"}, Usage: "client config file"}, &cli.StringFlag{Name: "title", Aliases: []string{"t"}, Usage: "message title"}, &cli.StringFlag{Name: "priority", Aliases: []string{"p"}, Usage: "priority of the message (1=min, 2=low, 3=default, 4=high, 5=max)"}, - &cli.StringFlag{Name: "tags", Aliases: []string{"ta"}, Usage: "comma separated list of tags and emojis"}, - &cli.StringFlag{Name: "delay", Aliases: []string{"at", "in"}, Usage: "delay/schedule message"}, + &cli.StringFlag{Name: "tags", Aliases: []string{"tag", "T"}, Usage: "comma separated list of tags and emojis"}, + &cli.StringFlag{Name: "delay", Aliases: []string{"at", "in", "D"}, Usage: "delay/schedule message"}, &cli.BoolFlag{Name: "no-cache", Aliases: []string{"C"}, Usage: "do not cache message server-side"}, &cli.BoolFlag{Name: "no-firebase", Aliases: []string{"F"}, Usage: "do not forward message to Firebase"}, + &cli.BoolFlag{Name: "quiet", Aliases: []string{"q"}, Usage: "do print message"}, }, Description: `Publish a message to a ntfy server. @@ -33,12 +36,19 @@ Examples: ntfy trigger mywebhook # Sending without message, useful for webhooks Please also check out the docs on publishing messages. Especially for the --tags and --delay options, -it has incredibly useful information: https://ntfy.sh/docs/publish/.`, +it has incredibly useful information: https://ntfy.sh/docs/publish/. + +The default config file for all client commands is /etc/ntfy/client.yml (if root user), +or ~/.config/ntfy/client.yml for all other users.`, } func execPublish(c *cli.Context) error { if c.NArg() < 1 { - return errors.New("topic missing") + return errors.New("must specify topic, type 'ntfy publish --help' for help") + } + conf, err := loadConfig(c) + if err != nil { + return err } title := c.String("title") priority := c.String("priority") @@ -46,6 +56,7 @@ func execPublish(c *cli.Context) error { delay := c.String("delay") noCache := c.Bool("no-cache") noFirebase := c.Bool("no-firebase") + quiet := c.Bool("quiet") topic := c.Args().Get(0) message := "" if c.NArg() > 1 { @@ -70,10 +81,13 @@ func execPublish(c *cli.Context) error { if noFirebase { options = append(options, client.WithNoFirebase()) } - conf, err := loadConfig(c) + cl := client.New(conf) + m, err := cl.Publish(topic, message, options...) if err != nil { return err } - cl := client.New(conf) - return cl.Publish(topic, message, options...) + if !quiet { + fmt.Fprintln(c.App.Writer, strings.TrimSpace(m.Raw)) + } + return nil } diff --git a/cmd/publish_test.go b/cmd/publish_test.go index 315413b4..dc2545ce 100644 --- a/cmd/publish_test.go +++ b/cmd/publish_test.go @@ -6,7 +6,7 @@ import ( "testing" ) -func TestCLI_Publish_Real_Server(t *testing.T) { +func TestCLI_Publish_Subscribe_Poll_Real_Server(t *testing.T) { testMessage := util.RandomString(10) app, _, _, _ := newTestApp() diff --git a/cmd/serve.go b/cmd/serve.go index ef963ebe..874cf0e7 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -47,6 +47,10 @@ Examples: } func execServe(c *cli.Context) error { + if c.NArg() > 0 { + return errors.New("no arguments expected, see 'ntfy serve --help' for help") + } + // Read all the options listenHTTP := c.String("listen-http") listenHTTPS := c.String("listen-https") diff --git a/cmd/subscribe.go b/cmd/subscribe.go index a967daec..8d614771 100644 --- a/cmd/subscribe.go +++ b/cmd/subscribe.go @@ -26,7 +26,7 @@ var cmdSubscribe = &cli.Command{ }, Flags: []cli.Flag{ - &cli.StringFlag{Name: "config", Aliases: []string{"c"}, Usage: "config file `FILE`"}, + &cli.StringFlag{Name: "config", Aliases: []string{"c"}, Usage: "client config file"}, &cli.StringFlag{Name: "since", Aliases: []string{"s"}, Usage: "return events since `SINCE` (Unix timestamp, or all)"}, &cli.BoolFlag{Name: "from-config", Aliases: []string{"C"}, Usage: "read subscriptions from config file (service mode)"}, &cli.BoolFlag{Name: "poll", Aliases: []string{"p"}, Usage: "return events and exit, do not listen for new events"}, @@ -72,7 +72,9 @@ ntfy subscribe --from-config Examples: ntfy sub --from-config # Read topics from config file ntfy sub --config=/my/client.yml --from-config # Read topics from alternate config file -`, + +The default config file for all client commands is /etc/ntfy/client.yml (if root user), +or ~/.config/ntfy/client.yml for all other users.`, } func execSubscribe(c *cli.Context) error { diff --git a/docs/install.md b/docs/install.md index 45a6c25b..b822617b 100644 --- a/docs/install.md +++ b/docs/install.md @@ -1,10 +1,10 @@ # Installing ntfy -The `ntfy` CLI allows you to [publish messages](publish.md), [subscribe to topics](subscribe/cli.md) as well as to -**self-host your own ntfy server**. It's all pretty straight forward. Just install the binary, package or Docker image, +The `ntfy` CLI allows you to [publish messages](publish.md), [subscribe to topics](subscribe/cli.md) as well as to +self-host your own ntfy server. It's all pretty straight forward. Just install the binary, package or Docker image, configure it and run it. Just like any other software. No fuzz. !!! info - The following steps are only required if you want to **self-host your own ntfy server** or you want to use the ntfy CLI. + The following steps are only required if you want to **self-host your own ntfy server or you want to use the ntfy CLI**. If you just want to [send messages using ntfy.sh](publish.md), you don't need to install anything. You can just use `curl`. diff --git a/docs/static/css/extra.css b/docs/static/css/extra.css index a42c63d4..c2be793c 100644 --- a/docs/static/css/extra.css +++ b/docs/static/css/extra.css @@ -8,6 +8,10 @@ width: unset !important; } +.admonition { + font-size: .74rem !important; +} + article { padding-bottom: 50px; } diff --git a/docs/subscribe/api.md b/docs/subscribe/api.md index 8a57bd6b..c11f37b0 100644 --- a/docs/subscribe/api.md +++ b/docs/subscribe/api.md @@ -1,7 +1,7 @@ # Subscribe via API -You can create and subscribe to a topic either in the [web UI](web.md), via the [phone app](phone.md), or in your own -app or script by subscribing the API. This page describes how to subscribe via API. You may also want to check out the -page that describes how to [publish messages](../publish.md). +You can create and subscribe to a topic in the [web UI](web.md), via the [phone app](phone.md), via the [ntfy CLI](cli.md), +or in your own app or script by subscribing the API. This page describes how to subscribe via API. You may also want to +check out the page that describes how to [publish messages](../publish.md). The subscription API relies on a simple HTTP GET request with a streaming HTTP response, i.e **you open a GET request and the connection stays open forever**, sending messages back as they come in. There are three different API endpoints, which @@ -26,6 +26,13 @@ recommended way to subscribe to a topic**. The notable exception is JavaScript, ... ``` +=== "ntfy CLI" + ``` + $ ntfy subcribe disk-alerts + {"id":"hwQ2YpKdmg","time":1635528741,"event":"message","topic":"mytopic","message":"Disk full"} + ... + ``` + === "HTTP" ``` http GET /disk-alerts/json HTTP/1.1 diff --git a/docs/subscribe/cli.md b/docs/subscribe/cli.md index 9f492b95..fc64aa4e 100644 --- a/docs/subscribe/cli.md +++ b/docs/subscribe/cli.md @@ -1,6 +1,64 @@ -# Subscribe via CLI +# Subscribe via ntfy CLI +In addition to subscribing via the [web UI](web.md), the [phone app](phone.md), or the [API](api.md), you can subscribe +to topics via the ntfy CLI. The CLI is included in the same `ntfy` binary that can be used to [self-host a server](../install.md). !!! info - The `ntfy subscribe` command is incubating. It's very much work in progress. + The **ntfy CLI is not required to send or receive messages**. You can instead [send messages with curl](../publish.md), + and even use it to [subscribe to topics](api.md). It may be a little more convenient to use the ntfy CLI than writing + your own script. Or it may not be. It all depends on the use case. 😀 + +## Install + configure +To install the ntfy CLI, simply follow the steps outlined on the [install page](../install.md). The ntfy server and +client are the same binary, so it's all very convenient. After installing, you can (optionally) configure the client +by creating `~/.config/ntfy/client.yml` (for the non-root user), or `/etc/ntfy/client.yml` (for the root user). You +can find a [skeleton config](https://github.com/binwiederhier/ntfy/blob/main/client/client.yml) on GitHub. + +If you just want to use [ntfy.sh](https://ntfy.sh), you don't have to change anything. If you **self-host your own server**, +you may want to edit the `default-host` option: + +``` yaml +# Base URL used to expand short topic names in the "ntfy publish" and "ntfy subscribe" commands. +# If you self-host a ntfy server, you'll likely want to change this. +# +default-host: https://ntfy.myhost.com +``` + +## Sending messages +You can send messages with the ntfy CLI using the `ntfy publish` command (or any of its aliases `pub`, `send` or +`trigger`). There are a lot of examples on the page about [publishing messages](../publish.md), but here are a few +quick ones: + +=== "Simple send" + ``` + ntfy publish mytopic This is a message + ntfy publish mytopic "This is a message" + ntfy pub mytopic "This is a message" + ``` + +=== "Send with title, priority, and tags" + ``` + ntfy publish \ + --title="Thing sold on eBay" \ + --priority=high \ + --tags=partying_face \ + mytopic \ + "Somebody just bought the thing that you sell" + ``` + +=== "Triggering a webhook" + ``` + ntfy trigger mywebhook + ntfy pub mywebhook + ``` + +## Using the systemd service + +``` +[Service] +User=pheckel +Group=pheckel +Environment="DISPLAY=:0" "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus" +``` + +Here's an example for a complete client config for a self-hosted server: -(This page is a stub. I'll write something once I'm happy with what the command looks like.) diff --git a/mkdocs.yml b/mkdocs.yml index fdd5979f..2fec3dc1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -74,7 +74,7 @@ nav: - "Subscribing": - "From your phone": subscribe/phone.md - "From the Web UI": subscribe/web.md - - "Using the CLI": subscribe/cli.md + - "From the CLI": subscribe/cli.md - "Using the API": subscribe/api.md - "Self-hosting": - "Installation": install.md