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

后台管理 API

文档首页 · 通用协议

基地址 https://wristhyme.tolike.cn,页面 /admin,接口 /admin/api/*。本文只使用占位凭据;不提供真实密码、Cookie、CSRF 或内部密钥。

1. 鉴权与请求格式

请求 必需条件
POST login Origin: https://wristhyme.tolike.cn、JSON对象,不要求已登录或CSRF
其他GET 有效管理Cookie
其他POST 有效Cookie + 完全相等的Origin + X-CSRF-Token

会话Cookie名 __Secure-wristhyme_admin,属性 Path=/admin; Secure; HttpOnly; SameSite=Strict; Max-Age=28800。8小时固定会话,服务端最多保存20个;不是JWT,不通过Authorization头传递。

除登录外写接口先校验会话/同源/CSRF,再检查写频率并解析body。错误:401未登录/过期;403Origin或CSRF不匹配。GET也应同源使用,但后端读接口没有与POST相同的Origin检查。

POST必须application/json、一个Content-Length、JSON对象,不支持Transfer-Encoding;普通body最多1450000字节,登录4096字节。即使logout等无字段操作也发送{},不能零长度body。

通用错误为{error:string};成功修改通常{ok:true},不返回修改后对象或新生成ID。操作后通过catalog/dashboard读取结果。后台没有异步jobID或统一幂等键;网络失败可能已经完成写入,重复提交前先读状态。

文本字段通常要求string、去两端空白、禁止控制字符;长度检查在strip前,未注明可选的文本不能为空。enabled/trust/hours等要求真实JSON类型,字符串false/24不等价。

12并发槽、单任务导入;同会话每分钟80个POST。没有通用事务批量更新或资源版本/If-Match保护,不假定并发管理员编辑能自动避免覆盖。

2. 登录、会话与密码

2.1 POST /admin/api/login

body:{username:string,password:string}。

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

200:{csrf:string,username:string},并Set-Cookie。username取服务器配置;示例admin不能代替配置。密码校验采用scrypt,登录输入长度12..256,非法长度最终表现为登录失败。

401账号或密码不正确;403登录Origin不符;429每IP15分钟8次或全局80次限制;400请求结构无效;503服务失败。

2.2 GET /admin/api/session

200:{csrf:string,username:string}。页面刷新可恢复会话与CSRF,不必再传密码。不会自动延长固定会话;无效会话401。

2.3 POST /admin/api/logout

body {};200 {ok:true},清除当前服务端会话并Set-Cookie Max-Age=0。仅退出当前Cookie,不是注销所有管理员会话。

2.4 POST /admin/api/password

字段 类型 必需 校验
old string 是 原密码,最多256字符
new string 是 新密码16..128字符

200:{ok:true,relogin:true}。成功后全部管理会话失效,需要重新登录;请求响应不保证立即清当前浏览器Cookie,但服务端不再接受它。

400原密码错误或新密码长度不符;其余通用鉴权/频率错误。修改的是独立后台密码,不是SSH或平台音乐账号密码。

3. 查询

3.1 GET /admin/api/dashboard

query page可选默认1;转整数后钳制1..1000,非整数400。每页最多100条,当前接口不接受服务端search/variant/online筛选;UI筛选只是当前已取回数据。

200:

{
  "stats":{
    "device":{"retained":10,"today_active":4,"online":2},
    "legacy_ip":{"retained":8,"today_active":3,"online":1}
  },
  "devices":[
    {"kind":"device","id":"11111111111111111111111111111111","ip":"192.0.2.10","first":1790810000,"last":1790812800,"requests":12,"version":"2.2.2","variant":"phone","online":true}
  ],
  "page":1,
  "online_window_seconds":300,
  "retention_days":30,
  "notice":"设备=自愿注册的匿名安装实例,不是人数/物理设备;旧客户端仅记录独立IP,不能据此统计用户。在线=最近5分钟API活动。无硬件指纹、关键词或播放内容采集。"
}

统计和示例IP均为合成说明数据。first/last为Unix秒,requests是合并上报活动次数,不是音乐播放/下载次数。today_active代码实现是滚动86400秒,不是北京时间自然日零点起算。

