feat: 文件代理、群消息防双发、下载指令出选歌菜单与音频平台选集
代码测试 / ruff + pytest (push) Canceled after 0s

- 文件代理(PUBLIC_URL):上传与直链文本的 URL 换成 bot 公开地址,请求
  到来现场重解析直链再转发字节,绕开 QQ 抓风控直链失败(40093007)与
  qs 本地流外网不可达;PROXY_PORT 指定监听端口
- 群消息去重补第二把钥匙:全量模式下 @消息同时命中两事件且 msg_id 不同,
  只按 msg_id 拦不住,再加「群+发送人+文本」10 秒短窗(修双发)
- 「<平台>下载 <歌名>」改出该平台的选歌菜单(与 /点歌 同款交互、spage
  翻页锁定平台),不再直接下第一首
- 猫耳/懒人/蜻蜓节目级条目点编号先出选集菜单(/album):猫耳选集接口
  忽略翻页参数,一次拉 50 集本地切片;懒人/蜻蜓走服务端翻页;空选集
  (已下线/需登录)给明确提示
- 指令面板条目按 /sources 现算、按宽度裁剪(修 30013 超限);装配入口
  改用 Bot 门面;qqbotsdk 依赖更名 qqbotsdk-py(撤 GitHub archive 源)
This commit is contained in:
snowy-yang committed 2026-10-03 23:55:33 +08:00
1 parent 324fe86962
commit c364506925
10 files changed
+1883 -254

No files matched your search

