11package service
22
33import (
4+ "bytes"
5+ "encoding/json"
46 "fmt"
57 "net/http"
68 "net/url"
@@ -37,13 +39,16 @@ func NotifyUser(userId int, userEmail string, userSetting dto.UserSetting, data
3739
3840 switch notifyType {
3941 case dto .NotifyTypeEmail :
40- // check setting email
41- userEmail = userSetting .NotificationEmail
42- if userEmail == "" {
42+ // 优先使用设置中的通知邮箱,如果为空则使用用户的默认邮箱
43+ emailToUse := userSetting .NotificationEmail
44+ if emailToUse == "" {
45+ emailToUse = userEmail
46+ }
47+ if emailToUse == "" {
4348 common .SysLog (fmt .Sprintf ("user %d has no email, skip sending email" , userId ))
4449 return nil
4550 }
46- return sendEmailNotify (userEmail , data )
51+ return sendEmailNotify (emailToUse , data )
4752 case dto .NotifyTypeWebhook :
4853 webhookURLStr := userSetting .WebhookUrl
4954 if webhookURLStr == "" {
@@ -61,6 +66,14 @@ func NotifyUser(userId int, userEmail string, userSetting dto.UserSetting, data
6166 return nil
6267 }
6368 return sendBarkNotify (barkURL , data )
69+ case dto .NotifyTypeGotify :
70+ gotifyUrl := userSetting .GotifyUrl
71+ gotifyToken := userSetting .GotifyToken
72+ if gotifyUrl == "" || gotifyToken == "" {
73+ common .SysLog (fmt .Sprintf ("user %d has no gotify url or token, skip sending gotify" , userId ))
74+ return nil
75+ }
76+ return sendGotifyNotify (gotifyUrl , gotifyToken , userSetting .GotifyPriority , data )
6477 }
6578 return nil
6679}
@@ -144,3 +157,98 @@ func sendBarkNotify(barkURL string, data dto.Notify) error {
144157
145158 return nil
146159}
160+
161+ func sendGotifyNotify (gotifyUrl string , gotifyToken string , priority int , data dto.Notify ) error {
162+ // 处理占位符
163+ content := data .Content
164+ for _ , value := range data .Values {
165+ content = strings .Replace (content , dto .ContentValueParam , fmt .Sprintf ("%v" , value ), 1 )
166+ }
167+
168+ // 构建完整的 Gotify API URL
169+ // 确保 URL 以 /message 结尾
170+ finalURL := strings .TrimSuffix (gotifyUrl , "/" ) + "/message?token=" + url .QueryEscape (gotifyToken )
171+
172+ // Gotify优先级范围0-10,如果超出范围则使用默认值5
173+ if priority < 0 || priority > 10 {
174+ priority = 5
175+ }
176+
177+ // 构建 JSON payload
178+ type GotifyMessage struct {
179+ Title string `json:"title"`
180+ Message string `json:"message"`
181+ Priority int `json:"priority"`
182+ }
183+
184+ payload := GotifyMessage {
185+ Title : data .Title ,
186+ Message : content ,
187+ Priority : priority ,
188+ }
189+
190+ // 序列化为 JSON
191+ payloadBytes , err := json .Marshal (payload )
192+ if err != nil {
193+ return fmt .Errorf ("failed to marshal gotify payload: %v" , err )
194+ }
195+
196+ var req * http.Request
197+ var resp * http.Response
198+
199+ if system_setting .EnableWorker () {
200+ // 使用worker发送请求
201+ workerReq := & WorkerRequest {
202+ URL : finalURL ,
203+ Key : system_setting .WorkerValidKey ,
204+ Method : http .MethodPost ,
205+ Headers : map [string ]string {
206+ "Content-Type" : "application/json; charset=utf-8" ,
207+ "User-Agent" : "OneAPI-Gotify-Notify/1.0" ,
208+ },
209+ Body : payloadBytes ,
210+ }
211+
212+ resp , err = DoWorkerRequest (workerReq )
213+ if err != nil {
214+ return fmt .Errorf ("failed to send gotify request through worker: %v" , err )
215+ }
216+ defer resp .Body .Close ()
217+
218+ // 检查响应状态
219+ if resp .StatusCode < 200 || resp .StatusCode >= 300 {
220+ return fmt .Errorf ("gotify request failed with status code: %d" , resp .StatusCode )
221+ }
222+ } else {
223+ // SSRF防护:验证Gotify URL(非Worker模式)
224+ fetchSetting := system_setting .GetFetchSetting ()
225+ if err := common .ValidateURLWithFetchSetting (finalURL , fetchSetting .EnableSSRFProtection , fetchSetting .AllowPrivateIp , fetchSetting .DomainFilterMode , fetchSetting .IpFilterMode , fetchSetting .DomainList , fetchSetting .IpList , fetchSetting .AllowedPorts , fetchSetting .ApplyIPFilterForDomain ); err != nil {
226+ return fmt .Errorf ("request reject: %v" , err )
227+ }
228+
229+ // 直接发送请求
230+ req , err = http .NewRequest (http .MethodPost , finalURL , bytes .NewBuffer (payloadBytes ))
231+ if err != nil {
232+ return fmt .Errorf ("failed to create gotify request: %v" , err )
233+ }
234+
235+ // 设置请求头
236+ req .Header .Set ("Content-Type" , "application/json; charset=utf-8" )
237+ req .Header .Set ("User-Agent" , "NewAPI-Gotify-Notify/1.0" )
238+
239+ // 发送请求
240+ client := GetHttpClient ()
241+ resp , err = client .Do (req )
242+ if err != nil {
243+ return fmt .Errorf ("failed to send gotify request: %v" , err )
244+ }
245+ defer resp .Body .Close ()
246+
247+ // 检查响应状态
248+ if resp .StatusCode < 200 || resp .StatusCode >= 300 {
249+ return fmt .Errorf ("gotify request failed with status code: %d" , resp .StatusCode )
250+ }
251+ }
252+
253+ return nil
254+ }
0 commit comments