Documentation, fix test, return JSON on publish, add --quiet flag for publish
This commit is contained in:
parent
ddd5ce2c21
commit
f24855ca9a
10 changed files with 142 additions and 32 deletions
|
@ -6,6 +6,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -20,6 +21,10 @@ const (
|
||||||
OpenEvent = "open"
|
OpenEvent = "open"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxResponseBytes = 4096
|
||||||
|
)
|
||||||
|
|
||||||
// Client is the ntfy client that can be used to publish and subscribe to ntfy topics
|
// Client is the ntfy client that can be used to publish and subscribe to ntfy topics
|
||||||
type Client struct {
|
type Client struct {
|
||||||
Messages chan *Message
|
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,
|
// To pass title, priority and tags, check out WithTitle, WithPriority, WithTagsList, WithDelay, WithNoCache,
|
||||||
// WithNoFirebase, and the generic WithHeader.
|
// 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)
|
topicURL := c.expandTopicURL(topic)
|
||||||
req, _ := http.NewRequest("POST", topicURL, strings.NewReader(message))
|
req, _ := http.NewRequest("POST", topicURL, strings.NewReader(message))
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
if err := option(req); err != nil {
|
if err := option(req); err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
if resp.StatusCode != http.StatusOK {
|
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
|
// 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()
|
defer resp.Body.Close()
|
||||||
scanner := bufio.NewScanner(resp.Body)
|
scanner := bufio.NewScanner(resp.Body)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
var m *Message
|
m, err := toMessage(scanner.Text(), topicURL)
|
||||||
line := scanner.Text()
|
if err != nil {
|
||||||
if err := json.NewDecoder(strings.NewReader(line)).Decode(&m); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
m.TopicURL = topicURL
|
|
||||||
m.Raw = line
|
|
||||||
msgChan <- m
|
msgChan <- m
|
||||||
}
|
}
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"heckel.io/ntfy/client"
|
"heckel.io/ntfy/client"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -9,17 +10,19 @@ import (
|
||||||
|
|
||||||
var cmdPublish = &cli.Command{
|
var cmdPublish = &cli.Command{
|
||||||
Name: "publish",
|
Name: "publish",
|
||||||
Aliases: []string{"pub", "send", "push", "trigger"},
|
Aliases: []string{"pub", "send", "trigger"},
|
||||||
Usage: "Send message via a ntfy server",
|
Usage: "Send message via a ntfy server",
|
||||||
UsageText: "ntfy send [OPTIONS..] TOPIC MESSAGE",
|
UsageText: "ntfy send [OPTIONS..] TOPIC [MESSAGE]",
|
||||||
Action: execPublish,
|
Action: execPublish,
|
||||||
Flags: []cli.Flag{
|
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: "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: "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: "tags", Aliases: []string{"tag", "T"}, Usage: "comma separated list of tags and emojis"},
|
||||||
&cli.StringFlag{Name: "delay", Aliases: []string{"at", "in"}, Usage: "delay/schedule message"},
|
&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-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: "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.
|
Description: `Publish a message to a ntfy server.
|
||||||
|
|
||||||
|
@ -33,12 +36,19 @@ Examples:
|
||||||
ntfy trigger mywebhook # Sending without message, useful for webhooks
|
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,
|
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 {
|
func execPublish(c *cli.Context) error {
|
||||||
if c.NArg() < 1 {
|
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")
|
title := c.String("title")
|
||||||
priority := c.String("priority")
|
priority := c.String("priority")
|
||||||
|
@ -46,6 +56,7 @@ func execPublish(c *cli.Context) error {
|
||||||
delay := c.String("delay")
|
delay := c.String("delay")
|
||||||
noCache := c.Bool("no-cache")
|
noCache := c.Bool("no-cache")
|
||||||
noFirebase := c.Bool("no-firebase")
|
noFirebase := c.Bool("no-firebase")
|
||||||
|
quiet := c.Bool("quiet")
|
||||||
topic := c.Args().Get(0)
|
topic := c.Args().Get(0)
|
||||||
message := ""
|
message := ""
|
||||||
if c.NArg() > 1 {
|
if c.NArg() > 1 {
|
||||||
|
@ -70,10 +81,13 @@ func execPublish(c *cli.Context) error {
|
||||||
if noFirebase {
|
if noFirebase {
|
||||||
options = append(options, client.WithNoFirebase())
|
options = append(options, client.WithNoFirebase())
|
||||||
}
|
}
|
||||||
conf, err := loadConfig(c)
|
cl := client.New(conf)
|
||||||
|
m, err := cl.Publish(topic, message, options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
cl := client.New(conf)
|
if !quiet {
|
||||||
return cl.Publish(topic, message, options...)
|
fmt.Fprintln(c.App.Writer, strings.TrimSpace(m.Raw))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCLI_Publish_Real_Server(t *testing.T) {
|
func TestCLI_Publish_Subscribe_Poll_Real_Server(t *testing.T) {
|
||||||
testMessage := util.RandomString(10)
|
testMessage := util.RandomString(10)
|
||||||
|
|
||||||
app, _, _, _ := newTestApp()
|
app, _, _, _ := newTestApp()
|
||||||
|
|
|
@ -47,6 +47,10 @@ Examples:
|
||||||
}
|
}
|
||||||
|
|
||||||
func execServe(c *cli.Context) error {
|
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
|
// Read all the options
|
||||||
listenHTTP := c.String("listen-http")
|
listenHTTP := c.String("listen-http")
|
||||||
listenHTTPS := c.String("listen-https")
|
listenHTTPS := c.String("listen-https")
|
||||||
|
|
|
@ -26,7 +26,7 @@ var cmdSubscribe = &cli.Command{
|
||||||
},
|
},
|
||||||
|
|
||||||
Flags: []cli.Flag{
|
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.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: "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"},
|
&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:
|
Examples:
|
||||||
ntfy sub --from-config # Read topics from config file
|
ntfy sub --from-config # Read topics from config file
|
||||||
ntfy sub --config=/my/client.yml --from-config # Read topics from alternate 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 {
|
func execSubscribe(c *cli.Context) error {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
# Installing ntfy
|
# Installing ntfy
|
||||||
The `ntfy` CLI allows you to [publish messages](publish.md), [subscribe to topics](subscribe/cli.md) as well as to
|
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,
|
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.
|
configure it and run it. Just like any other software. No fuzz.
|
||||||
|
|
||||||
!!! info
|
!!! 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
|
If you just want to [send messages using ntfy.sh](publish.md), you don't need to install anything. You can just use
|
||||||
`curl`.
|
`curl`.
|
||||||
|
|
||||||
|
|
4
docs/static/css/extra.css
vendored
4
docs/static/css/extra.css
vendored
|
@ -8,6 +8,10 @@
|
||||||
width: unset !important;
|
width: unset !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admonition {
|
||||||
|
font-size: .74rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
article {
|
article {
|
||||||
padding-bottom: 50px;
|
padding-bottom: 50px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Subscribe via API
|
# 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
|
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),
|
||||||
app or script by subscribing the API. This page describes how to subscribe via API. You may also want to check out the
|
or in your own app or script by subscribing the API. This page describes how to subscribe via API. You may also want to
|
||||||
page that describes how to [publish messages](../publish.md).
|
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 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
|
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"
|
||||||
``` http
|
``` http
|
||||||
GET /disk-alerts/json HTTP/1.1
|
GET /disk-alerts/json HTTP/1.1
|
||||||
|
|
|
@ -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
|
!!! 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.)
|
|
||||||
|
|
|
@ -74,7 +74,7 @@ nav:
|
||||||
- "Subscribing":
|
- "Subscribing":
|
||||||
- "From your phone": subscribe/phone.md
|
- "From your phone": subscribe/phone.md
|
||||||
- "From the Web UI": subscribe/web.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
|
- "Using the API": subscribe/api.md
|
||||||
- "Self-hosting":
|
- "Self-hosting":
|
||||||
- "Installation": install.md
|
- "Installation": install.md
|
||||||
|
|
Loading…
Reference in a new issue