30 Commits

Author SHA1 Message Date
Matrix Music Bot Dev aedaa2fdf0 Merge branch 'master' of http://192.168.6.200:3080/1076code/matrix
build-and-push-image / build-push (push) Failing after 16m31s
build-and-push-image / vulnerability-scan (push) Has been skipped
2026-07-03 01:06:07 +08:00
luck1076 78e7afe0fc 更新 .gitea/workflows/build-image.yml
build-and-push-image / build-push (push) Has been cancelled
build-and-push-image / vulnerability-scan (push) Has been cancelled
2026-07-03 01:04:53 +08:00
Matrix Music Bot Dev 1bf7a39f0f ci: install-docker.sh 脚本 + .gitea/README.md 改 host 部署指南
用户决定 runner 用 host 部署(不是 Docker 容器)。

变更:
  scripts/install-docker.sh (新增, 140 行)
    - 一键装 Docker Engine + BuildKit + containerd
    - 用阿里云镜像源替代 Docker 官方源(国内快)
    - 自动 systemctl enable --now docker
    - 把当前用户加入 docker 组(免 sudo)
    - 验证 docker / buildx / pull hello-world
    - 卸载指南(回滚)

  .gitea/README.md (修改 Runner 安装章节)
    - 移除"在 Docker 中跑 runner"流程
    - 改为 host 部署 6 步流程:
      1. 下载 act_runner
      2. 装 Docker(用 install-docker.sh)
      3. 配国内镜像(用 setup-runner-mirrors.sh)
      4. 注册 runner
      5. 启动 act_runner daemon
      6. (推荐)systemd 托管
    - 明确"不推荐在 Docker 容器里跑 runner"
    - 明确 Docker 20.10+ + BuildKit 是硬性要求

为什么 host 部署更好(对比 DinD / docker.sock mount):
  - 不需要特权模式(DinD)或突破容器隔离(socket mount)
  - Docker 缓存、镜像、卷都和宿主机共享,运维简单
  - act_runner 在 host 跑 action 时直接用宿主 docker daemon
  - 性能更好(无嵌套虚拟化层)

部署 hermes-runner 的完整流程:
  1. curl + chmod act_runner 二进制
  2. sudo ./scripts/install-docker.sh
  3. sudo ./scripts/setup-runner-mirrors.sh
  4. act_runner register --labels ubuntu-22.04 ...
  5. 配置 systemd 服务
  6. 推送仓库触发 workflow 验证
2026-07-03 00:52:39 +08:00
Matrix Music Bot Dev 6ec609b1ce perf(国内网络): runner 镜像加速配置 + Dockerfile cargo 镜像
按用户全选 4 个加速维度实施:

1. Docker 镜像加速(最大影响面)
   - scripts/setup-runner-mirrors.sh 写入 /etc/docker/daemon.json
   - registry-mirrors: docker.m.daocloud.io + ghcr.nju.edu.cn
   - 影响所有 docker pull:actions / base images / Gitea registry

2. crates.io 镜像
   - .cargo/config-docker.toml(与 .cargo/config.toml 区分,后者用于 Windows 本地开发)
   - rsproxy.cn 镜像(rustcc 维护,国内最稳)
   - Dockerfile COPY 此文件到 /root/.cargo/config.toml(在 cargo build 前)

3. GitHub git clone 加速
   - scripts/setup-runner-mirrors.sh 自动配置 git config --global
   - url.https://ghproxy.com/https://github.com.insteadOf https://github.com

4. 优先做 Docker 镜像加速(4 = 1 的子集,重复)

setup-runner-mirrors.sh 特性:
  - 自动备份原配置(.bak)
  - 用 python3 合并 daemon.json(保留用户已有配置)
  - 重启 docker 后验证 Registry Mirrors
  - 测试 hello-world pull 验证生效
  - --undo 回滚模式

镜像源选型理由:
  - docker.m.daocloud.io:DaoCloud 维护,最稳
  - ghcr.nju.edu.cn:NJU CTF 团队维护的 GitHub Container Registry 镜像
  - rsproxy.cn:rustcc 维护,sparse 协议兼容
  - ghproxy.com:通用 GitHub 镜像

使用方法(runner 上):
  sudo ./scripts/setup-runner-mirrors.sh
  sudo ./scripts/setup-runner-mirrors.sh --undo  # 回滚

下一步:处理 runner 缺 docker 能力的问题
2026-07-03 00:45:25 +08:00
luck1076 ebfd67330f 更新 .gitea/workflows/build-image.yml
build-and-push-image / build-push (push) Failing after 19m37s
build-and-push-image / vulnerability-scan (push) Has been skipped
2026-07-03 00:39:58 +08:00
Matrix Music Bot Dev a22d9578fb ci(perf): runner 本地预拉 action 镜像加速 Set up job
build-and-push-image / build-push (push) Has been cancelled
build-and-push-image / vulnerability-scan (push) Has been cancelled
问题:workflow "Set up job" 步骤非常缓慢
根因:每个 GitHub Actions step 都是 Docker 容器,需从 Docker Hub 拉镜像
      国内网络拉 Docker Hub 特别慢(甚至失败)

解决方案:
  scripts/prepull-actions.sh
    - 列出 build-image.yml 用到的所有 Docker 镜像
    - xargs -P 4 并行拉取
    - 提供验证命令 + cron 清理建议

更新 workflow 用的镜像列表:
  - docker/actions/setup-buildx:v3
  - docker/actions/login:v3
  - docker/actions/metadata:v5
  - docker/actions/build-push:v5
  - aquasec/trivy-action:master
  - moby/buildkit:v0.13.0

非 Docker action(actions/checkout / dtolnay/rust-toolchain / Swatinem/rust-cache)
是内置脚本,不需预拉。

README 新增"性能优化"章节:
  - 根因分析
  - 使用步骤
  - 清理策略 cron
  - 进一步优化建议(BuildKit cache / cargo 缓存 / 多架构)

使用方法:
  chmod +x scripts/prepull-actions.sh
  sudo ./scripts/prepull-actions.sh
2026-07-03 00:33:49 +08:00
Matrix Music Bot Dev 725c6b1d65 docs: .gitea 文档记录 hermes-runner 标签 + workflow 同步注释
build-and-push-image / build-push (push) Failing after 17m29s
build-and-push-image / vulnerability-scan (push) Has been skipped
变更:
  - .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"状态。
2026-07-03 00:16:47 +08:00
Matrix Music Bot Dev 89ee450d6e ci: 修复 runner 标签(self-hosted → ubuntu-22.04)
Gitea 管理 UI 显示 runner 信息:
  名称: hermes-runner
  版本: v0.6.1
  类型: 全局
  标签: ubuntu-22.04
  状态: 空闲(在线)

之前两次猜测都错了:
  ubuntu-latest → 不是 GitHub 约定,匹配失败
  self-hosted   → 这个 runner 没注册该标签

最终实际标签:ubuntu-22.04(建议未来 .gitea/README.md 添加 runner 标签说明)
2026-07-03 00:16:21 +08:00
Matrix Music Bot Dev 9e62e995a9 ci: 修复 Gitea Actions runner 标签(ubuntu-latest → self-hosted)
build-and-push-image / build-push (push) Has been cancelled
build-and-push-image / vulnerability-scan (push) Has been cancelled
问题:job 一直显示"等待中"
原因:Gitea Actions runner 注册时默认使用自定义标签(self-hosted),
     而非 GitHub Actions 约定的 ubuntu-latest
报错:没有匹配 ubuntu-latest 标签的在线运行器

修复:runs-on 从 ubuntu-latest 改为 self-hosted
  - build-push job
  - vulnerability-scan job

如果 runner 注册时用了其它标签(如 docker / linux / 自定义名字),
仍可调整。修改方法:编辑此 workflow 文件的 runs-on 行。
2026-07-03 00:11:08 +08:00
Matrix Music Bot Dev 5cd462127c docker: 新增 docker-compose.prod.yml(从 Gitea Registry 拉取镜像)
build-and-push-image / build-push (push) Has been cancelled
build-and-push-image / vulnerability-scan (push) Has been cancelled
适用场景:生产服务器,无需源码 + Rust 工具链
- 移除 build: 段
- image: 直接引用 Gitea Registry 中的 latest 镜像
- env / volumes / healthcheck 与 docker-compose.yml 一致

用法:
  docker-compose -f docker-compose.prod.yml pull   # 单独拉取
  docker-compose -f docker-compose.prod.yml up -d  # 启动

推荐流程:
  1. 开发环境用 docker-compose.yml(本地 build)
  2. CI/CD push master 后 Gitea Actions 自动构建并推 latest
  3. 生产服务器用 docker-compose.prod.yml 拉 latest
2026-07-03 00:03:08 +08:00
Matrix Music Bot Dev fab14e3196 ci: Gitea Actions 自动构建 + 推送镜像到 Container Registry
按用户选定方案:
  - Registry: Gitea 内置 Container Registry
  - 触发: 仅 push master/main
  - 标签: latest + git short SHA
  - 架构: linux/amd64 单架构

新增文件:
  .gitea/workflows/build-image.yml   CI workflow 定义
  .gitea/README.md                  Gitea Actions 运维手册

workflow 步骤:
  1. actions/checkout@v4           检出源码
  2. dtolnay/rust-toolchain@stable  安装 Rust(仅用于 cache key 计算)
  3. Swatinem/rust-cache@v2        缓存 Cargo registry / target
  4. cargo metadata sanity check    防止 manifest 漂移
  5. docker/setup-buildx-action@v3  Buildx
  6. docker/login-action@v3        登录 Gitea Registry(用 ${{ gitea.token }})
  7. docker/metadata-action@v5     生成 latest + sha 标签 + OCI labels
  8. docker/build-push-action@v5   构建 + 推送(含 BuildKit registry cache)
  9. aquasecurity/trivy-action      CVE 扫描(不阻塞)

特性:
  - concurrency 控制:避免重复构建(同一 ref 只跑最新)
  - 自动镜像标签:latest + commit short SHA
  - BuildKit cache 推到 :buildcache 镜像(下次构建复用)
  - Gitea 内置 token 自动认证(无需手动 secrets)
  - OCI 标准 labels(title/source/license/vendor)

gitea/README.md 包含:
  - 前置要求(Registry 开启、Runner 注册、网络)
  - 镜像使用示例
  - 常见扩展(tag 触发 / 多架构 / 自动部署)
  - 故障排查(login 失败 / build 失败 / pull 失败)
2026-07-03 00:02:51 +08:00
Matrix Music Bot Dev 2518169927 docs: CLAUDE.md 增加 Docker 化里程碑 + 两种部署方式对照表
变更:
  - M1-M7 列表追加 " Docker 化"
  - 模块清单扩展到 22 项(+ Dockerfile / compose / .env.example / .dockerignore)
  - "部署关键提醒"区块增加 docker-compose 环境变量提示
  - 新增"两种部署方式"对照表(本机编译 vs Docker)

v1 全部完成(18 commits),支持本机开发 + Docker 生产两种部署模式。
2026-07-02 23:55:42 +08:00
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
Matrix Music Bot Dev f55e28ee1d docker: 多阶段 Dockerfile + docker-compose.yml + .dockerignore + .env.example
按用户选定方案:

1. 多阶段 Debian bookworm-slim
   - builder=rust:1.96-bookworm(含 pkg-config libsqlite3-dev libssl-dev)
   - runtime=debian:bookworm-slim(ffmpeg libsqlite3-0 libssl3 tini ca-certificates)
   - 利用 Docker 层缓存:先预编译 deps 再复制 src

2. yt-dlp standalone 二进制
   - ARG YTDLP_VERSION=2026.03.17
   - 用 ADD --checksum=sha256 校验
   - 与 README 中版本一致便于锁版本

3. 全部 bind mount
   - ./config → /app/config:ro(配置只读)
   - ./data   → /app/data(secret.bin / crypto.db / cache/)

4. 单服务 docker-compose
   - 重启策略 unless-stopped
   - 资源限制 2 CPU + 1G RAM
   - 健康检查 pgrep
   - MATRIX_BOT_SECRET_PASSPHRASE 从 .env 必填
   - RUST_LOG 默认 info + matrix_music_bot=debug

