diff --git a/README.md b/README.md index d3ad70a..3b90400 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,7 @@ python3 -m wechatter | 配置项 | 解释 | 备注 | | --- | --- | --- | | `command_prefix` | 机器人命令前缀 | 默认为 `/` ,可以设置为`>>`、`!` 等任意字符 | -| `need_mentioned` | 群聊中的命令是否需要@机器人 | 默认为 `True` | +| `need_mentioned` | 群聊中的命令是否需要@机器人 | 默认为 `False` | ### ⚙️ LLM 配置 diff --git a/wechatter/app/routers/wechat.py b/wechatter/app/routers/wechat.py index 73e6795..e0abed0 100644 --- a/wechatter/app/routers/wechat.py +++ b/wechatter/app/routers/wechat.py @@ -4,7 +4,7 @@ from fastapi import APIRouter, Form, UploadFile from loguru import logger -from wechatter.bot import BotInfo +# from wechatter.bot import BotInfo from wechatter.commands import commands, quoted_handlers from wechatter.config import config from wechatter.database import ( @@ -22,6 +22,11 @@ router = APIRouter() +# 传入命令字典,构造消息处理器 +message_handler = MessageHandler( + commands=commands, quoted_handlers=quoted_handlers, games=games +) + @router.post(config["wx_webhook_recv_api_path"]) async def recv_wechat_msg( @@ -29,18 +34,22 @@ async def recv_wechat_msg( content: Union[UploadFile, str] = Form(), source: str = Form(), is_mentioned: str = Form(alias="isMentioned"), - is_system_event: str = Form(alias="isSystemEvent"), + is_from_self: str = Form(alias="isMsgFromSelf"), ): """ - 接收Docker转发过来的消息的接口 + 用于接收 wxBotWebhook 转发过来的消息的接口 """ # 更新机器人信息(id和name) - BotInfo.update_from_source(source) + # BotInfo.update_from_source(source) + + if type == "unknown": + logger.info(f"收到未知消息:{content}") + return # 判断是否是系统事件 - if is_system_event == "1": - logger.warning(f"收到系统事件:{content}") + if type in ["system_event_login", "system_event_logout", "system_event_error"]: + logger.info(f"收到系统事件:{type}") handle_system_event(content) return @@ -51,29 +60,25 @@ async def recv_wechat_msg( # 解析命令 # 构造消息对象 - message = Message.from_api_msg( + message_obj = Message.from_api_msg( type=type, content=content, source=source, is_mentioned=is_mentioned, + is_from_self=is_from_self, ) # 向群组表中添加该群组 - add_group(message.group) + add_group(message_obj.group) # 向用户表中添加该用户 - add_person(message.person) + add_person(message_obj.person) # 向消息表中添加该消息 - message.id = add_message(message) - # TODO: 添加自己发送的消息,等待 wechatbot-webhook 支持 + message_obj.id = add_message(message_obj) # DEBUG - print(str(message)) + print(str(message_obj)) - # 传入命令字典,构造消息处理器 - message_handler = MessageHandler( - commands=commands, quoted_handlers=quoted_handlers, games=games - ) # 用户发来的消息均送给消息解析器处理 - message_handler.handle_message(message) + message_handler.handle_message(message_obj) # 快捷回复 # return {"success": True, "data": {"type": "text", "content": "hello world!"}} diff --git a/wechatter/config/validate.py b/wechatter/config/validate.py index 4373951..9fc75b0 100644 --- a/wechatter/config/validate.py +++ b/wechatter/config/validate.py @@ -74,6 +74,22 @@ def validate_config(config): error_msg = f"配置参数错误:task_cron_list[{i}].commands[{i2}] 缺少必要字段 {field}" logger.critical(error_msg) raise ValueError(error_msg) + # 微信消息转发配置 + ess_fields = ["from_list"] + for i, rule in enumerate(config["message_forwarding_rule_list"]): + for field in ess_fields: + if field not in rule: + error_msg = f"配置参数错误:message_forwarding_rule_list[{i}] 缺少必要字段 {field}" + logger.critical(error_msg) + raise ValueError(error_msg) + + # 判断这个 rule 是否有 from_list_exclude 字段 + if ( + "from_list_exclude" in rule + and config["bot_name"] not in rule["from_list_exclude"] + ): + # 加入机器人名字,防止机器人自己转发自己的消息,导致死循环刷屏 + rule["from_list_exclude"].append(config["bot_name"]) # Discord 消息转发配置 ess_fields = ["from_list", "webhook_url"] diff --git a/wechatter/message/message_forwarder.py b/wechatter/message/message_forwarder.py index b9c5774..44e254f 100644 --- a/wechatter/message/message_forwarder.py +++ b/wechatter/message/message_forwarder.py @@ -112,14 +112,14 @@ def reply_wechat_forwarded_message(message_obj: Message): 回复转发消息 :param message_obj: 消息对象 """ - assert message_obj.forwarded_source - name, is_group = message_obj.forwarded_source + assert message_obj.forwarded_source_name + name, is_group = message_obj.forwarded_source_name sender.send_msg( name, message_obj.pure_content, is_group=is_group, ) - logger.info(f"回复 {message_obj.forwarded_source} 的转发消息") + logger.info(f"回复 {message_obj.forwarded_source_name} 的转发消息") def remind_official_account_article(self, message_obj: Message): """ diff --git a/wechatter/message/message_handler.py b/wechatter/message/message_handler.py index 1d51005..cb56a66 100644 --- a/wechatter/message/message_handler.py +++ b/wechatter/message/message_handler.py @@ -63,7 +63,7 @@ def handle_message(self, message_obj: Message): # 尝试进行消息转发 message_forwarder.forwarding_to_wechat(message_obj) # 尝试进行转发消息的回复 - if message_obj.forwarded_source: + if message_obj.forwarded_source_name: message_forwarder.reply_wechat_forwarded_message(message_obj) return @@ -74,7 +74,11 @@ def handle_message(self, message_obj: Message): ): message_forwarder.forwarding_to_discord(message_obj) - to = SendTo(person=message_obj.person, group=message_obj.group) + # 判断是否是自己的消息,是则需要将 to 设置为对方 + if message_obj.is_from_self: + to = SendTo(person=message_obj.receiver, group=message_obj.group) + else: + to = SendTo(person=message_obj.person, group=message_obj.group) # 解析命令 content = message_obj.content diff --git a/wechatter/models/wechat/message.py b/wechatter/models/wechat/message.py index e174e22..79f5a33 100644 --- a/wechatter/models/wechat/message.py +++ b/wechatter/models/wechat/message.py @@ -26,6 +26,11 @@ class MessageType(enum.Enum): file = "file" urlLink = "urlLink" friendship = "friendship" + system_event_login = "system_event_login" + system_event_logout = "system_event_logout" + system_event_error = "system_event_error" + system_event_push_notify = "system_event_push_notify" + unknown = "unknown" class MessageSenderType(enum.Enum): @@ -47,8 +52,10 @@ class Message(BaseModel): type: MessageType person: Person group: Optional[Group] = None + receiver: Optional[Person] = None content: str is_mentioned: bool = False + is_from_self: bool = False id: Optional[int] = None @classmethod @@ -58,6 +65,7 @@ def from_api_msg( content: str, source: str, is_mentioned: str, + is_from_self: str = 0, ): """ 从API接口创建消息对象 @@ -65,6 +73,7 @@ def from_api_msg( :param content: 消息内容 :param source: 消息来源 :param is_mentioned: 是否@机器人 + :param is_from_self: 是否是自己发送的消息 :return: 消息对象 """ try: @@ -112,16 +121,34 @@ def from_api_msg( admin_id_list=payload.get("adminIdList", []), member_list=payload.get("memberList", []), ) + + _receiver = None + if source_json.get("to"): + to_payload = source_json.get("to").get("payload", {}) + _receiver = Person( + id=to_payload.get("id", ""), + name=to_payload.get("name", ""), + alias=to_payload.get("alias", ""), + gender="unknown", + is_star=to_payload.get("star", ""), + is_friend=to_payload.get("friend", ""), + ) + _content = content.replace("\u2005", " ", 1) _is_mentioned = False if is_mentioned == "1": _is_mentioned = True + _is_from_self = False + if is_from_self == "1": + _is_from_self = True return cls( type=type, person=_person, group=_group, + receiver=_receiver, content=_content, - is_mentioned=is_mentioned, + is_mentioned=_is_mentioned, + is_from_self=_is_from_self, ) @computed_field @@ -190,10 +217,10 @@ def pure_content(self) -> str: @computed_field @cached_property - def forwarded_source(self) -> Optional[Tuple[str, bool]]: + def forwarded_source_name(self) -> Optional[Tuple[str, bool]]: """ 获取转发消息的来源的名字 - :return: 消息来源的名字和是否为群的元组(source, is_group) + :return: 消息来源的名字和是否为群的元组(source_name, is_group) """ if self.is_quoted: # 先尝试匹配群消息 diff --git a/wechatter/models/wechat/person.py b/wechatter/models/wechat/person.py index 6a49da8..6f327e1 100644 --- a/wechatter/models/wechat/person.py +++ b/wechatter/models/wechat/person.py @@ -1,4 +1,5 @@ import enum +from typing import Optional from pydantic import BaseModel @@ -22,9 +23,9 @@ class Person(BaseModel): name: str alias: str gender: Gender - signature: str - province: str - city: str + signature: Optional[str] = None + province: Optional[str] = None + city: Optional[str] = None # phone_list: List[str] is_star: bool is_friend: bool diff --git a/wechatter/sender/sender.py b/wechatter/sender/sender.py index 322518f..84764b2 100644 --- a/wechatter/sender/sender.py +++ b/wechatter/sender/sender.py @@ -10,6 +10,7 @@ from wechatter.config import config from wechatter.models.wechat import QuotedResponse, SendTo from wechatter.sender.quotable import make_quotable +from wechatter.utils.url_joiner import join_urls # 对retry装饰器重新包装,增加日志输出 @@ -99,10 +100,12 @@ def _post_request( URL = ( - f"{config['wx_webhook_base_api']}/webhook/msg/v2?token={config['wx_webhook_token']}" + join_urls(config["wx_webhook_base_api"], "webhook/msg/v2") + + f"?token={config['wx_webhook_token']}" ) V1_URL = ( - f"{config['wx_webhook_base_api']}/webhook/msg?token={config['wx_webhook_token']}" + join_urls(config["wx_webhook_base_api"], "webhook/msg") + + f"?token={config['wx_webhook_token']}" ) MSG_TYPE = ["text", "fileUrl", "localfile"]