package op import ( "bytes" "encoding/json" "fmt" "mime/multipart" "net/http" "net/url" "reflect" "strings" "github.com/alist-org/alist/v3/internal/conf" "github.com/alist-org/alist/v3/internal/model" "github.com/pkg/errors" log "github.com/sirupsen/logrus" "golang.org/x/text/cases" "golang.org/x/text/language" ) type SendNotifyPlatform struct{} // 注意映射方法名必需大写要不然找不到 func (e SendNotifyPlatform) Bark(body string, title string, content string) (bool, error) { var bark model.Bark err := json.Unmarshal([]byte(body), &bark) if err != nil { log.Errorln("无法解析配置文件") return false, errors.Errorf("无法解析配置文件") } if len(bark.BarkPush) < 2 { log.Errorln("请正确设置BarkPush") return false, errors.Errorf("请正确设置BarkPush") } if !strings.HasPrefix(bark.BarkPush, "http") { bark.BarkPush = fmt.Sprintf("https://api.day.app/%s", bark.BarkPush) } urlValues := url.Values{} urlValues.Set("icon", bark.BarkIcon) urlValues.Set("sound", bark.BarkSound) urlValues.Set("group", bark.BarkGroup) urlValues.Set("level", bark.BarkLevel) urlValues.Set("url", bark.BarkUrl) url := fmt.Sprintf("%s/%s/%s?%s", bark.BarkPush, url.QueryEscape(title), url.QueryEscape(content), urlValues.Encode()) resp, err := http.Get(url) if err != nil { log.Error("通知发送失败") return false, err } defer resp.Body.Close() if resp.StatusCode == http.StatusOK { return true, nil } else { return false, fmt.Errorf("Unexpected status code: %d", resp.StatusCode) } } // 注意映射方法名必需大写要不然找不到 func (e SendNotifyPlatform) Gotify(body string, title string, content string) (bool, error) { m := make(map[string]interface{}) err := json.Unmarshal([]byte(body), &m) gotifyUrl, gotifyToken, gotifyPriority := m["gotifyUrl"].(string), m["gotifyToken"].(string), m["gotifyPriority"].(string) surl := fmt.Sprintf("%s/message?token=%s", gotifyUrl, gotifyToken) data := url.Values{} data.Set("title", title) data.Set("message", content) data.Set("priority", fmt.Sprintf("%d", gotifyPriority)) req, err := http.NewRequest("POST", surl, bytes.NewBufferString(data.Encode())) if err != nil { return false, err } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") client := &http.Client{} resp, err := client.Do(req) if err != nil { return false, err } defer resp.Body.Close() if err != nil { log.Error("通知发送失败") return false, err } defer resp.Body.Close() if resp.StatusCode == http.StatusOK { return true, nil } else { return false, fmt.Errorf("Unexpected status code: %d", resp.StatusCode) } } // 注意映射方法名必需大写要不然找不到 func (e SendNotifyPlatform) GoCqHttpBot(body string, title string, content string) (bool, error) { m := make(map[string]interface{}) err := json.Unmarshal([]byte(body), &m) goCqHttpBotUrl, goCqHttpBotToken, goCqHttpBotQq := m["goCqHttpBotUrl"].(string), m["goCqHttpBotToken"].(string), m["goCqHttpBotQq"].(string) surl := fmt.Sprintf("%s?user_id=%s", goCqHttpBotUrl, goCqHttpBotQq) data := map[string]string{"message": fmt.Sprintf("%s\n%s", title, content)} jsonData, err := json.Marshal(data) if err != nil { return false, err } req, err := http.NewRequest("POST", surl, bytes.NewBuffer(jsonData)) if err != nil { return false, err } req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+goCqHttpBotToken) client := &http.Client{} resp, err := client.Do(req) if err != nil { return false, err } defer resp.Body.Close() if err != nil { log.Error("通知发送失败") return false, err } defer resp.Body.Close() if resp.StatusCode == http.StatusOK { return true, nil } else { return false, fmt.Errorf("Unexpected status code: %d", resp.StatusCode) } } // 注意映射方法名必需大写要不然找不到 func (e SendNotifyPlatform) ServerChan(body string, title string, content string) (bool, error) { m := make(map[string]interface{}) err := json.Unmarshal([]byte(body), &m) serverChanKey := m["serverChanKey"].(string) surl := "" if len(serverChanKey) >= 3 && serverChanKey[:3] == "SCT" { surl = fmt.Sprintf("https://sctapi.ftqq.com/%s.send", serverChanKey) } else { surl = fmt.Sprintf("https://sc.ftqq.com/%s.send", serverChanKey) } data := url.Values{} data.Set("title", title) data.Set("desp", content) req, err := http.NewRequest("POST", surl, bytes.NewBufferString(data.Encode())) if err != nil { return false, err } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") client := &http.Client{} resp, err := client.Do(req) if err != nil { return false, err } defer resp.Body.Close() if err != nil { log.Error("通知发送失败") return false, err } defer resp.Body.Close() if resp.StatusCode == http.StatusOK { return true, nil } else { return false, fmt.Errorf("Unexpected status code: %d", resp.StatusCode) } } // 注意映射方法名必需大写要不然找不到 func (e SendNotifyPlatform) PushDeer(body string, title string, content string) (bool, error) { m := make(map[string]interface{}) err := json.Unmarshal([]byte(body), &m) pushDeerKey, pushDeerUrl := m["pushDeerKey"].(string), m["pushDeerUrl"].(string) surl := pushDeerUrl if surl == "" { surl = "https://api2.pushdeer.com/message/push" } data := url.Values{} data.Set("pushkey", pushDeerKey) data.Set("text", title) data.Set("desp", content) data.Set("type", "markdown") req, err := http.NewRequest("POST", surl, bytes.NewBufferString(data.Encode())) if err != nil { return false, err } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") client := &http.Client{} resp, err := client.Do(req) if err != nil { return false, err } defer resp.Body.Close() if err != nil { log.Error("通知发送失败") return false, err } defer resp.Body.Close() if resp.StatusCode == http.StatusOK { return true, nil } else { return false, fmt.Errorf("Unexpected status code: %d", resp.StatusCode) } } // 注意映射方法名必需大写要不然找不到 func (e SendNotifyPlatform) TelegramBot(body string, title string, content string) (bool, error) { m := make(map[string]interface{}) err := json.Unmarshal([]byte(body), &m) telegramBotToken, telegramBotUserId, telegramBotProxyHost, telegramBotProxyPort, telegramBotProxyAuth, telegramBotApiHost := m["telegramBotToken"].(string), m["telegramBotUserId"].(string), m["telegramBotProxyHost"].(string), m["telegramBotProxyPort"].(string), m["telegramBotProxyAuth"].(string), m["telegramBotApiHost"].(string) if telegramBotApiHost == "" { telegramBotApiHost = "https://api.telegram.org" } surl := fmt.Sprintf("%s/bot%s/sendMessage", telegramBotApiHost, telegramBotToken) var client *http.Client if telegramBotProxyHost != "" && telegramBotProxyPort != "" { proxyURL := fmt.Sprintf("http://%s:%s", telegramBotProxyHost, telegramBotProxyPort) if telegramBotProxyAuth != "" { proxyURL = fmt.Sprintf("http://%s@%s:%s", telegramBotProxyAuth, telegramBotProxyHost, telegramBotProxyPort) } proxy := func(_ *http.Request) (*url.URL, error) { return url.Parse(proxyURL) } client = &http.Client{ Transport: &http.Transport{ Proxy: proxy, }, } } else { client = http.DefaultClient } data := url.Values{} data.Set("chat_id", telegramBotUserId) data.Set("text", fmt.Sprintf("%s\n\n%s", title, content)) data.Set("disable_web_page_preview", "true") req, err := http.NewRequest("POST", surl, strings.NewReader(data.Encode())) if err != nil { return false, err } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") resp, err := client.Do(req) if err != nil { return false, err } defer resp.Body.Close() if err != nil { log.Error("通知发送失败") return false, err } defer resp.Body.Close() if resp.StatusCode == http.StatusOK { return true, nil } else { return false, fmt.Errorf("Unexpected status code: %d", resp.StatusCode) } } // 注意映射方法名必需大写要不然找不到 func (e SendNotifyPlatform) WeWorkBot(body string, title string, content string) (bool, error) { m := make(map[string]interface{}) err := json.Unmarshal([]byte(body), &m) weWorkBotKey, weWorkOrigin := m["weWorkBotKey"].(string), m["weWorkOrigin"].(string) if weWorkOrigin == "" { weWorkOrigin = "https://qyapi.weixin.qq.com" } surl := fmt.Sprintf("%s/cgi-bin/webhook/send?key=%s", weWorkOrigin, weWorkBotKey) bodyData := map[string]interface{}{ "msgtype": "text", "text": map[string]string{ "content": fmt.Sprintf("%s\n\n%s", title, content), }, } data, err := json.Marshal(bodyData) if err != nil { return false, err } var client *http.Client client = http.DefaultClient req, err := http.NewRequest("POST", surl, strings.NewReader(string(data))) if err != nil { return false, err } req.Header.Set("Content-Type", "application/json") resp, err := client.Do(req) if err != nil { return false, err } defer resp.Body.Close() if err != nil { log.Error("通知发送失败") return false, err } defer resp.Body.Close() if resp.StatusCode == http.StatusOK { return true, nil } else { return false, fmt.Errorf("Unexpected status code: %d", resp.StatusCode) } } // 注意映射方法名必需大写要不然找不到 func (e SendNotifyPlatform) Webhook(body string, title string, content string) (bool, error) { var webhook model.Webhook err := json.Unmarshal([]byte(body), &webhook) webhookBodyString := string(webhook.WebhookBody) if err != nil { log.Errorln("无法解析配置文件") return false, errors.New("无法解析配置文件") } if !strings.Contains(webhook.WebhookUrl, "$title") && !strings.Contains(webhookBodyString, "$title") { return false, errors.New("URL 或者 Body 中必须包含 $title") } headers := make(map[string]string) if len(webhook.WebhookHeaders) > 2 { // 按换行符分割字符串 headerLines := strings.Split(webhook.WebhookHeaders, "\n") // 遍历每一行 for _, line := range headerLines { // 忽略空行 if line == "" { continue } // 按冒号分割键值对 parts := strings.SplitN(line, ":", 2) if len(parts) != 2 { return false, fmt.Errorf("malformed header: %s", line) } // 去除键和值两端的空白字符 key := strings.TrimSpace(parts[0]) value := strings.TrimSpace(parts[1]) // 将键值对添加到 map 中 headers[key] = value } } targetBody := strings.ReplaceAll(strings.ReplaceAll(webhookBodyString, "$title", title), "$content", content) rbodys := make(map[string]string) if len(webhook.WebhookHeaders) > 2 { // 按换行符分割字符串 headerLines := strings.Split(targetBody, "\n") // 遍历每一行 for _, line := range headerLines { // 忽略空行 if line == "" { continue } // 按冒号分割键值对 parts := strings.SplitN(line, ":", 2) if len(parts) != 2 { return false, fmt.Errorf("malformed header: %s", line) } // 去除键和值两端的空白字符 key := strings.TrimSpace(parts[0]) value := strings.TrimSpace(parts[1]) // 将键值对添加到 map 中 rbodys[key] = value } } var fbody *bytes.Buffer switch webhook.WebhookContentType { case "application/json": fbody, err = formatJSON(rbodys) case "multipart/form-data": fbody, err = formatMultipart(rbodys) case "application/x-www-form-urlencoded", "text/plain": fbody, err = formatURLForm(rbodys) default: fmt.Println("Unsupported content type") return false, nil } // if err != nil { // log.Errorln("WebhookBody解析失败") // return false, errors.New("WebhookBody解析失败") // } formatURL := strings.ReplaceAll(strings.ReplaceAll(webhook.WebhookUrl, "$title", url.QueryEscape(title)), "$content", url.QueryEscape(content)) client := &http.Client{} req, err := http.NewRequest(webhook.WebhookMethod, formatURL, fbody) if err != nil { log.Errorln("Webhook创建请求失败") return false, err } for key, value := range headers { req.Header.Set(key, value) } resp, err := client.Do(req) if err != nil { return false, err } defer resp.Body.Close() if err != nil { log.Error("通知发送失败") return false, err } defer resp.Body.Close() if resp.StatusCode == http.StatusOK { return true, nil } else { return false, fmt.Errorf("Unexpected status code: %d", resp.StatusCode) } } var platform model.SettingItem func Notify(title string, content string) { platform, err := GetSettingItemByKey(conf.NotifyPlatform) enable, err := GetSettingItemByKey(conf.NotifyEnabled) notifyBody, err := GetSettingItemByKey(conf.NotifyValue) if err != nil { log.Error("无法找到配置信息") } if enable.Value != "true" && enable.Value != "1" { log.Debug("未开启消息推送功能") return } if !conf.Conf.Notify { log.Debug("配置文件禁用通知") return } caser := cases.Title(language.English) methodName := caser.String(platform.Value) //注意映射方法名必需大写要不然找不到 // 使用反射获取结构体实例的值 v := reflect.ValueOf(SendNotifyPlatform{}) // 检查是否成功获取结构体实例的值 if v.IsValid() { log.Debug("成功获取结构体实例的值") } else { log.Debug("未能获取结构体实例的值") return } method := v.MethodByName(methodName) // 检查方法是否存在 if !method.IsValid() { log.Debug("Method %s not found\n", methodName) return } args := []reflect.Value{reflect.ValueOf(notifyBody.Value), reflect.ValueOf(title), reflect.ValueOf(content)} // 调用方法 method.Call(args) } // formatJSON 格式化为 JSON func formatJSON(bodys map[string]string) (*bytes.Buffer, error) { jsonData, err := json.Marshal(bodys) if err != nil { return nil, err } return bytes.NewBuffer(jsonData), nil } // formatMultipart 格式化为 multipart/form-data func formatMultipart(bodys map[string]string) (*bytes.Buffer, error) { var b bytes.Buffer writer := multipart.NewWriter(&b) for key, value := range bodys { err := writer.WriteField(key, value) if err != nil { return nil, err } } err := writer.Close() if err != nil { return nil, err } return &b, nil } // formatURLForm 格式化为 application/x-www-form-urlencoded func formatURLForm(bodys map[string]string) (*bytes.Buffer, error) { values := url.Values{} for key, value := range bodys { values.Add(key, value) } formData := values.Encode() return bytes.NewBufferString(formData), nil }