1
0
Fork 0
golang-github-nicholas-fedo.../pkg/services/teams/teams_config.go
Daniel Baumann 6819b9812e
Adding upstream version 0.8.13.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-05-29 07:17:59 +02:00

203 lines
5.4 KiB
Go

package teams
import (
"fmt"
"net/url"
"regexp"
"strings"
"github.com/nicholas-fedor/shoutrrr/pkg/format"
"github.com/nicholas-fedor/shoutrrr/pkg/services/standard"
"github.com/nicholas-fedor/shoutrrr/pkg/types"
)
// Scheme is the identifier for the Teams service protocol.
const Scheme = "teams"
// Config constants.
const (
DummyURL = "teams://dummy@dummy.com" // Default placeholder URL
ExpectedOrgMatches = 2 // Full match plus organization domain capture group
MinPathComponents = 3 // Minimum required path components: AltID, GroupOwner, ExtraID
)
// Config represents the configuration for the Teams service.
type Config struct {
standard.EnumlessConfig
Group string `optional:"" url:"user"`
Tenant string `optional:"" url:"host"`
AltID string `optional:"" url:"path1"`
GroupOwner string `optional:"" url:"path2"`
ExtraID string `optional:"" url:"path3"`
Title string `key:"title" optional:""`
Color string `key:"color" optional:""`
Host string `key:"host" optional:""` // Required, no default
}
// WebhookParts returns the webhook components as an array.
func (config *Config) WebhookParts() [5]string {
return [5]string{config.Group, config.Tenant, config.AltID, config.GroupOwner, config.ExtraID}
}
// SetFromWebhookURL updates the Config from a Teams webhook URL.
func (config *Config) SetFromWebhookURL(webhookURL string) error {
orgPattern := regexp.MustCompile(
`https://([a-zA-Z0-9-\.]+)` + WebhookDomain + `/` + Path + `/([0-9a-f\-]{36})@([0-9a-f\-]{36})/` + ProviderName + `/([0-9a-f]{32})/([0-9a-f\-]{36})/([^/]+)`,
)
orgGroups := orgPattern.FindStringSubmatch(webhookURL)
if len(orgGroups) != ExpectedComponents {
return ErrInvalidWebhookFormat
}
config.Host = orgGroups[1] + WebhookDomain
parts, err := ParseAndVerifyWebhookURL(webhookURL)
if err != nil {
return err
}
config.setFromWebhookParts(parts)
return nil
}
// ConfigFromWebhookURL creates a new Config from a parsed Teams webhook URL.
func ConfigFromWebhookURL(webhookURL url.URL) (*Config, error) {
webhookURL.RawQuery = ""
config := &Config{Host: webhookURL.Host}
if err := config.SetFromWebhookURL(webhookURL.String()); err != nil {
return nil, err
}
return config, nil
}
// GetURL constructs a URL from the Config fields.
func (config *Config) GetURL() *url.URL {
resolver := format.NewPropKeyResolver(config)
return config.getURL(&resolver)
}
// getURL constructs a URL using the provided resolver.
func (config *Config) getURL(resolver types.ConfigQueryResolver) *url.URL {
if config.Host == "" {
return nil
}
return &url.URL{
User: url.User(config.Group),
Host: config.Tenant,
Path: "/" + config.AltID + "/" + config.GroupOwner + "/" + config.ExtraID,
Scheme: Scheme,
RawQuery: format.BuildQuery(resolver),
}
}
// SetURL updates the Config from a URL.
func (config *Config) SetURL(url *url.URL) error {
resolver := format.NewPropKeyResolver(config)
return config.setURL(&resolver, url)
}
// setURL updates the Config from a URL using the provided resolver.
// It parses the URL parts, sets query parameters, and ensures the host is specified.
// Returns an error if the URL is invalid or the host is missing.
func (config *Config) setURL(resolver types.ConfigQueryResolver, url *url.URL) error {
parts, err := parseURLParts(url)
if err != nil {
return err
}
config.setFromWebhookParts(parts)
if err := config.setQueryParams(resolver, url.Query()); err != nil {
return err
}
// Allow dummy URL during documentation generation
if config.Host == "" && (url.User != nil && url.User.Username() == "dummy") {
config.Host = "dummy.webhook.office.com"
} else if config.Host == "" {
return ErrMissingHostParameter
}
return nil
}
// parseURLParts extracts and validates webhook components from a URL.
func parseURLParts(url *url.URL) ([5]string, error) {
var parts [5]string
if url.String() == DummyURL {
return parts, nil
}
pathParts := strings.Split(url.Path, "/")
if pathParts[0] == "" {
pathParts = pathParts[1:]
}
if len(pathParts) < MinPathComponents {
return parts, ErrMissingExtraIDComponent
}
parts = [5]string{
url.User.Username(),
url.Hostname(),
pathParts[0],
pathParts[1],
pathParts[2],
}
if err := verifyWebhookParts(parts); err != nil {
return parts, fmt.Errorf("invalid URL format: %w", err)
}
return parts, nil
}
// setQueryParams applies query parameters to the Config using the resolver.
// It resets Color, Host, and Title, then updates them based on query values.
// Returns an error if the resolver fails to set any parameter.
func (config *Config) setQueryParams(resolver types.ConfigQueryResolver, query url.Values) error {
config.Color = ""
config.Host = ""
config.Title = ""
for key, vals := range query {
if len(vals) > 0 && vals[0] != "" {
switch key {
case "color":
config.Color = vals[0]
case "host":
config.Host = vals[0]
case "title":
config.Title = vals[0]
}
if err := resolver.Set(key, vals[0]); err != nil {
return fmt.Errorf(
"%w: key=%q, value=%q: %w",
ErrSetParameterFailed,
key,
vals[0],
err,
)
}
}
}
return nil
}
// setFromWebhookParts sets Config fields from webhook parts.
func (config *Config) setFromWebhookParts(parts [5]string) {
config.Group = parts[0]
config.Tenant = parts[1]
config.AltID = parts[2]
config.GroupOwner = parts[3]
config.ExtraID = parts[4]
}