⛳ alerts for any ctfd instance via ntfy
1package clients
2
3import (
4 "bytes"
5 "crypto/tls"
6 "encoding/json"
7 "fmt"
8 "io"
9 "net/http"
10 "strings"
11 "time"
12)
13
14// NtfyMessage represents a notification message to be sent via ntfy
15type NtfyMessage struct {
16 Topic string `json:"topic"`
17 Message string `json:"message,omitempty"`
18 Title string `json:"title,omitempty"`
19 Tags []string `json:"tags,omitempty"`
20 Priority int `json:"priority,omitempty"`
21 Click string `json:"click,omitempty"`
22 Actions []map[string]any `json:"actions,omitempty"`
23 Attach string `json:"attach,omitempty"`
24 Filename string `json:"filename,omitempty"`
25 Markdown bool `json:"markdown,omitempty"`
26 Icon string `json:"icon,omitempty"`
27 Email string `json:"email,omitempty"`
28 Call string `json:"call,omitempty"`
29 Delay string `json:"delay,omitempty"`
30}
31
32// NtfyClient represents a client for sending notifications via ntfy.sh
33type NtfyClient struct {
34 Topic string
35 ServerURL string
36 HTTPClient *http.Client
37 // Optional authentication token
38 AccessToken string
39}
40
41// NewNtfyClient creates a new ntfy client with the specified topic and server URL.
42// It configures an HTTP client with a 10-second timeout and insecure TLS verification.
43func NewNtfyClient(topic, serverURL string, accessToken string) *NtfyClient {
44 serverURL = strings.TrimSuffix(serverURL, "/")
45 if serverURL == "" {
46 serverURL = "https://ntfy.sh"
47 }
48
49 return &NtfyClient{
50 Topic: topic,
51 ServerURL: serverURL,
52 AccessToken: accessToken,
53 HTTPClient: &http.Client{
54 Timeout: 10 * time.Second,
55 Transport: &http.Transport{
56 TLSClientConfig: &tls.Config{
57 InsecureSkipVerify: true,
58 },
59 },
60 },
61 }
62}
63
64// NewMessage creates a new NtfyMessage with the specified message content
65func (c *NtfyClient) NewMessage(messageText string) *NtfyMessage {
66 return &NtfyMessage{
67 Topic: c.Topic,
68 Message: messageText,
69 }
70}
71
72// SendMessage sends a structured NtfyMessage
73func (c *NtfyClient) SendMessage(msg *NtfyMessage) error {
74 // Ensure topic is set
75 if msg.Topic == "" {
76 msg.Topic = c.Topic
77 }
78
79 payload, err := json.Marshal(msg)
80 if err != nil {
81 return fmt.Errorf("error marshaling message: %v", err)
82 }
83
84 req, err := http.NewRequest("POST", c.ServerURL, bytes.NewBuffer(payload))
85 if err != nil {
86 return fmt.Errorf("error creating request: %v", err)
87 }
88
89 req.Header.Add("Content-Type", "application/json")
90
91 if c.AccessToken != "" {
92 req.Header.Add("Authorization", "Bearer "+c.AccessToken)
93 }
94
95 resp, err := c.HTTPClient.Do(req)
96 if err != nil {
97 return fmt.Errorf("error executing request: %v", err)
98 }
99 defer resp.Body.Close()
100
101 if resp.StatusCode < 200 || resp.StatusCode >= 300 {
102 body, _ := io.ReadAll(resp.Body)
103 return fmt.Errorf("error response (status %d): %s", resp.StatusCode, string(body))
104 }
105
106 return nil
107}