Files
helper/wxbot_framework/text.py
T
2026-10-15 18:27:46 +08:00

108 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""纯 Python 的文本会话解析;不依赖 Windows 或原生 DLL。"""
from __future__ import annotations
import re
WXID = re.compile(r"^[A-Za-z0-9_.@-]{1,127}$")
SYSTEM_PEERS = frozenset({"weixin", "fmessage", "newsapp", "floatbottle"})
GROUP_CONTROL_COMMANDS = frozenset({"/help", "成语接龙", "合并接龙", "退出接龙"})
FOUR_CHINESE = re.compile(r"^[\u3400-\u4dbf\u4e00-\u9fff]{4}$")
def own_group_command(text):
value = text.strip()
return bool(value in GROUP_CONTROL_COMMANDS or FOUR_CHINESE.fullmatch(value) or
value.split(maxsplit=1)[:1] in (["/绑定"], ["/总结"], ["/总结群聊"], ["/切换人格"], ["/清空"]))
def validate_target(target):
if not isinstance(target, str) or not WXID.fullmatch(target):
raise ValueError("目标应为微信 ID 或群 ID,长度最多 127 个 ASCII 字符")
return target
def validate_mentions(room, members):
validate_target(room)
if not room.endswith("@chatroom"):
raise ValueError("艾特消息的目标必须是群 ID")
if not isinstance(members, (list, tuple)) or not 1 <= len(members) <= 8:
raise ValueError("at_list 应包含 1–8 个成员微信 ID")
from .account import validate_account
result = tuple(validate_account(member) for member in members)
if len(set(result)) != len(result):
raise ValueError("at_list 不能重复")
return result
class TextRouter:
"""先识别本账号,再区分会话与作者,避免回复自己同步过来的消息。"""
def __init__(self, *, include_self_groups=False):
self.include_self_groups = include_self_groups
self.self_wxid = ""
self.unidentified = 0
self.unparsed_groups = 0
self.ignored_self = 0
def set_account(self, account):
from .account import validate_account
validate_account(account)
if self.self_wxid and self.self_wxid != account:
raise RuntimeError("当前登录账号发生变化,需要关闭旧连接后重新识别")
self.self_wxid = account
def convert(self, sender, receiver, text, *, sequence=0, thread_id=0):
validate_target(sender)
validate_target(receiver)
filehelper = "filehelper" in (sender, receiver)
incoming_group = sender.endswith("@chatroom")
# 文件传输助手的另一端,以及收到群消息的接收方,都是本账号。
identified = (receiver if sender == "filehelper" else sender) if filehelper else (
receiver if incoming_group else "")
if identified:
validate_target(identified)
if identified == "filehelper" or identified.endswith("@chatroom"):
return None
self.set_account(identified)
if not self.self_wxid:
self.unidentified += 1
return None
is_self = sender == self.self_wxid
if filehelper:
chat, author, room, body = "filehelper", sender, "", text
elif incoming_group:
if receiver != self.self_wxid:
return None
author, separator, body = text.partition(":\n")
# Sync AddMsg 的群文本包含“成员 ID:\n正文”;不猜测缺失的作者。
if not separator or not WXID.fullmatch(author) or author.endswith("@chatroom"):
self.unparsed_groups += 1
return None
chat = room = sender
is_self = author == self.self_wxid
elif receiver.endswith("@chatroom"):
if not is_self:
return None
chat = room = receiver
author, body = sender, text
elif receiver == self.self_wxid:
chat, author, room, body = sender, sender, "", text
elif is_self:
chat, author, room, body = receiver, sender, "", text
else:
return None
own_group_input = bool(room and own_group_command(body))
if not filehelper and is_self and not own_group_input and not (room and self.include_self_groups):
self.ignored_self += 1
return None
if chat.startswith("gh_") or chat in SYSTEM_PEERS:
return None
return {"msg": body, "content": body, "from_wxid": author if room else chat,
"room_wxid": room, "to_wxid": receiver, "wx_type": 1,
"chat_id": chat, "sender_id": author, "is_self": is_self,
"self_wxid": self.self_wxid, "native_sender": sender,
"native_receiver": receiver, "sequence": sequence,
"dispatch_to_plugins": not (room and is_self and not own_group_input),
"native_thread_id": thread_id}