变更:
- .gitea/README.md 记录 Gitea 实例已有的 hermes-runner
(标签 ubuntu-22.04,全局),避免后续再猜错标签
- build-image.yml 顶部注释更新:标明 runner 实际标签
- README 注册命令示例从 --labels ubuntu-latest:docker 改为
--labels ubuntu-22.04(与实际匹配)
运维经验:act_runner --labels 标签必须与 workflow runs-on 一致,
否则 job 会一直显示"等待 runner"状态。
Matrix 点歌机器人 (matrix-music-bot)
一个运行在 Matrix(兼容 Element / Element Call)房间中的点歌机器人,使用
matrix-rust-sdk 实现 端到端加密 (E2EE) 房间内搜索、点歌与音频投递。
v1 已完成基础闭环:登录 → 加入加密房间 → 接收
!req/!req-url命令 → yt-dlp 搜索 → 用户通过 emoji 反应选择 → 后台下载 → ffmpeg 转码 → 通过Timeline::send投递加密m.audio。
目录
功能特性
| 能力 | 说明 |
|---|---|
| ✅ E2EE 房间 | 完全支持 Megolm 加密(聊天消息 + 音频文件) |
| ✅ Element Call 兼容 | MSC3401 call.member 状态事件观测(不参与通话媒体) |
| ✅ 多种点歌入口 | !req <关键词> 搜索 + !req-url <URL> 直传 |
| ✅ 用户两步选择 | 搜索后发带 1️⃣~5️⃣ emoji 的候选列表,点击 emoji 选定 |
| ✅ yt-dlp 通用后端 | 支持 YouTube / B 站 / SoundCloud 等 |
| ✅ 立体声 Opus 128k | 通过 ffmpeg 转码为 audio/ogg; codecs=opus |
| ✅ 每房间独立队列 | tokio mpsc 通道,房间间互不阻塞 |
| ✅ 队列去重 | 同 (source_kind, source_id) 同房间去重 |
| ✅ 反应超时清理 | 60 秒未选择自动取消搜索会话 |
| ✅ 加密会话存储 | access_token 用 Argon2id + AES-256-GCM 加密保存到 data/secret.bin |
| ✅ 自动交叉签名 | homeserver 支持 MSC3967 时自动建立交叉签名 |
架构概览
┌─────────────────────────────────────────────────────────┐
│ matrix-music-bot (Rust binary) │
│ │
│ main.rs (CLI) │
│ ├─ login → bootstrap → secret_store::save │
│ ├─ run → bootstrap::restore → event_loop │
│ └─ config → init / path / print │
│ │
│ event_loop.rs │
│ ├─ client.sync() ──────────────────┐ │
│ ├─ add_event_handler (m.room.message) │
│ ├─ add_event_handler (m.reaction) │ Megolm 解密 │
│ ├─ add_event_handler (MSC3401 state)│ │
│ └─ commands::parse → 派发 │
│ │
│ worker.rs (每房间一个) │
│ resolve → downloader (yt-dlp) → transcoder (ffmpeg) │
│ → delivery::audio_upload (m.audio via Timeline::send) │
│ │
│ 数据存储: │
│ data/crypto.db matrix-sdk crypto (Olm/Megolm) │
│ data/bot.db (预留,应用状态;v1 暂用内存) │
│ data/secret.bin AES-256-GCM 加密的 access_token │
│ data/cache/opus 转码后的 Opus/WebM │
└─────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
Matrix homeserver yt-dlp ffmpeg
(E2EE Megolm) (HTTP 下载) (音频转码)
详细架构见 C:\Users\Administrator\.claude\plans\lexical-meandering-lighthouse.md。
系统要求
操作系统
- Windows 10/11(x86_64)
- Linux(理论上可编译,需把
Cargo.toml中.cargo/config.toml的linker = "C:/msys64/mingw64/bin/gcc.exe"改为本地 gcc)
必备外部工具
| 工具 | 版本要求 | 安装方式(Windows) | 验证命令 |
|---|---|---|---|
| Rust | stable-x86_64-pc-windows-gnu ≥ 1.96 | winget install Rustlang.Rustup |
rustc --version |
| MinGW gcc | ≥ 13(链接器) | winget install MSYS2.MSYS2 后 pacman -S mingw-w64-x86_64-gcc |
gcc --version |
| SQLite3 | 系统库(矩阵 SDK 链接) | pacman -S mingw-w64-x86_64-sqlite3 |
系统 PATH 中 |
| FFmpeg | ≥ 5.0(音频转码) | https://www.gyan.dev/ffmpeg/builds/ 或 choco/scoop | ffmpeg -version |
| yt-dlp | ≥ 2024(音乐下载) | pip install yt-dlp 或下载 standalone binary |
yt-dlp --version |
验证脚本
rustc --version # rustc 1.96.x
cargo --version # cargo 1.96.x
gcc --version # gcc.exe (Rev built by MSYS2 project)
ffmpeg -version # ffmpeg version 8.x
yt-dlp --version # 2026.x.x
sqlite3 --version # 3.x.x
快速部署
1. 克隆与编译
git clone http://192.168.6.200:3080/1076code/matrix.git
cd matrix
# Debug 编译(开发用)
cargo build
# Release 编译(生产用)
cargo build --release
二进制产物:
- Debug:
target/x86_64-pc-windows-gnu/debug/matrix-music-bot.exe - Release:
target/x86_64-pc-windows-gnu/release/matrix-music-bot.exe
2. 创建配置文件
./target/release/matrix-music-bot.exe config init
# 生成 ./config/bot.toml
编辑 ./config/bot.toml:
[homeserver]
url = "https://matrix.example.cn" # ← 改成你的 homeserver
[bot]
user_id = "@musicbot:example.cn" # ← 改成 bot 账户 MXID
device_name = "matrix-music-bot-v1"
admin_user_ids = ["@owner:example.cn"] # ← 可选
[data]
dir = "./data" # 默认即可
[commands]
prefix = "!"
mention_required_in_rooms = true # 房间中需 @-mention bot
allow_dm = true
[crypto]
auto_enable_cross_signing = true # 自动建立交叉签名
auto_enable_key_backup = false # bot 不参与密钥备份
[music]
ytdlp_path = "yt-dlp" # yt-dlp 路径或 PATH 中
search_limit = 5 # reactions 候选数
search_timeout_secs = 30 # 搜索超时
3. 首次登录
# 必须设置 passphrase(用于加密 secret.bin 中的 access_token)
export MATRIX_BOT_SECRET_PASSPHRASE="your-strong-passphrase-min-8-chars"
./target/release/matrix-music-bot.exe login \
--user-id "@musicbot:example.cn" \
--homeserver "https://matrix.example.cn"
# CLI 会提示输入密码(也可通过 MATRIX_BOT_PASSWORD 环境变量)
成功后输出类似:
╭─────────────────────────────────────────────────────────────╮
│ ✅ 登录成功 │
│ │
│ user_id: @musicbot:example.cn │
│ device_id: AAAAAAAAAA │
│ homeserver: https://matrix.example.cn │
╰─────────────────────────────────────────────────────────────╯
📋 下一步(重要):
在 Element 桌面客户端登录 @musicbot:example.cn,
进入 设置 → 设备 → 找到 device_id `AAAAAAAAAA`
点击 "信任"
4. Element 客户端信任 bot 设备
这一步必须做,否则交叉签名不通过、其他客户端会显示黄色盾牌。
- 在 Element Web/Desktop 用
@musicbot:example.cn登录 - 设置 → 设备与会话 → 找到
matrix-music-bot-v1 - 点击"信任"(或选中"Verify all sessions")
5. 启动 bot
export MATRIX_BOT_SECRET_PASSPHRASE="your-strong-passphrase-min-8-chars"
./target/release/matrix-music-bot.exe run
预期日志:
2026-07-02T13:00:00 INFO 恢复登录成功 user_id=@musicbot:example.cn
2026-07-02T13:00:01 INFO 开始初始 sync_once...
2026-07-02T13:00:05 INFO 初始 sync 完成
2026-07-02T13:00:05 INFO 已注册 m.room.message 事件 handler
2026-07-02T13:00:05 INFO 已注册 MSC3401 call.member state event handler
2026-07-02T13:00:05 INFO 开始 long-running sync(按 Ctrl-C 停止)
6. 加入加密房间
在 Element 中把 bot 邀请进一个加密房间。bot 会自动发现并启动 worker。
7. 测试点歌
在房间中发送:
@musicbot:example.cn !help
@musicbot:example.cn !req 周杰伦 晴天
@musicbot:example.cn !req-url https://www.youtube.com/watch?v=xxxxxxxxxxx
配置说明
MATRIX_BOT_SECRET_PASSPHRASE 环境变量(必填)
用于加密 data/secret.bin 中的 access_token + device_id。必须 ≥ 8 字符。
# Linux/macOS
export MATRIX_BOT_SECRET_PASSPHRASE="correct-horse-battery-staple"
# Windows cmd
set MATRIX_BOT_SECRET_PASSPHRASE=correct-horse-battery-staple
# Windows PowerShell
$env:MATRIX_BOT_SECRET_PASSPHRASE = "correct-horse-battery-staple"
如果忘记此环境变量,bot 会输出明确错误:
ERROR 密钥存储错误: 未设置 MATRIX_BOT_SECRET_PASSPHRASE 环境变量
MATRIX_BOT_PASSWORD 环境变量(仅 login 时需要)
仅在交互式登录时使用。可选——不设置则 login 提示输入密码。
命令列表
| 命令 | 别名 | 说明 |
|---|---|---|
!help |
!h, !? |
显示帮助 |
!req <关键词> |
!request, !点歌, !来一首 |
关键词搜索(用 reactions 选) |
!req-url <URL> |
!url |
直传 URL(YouTube/B站等 yt-dlp 支持的源) |
!queue |
!q, !队列 |
查看当前房间队列 |
!np |
!nowplaying, !当前 |
当前正在播放 |
!skip |
!next, !跳过 |
跳过当前播放 |
房间权限
- DM:无需
@-mention,直接发命令即可 - 房间:默认需
@-mentionbot 才会响应(可在[commands]配置关闭)
reactions 选择流程
@musicbot !req 周杰伦 晴天
↓
bot: 🔍 搜索「周杰伦 晴天」找到 5 个结果,点击 emoji 选择:
1️⃣ 晴天 - 周杰伦 (3:42)
2️⃣ 晴天 (Live) - 周杰伦 (5:18)
3️⃣ 晴天 (Piano) - 周杰伦 (4:01)
4️⃣ 晴天 (Remix) - 周杰伦 (3:30)
5️⃣ 晴天故事 - 周杰伦 (4:12)
⏰ 60 秒后未选择自动取消
↓
你:点击 2️⃣(emoji 反应)
↓
bot: ✅ 已加入队列:晴天 (Live) - 周杰伦
bot: 🎵 [内嵌音频播放器] ← Element 内显示,可直接播放
Docker 部署(推荐用于生产服务器)
仓库自带 Dockerfile(多阶段 Debian bookworm-slim 构建)+ docker-compose.yml(单服务编排)。
镜像内含 Rust 工具链、ffmpeg、yt-dlp standalone 二进制(锁版本)、tini(PID 1)。
快速启动
# 1. 准备配置
./target/release/matrix-music-bot.exe config init # 或在 Linux 上:./matrix-music-bot config init
# 编辑 ./config/bot.toml
# 2. 首次登录(如未在本机登录过)
./target/release/matrix-music-bot.exe login
# 或在 docker 内执行(见下方"首次登录")
# 3. 创建 .env
cp .env.example .env
# 编辑 .env,至少设置 MATRIX_BOT_SECRET_PASSPHRASE
# 4. 启动容器
docker-compose up -d --build
# 5. 查看日志
docker-compose logs -f matrix-music-bot
# 6. 停止
docker-compose down
首次登录(如果未在本机登录过)
两种方案:
方案 A:先在主机登录,再启动容器(推荐)
# 在 Linux 主机上 build 并登录
cargo build --release
MATRIX_BOT_SECRET_PASSPHRASE="..." ./target/release/matrix-music-bot login
# 登录成功后 data/secret.bin 与 data/crypto.db 已生成
# 启动容器时会自动用这些文件
docker-compose up -d
方案 B:在容器内登录
# 启动一次性容器执行 login
docker-compose run --rm matrix-music-bot login
# 按提示输入 homeserver / user_id / password
# 完成登录后退出,secret.bin 与 crypto.db 已写入 ./data/
docker-compose up -d
Dockerfile 设计要点
- 多阶段:builder=
rust:1.96-bookworm(约 1.5 GB),runtime=debian:bookworm-slim(约 400 MB 含 ffmpeg) - 层缓存优化:先复制 Cargo.toml 预编译 deps,再复制 src 编译
- strip 二进制:runtime 镜像中减小可执行文件体积
- 非 root 运行:
bot:bot用户 - tini 作为 PID 1:正确转发 SIGTERM/SIGINT 给 bot(避免僵尸进程)
- 健康检查:仅检测进程存活(bot 无内置 health endpoint)
- yt-dlp checksum 校验:用 ADD --checksum=sha256 确保下载完整
镜像版本管理
# 锁定 yt-dlp 版本(构建参数)
docker-compose build --build-arg YTDLP_VERSION=2026.03.17
# 推送镜像到 registry
docker tag matrix-music-bot:latest your-registry/matrix-music-bot:v0.1.0
docker push your-registry/matrix-music-bot:v0.1.0
容器内手动调试
# 进入运行中的容器
docker-compose exec matrix-music-bot /bin/bash
# 查看版本
docker-compose exec matrix-music-bot matrix-music-bot version
# 检查 ffmpeg / yt-dlp
docker-compose exec matrix-music-bot ffmpeg -version
docker-compose exec matrix-music-bot yt-dlp --version
# 查看日志
docker-compose logs --tail=100 matrix-music-bot
常见 Docker 问题
镜像构建失败:failed to fetch
国内服务器拉取 crates.io 慢。方案:
# 在 Cargo 配置中加镜像源(项目内 .cargo/config.toml 已配 git-fetch-with-cli=true)
# 或临时设环境变量:
CARGO_NET_GIT_FETCH_WITH_CLI=true docker-compose build
或编辑 ~/.cargo/config.toml(主机):
[source.crates-io]
replace-with = "rsproxy-sparse"
[source.rsproxy-sparse]
registry = "sparse+https://rsproxy.cn/index/"
容器启动后立即退出
查看日志:docker-compose logs matrix-music-bot。常见原因:
MATRIX_BOT_SECRET_PASSPHRASE未设置 →Environment块报错退出./config/bot.toml不存在 → 用config init生成./data/为空且未在主机登录过 → 用方案 A 或 B 先登录
升级 bot 后旧配置不兼容
data/crypto.db 是 matrix-sdk 的 crypto store,升级 bot 版本可能导致 schema 不匹配。
如果 bot 启动后 sync 异常,删除 data/crypto.db 重启(需要重新在 Element 信任 bot 设备)。
故障排查
登录失败:error sending request
- 检查
[homeserver] url是否正确 - 检查 bot 账户密码是否正确
- 检查 homeserver 是否能从 bot 所在服务器访问
linker 'gcc.exe' not found
# 安装 MinGW gcc
winget install MSYS2.MSYS2
# 在 MSYS2 中:
pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-sqlite3
cannot find -lsqlite3
pacman -S mingw-w64-x86_64-sqlite3
ffmpeg not found
确保 ffmpeg.exe 在 PATH 中,或在 [music] ytdlp_path 中设置绝对路径。
yt-dlp not found
pip install yt-dlp
# 或下载 standalone exe 放到 PATH 中
反应选择不生效
v1 妥协:SendHandle 的 transaction_id 是私有的,reactions 监听使用占位 event_id,
可能匹配不到对应的搜索会话。M8 修复方向是通过 timeline 订阅事件→匹配
transaction_id 与真实 event_id。
临时方案:使用 !req-url(不走 reactions)作为可靠路径。
UnableToDecrypt 警告
- bot 加入加密房间后需要几秒同步 room keys
- 检查 bot 账户的设备是否在 Element 中被信任
- 若 homeserver 不支持 MSC3967,bot 自动建立交叉签名可能失败
CPU 占用高 / 卡顿
- 减少
[music] search_limit(默认 5) - 检查 yt-dlp 是否在并发拉取(搜索时短暂占用)
开发指南
项目结构
matrix-music-bot/
├── Cargo.toml 依赖
├── .cargo/config.toml MinGW gcc 链接器配置
├── config/bot.toml 操作员配置(不进 git)
├── data/
│ ├── crypto.db matrix-sdk crypto store(Olm/Megolm)
│ ├── secret.bin AES-256-GCM 加密的 access_token
│ └── cache/opus/ 转码缓存
└── src/
├── main.rs CLI 入口 + tokio runtime
├── config.rs AppConfig 结构 + TOML 加载
├── error.rs AppError 枚举(thiserror)
├── secret_store.rs Argon2id + AES-256-GCM
├── client.rs matrix-sdk Client 构造
├── bootstrap.rs login / restore
├── commands.rs !req / !queue 等命令解析
├── event_loop.rs sync + add_event_handler + Timeline 加密通道
├── queue.rs 队列(in-memory + 去重)
├── reaction_store.rs reaction 选择存储
├── rtc_handler.rs MSC3401 call.member 状态事件
├── transcoder.rs ffmpeg 子进程 → Opus 128k
├── delivery/
│ ├── mod.rs DeliveryStrategy trait
│ ├── audio_upload.rs Pattern A(m.audio via Timeline)
│ └── url_link.rs Pattern B(v2 stub)
├── music/
│ ├── mod.rs MusicSource trait + SearchFacade
│ ├── ytdlp.rs YtDlpBackend(完整)
│ ├── netease.rs NetEaseBackend(stub)
│ ├── qq.rs QqMusicBackend(stub)
│ └── selfhosted.rs LocalFsBackend(stub)
├── downloader.rs yt-dlp 子进程下载
└── worker.rs 每房间 worker(resolve → download → transcode → upload)
重新登录(重新生成 secret.bin)
rm data/secret.bin
export MATRIX_BOT_SECRET_PASSPHRASE="..."
./target/release/matrix-music-bot.exe login
清理 crypto.db(重置 E2EE 状态)
rm data/crypto.db
# 下次启动会重新建立 crypto store
# 注意:其他客户端需要重新信任 bot 设备
运行测试
cargo test # 全部
cargo test commands # 仅 commands 模块
cargo test -- --ignored # 包含需要 yt-dlp/ffmpeg/网络的测试
添加新音乐源
实现 MusicSource trait 即可自动接入:
// src/music/mysource.rs
use crate::music::{MusicSource, SongCandidate, ResolvedSong};
pub struct MySource { /* ... */ }
#[async_trait]
impl MusicSource for MySource {
fn kind(&self) -> &'static str { "mysource" }
fn display_name(&self) -> &'static str { "我的音乐源" }
async fn search(&self, query: &str, limit: u8) -> Result<Vec<SongCandidate>, AppError> { /* ... */ }
async fn resolve(&self, candidate: &SongCandidate) -> Result<ResolvedSong, AppError> { /* ... */ }
}
在 src/event_loop.rs::build_facade() 中加入你的 backend。
已知限制
| 限制 | 原因 | 影响 | 计划修复 |
|---|---|---|---|
| event_id 跟踪用占位 | SendHandle.transaction_id 私有 |
reactions 选择 v1 可能不生效 | M8: timeline subscription 匹配 |
| 队列进程重启清空 | rusqlite 与 matrix-sdk-sqlite 共享 libsqlite3-sys 冲突 | 重启后未完成的歌曲丢失 | v2:解决链接冲突后用 SqliteQueue |
| HEAD 探测未实现 | 未引入 reqwest | 上传前无法预知大小 | M5+ 加 reqwest |
| 不参与通话媒体 | v1 范围外(需要 matrix_sdk::rtc + livekit-rs) | 不能在 Element Call 中"唱歌" | v2 |
| 国内 YouTube 不稳定 | 网络限制 | 部分搜索/下载失败 | fallback 到 NetEase/QQ(M3+ 可实现) |
| 候选仅按 title+artist 去重 | 简单实现 | 不同版本可能被错误合并 | v2 加 (duration, album) 二次校验 |
路线图
v1(已完成)
- ✅ M1 骨架 + 配置 + 加密 secret_store
- ✅ M2 命令解析 + E2EE Timeline 加密通道
- ✅ M3 MusicSource trait + yt-dlp backend
- ✅ M4 转码 + 投递
- ✅ M5 端到端(!req/!req-url/!queue/!np/!skip)
- ✅ M6 Element Call 兼容(MSC3401 状态事件)
- ✅ M7 reactions 两步选择
v1.x(可选优化)
- ⏳ M8: event_id 跟踪修复(reactions 选择真实可用)
- ⏳ M9: NetEase / QQ 音乐 backend 完整实现
- ⏳ M10: 队列持久化(解决 libsqlite3-sys 冲突)
- ⏳ M11: Pattern B(URL 链接兜底,当文件过大时降级)
- ⏳ M12: HEAD 探测(reqwest 集成)
v2(未来)
- 🔮 bot 作为 Element Call 参与者(matrix_sdk::rtc + livekit-rs)
- 🔮 多语言 NLP 解析(自然语言点歌)
- 🔮 用户点歌历史 + 收藏夹
- 🔮 歌单导入(网易云 / QQ 音乐)
- 🔮 投票跳过(多用户协同)
测试
cargo test # 29 passed + 4 ignored
测试覆盖:
- 命令解析(11 测试):DM/房间 mention、缺参、中英文别名
- TOML 配置(2):最小配置解析、示例配置往返
- secret_store(2):加密往返、错误口令拒绝
- music(4):AudioFormat 推断、serde 往返
- queue(3):入队去重、不同 source_id 不去重、skip
- reaction_store(4):emoji 解析、会话记录消费、过期清理
ignored 测试需要 yt-dlp / ffmpeg / 真实网络。
许可证
待定(项目内未声明)。
致谢
- matrix-rust-sdk — Matrix 协议 Rust SDK
- yt-dlp — 通用视频/音频下载器
- FFmpeg — 音频转码
- Argon2 — 密钥派生
- aes-gcm — 认证加密