Files
Havline/internal/notify/push.go
T

155 lines
4.3 KiB
Go
Raw Normal View History

2026-09-23 09:40:59 +08:00
package notify
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
)
func SendWebhook(ctx context.Context, client *http.Client, cfg RuntimeConfig, content AlertContent) error {
switch cfg.Webhook.Provider {
case WebhookBark:
return sendBark(ctx, client, cfg, content.PushTitle, content.barkMessage())
case WebhookNtfy:
return sendNtfy(ctx, client, cfg, content.PushTitle, content.PlainBody)
case WebhookGotify:
return sendGotify(ctx, client, cfg, content.PushTitle, content.PlainBody)
case WebhookCustom:
return sendCustomWebhook(ctx, client, cfg, content)
default:
return fmt.Errorf("不支持的 Webhook 预设")
}
}
func webhookSecret(cfg RuntimeConfig) string {
if strings.TrimSpace(cfg.WebhookSecret) != "" {
return strings.TrimSpace(cfg.WebhookSecret)
}
return strings.TrimSpace(cfg.Webhook.Key)
}
func sendBark(ctx context.Context, client *http.Client, cfg RuntimeConfig, title, message string) error {
key := webhookSecret(cfg)
if key == "" {
return fmt.Errorf("Bark Device Key 未配置")
}
server := normalizeServerURL(cfg.Webhook.Server)
if server == "" {
server = DefaultBarkServer
}
endpoint := fmt.Sprintf("%s/%s/%s/%s", server, url.PathEscape(key), url.PathEscape(title), url.PathEscape(message))
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, nil)
if err != nil {
return err
}
return doRequest(client, req)
}
func sendNtfy(ctx context.Context, client *http.Client, cfg RuntimeConfig, title, message string) error {
topic := strings.TrimSpace(cfg.Webhook.Topic)
if topic == "" {
return fmt.Errorf("ntfy Topic 未配置")
}
server := normalizeServerURL(cfg.Webhook.Server)
if server == "" {
server = "https://ntfy.sh"
}
endpoint := joinURL(server, topic)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, strings.NewReader(message))
if err != nil {
return err
}
req.Header.Set("Title", title)
req.Header.Set("Content-Type", "text/plain; charset=utf-8")
if token := webhookSecret(cfg); token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}
return doRequest(client, req)
}
func sendGotify(ctx context.Context, client *http.Client, cfg RuntimeConfig, title, message string) error {
token := webhookSecret(cfg)
if token == "" {
return fmt.Errorf("Gotify App Token 未配置")
}
server := normalizeServerURL(cfg.Webhook.Server)
if server == "" {
return fmt.Errorf("Gotify 服务地址未配置")
}
endpoint := joinURL(server, "message") + "?token=" + url.QueryEscape(token)
payload := map[string]any{
"title": title,
"message": message,
"priority": 5,
}
body, _ := json.Marshal(payload)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(body))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
return doRequest(client, req)
}
func sendCustomWebhook(ctx context.Context, client *http.Client, cfg RuntimeConfig, content AlertContent) error {
target := strings.TrimSpace(cfg.Webhook.URL)
if target == "" {
return fmt.Errorf("Webhook URL 未配置")
}
body, _ := json.Marshal(content.webhookPayload())
req, err := http.NewRequestWithContext(ctx, http.MethodPost, target, bytes.NewReader(body))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
return doRequest(client, req)
}
func doRequest(client *http.Client, req *http.Request) error {
if client == nil {
client = &http.Client{Timeout: 10 * time.Second}
}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
body, _ := io.ReadAll(io.LimitReader(resp.Body, 512))
if len(body) > 0 {
return fmt.Errorf("请求失败 (%d): %s", resp.StatusCode, strings.TrimSpace(string(body)))
}
return fmt.Errorf("请求失败 (%d)", resp.StatusCode)
}
return nil
}
func normalizeServerURL(server string) string {
server = strings.TrimSpace(server)
server = strings.TrimRight(server, "/")
if server == "" {
return ""
}
if !strings.Contains(server, "://") {
server = "https://" + server
}
return server
}
func joinURL(base string, parts ...string) string {
base = strings.TrimRight(normalizeServerURL(base), "/")
for _, part := range parts {
part = strings.Trim(part, "/")
if part != "" {
base += "/" + part
}
}
return base
}