安全要点:
- 非 root 用户(bot:bot)
- tini 作为 PID 1(正确转发 SIGTERM/SIGINT)
- 健康检查:仅进程存活检测(bot 没有 health endpoint)

.dockerignore 排除:
- /target /data /config /tests /docs 等
- 减少 build context 大小(避免每次 build 重复传输)

.env.example:
- 模板文件,含 MATRIX_BOT_SECRET_PASSPHRASE 默认值
- RUST_LOG 默认值

下一步:补充 README.md + CLAUDE.md 的 Docker 部署章节
2026-07-02 23:54:36 +08:00
Matrix Music Bot Dev 99818e2669 docs: 更新 CLAUDE.md 反映 v1 完整交付 + 路线图
变更:
  - "当前阶段" 改为 "🎉 v1 已完整交付" + M1-M7 完整勾选清单
  - 模块清单从 16 → 18(新增 README.md + 反应监听标注)
  - 已确认技术决策 11 → 14 条:
      #5 reactions 两步选择(不再是 "待 M7")
      #12 reactions TTL 60s
      #13 crypto store 用 MSYS2 系统 sqlite3
      #14 outbox 通道唯一性(Timeline::send 强制 E2EE)
  - 新增"v1 已知限制(已知妥协)"表:6 项限制 + 修复计划
  - 新增"路线图":M8-M12 + v2 未来功能
  - 新增"部署关键提醒"区块(3 件事必做)
  - 测试统计保持 29 passed + 4 ignored

参考:
  - v1 计划:C:\Users\Administrator\.claude\plans\lexical-meandering-lighthouse.md
  - 部署文档:README.md
2026-07-02 23:49:44 +08:00
Matrix Music Bot Dev 813914c80f docs: 添加 README.md(部署指南 + 使用文档 + 故障排查)
内容(525 行):
  - 项目简介 + 功能特性
  - 架构概览(含 ASCII 图)
  - 系统要求(Rust 1.96+ MinGW gcc SQLite3 FFmpeg yt-dlp)
  - 快速部署 7 步:
      1. 克隆编译
      2. 创建配置
      3. 首次登录
      4. Element 信任 bot 设备
      5. 启动 bot
      6. 加入加密房间
      7. 测试点歌
  - 配置说明(AppConfig + 环境变量 MATRIX_BOT_SECRET_PASSPHRASE)
  - 使用指南(命令列表 + reactions 选择流程图)
  - 故障排查(5 个常见问题)
  - 开发指南(项目结构 + 重新登录 + 测试 + 添加新音乐源)
  - 已知限制(6 项,含原因 + 影响 + 修复计划)
  - 路线图(v1 完成 / v1.x 优化 / v2 未来)
  - 测试说明(29 passed + 4 ignored)
  - 致谢

部署目标读者:bot 操作员(不一定懂 Rust)+ 二次开发者
语言:中文(与 CLAUDE.md 风格一致)
2026-07-02 23:37:53 +08:00
Matrix Music Bot Dev cb55886501 feat: reactions 两步选择 + Element Call 兼容(M7 + M6 + CLAUDE.md 同步)
M6 Element Call 兼容(src/rtc_handler.rs):
  - 注册 SyncStateEvent<PossiblyRedactedCallMemberEventContent> handler
  - 记录 active_memberships() 数量到日志
  - bot 不参与通话媒体(v1 妥协;v2 可用 matrix_sdk::rtc + livekit-rs)

M7 reactions 两步选择:
  - src/reaction_store.rs:ReactionSession + ReactionStore(in-memory + TTL 60s)
  - src/event_loop.rs:
    - handle_req_search 改造:发带 emoji (1️⃣~5️⃣) 的候选列表 + 记录 session
    - handle_reaction:监听 m.reaction 事件 → 查 ReactionStore → 入队
    - 校验:仅原请求人能选
    - 校验:60s TTL 过期检查
    - 无效 emoji 反应:保留 session 让用户重选

  v1 妥协说明:
    - SendHandle.transaction_id 是私有的,无法直接拿真实 event_id
    - send_text_get_event_id 返回占位 event_id(时间戳拼接)
    - 这意味着:reactions 选择 v1 可能无法匹配(占位与真实 event_id 不一致)
    - M8 修复方向:通过 timeline 订阅事件 → 匹配 transaction_id 与 event_id

