Skip to content

feat(api): 同步上游 api-enhanced 至 2026-10 并修复 #1 #2 #3 - #4

Open
jinzhongjia wants to merge 6 commits into
SPlayer-Dev:mainfrom
jinzhongjia:feat/sync-upstream-2026-10
Open

jinzhongjia wants to merge 6 commits into
SPlayer-Dev:mainfrom
jinzhongjia:feat/sync-upstream-2026-10

Conversation

@jinzhongjia

@jinzhongjia jinzhongjia commented Oct 6, 2026 •

Copy link
Copy Markdown

概要

同步 api-enhanced 自 2026-03-28(4.31.0)至 2026-10-05 的改动,并修复本仓库 3 个 open issue。

Fixes #1
Fixes #2
Fixes #3

加密 / 请求层

  • xeapi:X25519 + AES-GCM 会话密钥,首次使用时自动拉取公钥并缓存,复用服务端下发的会话。song_url_v1、评论、红心、签到等接口按上游切换
  • neapi:ChaCha20-Poly1305 + zstd,含密钥配置解析(混淆 Lua 字节码)、过期后台刷新、777/999 刷新重试。服务端下发的 zstd 帧缺少结束块(上游 Node 版目前因此解析失败),这里做了兼容
  • checkToken:新增易盾 v3 实时 token;v2 上游依赖 jsdom 运行易盾 SDK,暂以静态 token 兜底
  • eapi 默认域名切换为 interfacepc.music.163.com;NMTID 改为从服务端下发采集;新增 osx 系统配置,更新 UA
  • 请求选项补齐 e_r(weapi 返回解密)、timeout、headers、crypto 覆盖、ENABLE_RANDOM_CN_IP
  • 修复 HTTP 服务未解析 randomCNIP / ua / domain / e_r 参数的问题
  • HTTP 服务透传客户端 IP(与 Node 一致):优先 realIP,其次 X-Forwarded-For / 连接地址,本机请求使用进程级随机中国 IP。此前本机请求不带 IP,网易看到的是服务器出口 IP,手机号登录会被风控(-462)

