Docs, LoadConfig, config test
This commit is contained in:
parent
66c749d5f0
commit
68d881291c
11 changed files with 130 additions and 41 deletions
|
@ -14,6 +14,8 @@
|
|||
# command: /usr/local/bin/mytopic-triggered.sh
|
||||
# - topic: myserver.com/anothertopic
|
||||
# command: 'echo "$message"'
|
||||
# if:
|
||||
# priority: high,urgent
|
||||
#
|
||||
# Variables:
|
||||
# Variable Aliases Description
|
||||
|
@ -26,4 +28,8 @@
|
|||
# $NTFY_PRIORITY $priority, $p Message priority (1=min, 5=max)
|
||||
# $NTFY_TAGS $tags, $ta Message tags (comma separated list)
|
||||
#
|
||||
# Filters ('if:'):
|
||||
# You can filter 'message', 'title', 'priority' (comma-separated list, logical OR)
|
||||
# and 'tags' (comma-separated list, logical AND). See https://ntfy.sh/docs/subscribe/api/#filter-messages.
|
||||
#
|
||||
# subscribe:
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"gopkg.in/yaml.v2"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultBaseURL is the base URL used to expand short topic names
|
||||
DefaultBaseURL = "https://ntfy.sh"
|
||||
|
@ -22,3 +27,16 @@ func NewConfig() *Config {
|
|||
Subscribe: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// LoadConfig loads the Client config from a yaml file
|
||||
func LoadConfig(filename string) (*Config, error) {
|
||||
b, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := NewConfig()
|
||||
if err := yaml.Unmarshal(b, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
|
35
client/config_test.go
Normal file
35
client/config_test.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConfig_Load(t *testing.T) {
|
||||
filename := filepath.Join(t.TempDir(), "client.yml")
|
||||
require.Nil(t, os.WriteFile(filename, []byte(`
|
||||
default-host: http://localhost
|
||||
subscribe:
|
||||
- topic: no-command
|
||||
- topic: echo-this
|
||||
command: 'echo "Message received: $message"'
|
||||
- topic: alerts
|
||||
command: notify-send -i /usr/share/ntfy/logo.png "Important" "$m"
|
||||
if:
|
||||
priority: high,urgent
|
||||
`), 0600))
|
||||
|
||||
conf, err := LoadConfig(filename)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "http://localhost", conf.DefaultHost)
|
||||
require.Equal(t, 3, len(conf.Subscribe))
|
||||
require.Equal(t, "no-command", conf.Subscribe[0].Topic)
|
||||
require.Equal(t, "", conf.Subscribe[0].Command)
|
||||
require.Equal(t, "echo-this", conf.Subscribe[1].Topic)
|
||||
require.Equal(t, `echo "Message received: $message"`, conf.Subscribe[1].Command)
|
||||
require.Equal(t, "alerts", conf.Subscribe[2].Topic)
|
||||
require.Equal(t, `notify-send -i /usr/share/ntfy/logo.png "Important" "$m"`, conf.Subscribe[2].Command)
|
||||
require.Equal(t, "high,urgent", conf.Subscribe[2].If["priority"])
|
||||
}
|
|
@ -4,7 +4,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gopkg.in/yaml.v2"
|
||||
"heckel.io/ntfy/client"
|
||||
"heckel.io/ntfy/util"
|
||||
"log"
|
||||
|
@ -225,7 +224,7 @@ func envVar(value string, vars ...string) []string {
|
|||
func loadConfig(c *cli.Context) (*client.Config, error) {
|
||||
filename := c.String("config")
|
||||
if filename != "" {
|
||||
return loadConfigFromFile(filename)
|
||||
return client.LoadConfig(filename)
|
||||
}
|
||||
u, _ := user.Current()
|
||||
configFile := defaultClientRootConfigFile
|
||||
|
@ -233,19 +232,7 @@ func loadConfig(c *cli.Context) (*client.Config, error) {
|
|||
configFile = util.ExpandHome(defaultClientUserConfigFile)
|
||||
}
|
||||
if s, _ := os.Stat(configFile); s != nil {
|
||||
return loadConfigFromFile(configFile)
|
||||
return client.LoadConfig(configFile)
|
||||
}
|
||||
return client.NewConfig(), nil
|
||||
}
|
||||
|
||||
func loadConfigFromFile(filename string) (*client.Config, error) {
|
||||
b, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := client.NewConfig()
|
||||
if err := yaml.Unmarshal(b, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
|
BIN
docs/static/img/cli-subscribe-video-3.webm
vendored
BIN
docs/static/img/cli-subscribe-video-3.webm
vendored
Binary file not shown.
|
@ -217,16 +217,25 @@ curl -s "ntfy.sh/mytopic/json?poll=1&sched=1"
|
|||
|
||||
### Filter messages
|
||||
You can filter which messages are returned based on the well-known message fields `message`, `title`, `priority` and
|
||||
`tags`. Currently, only exact matches are supported. Here's an example that only returns messages of high priority
|
||||
with the tag "zfs-error":
|
||||
`tags`. Here's an example that only returns messages of high or urgent priority that contains the both tags
|
||||
"zfs-error" and "error". Note that the `priority` filter is a logical OR and the `tags` filter is a logical AND.
|
||||
|
||||
```
|
||||
$ curl "ntfy.sh/alerts/json?priority=high&tags=zfs-error"
|
||||
{"id":"0TIkJpBcxR","time":1640122627,"event":"open","topic":"alerts"}
|
||||
{"id":"X3Uzz9O1sM","time":1640122674,"event":"message","topic":"alerts","priority":4,"tags":["zfs-error"],
|
||||
"message":"ZFS pool corruption detected"}
|
||||
{"id":"X3Uzz9O1sM","time":1640122674,"event":"message","topic":"alerts","priority":4,
|
||||
"tags":["error", "zfs-error"], "message":"ZFS pool corruption detected"}
|
||||
```
|
||||
|
||||
Available filters (all case-insensitive):
|
||||
|
||||
| Filter variable | Alias | Example | Description |
|
||||
|---|---|---|---|
|
||||
| `message` | `X-Message`, `m` | `ntfy.sh/mytopic?some_message` | Only return messages that match this exact message string |
|
||||
| `title` | `X-Title`, `t` | `ntfy.sh/mytopic?title=some+title` | Only return messages that match this exact title string |
|
||||
| `priority` | `X-Priority`, `prio`, `p` | `ntfy.sh/mytopic?p=high,urgent` | Only return messages that match *any priority listed* (comma-separated) |
|
||||
| `tags` | `X-Tags`, `tag`, `ta` | `ntfy.sh/mytopic?tags=error,alert` | Only return messages that match *all listed tags* (comma-separated) |
|
||||
|
||||
### Subscribe to multiple topics
|
||||
It's possible to subscribe to multiple topics in one HTTP call by providing a comma-separated list of topics
|
||||
in the URL. This allows you to reduce the number of connections you have to maintain:
|
||||
|
@ -314,5 +323,5 @@ and can be passed as **HTTP headers** or **query parameters in the URL**. They a
|
|||
| `scheduled` | `X-Scheduled`, `sched` | Include scheduled/delayed messages in message list |
|
||||
| `message` | `X-Message`, `m` | Filter: Only return messages that match this exact message string |
|
||||
| `title` | `X-Title`, `t` | Filter: Only return messages that match this exact title string |
|
||||
| `priority` | `X-Priority`, `prio`, `p` | Filter: Only return messages that match this priority |
|
||||
| `tags` | `X-Tags`, `tag`, `ta` | Filter: Only return messages that all listed tags (comma-separated) |
|
||||
| `priority` | `X-Priority`, `prio`, `p` | Filter: Only return messages that match *any priority listed* (comma-separated) |
|
||||
| `tags` | `X-Tags`, `tag`, `ta` | Filter: Only return messages that match *all listed tags* (comma-separated) |
|
||||
|
|
|
@ -125,25 +125,31 @@ Here's an example config file that subscribes to three different topics, executi
|
|||
=== "~/.config/ntfy/client.yml"
|
||||
```yaml
|
||||
subscribe:
|
||||
- topic: echo-this
|
||||
command: 'echo "Message received: $message"'
|
||||
- topic: get-temp
|
||||
command: |
|
||||
temp="$(sensors | awk '/Package/ { print $4 }')"
|
||||
ntfy publish --quiet temp "$temp";
|
||||
echo "CPU temp is $temp; published to topic 'temp'"
|
||||
- topic: echo-this
|
||||
command: 'echo "Message received: $message"'
|
||||
- topic: alerts
|
||||
command: notify-send "$m"
|
||||
command: notify-send -i /usr/share/ntfy/logo.png "Important" "$m"
|
||||
if:
|
||||
priority: high,urgent
|
||||
- topic: calc
|
||||
command: 'gnome-calculator 2>/dev/null &'
|
||||
```
|
||||
- topic: print-temp
|
||||
command: |
|
||||
echo "You can easily run inline scripts, too."
|
||||
temp="$(sensors | awk '/Pack/ { print substr($4,2,2) }')"
|
||||
if [ $temp -gt 80 ]; then
|
||||
echo "Warning: CPU temperature is $temp. Too high."
|
||||
else
|
||||
echo "CPU temperature is $temp. That's alright."
|
||||
fi
|
||||
```
|
||||
|
||||
In this example, when `ntfy subscribe --from-config` is executed:
|
||||
|
||||
* Messages to topic `echo-this` will be simply echoed to standard out
|
||||
* Messages to topic `get-temp` will publish the CPU core temperature to topic `temp`
|
||||
* Messages to topic `alerts` will be displayed as desktop notification using `notify-send`
|
||||
* And messages to topic `calc` will open the gnome calculator 😀 (*because, why not*)
|
||||
* Messages to `echo-this` simply echos to standard out
|
||||
* Messages to `alerts` display as desktop notification for high priority messages using `notify-send`
|
||||
* Messages to `calc` open the gnome calculator 😀 (*because, why not*)
|
||||
* Messages to `print-temp` execute an inline script and print the CPU temperature
|
||||
|
||||
I hope this shows how powerful this command is. Here's a short video that demonstrates the above example:
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ if [ "$1" = "configure" ] && [ -d /run/systemd/system ]; then
|
|||
fi
|
||||
fi
|
||||
|
||||
# Restart service
|
||||
# Restart services
|
||||
systemctl --system daemon-reload >/dev/null || true
|
||||
if systemctl is-active -q ntfy.service; then
|
||||
echo "Restarting ntfy.service ..."
|
||||
|
@ -32,4 +32,12 @@ if [ "$1" = "configure" ] && [ -d /run/systemd/system ]; then
|
|||
systemctl restart ntfy.service >/dev/null || true
|
||||
fi
|
||||
fi
|
||||
if systemctl is-active -q ntfy-client.service; then
|
||||
echo "Restarting ntfy-client.service ..."
|
||||
if [ -x /usr/bin/deb-systemd-invoke ]; then
|
||||
deb-systemd-invoke try-restart ntfy-client.service >/dev/null || true
|
||||
else
|
||||
systemctl restart ntfy-client.service >/dev/null || true
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
|
|
@ -480,15 +480,22 @@ func (s *Server) handleSubscribe(w http.ResponseWriter, r *http.Request, v *visi
|
|||
}
|
||||
}
|
||||
|
||||
func parseQueryFilters(r *http.Request) (messageFilter string, titleFilter string, priorityFilter int, tagsFilter []string, err error) {
|
||||
func parseQueryFilters(r *http.Request) (messageFilter string, titleFilter string, priorityFilter []int, tagsFilter []string, err error) {
|
||||
messageFilter = readParam(r, "x-message", "message", "m")
|
||||
titleFilter = readParam(r, "x-title", "title", "t")
|
||||
tagsFilter = util.SplitNoEmpty(readParam(r, "x-tags", "tags", "tag", "ta"), ",")
|
||||
priorityFilter, err = util.ParsePriority(readParam(r, "x-priority", "priority", "prio", "p"))
|
||||
return // may be err!
|
||||
priorityFilter = make([]int, 0)
|
||||
for _, p := range util.SplitNoEmpty(readParam(r, "x-priority", "priority", "prio", "p"), ",") {
|
||||
priority, err := util.ParsePriority(p)
|
||||
if err != nil {
|
||||
return "", "", nil, nil, err
|
||||
}
|
||||
priorityFilter = append(priorityFilter, priority)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func passesQueryFilter(msg *message, messageFilter string, titleFilter string, priorityFilter int, tagsFilter []string) bool {
|
||||
func passesQueryFilter(msg *message, messageFilter string, titleFilter string, priorityFilter []int, tagsFilter []string) bool {
|
||||
if msg.Event != messageEvent {
|
||||
return true // filters only apply to messages
|
||||
}
|
||||
|
@ -502,7 +509,7 @@ func passesQueryFilter(msg *message, messageFilter string, titleFilter string, p
|
|||
if messagePriority == 0 {
|
||||
messagePriority = 3 // For query filters, default priority (3) is the same as "not set" (0)
|
||||
}
|
||||
if priorityFilter > 0 && messagePriority != priorityFilter {
|
||||
if len(priorityFilter) > 0 && !util.InIntList(priorityFilter, messagePriority) {
|
||||
return false
|
||||
}
|
||||
if len(tagsFilter) > 0 && !util.InStringListAll(msg.Tags, tagsFilter) {
|
||||
|
|
|
@ -408,6 +408,9 @@ func TestServer_PollWithQueryFilters(t *testing.T) {
|
|||
queriesThatShouldReturnMessageOne := []string{
|
||||
"/mytopic/json?poll=1&priority=1",
|
||||
"/mytopic/json?poll=1&priority=min",
|
||||
"/mytopic/json?poll=1&priority=min,low",
|
||||
"/mytopic/json?poll=1&priority=1,2",
|
||||
"/mytopic/json?poll=1&p=2,min",
|
||||
"/mytopic/json?poll=1&tags=tag1",
|
||||
"/mytopic/json?poll=1&tags=tag1,tag2",
|
||||
"/mytopic/json?poll=1&message=my+first+message",
|
||||
|
|
12
util/util.go
12
util/util.go
|
@ -18,7 +18,7 @@ var (
|
|||
random = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
randomMutex = sync.Mutex{}
|
||||
|
||||
errInvalidPriority = errors.New("unknown priority")
|
||||
errInvalidPriority = errors.New("invalid priority")
|
||||
)
|
||||
|
||||
// FileExists checks if a file exists, and returns true if it does
|
||||
|
@ -50,6 +50,16 @@ func InStringListAll(haystack []string, needles []string) bool {
|
|||
return matches == len(needles)
|
||||
}
|
||||
|
||||
// InIntList returns true if needle is contained in haystack
|
||||
func InIntList(haystack []int, needle int) bool {
|
||||
for _, s := range haystack {
|
||||
if s == needle {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SplitNoEmpty splits a string using strings.Split, but filters out empty strings
|
||||
func SplitNoEmpty(s string, sep string) []string {
|
||||
res := make([]string, 0)
|
||||
|
|
Loading…
Reference in a new issue