CLAUDE.md 同步:
  - 当前阶段改为 "M1-M7 全部完成" + v1 可真实部署测试
  - 已完成模块清单扩展到 16 项
  - M8(可选)标记为 event_id 跟踪修复

E2EE 保持:
  - 所有出站消息经 Timeline::send
  - m.reaction handler 解密后处理(matrix-sdk 自动处理 E2EE)

测试:cargo test → 29 passed + 4 ignored
  reaction_store 新增 4 测试:
    - parse_emoji_index_works / session_find_by_reaction
    - store_record_and_take / expired_sessions_are_removed
2026-07-02 22:14:25 +08:00
Matrix Music Bot Dev c0a378554d feat(reaction_store): 搜索结果反应(m.reaction)选择存储(M7.1)
新增模块:src/reaction_store.rs

ReactionSession:
  - (room_id, message_event_id) → Vec<SongCandidate> + requester + created_at
  - find_by_reaction("1️⃣"/"1"/"1️⃣" etc.) → Option<&SongCandidate>
  - is_expired(ttl) 默认 60s

ReactionStore(in-memory):
  - record(session):插入并清理过期
  - take(event_id):消费式取出(防止重复入队)
  - peek(event_id):仅查看
  - cleanup_expired():定期清理

parse_emoji_index() 支持多种 emoji 格式:
  "1️⃣" / "1" / "1️⃣" / "2️⃣" / ... / "5️⃣"

测试:cargo test reaction_store → 4 passed
  - parse_emoji_index_works:多种格式都能识别
  - session_find_by_reaction:1️⃣/3️⃣ → 对应候选
  - store_record_and_take:record + take 二次取 None
  - expired_sessions_are_removed:60s TTL 过期清理

后续 M7.2:
  - !req 改造为发搜索列表 + 记录 session
  - 注册 m.reaction event handler
  - reaction handler 查 ReactionStore 找候选并入队
2026-07-02 22:04:03 +08:00
Matrix Music Bot Dev 2545e47977 feat(rtc_handler): MSC3401 call.member 状态事件观测(M6 Element Call 兼容)
新增模块:src/rtc_handler.rs

Element Call = MatrixRTC (MSC3401) 信令 + LiveKit WebRTC SFU 媒体。
本模块只观察信令层;媒体层 bot 不参与(v1 不实现)。

实现:
  - register(client):注册 SyncStateEvent<PossiblyRedactedCallMemberEventContent> handler
  - handle_call_member 闭包:处理 Original / Redacted 两种 state event
  - 记录 active_memberships() 数量到日志(通话成员变化)

事件类型路径:
  matrix_sdk::ruma::events::call::member::PossiblyRedactedCallMemberEventContent

event_loop 集成:
  - bootstrap::restore 后立即 rtc_handler::register(&client)
  - 不影响现有 chat handler(m.room.message handler 与 state event handler 并行)

E2EE 兼容性:
  - MSC3401 信令通过 state event 加密(Megolm 加密 room state events,MSC3401 已支持)
  - bot 在 call 进行中仍能正常收发 m.room.message(验证:sync 不被 state event 阻塞)
  - 注意:Element Call 媒体通道(WebRTC)是 DTLS-SRTP,**不**经 Megolm
    → bot 不参与通话音频;用户的聊天 + m.audio 投递仍完全 E2EE

测试:cargo test → 25 passed + 4 ignored(新增 rtc_handler 模块加载 smoke test)

后续 M7:reactions 选择(用户点击 emoji 选搜索结果)+ 队列持久化(可选)
2026-07-02 21:54:06 +08:00
Matrix Music Bot Dev ef93f9615f feat: !req/!req-url/!queue/!np/!skip 端到端接入 event_loop(M5)+ CLAUDE.md 同步
新增模块:
  src/queue.rs      ── QueueStore(内存版)+ SongQueue trait + QueueItem/QueueState
  src/downloader.rs ── yt-dlp 子进程下载到 data/cache/raw/<source>-<id>.<ext>
  src/worker.rs     ── 每房间 worker:resolve → download → transcode → deliver