接口

  • 新增 66 个接口:日推风格、乐迷团、云小编、助眠解压、设备管理、scrobble_v1(NCBL 加密上报)、decrypt、user_event_all 等(见 README「上游同步新增」)
  • 同步已有接口改动:song_url_v1 / song_download_url_v1 / song_url_v1_302 支持 vivid,scrobble、vip_sign、comment 系列、share_resource、yunbei_sign 等
  • voice_upload 支持 imgFile 封面上传
  • 新增上游驼峰路由别名:/digitalAlbum/*、/djRadio/top、/verify/getQr、/rep/ugc/user/collect-vip、/personal/fm/mode

Issue 修复

  • Nodejs 版 api 中可用的 /inner/version 端点在 rs 版上不可用 #1:/inner/version、/eapi/decrypt 签名特殊被 build.rs 排除但未手动注册,现已注册
  • playlist_tracks 走 eapi 通道请求发送失败,Node 参考实现该端点为 weapi #2:playlist_tracks 改走 weapi,并对齐 Node 版 512 重试逻辑。根因:eapi / xeapi / neapi 使用的 interface*.music.163.com 5 秒即关闭空闲连接,而 reqwest 连接池默认保留 90 秒,复用已关闭连接导致偶发 error sending request(weapi 的 music.163.com 为 65 秒,所以改 weapi 能绕过)。且复用中的连接还会被服务端不定期重置(Connection reset by peer)。现已关闭连接复用,与 Node 版每次请求新建 Agent 的行为一致;混合间隔(0~4.8 秒)压测 80 次 0 失败。代价是每次请求需新建 TLS 连接,实测单请求中位耗时约 1.5s(复用时约 0.65s),与 Node 版相同
  • /register/anonimous接口与nodejs返回结果不一致 #3:register_anonimous 成功时在 body 中返回拼接后的 cookie(与 Node 一致);改用客户端当前设备 ID 后注册成功(此前对两边均返回 400)。HTTP 服务启动时自动注册游客身份,未登录请求携带 MUSIC_A(与 Node 一致)

不兼容变更

  • comment_reply 参数按上游改为 id / type / cid(原 threadId / commentId)
  • RequestOption.check_token 由 bool 改为 CheckToken 枚举;RequestOption / Query 新增 headers、timeout 字段
  • voice_upload 新增 cover_img 参数
  • 上游返回非 200 时,request() 改为返回新增的 NcmError::Response(携带原始 status / body / cookie),不再返回 AuthRequired / Api 等变体;可用 status_code() / is_auth_required() / response() 判断。HTTP 服务出错时原样返回上游 body(与 Node 版一致)
  • login_qr_key 返回结构改为 { data: { unikey }, code }(与 Node 版一致)

测试

cargo fmt --check、cargo clippy --features server --all-targets(0 警告)通过;cargo test 单元测试通过。注意:tests/integration.rs 中 4 个联网测试目前失败,原因是测试机当天请求量过大,出口 IP 被网易风控(未登录请求返回 -462,切回改动前的提交同样失败,Node 版直连也被拦),与代码改动无关,风控解除后需复跑确认。

实测方式(对照请求带时间戳,规避 Node 版按 URL 缓存 2 分钟的 apicache):同时运行本实现与上游 Node 版,用真实登录账号(黑胶 VIP)逐个调用对照;写操作均为「执行 → 验证效果 → 撤销」。

✅ 已实测通过

  • 加密通道:xeapi、neapi(crypto=neapi)、weapi / eapi 的 e_r=true 返回解密
  • 音质:song_url_v1 vivid(臻音全景声 av3a)/ jymaster(超清母带 flac)、song_url_v1/302、song_download_url_v1 vivid
  • 只读(登录)与 Node 结果一致:artist_new_song_mv_list_v2、artist_new_song_playall、fans_group_user_groups / detail / user_group_detail / feed_recommend、recommend_songs(含 afresh)、recommend_songs_v1、recommend_category_configs / songs、device_list、listen_data_song_play_rank、sati_resource_sub_list、user_event_all、vip_sign_history / detail、vip_tasks_v1、yunbei_task_list_v1 / recommend_song、ad_get、ad_listening_rights、rep_ugc_activity_get、rep_ugc_user_get / vip、rep_ugc_exam_info_get、middle_play_lottery_remain_chance、thinktank_audit_resource_detail
  • 只读(免登录)与 Node 一致:chart_detail、chart_song_detail、sati_resource_list / list_more / timescene_resources_get、sati_tag_list、song_copyright_rcmd、song_wiki_info、song_creators、song_simi_get、lbs_city_code、radio_sport_get、user_event、register_xeapikey、register_neapikey、register_checktoken_v3、decrypt(全部 5 种模式)
  • 写操作(已验证效果并撤销):like_v1 红心 / 取消;playlist_tracks 加歌 / 删歌(两轮,playlist_tracks 走 eapi 通道请求发送失败,Node 参考实现该端点为 weapi #2);playlist_subscribe 收藏 / 取消(静态 token 有效);sati_resource_sub 收藏 / 取消;recommend_category_set 修改 / 还原
  • 听歌上报(效果已验证:进入最近播放、本周排行次数 +1):scrobble、scrobble_v1(单独验证)、relay_play_state_submit
  • 签到 / 领取:vip_sign(黑胶乐签打卡成功)、yunbei_sign(xeapi + v3)、vip_growthpoint_getall、yunbei_task_finish_v1(领到 150 云贝)、rep_ugc_user_sign、rep_ugc_activity_collect、rep_ugc_user_collect_vip、middle_play_do_lottery(静态 token 有效,次数 3→2)
  • 云小编考试:rep_ugc_exam_start → question_single_get → submit → result_get 全流程
  • 上传:avatar_upload(重构后的公共图片上传)
  • 云盘:cloud_import(修复后)→ song_cloud_download 拿到下载链接 → user_cloud_del 清理
  • 扫码登录:login_qr_key / create(platform=web、qrimg)/ check,使用本实现生成的二维码实际扫码登录成功
  • 设备:deviceinfo_center_upload 修改当前登录设备名称并还原;captcha_safe_sent 短信验证码实际收到
  • 手机号:cellphone_existence_check(与 Node 一致)、captcha_sent_v1(短信实际收到)
  • 游客注册:register_anonimous 成功返回 cookie
  • 路由 / 参数校验:/inner/version、/eapi/decrypt、驼峰别名路由;event_privacy、deviceinfo_center_upload、relay_play_state_submit 参数校验与 Node 一致
  • 请求选项(本地回显服务):headers、ua、domain、randomCNIP、ENABLE_RANDOM_CN_IP、timeout、crypto

⚠️ 与 Node 行为一致,但成功分支未能验证

  • comment_add、comment_reply、comment:测试账号被风控(250「风险提示」/ 401),Node 版返回完全相同
  • share_resource:测试账号被风控(250「安全验证分享异常」),Node 版相同;因此 event_privacy 只验证了错误路径
  • device_kickoff:带短信验证码后,对长期未活跃的旧设备两边均返回 500「登出失败,请重试」(验证码已通过校验);未对活跃设备测试以免掉线
  • thinktank_audit_resource_update:账号无权限(402),与 Node 一致
  • login_cellphone:修复参数构造(有验证码时不再多传 password)与返回体(补 cookie、avatarImgIdStr)。修复 IP 透传前返回 -462 安全验证(Node 版同一验证码登录成功);修复后因短时间内多次登录,账号被风控,两边均返回 10004「当前登录存在安全风险」,成功分支待风控解除后复测

❌ 未测试

  • comment_delete 成功分支(没能成功发出评论;错误路径与 Node 一致)
  • voice_upload(账号无播客)

未同步

  • register_checktoken_v2(依赖 jsdom 运行易盾 SDK)
  • ad_listening_rights_gain(看广告领免费听权益)

…ayer-Dev#3

- 新增 xeapi 加密通道(X25519 + AES-GCM 会话密钥),song_url_v1、评论、红心、签到等按上游切换
- 新增 neapi 加密通道(ChaCha20-Poly1305 + zstd),支持密钥配置解析、过期刷新与 777/999 重试;兼容服务端未结束的 zstd 帧
- 新增易盾 v3 反作弊 token;v2 依赖 jsdom,暂以静态 token 兜底
- eapi 默认域名切换为 interfacepc,NMTID 改为从服务端下发采集,新增 osx 系统配置并更新 UA
- 新增 66 个接口(日推风格、乐迷团、云小编、助眠解压、设备管理、scrobble_v1、decrypt、user_event_all 等)
- 同步已有接口改动:song_url_v1 / song_download_url_v1 / song_url_v1_302 支持 vivid,scrobble、vip_sign、comment 系列等
- voice_upload 支持 imgFile 封面上传,抽取公共图片上传函数
- 请求选项补齐 e_r(weapi 返回解密)、timeout、headers、crypto 覆盖、ENABLE_RANDOM_CN_IP
- 修复 HTTP 服务未解析 randomCNIP / ua / domain / e_r 参数的问题
- 修复 #1:注册 /inner/version 与 /eapi/decrypt 路由
- 修复 SPlayer-Dev#2:playlist_tracks 改走 weapi,并对齐 512 重试逻辑
- 修复 SPlayer-Dev#3:register_anonimous 成功时在 body 中返回拼接后的 cookie
- 新增上游驼峰路由别名(/digitalAlbum/*、/djRadio/top、/verify/getQr 等),README 补充新接口与参数说明
@jinzhongjia
jinzhongjia marked this pull request as draft October 6, 2026 07:21
@jinzhongjia

Copy link
Copy Markdown
Author

还没有经过完整测试,暂时转为草稿

- 连接池空闲超时设为 4 秒:interface*.music.163.com 5 秒即关闭空闲连接,复用已关闭连接导致偶发 error sending request(issue SPlayer-Dev#2 根因)
- 新增 NcmError::Response 携带上游原始响应,HTTP 服务原样返回 body / cookie / 状态码,与 Node.js 版一致;新增 status_code()、is_auth_required()、response() 辅助方法
- 错误信息在 msg 缺失时回退读取 message
- login_qr_key 返回结构对齐 Node(data 包裹)
- login_qr_create 支持 platform=web 生成 chainId(网易云 App 扫码需要)及 qrimg 二维码图片(SVG data URL)
- login_qr_check 在 body 中返回拼接后的 cookie
- 关闭连接复用(与 Node.js 版每次请求新建 Agent 一致):interface*.music.163.com 除 5 秒空闲关闭外还会不定期重置复用中的连接,仅缩短空闲超时仍会偶发 Connection reset
- cloud_import 先调用检查接口获取 songId 再导入(原实现 songId 恒为空,导入必然失败)
- comment_delete 参数名按上游改为 cid(兼容旧参数 commentId)
- 服务端透传客户端 IP:优先 realIP 参数,其次 X-Forwarded-For / 连接地址,本机请求使用进程级随机中国 IP(与 Node.js 版一致,避免服务器出口 IP 触发登录风控 -462)
- 服务启动时注册游客身份,未登录请求携带 MUSIC_A;register_anonimous 改用客户端当前设备 ID(修复后注册成功,issue SPlayer-Dev#3)
- _ntes_nuid 长度对齐 Node(32 字节)
- login_cellphone 有验证码时不再额外传 password,返回 body 中补充 cookie 并将 avatarImgId_str 改名为 avatarImgIdStr
@jinzhongjia
jinzhongjia marked this pull request as ready for review October 7, 2026 12:44
@jinzhongjia

Copy link
Copy Markdown
Author

应该可以合并了,我剩下的那两个没法验证了

原先 pool_max_idle_per_host(0) 完全不复用连接,每个请求都重新 TCP + TLS 握手,
单次多 0.4–0.5 秒,多段请求的命令成倍变慢。改为:

- 空闲连接 4 秒后主动丢弃,先于 interface*.music.163.com 约 5 秒的空闲回收
- 仍撞上被关闭 / 重置的复用连接(请求没拿到任何响应)时换新连接重试一次;
  超时、读响应体或解码失败不重试,避免重复执行
- 连接策略拆到 src/request/transport.rs,补本地 TCP 服务模拟「复用时断开」的测试

连续 10 个请求的中位数合计 10.8 秒 → 5.8 秒;跨 0–7 秒空闲间隔的
请求全部成功,未再出现 error sending request。
perf(request): 恢复连接复用并在失效连接上重试一次
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant