package service import ( "fmt" "net/http" "net/url" "one-api/common" "one-api/dto" "one-api/model" "one-api/setting/system_setting" "strings" ) 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: // check setting email userEmail = userSetting.NotificationEmail if userEmail == "" { common.SysLog(fmt.Sprintf("user %d has no email, skip sending email", userId)) return nil } return sendEmailNotify(userEmail, 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) } 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 }