diff --git a/auto_reply.py b/auto_reply.py index a9977eb..226bbf3 100644 --- a/auto_reply.py +++ b/auto_reply.py @@ -67,15 +67,19 @@ class AutoReply: def _get_answer(self, platform: str, msg: str, doc: dict, score: float) -> str or None: """提取答案 + 防重复检查。 优先返回 doc['answer'](Q&A 段),兜底用整段 content。 + 非 Q&A 文档限制返回内容首 500 字符,避免子串匹配触发后返回整篇文件。 """ - answer = (doc.get("answer") or doc["content"]).strip() + answer = doc.get("answer") + if answer: + answer = answer.strip() + else: + # 非 Q&A 文档 → 取 content 前半段(最多 500 字符),不返回全文 + content = doc.get("content", "").strip() + answer = content[:497] + "..." if len(content) > 500 else content + if not answer: return None - # 截取过长内容 - if len(answer) > 500: - answer = answer[:497] + "..." - # 防重复指纹 fp = hash(f"{platform}:{msg}:{answer[:80]}") replied_set = self._replied.setdefault(platform, set()) diff --git a/knowledge_base.py b/knowledge_base.py index d9d5f78..bb9f029 100644 --- a/knowledge_base.py +++ b/knowledge_base.py @@ -151,11 +151,13 @@ class KnowledgeBase: # Fallback: 子串匹配(查询包含在文档标题或内容中) q = query.strip() - for doc in self.docs: - if q in doc["content"] or q in doc["title"]: - logger = logging.getLogger("msgbot.kb") - logger.debug(f"子串匹配: \"{q}\" → {doc['title']}") - return [(doc, min_score)] + # 短文本(<4 字符)不做子串匹配,避免"19:15""14:30"等时间戳误匹配 KB 文档 + if len(q) >= 4: + for doc in self.docs: + if q in doc["content"] or q in doc["title"]: + logger = logging.getLogger("msgbot.kb") + logger.debug(f"子串匹配: \"{q}\" → {doc['title']}") + return [(doc, min_score)] return results def get_doc_count(self) -> int: diff --git a/main.py b/main.py index 0dd0316..2f141f2 100644 --- a/main.py +++ b/main.py @@ -3,6 +3,7 @@ import logging import os import sys +import signal # 确保在项目目录 os.chdir(os.path.dirname(os.path.abspath(__file__))) @@ -42,6 +43,17 @@ def main(): sys.exit(1) app = MsgBotApp() + + # Ctrl+C 信号处理:Windows Tkinter mainloop 不响应 KeyboardInterrupt + # 需 signal handler 强制停止监控 + 退出 + def _sigint_handler(sig, frame): + logger.info("收到 Ctrl+C,正在关闭...") + app.monitor.stop() + app.quit() + sys.exit(0) + + signal.signal(signal.SIGINT, _sigint_handler) + app.mainloop() diff --git a/monitor.py b/monitor.py index 376d2ed..7a6180c 100644 --- a/monitor.py +++ b/monitor.py @@ -16,7 +16,7 @@ import ctypes import difflib import os from datetime import datetime -from concurrent.futures import ThreadPoolExecutor, TimeoutError as FuturesTimeout +import signal from PIL import ImageGrab from winrt.windows.media.ocr import OcrEngine from winrt.windows.globalization import Language @@ -212,6 +212,26 @@ class MessageMonitor: pass return None + def _ensure_hwnd_with_timeout(self, platform: str, timeout: float = 12.0) -> Optional[int]: + """_ensure_hwnd 包装 daemon 线程超时保护。 + + ThreadPoolExecutor 创建非 daemon 线程 → Ctrl+C 进程不退出。 + 改用 daemon thread + Event.wait(timeout),daemon 线程不阻塞进程退出。 + """ + result = [None] + done = threading.Event() + + def worker(): + result[0] = self._ensure_hwnd(platform) + done.set() + + t = threading.Thread(target=worker, daemon=True) + t.start() + if not done.wait(timeout=timeout): + logger.warning(f"[{platform}] _ensure_hwnd 超时({timeout:.0f}s),跳过本轮") + return None + return result[0] + # ── 强制刷新 UIA ────────────────────────────────────────── def _fresh_window(self, platform: str): @@ -1411,16 +1431,7 @@ class MessageMonitor: self._poll_count[platform] = counter # 1. 确保窗口句柄(带超时保护,防止 UIA COM 调用挂死不返回) - hwnd = None - _ex = ThreadPoolExecutor(max_workers=1) - try: - hwnd = _ex.submit(self._ensure_hwnd, platform).result(timeout=12.0) - except FuturesTimeout: - logger.warning(f"[{platform}] _ensure_hwnd 超时(12s),跳过本轮") - except Exception as e: - logger.warning(f"[{platform}] _ensure_hwnd 异常: {e}") - finally: - _ex.shutdown(wait=False) + hwnd = self._ensure_hwnd_with_timeout(platform, timeout=12.0) if not hwnd: if counter % 10 == 0: logger.debug(f"[{platform}] 窗口未找到") @@ -1499,6 +1510,14 @@ class MessageMonitor: MAX_NEW_ITEMS = 3 new_items = new_items[:MAX_NEW_ITEMS] + # 过滤纯时间/日期文本 → 不应进入 KB 匹配(OCR 时间戳分割遗留的噪音片段) + # 这些文本在 KB 子串匹配阶段会命中文件内任何含相同数字的内容 + new_items = [ + it for it in new_items + if not re.match(r'^\d{1,2}[::]\d{2}(?:[::]\d{2})?$', it.strip()) + and not re.match(r'^\d{4}[/-]\d{1,2}', it.strip()) + ] + # 更新 last_texts 与签名 self._last_texts[platform] = list(texts) self._last_sig[platform] = sig diff --git a/ocr_debug_20260726_144250.png b/ocr_debug_20260726_144250.png new file mode 100644 index 0000000..95a1fc0 Binary files /dev/null and b/ocr_debug_20260726_144250.png differ diff --git a/ocr_debug_20260726_144255.png b/ocr_debug_20260726_144255.png new file mode 100644 index 0000000..95a1fc0 Binary files /dev/null and b/ocr_debug_20260726_144255.png differ diff --git a/ocr_debug_20260726_144300.png b/ocr_debug_20260726_144300.png new file mode 100644 index 0000000..95a1fc0 Binary files /dev/null and b/ocr_debug_20260726_144300.png differ diff --git a/ocr_debug_20260726_144305.png b/ocr_debug_20260726_144305.png new file mode 100644 index 0000000..95a1fc0 Binary files /dev/null and b/ocr_debug_20260726_144305.png differ diff --git a/ocr_debug_20260726_144312.png b/ocr_debug_20260726_144312.png new file mode 100644 index 0000000..95a1fc0 Binary files /dev/null and b/ocr_debug_20260726_144312.png differ diff --git a/ocr_debug_20260726_144905.png b/ocr_debug_20260726_144905.png new file mode 100644 index 0000000..5ae0dff Binary files /dev/null and b/ocr_debug_20260726_144905.png differ diff --git a/ocr_debug_20260726_144910.png b/ocr_debug_20260726_144910.png new file mode 100644 index 0000000..5ae0dff Binary files /dev/null and b/ocr_debug_20260726_144910.png differ diff --git a/ocr_debug_20260726_144915.png b/ocr_debug_20260726_144915.png new file mode 100644 index 0000000..5ae0dff Binary files /dev/null and b/ocr_debug_20260726_144915.png differ diff --git a/ocr_debug_20260726_144920.png b/ocr_debug_20260726_144920.png new file mode 100644 index 0000000..5ae0dff Binary files /dev/null and b/ocr_debug_20260726_144920.png differ diff --git a/ocr_debug_20260726_144925.png b/ocr_debug_20260726_144925.png new file mode 100644 index 0000000..5ae0dff Binary files /dev/null and b/ocr_debug_20260726_144925.png differ diff --git a/ocr_debug_20260726_144930.png b/ocr_debug_20260726_144930.png new file mode 100644 index 0000000..5ae0dff Binary files /dev/null and b/ocr_debug_20260726_144930.png differ diff --git a/ocr_debug_20260726_151531.png b/ocr_debug_20260726_151531.png new file mode 100644 index 0000000..ea8cc1b Binary files /dev/null and b/ocr_debug_20260726_151531.png differ