diff --git a/gui.py b/gui.py index f33444e..c6ba7e8 100644 --- a/gui.py +++ b/gui.py @@ -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,19 +1162,21 @@ 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: - self._log(f"[{platform}] 🤖 已自动回复: {answer[:60]}...") - return # 成功直接退出 + # 首次正常发送,重试时 force_retry 跳过60秒冷却 + ok = self.monitor._paste_and_send(platform, answer, force_retry=(attempt > 0)) + if ok: + self._log(f"[{platform}] 🤖 已自动回复: {answer[:60]}...") + return # 成功直接退出 + else: + if attempt < max_retries - 1: + self._log(f"[{platform}] ⚠ 自动回复发送失败,{retry_delay}秒后重试({attempt+1}/{max_retries})") + # ⭐ 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: - if attempt < max_retries - 1: - self._log(f"[{platform}] ⚠ 自动回复发送失败,{retry_delay}秒后重试({attempt+1}/{max_retries})") - time.sleep(retry_delay) - else: - self._log(f"[{platform}] ⚠ 自动回复发送失败(连续{max_retries}次),用户可能在接管,本轮跳过") + self._log(f"[{platform}] ⚠ 自动回复发送失败(连续{max_retries}次),用户可能在接管,本轮跳过") finally: self.monitor._sending_event.clear() diff --git a/monitor.py b/monitor.py index c7a3b40..4b54677 100644 --- a/monitor.py +++ b/monitor.py @@ -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,18 +4974,28 @@ 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: - nicknames.append(username) - try: - from config import load_config, save_config - cfg = load_config() - cfg.setdefault('qq_nicknames', []).append(username) - save_config(cfg) - 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}") + if is_clean_at: + # 干净@提及:剥掉昵称,保留 rest + ts = rest.lstrip() + if username not in nicknames: + nicknames.append(username) + try: + from config import load_config, save_config + cfg = load_config() + cfg.setdefault('qq_nicknames', []).append(username) + save_config(cfg) + logger.info(f"[qq] 自动入库@提及昵称: {username}") + except Exception as e: + logger.warning(f"[qq] 自动入库昵称失败: {e}") + 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()