fix: @提及昵称+正文拼凑吞消息+GUI冻结(主线程sleep)

问题1: '@咱想了解一下咱那个登录插件去哪儿下载?' 只剩'?'
- 根因: _at_user_pattern贪婪吞18字符当username, rest='?'
- is_clean_at=False只拦了入库, 但ts已被覆盖成rest('?')→整条问句丢失
- 修复: 不干净时仅剥@保留全部正文(宁留脏昵称不吞消息)

问题2: 跟踪日志时GUI冻结必须ctrl+c
- 根因: _execute_reply跑在Tk主线程, _paste_and_send失败后
  time.sleep(5)×3次=主线程冻结15秒(QQ窗不前置时必失败)
- 修复: after()异步重试+data['_attempt']计数, 主线程不阻塞
This commit is contained in:
youpaishidifu
2026-08-17 17:41:39 +08:00
parent c13a41771a
commit 0feadd9c62
2 changed files with 37 additions and 25 deletions
+6 -3
View File
@@ -1145,6 +1145,7 @@ class MsgBotApp(ctk.CTk):
msg = data.get("msg", "")
max_retries = 3
retry_delay = 5 # 秒
attempt = data.get("_attempt", 0) # ⭐ 主线程异步重试计数(不阻塞GUI)
# ⭐ 发送前强制检查时间戳:≤300秒则发送,≥300秒则丢弃
msg_age = self._check_message_freshness(msg)
@@ -1161,8 +1162,6 @@ class MsgBotApp(ctk.CTk):
if not found:
self._log(f"[{platform}] ⚠ 找不到联系人 {sender},当前会话回复")
# 带重试的发送逻辑
for attempt in range(max_retries):
# 首次正常发送,重试时 force_retry 跳过60秒冷却
ok = self.monitor._paste_and_send(platform, answer, force_retry=(attempt > 0))
if ok:
@@ -1171,7 +1170,11 @@ class MsgBotApp(ctk.CTk):
else:
if attempt < max_retries - 1:
self._log(f"[{platform}] ⚠ 自动回复发送失败,{retry_delay}秒后重试({attempt+1}/{max_retries})")
time.sleep(retry_delay)
# ⭐ 2026-08-17 修复GUI冻结:原实现主线程 time.sleep(5) ×3 = 界面冻结15秒+
# 窗口不前置时 _paste_and_send 必失败,用户跟踪日志时 GUI 无响应。
# 改为 after() 异步重试:主线程立即返回,5秒后再执行,界面保持流畅。
data["_attempt"] = attempt + 1
self.after(int(retry_delay * 1000), lambda d=data: self._execute_reply(d))
else:
self._log(f"[{platform}] ⚠ 自动回复发送失败(连续{max_retries}次),用户可能在接管,本轮跳过")
finally:
+13 -4
View File
@@ -4966,7 +4966,6 @@ class MessageMonitor:
if m_at:
username = m_at.group(1)
rest = ts[m_at.end():]
ts = rest.lstrip()
# ⭐ Task #1508: 自动入库前校验,防「昵称+正文」整段被当昵称入库。
# 典型污染例子:"@游戏专用PHP有哪两种登录模式?" → 正则贪婪吞掉
# "游戏专用PHP有哪两种登录模式" 整段入库,导致后续匹配时正确正文
@@ -4975,7 +4974,10 @@ class MessageMonitor:
# ② 长度≤12(QQ昵称常见上限,超出多半是昵称+正文拼凑)才入库。
rest_head = rest[:1] if rest else ''
is_clean_at = (len(username) <= 12) and (not rest_head or rest_head in ' \t,,。.!!??;;::\n')
if is_clean_at and username not in nicknames:
if is_clean_at:
# 干净@提及:剥掉昵称,保留 rest
ts = rest.lstrip()
if username not in nicknames:
nicknames.append(username)
try:
from config import load_config, save_config
@@ -4985,8 +4987,15 @@ class MessageMonitor:
logger.info(f"[qq] 自动入库@提及昵称: {username}")
except Exception as e:
logger.warning(f"[qq] 自动入库昵称失败: {e}")
elif username not in nicknames:
logger.debug(f"[qq] 跳过自动入库(疑似昵称+正文拼凑): {username!r}, rest={rest_head!r}")
else:
# ⭐ 2026-08-17 修复:疑似「昵称+正文」拼凑时**保留正文**!
# 原先 ts=rest.lstrip() 会把正文整段吞掉:
# "@咱想了解一下咱那个登录插件去哪儿下载?" → username=18字符,
# rest='?' → ts='?' 只剩问号 → 整条问句丢失。
# 未知昵称+正文粘连无法可靠切分,宁可保留所有字符(只剥@),
# 让 KB 用正文匹配;脏文本进查询的代价 << 消息全部丢失。
ts = ts[1:].lstrip()
logger.debug(f"[qq] @提及疑似昵称+正文拼凑,保留正文: ts={ts[:40]!r} username={username!r} rest={rest_head!r}")
else:
# 无法识别用户名,只剥离@符号
ts = ts[1:].lstrip()