diff --git a/client/client.go b/client/client.go index 0103a1da1c28ab434f05bd6fa0ddd69ac3cc61cd..0a1022c22e565ff0fc54854412f0bbfc04a45d02 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 4d64a347adb3c6c33c7cc856c7ec90f84e107e57..3dd15ddaafc393e9641afd6bb8647496eebb8b8d 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 315413b45775d2ab98f0cb91ee7b603af5d08d58..dc2545ce07b481359b2bbcda5c41c85adf35ce28 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 ef963ebe0a8bfd8923bf81c54bc337d57cb13b68..874cf0e7f44bebbdd89bcc8aa080abf87a496416 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 a967daec2cfd7c543f3d159838f2e606223d1721..8d6147719698f29c9d93538ab9683684edbdbc09 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 45a6c25bf5b637aa017c4e0bae5a8e261fa8073c..b822617b7899c88c3634c97fa4a5c105cca8621f 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 a42c63d4c9f1d4f29aabfc3c8b5039397b3a3bf0..c2be793c0563f64f0c4e992b9619530d87e0d1ca 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 8a57bd6b638dcf0ee6cfb4b98d280e53b26e5197..c11f37b0dcfa9f59743d465b1b8d2d28933979dd 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 9f492b9568c34cd0e33170e7657de96f6d60ae6d..fc64aa4e1c900b89e01367d390665c873650ac7b 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 fdd5979f17b72f3f75cc0b21c1c26205c57d201d..2fec3dc14e415af0e13c5c5cb3e348978ae25c64 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