协议与数据模型
独立部署说明:文中官方域名为原服务示例,实际使用配置的实例域名;后台ADMIN_ORIGIN要求HTTPS且包含实际非默认端口。可信代理通过TRUSTED_PROXY_IPS显式配置,内部服务/数据路径也已环境化,详见部署文档。初次实例不带旧数据库/密钥/默认插件或原音源启停策略。
1. HTTP 约定
- 公开和管理接口基地址均为
https://wristhyme.tolike.cn,路径区分大小写。 - 音乐元数据读取为 GET,查询参数采用 URL 编码;不要把中文、
ref或歌单链接手工拼接进 URL。 - 写入接口为 POST,JSON 根必须是对象,
Content-Type: application/json,须提供一个明确的Content-Length。设备与后台实现拒绝Transfer-Encoding分块上传。 - 成功 JSON 没有统一
data外层,也不是所有接口都返回ok;按各接口实际结构解析。 - 网关 JSON 通常为
application/json; charset=utf-8;音频是二进制,不可调用 JSON 解析器。 - 网关/后台通常返回
Cache-Control: no-store、X-Content-Type-Options: nosniff、Referrer-Policy: no-referrer、X-Frame-Options: DENY及 CSP。静态发布文件遵循反向代理的缓存策略。 - 不存在通用的 Bearer/JWT/API Key 鉴权。不要给音乐请求携带管理员 Cookie、内部密钥或平台登录凭证。
- 当前代码没有通用 CORS/OPTIONS 支持。原生客户端可访问,浏览器建议同源调用;不能假定跨域前端能直接使用。
- 没有统一 HEAD 支持,也没有公共 PUT/PATCH/DELETE 合同。Python 默认处理可能返回 HTML 501,而非 JSON 错误。
- 查询解析只使用同名参数的首个值,空值可能被忽略;因此不要依赖重复参数或空参数来表达列表。
- 未知查询字段不一定被拒绝,但不能据此声称支持该功能。
可选客户端身份头
| 头 | 格式 | 用途 |
|---|---|---|
X-Wristhyme-Device |
32 位小写十六进制 ID + . + 64 位小写十六进制签名 |
已自愿注册实例的统计及设备封禁识别 |
X-Wristhyme-Version |
字符串,观察逻辑最多保留 32 字符 | 应用版本 |
X-Wristhyme-Variant |
字符串,最多保留 16 字符 | 如 phone、watch、desktop、ios |
Range |
单个 bytes=start-end 范围 |
仅音频流请求 |
设备头不是管理员身份,也不证明物理设备或自然人身份。客户端只应向本站 HTTPS /api/ 发送设备凭证,并阻止跨站重定向。
X-Real-IP 由可信反向代理覆盖,仅特定代理地址受信任;不要依靠客户端伪造此头改变限流或封禁。服务端不信任任意 X-Forwarded-For。
2. 标识与有效期
| 字段 | 含义 | 生命周期 |
|---|---|---|
source.id |
客户端可选音源目录 ID,可为自定义别名 | 后台可改名、启停、删除自定义项 |
source.backend / song.source |
实际平台身份,例如 netease |
歌曲协议必须保留真实平台,不能替换为别名 |
song.key |
服务器已知歌曲的短期索引,当前生成算法为 SHA256 前 32 位小写 hex | 写入歌曲缓存后 21600 秒;最多 1200 条,可能提前淘汰;重启丢失 |
song.ref |
可恢复歌曲的签名引用 | 无内置时间戳过期;依赖签名密钥、合法结构和音源仍启用 |
plugin.id |
脚本原始字节 SHA256 的前 16 位小写 hex | 内容变化会改变 ID;目录启停影响可用性 |
0000000000000000 |
跟随后台默认插件的稳定别名 | 每次解析映射至当时有效默认,非真实脚本 ID |
ticket |
插件解析后的随机音频访问票据 | 3600 秒;最多 512 条,可能提前淘汰;重启丢失 |
device_id |
随机匿名安装实例 ID | 非硬件 ID;撤回统计后需客户端停止使用旧凭证 |
| 管理 Cookie | 管理会话随机令牌 | 8 小时固定有效期;退出、改密码等会使其失效 |
key 与 ref
服务器依据歌曲完整内部对象计算 key,并保存歌曲。客户端不能提交任意平台 ID、标题或 URL 来代替 key。
ref 目前为 base64url(JSON歌曲).HMAC-SHA256,是防篡改引用,不是加密数据。客户端应将其视为不透明字符串,不自行解码、修改、生成或提取第三方信息。原有“签名过期”错误文案不表示 ref 有独立 TTL。
收藏应保存 ref;下次播放可先 /api/restore 获取当前 key。恢复返回的新 key 有可能与原 key 相同,但重新注册缓存项;不要要求 key 一定变化。签名密钥被替换、引用损坏或音源禁用时恢复会失败。
网易云 search_signals 被排除在歌曲缓存和签名之外,避免搜索排名变化改变歌曲身份。经恢复得到的歌曲通常没有该查询的排序信号。
默认插件
- 后台目录
default_plugin是管理员配置值;effective_default_plugin是当前实际生效值。 - 公开
/api/sources.default_plugin和/api/plugins.default_plugin返回有效值,不是必须等于原始配置值。 - 没有配置默认时有效值为空,不自动挑插件;原平台
/api/media仍可使用。 - 已配置的插件禁用后,按后台插件创建时间/ID顺序选择其他启用项;全部禁用时为空。原配置插件重新启用后恢复原默认。
- 公开插件接口中
plugin省略、default或稳定别名都会映射有效默认;没有默认返回 503,不会自动转为原平台解析。 - 当前 Android 2.2.2 在解析支持的平台前读
/api/sources;有效默认为空才走原平台。获取配置或插件解析失败会报告错误,不静默换源。
3. 公共数据模型
3.1 Source
| 字段 | 类型 | 说明 |
|---|---|---|
id |
string | 目录 ID;通常 [a-z0-9_]{1,48} |
name |
string | 展示名称,可被后台修改 |
backend |
string | 基础平台 ID;旧网关可能缺少 |
plugin |
string | 关联插件推荐 ID;未关联或已禁用时为空;不等于自动解析插件 |
已知基础平台:qq、netease、kugou、kuwo、migu、soda、bilibili、qianqian、joox、jamendo、fivesing、apple。后台支持配置不代表当前公开目录全部启用。
LX 映射:netease → wy、qq → tx、kugou → kg、kuwo → kw、migu → mg;汽水和哔哩哔哩不在当前网关 LX 映射中。
3.2 Song
| 字段 | 类型 | 是否通常存在 | 说明 |
|---|---|---|---|
key |
string | 是 | 服务器歌曲索引 |
ref |
string | 是 | 签名恢复引用 |
name |
string | 是 | 标题 |
artist |
string | 是 | 歌手/上传者,多人可能以 、 分隔 |
album |
string | 是 | 专辑;哔哩哔哩可能为 BV 号 |
duration |
string | 是 | 上游通常为秒数字符串,可为空;不是毫秒整数 |
source |
string | 是 | 基础平台,而非自定义目录别名 |
original |
boolean | 是 | 保留的历史标记;false 不证明是翻唱,true 也不构成独立音频鉴定 |
cover |
string | 是 | 封面地址或空字符串;不保证能访问,客户端有自己的允许策略 |
access_hint |
string | 否 | 平台权限提示,不是权限验证结果 |
search_signals |
object | 否 | 网易云搜索的查询相关信号 |
Song 不返回原始平台 id、内部 extra 和上游音频 URL 字段。ref 不透明,不应作为这些字段的公共替代查询接口。
3.3 Playlist
| 字段 | 类型 | 说明 |
|---|---|---|
id |
string | 平台公共歌单 ID,不是歌曲 key |
source |
string | 基础平台 |
name |
string | 歌单标题 |
creator |
string | 创建者名称,可为空 |
track_count |
string | HTML 元数据保留为字符串,可能为空,不应强制要求 number |
3.4 网易云 search_signals
| 字段 | 类型/固定值 | 意义 |
|---|---|---|
policy |
netease-signals-221 |
信号合同标识 |
original_recording |
object | status: unknown、rank: 0、references: []、candidate_fields: {} |
popularity |
object | status: unknown、value: null、metric: null、reason、candidate_fields |
version |
object | status、kinds: string[]、evidence: object[] |
upstream_index |
integer | 原始上游位置,0 起始,包含页偏移 |
upstream_order_role |
stable_tiebreak_only_not_popularity |
上游位置仅用于稳定兜底 |
ranking |
object,非空查询时存在 | relevance、version_intent_misses、original_rank、popularity_rank、version_penalty |
version.status 为 metadata_hint 或 unmarked_not_original。已识别 kinds:live、cover、remix、instrumental、piano、sped_up、slowed、acoustic、remaster、interlude、rnb。
证据对象:{kind, field, text, confidence: "metadata_hint"},来自明确标题版本段或 alia/alias。原唱引用对象:{artist_text, field, text, confidence: "reference_only_not_recording_proof"}。
候选字段只是未经解释的观察值;标题、权限码、上游次序都不应被展示为真实热度。未标注版本不等于原版。排序范围是当前页,不是全库热度排序。
4. 错误与状态码
常见错误:
{"error":"结果已过期,请重新搜索"}
可选附加字段有 code、source、upstream_status、state、songs。插件 probe 失败可能返回 {ok:false, detail, error};客户端可按 error、detail、HTTP 状态依次降级。不存在统一业务数字错误码表;code: access_blocked 是明确存在的例外。
| HTTP | 典型含义 | 客户端处理 |
|---|---|---|
| 200 | 正常结果;允许空列表、空歌词、valid:false、内部 ok:false |
检查具体业务字段 |
| 206 | 上游实际支持的局部音频响应 | 核对 Content-Range 后处理续传 |
| 400 | 参数/JSON/签名/consent/导入验证失败 | 修正请求;不要原样无限重试 |
| 401 | 管理会话失效或设备凭证缺失/无效 | 重新登录;设备流程先处理同意和凭证 |
| 403 | 设备/IP封禁、插件/音源禁用、同源/CSRF失败 | 停止当前操作;不自动绕过策略 |
| 404 | 路径不存在;内部接口对未授权隐藏 | 检查接口、方法及部署版本 |
| 410 | key 或 ticket 失效;key 查找还可能因音源禁用失败 | restore 或重新搜索/解析,避免无界循环 |
| 416 | Range 格式不支持 | 改用单范围;根据播放器/下载器策略重试 |
| 422 | 插件执行/初始化失败 | 查看 detail 或显式切换可用解析方式 |
| 429 | 并发/频率限制 | 指数退避加随机抖动;不要并发重试 |
| 502 | 上游/第三方失败、返回非音频或资源变化 | 保留真实错误,允许有限手动重试 |
| 503 | 策略、后台或运行时暂不可用;默认插件未设置 | 区分配置问题与暂时失败 |
| 504 | 原平台音频连接失败/超时分支 | 有限重试,不把 JSON 错误保存为音频 |
没有保证的 Retry-After。原平台上游 401/403/404/429 等通常被包装为外层 502,原值在 upstream_status,不可只看 HTTP 判断平台原因。
音频响应一旦发送头部,后续断流不能再转为 JSON 错误。HTTP 200 不等于文件完整;完整播放与下载完整性要由客户端核验。
5. 资源与缓存限制
以下为找到的源码默认值,按进程计算,并非公开吞吐 SLA。
| 项目 | 限制 |
|---|---|
| 普通网关慢请求 | 6 个并发槽;通常包括搜索、歌单、歌词、inspect、snapshot、原平台媒体 |
| 插件 probe/resolve/lyric/media | 共用 2 个并发槽,媒体流期间占槽 |
| 插件操作频率 | 每真实 IP 60 秒 24 次;probe/resolve/lyric 在同意校验后计数,media 不计入此窗口 |
| 轻量列表 | 2 并发槽;缓存 300 秒、最多 40 项 |
| 热榜 | QQ/网易云分别一个互斥加载锁;缓存 300 秒、最多 48 项 |
| 普通搜索 | 非空结果缓存 90 秒、最多 100 项;refresh=1 绕过,空结果不提供有效 TTL |
| 设备注册 | 同一 IP 每小时最多 5 次 |
| 活动观察 | 每实例/未识别IP约30秒聚合一次;队列1024,满时可丢弃,不阻塞播放 |
| 后台 | 12 个并发槽;导入另限 1 个任务 |
| 后台登录 | 每IP 15分钟8次,整体15分钟80次,失败尝试也占用 |
| 后台写入 | 每会话60秒80次 |
| 设备 POST body | 1..4096 字节;反向代理也有4KB层限制 |
| 登录 body | 1..4096 字节 |
| 后台普通 body | 1..1450000 字节;代理还有1500KB限制 |
| 内部统计 body | 最多65536字节 |
LX /run body |
1..32768字节 |
| 歌词读取 | 通常最多131072字节;snapshot另读1字节检测超限 |
服务器搜索/元数据超时通常 12..22 秒;原平台音频连接 35 秒;LX 运行时请求 32 秒、工作进程 28 秒、第三方取流 15 秒。实际端到端可叠加,不应把其中某个值当固定 SLA。
6. 安全与兼容
- 网关仅接受服务器已知歌曲,插件媒体仅接受票据;没有公开任意 URL 代理接口。
- 歌单链接只提取受支持主机的 ID,不任意抓取用户 URL;短链不展开。
- 插件 HTTP 使用 SSRF 防护、公共地址校验及资源限制;启用脚本需要管理员确认信任。
- 管理统计包含真实 IP 等敏感运营数据,不应公开导出示例或备份。
- GET 插件 probe/resolve 会执行第三方请求;不要因其是 GET 就进行预取、爬取或浏览器自动探测。
- 多实例网关的进程内 key/ticket 需要共享存储或粘性路由,签名密钥也须一致。当前代码不提供这套协调机制。
- 接口接受新增字段;客户端应忽略未知字段,但不能把缺失字段填成已确认的热度、原唱、可播放或无损结论。