legacy_ip是历史类型名,现在也用于未提供有效凭证的访问;不得全部标作旧客户端。响应没有总页数、记录总数或has_more,不能假定UI下一页按钮有精准总量。

3.2 GET /admin/api/catalog

无参数。200返回:

字段 类型 说明
default_plugin string 原始配置默认ID,可空
effective_default_plugin string 当前有效默认ID,可空
plugins AdminPlugin[] 所有后台插件,包含禁用项;创建时间降序
sources AdminSource[] 所有音源,包含禁用项;内置优先
bans Ban[] 创建时间降序,最多500条,可能含已过期项
audit Audit[] 最新最多100条

AdminPlugin:

字段 类型 含义
id string 16位SHA256前缀
name / version string 展示名称/脚本版本
sha256 string 完整64位内容哈希
filename string 服务端保存文件名,不是客户端下载URL
managed integer 0/1 受管导入文件还是只读原始文件
enabled integer 0/1 SQLite行原始启用值;注意不同于写入时boolean
origin string 原始本地/local-upload/固定GitHub来源
created integer Unix秒

AdminSource:{id,name,backend,plugin,enabled,builtin}。enabled/builtin为SQLite整数0/1,plugin可空。AdminSource和公开Source不完全相同。

Ban:{id,kind,target,reason,created,expires}。id为16位随机hex,kind=device/ip,target为设备ID或规范化CIDR;时间Unix秒,expires=0永久。过期项可留在目录中但不再生效。

Audit:{id,at,action,target,detail}。id整数,at Unix秒。action包含login/logout/password_changed、ban_device/ban_ip/unban、plugin_update/plugin_import、source_save/source_delete、forget_statistics/device_statistics_optout/default_plugin及迁移审计标记;它不是封闭枚举。审计最多10000条,并周期清理30天前记录。

该目录含运营IP、脚本来源等私有信息,不应像公开plugins目录一样发送给客户端或公开网站。

4. 封禁及统计清理

4.1 POST /admin/api/ban

字段 类型 必需 约束
kind string 是 device/ip
target string 是 最多100字符;device为32位小写hex,ip可为地址或CIDR
reason string 否 默认空,最多200字符
hours integer 否 默认24,0..8760;0永久,不接受boolean

IP由ip_network(strict=false)规范化;IPv4网段至少/16,IPv6至少/32,拒绝过大网段。示例:

{"kind":"ip","target":"192.0.2.10","reason":"示例限制","hours":24}

200 {ok:true}。同kind/target已有封禁会被替换,并生成新封禁id;最大2000条。条数检查在替换前执行,满额时同target替换也可能被拒绝。

限制音乐API,不等于限制后台登录或静态发布下载。隐私说明和持有效凭证的设备forget例外仍开放。400格式/期限/容量错误;其他鉴权/频率错误见通用规则。

4.2 POST /admin/api/unban

body {id:string}:封禁记录ID,不是设备ID/IP,文本最大32。200 {ok:true},解除后投影策略;不存在的记录也可能成功,不保证404。

4.3 POST /admin/api/forget

body {kind:"device"|"legacy_ip",id:string},id为32位小写hex活动记录ID。200 {ok:true}。

只清活动记录,不解除封禁,不删除插件/音源,不写用户撤回标记;后续活动可以再次建记录。不存在的匹配活动可能也成功。

5. 插件管理

5.1 POST /admin/api/plugin

字段 类型 必需 约束
id string 是 已存在插件ID,最多16字符
name string 是 非空、最多80字符
enabled boolean 是 不接受数字/string替代
trust boolean 由禁用转启用时需要 必须true,确认信任第三方脚本

200 {ok:true}。修改名称只改展示元数据,不重写原始脚本;恢复后的运行核心仍使用文件stem作为currentScriptInfo.name语义。

启用时检查文件存在、非符号链接、完整SHA256一致。禁用会隐藏公开目录项并阻止之后的解析/已有票据请求;不表示已开始的流能立即被强制中断。

400插件不存在、trust缺失、名称不合法或文件/哈希错误。当前没有公开插件删除接口;不要把enabled=false当删除。

