Files
matrix/README.md
T
Matrix Music Bot Dev 2dd94daa54 docs: README.md 增加 Docker 部署章节(快速启动 / Dockerfile 设计 / 调试)
新增内容(145 行):
  - 快速启动 6 步(config init → login → .env → up -d → logs → down)
  - 首次登录两种方案:
      A. 先在主机登录(推荐)
      B. docker-compose run --rm 容器内登录
  - Dockerfile 设计要点(多阶段 / 层缓存 / strip / tini / 非 root)
  - 镜像版本管理(build-arg + tag/push)
  - 容器内调试命令(exec / version / ffmpeg / yt-dlp / logs)
  - 常见 Docker 问题:
      - crates.io 拉取慢 → rsproxy 镜像源
      - 容器立即退出 → 缺环境变量 / 配置 / data
      - 升级后 crypto.db 不兼容 → 删除重建 + 重新信任

参考 README 系统要求章节了解详细依赖。
2026-07-02 23:55:20 +08:00

656 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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/11x86_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` |
### 验证脚本
```bash
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. 克隆与编译
```bash
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. 创建配置文件
```bash
./target/release/matrix-music-bot.exe config init
# 生成 ./config/bot.toml
```
编辑 `./config/bot.toml`
```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. 首次登录
```bash
# 必须设置 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 设备
这一步**必须做**,否则交叉签名不通过、其他客户端会显示黄色盾牌。
1. 在 Element Web/Desktop 用 `@musicbot:example.cn` 登录
2. 设置 → 设备与会话 → 找到 `matrix-music-bot-v1`
3. 点击"信任"(或选中"Verify all sessions"
### 5. 启动 bot
```bash
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 字符**。
```bash
# 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` | 直传 URLYouTube/B站等 yt-dlp 支持的源) |
| `!queue` | `!q`, `!队列` | 查看当前房间队列 |
| `!np` | `!nowplaying`, `!当前` | 当前正在播放 |
| `!skip` | `!next`, `!跳过` | 跳过当前播放 |
### 房间权限
- **DM**:无需 `@-mention`,直接发命令即可
- **房间**:默认需 `@-mention` bot 才会响应(可在 `[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 二进制(锁版本)、tiniPID 1)。
### 快速启动
```bash
# 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:先在主机登录,再启动容器**(推荐)
```bash
# 在 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:在容器内登录**
```bash
# 启动一次性容器执行 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 确保下载完整
### 镜像版本管理
```bash
# 锁定 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
```
### 容器内手动调试
```bash
# 进入运行中的容器
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 慢。方案:
```bash
# 在 Cargo 配置中加镜像源(项目内 .cargo/config.toml 已配 git-fetch-with-cli=true
# 或临时设环境变量:
CARGO_NET_GIT_FETCH_WITH_CLI=true docker-compose build
```
或编辑 `~/.cargo/config.toml`(主机):
```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`
```bash
# 安装 MinGW gcc
winget install MSYS2.MSYS2
# 在 MSYS2 中:
pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-sqlite3
```
### `cannot find -lsqlite3`
```bash
pacman -S mingw-w64-x86_64-sqlite3
```
### `ffmpeg not found`
确保 `ffmpeg.exe` 在 PATH 中,或在 `[music] ytdlp_path` 中设置绝对路径。
### `yt-dlp not found`
```bash
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 不支持 MSC3967bot 自动建立交叉签名可能失败
### 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 storeOlm/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 Am.audio via Timeline
│ └── url_link.rs Pattern Bv2 stub
├── music/
│ ├── mod.rs MusicSource trait + SearchFacade
│ ├── ytdlp.rs YtDlpBackend(完整)
│ ├── netease.rs NetEaseBackendstub
│ ├── qq.rs QqMusicBackendstub
│ └── selfhosted.rs LocalFsBackendstub
├── downloader.rs yt-dlp 子进程下载
└── worker.rs 每房间 workerresolve → download → transcode → upload
```
### 重新登录(重新生成 secret.bin
```bash
rm data/secret.bin
export MATRIX_BOT_SECRET_PASSPHRASE="..."
./target/release/matrix-music-bot.exe login
```
### 清理 crypto.db(重置 E2EE 状态)
```bash
rm data/crypto.db
# 下次启动会重新建立 crypto store
# 注意:其他客户端需要重新信任 bot 设备
```
### 运行测试
```bash
cargo test # 全部
cargo test commands # 仅 commands 模块
cargo test -- --ignored # 包含需要 yt-dlp/ffmpeg/网络的测试
```
### 添加新音乐源
实现 `MusicSource` trait 即可自动接入:
```rust
// 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/QQM3+ 可实现) |
| 候选仅按 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 音乐)
- 🔮 投票跳过(多用户协同)
---
## 测试
```bash
cargo test # 29 passed + 4 ignored
```
测试覆盖:
- 命令解析(11 测试):DM/房间 mention、缺参、中英文别名
- TOML 配置(2):最小配置解析、示例配置往返
- secret_store(2):加密往返、错误口令拒绝
- music4):AudioFormat 推断、serde 往返
- queue3):入队去重、不同 source_id 不去重、skip
- reaction_store4):emoji 解析、会话记录消费、过期清理
ignored 测试需要 yt-dlp / ffmpeg / 真实网络。
---
## 许可证
待定(项目内未声明)。
---
## 致谢
- [matrix-rust-sdk](https://github.com/matrix-org/matrix-rust-sdk) — Matrix 协议 Rust SDK
- [yt-dlp](https://github.com/yt-dlp/yt-dlp) — 通用视频/音频下载器
- [FFmpeg](https://ffmpeg.org/) — 音频转码
- [Argon2](https://github.com/P-H-C/phc-winner-argon2) — 密钥派生
- [aes-gcm](https://github.com/RustCrypto/AEADs) — 认证加密