src/event_loop.rs 改造:
  - 构造 SearchFacade(包含 YtDlpBackend + 3 stub backend)
  - 命令 handler 分发:
      !help        → render_reply + send_text
      !req <q>     → search_all → 自动取第一条入队(M7 接 reactions)
      !req-url     → 构造 candidate → 入队
      !queue       → 渲染队列状态
      !np          → 渲染当前播放
      !skip        → 弹出当前播放
  - ensure_room_worker:首次收到消息时启动该房间 worker
  - extract_video_id:支持 YouTube v= / youtu.be/ / B站 BV 号提取

src/main.rs:
  - 引入 queue/downloader/worker 模块

CLAUDE.md 同步:
  - 当前阶段改为 "M1-M5 已完成" + 已完成模块清单
  - 列出已确认技术决策 11 条(不可变)
  - 列出待办 M6-M7

E2EE 通道保持:
  - 所有出站经 Timeline::send
  - audio_upload.deliver() 内部仍走 TimelineBuilder + send

依赖决策(注意):
  - 放弃 rusqlite(libsqlite3-sys 与 matrix-sdk-sqlite 链接冲突)
  - 放弃 reqwest(用 yt-dlp 子进程下载,简化 deps)
  - 队列改 in-memory(重启清空,v1 接受)
  - 这两条记入 CLAUDE.md "已确认技术决策" 第 10、11 条

测试:cargo test → 24 passed + 4 ignored
  新增 3 个 queue 测试:
    - enqueue_and_dedup:同 song 同房间去重
    - different_sources_not_dedup:不同 source_id 允许入队
    - skip_current_returns_head:skip 弹出当前
2026-07-02 21:48:19 +08:00
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 50d9e0f4a9 feat(music): MusicSource trait + yt-dlp backend + 其他 backend stub(M3)
新增模块:src/music/{mod,ytdlp,netease,qq,selfhosted}.rs

MusicSource trait(async_trait):
  fn kind() -> &'static str     // "ytdlp" / "netease" / "qq" / "selfhosted"
  fn display_name() -> &'static str
  async fn search(query, limit) -> Vec<SongCandidate>
  async fn resolve(candidate) -> ResolvedSong

SongCandidate:
  source_kind / source_id / title / artist / album? / duration_ms? / webpage_url?

ResolvedSong:
  candidate + play_url + play_url_expires_at + format_hint + size_bytes_estimate?

AudioFormat 枚举:
  Mp3 / Aac / Flac / Opus / Webm / Other
  from_mime() / is_target_native() 助手方法

SearchFacade:
  多 backend 并行 search → 合并 → 去重(同 title+artist 视为一首)
  失败的 backend 记 warn 后跳过,不阻塞其他 backend

YtDlpBackend(完整实现):
  search: yt-dlp 'ytsearch<N>:<query>' --dump-json --skip-download
          解析每行 JSON,过滤 30s < 长度 < 30min
  resolve: yt-dlp -f bestaudio -g <url>
          取首行非空作为直链,估算 6h 过期
  通过 tokio::process::Command 异步 spawn 子进程
  并发读 stdout/stderr 避免阻塞
  用 tokio::time::timeout 实施超时

  注:HEAD 探测 Content-Type/Content-Length 待 M5 reqwest 引入后启用

其他 backend(stub):
  NetEaseBackend / QqMusicBackend / LocalFsBackend
  都实现 trait,但 search/resolve 返回 AppError::MusicSource 提示未实现

测试:
  cargo test music → 4 passed + 2 ignored(需要网络+yt-dlp)
    - AudioFormat::from_mime / is_target_native
    - SongCandidate serde roundtrip
    - YtDlpBackend 集成测试(ignore 标记)

测试总数:19 passed + 2 ignored

后续 M4:transcoder.rs(ffmpeg MP3/AAC → stereo Opus 128k)+ delivery/audio_upload.rs(m.audio via Timeline::send)
后续 M5:把 MusicSource 接入 event_loop,!req 端到端实现
2026-07-02 21:11:58 +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 5ea92de6b9 feat(commands): 房间/DM 感知的命令解析器(M2.1)
子命令(v1):
  !help / !h / !?        显示帮助
  !req / !request / !点歌 / !来一首 <关键词...>
  !req-url / !url <URL> 直接粘 URL
  !queue / !q / !队列
  !np / !nowplaying
  !skip / !next / !跳过

