feat(ncm): 复古UI重做 (agent-211, 按 NCM_UI_DESIGN.md) — 白红/黑红双套配色(ncm_palette+主题监听实时换肤); 主页红topbar+个人区+双页签(我创建的/我收藏的, is_collected/subscribed已补); 搜索页(cloudsearch,仅确认时发请求); 歌单详情封面头块+播放全部; 播放页黑胶唱盘(行扫圆+封面裁圆+唱针+分带刷新); 播放页tick分带刷新修整页重绘隐患

ncm→90a25cf0; 0 warning; 宿主14张双主题截图目检+core测试全过
This commit is contained in:
MeowOS committed 2026-08-31 10:26:44 +08:00
1 parent 4cec476dd9
commit 8964cd1750
33 files changed
+1333 -320

No files matched your search

+63
View File
@@ -92,6 +92,14 @@ static long obj_int(const ncm_json_t *j, int tok, const char *key) {
return ncm_json_get_int_at(j, vt, &rc);
}
/* 布尔键取值(jsmn 无原生 bool;比较 "true"/"false" 字面量)。 */
static int obj_bool(const ncm_json_t *j, int tok, const char *key) {
int vt = ncm_json_obj_get(j, tok, key);
char s[8];
if (!ncm_json_get_str_at(j, vt, s, sizeof s)) return 0;
return (strcmp(s, "true") == 0);
}
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) {
if (!out_n) return NCM_ERR_ARG;
@@ -118,6 +126,8 @@ int ncm_api_user_playlist(const char *device_id, const char *cookie, const char
obj_str(&c.j, e, "name", out[i].name, sizeof out[i].name);
obj_str(&c.j, e, "coverImgUrl", out[i].cover, sizeof out[i].cover);
out[i].track_count = (int)obj_int(&c.j, e, "trackCount");
/* subscribed=true = 收藏的(订阅他人);false = 自己创建(§7.0 分组数据依赖) */
out[i].is_collected = obj_bool(&c.j, e, "subscribed") ? 1 : 0;
}
*out_n = n;
api_free(&c);
@@ -269,3 +279,56 @@ int ncm_api_lyric(const char *device_id, const char *cookie, const char *id,
api_free(&c);
return NCM_OK;
}
int ncm_api_cloudsearch(const char *device_id, const char *cookie, const char *keyword,
ncm_song_t *out, int max, int *out_n) {
if (!out_n) return NCM_ERR_ARG;
*out_n = 0;
if (!keyword || !keyword[0] || !out || max <= 0) return NCM_ERR_ARG;
/* /api/cloudsearch/pc:s=关键词 type=1(单曲) offset/limit 翻页。v1 只取前 max 条。
* 关键词转义(\" 与 \\),防止 IME 输入引号/反斜杠拼出坏 JSON。 */
char esc[256], json[512];
size_t ei = 0;
for (size_t si = 0; keyword[si] && ei < sizeof esc - 3; si++) {
char ch = keyword[si];
if (ch == '"' || ch == '\\') esc[ei++] = '\\';
esc[ei++] = ch;
}
esc[ei] = 0;
snprintf(json, sizeof json, "{\"s\":\"%s\",\"type\":1,\"offset\":0,\"limit\":%d,\"total\":true}",
esc, max);
api_json_t c; memset(&c, 0, sizeof c);
int rc = api_post(device_id, cookie, "/api/cloudsearch/pc", json, NCM_CRYPT_WEAPI,
NCM_RESP_MAX, &c);
if (rc != NCM_OK) return rc;
int arr = ncm_json_path(&c.j, "result.songs");
if (arr < 0 || ncm_json_type(&c.j, arr) != JSMN_ARRAY) { api_free(&c); return NCM_ERR_NOTFOUND; }
int cnt = ncm_json_arr_len(&c.j, arr);
if (cnt > max) cnt = max;
memset(out, 0, sizeof(*out) * (size_t)(cnt > 0 ? cnt : 0));
for (int i = 0; i < cnt; i++) {
int e = ncm_json_arr_get(&c.j, arr, i);
if (e < 0) break;
obj_str(&c.j, e, "name", out[i].name, sizeof out[i].name);
out[i].duration_ms = obj_int(&c.j, e, "dt");
out[i].fee = (int)obj_int(&c.j, e, "fee");
/* id 用 i64:网易云歌曲 id 可 >2^31 */
int vt = ncm_json_obj_get(&c.j, e, "id");
int rc2;
int64_t idv = ncm_json_get_i64_at(&c.j, vt, &rc2);
snprintf(out[i].id, sizeof out[i].id, "%lld", (long long)idv);
int ar = ncm_json_obj_get(&c.j, e, "ar");
if (ar >= 0 && ncm_json_type(&c.j, ar) == JSMN_ARRAY) {
int a0 = ncm_json_arr_get(&c.j, ar, 0);
if (a0 >= 0) obj_str(&c.j, a0, "name", out[i].art_name, sizeof out[i].art_name);
}
int al = ncm_json_obj_get(&c.j, e, "al");
if (al >= 0) obj_str(&c.j, al, "name", out[i].album_name, sizeof out[i].album_name);
}
*out_n = cnt;
api_free(&c);
return NCM_OK;
}
+6
View File
@@ -30,6 +30,7 @@ typedef struct ncm_playlist {
char name[256];
char cover[512];
int track_count;
int is_collected; /* 1=我收藏的(订阅他人);0=我创建的。来自响应 subscribed(§7.0) */
} ncm_playlist_t;
typedef struct ncm_song {
@@ -77,6 +78,11 @@ int ncm_api_song_url_auto(const char *device_id, const char *cookie, const char
int ncm_api_lyric(const char *device_id, const char *cookie, const char *id,
char *lrc, size_t lrc_sz, char *tlrc, size_t tlrc_sz);
/* 搜索(weapi /api/cloudsearch/pc):keyword 搜索歌曲(type=1)。out 填 ncm_song_t。
* 仅返回前端可解析字段(name/ar[0]/al/dt/id)。风控:只在用户确认时调用。 */
int ncm_api_cloudsearch(const char *device_id, const char *cookie, const char *keyword,
ncm_song_t *out, int max, int *out_n);
#ifdef __cplusplus
}
#endif