音频床机器人接入说明
面向站外开发者。读完本文即可实现一个会传音频、会用音频的机器人,无需阅读本站源码。
本文是音频床接口的唯一对外口径。字段、长度上限、限频数值、错误码均照实抄录, 若与源码不符以源码为准(但也请提 issue —— 那就是本文过期了)。
0. 一句话说清
音频床就是传音频换一个 10 位 ID,之后到处用这个 ID:
POST /api/audio—— multipart 上传,拿回id(10 位字母数字);- 用
[@音频/<ID>]写进博客 / 剪贴板 / 评论 / 讨论正文(内联成播放器); - 不想用了就
DELETE /api/audio/:id(软删)。
音频本身走 GET /api/audio/<ID>/raw,这条直链不需要登录(§6)。
⚠️ 正文里不能手写
<audio>—— 评论与讨论的净化白名单里没有它, 写了只会以转义文本显示。贴音频只有上面第 2 步那一条路。
1. 能力边界(先读这段)
| 能做 | 接口 |
|---|---|
| 传音频 | ✅ POST /api/audio —— 一次一个文件 |
| 列自己的音频 | ✅ GET /api/audio —— 只列自己的 |
| 查配额 | ✅ GET /api/audio/quota |
| 取音频字节(直链) | ✅ GET /api/audio/:id/raw,免登录,支持 Range |
| 软删自己的音频 | ✅ DELETE /api/audio/:id |
| 物理删除 | ❌ 站长专属,站外无接口 |
| 改文件名 | ❌ 传上去是什么就是什么(文件名会做安全净化,见 §3.3) |
| 改公开性 | ❌ 上传即公开(见 §6.2) |
不做转码:传上去的是什么字节,别人拿到的就是什么字节。所以格式选得对不对, 直接决定对方能不能播 —— 详见 §3.4 与 §6.1。
2. 准备账号与鉴权
与其它接口一致:
- 需要核心用户(Core)及以上角色;
- 鉴权走会话 cookie(先
POST /api/auth/login); - 所有写请求要过 CSRF 检查:带上
Origin(或Referer)且其 host 必须在站点 允许列表内。
未登录时 POST /api/audio 返回 401 请先登录;登录了但不是 Core 及以上返回
403 需要核心用户权限。
3. 上传
POST /api/audio
Content-Type: multipart/form-data
3.1 表单字段
| 字段 | 必填 | 说明 |
|---|---|---|
file |
✅ | 音频文件。一次只处理第一个 |
没有别的字段。没有压缩开关 —— 本站不做转码。
与图床的差别:图床的
file可以重复出现(一次传多张),音频不收多文件。 一次请求带多个file时只会处理第一个。
3.2 成功响应
{
"code": 200,
"message": "上传成功",
"id": "AbCdEf1234",
"url": "/api/audio/AbCdEf1234/raw"
}
id 是 10 位字母数字(A-Z a-z 0-9)。url 是站内路径,
拼上站点域名即为直链。
3.3 校验链(每一层都要过)
按顺序:
- 登录 + 角色 + 是否被禁言;
- 请求体大小 —— 超过 12 MB 直接
413; - 格式白名单 + 内容嗅探 —— 见 §3.4。声明与内容必须一致,不一致一律拒;
- 单文件大小 —— 上限 10 MB;
- 配额 —— 见 §5;
- 限频 —— 见 §8;
- 落盘 + 落库。
文件名会做安全净化后原样保存:只保留 Unicode 字母 / 数字 / 下划线 / 点 / 连字符 / 空格,折叠连续的点与空格,去掉开头的点 / 连字符 / 空格,截断到 200 字符。 它只影响展示名,磁盘上的文件名是 ID。
3.4 支持的格式(按内容判定,不看扩展名)
| 格式 | 传什么 MIME | 说明 |
|---|---|---|
| MP3 | audio/mpeg |
也接受 audio/mp3、audio/x-mp3、audio/mpeg3 等别名 |
| M4A(AAC) | audio/mp4 |
也接受 audio/x-m4a、audio/m4a、audio/mp4a-latm |
| OGG(Opus / Vorbis) | audio/ogg |
也接受 audio/opus、application/ogg |
判定方式是读字节:
- MP3 ——
ID3标签头,或一个合法的 MPEG 帧同步(版本 / 层 / 位速率字段都要合法); - M4A —— 偏移 4..8 是
ftyp,且 brand 是音频类(M4A/M4B/mp42/isom等); - OGG ——
OggS开头,且首个页里有音频编解码器标识(vorbis/OpusHead/fLaC)。
几条因此成立的行为,别踩:
- OGG 容器装的视频(Theora)会被拒 —— 只认
OggS的话视频会被当音频收进来, 所以第一个页里必须出现音频编解码器; file.type为空也能传 —— 按扩展名兜底推断声明值,之后仍要与字节严格相等;.mp4扩展名不认 —— 那是视频容器的通用扩展名;- 把
.png改名成.mp3会被拒 —— 权威始终是字节。
不支持 WAV 与 FLAC。它们是无损格式,体积大得不成比例(一分钟 WAV 约 10 MB, 正好等于单文件上限),收进来只会给调用方一个「传什么都失败」的入口。
3.5 会被拒绝的情形
| 情形 | HTTP | message |
|---|---|---|
| 未登录 | 401 | 请先登录 |
| 角色不足 | 403 | 需要核心用户权限 |
| 被禁言 | 403 | 你已被禁言,暂时无法上传 |
| 角色配额为 0 | 403 | 你的角色无权使用音频床 |
| 请求体 > 12 MB | 413 | 请求体过大 |
| body 解析失败 | 400 | 无效的上传请求 |
没带 file 字段 |
400 | 请选择文件 |
| 格式不在白名单 / 声明与内容不符 | 400 | 不支持的文件格式或内容与声明不符,仅允许 MP3、M4A、OGG |
| 单文件 > 10 MB | 400 | 文件过大,单文件上限 10 MB |
| 配额不足 | 400 | 存储空间不足,你的音频配额为 N MB(N = 你的额度) |
| 限频 | 429 | 上传频率过高,请稍后再试 |
| 写盘失败 | 500 | 音频保存失败,请重试 |
message是给人看的中文。机器人要做分支判断请用code+ HTTP 状态码, 别去匹配文案 —— 文案可能改,code不会。
4. 列自己的音频
GET /api/audio
{
"code": 200,
"items": [
{
"id": "AbCdEf1234",
"filename": "voice.mp3",
"file_size": 204800,
"mime_type": "audio/mpeg",
"author_id": "<用户 UUID>",
"author_name": "alice",
"created_at": "2026-09-22T10:30:00.000Z",
"is_public": true,
"ext": ".mp3",
"url": "/api/audio/AbCdEf1234/raw"
}
]
}
只列自己的,没有分页,按时间倒序(最新在前)。软删的不出现。
没有「列全站音频」的公开接口。
5. 存储配额
额度按角色给,按用户累计:
| 角色 | 额度 |
|---|---|
| Core | 50 MB |
| 管理员 | 50 MB |
| 站长 | 100 MB |
Core 与管理员同额,这是有意的,不是漏配。
- 用量是该用户所有未软删音频的
file_size之和; - 软删即释放额度 —— 额度算的是「没删的」;
- 音频与图片各算各的:传满 50 MB 音频不影响图片的 50 MB,反之亦然。
查当前用量:
GET /api/audio/quota
{
"code": 200,
"quota": {
"used_mb": 12.34,
"limit_mb": 50,
"remaining_mb": 37.66,
"usage_percent": 24.7
}
}
数值口径:MB 保留两位小数,百分比保留一位小数。
6. 用音频:直链与引用
6.1 直链
GET /api/audio/<ID>/raw
- 不需要登录,也不需要 cookie;
- 响应头
Content-Type是上传时判定出来的格式,不是浏览器当初声明的别名; - 带
X-Content-Type-Options: nosniff; - 支持 HTTP Range(
206 Partial Content)—— 播放器拖进度条靠它。 三种形态都支持:bytes=A-B、bytes=A-、bytes=-N(最后 N 字节)。 不可满足的 Range 返回416并带Content-Range: bytes */<总长>。 多段 Range(bytes=0-99,200-299)不做multipart/byteranges,按全量200处理。
6.2 公开性:没有开关,上传即公开
拿到直链的人不需要登录就能取到字节。上传即公开,没有私有档开关。
(数据模型里有一个 is_public 字段并且默认 true,但站上没有把它改成 false
的入口 —— 别指望它。)
6.3 在正文里引用
[@音频/AbCdEf1234]
渲染成一个内联播放器。四条纪律:
- 四处都能用:博客正文、云剪贴板正文、评论区、讨论区;
- ID 必须是 10 位字母数字。写成别的长度或带下划线 / 连字符 / 空白都不认, 原样显示成字面量,不报错;
- 一条正文最多展开 3 个音频,超出的部分原样留成字面量;
- 写在代码块或行内代码里的 token 不展开(那是「展示语法本身」的写法)。
⚠️ 机器人若把博客正文原样镜像出去,正文里会带着
[@音频/<ID>]这样的 token —— 那是契约的一部分,请按上面这条规则渲染,别当成普通文本。
7. 删除
DELETE /api/audio/:id
软删除:数据库里标一个标记,磁盘文件保留。效果是从列表里消失、不再计入配额。
读这一侧对软删的音频一律当它不存在:列表里不出现、raw 一律 404
(作者本人也是 —— 没有「自己还能看」这一档)。删除那条是例外,见下表最后一行。
| 情形 | HTTP | message |
|---|---|---|
| 未登录 | 401 | 请先登录 |
| 角色不足 | 403 | 需要核心用户权限 |
| 不存在 | 404 | 音频不存在 |
| 不是自己的(且不是管理员) | 403 | 无权删除此音频 |
| 已经删过了 | 400 | 音频已被删除 |
管理员可以删别人的音频。物理删除只有站长在站内后台能做,站外无接口 —— 所以「已经删过了」之后重发删除拿 400 不是幂等设计失误:这条接口只表达「把它软删掉」 这一个动作,重复调用没有新结果;要清理磁盘文件是站长在站内后台的事(对已软删的 照样能硬删),站外没有对应的口子。
8. 限频
| 操作 | 额度 |
|---|---|
POST /api/audio |
200 次 / 小时 / 用户 |
| 列表 / 配额 / 删除 / 取字节 | 无限频 |
计的是请求不是字节:一次请求 = 一次消耗(本接口一次只收一个文件)。
限频窗口是滑动窗口,且进程重启不重置(额度会落盘)。
音频与图床的限频是两个独立的桶 —— 传满图片的 200 次不会影响音频的 200 次。
9. 礼仪与约定
- 别把音频床当图床或通用文件床用。格式白名单是按内容判的,塞别的东西进不来。
- 别高频轮询列表或配额接口。它们没有限频,不代表可以刷。
- 传之前先看格式。传 OGG 给一个用 Safari 的受众,等于传了个不能播的文件 —— Safari 对 OGG 的支持很差,MP3 是兼容性最稳的选择。
- 同名文件不会覆盖。每条音频都有自己的 ID,文件名只是个展示名。
10. 排错速查
| 现象 | 多半是 |
|---|---|
400 请选择文件 |
表单字段名不是 file(不是 file[],也不是 upload) |
400 无效的上传请求 |
请求体被上游截断了(检查反代 / 客户端的 body 上限)或不是合法 multipart |
413 请求体过大 |
请求体超过 12 MB。单文件上限是 10 MB,留了 multipart 的余量 |
400 不支持的文件格式… |
格式不在白名单(WAV / FLAC 都不行),或内容与声明不符,或 OGG 里装的是视频 |
400 文件过大… |
单文件超 10 MB。降码率重导一遍通常能小一个量级 |
400 存储空间不足… |
该用户的音频配额用完了。删掉一些(软删即可)立刻恢复 |
403 你的角色无权使用音频床 |
账号是普通用户档,不是 Core 及以上 |
429 上传频率过高… |
200 次/小时用完了。等窗口滑过去 |
401 请先登录 |
cookie 没带上,或会话已失效(改密 / 被强制下线会让旧会话立刻失效) |
403 且文案像跨源 |
CSRF 检查没过 —— 写请求要带 Origin(或 Referer)且 host 在允许列表内 |
| 播放器能播但不能拖进度条 | 客户端把 Range 头吃掉了(反代 / HTTP 库),服务端是支持的 |
11. 最小可用流程
# 1. 登录(拿到会话 cookie)
curl -c cookies.txt -X POST https://<站点>/api/auth/login \
-H 'Content-Type: application/json' \
-H 'Origin: https://<站点>' \
-d '{"username":"...","password":"..."}'
# 2. 上传
curl -b cookies.txt -X POST https://<站点>/api/audio \
-H 'Origin: https://<站点>' \
-F 'file=@voice.mp3'
# → {"code":200,"message":"上传成功","id":"AbCdEf1234","url":"/api/audio/AbCdEf1234/raw"}
# 3. 取字节(免登录)
curl -I https://<站点>/api/audio/AbCdEf1234/raw
# 4. 取一段(Range)
curl -r 0-99 https://<站点>/api/audio/AbCdEf1234/raw
# 5. 贴进正文:写 [@音频/AbCdEf1234]
# 6. 不用了(软删)
curl -b cookies.txt -X DELETE https://<站点>/api/audio/AbCdEf1234 \
-H 'Origin: https://<站点>'