From 94ac6fa6ce50b1acd88f73c64fdf8899ffd4670b Mon Sep 17 00:00:00 2001 From: ShukeBta <272197458+ShukeBta@users.noreply.github.com> Date: Sat, 27 Jun 2026 10:15:17 +0800 Subject: [PATCH] split mteam adapter response parser --- internal/service/site_adapter_mteam.go | 126 ----------------- internal/service/site_adapter_mteam_parse.go | 135 +++++++++++++++++++ 2 files changed, 135 insertions(+), 126 deletions(-) create mode 100644 internal/service/site_adapter_mteam_parse.go diff --git a/internal/service/site_adapter_mteam.go b/internal/service/site_adapter_mteam.go index d228d02..4b3eb60 100644 --- a/internal/service/site_adapter_mteam.go +++ b/internal/service/site_adapter_mteam.go @@ -232,129 +232,3 @@ func (a *MTeamAdapter) GetDownloadURL(ctx context.Context, cfg SiteConfig, id st } return dl, nil } - -// parseMTeamJSON 解析 MTeam v3 JSON 响应。 -// -// 响应结构(与旧版参考实现一致): -// -// { -// "code": "0", // 字符串 "0" 表示成功 -// "message": "SUCCESS", -// "data": { -// "total": "123", -// "data": [ ... ] // 旧字段名 "lists" 已被替换为 "data" -// } -// } -func parseMTeamJSON(data []byte, siteName, baseURL string) (*SiteSearchResult, error) { - // 用 map 反序列化以兼容 code/total 既可能是字符串又可能是数字。 - var raw map[string]interface{} - if err := json.Unmarshal(data, &raw); err != nil { - return nil, fmt.Errorf("parse JSON: %w", err) - } - - // code 兼容字符串与数字。 - codeStr := "" - switch v := raw["code"].(type) { - case string: - codeStr = v - case float64: - codeStr = strconv.Itoa(int(v)) - } - if codeStr != "" && codeStr != "0" && codeStr != "200" { - msg, _ := raw["message"].(string) - if msg == "" { - msg = fmt.Sprintf("code=%s", codeStr) - } - return nil, fmt.Errorf("mteam: %s", msg) - } - - dataField, _ := raw["data"].(map[string]interface{}) - if dataField == nil { - return &SiteSearchResult{SiteName: siteName, Items: []TorrentItem{}}, nil - } - - // total 兼容字符串与数字。 - total := 0 - switch v := dataField["total"].(type) { - case string: - total, _ = strconv.Atoi(v) - case float64: - total = int(v) - } - - // data.data(v3)优先;兜底兼容旧的 data.lists。 - var rows []interface{} - switch v := dataField["data"].(type) { - case []interface{}: - rows = v - } - if rows == nil { - if v, ok := dataField["lists"].([]interface{}); ok { - rows = v - } - } - - result := &SiteSearchResult{ - SiteName: siteName, - Items: []TorrentItem{}, - Total: total, - } - - for _, rawT := range rows { - t, ok := rawT.(map[string]interface{}) - if !ok { - continue - } - item := TorrentItem{} - if v, ok := t["id"].(string); ok { - item.ID = v - } else if v, ok := t["id"].(float64); ok { - item.ID = strconv.Itoa(int(v)) - } - if v, ok := t["name"].(string); ok { - item.Title = v - } - if v, ok := t["subtitle"].(string); ok { - item.Subtitle = v - } - if v, ok := t["category"].(map[string]interface{}); ok { - if name, ok := v["name"].(string); ok { - item.Category = name - } - } - if v, ok := t["size"].(float64); ok { - item.Size = int64(v) - } else if v, ok := t["size"].(string); ok { - // v3 API 把 size 序列化成字符串。 - if n, err := strconv.ParseInt(v, 10, 64); err == nil { - item.Size = n - } - } - if v, ok := t["status"].(map[string]interface{}); ok { - if seeders, ok := v["seeders"].(float64); ok { - item.Seeders = int(seeders) - } - if leechers, ok := v["leechers"].(float64); ok { - item.Leechers = int(leechers) - } - if snatched, ok := v["completed"].(float64); ok { - item.Snatched = int(snatched) - } - } - if v, ok := t["free"].(bool); ok { - item.Free = v - } - if v, ok := t["uploadTime"].(float64); ok { - item.UploadTime = time.Unix(int64(v), 0) - } - - item.DetailURL = baseURL + "/detail/" + item.ID - // 标记 download_url 指向 genDlToken;真正的下载链接由 handler 层 - // 在用户点"下载"时通过 MTeamAdapter.GetDownloadURL 解析。 - // 这样前端 SiteSearchPage 才知道这一行有可用的下载入口。 - item.DownloadURL = baseURL + "/api/torrent/genDlToken?id=" + item.ID - result.Items = append(result.Items, item) - } - - return result, nil -} diff --git a/internal/service/site_adapter_mteam_parse.go b/internal/service/site_adapter_mteam_parse.go new file mode 100644 index 0000000..c8917ea --- /dev/null +++ b/internal/service/site_adapter_mteam_parse.go @@ -0,0 +1,135 @@ +// Package service — M-Team search response parsing. +package service + +import ( + "encoding/json" + "fmt" + "strconv" + "time" +) + +// parseMTeamJSON 解析 MTeam v3 JSON 响应。 +// +// 响应结构(与旧版参考实现一致): +// +// { +// "code": "0", // 字符串 "0" 表示成功 +// "message": "SUCCESS", +// "data": { +// "total": "123", +// "data": [ ... ] // 旧字段名 "lists" 已被替换为 "data" +// } +// } +func parseMTeamJSON(data []byte, siteName, baseURL string) (*SiteSearchResult, error) { + // 用 map 反序列化以兼容 code/total 既可能是字符串又可能是数字。 + var raw map[string]interface{} + if err := json.Unmarshal(data, &raw); err != nil { + return nil, fmt.Errorf("parse JSON: %w", err) + } + + // code 兼容字符串与数字。 + codeStr := "" + switch v := raw["code"].(type) { + case string: + codeStr = v + case float64: + codeStr = strconv.Itoa(int(v)) + } + if codeStr != "" && codeStr != "0" && codeStr != "200" { + msg, _ := raw["message"].(string) + if msg == "" { + msg = fmt.Sprintf("code=%s", codeStr) + } + return nil, fmt.Errorf("mteam: %s", msg) + } + + dataField, _ := raw["data"].(map[string]interface{}) + if dataField == nil { + return &SiteSearchResult{SiteName: siteName, Items: []TorrentItem{}}, nil + } + + // total 兼容字符串与数字。 + total := 0 + switch v := dataField["total"].(type) { + case string: + total, _ = strconv.Atoi(v) + case float64: + total = int(v) + } + + // data.data(v3)优先;兜底兼容旧的 data.lists。 + var rows []interface{} + switch v := dataField["data"].(type) { + case []interface{}: + rows = v + } + if rows == nil { + if v, ok := dataField["lists"].([]interface{}); ok { + rows = v + } + } + + result := &SiteSearchResult{ + SiteName: siteName, + Items: []TorrentItem{}, + Total: total, + } + + for _, rawT := range rows { + t, ok := rawT.(map[string]interface{}) + if !ok { + continue + } + item := TorrentItem{} + if v, ok := t["id"].(string); ok { + item.ID = v + } else if v, ok := t["id"].(float64); ok { + item.ID = strconv.Itoa(int(v)) + } + if v, ok := t["name"].(string); ok { + item.Title = v + } + if v, ok := t["subtitle"].(string); ok { + item.Subtitle = v + } + if v, ok := t["category"].(map[string]interface{}); ok { + if name, ok := v["name"].(string); ok { + item.Category = name + } + } + if v, ok := t["size"].(float64); ok { + item.Size = int64(v) + } else if v, ok := t["size"].(string); ok { + // v3 API 把 size 序列化成字符串。 + if n, err := strconv.ParseInt(v, 10, 64); err == nil { + item.Size = n + } + } + if v, ok := t["status"].(map[string]interface{}); ok { + if seeders, ok := v["seeders"].(float64); ok { + item.Seeders = int(seeders) + } + if leechers, ok := v["leechers"].(float64); ok { + item.Leechers = int(leechers) + } + if snatched, ok := v["completed"].(float64); ok { + item.Snatched = int(snatched) + } + } + if v, ok := t["free"].(bool); ok { + item.Free = v + } + if v, ok := t["uploadTime"].(float64); ok { + item.UploadTime = time.Unix(int64(v), 0) + } + + item.DetailURL = baseURL + "/detail/" + item.ID + // 标记 download_url 指向 genDlToken;真正的下载链接由 handler 层 + // 在用户点"下载"时通过 MTeamAdapter.GetDownloadURL 解析。 + // 这样前端 SiteSearchPage 才知道这一行有可用的下载入口。 + item.DownloadURL = baseURL + "/api/torrent/genDlToken?id=" + item.ID + result.Items = append(result.Items, item) + } + + return result, nil +}