+21 -6
View File
@@ -2,13 +2,23 @@
QQ 群点歌 bot,基于 [qqbotsdk](https://github.com/snowy-yang/qqbotsdk),两步点歌:
1. **选歌菜单**——`点歌 <歌名 [歌手]>`(或斜杠指令 `/点歌 <歌名 [歌手]>`)只负责选歌:Markdown 歌单(编号 + 歌名/歌手,默认 QQ 音乐源,搜不到自动聚合兜底),下方每行 5 个编号回调按钮 + `[上一页][下一页]`(每页 `MENU_PAGE_SIZE` 首,默认 10,上限 20)
1. **选歌菜单**——`点歌 <歌名 [歌手]>`(或斜杠指令 `/点歌 <歌名 [歌手]>`)只负责选歌:Markdown 歌单(编号 + 歌名/歌手 + 条目封面小图,默认 QQ 音乐源,搜不到自动聚合兜底),下方每行 5 个编号回调按钮 + `[上一页][下一页]`(每页 `MENU_PAGE_SIZE` 首,默认 10,上限 20)
2. **歌曲卡片**——点编号弹出卡片:专辑封面图 + 歌名/歌手/来源 + `[▶ 播放][⬇ 下载]` 回调按钮,再点才执行
- **专辑封面**:卡片用 Markdown 图片语法 `![封面 #240px #240px](img)` 显示封面(QQ 后台会代理并缓存图片到客户端,不落库);封面来自 MusicServer 的 `img` 字段,tx/wy 有值,kw/kg 无(这些源卡片就不带图)
其余指令(都能从输入框 `/` 菜单直接触发):
- **`/<平台>下载 <歌名>`**(如 `/酷狗下载 稻香`):在指定平台搜歌、出该平台的选歌菜单(与 `/点歌` 同款交互:点编号弹卡片、再点播放/下载;翻页锁定该平台);平台与指令名按 MusicServer `/sources` 动态生成(服务器加源即自动出现新指令),全名/短名/惯用名都可(`酷狗下载`、`酷狗音乐下载`;`网易云下载`、`网易音乐下载`)
- **`/热搜`**:拉取热搜词(编号列表 + 词按钮),点词直接出该词的选歌菜单
- **音频平台选集菜单**:猫耳FM/懒人/蜻蜓的搜索结果是节目级条目(一个节目多集音频,猫耳为 `d:` 前缀、懒人是书、蜻蜓是频道,直接取链会失败或只拿到一集),点编号先出**选集菜单**(`/album` 接口,标题带总集数、可翻页),选集再点编号才弹卡片播放/下载;猫耳的选集接口不支持翻页参数,一次拉 50 集本地切片分页(超过 50 集的节目被截断),懒人/蜻蜓走服务端翻页;选集为空(已下线/需登录)给明确提示。喜马拉雅搜索返回单集级条目(带真实时长),不受影响
- **`/榜单`**:拉取榜单列表,点榜单名直接出该榜单的选歌菜单(可翻页,按服务器返回的总数判断)
- **`/歌词 <歌名>`**:歌词按源搜索,无歌词能力的平台(喜马拉雅等音频源)给明确提示
- **专辑封面**:卡片与选歌菜单都用 Markdown 图片语法 `![封面 #宽px #高px](img)` 显示封面(QQ 后台会代理并缓存图片到客户端,不落库;卡片 240px 大图,菜单条目 80px 小图放在编号行下);封面来自 MusicServer 的 `img` 字段,tx/wy/qs/xm/me/qt/lr 有值,kw/kg 无(这些源条目就不带图)
- **链接统一标准编码**:发出的直链文本、上传给 QQ 的 URL、封面图地址都先转标准百分号编码(`_encode_url`)——空格/中文/引号等非法字符转义,`:/?#&=%` 等结构字符与已有 `%XX` 原样保留(幂等,不会二次编码);Markdown 图片语法里额外转义 `(` `)`(否则会截断 `![]()` 链接)
- 回调按钮(type=1)点一下直接生效,无需再按发送;播放发语音、下载发文件或直链(见 `SEND_MODE`),失败回退直链文本;无 Markdown 权限自动回退文本
- **下载发送方式**(`SEND_MODE`,默认 `link`):群文件上传仅限群主/管理员(40093005),故默认直接发直链;`file` 则先尝试上传群文件、失败(含无权限)回退直链。播放始终发语音
- **直链候选源回退**:tx 等源直链常受 CDN 风控抓不动(bot 抓不动时 QQ 服务器侧上传同样报 40093007),上传前先探测直链可抓性,不可抓则按 kw/wy/mg 逐个搜索换源
- **直链候选源回退**:tx 等源直链常受 CDN 风控抓不动(bot 抓不动时 QQ 服务器侧上传同样报 40093007),取直链时附带歌名/歌手/时长(原源取流失败 MusicServer 会先做酷我兜底匹配),bot 侧上传前再探测直链可抓性,不可抓则按 kw/wy 逐个搜索换源;tx/qs 不作候选(qs 匿名只有 30s 试听,登录态直链是 MusicServer 本地解密流代理,QQ 抓不到)
- **文件代理**(`PUBLIC_URL`,配置即开、`FILE_PROXY=off` 可关):bot 起一个 HTTP 服务,上传用与直链文本里的 URL 换成 `<PUBLIC_URL>/file/<key>/<歌名>.mp3`——QQ 服务器或群友来取时才现场重解析直链(含候选源回退)再流式转发字节,自然绕开 QQ 侧抓风控直链失败(40093007)与 qs 本地解密流外网不可达;需 bot 主机公网可达(公网 IP 或端口映射),`PROXY_PORT` 指定监听端口(缺省跟 `PUBLIC_URL` 的端口);服务起不来时自动退回发原直链
- **自动撤回**(`WITHDRAW`,默认开):翻页撤旧菜单、选歌后撤菜单、下载/播放点击后撤卡片(无论发送成败都撤,卡片是一次性 UI);关闭后所有消息都保留
- **回调防抖与先应答**:按钮回调进门先回应互动(`PUT /interactions`,几秒内不应答 QQ 客户端就报超时),同一互动 uuid(平台重复推送)或同群同按钮 10 秒内只处理一次;播放/下载先回「正在获取」占位消息再取直链,结果以递增的 `msg_seq` 补发(同一事件相同 `msg_seq` 的第二次回复会被 QQ 拒绝)
- **日志**:控制台 + `LOG_DIR/musicbot.log`(按天轮转、保留 7 天),级别由 `LOG_LEVEL` 控制;目录不可写时自动退化为仅控制台
@@ -17,7 +27,7 @@ QQ 群点歌 bot,基于 [qqbotsdk](https://github.com/snowy-yang/qqbotsdk),
## 配置
复制 `.env.example` 为 `.env`,填入开放平台 `APPID`/`APPSECRET`。`intents.toml` 控制事件订阅。MusicServer(lx-music 聚合音源)需在本机或 `MUSIC_SERVER_URL` 指向处运行。
复制 `.env.example` 为 `.env`,填入开放平台 `APPID`/`APPSECRET`。`intents.toml` 控制事件订阅。MusicServer(lx-music 聚合音源)需在本机或 `MUSIC_SERVER_URL` 指向处运行——当前版本 9 个平台:kw 酷我/kg 酷狗/tx QQ音乐/wy 网易云/qs 汽水音乐/xm 喜马拉雅/me 猫耳FM/qt 蜻蜓FM/lr 懒人听书,实际可用平台与显示名以服务器 `/sources` 为准(bot 启动时拉取并记日志),服务器增删源后「`<平台>下载`」指令与直链候选源自动跟着变。
点歌相关开关:
@@ -26,6 +36,9 @@ QQ 群点歌 bot,基于 [qqbotsdk](https://github.com/snowy-yang/qqbotsdk),
| `MENU_PAGE_SIZE` | `10` | 选歌菜单每页候选数,1~20(内嵌键盘 5 行所限) |
| `WITHDRAW` | `on` | 自动撤回旧菜单/卡片;`off` 则全部保留 |
| `SEND_MODE` | `link` | 下载按钮:`link` 直接发直链 / `file` 先传群文件(需群主/管理员) |
| `PUBLIC_URL` | 空 | 文件代理对外地址(如 `http://1.2.3.4:16689`);配置后文件代理开启 |
| `FILE_PROXY` | `on` | 文件代理开关,仅在 `PUBLIC_URL` 已配置时生效 |
| `PROXY_PORT` | 跟 `PUBLIC_URL` | 文件代理监听端口(无端口则 `16689`);https 反代时须显式配置 |
| `LOG_LEVEL` | `INFO` | 日志级别 TRACE/DEBUG/INFO/SUCCESS/WARNING/ERROR/CRITICAL |
| `LOG_DIR` | `logs` | 日志目录(自动按天轮转、保留 7 天) |
| `RELOAD` | `off` | 热重载;`on` 等价于命令行 `--reload`(`start.bat` 已带) |
@@ -39,11 +52,13 @@ QQ 群点歌 bot,基于 [qqbotsdk](https://github.com/snowy-yang/qqbotsdk),
## 斜杠指令(输入框 `/` 菜单)
群里输入框敲 `/` 唤出的「指令面板」由 bot **调接口自动注册**(开放平台 2026-08 新增的 `/v2/panels` 系列接口):每次建连(READY)时把 `点歌`/`歌词`/`帮助` 三条指令同步上去——没有就创建(`target_type=all` 对全部群生效)、内容有变就更新、一致则跳过,重连即自愈;失败只记日志不影响运行。`COMMAND_PANEL=off` 可关闭,改在开放平台后台「功能配置 → 指令」手工配置。
群里输入框敲 `/` 唤出的「指令面板」由 bot **调接口自动注册**(开放平台 2026-08 新增的 `/v2/panels` 系列接口):每次建连(READY)时同步一次——没有就创建(`target_type=all` 对全部群生效)、内容有变就更新、一致则跳过,重连即自愈;失败只记日志不影响运行。`COMMAND_PANEL=off` 可关闭,改在开放平台后台「功能配置 → 指令」手工配置。
面板条目按 MusicServer `/sources` **现算**:`点歌`/`歌词` + `热搜`/`榜单`(有平台支持才注册)+ 各平台「`<平台>下载`」(search+url 能力齐备才注册,指令名取惯用/短名:酷狗、QQ音乐、网易云……)+ `帮助` 垫底。当前 9 平台下共 14 条(面板接口上限 20 条,条目生成时按 name ≤14 字符、desc ≤30 字符裁剪)。
指令消息以 `/点歌 稻香` 形式推给 bot(走 `GROUP_AT_MESSAGE_CREATE`,指令自动 @ 机器人),与文本命令共用同一套解析——`_normalize` 会剥掉开头的 `/` 与 `@机器人` 前缀,`点歌 稻香`、`/点歌 稻香`、`@bot /点歌 稻香` 三种写法等价。只敲命令不带参数(斜杠指令参数可为空)时回复用法提示。
「帮助」消息带 `[🎤 点歌][📝 歌词]` 指令按钮(action `type=2`):点击把 `/点歌 ` 填进输入框,补上歌名发送即可;键盘无权限时自动回退纯文本。
「帮助」消息带 `[🎤 点歌][📝 歌词][🔥 热搜][🏆 榜单]` 指令按钮(action `type=2`):点击把对应斜杠指令填进输入框,补上歌名(如需要)发送即可;键盘无权限时自动回退纯文本。
## 运行