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:
1 parent
4cec476dd9
commit
8964cd1750
33 files changed
+1333
-320
No files matched your search
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user