5.2 POST /admin/api/default-plugin

body {id:string},最多16字符,允许空字符串清除配置。非空必须是已启用插件且文件/哈希验证有效。

200 {ok:true},随后GET catalog确认配置值/有效值。禁用原默认时可回退,重新启用原默认时恢复;具体算法见协议文档。

此设置并不强制覆盖旧客户端手动插件选择,也不使 /api/media 自动变为插件流。当前Android2.2.2会在解析时读有效默认并使用稳定别名。请求失败时不要以另一个插件成功掩盖错误。

5.3 POST /admin/api/import

后台插件管理开放本地文件上传与GitHub固定提交导入。导入默认禁用,需另行启用/trust;导入不会执行脚本或证明其安全、兼容、可播放。

共同可选字段 sha256:非空时必须64位hex并和内容哈希一致,大小写可接受。

方式A:上传

{"method":"upload","filename":"example.js","content_base64":"<BASE64_UTF8_JAVASCRIPT>","sha256":"<EXPECTED_SHA256>"}

filename为普通.js名称,1..160字符前缀,不能有路径分隔、控制字符;content_base64必须严格标准Base64,字符串不超过1400000字符。解码后文件1字节..1MiB,UTF-8/UTF-8 BOM,可包含非ASCII名称/内容;拒绝NUL、常见HTML/XML。允许任意JavaScript内容,不要求LX字符串标识,不在导入时执行或判定语法兼容性;运行时仍仅支持LX协议,不自动兼容Node/CommonJS/ES模块或其他音源协议。

方式B:GitHub固定文件

{"method":"github","url":"https://github.com/OWNER/REPO/blob/<40_HEX_COMMIT>/path/example.js","sha256":"<EXPECTED_SHA256>"}

或raw.githubusercontent.com/OWNER/REPO/完整commit/path/example.js。地址最多1500字符;只HTTPS,端口默认/443,无凭证/query/fragment。每段路径只允许普通ASCII字母数字_.-且1..100字符,禁止./..、转义、分支main/master、目录/仓库/压缩包链接。commit必须完整40位hex,末尾.js。

服务器规范化为固定raw地址,通过公共DNS/SSRF策略获取,不接受跳转或压缩响应,超时8秒;未知HTTP/下载失败按400或503等分支报告,不保证统一“网络错误码”。

新导入200:

{"id":"0123456789abcdef","name":"示例插件","version":"1.0.0","sha256":"<SHA256>","enabled":false,"duplicate":false,"validation":"仅UTF-8、大小、格式线索和哈希检查,未执行脚本;不代表安全或兼容"}

重复同内容200:{id,sha256,duplicate:true,enabled:boolean},字段比新导入少,不覆盖名称、启用状态或原文件。ID为SHA256前16位;短ID冲突拒绝覆盖。插件最多100个,导入文件按完整哈希保存。

400内容、地址、哈希或容量校验失败;429已有导入/写频率/并发;503未完成异常。只有GitHub方式涉及网络,离线测试通过不代表真实GitHub取回成功。

6. 音源目录

6.1 POST /admin/api/source

字段 类型 必需 校验
id string 否 无/空时生成custom_+12位hex;存在时[a-z0-9_]{1,48}
name string 是 非空、最多80字符
backend string 是 已实现基础平台之一
plugin string 否 默认空;非空须为已有插件,允许目录关联暂时禁用插件
enabled boolean 是 真正boolean

200 {ok:true}。无id新增后通过catalog找生成ID;不返回新source。基础平台只能现有十二种;新增自定义项是平台别名/展示配置,不是新平台适配器或任意URL数据源。

内置平台ID不能改backend;可改名、启停、关联插件。新增时builtin=0,已有内置项保持builtin身份。音源最多100项。自定义项启用但基础平台禁用时不会在公开sources显示。

400未知平台、enabled类型错误、关联插件不存在、内置身份变更或容量/文本错误。

6.2 POST /admin/api/source/delete

body {id:string}。200 {ok:true}。只删除自定义项;内置音源400“只能禁用或改名”。不存在的ID可能正常成功。删除目录别名不等于删除歌曲、本地库或基础平台适配代码。

后台音源目录开放新增表单,可创建现有平台别名、选择基础平台和关联已启用插件。前端新增默认禁用,需管理员随后手动启用。

7. 错误分支与运维

后台通用400文案可能为“请求参数无效,请核对格式、期限、脚本地址或校验值”;已认证/同源验证后的业务ValueError会返回更具体的文本,最多200字符。未预期异常503“操作未完成”,私有服务日志用于诊断,不应向客户端暴露配置或脚本token。

后台写入SQLite并投影只读策略/运行时注册表。出现网络错误或投影失败时,不保证数据库操作完全未发生。重新GET catalog/dashboard核对状态后再重试,尤其是注册、新增、导入和封禁。

后台设备/IP数据、数据库备份、password_hash/internal_secret、真实Cookie和管理导入来源都应按私有运营数据处理。文档不应随着发布源码包上传这些数据。

8. API 运维

GET /admin/api/maintenance

返回 {enabled:boolean,message:string,retry_after:integer,paths:string[]}。paths为空表示全局维护。

POST /admin/api/maintenance

同样结构,message为1..200字符,retry_after为30..86400秒,paths仅允许已知音乐接口路径,不接受通配符/管理接口。SQLite持久化并投影策略;新请求返回503、code: maintenance和Retry-After,健康/隐私/撤回不阻断,已有流不强制终止。示例域名的Caddy硬性403不会被模拟维护开关打开。

9. 发布管理

自建后台提供真实上传/发布功能。示例后台仅浏览器内模拟,任意非空密码可进入,实际/admin/api/*均403;上传文件不发送到服务器,刷新页面丢弃演示状态。自建真实后台不接受任意密码。

GET /admin/api/releases

200 {files:[],feeds:{}}。file包含filename、size、sha256、uploaded_at及url,feed按phone/watch/desktop/ios键组织。

POST /admin/api/releases/begin

JSON {filename,size,sha256?}:普通ASCII文件名1..160,大小1字节..512MiB,apk/ipa/zip/exe/msi/dmg/deb/rpm/appimage后缀;同名不覆盖。最多4个未完成上传、100个发行文件,未完成上传24小时清理。返回 {upload_id,chunk_size:4194304,offset:0}。

POST /admin/api/releases/chunk

query id为上传ID,offset为当前偏移;Content-Type为application/octet-stream,单块1字节..4MiB,明确Content-Length,同源Cookie/CSRF仍必需。返回 {offset},偏移错位400。每会话每分钟最多1024块,最多单文件512MiB;代理max_size应至少5MB。

POST /admin/api/releases/finish

JSON {upload_id}。校验总长度、SHA256和APK/IPA/ZIP容器,APK检查AndroidManifest.xml存在;不验证应用ID、实际版本、证书或恶意内容,发布者需自行审核。返回 {ok,file,validation};不完整/哈希不符400,文件仍可取消。

POST /admin/api/releases/cancel

JSON {upload_id}。删除该未完成上传,返回ok,不删除已完成发行文件。

POST /admin/api/releases/publish

JSON {channel,filename,versionName,versionCode,minSdk?,notes?},versionName为三段数字,versionCode正整数且大于当前渠道值,notes最多4000字。Android固定包名,phone文件名Wristhyme-Phone-VERSION.apk、watch为Wristhyme-VERSION.apk,minSdk23..100。发布前人工确认包名/签名,界面提供确认弹窗。

原子manifest是渠道唯一数据源;公开GET /downloads/latest-{channel}.json由admin生成,apkUrl/URL使用ADMIN_ORIGIN和发行文件路径,Android文件位于/downloads/NAME.apk。保持历史客户端字段合同,但用户应用仍需改base/更新策略才能接新实例。

POST /admin/api/releases/delete

JSON {filename},只删除已记录且未被当前feed引用的文件,禁止删正在发布的包。未提供自动降版本回滚,需独立受控操作。

发行数据在独立releases卷,私有分块文件在data/uploads;需备份两者。公开目录只由文件服务提供,不执行文件,不允许上传JS/HTML到发行目录。所有写操作要求真实管理员Cookie、Origin/CSRF,不更改旧SSH账号或其他项目。