1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
|
package send
import (
"errors"
"fmt"
"io"
"log"
"os"
"strings"
"github.com/spf13/cobra"
"github.com/nicholas-fedor/shoutrrr/internal/dedupe"
internalUtil "github.com/nicholas-fedor/shoutrrr/internal/util"
"github.com/nicholas-fedor/shoutrrr/pkg/router"
"github.com/nicholas-fedor/shoutrrr/pkg/types"
"github.com/nicholas-fedor/shoutrrr/pkg/util"
cli "github.com/nicholas-fedor/shoutrrr/shoutrrr/cmd"
)
// MaximumNArgs defines the maximum number of arguments accepted by the command.
const (
MaximumNArgs = 2
MaxMessageLength = 100
)
// Cmd sends a notification using a service URL.
var Cmd = &cobra.Command{
Use: "send",
Short: "Send a notification using a service url",
Args: cobra.MaximumNArgs(MaximumNArgs),
PreRun: internalUtil.LoadFlagsFromAltSources,
RunE: Run,
}
func init() {
Cmd.Flags().BoolP("verbose", "v", false, "")
Cmd.Flags().StringArrayP("url", "u", []string{}, "The notification url")
_ = Cmd.MarkFlagRequired("url")
Cmd.Flags().
StringP("message", "m", "", "The message to send to the notification url, or - to read message from stdin")
_ = Cmd.MarkFlagRequired("message")
Cmd.Flags().StringP("title", "t", "", "The title used for services that support it")
}
func logf(format string, a ...any) {
fmt.Fprintf(os.Stderr, format+"\n", a...)
}
func run(cmd *cobra.Command) error {
flags := cmd.Flags()
verbose, _ := flags.GetBool("verbose")
urls, _ := flags.GetStringArray("url")
urls = dedupe.RemoveDuplicates(urls)
message, _ := flags.GetString("message")
title, _ := flags.GetString("title")
if message == "-" {
logf("Reading from STDIN...")
stringBuilder := strings.Builder{}
count, err := io.Copy(&stringBuilder, os.Stdin)
if err != nil {
return fmt.Errorf("failed to read message from stdin: %w", err)
}
logf("Read %d byte(s)", count)
message = stringBuilder.String()
}
var logger *log.Logger
if verbose {
urlsPrefix := "URLs:"
for i, url := range urls {
logf("%s %s", urlsPrefix, url)
if i == 0 {
// Only display "URLs:" prefix for first line, replace with indentation for the subsequent
urlsPrefix = strings.Repeat(" ", len(urlsPrefix))
}
}
logf("Message: %s", util.Ellipsis(message, MaxMessageLength))
if title != "" {
logf("Title: %v", title)
}
logger = log.New(os.Stderr, "SHOUTRRR ", log.LstdFlags)
} else {
logger = util.DiscardLogger
}
serviceRouter, err := router.New(logger, urls...)
if err != nil {
return cli.ConfigurationError(fmt.Sprintf("error invoking send: %s", err))
}
params := make(types.Params)
if title != "" {
params["title"] = title
}
errs := serviceRouter.SendAsync(message, ¶ms)
for err := range errs {
if err != nil {
return cli.TaskUnavailable(err.Error())
}
logf("Notification sent")
}
return nil
}
// Run executes the send command and handles its result.
func Run(cmd *cobra.Command, _ []string) error {
err := run(cmd)
if err != nil {
var result cli.Result
if errors.As(err, &result) && result.ExitCode != cli.ExUsage {
// If the error is not related to CLI usage, report error and exit to avoid cobra error output
_, _ = fmt.Fprintln(os.Stderr, err.Error())
os.Exit(result.ExitCode)
}
}
return err
}
|