mirror of
https://github.com/binwiederhier/ntfy.git
synced 2025-05-18 13:04:34 +02:00
Continued work on Windows CLI
This commit is contained in:
parent
7d473488de
commit
613348d37e
9 changed files with 135 additions and 71 deletions
|
@ -9,11 +9,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
defaultClientRootConfigFile = "/etc/ntfy/client.yml"
|
|
||||||
defaultClientUserConfigFile = "~/.config/ntfy/client.yml"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
categoryClient = "Client commands"
|
categoryClient = "Client commands"
|
||||||
categoryServer = "Server commands"
|
categoryServer = "Server commands"
|
||||||
|
|
|
@ -63,8 +63,7 @@ Examples:
|
||||||
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),
|
` + defaultClientConfigFileDescriptionSuffix,
|
||||||
or ~/.config/ntfy/client.yml for all other users.`,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func execPublish(c *cli.Context) error {
|
func execPublish(c *cli.Context) error {
|
||||||
|
|
|
@ -6,10 +6,7 @@ import (
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"heckel.io/ntfy/client"
|
"heckel.io/ntfy/client"
|
||||||
"heckel.io/ntfy/util"
|
"heckel.io/ntfy/util"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"os/user"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -64,19 +61,17 @@ ntfy subscribe TOPIC COMMAND
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
ntfy sub mytopic 'notify-send "$m"' # Execute command for incoming messages
|
ntfy sub mytopic 'notify-send "$m"' # Execute command for incoming messages
|
||||||
ntfy sub topic1 /my/script.sh # Execute script for incoming messages
|
ntfy sub topic1 myscript.sh # Execute script for incoming messages
|
||||||
|
|
||||||
ntfy subscribe --from-config
|
ntfy subscribe --from-config
|
||||||
Service mode (used in ntfy-client.service). This reads the config file (/etc/ntfy/client.yml
|
Service mode (used in ntfy-client.service). This reads the config file and sets up
|
||||||
or ~/.config/ntfy/client.yml) and sets up subscriptions for every topic in the "subscribe:"
|
subscriptions for every topic in the "subscribe:" block (see config file).
|
||||||
block (see config file).
|
|
||||||
|
|
||||||
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=myclient.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),
|
` + defaultClientConfigFileDescriptionSuffix,
|
||||||
or ~/.config/ntfy/client.yml for all other users.`,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func execSubscribe(c *cli.Context) error {
|
func execSubscribe(c *cli.Context) error {
|
||||||
|
@ -160,7 +155,7 @@ func doPollSingle(c *cli.Context, cl *client.Client, topic, command string, opti
|
||||||
}
|
}
|
||||||
|
|
||||||
func doSubscribe(c *cli.Context, cl *client.Client, conf *client.Config, topic, command string, options ...client.SubscribeOption) error {
|
func doSubscribe(c *cli.Context, cl *client.Client, conf *client.Config, topic, command string, options ...client.SubscribeOption) error {
|
||||||
commands := make(map[string]string) // Subscription ID -> command
|
cmds := make(map[string]string) // Subscription ID -> command
|
||||||
for _, s := range conf.Subscribe { // May be nil
|
for _, s := range conf.Subscribe { // May be nil
|
||||||
topicOptions := append(make([]client.SubscribeOption, 0), options...)
|
topicOptions := append(make([]client.SubscribeOption, 0), options...)
|
||||||
for filter, value := range s.If {
|
for filter, value := range s.If {
|
||||||
|
@ -170,18 +165,18 @@ func doSubscribe(c *cli.Context, cl *client.Client, conf *client.Config, topic,
|
||||||
topicOptions = append(topicOptions, client.WithBasicAuth(s.User, s.Password))
|
topicOptions = append(topicOptions, client.WithBasicAuth(s.User, s.Password))
|
||||||
}
|
}
|
||||||
subscriptionID := cl.Subscribe(s.Topic, topicOptions...)
|
subscriptionID := cl.Subscribe(s.Topic, topicOptions...)
|
||||||
commands[subscriptionID] = s.Command
|
cmds[subscriptionID] = s.Command
|
||||||
}
|
}
|
||||||
if topic != "" {
|
if topic != "" {
|
||||||
subscriptionID := cl.Subscribe(topic, options...)
|
subscriptionID := cl.Subscribe(topic, options...)
|
||||||
commands[subscriptionID] = command
|
cmds[subscriptionID] = command
|
||||||
}
|
}
|
||||||
for m := range cl.Messages {
|
for m := range cl.Messages {
|
||||||
command, ok := commands[m.SubscriptionID]
|
cmd, ok := cmds[m.SubscriptionID]
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
printMessageOrRunCommand(c, m, command)
|
printMessageOrRunCommand(c, m, cmd)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -200,33 +195,6 @@ func runCommand(c *cli.Context, command string, m *client.Message) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCommandInternal(c *cli.Context, command string, m *client.Message) error {
|
|
||||||
scriptFile, err := createTmpScript(command)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer os.Remove(scriptFile)
|
|
||||||
verbose := c.Bool("verbose")
|
|
||||||
if verbose {
|
|
||||||
log.Printf("[%s] Executing: %s (for message: %s)", util.ShortTopicURL(m.TopicURL), command, m.Raw)
|
|
||||||
}
|
|
||||||
cmd := exec.Command("sh", "-c", scriptFile)
|
|
||||||
cmd.Stdin = c.App.Reader
|
|
||||||
cmd.Stdout = c.App.Writer
|
|
||||||
cmd.Stderr = c.App.ErrWriter
|
|
||||||
cmd.Env = envVars(m)
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func createTmpScript(command string) (string, error) {
|
|
||||||
scriptFile := fmt.Sprintf("%s/ntfy-subscribe-%s.sh.tmp", os.TempDir(), util.RandomString(10))
|
|
||||||
script := fmt.Sprintf("#!/bin/sh\n%s", command)
|
|
||||||
if err := os.WriteFile(scriptFile, []byte(script), 0700); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return scriptFile, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func envVars(m *client.Message) []string {
|
func envVars(m *client.Message) []string {
|
||||||
env := os.Environ()
|
env := os.Environ()
|
||||||
env = append(env, envVar(m.ID, "NTFY_ID", "id")...)
|
env = append(env, envVar(m.ID, "NTFY_ID", "id")...)
|
||||||
|
@ -253,11 +221,7 @@ func loadConfig(c *cli.Context) (*client.Config, error) {
|
||||||
if filename != "" {
|
if filename != "" {
|
||||||
return client.LoadConfig(filename)
|
return client.LoadConfig(filename)
|
||||||
}
|
}
|
||||||
u, _ := user.Current()
|
configFile := defaultConfigFile()
|
||||||
configFile := defaultClientRootConfigFile
|
|
||||||
if u.Uid != "0" {
|
|
||||||
configFile = util.ExpandHome(defaultClientUserConfigFile)
|
|
||||||
}
|
|
||||||
if s, _ := os.Stat(configFile); s != nil {
|
if s, _ := os.Stat(configFile); s != nil {
|
||||||
return client.LoadConfig(configFile)
|
return client.LoadConfig(configFile)
|
||||||
}
|
}
|
||||||
|
|
57
cmd/subscribe_linux.go
Normal file
57
cmd/subscribe_linux.go
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
"heckel.io/ntfy/client"
|
||||||
|
"heckel.io/ntfy/util"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultClientRootConfigFile = "/etc/ntfy/client.yml"
|
||||||
|
defaultClientUserConfigFileRelative = "ntfy/client.yml"
|
||||||
|
defaultClientConfigFileDescriptionSuffix = `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 runCommandInternal(c *cli.Context, command string, m *client.Message) error {
|
||||||
|
scriptFile, err := createTmpScript(command)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer os.Remove(scriptFile)
|
||||||
|
verbose := c.Bool("verbose")
|
||||||
|
if verbose {
|
||||||
|
log.Printf("[%s] Executing: %s (for message: %s)", util.ShortTopicURL(m.TopicURL), command, m.Raw)
|
||||||
|
}
|
||||||
|
cmd := exec.Command("sh", "-c", scriptFile)
|
||||||
|
cmd.Stdin = c.App.Reader
|
||||||
|
cmd.Stdout = c.App.Writer
|
||||||
|
cmd.Stderr = c.App.ErrWriter
|
||||||
|
cmd.Env = envVars(m)
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTmpScript(command string) (string, error) {
|
||||||
|
scriptFile := fmt.Sprintf("%s/ntfy-subscribe-%s.sh.tmp", os.TempDir(), util.RandomString(10))
|
||||||
|
script := fmt.Sprintf("#!/bin/sh\n%s", command)
|
||||||
|
if err := os.WriteFile(scriptFile, []byte(script), 0700); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return scriptFile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultConfigFile() string {
|
||||||
|
u, _ := user.Current()
|
||||||
|
configFile := defaultClientRootConfigFile
|
||||||
|
if u.Uid != "0" {
|
||||||
|
homeDir, _ := os.UserConfigDir()
|
||||||
|
return filepath.Join(homeDir, defaultClientUserConfigFileRelative)
|
||||||
|
}
|
||||||
|
return configFile
|
||||||
|
}
|
48
cmd/subscribe_windows.go
Normal file
48
cmd/subscribe_windows.go
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
"heckel.io/ntfy/client"
|
||||||
|
"heckel.io/ntfy/util"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultClientUserConfigFileRelative = "ntfy\\client.yml"
|
||||||
|
defaultClientConfigFileDescriptionSuffix = `The default config file for all client commands is %AppData%\ntfy\client.yml.`
|
||||||
|
)
|
||||||
|
|
||||||
|
func runCommandInternal(c *cli.Context, command string, m *client.Message) error {
|
||||||
|
scriptFile, err := createTmpScript(command)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer os.Remove(scriptFile)
|
||||||
|
verbose := c.Bool("verbose")
|
||||||
|
if verbose {
|
||||||
|
log.Printf("[%s] Executing: %s (for message: %s)", util.ShortTopicURL(m.TopicURL), command, m.Raw)
|
||||||
|
}
|
||||||
|
cmd := exec.Command("cmd.exe", "/Q", "/C", scriptFile)
|
||||||
|
cmd.Stdin = c.App.Reader
|
||||||
|
cmd.Stdout = c.App.Writer
|
||||||
|
cmd.Stderr = c.App.ErrWriter
|
||||||
|
cmd.Env = envVars(m)
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTmpScript(command string) (string, error) {
|
||||||
|
scriptFile := fmt.Sprintf("%s/ntfy-subscribe-%s.bat", os.TempDir(), util.RandomString(10))
|
||||||
|
if err := os.WriteFile(scriptFile, []byte(command), 0700); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return scriptFile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultConfigFile() string {
|
||||||
|
homeDir, _ := os.UserConfigDir()
|
||||||
|
return filepath.Join(homeDir, defaultClientUserConfigFileRelative)
|
||||||
|
}
|
|
@ -38,7 +38,7 @@ Here's an example showing how to publish a simple message using a POST request:
|
||||||
|
|
||||||
=== "PowerShell"
|
=== "PowerShell"
|
||||||
``` powershell
|
``` powershell
|
||||||
Invoke-RestMethod -Method 'Post' -Uri https://ntfy.sh/topic -Body "Backup successful 😀" -UseBasicParsing
|
Invoke-RestMethod -Method 'Post' -Uri https://ntfy.sh/mytopic -Body "Backup successful" -UseBasicParsing
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "Python"
|
=== "Python"
|
||||||
|
|
|
@ -145,12 +145,27 @@ Here's an example config file that subscribes to three different topics, executi
|
||||||
fi
|
fi
|
||||||
```
|
```
|
||||||
|
|
||||||
|
=== "%AppData%\ntfy\client.yml"
|
||||||
|
```
|
||||||
|
subscribe:
|
||||||
|
- topic: echo-this
|
||||||
|
command: 'echo Message received: %message%'
|
||||||
|
- topic: calc
|
||||||
|
command: calc
|
||||||
|
if:
|
||||||
|
priority: high,urgent
|
||||||
|
- topic: toastthis
|
||||||
|
command: |
|
||||||
|
notifu /p "a title: %NTFY_TITLE%" /m "%NTFY_MESSAGE%"
|
||||||
|
exit 0
|
||||||
|
```
|
||||||
|
|
||||||
In this example, when `ntfy subscribe --from-config` is executed:
|
In this example, when `ntfy subscribe --from-config` is executed:
|
||||||
|
|
||||||
* Messages to `echo-this` simply echos to standard out
|
* Messages to `echo-this` simply echos to standard out
|
||||||
* Messages to `alerts` display as desktop notification for high priority messages using [notify-send](https://manpages.ubuntu.com/manpages/focal/man1/notify-send.1.html)
|
* Messages to `alerts` display as desktop notification for high priority messages using [notify-send](https://manpages.ubuntu.com/manpages/focal/man1/notify-send.1.html)
|
||||||
* Messages to `calc` open the gnome calculator 😀 (*because, why not*)
|
* Messages to `calc` open the gnome calculator 😀 (*because, why not*)
|
||||||
* Messages to `print-temp` execute an inline script and print the CPU temperature
|
* Messages to `print-temp` execute an inline script and print the CPU temperature (Linux version only)
|
||||||
|
|
||||||
I hope this shows how powerful this command is. Here's a short video that demonstrates the above example:
|
I hope this shows how powerful this command is. Here's a short video that demonstrates the above example:
|
||||||
|
|
||||||
|
|
|
@ -183,11 +183,6 @@ func PriorityString(priority int) (string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExpandHome replaces "~" with the user's home directory
|
|
||||||
func ExpandHome(path string) string {
|
|
||||||
return os.ExpandEnv(strings.ReplaceAll(path, "~", "$HOME"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShortTopicURL shortens the topic URL to be human-friendly, removing the http:// or https://
|
// ShortTopicURL shortens the topic URL to be human-friendly, removing the http:// or https://
|
||||||
func ShortTopicURL(s string) string {
|
func ShortTopicURL(s string) string {
|
||||||
return strings.TrimPrefix(strings.TrimPrefix(s, "https://"), "http://")
|
return strings.TrimPrefix(strings.TrimPrefix(s, "https://"), "http://")
|
||||||
|
|
|
@ -3,7 +3,6 @@ package util
|
||||||
import (
|
import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -75,14 +74,6 @@ func TestSplitNoEmpty(t *testing.T) {
|
||||||
require.Equal(t, []string{"tag1", "tag2"}, SplitNoEmpty("tag1,tag2,", ","))
|
require.Equal(t, []string{"tag1", "tag2"}, SplitNoEmpty("tag1,tag2,", ","))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExpandHome_WithTilde(t *testing.T) {
|
|
||||||
require.Equal(t, os.Getenv("HOME")+"/this/is/a/path", ExpandHome("~/this/is/a/path"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExpandHome_NoTilde(t *testing.T) {
|
|
||||||
require.Equal(t, "/this/is/an/absolute/path", ExpandHome("/this/is/an/absolute/path"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParsePriority(t *testing.T) {
|
func TestParsePriority(t *testing.T) {
|
||||||
priorities := []string{"", "1", "2", "3", "4", "5", "min", "LOW", " default ", "HIgh", "max", "urgent"}
|
priorities := []string{"", "1", "2", "3", "4", "5", "min", "LOW", " default ", "HIgh", "max", "urgent"}
|
||||||
expected := []int{0, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 5}
|
expected := []int{0, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 5}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue