|
@@ -1,6 +1,8 @@
|
|
|
package service
|
|
package service
|
|
|
|
|
|
|
|
import (
|
|
import (
|
|
|
|
|
+ "bytes"
|
|
|
|
|
+ "encoding/json"
|
|
|
"fmt"
|
|
"fmt"
|
|
|
"net/http"
|
|
"net/http"
|
|
|
"net/url"
|
|
"net/url"
|
|
@@ -37,13 +39,16 @@ func NotifyUser(userId int, userEmail string, userSetting dto.UserSetting, data
|
|
|
|
|
|
|
|
switch notifyType {
|
|
switch notifyType {
|
|
|
case dto.NotifyTypeEmail:
|
|
case dto.NotifyTypeEmail:
|
|
|
- // check setting email
|
|
|
|
|
- userEmail = userSetting.NotificationEmail
|
|
|
|
|
- if userEmail == "" {
|
|
|
|
|
|
|
+ // 优先使用设置中的通知邮箱,如果为空则使用用户的默认邮箱
|
|
|
|
|
+ emailToUse := userSetting.NotificationEmail
|
|
|
|
|
+ if emailToUse == "" {
|
|
|
|
|
+ emailToUse = userEmail
|
|
|
|
|
+ }
|
|
|
|
|
+ if emailToUse == "" {
|
|
|
common.SysLog(fmt.Sprintf("user %d has no email, skip sending email", userId))
|
|
common.SysLog(fmt.Sprintf("user %d has no email, skip sending email", userId))
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
|
- return sendEmailNotify(userEmail, data)
|
|
|
|
|
|
|
+ return sendEmailNotify(emailToUse, data)
|
|
|
case dto.NotifyTypeWebhook:
|
|
case dto.NotifyTypeWebhook:
|
|
|
webhookURLStr := userSetting.WebhookUrl
|
|
webhookURLStr := userSetting.WebhookUrl
|
|
|
if webhookURLStr == "" {
|
|
if webhookURLStr == "" {
|
|
@@ -61,6 +66,14 @@ func NotifyUser(userId int, userEmail string, userSetting dto.UserSetting, data
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
|
return sendBarkNotify(barkURL, data)
|
|
return sendBarkNotify(barkURL, data)
|
|
|
|
|
+ case dto.NotifyTypeGotify:
|
|
|
|
|
+ gotifyUrl := userSetting.GotifyUrl
|
|
|
|
|
+ gotifyToken := userSetting.GotifyToken
|
|
|
|
|
+ if gotifyUrl == "" || gotifyToken == "" {
|
|
|
|
|
+ common.SysLog(fmt.Sprintf("user %d has no gotify url or token, skip sending gotify", userId))
|
|
|
|
|
+ return nil
|
|
|
|
|
+ }
|
|
|
|
|
+ return sendGotifyNotify(gotifyUrl, gotifyToken, userSetting.GotifyPriority, data)
|
|
|
}
|
|
}
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
@@ -144,3 +157,98 @@ func sendBarkNotify(barkURL string, data dto.Notify) error {
|
|
|
|
|
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+func sendGotifyNotify(gotifyUrl string, gotifyToken string, priority int, data dto.Notify) error {
|
|
|
|
|
+ // 处理占位符
|
|
|
|
|
+ content := data.Content
|
|
|
|
|
+ for _, value := range data.Values {
|
|
|
|
|
+ content = strings.Replace(content, dto.ContentValueParam, fmt.Sprintf("%v", value), 1)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 构建完整的 Gotify API URL
|
|
|
|
|
+ // 确保 URL 以 /message 结尾
|
|
|
|
|
+ finalURL := strings.TrimSuffix(gotifyUrl, "/") + "/message?token=" + url.QueryEscape(gotifyToken)
|
|
|
|
|
+
|
|
|
|
|
+ // Gotify优先级范围0-10,如果超出范围则使用默认值5
|
|
|
|
|
+ if priority < 0 || priority > 10 {
|
|
|
|
|
+ priority = 5
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 构建 JSON payload
|
|
|
|
|
+ type GotifyMessage struct {
|
|
|
|
|
+ Title string `json:"title"`
|
|
|
|
|
+ Message string `json:"message"`
|
|
|
|
|
+ Priority int `json:"priority"`
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ payload := GotifyMessage{
|
|
|
|
|
+ Title: data.Title,
|
|
|
|
|
+ Message: content,
|
|
|
|
|
+ Priority: priority,
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 序列化为 JSON
|
|
|
|
|
+ payloadBytes, err := json.Marshal(payload)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return fmt.Errorf("failed to marshal gotify payload: %v", err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var req *http.Request
|
|
|
|
|
+ var resp *http.Response
|
|
|
|
|
+
|
|
|
|
|
+ if system_setting.EnableWorker() {
|
|
|
|
|
+ // 使用worker发送请求
|
|
|
|
|
+ workerReq := &WorkerRequest{
|
|
|
|
|
+ URL: finalURL,
|
|
|
|
|
+ Key: system_setting.WorkerValidKey,
|
|
|
|
|
+ Method: http.MethodPost,
|
|
|
|
|
+ Headers: map[string]string{
|
|
|
|
|
+ "Content-Type": "application/json; charset=utf-8",
|
|
|
|
|
+ "User-Agent": "OneAPI-Gotify-Notify/1.0",
|
|
|
|
|
+ },
|
|
|
|
|
+ Body: payloadBytes,
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ resp, err = DoWorkerRequest(workerReq)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return fmt.Errorf("failed to send gotify request through worker: %v", err)
|
|
|
|
|
+ }
|
|
|
|
|
+ defer resp.Body.Close()
|
|
|
|
|
+
|
|
|
|
|
+ // 检查响应状态
|
|
|
|
|
+ if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
|
|
|
+ return fmt.Errorf("gotify request failed with status code: %d", resp.StatusCode)
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // SSRF防护:验证Gotify URL(非Worker模式)
|
|
|
|
|
+ fetchSetting := system_setting.GetFetchSetting()
|
|
|
|
|
+ if err := common.ValidateURLWithFetchSetting(finalURL, fetchSetting.EnableSSRFProtection, fetchSetting.AllowPrivateIp, fetchSetting.DomainFilterMode, fetchSetting.IpFilterMode, fetchSetting.DomainList, fetchSetting.IpList, fetchSetting.AllowedPorts, fetchSetting.ApplyIPFilterForDomain); err != nil {
|
|
|
|
|
+ return fmt.Errorf("request reject: %v", err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 直接发送请求
|
|
|
|
|
+ req, err = http.NewRequest(http.MethodPost, finalURL, bytes.NewBuffer(payloadBytes))
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return fmt.Errorf("failed to create gotify request: %v", err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 设置请求头
|
|
|
|
|
+ req.Header.Set("Content-Type", "application/json; charset=utf-8")
|
|
|
|
|
+ req.Header.Set("User-Agent", "NewAPI-Gotify-Notify/1.0")
|
|
|
|
|
+
|
|
|
|
|
+ // 发送请求
|
|
|
|
|
+ client := GetHttpClient()
|
|
|
|
|
+ resp, err = client.Do(req)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return fmt.Errorf("failed to send gotify request: %v", err)
|
|
|
|
|
+ }
|
|
|
|
|
+ defer resp.Body.Close()
|
|
|
|
|
+
|
|
|
|
|
+ // 检查响应状态
|
|
|
|
|
+ if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
|
|
|
+ return fmt.Errorf("gotify request failed with status code: %d", resp.StatusCode)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return nil
|
|
|
|
|
+}
|