5 Commits

Author SHA1 Message Date
Matrix Music Bot Dev 11b484d644 feat: 转码 + 投递(M4:transcoder + delivery/audio_upload)
新增模块:
  src/transcoder.rs      ── ffmpeg 子进程封装(MP3/AAC → stereo Opus 128k)
  src/delivery/mod.rs    ── DeliveryStrategy trait + DeliveryKind + DeliveryReceipt
  src/delivery/audio_upload.rs ── Pattern A 实现(m.audio via Timeline::send)
  src/delivery/url_link.rs     ── Pattern B stub(v2 实现)

依赖新增:mime 0.3.17

transcoder.rs 关键设计:
  - 输入已是 native Opus/WebM → 直接 copy(保留质量 + 节省 CPU)
  - 其他格式 → ffmpeg 子进程转码
  - ffmpeg 参数(与 plan 一致):
      -c:a libopus -b:a 128k -ac 2 -ar 48000 -vbr on
      -f webm -application audio -map_metadata 0
  - 用 tokio::process::Command + tokio::time::timeout
  - 提供 cache_path_for() 生成 <cache>/opus/<source>-<id>.webm

delivery/audio_upload.rs 关键设计:
  - 读取本地 Opus 文件 → client.media().upload(...)
  - 检查 max_upload_bytes(默认 50MB,留余量)
  - 构造 AudioInfo + AudioMessageEventContent(用 MediaSource::Plain(mxc_uri))
  - 经 Timeline::send 投递(SDK 内部 Megolm 加密)
  - DeliveryReceipt.event_id 暂为 None(需要 timeline subscription 跟踪;M5 queue 实现)

遇到的 API 坑(已解决):
  - AudioInfo 是 non_exhaustive 结构体 + 字段全 public,
    需用 AudioInfo::new() + 字段赋值(不能用 struct expression)
  - AudioInfo.duration 是 Option<std::time::Duration>(不是 UInt 秒数)
  - MediaSource 在 matrix_sdk::ruma::events::room::MediaSource(非 message 子模块)
  - AudioMessageEventContent::new(body, source) 只 2 参,info 用 .info(Some(Box::new(...))) 设置
  - SendHandle 的 transaction_id 是私有的;v1 阶段用不到,Receipt.event_id 留 None

测试:cargo test → 21 passed + 4 ignored(需要 ffmpeg / yt-dlp + 网络)
新增测试:
  - transcoder::cache_path_for_sanitizes
  - transcoder::audio_format_decision
  - transcoder::real_transcode_mp3_to_opus(ignored)
  - transcoder::check_ffmpeg_works(ignored)

总测试:21 passed + 4 ignored

E2EE 不变量保持:
   所有出站消息经 Timeline::send(delivery/audio_upload 强制)
   m.audio 消息体(MediaSource::Plain mxc_uri)经 Megolm 加密

后续 M5:!req 端到端(队列 + 下载 + 接入 event_loop)
2026-07-02 21:22:49 +08:00
Matrix Music Bot Dev a2c9904ec3 feat(event_loop): sync + add_event_handler + Timeline::send 加密通道(M2.2/c)
新增模块:src/event_loop.rs

依赖新增:futures-util 0.3
源码增加:#![recursion_limit = "512"](matrix-sdk 类型嵌套很深)

实现要点:
  1. bootstrap::restore 拿到 Client + user_id + device_id
  2. 注册全局 SyncRoomMessageEvent handler(matrix-sdk add_event_handler 闭包模式)
  3. handler 内部:
     - 提取 m.text 文本
     - 跳过 bot 自己发的消息
     - 用 commands::parse 解析(自动处理 mention / DM 区分)
     - 用 commands::render_reply 渲染回复
     - 用 TimelineBuilder::new(room).build() 构造 Timeline
     - 用 timeline.send(RoomMessageEventContent::text_plain(...).into()) 发送
  4. 启动 long-running client.sync()(用 tokio::select! 与 ctrl-c 并发)

E2EE 不变量已实现并强制:
  -  唯一出站通道:Timeline::send(SDK 内部用 Megolm 加密)
  -  绝不使用 Room::send_raw()(明文)
  -  入站消息经 matrix-sdk 解密后是 plaintext,可直接解析

类型挑战(已解决):
  - matrix-sdk 0.18 EventHandler 通过 impl_event_handler! 宏实现,
    要求 FnOnce(Ev, ...) -> Fut 形式,不能包 Pin<Box<>>
  - matrix-sdk-crypto 内部有 !Send 类型,所以 client.sync() 不能 tokio::spawn,
    必须在当前 task 用 tokio::select! 与 ctrl-c 并发
  - 嵌套 async 闭包触发 rustc 深度限制,需要 #![recursion_limit = "512"]

cmd_run 改造:
  - 同样从 MATRIX_BOT_SECRET_PASSPHRASE 环境变量读取 passphrase
  - 用 tokio multi-thread runtime 跑 event_loop::run

CLI 实测(fake config + 缺 secret.bin):
  - 无 config → 友好错误
  - 有 config + 缺 secret.bin → '请先运行 login' 错误
  - 完整流程需真实 homeserver 才能跑通

