From b6235b2de8e5fd6a9a598f1c6976f5b10becedcf Mon Sep 17 00:00:00 2001 From: Cassius0924 <2670226747@qq.com> Date: Mon, 4 Mar 2024 20:04:45 +0800 Subject: [PATCH 1/4] =?UTF-8?q?docs:=20=E6=9B=B4=E6=96=B0=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 +-- .../{copilot_gpt4.py => openai_chat_gpt.py} | 70 +++++++++---------- 2 files changed, 39 insertions(+), 39 deletions(-) rename wechatter/commands/_commands/{copilot_gpt4.py => openai_chat_gpt.py} (88%) diff --git a/README.md b/README.md index ccb49bf..d3ad70a 100644 --- a/README.md +++ b/README.md @@ -226,10 +226,10 @@ python3 -m wechatter ### ⚙️ Discord Message Forwarding 配置 -| 配置项 | 解释 | 备注 | -| --- | --- | --- | -| `discord_message_forwarding_enabled` | 功能开关,是否开启 Discord 消息转发 | 默认为 `False` | -| `discord_message_forwarding_rule_list` | 消息规则列表,每个规则包含三个字段:`from_list`、`to_discord_webhook_url` 和 `to_discord_webhook_name` | | +| 配置项 | 子项 | 解释 | 备注 | +| --- | --- | --- | --- | +| `discord_message_forwarding_enabled` | | 功能开关,是否开启 Discord 消息转发 | 默认为 `False` | +| `discord_message_forwarding_rule_list` | | 消息规则列表,每个规则包含三个字段:`from_list`、`to_discord_webhook_url` 和 `to_discord_webhook_name` | | | `from_list` | 消息转发来源列表,即消息发送者 | 可以填多个用户名称或群名称,若要转发所有消息则使用 `["%ALL"]` | | | `from_list_exclude` | 消息转发来源排除列表,不转发此列表的用户和群 | 只在 `from_list` 为 `["%ALL"]` 时生效 | | | `discord_webhook_url` | 消息转发目标 Discord Webhook URL | | diff --git a/wechatter/commands/_commands/copilot_gpt4.py b/wechatter/commands/_commands/openai_chat_gpt.py similarity index 88% rename from wechatter/commands/_commands/copilot_gpt4.py rename to wechatter/commands/_commands/openai_chat_gpt.py index b3be78c..d44b899 100644 --- a/wechatter/commands/_commands/copilot_gpt4.py +++ b/wechatter/commands/_commands/openai_chat_gpt.py @@ -120,12 +120,12 @@ def gpt4_remove_command_handler( def _gptx(model: str, to: SendTo, message: str = "", message_obj=None) -> None: person = to.person # 获取文件夹下最新的对话记录 - chat_info = CopilotGPT4.get_chatting_chat_info(person, model) + chat_info = OpenaiChatGPT.get_chatting_chat_info(person, model) if message == "": # /gpt4 # 判断对话是否有效 sender.send_msg(to, "正在创建新对话...") - if chat_info is None or CopilotGPT4._is_chat_valid(chat_info): - CopilotGPT4.create_chat(person, model) + if chat_info is None or OpenaiChatGPT._is_chat_valid(chat_info): + OpenaiChatGPT.create_chat(person, model) logger.info("创建新对话成功") sender.send_msg(to, "创建新对话成功") return @@ -135,23 +135,23 @@ def _gptx(model: str, to: SendTo, message: str = "", message_obj=None) -> None: # 如果没有对话记录,则创建新对话 sender.send_msg(to, f"正在调用 {model} 进行对话...") if chat_info is None: - chat_info = CopilotGPT4.create_chat(person, model) + chat_info = OpenaiChatGPT.create_chat(person, model) logger.info("无历史对话记录,创建新对话成功") sender.send_msg(to, "无历史对话记录,创建新对话成功") try: - response = CopilotGPT4.chat( + response = OpenaiChatGPT.chat( chat_info, message=message, message_obj=message_obj ) logger.info(response) sender.send_msg(to, response) except Exception as e: - error_message = f"调用Copilot-GPT4-Server失败,错误信息:{str(e)}" + error_message = f"调用 ChatGPT 服务失败,错误信息:{str(e)}" logger.error(error_message) sender.send_msg(to, error_message) def _gptx_chats(model: str, to: SendTo, message: str = "", message_obj=None) -> None: - response = CopilotGPT4.get_chat_list_str(to.person, model) + response = OpenaiChatGPT.get_chat_list_str(to.person, model) sender.send_msg(to, response) @@ -159,15 +159,15 @@ def _gptx_record(model: str, to: SendTo, message: str = ""): person = to.person if message == "": # 获取当前对话的对话记录 - chat_info = CopilotGPT4.get_chatting_chat_info(person, model) + chat_info = OpenaiChatGPT.get_chatting_chat_info(person, model) else: # 获取指定对话的对话记录 - chat_info = CopilotGPT4.get_chat_info(person, model, int(message)) + chat_info = OpenaiChatGPT.get_chat_info(person, model, int(message)) if chat_info is None: logger.warning("对话不存在") sender.send_msg(to, "对话不存在") return - response = CopilotGPT4.get_brief_conversation_str(chat_info) + response = OpenaiChatGPT.get_brief_conversation_str(chat_info) logger.info(response) sender.send_msg(to, response) @@ -180,7 +180,7 @@ def _gptx_continue(model: str, to: SendTo, message: str = "") -> None: sender.send_msg(to, "请输入对话记录编号") return sender.send_msg(to, f"正在切换到对话记录 {message}...") - chat_info = CopilotGPT4.continue_chat( + chat_info = OpenaiChatGPT.continue_chat( person=person, model=model, chat_index=int(message) ) if chat_info is None: @@ -188,14 +188,14 @@ def _gptx_continue(model: str, to: SendTo, message: str = "") -> None: logger.warning(warning_message) sender.send_msg(to, warning_message) return - response = CopilotGPT4.get_brief_conversation_str(chat_info) + response = OpenaiChatGPT.get_brief_conversation_str(chat_info) response += "====================\n" response += "对话已选中,输入命令继续对话" logger.info(response) sender.send_msg(to, response) -class CopilotGPT4: +class OpenaiChatGPT: chat_api = join_urls(config["openai_base_api"], "v1/chat/completions") token = "Bearer " + config["openai_token"] @@ -208,8 +208,8 @@ def create_chat(person: Person, model: str) -> GptChatInfo: :return: 新的对话信息 """ # 生成上一次对话的主题 - CopilotGPT4._save_chatting_chat_topic(person, model) - CopilotGPT4._set_all_chats_not_chatting(person, model) + OpenaiChatGPT._save_chatting_chat_topic(person, model) + OpenaiChatGPT._set_all_chats_not_chatting(person, model) gpt_chat_info = GptChatInfo( person=person, model=model, @@ -237,18 +237,18 @@ def continue_chat( :return: 对话信息 """ # 读取对话记录文件 - chat_info = CopilotGPT4.get_chat_info(person, model, chat_index) + chat_info = OpenaiChatGPT.get_chat_info(person, model, chat_index) if chat_info is None: return None - chatting_chat_info = CopilotGPT4.get_chatting_chat_info(person, model) + chatting_chat_info = OpenaiChatGPT.get_chatting_chat_info(person, model) if chatting_chat_info: - if not CopilotGPT4._is_chat_valid(chatting_chat_info): + if not OpenaiChatGPT._is_chat_valid(chatting_chat_info): # 如果对话无效,则删除该对话记录后再继续对话 - CopilotGPT4._delete_chat(chatting_chat_info) + OpenaiChatGPT._delete_chat(chatting_chat_info) else: # 生成上一次对话的主题 - CopilotGPT4._save_chatting_chat_topic(person, model) - CopilotGPT4._set_chatting_chat(person, model, chat_info) + OpenaiChatGPT._save_chatting_chat_topic(person, model) + OpenaiChatGPT._set_chatting_chat(person, model, chat_info) return chat_info @staticmethod @@ -257,7 +257,7 @@ def _set_chatting_chat(person: Person, model: str, chat_info: GptChatInfo) -> No 设置正在进行中的对话记录 """ # 先将所有对话记录的 is_chatting 字段设置为 False - CopilotGPT4._set_all_chats_not_chatting(person, model) + OpenaiChatGPT._set_all_chats_not_chatting(person, model) with make_db_session() as session: chat_info = session.query(DbGptChatInfo).filter_by(id=chat_info.id).first() if chat_info is None: @@ -349,7 +349,7 @@ def get_chat_list_str(person: Person, model: str) -> str: :param model: 模型 :return: 对话记录 """ - chat_info_list = CopilotGPT4._list_chat_info(person, model) + chat_info_list = OpenaiChatGPT._list_chat_info(person, model) chat_info_list_str = f"✨==={model}对话记录===✨\n" if not chat_info_list: chat_info_list_str += " 📭 无对话记录" @@ -374,7 +374,7 @@ def get_chat_info( :param chat_index: 对话记录索引(从1开始) :return: 对话信息 """ - chat_info_id_list = CopilotGPT4._list_chat_info(person, model) + chat_info_id_list = OpenaiChatGPT._list_chat_info(person, model) if not chat_info_id_list: return None if chat_index <= 0 or chat_index > len(chat_info_id_list): @@ -409,7 +409,7 @@ def chat(chat_info: GptChatInfo, message: str, message_obj) -> str: :return: GPT 回复 """ # 对外暴露的对话方法,必须保存对话记录 - return CopilotGPT4._chat( + return OpenaiChatGPT._chat( chat_info=chat_info, message=message, message_obj=message_obj, is_save=True ) @@ -426,7 +426,7 @@ def _chat(chat_info: GptChatInfo, message: str, message_obj, is_save: bool) -> s newconv = [{"role": "user", "content": message}] # 发送请求 headers = { - "Authorization": CopilotGPT4.token, + "Authorization": OpenaiChatGPT.token, "Content-Type": "application/json", } json = { @@ -434,15 +434,15 @@ def _chat(chat_info: GptChatInfo, message: str, message_obj, is_save: bool) -> s "messages": DEFAULT_CONVERSATION + chat_info.get_conversation() + newconv, } r_json = post_request_json( - url=CopilotGPT4.chat_api, headers=headers, json=json, timeout=60 + url=OpenaiChatGPT.chat_api, headers=headers, json=json, timeout=60 ) # 判断是否有 error 或 code 字段 if "error" in r_json or "code" in r_json: - raise ValueError("Copilot-GPT4-Server返回值错误") + raise ValueError("ChatGPT 服务返回值错误") msg = r_json["choices"][0]["message"] - msg_content = msg.get("content", "调用Copilot-GPT4-Server失败") + msg_content = msg.get("content", "调用 ChatGPT 服务失败") # 将返回的 assistant 回复添加到对话记录中 if is_save is True: newconv.append({"role": "assistant", "content": msg_content}) @@ -464,15 +464,15 @@ def _save_chatting_chat_topic(person: Person, model: str) -> None: """ 生成正在进行的对话的主题 """ - chat_info = CopilotGPT4.get_chatting_chat_info(person, model) - if chat_info is None or CopilotGPT4._has_topic(chat_info): + chat_info = OpenaiChatGPT.get_chatting_chat_info(person, model) + if chat_info is None or OpenaiChatGPT._has_topic(chat_info): return # 生成对话主题 - if not CopilotGPT4._is_chat_valid(chat_info): + if not OpenaiChatGPT._is_chat_valid(chat_info): logger.error("对话记录长度小于1") return - topic = CopilotGPT4._generate_chat_topic(chat_info) + topic = OpenaiChatGPT._generate_chat_topic(chat_info) if not topic: logger.error("生成对话主题失败") raise ValueError("生成对话主题失败") @@ -487,10 +487,10 @@ def _generate_chat_topic(chat_info: GptChatInfo) -> str: """ 生成对话主题,用于保存对话记录 """ - assert CopilotGPT4._is_chat_valid(chat_info) + assert OpenaiChatGPT._is_chat_valid(chat_info) # 通过一次对话生成对话主题,但这次对话不保存到对话记录中 prompt = "请用10个字以内总结一下这次对话的主题,不带任何标点符号" - topic = CopilotGPT4._chat( + topic = OpenaiChatGPT._chat( chat_info=chat_info, message=prompt, message_obj=None, is_save=False ) # 限制主题长度 From 01afb4643577593d28ed3e576a13c74709cd56c8 Mon Sep 17 00:00:00 2001 From: Cassius0924 <2670226747@qq.com> Date: Mon, 4 Mar 2024 22:13:03 +0800 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=20wechatbot-webh?= =?UTF-8?q?ook=20=E8=87=B3=20v2.6.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- wechatter/app/routers/wechat.py | 39 +++++++++++++++----------- wechatter/config/validate.py | 16 +++++++++++ wechatter/message/message_forwarder.py | 6 ++-- wechatter/message/message_handler.py | 8 ++++-- wechatter/models/wechat/message.py | 33 ++++++++++++++++++++-- wechatter/models/wechat/person.py | 7 +++-- wechatter/sender/sender.py | 7 +++-- 8 files changed, 87 insertions(+), 31 deletions(-) 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"] From c6400af925165c278cf0f902e3994e4a8e3c5abd Mon Sep 17 00:00:00 2001 From: Cassius0924 <2670226747@qq.com> Date: Mon, 4 Mar 2024 22:50:17 +0800 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20=E9=9B=86=E6=88=90bark=E6=9C=BA?= =?UTF-8?q?=E5=99=A8=E4=BA=BA=E7=8A=B6=E6=80=81=E5=8F=98=E5=8C=96=E6=8F=90?= =?UTF-8?q?=E9=86=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ config.yaml.example | 1 + wechatter/app/routers/wechat.py | 15 +++++---------- wechatter/sender/notifier.py | 22 ++++++++++++++++++++-- 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 3b90400..d6da182 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,7 @@ python3 -m wechatter ## 支持的功能 +- [x] **掉线提醒**:当机器人掉线时,通过 Bark 推送提醒消息。 - [x] **消息可引用回复**:用户可以通过引用并回复命令消息进一步获取消息内容。带`(可引用:***)`的机器人消息即为可进一步互动的可引用消息。 - [x] **消息转发**:转发用户或群的消息到其他用户或群,并支持引用回复转发消息。需进行[配置](#%EF%B8%8F-message-forwarding-配置)。 ![message_forwarding_and_quoted_reply_show](docs/images/message_forwarding_and_quoted_reply_show.png) @@ -156,6 +157,7 @@ python3 -m wechatter | --- | --- | --- | | `admin_list` | 设置管理员,用于接收机器人状态变化通知 | 填入管理员微信名(不是备注) | | `admin_group_list` | 与 `admin_list` 同理,接收机器人状态变化通知 | 填入群名称(不是群备注) | +| `bark_url` | 用于接收机器人状态变化通知的 Bark URL | [Bark](https://github.com/Finb/Bark) 仅限 iOS 和 iPadOS | ### ⚙️ Bot 配置 diff --git a/config.yaml.example b/config.yaml.example index 521b66f..24830a3 100644 --- a/config.yaml.example +++ b/config.yaml.example @@ -15,6 +15,7 @@ wx_webhook_token: "your_wx_webhook_token" # Admin admin_list: [ "文件传输助手", "AdminName" ] admin_group_list: [ "AdminGroupName" ] +bark_url: your_bark_url # Bot diff --git a/wechatter/app/routers/wechat.py b/wechatter/app/routers/wechat.py index e0abed0..b76551d 100644 --- a/wechatter/app/routers/wechat.py +++ b/wechatter/app/routers/wechat.py @@ -1,4 +1,3 @@ -import json from typing import Union from fastapi import APIRouter, Form, UploadFile @@ -39,7 +38,6 @@ async def recv_wechat_msg( """ 用于接收 wxBotWebhook 转发过来的消息的接口 """ - # 更新机器人信息(id和name) # BotInfo.update_from_source(source) @@ -50,7 +48,7 @@ async def recv_wechat_msg( # 判断是否是系统事件 if type in ["system_event_login", "system_event_logout", "system_event_error"]: logger.info(f"收到系统事件:{type}") - handle_system_event(content) + handle_system_event(type) return # 不是系统消息,则是用户发来的消息 @@ -84,19 +82,16 @@ async def recv_wechat_msg( # return {"success": True, "data": {"type": "text", "content": "hello world!"}} -def handle_system_event(content: str) -> None: +def handle_system_event(event: str) -> None: """ 判断系统事件类型,并调用相应的函数 """ - content_dict: dict = json.loads(content) # 判断是否为机器人登录消息 - if content_dict["event"] == "login": - logger.info("机器人登录成功") + if event == "system_event_login": notifier.notify_logged_in() - elif content_dict["event"] == "logout": - logger.info("机器人已退出登录") + elif event == "system_event_logout": notifier.notify_logged_out() - elif content_dict["event"] == "error": + elif event == "system_event_error": pass else: pass diff --git a/wechatter/sender/notifier.py b/wechatter/sender/notifier.py index fa89097..d524e02 100644 --- a/wechatter/sender/notifier.py +++ b/wechatter/sender/notifier.py @@ -1,7 +1,12 @@ # 消息通知器 from typing import TYPE_CHECKING +from loguru import logger + +from wechatter.config import config from wechatter.sender import sender +from wechatter.utils import get_request +from wechatter.utils.url_joiner import join_urls if TYPE_CHECKING: from wechatter.models.wechat import SendTo @@ -21,13 +26,26 @@ def notify_logged_in() -> None: 通知登录成功 """ msg = "微信机器人启动成功" + logger.info(msg) sender.mass_send_msg_to_admins(msg) + if config.get("bark_url"): + url = join_urls( + config["bark_url"], + f"WeChatter/🟢 微信机器人({config['bot_name']})登录成功", + ) + get_request(url) -# FIXME: 登出消息发送不出去,因为发消息时候,机器人已经退出登录了 def notify_logged_out() -> None: """ 通知已退出登录 """ msg = "微信机器人已退出" - sender.mass_send_msg_to_admins(msg) + logger.info(msg) + # bark 提醒 + if config.get("bark_url"): + url = join_urls( + config["bark_url"], + f"WeChatter/🔴 微信机器人({config['bot_name']})已退出登录?copy={config['wx_webhook_base_api']}/login?token={config['wx_webhook_token']}", + ) + get_request(url) From 837c30c597ac2b34d79296d01194c69d4590cfcd Mon Sep 17 00:00:00 2001 From: Cassius0924 <2670226747@qq.com> Date: Mon, 4 Mar 2024 22:59:22 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=E7=BE=A4=E4=B8=AD=E8=87=AA=E8=BA=AB?= =?UTF-8?q?=E6=B6=88=E6=81=AFsendto=E5=AE=9E=E4=BE=8B=E5=8C=96=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- wechatter/message/message_handler.py | 2 +- wechatter/models/wechat/message.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/wechatter/message/message_handler.py b/wechatter/message/message_handler.py index cb56a66..a40b333 100644 --- a/wechatter/message/message_handler.py +++ b/wechatter/message/message_handler.py @@ -75,7 +75,7 @@ def handle_message(self, message_obj: Message): message_forwarder.forwarding_to_discord(message_obj) # 判断是否是自己的消息,是则需要将 to 设置为对方 - if message_obj.is_from_self: + if message_obj.is_from_self and not message_obj.is_group: to = SendTo(person=message_obj.receiver, group=message_obj.group) else: to = SendTo(person=message_obj.person, group=message_obj.group) diff --git a/wechatter/models/wechat/message.py b/wechatter/models/wechat/message.py index 79f5a33..221f622 100644 --- a/wechatter/models/wechat/message.py +++ b/wechatter/models/wechat/message.py @@ -40,8 +40,6 @@ class MessageSenderType(enum.Enum): PERSON = 0 GROUP = 1 - # TODO: 公众号文章 - # ARTICLE = 2 class Message(BaseModel):