|
|
@@ -0,0 +1,129 @@
|
|
|
+package notification
|
|
|
+
|
|
|
+import (
|
|
|
+ "bytes"
|
|
|
+ "context"
|
|
|
+ "encoding/json"
|
|
|
+ "fmt"
|
|
|
+ "github.com/0xJacky/Nginx-UI/model"
|
|
|
+ "github.com/uozi-tech/cosy/map2struct"
|
|
|
+ "net/http"
|
|
|
+ "strconv"
|
|
|
+)
|
|
|
+
|
|
|
+const (
|
|
|
+ DEFAULT_NTFY_PRIORITY = 3
|
|
|
+ DEFAULT_NTFY_ICON = "https://nginxui.com/assets/logo.svg"
|
|
|
+)
|
|
|
+
|
|
|
+// @external_notifier(Ntfy)
|
|
|
+type Ntfy struct {
|
|
|
+ ServerURL string `json:"server_url" title:"Server URL"`
|
|
|
+ Topic string `json:"topic" title:"Topic"`
|
|
|
+ Priority string `json:"priority" title:"Priority"`
|
|
|
+ Tags string `json:"tags" title:"Tags"`
|
|
|
+ Click string `json:"click" title:"Click URL"`
|
|
|
+ Actions string `json:"actions" title:"Actions"`
|
|
|
+ Username string `json:"username" title:"Username"`
|
|
|
+ Password string `json:"password" title:"Password"`
|
|
|
+ Token string `json:"token" title:"Token"`
|
|
|
+}
|
|
|
+
|
|
|
+type NtfyMessage struct {
|
|
|
+ Topic string `json:"topic,omitempty"`
|
|
|
+ Message string `json:"message,omitempty"`
|
|
|
+ Title string `json:"title,omitempty"`
|
|
|
+ Priority int `json:"priority,omitempty"`
|
|
|
+ Tags []string `json:"tags,omitempty"`
|
|
|
+ Click string `json:"click,omitempty"`
|
|
|
+ Actions []interface{} `json:"actions,omitempty"`
|
|
|
+ Icon string `json:"icon,omitempty"`
|
|
|
+}
|
|
|
+
|
|
|
+func init() {
|
|
|
+ RegisterExternalNotifier("ntfy", func(ctx context.Context, n *model.ExternalNotify, msg *ExternalMessage) error {
|
|
|
+ ntfyConfig := &Ntfy{}
|
|
|
+ err := map2struct.WeakDecode(n.Config, ntfyConfig)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ if ntfyConfig.ServerURL == "" || ntfyConfig.Topic == "" {
|
|
|
+ return ErrInvalidNotifierConfig
|
|
|
+ }
|
|
|
+
|
|
|
+ // Convert priority string to int
|
|
|
+ priority := DEFAULT_NTFY_PRIORITY
|
|
|
+ if ntfyConfig.Priority != "" {
|
|
|
+ p, err := strconv.Atoi(ntfyConfig.Priority)
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("invalid priority: %w", err)
|
|
|
+ }
|
|
|
+ if p < 1 || p > 5 {
|
|
|
+ return fmt.Errorf("invalid priority: must be between 1 and 5")
|
|
|
+ }
|
|
|
+ priority = p
|
|
|
+ }
|
|
|
+
|
|
|
+ // Prepare the message
|
|
|
+ ntfyMsg := NtfyMessage{
|
|
|
+ Topic: ntfyConfig.Topic,
|
|
|
+ Message: msg.GetContent(n.Language),
|
|
|
+ Title: msg.GetTitle(n.Language),
|
|
|
+ Priority: priority,
|
|
|
+ Icon: DEFAULT_NTFY_ICON,
|
|
|
+ Click: ntfyConfig.Click,
|
|
|
+ }
|
|
|
+
|
|
|
+ // Add tags if provided
|
|
|
+ if ntfyConfig.Tags != "" {
|
|
|
+ var tags []string
|
|
|
+ if err := json.Unmarshal([]byte(ntfyConfig.Tags), &tags); err != nil {
|
|
|
+ return fmt.Errorf("invalid tags: %w", err)
|
|
|
+ }
|
|
|
+ ntfyMsg.Tags = tags
|
|
|
+ }
|
|
|
+
|
|
|
+ // Add actions if provided
|
|
|
+ if ntfyConfig.Actions != "" {
|
|
|
+ var actions []interface{}
|
|
|
+ if err := json.Unmarshal([]byte(ntfyConfig.Actions), &actions); err != nil {
|
|
|
+ return fmt.Errorf("invalid actions: %w", err)
|
|
|
+ }
|
|
|
+ ntfyMsg.Actions = actions
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create HTTP request
|
|
|
+ jsonData, err := json.Marshal(ntfyMsg)
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("failed to marshal ntfy message: %w", err)
|
|
|
+ }
|
|
|
+ req, err := http.NewRequestWithContext(ctx, "POST", ntfyConfig.ServerURL, bytes.NewBuffer(jsonData))
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("failed to create HTTP request: %w", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Set headers
|
|
|
+ req.Header.Set("Content-Type", "application/json")
|
|
|
+ req.Header.Set("User-Agent", "Nginx-UI")
|
|
|
+ if ntfyConfig.Token != "" {
|
|
|
+ req.Header.Set("Authorization", "Bearer "+ntfyConfig.Token)
|
|
|
+ } else if ntfyConfig.Username != "" && ntfyConfig.Password != "" {
|
|
|
+ req.SetBasicAuth(ntfyConfig.Username, ntfyConfig.Password)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Send request
|
|
|
+ client := &http.Client{}
|
|
|
+ resp, err := client.Do(req)
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("failed to send ntfy request: %w", err)
|
|
|
+ }
|
|
|
+ defer resp.Body.Close()
|
|
|
+
|
|
|
+ // Check response status
|
|
|
+ if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
|
+ return fmt.Errorf("ntfy request failed with status: %d", resp.StatusCode)
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+ })
|
|
|
+}
|