fix(ncm): 启动卡顿+大歌单+No tracks 三连修 (agent-209) — ①home/playlist 加载改后台 worker ②大歌单 detail n=1(tracks瘦身)+响应cap 2MB ③song_detail c数组引号转义 ④P0: MIPS32 long=32位歌曲id溢出→int64 ⑤eapi批缓冲2048→4096

ncm→f6bd765a 已部署; 真机验证(风控合规): 小歌单✓ 大歌单256首✓ 启动不卡✓; 已知: 大歌单首屏~30s(二期改分页端点)
This commit is contained in:
MeowOS committed 2026-08-31 09:43:35 +08:00
1 parent a651df0e9b
commit 4cec476dd9
16 files changed
+344 -36

No files matched your search

+12 -6
View File
@@ -126,15 +126,18 @@ int ncm_api_user_playlist(const char *device_id, const char *cookie, const char
int ncm_api_playlist_detail(const char *device_id, const char *cookie, const char *playlist_id,
char *name, size_t name_sz, char *cover, size_t cover_sz,
long *ids, int id_cap, int *out_id_n) {
int64_t *ids, int id_cap, int *out_id_n) {
if (!out_id_n) return NCM_ERR_ARG;
*out_id_n = 0;
/* n=1:只让 playlist.tracks 带 1 首,避免大歌单把成百上千首完整歌曲对象塞进响应
* (n=100000 时 5191 曲歌单响应实测 3168819B → 破 1MB cap 报 -11 FULL → Load Failed)。
* trackIds 数组与 n 无关(n=1 实测 5191 条完整返回),足够取前 N 首。 */
char json[256];
snprintf(json, sizeof json, "{\"id\":\"%s\",\"n\":100000,\"s\":8}", playlist_id);
snprintf(json, sizeof json, "{\"id\":\"%s\",\"n\":1,\"s\":8}", playlist_id);
api_json_t c; memset(&c, 0, sizeof c);
int rc = api_post(device_id, cookie, "/api/v6/playlist/detail", json, NCM_CRYPT_EAPI,
NCM_RESP_MAX, &c);
2 * 1024 * 1024, &c);
if (rc != NCM_OK) return rc;
int pl = ncm_json_path(&c.j, "playlist");
@@ -153,20 +156,23 @@ int ncm_api_playlist_detail(const char *device_id, const char *cookie, const cha
int e = ncm_json_arr_get(&c.j, tid, i);
int vt = ncm_json_obj_get(&c.j, e, "id");
int rc2;
ids[i] = ncm_json_get_int_at(&c.j, vt, &rc2);
ids[i] = ncm_json_get_i64_at(&c.j, vt, &rc2); /* 64 位:id 可 >2^31 */
}
*out_id_n = take;
api_free(&c);
return NCM_OK;
}
int ncm_api_song_detail(const char *device_id, const char *cookie, const long *ids, int n,
int ncm_api_song_detail(const char *device_id, const char *cookie, const int64_t *ids, int n,
ncm_song_t *out, int max, int *out_n) {
if (!out_n) return NCM_ERR_ARG;
*out_n = 0;
/* 曲目 id 数组要作为 JSON 字符串嵌进 "c",必须把内部引号转义(\"),否则服务端收到
* {"c":"[{"id":..}]"}(坏 JSON)直接回 200 Content-Length:0 → 解析失败 → 空列表。
* 实测:转义后 {"c":"[{\"id\":2048962657}]"} 服务端返回 2146B 正常 songs。 */
char clist[4096] = "[", tmp[64];
for (int i = 0; i < n && strlen(clist) < sizeof clist - 80; i++) {
snprintf(tmp, sizeof tmp, "%s{\"id\":%ld}", i ? "," : "", ids[i]);
snprintf(tmp, sizeof tmp, "%s{\\\"id\\\":%lld}", i ? "," : "", (long long)ids[i]);
ncm_strlcat(clist, tmp, sizeof clist);
}
ncm_strlcat(clist, "]", sizeof clist);
+5 -4
View File
@@ -54,13 +54,14 @@ typedef struct ncm_play_url {
int ncm_api_user_playlist(const char *device_id, const char *cookie, const char *uid,
int limit, int offset, ncm_playlist_t *out, int max, int *out_n);
/* 歌单详情(eapi /v6/playlist/detail):取名字/封面 + trackIds 数组(填 ids, cap)。 */
/* 歌单详情(eapi /v6/playlist/detail):取名字/封面 + trackIds 数组(填 ids, cap)。
* ids 用 int64_t:网易云歌曲 id 可 >2^31(MIPS32 的 long 是 32 位,会溢出)。 */
int ncm_api_playlist_detail(const char *device_id, const char *cookie, const char *playlist_id,
char *name, size_t name_sz, char *cover, size_t cover_sz,
long *ids, int id_cap, int *out_id_n);
int64_t *ids, int id_cap, int *out_id_n);
/* 批量歌曲详情(eapi /v3/song/detail)。ids 传入曲目 id 数组。 */
int ncm_api_song_detail(const char *device_id, const char *cookie, const long *ids, int n,
/* 批量歌曲详情(eapi /v3/song/detail)。ids 传入曲目 id 数组(int64_t)。 */
int ncm_api_song_detail(const char *device_id, const char *cookie, const int64_t *ids, int n,
ncm_song_t *out, int max, int *out_n);
/* 播放地址(eapi /song/enhance/player/url/v1)。level 指定档位;url 可能为 null(无版权/VIP)。 */
+11
View File
@@ -42,6 +42,17 @@ int ncm_parse_int(const char *s, long *out) {
return 0;
}
/* 64 位解析(MIPS32 上 long=32 位;网易云歌曲 id 可达 ~18 亿以上,strtol 到 long 会饱和到
* LONG_MAX 而错)。用 strtoll 直读 int64_t,覆盖全部真实 id。 */
int ncm_parse_i64(const char *s, int64_t *out) {
if (!s || !*s) return -1;
char *end = NULL;
long long v = strtoll(s, &end, 10);
if (end == s || *end != '\0') return -1;
if (out) *out = (int64_t)v;
return 0;
}
/* 墙钟毫秒(CLOCK_REALTIME)。用于 request_id/_ntes_nnid/落盘时间戳等“取当前墙钟时间”的场合。
* 勿用于超时/截止计算:REALTIME 会随 NTP 校时跳变,导致 deadline-now 算错(详见 ncm_http.c 注释)。 */
int64_t ncm_now_ms(void) {
+1
View File
@@ -47,6 +47,7 @@ size_t ncm_strlcpy(char *dst, const char *src, size_t sz);
size_t ncm_strlcat(char *dst, const char *src, size_t sz);
int ncm_streq(const char *a, const char *b);
int ncm_parse_int(const char *s, long *out); /* 完整数字串,成功返回 0 */
int ncm_parse_i64(const char *s, int64_t *out); /* 完整数字串→64 位(网易云歌曲 id 可 >2^31,MIPS32 long=32 位会溢出) */
int64_t ncm_now_ms(void); /* 墙钟毫秒(CLOCK_REALTIME),供 request_id/落盘时间戳 */
int64_t ncm_monotonic_ms(void); /* 单调毫秒(CLOCK_MONOTONIC),仅用于超时/截止/间隔计时 */
void ncm_random_bytes(unsigned char *out, size_t n); /* /dev/urandom,失败则回退时钟扰动 */
+10
View File
@@ -125,6 +125,16 @@ long ncm_json_get_int_at(const ncm_json_t *j, int tok, int *rc) {
return v;
}
int64_t ncm_json_get_i64_at(const ncm_json_t *j, int tok, int *rc) {
if (rc) *rc = NCM_OK;
if (tok < 0 || tok >= j->ntoks) { if (rc) *rc = NCM_ERR_NOTFOUND; return 0; }
char s[32];
if (!ncm_json_get_str_at(j, tok, s, sizeof s)) { if (rc) *rc = NCM_ERR_NOTFOUND; return 0; }
int64_t v = 0;
if (ncm_parse_i64(s, &v) != 0) { if (rc) *rc = NCM_ERR_JSON; return 0; }
return v;
}
/* --- 路径导航 --- */
static int obj_get_path(const ncm_json_t *j, int cur, const char *seg_start, int klen) {
char key[256];
+3
View File
@@ -8,6 +8,7 @@
#define NCM_JSON_H
#include <stddef.h>
#include <stdint.h>
#include "jsmn.h"
#ifdef __cplusplus
@@ -42,6 +43,8 @@ int ncm_json_arr_len(const ncm_json_t *j, int tok);
const char *ncm_json_get_str_at(const ncm_json_t *j, int tok, char *out, size_t out_sz);
/* 取 token 处整数;primitiv/text 转 long,失败返回 0(用 rc 判错) */
long ncm_json_get_int_at(const ncm_json_t *j, int tok, int *rc);
/* 取 token 处 64 位整数(网易云歌曲 id 可 >2^31,MIPS32 long 装不下) */
int64_t ncm_json_get_i64_at(const ncm_json_t *j, int tok, int *rc);
/* 路径导航:支持 "data[0].url" / "playlist.trackIds" / "code" 等点+下标路径。
* 返回目标 token 索引,失败 -1。 */
+5 -2
View File
@@ -261,7 +261,7 @@ int ncm_req_build(const char *device_id, const char *cookie, const char *path,
hm_to_json(&h, header_json, sizeof header_json);
/* 注入 header 与 e_r 到加密 JSON */
char full2[3072];
char full2[4096];
{
size_t jl = strlen(json);
if (jl >= 2 && json[0]=='{' && json[jl-1]=='}') {
@@ -273,7 +273,10 @@ int ncm_req_build(const char *device_id, const char *cookie, const char *path,
}
}
char params[2048];
/* params 是 full2 的 AES-ECB 大写 hex(≈ plaintext×2)。
* 批量 song_detail 的 c 数组转义后 full2 ~1.6KB → hex ~3.2KB,旧 2048 溢出
* → ncm_crypto_eapi 返回 NCM_ERR_CRYPTO(实测 batch=64 时 rc=-8)。 */
char params[4096];
if (ncm_crypto_eapi(path, full2, params, sizeof params) != 0) return NCM_ERR_CRYPTO;
char cookie_hdr[2048];