File: gotify_config.go

package info (click to toggle)
golang-github-nicholas-fedor-shoutrrr 0.13.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 5,796 kB
  • sloc: sh: 74; makefile: 62
file content (161 lines) | stat: -rw-r--r-- 7,724 bytes parent folder | download
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package gotify

import (
	"encoding/json"
	"fmt"
	"log"
	"net/url"
	"strings"

	"github.com/nicholas-fedor/shoutrrr/pkg/format"
	"github.com/nicholas-fedor/shoutrrr/pkg/services/standard"
	"github.com/nicholas-fedor/shoutrrr/pkg/types"
)

// Scheme identifies this service in configuration URLs.
const (
	Scheme = "gotify"
)

// Config holds settings for the Gotify notification service.
// This struct contains all configuration parameters needed to connect to and authenticate
// with a Gotify server, including connection details, authentication credentials,
// notification defaults, and additional metadata.
type Config struct {
	standard.EnumlessConfig                // Embeds standard configuration functionality without enum handling
	Token                   string         `desc:"Application token"                     required:"" url:"path2"`                                                                          // Gotify application token for authentication (must be 15 chars starting with 'A')
	Host                    string         `desc:"Server hostname (and optionally port)" required:"" url:"host,port"`                                                                      // Gotify server hostname and optional port number
	Path                    string         `desc:"Server subpath"                                    url:"path1"     optional:""`                                                          // Optional subpath for Gotify installation (e.g., "/gotify")
	Priority                int            `                                                                                     default:"0"                     key:"priority"`           // Notification priority level (-2 to 10, where higher numbers are more important; negative values have special meanings in some clients)
	Title                   string         `                                                                                     default:"Shoutrrr notification" key:"title"`              // Default notification title when none provided
	DisableTLS              bool           `                                                                                     default:"No"                    key:"disabletls"`         // Disable TLS in URL scheme only (use HTTP instead of HTTPS)
	InsecureSkipVerify      bool           `                                                                                     default:"No"                    key:"insecureskipverify"` // Skip TLS certificate verification (insecure, use with caution)
	UseHeader               bool           `desc:"Enable header-based authentication"                                            default:"No"                    key:"useheader"`          // Send token in X-Gotify-Key header instead of URL query parameter
	Date                    string         `                                                                                     default:""                      key:"date"`               // Optional custom timestamp in ISO 8601 format for the notification
	Extras                  map[string]any // Additional extras parsed from JSON - custom key-value pairs sent with notifications
}

// SetURL updates the configuration from a URL representation.
func (config *Config) SetURL(url *url.URL) error {
	resolver := format.NewPropKeyResolver(config)

	return config.setURL(&resolver, url)
}

// GetURL returns a URL representation of the current configuration.
func (config *Config) GetURL() *url.URL {
	resolver := format.NewPropKeyResolver(config)

	return config.getURL(&resolver)
}

// getURL generates a URL from the current configuration values.
// This internal method constructs a URL representation of the configuration,
// including all settings as query parameters and extras as JSON in the query string.
// Used for serialization and URL reconstruction.
// Parameters:
//   - resolver: Configuration resolver for building query parameters from config fields
//
// Returns: *url.URL containing the complete configuration as a URL.
func (config *Config) getURL(resolver types.ConfigQueryResolver) *url.URL {
	// Build base query string from configuration fields using the resolver
	query := format.BuildQuery(resolver)

	// Handle extras serialization if present
	if config.Extras != nil {
		// Marshal extras map to JSON string
		extrasJSON, err := json.Marshal(config.Extras)
		if err != nil {
			// Skip adding extras when Extras cannot be serialized
			log.Printf("Failed to marshal Extras %v: %v, skipping extras", config.Extras, err)
		} else {
			// Append extras to query string with proper URL encoding
			if query != "" {
				query += "&"
			}

			query += "extras=" + url.QueryEscape(string(extrasJSON))
		}
	}

	// Construct and return the complete URL
	return &url.URL{
		Host:       config.Host,                // Server hostname and port
		Scheme:     Scheme,                     // URL scheme (gotify)
		ForceQuery: false,                      // Don't force query string presence
		Path:       config.Path + config.Token, // Path with token appended
		RawQuery:   query,                      // Query parameters including extras
	}
}

// setURL updates the configuration from a URL representation.
// This internal method parses a URL and extracts configuration values including
// host, path, token, and query parameters, populating the config struct fields.
// Used for deserialization from URL format.
// Parameters:
//   - resolver: Configuration resolver for setting config fields from query parameters
//   - url: The URL to parse configuration values from
//
// Returns: error if URL parsing or parameter processing fails.
func (config *Config) setURL(resolver types.ConfigQueryResolver, url *url.URL) error {
	// Extract and clean the path from the URL
	path := url.Path
	if len(path) > 0 && path[len(path)-1] == '/' {
		path = path[:len(path)-1] // Remove trailing slash if present
	}

	// Find the last slash to separate path from token
	tokenIndex := strings.LastIndex(path, "/") + 1

	// Extract path component (everything before the token)
	config.Path = path[:tokenIndex]
	if config.Path == "/" {
		config.Path = config.Path[1:] // Remove leading slash to normalize empty path
	}

	// Set host and token from URL components
	config.Host = url.Host
	config.Token = path[tokenIndex:]

	// Process query parameters to set remaining configuration fields
	if err := config.processQueryParameters(resolver, url.Query()); err != nil {
		return fmt.Errorf("failed to process query parameters: %w", err)
	}

	return nil
}

// processQueryParameters processes query parameters from URL.
// This function handles both standard configuration parameters and the special 'extras'
// JSON parameter, setting appropriate config fields through the resolver or direct assignment.
// Parameters:
//   - resolver: Configuration resolver for setting standard config properties
//   - query: URL query parameters to process
//
// Returns: error if parameter parsing or setting fails.
func (config *Config) processQueryParameters(
	resolver types.ConfigQueryResolver,
	query url.Values,
) error {
	// Iterate through all query parameters
	for key := range query {
		if key == "extras" {
			// Special handling for extras JSON parameter
			if query.Get(key) != "" {
				// Initialize extras map
				config.Extras = make(map[string]any)
				// Parse JSON string into map
				if err := json.Unmarshal([]byte(query.Get(key)), &config.Extras); err != nil {
					return fmt.Errorf("%w", ErrExtrasParseFailed)
				}
			}
		} else {
			// Standard parameter handling through resolver
			if err := resolver.Set(key, query.Get(key)); err != nil {
				return fmt.Errorf("%w", ErrConfigPropertyFailed)
			}
		}
	}

	return nil
}