测试:cargo test → 15 passed(11 commands + 2 config + 2 secret_store)
2026-07-02 21:03:56 +08:00
Matrix Music Bot Dev ecc40beed8 feat(secret_store): Argon2id + AES-256-GCM 加密的会话密钥存储(M1.3a)
依赖新增:
  - aes-gcm 0.11
  - argon2 0.5.3
  - getrandom 0.3 (std feature)
  - base64 0.22.1
  - serde_json 1.0
  - zeroize 1.9
  - rand_core (尝试后移除:rand 0.10 / 0.9 的 OsRng API 都复杂,改为更底层的 getrandom)

文件格式(JSON 包装):
  {
    "version": 1,
    "kdf": "argon2id",
    "salt_b64": "...",     // 16 bytes
    "nonce_b64": "...",    // 12 bytes
    "ciphertext_b64": "..."  // AES-256-GCM(plaintext, AAD="matrix-music-bot secret v1")
  }

安全参数:
  - KDF: Argon2id (m=19456 KiB, t=2, p=1) — OWASP 推荐
  - 加密: AES-256-GCM(认证加密,任何篡改都会被检测)
  - AAD: 标识文件版本与 KDF,防止 ciphertext 重放到不同上下文
  - 每次加密随机 salt + nonce,确保同样明文产生不同 ciphertext
  - 写入采用 .bin.tmp → rename 原子操作,避免半写状态

API:
  save(path, &secrets, passphrase) -> AppResult<()>
  load(path, passphrase) -> AppResult<SessionSecrets>
  exists(path) -> bool

SessionSecrets 包含 access_token / device_id / user_id 三个字段,
实现 Zeroize on drop。

测试:
  cargo test → 4 passed
    - secret_store::roundtrip_via_disk: 加密 + 解密往返,错误口令被 AES-GCM 拒绝
    - secret_store::ciphertext_differs_each_time: 同样明文两次加密 ciphertext 不同

后续 M1.3b:在 src/bootstrap.rs 中调用 matrix-sdk 的 Client::builder() + login_username,
拿到 access_token 后调 secret_store::save() 写入 ./data/secret.bin。
2026-07-02 19:50:56 +08:00
Matrix Music Bot Dev 19e724444b feat: 实现 config + clap CLI + 错误处理(M1.2)
新增依赖(与 matrix-sdk 0.18.0 兼容):
  - tokio 1.52 (full)
  - clap 4.6 (derive + env)
  - serde 1.0 (derive) + toml 1.1
  - thiserror 2.0 + anyhow 1.0
  - tracing 0.1 + tracing-subscriber 0.3 (env-filter)
  - async-trait 0.1

推迟到对应 milestone(M3 / M5)添加以避免 libsqlite3-sys 与
matrix-sdk-sqlite 冲突:rusqlite、reqwest。详见 Cargo.toml 注释。

新增模块:
  src/config.rs  ── AppConfig 结构(Homeserver/Bot/Data/Commands/Crypto/Music)+ TOML 加载/序列化 + 自动发现路径
  src/error.rs   ── AppError 枚举(thiserror)+ AppResult 别名
  src/main.rs    ── clap CLI 骨架:login / run / config / version 子命令

CLI 子命令状态:
  - version      打印版本 + matrix-sdk 版本
  - config init  在 ./config/bot.toml 生成示例配置
  - config path  打印当前配置文件路径
  - config print  打印当前配置
  - login       ⚠️ M1.3 占位(返回错误)
  - run         ⚠️ M2 占位(读取配置后返回错误)

配置默认值(plan 已确认的选型):
  - [crypto] auto_enable_cross_signing = true
  - [commands] prefix = !, mention_required_in_rooms = true
  - [music] ytdlp_path = 'yt-dlp'(PATH 中查找),search_limit = 5

gitignore 增补:排除 /config/bot.toml(操作员真实配置含凭证)

测试:
  - cargo build 通过(仅 dead_code warning,未使用的 AppError 变体将在后续 milestone 引用)
  - cargo test 2 passed(最小配置解析 + 示例配置往返)
  - CLI 实测:version / config init / config print / login / run 均按预期

后续 M1.3:在 src/bootstrap.rs 实现 matrix-sdk Client::builder() + login_username
+ secret_store 加密保存 access_token/device_id。
2026-07-02 01:35:54 +08:00
Matrix Music Bot Dev 17eb51e215 chore: 初始化 Cargo 项目骨架与构建验证
工具链:rustup + stable-x86_64-pc-windows-gnu + MinGW gcc 链接器

包含内容:
- Cargo.toml(edition 2024,name=matrix-music-bot)
- Cargo.lock(dev profile 验证通过)
- src/main.rs(hello world 占位,已 cargo build 通过)
- .cargo/config.toml(项目级 cargo 配置,指定 MinGW gcc 为链接器)
- .gitignore 增补:排除项目根的 plan 副本(正式版在 ~/.claude/plans/)

构建验证:cargo build 在 MinGW gcc 链接器下成功完成(1.56s)

后续 M1.2 将添加:clap(CLI 解析)+ serde/toml(配置文件)+ thiserror/anyhow
+ tracing(日志)+ tokio(async)+ matrix-sdk(含 E2EE crypto 与 UI timeline)
2026-07-02 01:25:33 +08:00