mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-06 13:26:38 +08:00
split mteam adapter response parser
This commit is contained in:
@@ -232,129 +232,3 @@ func (a *MTeamAdapter) GetDownloadURL(ctx context.Context, cfg SiteConfig, id st
|
|||||||
}
|
}
|
||||||
return dl, nil
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user