Continued work on Windows CLI

This commit is contained in:
Philipp Heckel 2022-05-09 16:22:52 -04:00
parent 7d473488de
commit 613348d37e
9 changed files with 135 additions and 71 deletions

View File

@ -9,11 +9,6 @@ import (
"os"
)
var (
defaultClientRootConfigFile = "/etc/ntfy/client.yml"
defaultClientUserConfigFile = "~/.config/ntfy/client.yml"
)
const (
categoryClient = "Client commands"
categoryServer = "Server commands"

View File

@ -63,8 +63,7 @@ Examples:
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/.
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.`,
` + defaultClientConfigFileDescriptionSuffix,
}
func execPublish(c *cli.Context) error {

View File

@ -6,10 +6,7 @@ import (
"github.com/urfave/cli/v2"
"heckel.io/ntfy/client"
"heckel.io/ntfy/util"
"log"
"os"
"os/exec"
"os/user"
"strings"
)
@ -64,19 +61,17 @@ ntfy subscribe TOPIC COMMAND
Examples:
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
Service mode (used in ntfy-client.service). This reads the config file (/etc/ntfy/client.yml
or ~/.config/ntfy/client.yml) and sets up subscriptions for every topic in the "subscribe:"
block (see config file).
Service mode (used in ntfy-client.service). This reads the config file and sets up
subscriptions for every topic in the "subscribe:" block (see config file).
Examples:
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),
or ~/.config/ntfy/client.yml for all other users.`,
` + defaultClientConfigFileDescriptionSuffix,
}
func execSubscribe(c *cli.Context) error {
@ -160,8 +155,8 @@ 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 {
commands := make(map[string]string) // Subscription ID -> command
for _, s := range conf.Subscribe { // May be nil
cmds := make(map[string]string) // Subscription ID -> command
for _, s := range conf.Subscribe { // May be nil
topicOptions := append(make([]client.SubscribeOption, 0), options...)
for filter, value := range s.If {
topicOptions = append(topicOptions, client.WithFilter(filter, value))
@ -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))
}
subscriptionID := cl.Subscribe(s.Topic, topicOptions...)
commands[subscriptionID] = s.Command
cmds[subscriptionID] = s.Command
}
if topic != "" {
subscriptionID := cl.Subscribe(topic, options...)
commands[subscriptionID] = command
cmds[subscriptionID] = command
}
for m := range cl.Messages {
command, ok := commands[m.SubscriptionID]
cmd, ok := cmds[m.SubscriptionID]
if !ok {
continue
}
printMessageOrRunCommand(c, m, command)
printMessageOrRunCommand(c, m, cmd)
}
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 {
env := os.Environ()
env = append(env, envVar(m.ID, "NTFY_ID", "id")...)
@ -253,11 +221,7 @@ func loadConfig(c *cli.Context) (*client.Config, error) {
if filename != "" {
return client.LoadConfig(filename)
}
u, _ := user.Current()
configFile := defaultClientRootConfigFile
if u.Uid != "0" {
configFile = util.ExpandHome(defaultClientUserConfigFile)
}
configFile := defaultConfigFile()
if s, _ := os.Stat(configFile); s != nil {
return client.LoadConfig(configFile)
}

57
cmd/subscribe_linux.go Normal file
View 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
View 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)
}

View File

@ -38,7 +38,7 @@ Here's an example showing how to publish a simple message using a POST request:
=== "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"

View File

@ -145,12 +145,27 @@ Here's an example config file that subscribes to three different topics, executi
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:
* 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 `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:

View File

@ -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://
func ShortTopicURL(s string) string {
return strings.TrimPrefix(strings.TrimPrefix(s, "https://"), "http://")

View File

@ -3,7 +3,6 @@ package util
import (
"github.com/stretchr/testify/require"
"io/ioutil"
"os"
"path/filepath"
"testing"
"time"
@ -75,14 +74,6 @@ func TestSplitNoEmpty(t *testing.T) {
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) {
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}