| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255 |
- package service
- import (
- "bytes"
- "encoding/json"
- "fmt"
- "net/http"
- "net/url"
- "strings"
- "github.com/QuantumNous/new-api/common"
- "github.com/QuantumNous/new-api/dto"
- "github.com/QuantumNous/new-api/model"
- "github.com/QuantumNous/new-api/setting/system_setting"
- )
- func NotifyRootUser(t string, subject string, content string) {
- user := model.GetRootUser().ToBaseUser()
- err := NotifyUser(user.Id, user.Email, user.GetSetting(), dto.NewNotify(t, subject, content, nil))
- if err != nil {
- common.SysLog(fmt.Sprintf("failed to notify root user: %s", err.Error()))
- }
- }
- func NotifyUser(userId int, userEmail string, userSetting dto.UserSetting, data dto.Notify) error {
- notifyType := userSetting.NotifyType
- if notifyType == "" {
- notifyType = dto.NotifyTypeEmail
- }
- // Check notification limit
- canSend, err := CheckNotificationLimit(userId, data.Type)
- if err != nil {
- common.SysLog(fmt.Sprintf("failed to check notification limit: %s", err.Error()))
- return err
- }
- if !canSend {
- return fmt.Errorf("notification limit exceeded for user %d with type %s", userId, notifyType)
- }
- switch notifyType {
- case dto.NotifyTypeEmail:
- // 优先使用设置中的通知邮箱,如果为空则使用用户的默认邮箱
- emailToUse := userSetting.NotificationEmail
- if emailToUse == "" {
- emailToUse = userEmail
- }
- if emailToUse == "" {
- common.SysLog(fmt.Sprintf("user %d has no email, skip sending email", userId))
- return nil
- }
- return sendEmailNotify(emailToUse, data)
- case dto.NotifyTypeWebhook:
- webhookURLStr := userSetting.WebhookUrl
- if webhookURLStr == "" {
- common.SysLog(fmt.Sprintf("user %d has no webhook url, skip sending webhook", userId))
- return nil
- }
- // 获取 webhook secret
- webhookSecret := userSetting.WebhookSecret
- return SendWebhookNotify(webhookURLStr, webhookSecret, data)
- case dto.NotifyTypeBark:
- barkURL := userSetting.BarkUrl
- if barkURL == "" {
- common.SysLog(fmt.Sprintf("user %d has no bark url, skip sending bark", userId))
- return nil
- }
- 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
- }
- func sendEmailNotify(userEmail string, data dto.Notify) error {
- // make email content
- content := data.Content
- // 处理占位符
- for _, value := range data.Values {
- content = strings.Replace(content, dto.ContentValueParam, fmt.Sprintf("%v", value), 1)
- }
- return common.SendEmail(data.Title, userEmail, content)
- }
- func sendBarkNotify(barkURL string, data dto.Notify) error {
- // 处理占位符
- content := data.Content
- for _, value := range data.Values {
- content = strings.Replace(content, dto.ContentValueParam, fmt.Sprintf("%v", value), 1)
- }
- // 替换模板变量
- finalURL := strings.ReplaceAll(barkURL, "{{title}}", url.QueryEscape(data.Title))
- finalURL = strings.ReplaceAll(finalURL, "{{content}}", url.QueryEscape(content))
- // 发送GET请求到Bark
- var req *http.Request
- var resp *http.Response
- var err error
- if system_setting.EnableWorker() {
- // 使用worker发送请求
- workerReq := &WorkerRequest{
- URL: finalURL,
- Key: system_setting.WorkerValidKey,
- Method: http.MethodGet,
- Headers: map[string]string{
- "User-Agent": "OneAPI-Bark-Notify/1.0",
- },
- }
- resp, err = DoWorkerRequest(workerReq)
- if err != nil {
- return fmt.Errorf("failed to send bark request through worker: %v", err)
- }
- defer resp.Body.Close()
- // 检查响应状态
- if resp.StatusCode < 200 || resp.StatusCode >= 300 {
- return fmt.Errorf("bark request failed with status code: %d", resp.StatusCode)
- }
- } else {
- // SSRF防护:验证Bark 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.MethodGet, finalURL, nil)
- if err != nil {
- return fmt.Errorf("failed to create bark request: %v", err)
- }
- // 设置User-Agent
- req.Header.Set("User-Agent", "OneAPI-Bark-Notify/1.0")
- // 发送请求
- client := GetHttpClient()
- resp, err = client.Do(req)
- if err != nil {
- return fmt.Errorf("failed to send bark request: %v", err)
- }
- defer resp.Body.Close()
- // 检查响应状态
- if resp.StatusCode < 200 || resp.StatusCode >= 300 {
- return fmt.Errorf("bark request failed with status code: %d", resp.StatusCode)
- }
- }
- 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
- }
|