升级到新版 SDK 并补充点歌业务日志
- 锁定 qqbotsdk b37a53f(含:健康断开零等待重连、静默常规连接日志、 失败才记断开与退避) - 新增业务日志:启动打印生效配置、点歌/查歌词/帮助、菜单已发、 选歌、按钮点击、语音/文件/直链已发,失败项统一带群号前缀 (此前 18 处日志 17 处是 warning,成功路径完全静默,故日志里 看不到任何点歌消息)
This commit is contained in:
1 parent
a11c0b375f
commit
6b6492dc8d
3 files changed
+20
-19
No files matched your search
+18
-4
@@ -404,6 +404,7 @@ async def send_menu(
|
|||||||
logger.warning(f"文本菜单发送失败: {e!r}")
|
logger.warning(f"文本菜单发送失败: {e!r}")
|
||||||
return
|
return
|
||||||
await _swap_message(api, config, group_openid, resp)
|
await _swap_message(api, config, group_openid, resp)
|
||||||
|
logger.info(f"[{group_openid}] 菜单已发:{keyword} 第{page}页 {len(songs)}首")
|
||||||
|
|
||||||
|
|
||||||
async def send_card(
|
async def send_card(
|
||||||
@@ -451,6 +452,7 @@ async def send_card(
|
|||||||
logger.warning(f"文本卡片发送失败: {e!r}")
|
logger.warning(f"文本卡片发送失败: {e!r}")
|
||||||
return
|
return
|
||||||
await _swap_message(api, config, group_openid, resp)
|
await _swap_message(api, config, group_openid, resp)
|
||||||
|
logger.info(f"[{group_openid}] 选歌:{title}")
|
||||||
|
|
||||||
|
|
||||||
async def _upload_group_file(
|
async def _upload_group_file(
|
||||||
@@ -489,6 +491,7 @@ async def send_song(
|
|||||||
无论成败都撤回卡片。"""
|
无论成败都撤回卡片。"""
|
||||||
found = await _fetchable_play(http, config, song, keyword)
|
found = await _fetchable_play(http, config, song, keyword)
|
||||||
if found is None:
|
if found is None:
|
||||||
|
logger.warning(f"[{group_openid}] 无可用直链:{song.name}")
|
||||||
await _reply(
|
await _reply(
|
||||||
api, group_openid, f"没拿到「{song.name}」的播放链接", msg_id, event_id
|
api, group_openid, f"没拿到「{song.name}」的播放链接", msg_id, event_id
|
||||||
)
|
)
|
||||||
@@ -510,26 +513,28 @@ async def send_song(
|
|||||||
)
|
)
|
||||||
if sent := _sent_id(resp):
|
if sent := _sent_id(resp):
|
||||||
_mark(sent)
|
_mark(sent)
|
||||||
|
logger.info(f"[{group_openid}] {label}已发:{_caption(song)}")
|
||||||
return
|
return
|
||||||
except _API_FAILS as e:
|
except _API_FAILS as e:
|
||||||
no_permission = isinstance(e, ApiError) and e.code == _NO_FILE_PERMISSION
|
no_permission = isinstance(e, ApiError) and e.code == _NO_FILE_PERMISSION
|
||||||
if no_permission:
|
if no_permission:
|
||||||
logger.warning("群文件上传仅限群主/管理员,改为直链文本")
|
logger.warning(f"[{group_openid}] 群文件上传无权限,改为直链文本")
|
||||||
else:
|
else:
|
||||||
logger.warning(f"以{label}发送失败: {e!r}")
|
logger.warning(f"[{group_openid}] 以{label}发送失败: {e!r}")
|
||||||
# 回退直链文本;文件权限问题多给一句说明
|
# 回退直链文本;文件权限问题多给一句说明
|
||||||
text = f"{_caption(song)}\n{url}"
|
text = f"{_caption(song)}\n{url}"
|
||||||
if no_permission:
|
if no_permission:
|
||||||
text = f"{_caption(song)}\n(本群文件上传仅限群主/管理员,先给直链)\n{url}"
|
text = f"{_caption(song)}\n(本群文件上传仅限群主/管理员,先给直链)\n{url}"
|
||||||
try:
|
try:
|
||||||
await _reply(api, group_openid, text, msg_id, event_id)
|
await _reply(api, group_openid, text, msg_id, event_id)
|
||||||
|
logger.info(f"[{group_openid}] 直链已发:{_caption(song)}")
|
||||||
except _API_FAILS as e:
|
except _API_FAILS as e:
|
||||||
# 被动回复被拒(如互动 event_id 超时效)→ 降级为主动消息
|
# 被动回复被拒(如互动 event_id 超时效)→ 降级为主动消息
|
||||||
logger.warning(f"被动回复失败,降级为主动消息: {e!r}")
|
logger.warning(f"[{group_openid}] 被动回复失败,降级为主动消息: {e!r}")
|
||||||
try:
|
try:
|
||||||
await _reply(api, group_openid, text)
|
await _reply(api, group_openid, text)
|
||||||
except _API_FAILS as e:
|
except _API_FAILS as e:
|
||||||
logger.warning(f"文本链接发送失败: {e!r}")
|
logger.warning(f"[{group_openid}] 文本链接发送失败: {e!r}")
|
||||||
|
|
||||||
|
|
||||||
async def send_lyric(
|
async def send_lyric(
|
||||||
@@ -688,14 +693,17 @@ async def handle_command(
|
|||||||
选歌/播放/下载现在都由内嵌按钮(回调)承担,这里只处理文字命令。"""
|
选歌/播放/下载现在都由内嵌按钮(回调)承担,这里只处理文字命令。"""
|
||||||
if match := _SONG_RE.search(content):
|
if match := _SONG_RE.search(content):
|
||||||
keyword = match.group(1).strip()
|
keyword = match.group(1).strip()
|
||||||
|
logger.info(f"[{group_openid}] 点歌:{keyword}")
|
||||||
await send_menu(api, http, config, group_openid, keyword, msg_id=msg_id)
|
await send_menu(api, http, config, group_openid, keyword, msg_id=msg_id)
|
||||||
return True
|
return True
|
||||||
if match := _LYRIC_RE.search(content):
|
if match := _LYRIC_RE.search(content):
|
||||||
keyword = match.group(1).strip()
|
keyword = match.group(1).strip()
|
||||||
|
logger.info(f"[{group_openid}] 查歌词:{keyword}")
|
||||||
if song := await _find(api, http, config, group_openid, msg_id, keyword):
|
if song := await _find(api, http, config, group_openid, msg_id, keyword):
|
||||||
await send_lyric(api, http, config, group_openid, song, msg_id=msg_id)
|
await send_lyric(api, http, config, group_openid, song, msg_id=msg_id)
|
||||||
return True
|
return True
|
||||||
if "音乐帮助" in content:
|
if "音乐帮助" in content:
|
||||||
|
logger.info(f"[{group_openid}] 查看帮助")
|
||||||
await _reply(api, group_openid, _HELP, msg_id=msg_id)
|
await _reply(api, group_openid, _HELP, msg_id=msg_id)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
@@ -732,6 +740,11 @@ def register(ee: EventEmitter, config: BotConfig, http: ClientSession) -> None:
|
|||||||
"""把点歌命令与按钮回调 handler 挂到 emitter(群场景);http 为 bot
|
"""把点歌命令与按钮回调 handler 挂到 emitter(群场景);http 为 bot
|
||||||
自建连接池(不受 SDK 的 TIMEOUT 约束),经闭包供 handler 使用——不能
|
自建连接池(不受 SDK 的 TIMEOUT 约束),经闭包供 handler 使用——不能
|
||||||
用形参注入,否则拿到的是 SDK 那个 5s 超时的连接池。"""
|
用形参注入,否则拿到的是 SDK 那个 5s 超时的连接池。"""
|
||||||
|
logger.info(
|
||||||
|
f"点歌已就绪:源={config.music_source or '聚合'} 音质={config.music_quality} "
|
||||||
|
f"每页={config.menu_page_size} 下载={config.send_mode} "
|
||||||
|
f"自动撤回={'开' if config.withdraw else '关'}"
|
||||||
|
)
|
||||||
|
|
||||||
# @机器人 的消息走前一个事件;开通「接收所有消息」后群内全部消息走
|
# @机器人 的消息走前一个事件;开通「接收所有消息」后群内全部消息走
|
||||||
# 全量事件,两个事件字段一致,共用同一个 handler
|
# 全量事件,两个事件字段一致,共用同一个 handler
|
||||||
@@ -743,6 +756,7 @@ def register(ee: EventEmitter, config: BotConfig, http: ClientSession) -> None:
|
|||||||
@ee.on("INTERACTION_CREATE")
|
@ee.on("INTERACTION_CREATE")
|
||||||
async def on_button(act: Interaction, ev: Event, api: BotApi) -> None:
|
async def on_button(act: Interaction, ev: Event, api: BotApi) -> None:
|
||||||
if act.chat_type == 1 and act.id and act.group_openid:
|
if act.chat_type == 1 and act.id and act.group_openid:
|
||||||
|
logger.info(f"[{act.group_openid}] 按钮:{act.button_data[:40]}")
|
||||||
# 被动回复的 event_id 用网关帧最外层 id(Event.event_id),
|
# 被动回复的 event_id 用网关帧最外层 id(Event.event_id),
|
||||||
# 裸 uuid(act.id)只用于回应互动
|
# 裸 uuid(act.id)只用于回应互动
|
||||||
await handle_button(
|
await handle_button(
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
"""开发用热重载:监控源码与配置文件,变更后重启 bot 子进程。
|
"""开发用热重载:监控源码与配置文件,变更后重启 bot 子进程。
|
||||||
|
|
||||||
进程级重启(而非进程内模块重载):bot 是长连接 + 模块级状态 + handler
|
进程级重启(而非进程内模块重载):bot 是长连接 + 模块级状态 + handler
|
||||||
闭包,就地重载要清理 pyee 监听器与旧闭包,容易留下幽灵 handler;重启
|
闭包,就地重载要清理事件监听器与旧闭包,容易留下幽灵 handler;重启
|
||||||
子进程则状态与闭包都是干净的,代价是重连一次 websocket。
|
子进程则状态与闭包都是干净的,代价是重连一次 websocket。
|
||||||
|
|
||||||
监督进程只负责拉起/杀掉子进程与轮询文件改动,自身不跑 bot;子进程由
|
监督进程只负责拉起/杀掉子进程与轮询文件改动,自身不跑 bot;子进程由
|
||||||
|
|||||||
@@ -628,18 +628,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" },
|
{ url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pyee"
|
|
||||||
version = "14.0.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "typing-extensions" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/1d/f1/fdedc2c75c3e31a330659c85e5793bb18b3397981fbf0844c6dee5b18926/pyee-14.0.0.tar.gz", hash = "sha256:76dd0f4314ecd27f02dc73589dea7fd3853f9b6176d8ef9b122860657e3602de", size = 98760, upload-time = "2026-08-13T04:26:11.021Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/81/12/5347938b1f9a6453f0dbdfcc3e2388a1320ef9b9ec17fbefbc4ab647ea98/pyee-14.0.0-py3-none-any.whl", hash = "sha256:3ac2d3229a9677f7de2c33d7f52fe25b638a46b19c413fea2edc8c6d0a644e4d", size = 15553, upload-time = "2026-08-13T04:26:09.916Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pygments"
|
name = "pygments"
|
||||||
version = "2.21.0"
|
version = "2.21.0"
|
||||||
@@ -702,12 +690,11 @@ wheels = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "qqbotsdk"
|
name = "qqbotsdk"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = { git = "https://gitea.4i.hk/hhhge/qqbotsdk.git#71cbfdaf5d4814d8798304c6e53b5acb6266f1c2" }
|
source = { git = "https://gitea.4i.hk/hhhge/qqbotsdk.git#b37a53f6e0dec360a3e684fbafd7140ec00a2711" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "aiohttp" },
|
{ name = "aiohttp" },
|
||||||
{ name = "cryptography" },
|
{ name = "cryptography" },
|
||||||
{ name = "loguru" },
|
{ name = "loguru" },
|
||||||
{ name = "pyee" },
|
|
||||||
{ name = "python-dotenv" },
|
{ name = "python-dotenv" },
|
||||||
{ name = "ujson" },
|
{ name = "ujson" },
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in new issue
Block a user