package fqapi import ( "fanqie/internal/common" "fmt" "net/url" ) var rankLogger = common.NewLogger("rank", "INFO", "") // rankSDKHosts 免签名榜单接口的可用域名(依次容错) var rankSDKHosts = []string{ "https://novel.snssdk.com", "https://api.fanqiesdk.com", "https://api-lf.fanqiesdk.com", } const rankSDKUA = "com.ss.android.article.news/7.0.3 (Linux; U; Android 12; zh_CN; Pixel 6)" // RankTypes 榜单类型 var RankTypes = map[int]string{ 10: "推荐榜", 11: "完结榜", 12: "热搜榜", 13: "黑马榜", 15: "爆更榜", } // HandleGetRank 处理获取榜单,榜单数据动态,短缓存 10 分钟 // sideType 见 RankTypes(无效值回退推荐榜);page 从 1 起,每页 20 本 func HandleGetRank(sideType, page int) (string, error) { if _, ok := RankTypes[sideType]; !ok { sideType = 10 } if page < 1 { page = 1 } return withCache(fmt.Sprintf("rank_%d_%d", sideType, page), 600, func() (string, error) { return fetchRank(sideType, page) }) } // fetchRank 免签名 SDK 接口获取榜单(无设备/签名依赖,多域名容错) func fetchRank(sideType, page int) (string, error) { vals := url.Values{ "aid": {"13"}, "type": {"1"}, "side_type": {fmt.Sprintf("%d", sideType)}, "offset": {fmt.Sprintf("%d", page-1)}, "limit": {"20"}, } path := "/api/novel/channel/homepage/rank/rank_list/v2/?" + vals.Encode() var lastErr error client := common.NewHTTPClient() for _, host := range rankSDKHosts { resp, err := client.Get(host+path, map[string]string{ "User-Agent": rankSDKUA, "Accept": "application/json, text/plain, */*", }) if err != nil { lastErr = err continue } respJSON, err := resp.JSON() if err != nil { lastErr = fmt.Errorf("响应解析失败: %w", err) continue } if code, ok := respJSON["code"].(float64); ok && code == 0 { return reencodeUnwrapped(resp.Text), nil } lastErr = fmt.Errorf("code=%.0f msg=%v", respJSON["code"], respJSON["message"]) } rankLogger.Error(fmt.Sprintf("获取榜单失败: %v", lastErr)) return BuildErrorResponse("获取榜单失败"), lastErr }