| 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 |
| } |
|
|
| |
| 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 |
| } |
|
|
| |
| 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 { |
| |
| 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)) |
|
|
| |
| var req *http.Request |
| var resp *http.Response |
| var err error |
|
|
| if system_setting.EnableWorker() { |
| |
| 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 { |
| |
| 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) |
| } |
|
|
| |
| 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) |
| } |
|
|
| |
| |
| finalURL := strings.TrimSuffix(gotifyUrl, "/") + "/message?token=" + url.QueryEscape(gotifyToken) |
|
|
| |
| if priority < 0 || priority > 10 { |
| priority = 5 |
| } |
|
|
| |
| type GotifyMessage struct { |
| Title string `json:"title"` |
| Message string `json:"message"` |
| Priority int `json:"priority"` |
| } |
|
|
| payload := GotifyMessage{ |
| Title: data.Title, |
| Message: content, |
| Priority: priority, |
| } |
|
|
| |
| 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() { |
| |
| 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 { |
| |
| 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 |
| } |
|
|