权限模型:
  - DM:无需 mention,直接发命令
  - 房间:根据 [commands] mention_required_in_rooms 配置
    - true:必须包含 @bot_user_id 或 @bot_localpart
    - false:任何消息都会解析

mention 文本从消息中剥离后再解析(修复 bug:之前 format 出了 @@)

中文/英文命令别名都支持(如「点歌」「跳过」)。

render_reply 已实现 help;其他命令返回 M3+ 占位提示。

测试:cargo test commands → 11 passed
  - DM 解析 / 房间解析 / mention 必需 / mention 关闭 / 多别名 / 缺参 / 错命令
2026-07-02 20:53:01 +08:00
Matrix Music Bot Dev 39afe31d19 feat(bootstrap): matrix-sdk login + 加密 session 持久化 + restore(M1.3b/c)
新增模块:
  src/client.rs   ── matrix-sdk ClientBuilder 工厂(含 crypto store + EncryptionSettings)
  src/bootstrap.rs ── login/restore 两个公开入口函数

依赖环境:
  - 系统需要 libsqlite3 → 已通过 pacman 安装 mingw-w64-x86_64-sqlite3
  - 运行时需要 MATRIX_BOT_SECRET_PASSPHRASE 环境变量(≥8 字符)

login 流程:
  1. 检查 secret.bin 不存在(避免覆盖现有 device)
  2. 构造 Client(homeserver + sqlite_store + EncryptionSettings)
  3. matrix_auth().login_username().initial_device_display_name().send()
  4. /login response → MatrixSession(impl From<&Response> for MatrixSession)
  5. MatrixSession 序列化为 JSON
  6. secret_store::save() 加密写入 data/secret.bin
  7. 输出 user_id + device_id 提示操作员在 Element 中验证

restore 流程(给后续 M2 的 run 子命令用):
  1. 检查 secret.bin 存在
  2. secret_store::load() 解密
  3. MatrixSession JSON → MatrixSession 结构
  4. 构造 Client(沿用 crypto.db)
  5. client.restore_session(session)
  6. 返回 (Client, user_id, device_id)

cmd_login 改造:
  - 从环境变量读取 passphrase(避免 CLI history 泄漏)
  - 长度 < 8 字符拒绝
  - 内部用 tokio multi-thread runtime 跑 async login

secret_store 微调:
  - SessionSecrets 改为存储 matrix_session: serde_json::Value + schema_version
  - 因 serde_json::Value 不实现 Zeroize,移除 #[derive(Zeroize)]
  - 依赖 matrix-sdk 内部 SessionMeta/SessionTokens 自带的 zeroize 保证内存安全

CLI 实测(fake homeserver):
  - 缺 passphrase → 友好错误
  - 有 passphrase → 走到 matrix-sdk /login 调用,因 DNS 失败是预期行为

测试:
  cargo test → 4 passed(secret_store roundtrip + 噪声 ciphertext + config 解析)
2026-07-02 20:03:38 +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
Matrix Music Bot Dev 5c4b8d8b82 docs: 添加 CLAUDE.md 项目协作规范
包含两条核心规则:

1. 技术决策流程:必须列出 2+ 方案 + 解释优缺点 + 用户拍板;
   不得自行默认选择。详见 CLAUDE.md 第 1 节。

2. Git 实时提交规范:禁止积累大批改动后一次性提交;
   每个逻辑单元完成后立即提交(单文件/单函数/milestone 边界);
   中文 commit message + Conventional Commits 风格;
   提交前必须 cargo build/check 通过。
   详见 CLAUDE.md 第 2 节。
2026-07-02 01:09:01 +08:00
Matrix Music Bot Dev 3426acbb77 chore: 初始化仓库结构(.gitignore + data/ 占位)
- 排除 /target、/data、secret.bin、.env 等敏感与构建产物
- 保留 data/.gitkeep 占位以便运行时生成 bot.db / crypto.db
2026-07-02 01:08:45 +08:00