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:
1 parent
a651df0e9b
commit
4cec476dd9
16 files changed
+344
-36
No files matched your search
@@ -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);
|
||||
|
||||
@@ -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)。 */
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,失败则回退时钟扰动 */
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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。 */
|
||||
|
||||
@@ -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];
|
||||
|
||||
Reference in new issue
Block a user