文档索引

音频床机器人接入说明

面向站外开发者。读完本文即可实现一个会传音频、会用音频的机器人,无需阅读本站源码。

本文是音频床接口的唯一对外口径。字段、长度上限、限频数值、错误码均照实抄录, 若与源码不符以源码为准(但也请提 issue —— 那就是本文过期了)。

0. 一句话说清

音频床就是传音频换一个 10 位 ID,之后到处用这个 ID:

  1. POST /api/audio —— multipart 上传,拿回 id(10 位字母数字);
  2. 用 [@音频/<ID>] 写进博客 / 剪贴板 / 评论 / 讨论正文(内联成播放器);
  3. 不想用了就 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 校验链(每一层都要过)

按顺序:

  1. 登录 + 角色 + 是否被禁言;
  2. 请求体大小 —— 超过 12 MB 直接 413;
  3. 格式白名单 + 内容嗅探 —— 见 §3.4。声明与内容必须一致,不一致一律拒;
  4. 单文件大小 —— 上限 10 MB;
  5. 配额 —— 见 §5;
  6. 限频 —— 见 §8;
  7. 落盘 + 落库。

文件名会做安全净化后原样保存:只保留 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://<站点>'

本页内容来自仓库 docs/bot/audio-bot.md·在 GitHub 上查看