From f0844edefd493fb4aa302ce0fe5c4f523c49a451 Mon Sep 17 00:00:00 2001 From: K2cr2O1 <2221577113@qq.com> Date: Thu, 1 Jan 2026 20:38:53 +0800 Subject: [PATCH] readmeupdate --- README.md | 69 ++++++++++ core/bot.py | 342 ++++++++++++++++++++++++++++++++++++++++++++-- models/message.py | 42 ++++++ 3 files changed, 442 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 4513ddd..fcbf4cd 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,51 @@ * **异步核心**:基于 `asyncio` 和 `websockets` 的高性能异步核心。 * **自动重连**:内置 WebSocket 断线重连机制。 +## 📝 待办事项 (TODO) + +### API 封装 +- [ ] **消息相关** + - `delete_msg`: 撤回消息 + - `get_msg`: 获取消息 + - `get_forward_msg`: 获取合并转发消息 + - `send_like`: 发送点赞 +- [ ] **群组管理** + - `set_group_kick`: 群组踢人 + - `set_group_ban`: 群组单人禁言 + - `set_group_anonymous_ban`: 群组匿名禁言 + - `set_group_whole_ban`: 群组全员禁言 + - `set_group_admin`: 群组设置管理员 + - `set_group_anonymous`: 群组匿名 + - `set_group_card`: 设置群名片(群备注) + - `set_group_name`: 设置群名 + - `set_group_leave`: 退出群组 + - `set_group_special_title`: 设置群组专属头衔 +- [ ] **群组信息** + - `get_group_info`: 获取群信息 + - `get_group_list`: 获取群列表 + - `get_group_member_info`: 获取群成员信息 + - `get_group_member_list`: 获取群成员列表 + - `get_group_honor_info`: 获取群荣誉信息 +- [ ] **用户相关** + - `get_login_info`: 获取登录号信息 + - `get_stranger_info`: 获取陌生人信息 + - `get_friend_list`: 获取好友列表 +- [ ] **请求处理** + - `set_friend_add_request`: 处理加好友请求 + - `set_group_add_request`: 处理加群请求/邀请 +- [ ] **系统/其他** + - `get_version_info`: 获取版本信息 + - `get_status`: 获取状态 + - `can_send_image`: 检查是否可以发送图片 + - `can_send_record`: 检查是否可以发送语音 + - `clean_cache`: 清理缓存 + +### 其他改进 +- [ ] **日志系统优化**: 引入更完善的日志记录机制,支持文件输出和日志级别控制。 +- [ ] **异常处理增强**: 增强插件执行过程中的异常捕获,防止单个插件崩溃影响整个 Bot。 +- [ ] **中间件支持**: 添加消息处理中间件,支持在指令执行前/后进行拦截和处理。 +- [ ] **权限系统**: 实现基础的权限管理(如超级管理员、群管理员等)。 + ## 📂 项目结构 ``` @@ -129,6 +174,30 @@ async def auto_approve_friend(bot: Bot, event: FriendRequestEvent): }) ``` +#### 4. 通用 API 调用 (call_api) + +如果框架尚未封装某个 OneBot API,你可以使用 `bot.call_api` 直接调用。这是通用的备用调用方法。 + +```python +from core.command_manager import matcher +from core.bot import Bot +from models import MessageEvent + +@matcher.command("info") +async def get_group_info(bot: Bot, event: MessageEvent, args: list[str]): + # 直接调用 get_group_info API + # action: API 名称 + # params: API 参数字典 + resp = await bot.call_api("get_group_info", { + "group_id": event.group_id, + "no_cache": False + }) + + if resp.get("status") == "ok": + group_name = resp["data"]["group_name"] + await event.reply(f"当前群名:{group_name}") +``` + ## 📚 事件模型说明 项目采用了基于工厂模式的事件处理系统,所有事件定义在 `models/events/` 下: diff --git a/core/bot.py b/core/bot.py index bf97b93..081d670 100644 --- a/core/bot.py +++ b/core/bot.py @@ -3,11 +3,11 @@ Bot 抽象模块 定义了 Bot 类,封装了 OneBot API 的调用逻辑,提供了便捷的消息发送方法。 """ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Union, List if TYPE_CHECKING: from .ws import WS - from models import OneBotEvent + from models import OneBotEvent, MessageSegment class Bot: @@ -33,33 +33,33 @@ class Bot: """ return await self.ws.call_api(action, params) - async def send_group_msg(self, group_id: int, message: str, auto_escape: bool = False) -> dict: + async def send_group_msg(self, group_id: int, message: Union[str, "MessageSegment", List["MessageSegment"]], auto_escape: bool = False) -> dict: """ 发送群消息 :param group_id: 群号 - :param message: 消息内容 - :param auto_escape: 是否自动转义 + :param message: 消息内容,可以是字符串、MessageSegment 对象或 MessageSegment 列表 + :param auto_escape: 是否自动转义(仅当 message 为字符串时有效) :return: API 响应结果 """ return await self.call_api( - "send_group_msg", {"group_id": group_id, "message": message, "auto_escape": auto_escape} + "send_group_msg", {"group_id": group_id, "message": self._process_message(message), "auto_escape": auto_escape} ) - async def send_private_msg(self, user_id: int, message: str, auto_escape: bool = False) -> dict: + async def send_private_msg(self, user_id: int, message: Union[str, "MessageSegment", List["MessageSegment"]], auto_escape: bool = False) -> dict: """ 发送私聊消息 :param user_id: 用户 QQ 号 - :param message: 消息内容 - :param auto_escape: 是否自动转义 + :param message: 消息内容,可以是字符串、MessageSegment 对象或 MessageSegment 列表 + :param auto_escape: 是否自动转义(仅当 message 为字符串时有效) :return: API 响应结果 """ return await self.call_api( - "send_private_msg", {"user_id": user_id, "message": message, "auto_escape": auto_escape} + "send_private_msg", {"user_id": user_id, "message": self._process_message(message), "auto_escape": auto_escape} ) - async def send(self, event: "OneBotEvent", message: str, auto_escape: bool = False) -> dict: + async def send(self, event: "OneBotEvent", message: Union[str, "MessageSegment", List["MessageSegment"]], auto_escape: bool = False) -> dict: """ 智能发送消息,根据事件类型自动选择发送方式 @@ -83,3 +83,323 @@ class Bot: return await self.send_private_msg(user_id, message, auto_escape) return {"status": "failed", "msg": "Unknown message target"} + + async def delete_msg(self, message_id: int) -> dict: + """ + 撤回消息 + + :param message_id: 消息 ID + :return: API 响应结果 + """ + return await self.call_api("delete_msg", {"message_id": message_id}) + + async def get_msg(self, message_id: int) -> dict: + """ + 获取消息 + + :param message_id: 消息 ID + :return: API 响应结果 + """ + return await self.call_api("get_msg", {"message_id": message_id}) + + async def get_forward_msg(self, id: str) -> dict: + """ + 获取合并转发消息 + + :param id: 合并转发 ID + :return: API 响应结果 + """ + return await self.call_api("get_forward_msg", {"id": id}) + + async def send_like(self, user_id: int, times: int = 1) -> dict: + """ + 发送点赞 + + :param user_id: 对方 QQ 号 + :param times: 点赞次数 + :return: API 响应结果 + """ + return await self.call_api("send_like", {"user_id": user_id, "times": times}) + + async def set_group_kick(self, group_id: int, user_id: int, reject_add_request: bool = False) -> dict: + """ + 群组踢人 + + :param group_id: 群号 + :param user_id: 要踢的 QQ 号 + :param reject_add_request: 拒绝此人的加群请求 + :return: API 响应结果 + """ + return await self.call_api("set_group_kick", {"group_id": group_id, "user_id": user_id, "reject_add_request": reject_add_request}) + + async def set_group_ban(self, group_id: int, user_id: int, duration: int = 30 * 60) -> dict: + """ + 群组单人禁言 + + :param group_id: 群号 + :param user_id: 要禁言的 QQ 号 + :param duration: 禁言时长(秒),0 表示解除禁言 + :return: API 响应结果 + """ + return await self.call_api("set_group_ban", {"group_id": group_id, "user_id": user_id, "duration": duration}) + + async def set_group_anonymous_ban(self, group_id: int, anonymous: dict = None, duration: int = 30 * 60, flag: str = None) -> dict: + """ + 群组匿名禁言 + + :param group_id: 群号 + :param anonymous: 可选,要禁言的匿名用户对象(群消息事件的 anonymous 字段) + :param duration: 禁言时长(秒) + :param flag: 可选,要禁言的匿名用户的 flag(需从群消息事件的 anonymous 字段中获取) + :return: API 响应结果 + """ + params = {"group_id": group_id, "duration": duration} + if anonymous: + params["anonymous"] = anonymous + if flag: + params["flag"] = flag + return await self.call_api("set_group_anonymous_ban", params) + + async def set_group_whole_ban(self, group_id: int, enable: bool = True) -> dict: + """ + 群组全员禁言 + + :param group_id: 群号 + :param enable: 是否开启 + :return: API 响应结果 + """ + return await self.call_api("set_group_whole_ban", {"group_id": group_id, "enable": enable}) + + async def set_group_admin(self, group_id: int, user_id: int, enable: bool = True) -> dict: + """ + 群组设置管理员 + + :param group_id: 群号 + :param user_id: 要设置的 QQ 号 + :param enable: True 为设置,False 为取消 + :return: API 响应结果 + """ + return await self.call_api("set_group_admin", {"group_id": group_id, "user_id": user_id, "enable": enable}) + + async def set_group_anonymous(self, group_id: int, enable: bool = True) -> dict: + """ + 群组匿名 + + :param group_id: 群号 + :param enable: 是否开启 + :return: API 响应结果 + """ + return await self.call_api("set_group_anonymous", {"group_id": group_id, "enable": enable}) + + async def set_group_card(self, group_id: int, user_id: int, card: str = "") -> dict: + """ + 设置群名片(群备注) + + :param group_id: 群号 + :param user_id: 要设置的 QQ 号 + :param card: 群名片内容,不填或空字符串表示删除群名片 + :return: API 响应结果 + """ + return await self.call_api("set_group_card", {"group_id": group_id, "user_id": user_id, "card": card}) + + async def set_group_name(self, group_id: int, group_name: str) -> dict: + """ + 设置群名 + + :param group_id: 群号 + :param group_name: 新群名 + :return: API 响应结果 + """ + return await self.call_api("set_group_name", {"group_id": group_id, "group_name": group_name}) + + async def set_group_leave(self, group_id: int, is_dismiss: bool = False) -> dict: + """ + 退出群组 + + :param group_id: 群号 + :param is_dismiss: 是否解散,如果登录号是群主,则仅在此项为 True 时能够解散 + :return: API 响应结果 + """ + return await self.call_api("set_group_leave", {"group_id": group_id, "is_dismiss": is_dismiss}) + + async def set_group_special_title(self, group_id: int, user_id: int, special_title: str = "", duration: int = -1) -> dict: + """ + 设置群组专属头衔 + + :param group_id: 群号 + :param user_id: 要设置的 QQ 号 + :param special_title: 专属头衔,不填或空字符串表示删除 + :param duration: 有效期(秒),-1 表示永久 + :return: API 响应结果 + """ + return await self.call_api("set_group_special_title", {"group_id": group_id, "user_id": user_id, "special_title": special_title, "duration": duration}) + + async def get_group_info(self, group_id: int, no_cache: bool = False) -> dict: + """ + 获取群信息 + + :param group_id: 群号 + :param no_cache: 是否不使用缓存 + :return: API 响应结果 + """ + return await self.call_api("get_group_info", {"group_id": group_id, "no_cache": no_cache}) + + async def get_group_list(self) -> dict: + """ + 获取群列表 + + :return: API 响应结果 + """ + return await self.call_api("get_group_list") + + async def get_group_member_info(self, group_id: int, user_id: int, no_cache: bool = False) -> dict: + """ + 获取群成员信息 + + :param group_id: 群号 + :param user_id: QQ 号 + :param no_cache: 是否不使用缓存 + :return: API 响应结果 + """ + return await self.call_api("get_group_member_info", {"group_id": group_id, "user_id": user_id, "no_cache": no_cache}) + + async def get_group_member_list(self, group_id: int) -> dict: + """ + 获取群成员列表 + + :param group_id: 群号 + :return: API 响应结果 + """ + return await self.call_api("get_group_member_list", {"group_id": group_id}) + + async def get_group_honor_info(self, group_id: int, type: str) -> dict: + """ + 获取群荣誉信息 + + :param group_id: 群号 + :param type: 要获取的群荣誉类型,可传入 talkative, performer, legend, strong_newbie, emotion 等 + :return: API 响应结果 + """ + return await self.call_api("get_group_honor_info", {"group_id": group_id, "type": type}) + + async def get_login_info(self) -> dict: + """ + 获取登录号信息 + + :return: API 响应结果 + """ + return await self.call_api("get_login_info") + + async def get_stranger_info(self, user_id: int, no_cache: bool = False) -> dict: + """ + 获取陌生人信息 + + :param user_id: QQ 号 + :param no_cache: 是否不使用缓存 + :return: API 响应结果 + """ + return await self.call_api("get_stranger_info", {"user_id": user_id, "no_cache": no_cache}) + + async def get_friend_list(self) -> dict: + """ + 获取好友列表 + + :return: API 响应结果 + """ + return await self.call_api("get_friend_list") + + async def set_friend_add_request(self, flag: str, approve: bool = True, remark: str = "") -> dict: + """ + 处理加好友请求 + + :param flag: 加好友请求的 flag(需从上报的数据中获取) + :param approve: 是否同意请求 + :param remark: 添加后的好友备注(仅在同意时有效) + :return: API 响应结果 + """ + return await self.call_api("set_friend_add_request", {"flag": flag, "approve": approve, "remark": remark}) + + async def set_group_add_request(self, flag: str, sub_type: str, approve: bool = True, reason: str = "") -> dict: + """ + 处理加群请求/邀请 + + :param flag: 加群请求的 flag(需从上报的数据中获取) + :param sub_type: add 或 invite,请求类型(需要与上报消息中的 sub_type 字段相符) + :param approve: 是否同意请求/邀请 + :param reason: 拒绝理由(仅在拒绝时有效) + :return: API 响应结果 + """ + return await self.call_api("set_group_add_request", {"flag": flag, "sub_type": sub_type, "approve": approve, "reason": reason}) + + async def get_version_info(self) -> dict: + """ + 获取版本信息 + + :return: API 响应结果 + """ + return await self.call_api("get_version_info") + + async def get_status(self) -> dict: + """ + 获取状态 + + :return: API 响应结果 + """ + return await self.call_api("get_status") + + async def can_send_image(self) -> dict: + """ + 检查是否可以发送图片 + + :return: API 响应结果 + """ + return await self.call_api("can_send_image") + + async def can_send_record(self) -> dict: + """ + 检查是否可以发送语音 + + :return: API 响应结果 + """ + return await self.call_api("can_send_record") + + async def clean_cache(self) -> dict: + """ + 清理缓存 + + :return: API 响应结果 + """ + return await self.call_api("clean_cache") + + def _process_message(self, message: Union[str, "MessageSegment", List["MessageSegment"]]) -> Union[str, List[dict]]: + """ + 处理消息内容,将其转换为 API 可接受的格式 + + :param message: 原始消息内容 + :return: 处理后的消息内容 + """ + if isinstance(message, str): + return message + + # 避免循环导入,在运行时导入 + from models import MessageSegment + + if isinstance(message, MessageSegment): + return [self._segment_to_dict(message)] + + if isinstance(message, list): + return [self._segment_to_dict(m) for m in message if isinstance(m, MessageSegment)] + + return str(message) + + def _segment_to_dict(self, segment: "MessageSegment") -> dict: + """ + 将 MessageSegment 对象转换为字典 + + :param segment: MessageSegment 对象 + :return: 字典格式的消息段 + """ + return { + "type": segment.type, + "data": segment.data + } diff --git a/models/message.py b/models/message.py index cabbe67..914a149 100644 --- a/models/message.py +++ b/models/message.py @@ -52,3 +52,45 @@ class MessageSegment: def __repr__(self): return f"[MS:{self.type}:{self.data}]" + + # --- 快捷构造方法 --- + + @staticmethod + def text(text: str) -> "MessageSegment": + """ + 构造文本消息段 + + :param text: 文本内容 + :return: MessageSegment 对象 + """ + return MessageSegment(type="text", data={"text": text}) + + @staticmethod + def at(user_id: int | str) -> "MessageSegment": + """ + 构造 @某人 消息段 + + :param user_id: 目标 QQ 号,"all" 表示 @全体成员 + :return: MessageSegment 对象 + """ + return MessageSegment(type="at", data={"qq": str(user_id)}) + + @staticmethod + def image(file: str) -> "MessageSegment": + """ + 构造图片消息段 + + :param file: 图片文件名、URL 或 Base64 + :return: MessageSegment 对象 + """ + return MessageSegment(type="image", data={"file": file}) + + @staticmethod + def face(id: int) -> "MessageSegment": + """ + 构造表情消息段 + + :param id: 表情 ID + :return: MessageSegment 对象 + """ + return MessageSegment(type="face", data={"id": str(id)})