Files
MeBox/internal/service/notify_dispatch.go
T
2026-06-27 01:08:02 +08:00

164 lines
4.4 KiB
Go

package service
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"github.com/ShukeBta/MediaStationGo/internal/model"
)
// channelSubscribes returns true when the channel's Events list contains the
// event, or when the list is the legacy empty/"all events" value.
func channelSubscribes(n model.NotifyChannel, event string) bool {
if event == "" || n.Events == "" {
return true
}
var ev []string
if err := json.Unmarshal([]byte(n.Events), &ev); err != nil {
return true
}
if len(ev) == 0 {
return true
}
for _, e := range ev {
switch e {
case NotifyEventNone:
return false
case NotifyEventAll:
return true
}
if e == event {
return true
}
}
return false
}
// dispatchOne is the inner dispatcher; the channel type drives which
// HTTP request gets built.
func (s *NotifyChannelService) dispatchOne(ctx context.Context, n model.NotifyChannel, title, body string) error {
return s.dispatchOneEvent(ctx, n, NotifyEvent{Title: title, Message: body})
}
func (s *NotifyChannelService) dispatchOneEvent(ctx context.Context, n model.NotifyChannel, event NotifyEvent) error {
cfg := map[string]any{}
_ = json.Unmarshal([]byte(n.Config), &cfg)
title := event.Title
body := event.Message
switch n.Type {
case "telegram":
telegramCfg := telegramStringConfigFromAny(cfg)
token := telegramCfg["bot_token"]
chats := telegramTargetChatIDs(telegramCfg)
if token == "" || len(chats) == 0 {
return errors.New("telegram missing bot_token / group_chat_id / channel_chat_id")
}
text := formatTelegramNotification(event)
photoURL := telegramEventPhotoURL(event)
var firstErr error
for _, chat := range chats {
if photoURL != "" && len(text) <= 1024 {
form := url.Values{}
form.Set("chat_id", chat)
form.Set("photo", photoURL)
form.Set("caption", text)
form.Set("parse_mode", "HTML")
if err := telegramPostForm(ctx, telegramCfg, "sendPhoto", form, 15*time.Second); err == nil {
continue
} else if firstErr == nil {
firstErr = err
}
}
form := url.Values{}
form.Set("chat_id", chat)
form.Set("text", text)
form.Set("parse_mode", "HTML")
if err := telegramPostForm(ctx, telegramCfg, "sendMessage", form, 15*time.Second); err != nil && firstErr == nil {
firstErr = err
}
}
return firstErr
case "bark":
key := str(cfg["device_key"])
if key == "" {
return errors.New("bark missing device_key")
}
server := str(cfg["server"])
if server == "" {
server = "https://api.day.app"
}
u := fmt.Sprintf("%s/%s/%s/%s",
strings.TrimRight(server, "/"),
url.PathEscape(key),
url.PathEscape(title),
url.PathEscape(body),
)
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
return s.do(req)
case "wechat":
key := str(cfg["sendkey"])
if key == "" {
return errors.New("wechat missing sendkey")
}
u := fmt.Sprintf("https://sctapi.ftqq.com/%s.send", url.PathEscape(key))
form := url.Values{}
form.Set("title", title)
form.Set("desp", body)
req, _ := http.NewRequestWithContext(ctx, http.MethodPost, u, strings.NewReader(form.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
return s.do(req)
case "webhook":
urlS := str(cfg["url"])
if urlS == "" {
return errors.New("webhook missing url")
}
method := strings.ToUpper(str(cfg["method"]))
if method == "" {
method = "POST"
}
// Substitute {{title}} / {{message}} in the body template.
bodyTpl := str(cfg["body_template"])
if bodyTpl == "" {
bodyTpl = `{"title":"{{title}}","message":"{{message}}"}`
}
bodyStr := strings.NewReplacer("{{title}}", title, "{{message}}", body).Replace(bodyTpl)
req, _ := http.NewRequestWithContext(ctx, method, urlS, strings.NewReader(bodyStr))
// Apply custom headers (encoded as JSON in the config).
if hdrRaw := str(cfg["headers"]); hdrRaw != "" {
var hdr map[string]string
if err := json.Unmarshal([]byte(hdrRaw), &hdr); err == nil {
for k, v := range hdr {
req.Header.Set(k, v)
}
}
}
if req.Header.Get("Content-Type") == "" && method != http.MethodGet {
req.Header.Set("Content-Type", "application/json")
}
return s.do(req)
}
return fmt.Errorf("unknown channel type %q", n.Type)
}
func (s *NotifyChannelService) do(req *http.Request) error {
resp, err := s.client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return fmt.Errorf("upstream returned %d", resp.StatusCode)
}
return nil
}