user_notify.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. package service
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "fmt"
  6. "net/http"
  7. "net/url"
  8. "strings"
  9. "github.com/QuantumNous/new-api/common"
  10. "github.com/QuantumNous/new-api/dto"
  11. "github.com/QuantumNous/new-api/model"
  12. "github.com/QuantumNous/new-api/setting/system_setting"
  13. )
  14. func NotifyRootUser(t string, subject string, content string) {
  15. user := model.GetRootUser().ToBaseUser()
  16. err := NotifyUser(user.Id, user.Email, user.GetSetting(), dto.NewNotify(t, subject, content, nil))
  17. if err != nil {
  18. common.SysLog(fmt.Sprintf("failed to notify root user: %s", err.Error()))
  19. }
  20. }
  21. func NotifyUpstreamModelUpdateWatchers(subject string, content string) {
  22. var users []model.User
  23. if err := model.DB.
  24. Select("id", "email", "role", "status", "setting").
  25. Where("status = ? AND role >= ?", common.UserStatusEnabled, common.RoleAdminUser).
  26. Find(&users).Error; err != nil {
  27. common.SysLog(fmt.Sprintf("failed to query upstream update notification users: %s", err.Error()))
  28. return
  29. }
  30. notification := dto.NewNotify(dto.NotifyTypeChannelUpdate, subject, content, nil)
  31. sentCount := 0
  32. for _, user := range users {
  33. userSetting := user.GetSetting()
  34. if !userSetting.UpstreamModelUpdateNotifyEnabled {
  35. continue
  36. }
  37. if err := NotifyUser(user.Id, user.Email, userSetting, notification); err != nil {
  38. common.SysLog(fmt.Sprintf("failed to notify user %d for upstream model update: %s", user.Id, err.Error()))
  39. continue
  40. }
  41. sentCount++
  42. }
  43. common.SysLog(fmt.Sprintf("upstream model update notifications sent: %d", sentCount))
  44. }
  45. func NotifyUser(userId int, userEmail string, userSetting dto.UserSetting, data dto.Notify) error {
  46. notifyType := userSetting.NotifyType
  47. if notifyType == "" {
  48. notifyType = dto.NotifyTypeEmail
  49. }
  50. // Check notification limit
  51. canSend, err := CheckNotificationLimit(userId, data.Type)
  52. if err != nil {
  53. common.SysLog(fmt.Sprintf("failed to check notification limit: %s", err.Error()))
  54. return err
  55. }
  56. if !canSend {
  57. return fmt.Errorf("notification limit exceeded for user %d with type %s", userId, notifyType)
  58. }
  59. switch notifyType {
  60. case dto.NotifyTypeEmail:
  61. // 优先使用设置中的通知邮箱,如果为空则使用用户的默认邮箱
  62. emailToUse := userSetting.NotificationEmail
  63. if emailToUse == "" {
  64. emailToUse = userEmail
  65. }
  66. if emailToUse == "" {
  67. common.SysLog(fmt.Sprintf("user %d has no email, skip sending email", userId))
  68. return nil
  69. }
  70. return sendEmailNotify(emailToUse, data)
  71. case dto.NotifyTypeWebhook:
  72. webhookURLStr := userSetting.WebhookUrl
  73. if webhookURLStr == "" {
  74. common.SysLog(fmt.Sprintf("user %d has no webhook url, skip sending webhook", userId))
  75. return nil
  76. }
  77. // 获取 webhook secret
  78. webhookSecret := userSetting.WebhookSecret
  79. return SendWebhookNotify(webhookURLStr, webhookSecret, data)
  80. case dto.NotifyTypeBark:
  81. barkURL := userSetting.BarkUrl
  82. if barkURL == "" {
  83. common.SysLog(fmt.Sprintf("user %d has no bark url, skip sending bark", userId))
  84. return nil
  85. }
  86. return sendBarkNotify(barkURL, data)
  87. case dto.NotifyTypeGotify:
  88. gotifyUrl := userSetting.GotifyUrl
  89. gotifyToken := userSetting.GotifyToken
  90. if gotifyUrl == "" || gotifyToken == "" {
  91. common.SysLog(fmt.Sprintf("user %d has no gotify url or token, skip sending gotify", userId))
  92. return nil
  93. }
  94. return sendGotifyNotify(gotifyUrl, gotifyToken, userSetting.GotifyPriority, data)
  95. }
  96. return nil
  97. }
  98. func sendEmailNotify(userEmail string, data dto.Notify) error {
  99. // make email content
  100. content := data.Content
  101. // 处理占位符
  102. for _, value := range data.Values {
  103. content = strings.Replace(content, dto.ContentValueParam, fmt.Sprintf("%v", value), 1)
  104. }
  105. return common.SendEmail(data.Title, userEmail, content)
  106. }
  107. func sendBarkNotify(barkURL string, data dto.Notify) error {
  108. // 处理占位符
  109. content := data.Content
  110. for _, value := range data.Values {
  111. content = strings.Replace(content, dto.ContentValueParam, fmt.Sprintf("%v", value), 1)
  112. }
  113. // 替换模板变量
  114. finalURL := strings.ReplaceAll(barkURL, "{{title}}", url.QueryEscape(data.Title))
  115. finalURL = strings.ReplaceAll(finalURL, "{{content}}", url.QueryEscape(content))
  116. // 发送GET请求到Bark
  117. var req *http.Request
  118. var resp *http.Response
  119. var err error
  120. if system_setting.EnableWorker() {
  121. // 使用worker发送请求
  122. workerReq := &WorkerRequest{
  123. URL: finalURL,
  124. Key: system_setting.WorkerValidKey,
  125. Method: http.MethodGet,
  126. Headers: map[string]string{
  127. "User-Agent": "OneAPI-Bark-Notify/1.0",
  128. },
  129. }
  130. resp, err = DoWorkerRequest(workerReq)
  131. if err != nil {
  132. return fmt.Errorf("failed to send bark request through worker: %v", err)
  133. }
  134. defer resp.Body.Close()
  135. // 检查响应状态
  136. if resp.StatusCode < 200 || resp.StatusCode >= 300 {
  137. return fmt.Errorf("bark request failed with status code: %d", resp.StatusCode)
  138. }
  139. } else {
  140. // SSRF防护:验证Bark URL(非Worker模式)
  141. fetchSetting := system_setting.GetFetchSetting()
  142. if err := common.ValidateURLWithFetchSetting(finalURL, fetchSetting.EnableSSRFProtection, fetchSetting.AllowPrivateIp, fetchSetting.DomainFilterMode, fetchSetting.IpFilterMode, fetchSetting.DomainList, fetchSetting.IpList, fetchSetting.AllowedPorts, fetchSetting.ApplyIPFilterForDomain); err != nil {
  143. return fmt.Errorf("request reject: %v", err)
  144. }
  145. // 直接发送请求
  146. req, err = http.NewRequest(http.MethodGet, finalURL, nil)
  147. if err != nil {
  148. return fmt.Errorf("failed to create bark request: %v", err)
  149. }
  150. // 设置User-Agent
  151. req.Header.Set("User-Agent", "OneAPI-Bark-Notify/1.0")
  152. // 发送请求
  153. client := GetHttpClient()
  154. resp, err = client.Do(req)
  155. if err != nil {
  156. return fmt.Errorf("failed to send bark request: %v", err)
  157. }
  158. defer resp.Body.Close()
  159. // 检查响应状态
  160. if resp.StatusCode < 200 || resp.StatusCode >= 300 {
  161. return fmt.Errorf("bark request failed with status code: %d", resp.StatusCode)
  162. }
  163. }
  164. return nil
  165. }
  166. func sendGotifyNotify(gotifyUrl string, gotifyToken string, priority int, data dto.Notify) error {
  167. // 处理占位符
  168. content := data.Content
  169. for _, value := range data.Values {
  170. content = strings.Replace(content, dto.ContentValueParam, fmt.Sprintf("%v", value), 1)
  171. }
  172. // 构建完整的 Gotify API URL
  173. // 确保 URL 以 /message 结尾
  174. finalURL := strings.TrimSuffix(gotifyUrl, "/") + "/message?token=" + url.QueryEscape(gotifyToken)
  175. // Gotify优先级范围0-10,如果超出范围则使用默认值5
  176. if priority < 0 || priority > 10 {
  177. priority = 5
  178. }
  179. // 构建 JSON payload
  180. type GotifyMessage struct {
  181. Title string `json:"title"`
  182. Message string `json:"message"`
  183. Priority int `json:"priority"`
  184. }
  185. payload := GotifyMessage{
  186. Title: data.Title,
  187. Message: content,
  188. Priority: priority,
  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. var req *http.Request
  196. var resp *http.Response
  197. if system_setting.EnableWorker() {
  198. // 使用worker发送请求
  199. workerReq := &WorkerRequest{
  200. URL: finalURL,
  201. Key: system_setting.WorkerValidKey,
  202. Method: http.MethodPost,
  203. Headers: map[string]string{
  204. "Content-Type": "application/json; charset=utf-8",
  205. "User-Agent": "OneAPI-Gotify-Notify/1.0",
  206. },
  207. Body: payloadBytes,
  208. }
  209. resp, err = DoWorkerRequest(workerReq)
  210. if err != nil {
  211. return fmt.Errorf("failed to send gotify request through worker: %v", err)
  212. }
  213. defer resp.Body.Close()
  214. // 检查响应状态
  215. if resp.StatusCode < 200 || resp.StatusCode >= 300 {
  216. return fmt.Errorf("gotify request failed with status code: %d", resp.StatusCode)
  217. }
  218. } else {
  219. // SSRF防护:验证Gotify URL(非Worker模式)
  220. fetchSetting := system_setting.GetFetchSetting()
  221. if err := common.ValidateURLWithFetchSetting(finalURL, fetchSetting.EnableSSRFProtection, fetchSetting.AllowPrivateIp, fetchSetting.DomainFilterMode, fetchSetting.IpFilterMode, fetchSetting.DomainList, fetchSetting.IpList, fetchSetting.AllowedPorts, fetchSetting.ApplyIPFilterForDomain); err != nil {
  222. return fmt.Errorf("request reject: %v", err)
  223. }
  224. // 直接发送请求
  225. req, err = http.NewRequest(http.MethodPost, finalURL, bytes.NewBuffer(payloadBytes))
  226. if err != nil {
  227. return fmt.Errorf("failed to create gotify request: %v", err)
  228. }
  229. // 设置请求头
  230. req.Header.Set("Content-Type", "application/json; charset=utf-8")
  231. req.Header.Set("User-Agent", "NewAPI-Gotify-Notify/1.0")
  232. // 发送请求
  233. client := GetHttpClient()
  234. resp, err = client.Do(req)
  235. if err != nil {
  236. return fmt.Errorf("failed to send gotify request: %v", err)
  237. }
  238. defer resp.Body.Close()
  239. // 检查响应状态
  240. if resp.StatusCode < 200 || resp.StatusCode >= 300 {
  241. return fmt.Errorf("gotify request failed with status code: %d", resp.StatusCode)
  242. }
  243. }
  244. return nil
  245. }