From 22434ae5dd849c10415dcf8bc4ca08f0b43aed9d Mon Sep 17 00:00:00 2001 From: lixiangwuxian Date: Sun, 13 Oct 2024 14:50:53 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=B8=8A=E7=BA=BF=E9=93=BE=E6=8E=A5?= =?UTF-8?q?=E5=A4=84=E7=90=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- handler/urlparser/url.go | 120 +++++++++++++++++++++++++++++++++++++++ register.go | 1 + 2 files changed, 121 insertions(+) create mode 100644 handler/urlparser/url.go diff --git a/handler/urlparser/url.go b/handler/urlparser/url.go new file mode 100644 index 0000000..7464931 --- /dev/null +++ b/handler/urlparser/url.go @@ -0,0 +1,120 @@ +package urlparser + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "regexp" + "strings" + + "git.lxtend.com/qqbot/handler" + "git.lxtend.com/qqbot/model" +) + +func init() { + handler.RegisterFrontMatchHandler("[CQ:json,data=", urlParser) +} + +func urlParser(msg model.Message) (reply model.Reply) { + qqdocurl, err := extractQQDocURL(msg.RawMsg) + if err != nil { + return model.Reply{ + ReplyMsg: fmt.Sprintf("解析失败: %v", err), + ReferOriginMsg: true, + FromMsg: msg, + } + } + + return model.Reply{ + ReplyMsg: qqdocurl, + ReferOriginMsg: true, + FromMsg: msg, + } +} + +func extractQQDocURL(input string) (string, error) { + // 使用正则表达式提取 JSON 数据部分 + re := regexp.MustCompile(`\{.*\}`) + jsonPart := re.FindString(input) + if jsonPart == "" { + return "", fmt.Errorf("无法找到 JSON 数据部分") + } + + // 解析 JSON 数据 + var jsonData map[string]interface{} + // 替换 HTML 实体为普通字符 + jsonPart = strings.ReplaceAll(jsonPart, ",", ",") + jsonPart = strings.ReplaceAll(jsonPart, "[", "[") + jsonPart = strings.ReplaceAll(jsonPart, "]", "]") + jsonPart = strings.ReplaceAll(jsonPart, "&", "&") + + if err := json.Unmarshal([]byte(jsonPart), &jsonData); err != nil { + return "", fmt.Errorf("解析 JSON 失败: %w", err) + } + + // 定位到 meta -> detail_1 -> qqdocurl + meta, ok := jsonData["meta"].(map[string]interface{}) + if !ok { + return "", fmt.Errorf("找不到 meta 字段") + } + detail, ok := meta["detail_1"].(map[string]interface{}) + if !ok { + return "", fmt.Errorf("找不到 detail_1 字段") + } + qqdocurl, ok := detail["qqdocurl"].(string) + if !ok { + return "", fmt.Errorf("找不到 qqdocurl 字段") + } + + qqdocurl, _ = removeTrackingParams(qqdocurl) + qqdocurl, _ = resolveFinalURL(qqdocurl) + qqdocurl, _ = removeTrackingParams(qqdocurl) + + return qqdocurl, nil +} + +func removeTrackingParams(rawURL string) (string, error) { + parsedURL, err := url.Parse(rawURL) + if err != nil { + return "", err + } + // 仅保留 URL 的 Scheme 和 Host + Path 部分 + return fmt.Sprintf("%s://%s%s", parsedURL.Scheme, parsedURL.Host, parsedURL.Path), nil +} + +func resolveFinalURL(initialURL string) (string, error) { + // 解析 URL 确保其格式正确 + parsedURL, err := url.Parse(initialURL) + if err != nil { + return "", fmt.Errorf("URL 解析失败: %w", err) + } + + // 创建一个 HTTP 客户端 + client := &http.Client{ + // 禁用自动重定向,以便手动处理 302 + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } + + // 发起 GET 请求 + resp, err := client.Get(parsedURL.String()) + if err != nil { + return "", fmt.Errorf("请求失败: %w", err) + } + defer resp.Body.Close() + + // 如果是 302 重定向,则递归访问新链接 + if resp.StatusCode == http.StatusFound || resp.StatusCode == http.StatusMovedPermanently { + redirectURL, err := resp.Location() + if err != nil { + return "", fmt.Errorf("解析重定向地址失败: %w", err) + } + fmt.Printf("重定向至: %s\n", redirectURL.String()) + return resolveFinalURL(redirectURL.String()) + } + + // 返回最终的非 302 链接 + return initialURL, nil +} diff --git a/register.go b/register.go index 2aa8d10..afaca47 100644 --- a/register.go +++ b/register.go @@ -6,5 +6,6 @@ import ( _ "git.lxtend.com/qqbot/handler/headmaster" _ "git.lxtend.com/qqbot/handler/jrrp" _ "git.lxtend.com/qqbot/handler/scoresaber" + _ "git.lxtend.com/qqbot/handler/urlparser" _ "git.lxtend.com/qqbot/handler/xibao" )