Wristhyme开发文档
示例网站公网音乐 API 已关闭。接口示例供阅读,自建部署后使用。
本页目录

接入、版本与维护

文档首页

1. 推荐调用流程

搜索到播放

  1. GET sources读取实时目录,处理空目录和禁用,保留id/backend区别。
  2. 用目录id发搜索。普通查询用search;10条增量列表用watch/search,不能混用两套page偏移。
  3. 保存Song.ref及展示字段,保留响应歌曲真实source。网易云排序信号仅与当前查询及policy一致时使用。
  4. 播放既有收藏时先restore;新搜索可直接使用key。410后允许有限恢复,400坏签名需要重新搜索。
  5. 当前受管解析策略:五个LX平台先sources读有效默认;非空则plugin_resolve使用稳定别名、consent=1、quality=128k;为空才走media。非LX平台用media。
  6. 插件返回的相对地址必须是本站plugin_media及ticket,禁止外部URL、凭证、fragment或重定向。直接媒体同样只能官方路径。
  7. 媒体请求带同一个已自愿注册的设备身份头,核验HTTP与Content-Type,交给播放器实际判断解码/播放。
  8. 原平台lyric与插件lyric独立;需要保存原平台歌词快照时使用snapshot。不要自动创造逐字时间或声称解析源歌词完全一致。

默认配置读取失败或插件失败不等于“没有配置”。不要吞错误然后静默换平台/插件;让用户知道当前失败,按产品策略提供明确重试或选择。

下载与续传

先确定解析方式,取得原平台media或plugin_media,再由客户端创建下载任务。本项目没有公开“创建服务器下载任务”接口。

原平台可download=1获取附件名;插件媒体自行命名。保存前确认是音频而非JSON/HTML,续传检查206/Content-Range/资源标记,200时从头处理。完整性无法只依inspect的size字符串判断;断流、缺长度、票据过期或资源变化应报告,不把文件自动标为完整。

分页刷新

列表仅进入页面/明确操作时加载,遵守has_more。首页refresh=1在轻量/榜单模块有清理快照作用;后续页refresh不等于重建全源快照。切源/切查询需取消旧任务或通过generation防止迟到响应覆盖当前列表。

重复页或去重后空页不应无限加载;通用网易云最多20页、轻量网易云第20页停止;推荐最多100个快照条目,B站轻量最多20个视频元数据条目。

2. curl 示例

以下代码块采用POSIX shell续行。Windows PowerShell请使用curl.exe,将命令写为单行或换为PowerShell反引号续行;不要把PowerShell内置curl别名当成curl程序。示例会访问线上接口,本次整理并未执行这些请求。

读取目录/搜索/公共歌单

curl --fail-with-body 'https://wristhyme.tolike.cn/api/sources'

curl --fail-with-body --get 'https://wristhyme.tolike.cn/api/search' \
  --data-urlencode 'source=netease' \
  --data-urlencode 'q=示例歌曲' \
  --data-urlencode 'page=1'

curl --fail-with-body --get 'https://wristhyme.tolike.cn/api/watch/search' \
  --data-urlencode 'source=qq' \
  --data-urlencode 'q=示例歌曲' \
  --data-urlencode 'page=1' \
  --data-urlencode 'refresh=1'

curl --fail-with-body --get 'https://wristhyme.tolike.cn/api/playlist' \
  --data-urlencode 'link=https://music.163.com/#/playlist?id=123456789' \
  --data-urlencode 'page=1'

curl --fail-with-body --get 'https://wristhyme.tolike.cn/api/charts/hot' \
  --data-urlencode 'source=netease' \
  --data-urlencode 'page=1'

引用恢复与插件解析

REF/KEY由真实搜索响应读取,不能拿文档示例key尝试播放。

curl --fail-with-body --get 'https://wristhyme.tolike.cn/api/restore' \
  --data-urlencode "ref=$REF"

curl --fail-with-body --get 'https://wristhyme.tolike.cn/api/plugin_resolve' \
  --data-urlencode "key=$KEY" \
  --data-urlencode 'plugin=0000000000000000' \
  --data-urlencode 'consent=1' \
  --data-urlencode 'quality=128k'

仅在产品具有相应第三方服务同意/受管解析政策时发送consent=1;匿名统计consent是另一个独立功能。没有默认插件时上述解析返回503。不要用curl -L无条件跟随重定向传播身份头。

后台同源请求形状

登录body由本地安全输入提供,避免把密码写进命令历史。这里只展示结构,不自动创建账号或运行管理操作:

POST /admin/api/login HTTP/1.1
Host: wristhyme.tolike.cn
Origin: https://wristhyme.tolike.cn
Content-Type: application/json
Content-Length: <UTF8_BODY_LENGTH>

{"username":"admin","password":"<ADMIN_PASSWORD>"}

随后一个设置默认插件请求:

POST /admin/api/default-plugin HTTP/1.1
Host: wristhyme.tolike.cn
Origin: https://wristhyme.tolike.cn
Cookie: __Secure-wristhyme_admin=<SESSION_TOKEN>
X-CSRF-Token: <CSRF_FROM_LOGIN_OR_SESSION>
Content-Type: application/json
Content-Length: <UTF8_BODY_LENGTH>

{"id":"<ENABLED_PLUGIN_ID>"}

这是HTTP结构模板,不是字节级可直接发送报文;Content-Length由HTTP库根据UTF-8实际body设置。不要把后台Cookie用于/api/media或第三方请求。

3. JavaScript接入示例

示例可用于同源前端或支持fetch的Node环境;跨域浏览器还受CORS限制。只演示JSON/解析链路,不含完整播放器或下载续传。

