package smtp2shoutrrr import ( "fmt" "log/slog" "net/url" "os" "strings" "github.com/pelletier/go-toml/v2" ) // LoadConfig reads the TOML configuration at path and applies its defaults. func LoadConfig(path string) (*Config, error) { f, err := os.Open(path) if err != nil { return nil, fmt.Errorf("opening config file %q: %w", path, err) } defer func() { _ = f.Close() }() var config Config if err := toml.NewDecoder(f).Decode(&config); err != nil { return nil, fmt.Errorf("decoding config file %q: %w", path, err) } config.SetDefaults() return &config, nil } type Config struct { Port int Username string Password string Recipients []ConfigRecipient CatchAll *ConfigRecipient } func (c *Config) SetDefaults() { if c.Port == 0 { c.Port = 11125 } if c.Username == "" { slog.Warn("no username provided, using default: username") c.Username = "username" } if c.Password == "" { slog.Warn("no password provided, using default: password") c.Password = "password" } } type ConfigRecipient struct { Addresses []string // email addresses Target string // deprecated: use Targets instead Targets []string // shoutrrr addresses (supports multiple) targetURLs []*url.URL // cached parsed URLs } func (cr *ConfigRecipient) GetTargetURLs() []*url.URL { if cr.targetURLs == nil { cr.targetURLs = make([]*url.URL, 0) // Collect all targets, merging Target into Targets allTargets := make([]string, 0) // Handle deprecated Target field if cr.Target != "" { allTargets = append(allTargets, cr.Target) slog.Warn("Target field is deprecated, use Targets instead", slog.String("addresses", strings.Join(cr.Addresses, ","))) } // Add all Targets allTargets = append(allTargets, cr.Targets...) // Parse and cache all URLs for _, target := range allTargets { parsedURL, err := url.Parse(target) if err != nil { slog.Error("failed to parse shoutrrr target URL", slog.String("target", target), slog.String("err", err.Error())) continue // Skip invalid URLs } cr.targetURLs = append(cr.targetURLs, parsedURL) } } return cr.targetURLs }