ncm search: 历史+单曲/歌单分类+分页+误触修复+不覆写歌单上下文 (§23)

- 搜索历史:进页自动弹键盘;meow_config 持久化 ≤12 去重最新在前;空态=历史列表(点搜/左滑删/清空)
- 结果分类单曲/歌单页签:独立结果集+独立滚动;歌单行进详情(playlist_open_pl);单曲行播放
- 触底分页:offset 累加 limit=30/页 cloudsearch 同参数;每类各记 offset
- 误触抑制:g_press_row + g_press_delta>10(对齐 home/playlist §13.3)
- 结果改独立数组(不写 g_st.songs[]);播放搜索单曲建搜索队列(queue_src=SEARCH),不弄丢歌单窗口
- core cloudsearch 扩展 type/offset/limit/total + 歌单解析
- bench/search_sim.c 离线回归(历史/独立集/翻页 offset/队列上下文);host searchtap 误触自检

验证:mipsel -Wall -Wextra 0 warning;core host/mipsel PASS;search_sim PASS;
宿主离屏 search(单曲)/searchpl(歌单)/searchhist(空态历史)/searchtap(误触) 均正常;0 真实 NCM 请求。
This commit is contained in:
MeowOS committed 2026-09-01 19:21:16 +08:00
1 parent d4928224a5
commit 1e97dbf54a
15 files changed
+1152 -121

No files matched your search

+75 -13
View File
@@ -456,14 +456,11 @@ int ncm_api_lyric(const char *device_id, const char *cookie, const char *id,
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。 */
/* /api/cloudsearch/pc 公共 POST(type=单曲/歌单/专辑 共用):s=关键词 type/offset/limit/total。
* 关键词转义(\" 与 \\),防止 IME 输入引号/反斜杠拼出坏 JSON。 */
static int cloudsearch_post(const char *device_id, const char *cookie, const char *keyword,
int type, int offset, int limit, api_json_t *c)
{
char esc[256], json[512];
size_t ei = 0;
for (size_t si = 0; keyword[si] && ei < sizeof esc - 3; si++) {
@@ -472,20 +469,85 @@ int ncm_api_cloudsearch(const char *device_id, const char *cookie, const char *k
esc[ei++] = ch;
}
esc[ei] = 0;
snprintf(json, sizeof json, "{\"s\":\"%s\",\"type\":1,\"offset\":0,\"limit\":%d,\"total\":true}",
esc, max);
snprintf(json, sizeof json, "{\"s\":\"%s\",\"type\":%d,\"offset\":%d,\"limit\":%d,\"total\":true}",
esc, type, offset > 0 ? offset : 0, limit > 0 ? limit : 30);
return api_post(device_id, cookie, "/api/cloudsearch/pc", json, NCM_CRYPT_WEAPI,
NCM_RESP_MAX, c);
}
/* 解析一处「歌单对象数组」(cloudsearch type=1000)。id 可为数字/字符串(均文本拷贝)。 */
static int parse_playlists_array(const ncm_json_t *j, int arr, ncm_playlist_t *out, int max, int *out_n)
{
*out_n = 0;
if (arr < 0 || ncm_json_type(j, arr) != JSMN_ARRAY) return NCM_ERR_NOTFOUND;
int cnt = ncm_json_arr_len(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(j, arr, i);
if (e < 0) break;
obj_str(j, e, "id", out[i].id, sizeof out[i].id);
obj_str(j, e, "name", out[i].name, sizeof out[i].name);
obj_str(j, e, "coverImgUrl", out[i].cover, sizeof out[i].cover);
out[i].track_count = (int)obj_int(j, e, "trackCount");
out[i].is_collected = 0;
}
*out_n = cnt;
return NCM_OK;
}
/* 搜索结果计数(cloudsearch result.songCount / result.playlistCount;缺省 → *out_total=-1 未知)。 */
static void cloudsearch_count(const ncm_json_t *j, const char *path, int64_t *out_total)
{
if (!out_total) return;
*out_total = -1;
int t = ncm_json_path(j, path);
int rc = NCM_ERR_NOTFOUND;
int64_t v = ncm_json_get_i64_at(j, t, &rc);
if (rc == NCM_OK) *out_total = v;
}
int ncm_api_cloudsearch_song(const char *device_id, const char *cookie, const char *keyword,
int offset, int limit, ncm_song_t *out, int max, int *out_n,
int64_t *out_total) {
if (!out_n) return NCM_ERR_ARG;
*out_n = 0;
if (out_total) *out_total = -1;
if (!keyword || !keyword[0] || !out || max <= 0) return NCM_ERR_ARG;
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);
int rc = cloudsearch_post(device_id, cookie, keyword, 1, offset, limit, &c);
if (rc != NCM_OK) return rc;
cloudsearch_count(&c.j, "result.songCount", out_total);
int arr = ncm_json_path(&c.j, "result.songs");
int rc2 = parse_songs_array(&c.j, arr, out, max, out_n);
api_free(&c);
return rc2;
}
int ncm_api_cloudsearch_playlist(const char *device_id, const char *cookie, const char *keyword,
int offset, int limit, ncm_playlist_t *out, int max, int *out_n,
int64_t *out_total) {
if (!out_n) return NCM_ERR_ARG;
*out_n = 0;
if (out_total) *out_total = -1;
if (!keyword || !keyword[0] || !out || max <= 0) return NCM_ERR_ARG;
api_json_t c; memset(&c, 0, sizeof c);
int rc = cloudsearch_post(device_id, cookie, keyword, 1000, offset, limit, &c);
if (rc != NCM_OK) return rc;
cloudsearch_count(&c.j, "result.playlistCount", out_total);
int arr = ncm_json_path(&c.j, "result.playlists");
int rc2 = parse_playlists_array(&c.j, arr, out, max, out_n);
api_free(&c);
return rc2;
}
int ncm_api_cloudsearch(const char *device_id, const char *cookie, const char *keyword,
ncm_song_t *out, int max, int *out_n) {
return ncm_api_cloudsearch_song(device_id, cookie, keyword, 0, max, out, max, out_n, NULL);
}
/* 从一段 track/all 响应 JSON 串解析歌曲数组 + total(离线样本/单元测试用,不打网络)。
* 数组取「songs → playlist.tracks → data.songs」首个命中;total 取「total → count →
* playlist.trackCount → musicCount」,全缺则 0。 */
+10 -2
View File
@@ -130,8 +130,16 @@ int ncm_api_parse_vip_info(const char *json, size_t len, int64_t now_ms, ncm_vip
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)。风控:只在用户确认时调用。 */
/* 搜索(weapi /api/cloudsearch/pc)。type 映射(BIZ_NOTES §5.1):1=单曲、1000=歌单、(10=专辑留后续)。
* offset/limit 分页(limit=30/页,REPORT §23);*out_total=服务端总数(-1=未知)。
* 风控:只在用户点「搜索」/翻页时调用。 */
int ncm_api_cloudsearch_song(const char *device_id, const char *cookie, const char *keyword,
int offset, int limit, ncm_song_t *out, int max, int *out_n,
int64_t *out_total);
int ncm_api_cloudsearch_playlist(const char *device_id, const char *cookie, const char *keyword,
int offset, int limit, ncm_playlist_t *out, int max, int *out_n,
int64_t *out_total);
/* 兼容旧签名:单曲(type=1, offset=0, limit=max)。 */
int ncm_api_cloudsearch(const char *device_id, const char *cookie, const char *keyword,
ncm_song_t *out, int max, int *out_n);