const ORIGIN = 'https://wristhyme.tolike.cn';
const LX_SOURCES = new Set(['netease', 'qq', 'kugou', 'kuwo', 'migu']);

async function getJSON(path, parameters = {}, signal) {
  const url = new URL(path, ORIGIN);
  if (url.origin !== ORIGIN || url.username || url.password ||
      url.hash || !url.pathname.startsWith('/api/')) {
    throw new Error('Invalid gateway URL');
  }
  for (const [key, value] of Object.entries(parameters)) {
    url.searchParams.set(key, String(value));
  }
  const response = await fetch(url, { redirect: 'error', signal });
  const result = await response.json();
  if (!response.ok) {
    const error = new Error(result.error || result.detail || `HTTP ${response.status}`);
    error.status = response.status;
    throw error;
  }
  return result;
}

async function mediaURL(savedSong, signal) {
  const song = savedSong.ref
    ? await getJSON('/api/restore', { ref: savedSong.ref }, signal)
    : savedSong;
  if (!song.key) throw new Error('Missing song key');
  if (LX_SOURCES.has(song.source)) {
    const catalog = await getJSON('/api/sources', {}, signal);
    if (catalog.default_plugin) {
      const resolved = await getJSON('/api/plugin_resolve', {
        key: song.key, plugin: '0000000000000000', consent: '1', quality: '128k'
      }, signal);
      if (typeof resolved.url !== 'string' ||
          !resolved.url.startsWith('/api/plugin_media?ticket=')) {
        throw new Error('Invalid plugin media path');
      }
      const media = new URL(resolved.url, ORIGIN);
      if (media.origin !== ORIGIN || media.username || media.password ||
          media.hash || media.pathname !== '/api/plugin_media' ||
          !media.searchParams.get('ticket')) {
        throw new Error('Invalid plugin media URL');
      }
      return media.href;
    }
  }
  const media = new URL('/api/media', ORIGIN);
  media.searchParams.set('key', song.key);
  return media.href;
}

生产客户端还应限制JSON响应大小、设置超时、持久化ref、清理已过期票据、保护设备头并校验实际音频。本站禁用检查由网关执行;客户端不要用旧目录绕过服务端错误。

4. 版本差异

证据/阶段 协议变化或限制
server-next历史网关 固定来源目录、搜索最多60、原平台媒体、LX基础合同;不能代表最新后台策略
search221-signals 网易云独立search_signals、当前页排序,信号不进key/ref
admin219及followup 动态来源、启停/封禁、匿名统计、Cookie/CSRF后台;用户撤回标记及基础平台过滤
restore-default225 默认真实ID与有效ID、稳定别名、禁用回退;隐藏新增/导入UI但保留API
netease-page20 通用网易云搜索上游单次60改20,不只是客户端截断
watch220-light/backend 新增watch/search、recommend、playlist;10项分页,网易云helper支持10/20
desktop-baseline221/new-backend 独立charts/hot路径、QQ/网易云每页10;不能误当推荐歌单
android当前2.2.2 手机/手表受管默认解析、自动发送解析consent=1、去掉手动插件设置;本地私人库仍本地保存
wristhyme-ios及桌面测试版 仍有各自手动插件/同意流程;不能假定采用Android2.2.2全部策略

老客户端硬编码来源可能不反映禁用目录;服务端仍可拒绝。排序支持也因客户端版本而异,服务端返回次序不保证旧客户端不二次重排。

独立包已只读取得包含动态策略、轻量分发和热榜的现有服务器源码,提取哈希见PROVENANCE.json;包装修改与历史合同分别记录,不再要求外部补丁目录。

5. 源码索引

所有链接相对文档目录,ZIP解压后可直接查看。原服务器来源及提取前哈希见PROVENANCE.json。

内容 来源
通用网关、key/ref、歌词、媒体及分发 server.py
轻量列表 watch_browse.py
网易云10/20参数合同 netease_catalog.py
搜索信号 netease_signals.py
去重元数据 catalog_dedup.py
插件及默认别名 plugin_routes.py
媒体验证 media_validation.py
歌词规范化 lyric_normalize.py
热榜 hot_charts.py
设备策略与动态来源 admin_gateway.py
后台HTTP admin_server.py
后台存储与默认配置 admin_store.py
脚本导入 admin_import.py
插件注册表 plugin_registry.py
运行时 runtime.py
工作进程 worker.py
反向代理 Caddyfile
上游注册路由 internal/web目录

原工作区历史STATUS/客户端证据仅用于版本比较,不随本包分发;本包运行版本测试另见VERIFICATION.md,不替代生产音乐在线检查。

6. 文档验证

工作区提供仅离线的文档检查:

node .\docs\api\verify-docs.cjs

检查本地Markdown链接、JSON示例语法、代码块闭合、公开/管理/内部路由的文档覆盖,并解析JavaScript示例语法。它不访问网络、不运行部署脚本、不登录后台、不验证所有业务行为。

后续后端变更时应同步参数表、错误分支、模型与来源索引;若需要线上验收,应另行明确测试环境和允许范围。只读搜索也会触发上游联网;plugin_probe会执行脚本;register/forget和后台POST会改变状态,不能当作无副作用探测。

**建议维护验证项:**动态目录为空/自定义别名/禁用、key淘汰与ref恢复、默认回退与无默认、两套分页边界、empty与error区别、consent/quality校验、旧票据禁用、Range200/206/416/非音频、统计撤回与封禁例外、登录CSRF/会话过期/改密码、GitHub固定commit及导入哈希。