From 516e82483ddb5fbadd823c84a54cd0b91bbe2983 Mon Sep 17 00:00:00 2001 From: Prometheus-first <524276169@qq.com> Date: Fri, 16 Oct 2026 12:26:23 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E7=BE=A4id=E5=92=8C?= =?UTF-8?q?=E7=A7=81=E8=81=8Aid=E7=9A=84=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 ++++++ native/text_service.py | 18 ++-------- tests/test_account.py | 16 +++++++++ tests/test_framework.py | 74 ++++++++++++++++++++++++++++++++++++++ wxbot_framework/runtime.py | 7 ++++ 5 files changed, 110 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 490470f..07d7d32 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,16 @@ python main.py `--check` 只检查配置、插件清单与语法,不注入微信,也不调用模型。正常运行前打开微信并登录配置中的账号。 +默认 `INFO` 日志级别下,启动后会在控制台实时显示当前连接的微信账号、收到私聊的发送者 ID 和正文,以及收到群消息的群 ID;这些日志也会保存到 `logs/bot.log`: + +```text +当前登录微信ID=wxid_example_robot;好友和群聊文本已就绪 +私聊消息 | 发送者微信ID=wxid_example_friend | 内容="你好" +群消息 | 群ID=12345678@chatroom +``` + +消息显示在会话白名单过滤之前,因此新群尚未加入白名单时,也可以让群里的人发一条文本,从控制台复制群 ID。自动消息日志不打印群正文或群成员 ID;总结功能使用的群聊天缓存按原有配置保存。私聊中的换行会显示为 `\n`,一条消息占一行。`logs/` 已被 Git 忽略。 + 也可以运行 `start_bot.ps1`:脚本优先使用项目的 `.venv`,其次使用 PATH 中的 Python;可通过 `-Python` 或环境变量 `WXBOT_PYTHON` 指定解释器,不依赖某台电脑的安装路径。 控制台命令: diff --git a/native/text_service.py b/native/text_service.py index 6b6c663..b8c0c26 100644 --- a/native/text_service.py +++ b/native/text_service.py @@ -77,7 +77,7 @@ class NativeTextService(NativeFileHelperService): # 手动指定 PID 时也先只读核对,避免向其他账号加载 DLL。 raise RuntimeError("目标进程登录的不是绑定的机器人账号,等待正确账号登录") result = super().start() - logger.info("当前账号已自动识别:%s;好友和群聊文本已就绪", self.self_wxid) + logger.info("当前登录微信ID=%s;好友和群聊文本已就绪", self.self_wxid) return result def _read_account(self): @@ -143,20 +143,8 @@ class NativeTextService(NativeFileHelperService): data["dispatch_to_plugins"] = False elif data["is_at_self"]: data["dispatch_to_plugins"] = True - if "WXBOT_AT_CHAT_PROBE" in data["msg"]: - import json - from pathlib import Path - path = Path(__file__).resolve().parents[1] / ".runtime/at_receive_probe.json" - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(json.dumps({"room": data["chat_id"], "sender": data["sender_id"], - "at_members": data["at_members"], "is_at_self": data["is_at_self"], - "source": source}, ensure_ascii=False, indent=2), encoding="utf-8") - logger.info("艾特接收探针:at_members=%s is_at_self=%s", data["at_members"], data["is_at_self"]) self.chats[data["chat_id"]] = {"chat_id": data["chat_id"], "is_group": bool(data["room_wxid"])} - if data["msg"].startswith(("WXBOT_", "echo WXBOT_")): - logger.info("测试消息已接收:chat_id=%s sender_id=%s group=%s marker=%s", - data["chat_id"], data["sender_id"], bool(data["room_wxid"]), data["msg"]) elif self.router.unparsed_groups > previous_unparsed: # 第一条失败时提示;后续通过 status 查看累计数。 if self.router.unparsed_groups == 1: @@ -180,7 +168,7 @@ class NativeTextService(NativeFileHelperService): self.last_result = self._run("--dispatch", pid=self.pid, text=content, target=to_wxid, keep_loaded=True, preserve_report=self.preserve_reports, mentions=mentions) if content.startswith("WXBOT_"): - logger.info("测试文本发送完成:chat_id=%s marker=%s success=%s native_thread=%s", - to_wxid, content, self.last_result["success"], + logger.info("测试文本发送完成:chat_id=%s success=%s native_thread=%s", + to_wxid, self.last_result["success"], self.last_result.get("report", {}).get("thread_id")) return self.last_result["success"] diff --git a/tests/test_account.py b/tests/test_account.py index a29195e..08ac897 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -178,6 +178,22 @@ class AccountTests(unittest.TestCase): with self.assertRaisesRegex(RuntimeError, "自动等待"): service._prepare_channel() + @unittest.skipUnless(sys.platform == "win32", "本机原生服务检查") + def test_start_logs_identity_read_from_current_process(self): + sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "native")) + from text_service import NativeTextService + service = NativeTextService(pid=123) + + def prepare_channel(): + service.router.set_account(SELF) + return True + + with patch("filehelper_service.NativeFileHelperService.start", side_effect=prepare_channel), \ + self.assertLogs("NativeText", "INFO") as captured: + self.assertTrue(service.start()) + self.assertIn(SELF, "\n".join(captured.output)) + self.assertIn("当前登录微信ID", "\n".join(captured.output)) + @unittest.skipUnless(sys.platform == "win32", "本机原生服务检查") def test_explicit_wrong_pid_is_rejected_before_loading_bridge(self): sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "native")) diff --git a/tests/test_framework.py b/tests/test_framework.py index eaf90cd..fe33904 100644 --- a/tests/test_framework.py +++ b/tests/test_framework.py @@ -12,6 +12,7 @@ import threading import time import unittest import uuid +from unittest.mock import Mock, patch from wxbot_framework.backend import CAPABILITIES from wxbot_framework.config import ConfigStore @@ -285,6 +286,79 @@ class FrameworkTests(unittest.TestCase): finally: app.plugins.shutdown() + def test_new_group_is_logged_before_allowlist_without_body_or_member(self): + self.config["chat_allowlist"] = ["filehelper"] + self.write_config() + app = BotApplication(self.root, self.store, backend_factory=lambda: self.backend) + app.members = Mock() + room = "87654321@chatroom" + sender = "wxid_example_member" + body = "本条群正文不应出现在日志中" + # 同时覆盖标准消息和仅有会话 ID、缺少群标记的消息,避免误当私聊泄露正文。 + logging.disable(logging.NOTSET) + with patch.object(app.plugins, "dispatch") as dispatch, self.assertLogs("Bot", "INFO") as captured: + for is_group in (True, False): + app._emit(Message(body, room, sender, is_group=is_group)) + self.assertEqual(len(captured.records), 2) + for record in captured.records: + self.assertIn(room, record.getMessage()) + self.assertNotIn(body, record.getMessage()) + self.assertNotIn(sender, record.getMessage()) + app.members.record.assert_not_called() + dispatch.assert_not_called() + + def test_private_log_keeps_sender_and_unicode_but_escapes_line_breaks(self): + app = BotApplication(self.root, self.store, backend_factory=lambda: self.backend) + sender = "wxid_example_sender" + body = "你好 🌸\r\n第二行\t100%" + # 文件传输助手的会话 ID 与实际作者不同,必须显示作者。 + message = Message.from_native(11046, {"msg": body, "chat_id": "filehelper", + "sender_id": sender, "dispatch_to_plugins": False}, "logging") + logging.disable(logging.NOTSET) + with self.assertLogs("Bot", "INFO") as captured: + app._emit(message) + self.assertEqual(len(captured.records), 1) + rendered = captured.records[0].getMessage() + self.assertIn(sender, rendered) + self.assertIn("你好 🌸", rendered) + self.assertNotIn("\n", rendered) + self.assertNotIn("\r", rendered) + self.assertEqual(json.loads(rendered.split("内容=", 1)[1]), body) + + @unittest.skipUnless(sys.platform == "win32", "本机原生服务检查") + def test_native_test_messages_do_not_bypass_group_log_privacy(self): + native = str(ROOT / "native") + if native not in sys.path: + sys.path.insert(0, native) + from text_service import NativeTextService + from filehelper_service import ReceivedTextMessage + service = NativeTextService(pid=123) + account, sender, room = "wxid_example_self", "wxid_example_member", "87654321@chatroom" + service.router.set_account(account) + service._sender_wechat_name = Mock(return_value=None) + app = BotApplication(self.root, self.store, backend_factory=lambda: self.backend) + markers = ("WXBOT_AT_CHAT_PROBE", "WXBOT_GROUP_TEST", "echo WXBOT_GROUP_TEST") + logging.disable(logging.NOTSET) + with self.assertLogs(level="INFO") as captured: + for body in markers: + raw = ReceivedTextMessage(type=1, sender=room.encode(), receiver=account.encode(), + text=(sender + ":\n" + body).encode()) + app._emit(Message.from_native(11046, service._map_message(raw), "logging")) + self.assertEqual(len(captured.records), len(markers)) + rendered = "\n".join(captured.output) + self.assertIn(room, rendered) + self.assertNotIn(sender, rendered) + for body in markers: + self.assertNotIn(body, rendered) + + service._owns_bridge = True + service._check_account = Mock() + service._run = Mock(return_value={"success": True}) + with self.assertLogs("NativeText", "INFO") as sent_logs: + self.assertTrue(service.send_text(room, "WXBOT_GROUP_SEND_TEST")) + self.assertIn(room, "\n".join(sent_logs.output)) + self.assertNotIn("WXBOT_GROUP_SEND_TEST", "\n".join(sent_logs.output)) + if __name__ == "__main__": unittest.main() diff --git a/wxbot_framework/runtime.py b/wxbot_framework/runtime.py index f313b5a..447038a 100644 --- a/wxbot_framework/runtime.py +++ b/wxbot_framework/runtime.py @@ -61,6 +61,13 @@ class BotApplication: self._disconnect() def _emit(self, message): + # 先显示会话 ID,未加入白名单的新群也能被发现;群正文不进入消息日志。 + if message.is_group or message.chat_id.endswith("@chatroom"): + logger.info("群消息 | 群ID=%s", message.chat_id) + else: + logger.info("私聊消息 | 发送者微信ID=%s | 内容=%s", + message.sender_id or message.chat_id, + json.dumps(message.text, ensure_ascii=False)) allowed = self.config.get("chat_allowlist", []) if not allowed or message.chat_id in allowed: if self.members is not None: