1
0
Fork 0

Adding upstream version 0.8.9.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-22 10:16:14 +02:00
parent 3b2c48b5e4
commit c0c4addb85
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
285 changed files with 25880 additions and 0 deletions

View file

@ -0,0 +1,240 @@
package generate
import (
"errors"
"fmt"
"net/url"
"os"
"strings"
"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/nicholas-fedor/shoutrrr/pkg/generators"
"github.com/nicholas-fedor/shoutrrr/pkg/router"
"github.com/nicholas-fedor/shoutrrr/pkg/types"
)
// MaximumNArgs defines the maximum number of positional arguments allowed.
const MaximumNArgs = 2
// ErrNoServiceSpecified indicates that no service was provided for URL generation.
var (
ErrNoServiceSpecified = errors.New("no service specified")
)
// serviceRouter manages the creation of notification services.
var serviceRouter router.ServiceRouter
// Cmd generates a notification service URL from user input.
var Cmd = &cobra.Command{
Use: "generate",
Short: "Generates a notification service URL from user input",
Run: Run,
PreRun: loadArgsFromAltSources,
Args: cobra.MaximumNArgs(MaximumNArgs),
}
// loadArgsFromAltSources populates command flags from positional arguments if provided.
func loadArgsFromAltSources(cmd *cobra.Command, args []string) {
if len(args) > 0 {
_ = cmd.Flags().Set("service", args[0])
}
if len(args) > 1 {
_ = cmd.Flags().Set("generator", args[1])
}
}
// init initializes the command flags for the generate command.
func init() {
serviceRouter = router.ServiceRouter{}
Cmd.Flags().
StringP("service", "s", "", "Notification service to generate a URL for (e.g., discord, smtp)")
Cmd.Flags().
StringP("generator", "g", "basic", "Generator to use (e.g., basic, or service-specific)")
Cmd.Flags().
StringArrayP("property", "p", []string{}, "Configuration property in key=value format (e.g., token=abc123)")
Cmd.Flags().
BoolP("show-sensitive", "x", false, "Show sensitive data in the generated URL (default: masked)")
}
// maskSensitiveURL masks sensitive parts of a Shoutrrr URL based on the service schema.
func maskSensitiveURL(serviceSchema, urlStr string) string {
parsedURL, err := url.Parse(urlStr)
if err != nil {
return urlStr // Return original URL if parsing fails
}
switch serviceSchema {
case "discord", "slack", "teams":
maskUser(parsedURL, "REDACTED")
case "smtp":
maskSMTPUser(parsedURL)
case "pushover":
maskPushoverQuery(parsedURL)
case "gotify":
maskGotifyQuery(parsedURL)
default:
maskGeneric(parsedURL)
}
return parsedURL.String()
}
// maskUser redacts the username in a URL with a placeholder.
func maskUser(parsedURL *url.URL, placeholder string) {
if parsedURL.User != nil {
parsedURL.User = url.User(placeholder)
}
}
// maskSMTPUser redacts the password in an SMTP URL, preserving the username.
func maskSMTPUser(parsedURL *url.URL) {
if parsedURL.User != nil {
parsedURL.User = url.UserPassword(parsedURL.User.Username(), "REDACTED")
}
}
// maskPushoverQuery redacts token and user query parameters in a Pushover URL.
func maskPushoverQuery(parsedURL *url.URL) {
queryParams := parsedURL.Query()
if queryParams.Get("token") != "" {
queryParams.Set("token", "REDACTED")
}
if queryParams.Get("user") != "" {
queryParams.Set("user", "REDACTED")
}
parsedURL.RawQuery = queryParams.Encode()
}
// maskGotifyQuery redacts the token query parameter in a Gotify URL.
func maskGotifyQuery(parsedURL *url.URL) {
queryParams := parsedURL.Query()
if queryParams.Get("token") != "" {
queryParams.Set("token", "REDACTED")
}
parsedURL.RawQuery = queryParams.Encode()
}
// maskGeneric redacts userinfo and all query parameters for unrecognized services.
func maskGeneric(parsedURL *url.URL) {
maskUser(parsedURL, "REDACTED")
queryParams := parsedURL.Query()
for key := range queryParams {
queryParams.Set(key, "REDACTED")
}
parsedURL.RawQuery = queryParams.Encode()
}
// Run executes the generate command, producing a notification service URL.
func Run(cmd *cobra.Command, _ []string) {
var service types.Service
var err error
serviceSchema, _ := cmd.Flags().GetString("service")
generatorName, _ := cmd.Flags().GetString("generator")
propertyFlags, _ := cmd.Flags().GetStringArray("property")
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
// Parse properties into a key-value map.
props := make(map[string]string, len(propertyFlags))
for _, prop := range propertyFlags {
parts := strings.Split(prop, "=")
if len(parts) != MaximumNArgs {
fmt.Fprint(
color.Output,
"Invalid property key/value pair: ",
color.HiYellowString(prop),
"\n",
)
continue
}
props[parts[0]] = parts[1]
}
if len(propertyFlags) > 0 {
fmt.Fprint(color.Output, "\n") // Add spacing after property warnings
}
// Validate and create the service.
if serviceSchema == "" {
err = ErrNoServiceSpecified
} else {
service, err = serviceRouter.NewService(serviceSchema)
}
if err != nil {
fmt.Fprint(os.Stdout, "Error: ", err, "\n")
}
if service == nil {
services := serviceRouter.ListServices()
serviceList := strings.Join(services, ", ")
cmd.SetUsageTemplate(cmd.UsageTemplate() + "\nAvailable services:\n " + serviceList + "\n")
_ = cmd.Usage()
os.Exit(1)
}
// Determine the generator to use.
var generator types.Generator
generatorFlag := cmd.Flags().Lookup("generator")
if !generatorFlag.Changed {
// Use the service-specific default generator if available and no explicit generator is set.
generator, _ = generators.NewGenerator(serviceSchema)
}
if generator != nil {
generatorName = serviceSchema
} else {
var genErr error
generator, genErr = generators.NewGenerator(generatorName)
if genErr != nil {
fmt.Fprint(os.Stdout, "Error: ", genErr, "\n")
}
}
if generator == nil {
generatorList := strings.Join(generators.ListGenerators(), ", ")
cmd.SetUsageTemplate(
cmd.UsageTemplate() + "\nAvailable generators:\n " + generatorList + "\n",
)
_ = cmd.Usage()
os.Exit(1)
}
// Generate and display the URL.
fmt.Fprint(color.Output, "Generating URL for ", color.HiCyanString(serviceSchema))
fmt.Fprint(color.Output, " using ", color.HiMagentaString(generatorName), " generator\n")
serviceConfig, err := generator.Generate(service, props, cmd.Flags().Args())
if err != nil {
_, _ = fmt.Fprint(os.Stdout, "Error: ", err, "\n")
os.Exit(1)
}
fmt.Fprint(color.Output, "\n")
maskedURL := maskSensitiveURL(serviceSchema, serviceConfig.GetURL().String())
if showSensitive {
fmt.Fprint(os.Stdout, "URL: ", serviceConfig.GetURL().String(), "\n")
} else {
fmt.Fprint(os.Stdout, "URL: ", maskedURL, "\n")
}
}