From 2c1989beb03cdb64332a3f99da619d5a760025fe Mon Sep 17 00:00:00 2001
From: Philipp Heckel <pheckel@datto.com>
Date: Sat, 18 Dec 2021 16:12:36 -0500
Subject: [PATCH] I think we're getting there

---
 client/client.yml |   4 +-
 client/config.go  |   4 +-
 cmd/subscribe.go  | 119 ++++++++++++++++++++++++++--------------------
 3 files changed, 71 insertions(+), 56 deletions(-)

diff --git a/client/client.yml b/client/client.yml
index c65a2f3..0a38a9b 100644
--- a/client/client.yml
+++ b/client/client.yml
@@ -11,8 +11,8 @@
 # Here's a (hopefully self-explanatory) example:
 #   subscribe:
 #     - topic: mytopic
-#       exec: /usr/local/bin/mytopic-triggered.sh
+#       command: /usr/local/bin/mytopic-triggered.sh
 #     - topic: myserver.com/anothertopic
-#       exec: 'echo "$message"'
+#       command: 'echo "$message"'
 #
 # subscribe:
diff --git a/client/config.go b/client/config.go
index 863d2fe..739174b 100644
--- a/client/config.go
+++ b/client/config.go
@@ -7,8 +7,8 @@ const (
 type Config struct {
 	DefaultHost string
 	Subscribe   []struct {
-		Topic string
-		Exec  string
+		Topic   string
+		Command string
 	}
 }
 
diff --git a/cmd/subscribe.go b/cmd/subscribe.go
index de4b65a..bbc36d2 100644
--- a/cmd/subscribe.go
+++ b/cmd/subscribe.go
@@ -27,6 +27,7 @@ var cmdSubscribe = &cli.Command{
 		&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: "scheduled", Aliases: []string{"sched", "S"}, Usage: "also return scheduled/delayed events"},
+		&cli.BoolFlag{Name: "verbose", Aliases: []string{"v"}, Usage: "print verbose output"},
 	},
 	Description: `Subscribe to a topic from a ntfy server, and either print or execute a command for 
 every arriving message. There are 3 modes in which the command can be run:
@@ -71,39 +72,9 @@ ntfy subscribe --from-config
 }
 
 func execSubscribe(c *cli.Context) error {
-	fromConfig := c.Bool("from-config")
-	if fromConfig {
-		return execSubscribeFromConfig(c)
-	}
-	return execSubscribeWithoutConfig(c)
-}
-
-func execSubscribeFromConfig(c *cli.Context) error {
-	conf, err := loadConfig(c)
-	if err != nil {
-		return err
-	}
-	cl := client.New(conf)
-	commands := make(map[string]string)
-	for _, s := range conf.Subscribe {
-		topicURL := cl.Subscribe(s.Topic)
-		commands[topicURL] = s.Exec
-	}
-	for m := range cl.Messages {
-		command, ok := commands[m.TopicURL]
-		if !ok {
-			continue
-		}
-		_ = dispatchMessage(c, command, m)
-	}
-	return nil
-}
-
-func execSubscribeWithoutConfig(c *cli.Context) error {
-	if c.NArg() < 1 {
-		return errors.New("topic missing")
-	}
 	fmt.Fprintln(c.App.ErrWriter, "\x1b[1;33mThis command is incubating. The interface may change without notice.\x1b[0m")
+
+	// Read config and options
 	conf, err := loadConfig(c)
 	if err != nil {
 		return err
@@ -112,8 +83,12 @@ func execSubscribeWithoutConfig(c *cli.Context) error {
 	since := c.String("since")
 	poll := c.Bool("poll")
 	scheduled := c.Bool("scheduled")
+	fromConfig := c.Bool("from-config")
 	topic := c.Args().Get(0)
 	command := c.Args().Get(1)
+	if !fromConfig {
+		conf.Subscribe = nil // wipe if --from-config not passed
+	}
 	var options []client.SubscribeOption
 	if since != "" {
 		options = append(options, client.WithSince(since))
@@ -124,49 +99,89 @@ func execSubscribeWithoutConfig(c *cli.Context) error {
 	if scheduled {
 		options = append(options, client.WithScheduled())
 	}
+	if topic == "" && len(conf.Subscribe) == 0 {
+		return errors.New("must specify topic, or have at least one topic defined in config")
+	}
+
+	// Execute poll or subscribe
 	if poll {
-		messages, err := cl.Poll(topic, options...)
-		if err != nil {
+		return execPoll(c, cl, conf, topic, command, options...)
+	}
+	return execSubscribeInternal(c, cl, conf, topic, command, options...)
+}
+
+func execPoll(c *cli.Context, cl *client.Client, conf *client.Config, topic, command string, options ...client.SubscribeOption) error {
+	for _, s := range conf.Subscribe { // may be nil
+		if err := execPollSingle(c, cl, s.Topic, s.Command, options...); err != nil {
 			return err
 		}
-		for _, m := range messages {
-			_ = dispatchMessage(c, command, m)
-		}
-	} else {
-		cl.Subscribe(topic, options...)
-		for m := range cl.Messages {
-			_ = dispatchMessage(c, command, m)
+	}
+	if topic != "" {
+		if err := execPollSingle(c, cl, topic, command, options...); err != nil {
+			return err
 		}
 	}
 	return nil
 }
 
-func dispatchMessage(c *cli.Context, command string, m *client.Message) error {
-	if command != "" {
-		return execCommand(c, command, m)
+func execPollSingle(c *cli.Context, cl *client.Client, topic, command string, options ...client.SubscribeOption) error {
+	messages, err := cl.Poll(topic, options...)
+	if err != nil {
+		return err
+	}
+	for _, m := range messages {
+		printMessageOrRunCommand(c, m, command)
 	}
-	fmt.Println(m.Raw)
 	return nil
 }
 
-func execCommand(c *cli.Context, command string, m *client.Message) error {
-	if m.Event == client.OpenEvent {
-		log.Printf("[%s] Connection opened, subscribed to topic", collapseTopicURL(m.TopicURL))
-	} else if m.Event == client.MessageEvent {
-		if err := runCommandInternal(c, command, m); err != nil {
-			log.Printf("[%s] Command failed: %s", collapseTopicURL(m.TopicURL), err.Error())
+func execSubscribeInternal(c *cli.Context, cl *client.Client, conf *client.Config, topic, command string, options ...client.SubscribeOption) error {
+	commands := make(map[string]string)
+	for _, s := range conf.Subscribe { // May be nil
+		topicURL := cl.Subscribe(s.Topic, options...)
+		commands[topicURL] = s.Command
+	}
+	if topic != "" {
+		topicURL := cl.Subscribe(topic, options...)
+		commands[topicURL] = command
+	}
+	for m := range cl.Messages {
+		command, ok := commands[m.TopicURL]
+		if !ok {
+			continue
 		}
+		printMessageOrRunCommand(c, m, command)
 	}
 	return nil
 }
 
+func printMessageOrRunCommand(c *cli.Context, m *client.Message, command string) {
+	if m.Event != client.MessageEvent {
+		return
+	}
+	if command != "" {
+		runCommand(c, command, m)
+	} else {
+		fmt.Fprintln(c.App.Writer, m.Raw)
+	}
+}
+
+func runCommand(c *cli.Context, command string, m *client.Message) {
+	if err := runCommandInternal(c, command, m); err != nil {
+		fmt.Fprintf(c.App.ErrWriter, "Command failed: %s\n", err.Error())
+	}
+}
+
 func runCommandInternal(c *cli.Context, command string, m *client.Message) error {
 	scriptFile, err := createTmpScript(command)
 	if err != nil {
 		return err
 	}
 	defer os.Remove(scriptFile)
-	log.Printf("[%s] Executing: %s (for message: %s)", collapseTopicURL(m.TopicURL), command, m.Raw)
+	verbose := c.Bool("verbose")
+	if verbose {
+		log.Printf("[%s] Executing: %s (for message: %s)", collapseTopicURL(m.TopicURL), command, m.Raw)
+	}
 	cmd := exec.Command("sh", "-c", scriptFile)
 	cmd.Stdin = c.App.Reader
 	cmd.Stdout = c.App.Writer
-- 
GitLab