# 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 ` 直传 | | ✅ 用户两步选择 | 搜索后发带 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` | ### 验证脚本 ```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(YouTube/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 二进制(锁版本)、tini(PID 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 不支持 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) ```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, AppError> { /* ... */ } async fn resolve(&self, candidate: &SongCandidate) -> Result { /* ... */ } } ``` 在 `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 音乐) - 🔮 投票跳过(多用户协同) --- ## 测试 ```bash 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](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) — 认证加密