增加了群id和私聊id的显示
This commit is contained in:
@@ -61,6 +61,16 @@ python main.py
|
|||||||
|
|
||||||
`--check` 只检查配置、插件清单与语法,不注入微信,也不调用模型。正常运行前打开微信并登录配置中的账号。
|
`--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` 指定解释器,不依赖某台电脑的安装路径。
|
也可以运行 `start_bot.ps1`:脚本优先使用项目的 `.venv`,其次使用 PATH 中的 Python;可通过 `-Python` 或环境变量 `WXBOT_PYTHON` 指定解释器,不依赖某台电脑的安装路径。
|
||||||
|
|
||||||
控制台命令:
|
控制台命令:
|
||||||
|
|||||||
+3
-15
@@ -77,7 +77,7 @@ class NativeTextService(NativeFileHelperService):
|
|||||||
# 手动指定 PID 时也先只读核对,避免向其他账号加载 DLL。
|
# 手动指定 PID 时也先只读核对,避免向其他账号加载 DLL。
|
||||||
raise RuntimeError("目标进程登录的不是绑定的机器人账号,等待正确账号登录")
|
raise RuntimeError("目标进程登录的不是绑定的机器人账号,等待正确账号登录")
|
||||||
result = super().start()
|
result = super().start()
|
||||||
logger.info("当前账号已自动识别:%s;好友和群聊文本已就绪", self.self_wxid)
|
logger.info("当前登录微信ID=%s;好友和群聊文本已就绪", self.self_wxid)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _read_account(self):
|
def _read_account(self):
|
||||||
@@ -143,20 +143,8 @@ class NativeTextService(NativeFileHelperService):
|
|||||||
data["dispatch_to_plugins"] = False
|
data["dispatch_to_plugins"] = False
|
||||||
elif data["is_at_self"]:
|
elif data["is_at_self"]:
|
||||||
data["dispatch_to_plugins"] = True
|
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"],
|
self.chats[data["chat_id"]] = {"chat_id": data["chat_id"],
|
||||||
"is_group": bool(data["room_wxid"])}
|
"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:
|
elif self.router.unparsed_groups > previous_unparsed:
|
||||||
# 第一条失败时提示;后续通过 status 查看累计数。
|
# 第一条失败时提示;后续通过 status 查看累计数。
|
||||||
if self.router.unparsed_groups == 1:
|
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,
|
self.last_result = self._run("--dispatch", pid=self.pid, text=content, target=to_wxid,
|
||||||
keep_loaded=True, preserve_report=self.preserve_reports, mentions=mentions)
|
keep_loaded=True, preserve_report=self.preserve_reports, mentions=mentions)
|
||||||
if content.startswith("WXBOT_"):
|
if content.startswith("WXBOT_"):
|
||||||
logger.info("测试文本发送完成:chat_id=%s marker=%s success=%s native_thread=%s",
|
logger.info("测试文本发送完成:chat_id=%s success=%s native_thread=%s",
|
||||||
to_wxid, content, self.last_result["success"],
|
to_wxid, self.last_result["success"],
|
||||||
self.last_result.get("report", {}).get("thread_id"))
|
self.last_result.get("report", {}).get("thread_id"))
|
||||||
return self.last_result["success"]
|
return self.last_result["success"]
|
||||||
|
|||||||
@@ -178,6 +178,22 @@ class AccountTests(unittest.TestCase):
|
|||||||
with self.assertRaisesRegex(RuntimeError, "自动等待"):
|
with self.assertRaisesRegex(RuntimeError, "自动等待"):
|
||||||
service._prepare_channel()
|
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", "本机原生服务检查")
|
@unittest.skipUnless(sys.platform == "win32", "本机原生服务检查")
|
||||||
def test_explicit_wrong_pid_is_rejected_before_loading_bridge(self):
|
def test_explicit_wrong_pid_is_rejected_before_loading_bridge(self):
|
||||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "native"))
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "native"))
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import threading
|
|||||||
import time
|
import time
|
||||||
import unittest
|
import unittest
|
||||||
import uuid
|
import uuid
|
||||||
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
from wxbot_framework.backend import CAPABILITIES
|
from wxbot_framework.backend import CAPABILITIES
|
||||||
from wxbot_framework.config import ConfigStore
|
from wxbot_framework.config import ConfigStore
|
||||||
@@ -285,6 +286,79 @@ class FrameworkTests(unittest.TestCase):
|
|||||||
finally:
|
finally:
|
||||||
app.plugins.shutdown()
|
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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -61,6 +61,13 @@ class BotApplication:
|
|||||||
self._disconnect()
|
self._disconnect()
|
||||||
|
|
||||||
def _emit(self, message):
|
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", [])
|
allowed = self.config.get("chat_allowlist", [])
|
||||||
if not allowed or message.chat_id in allowed:
|
if not allowed or message.chat_id in allowed:
|
||||||
if self.members is not None:
|
if self.members is not None:
|
||||||
|
|||||||
Reference in New Issue
Block a user