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 }) }