diff --git a/core/__init__.py b/core/__init__.py index 41e8a92..7ff7c22 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -2,4 +2,4 @@ from .command_manager import matcher from .config_loader import global_config from .WS import WS -__all__ = ["WS", "matcher", "global_config"] +__all__ = ["WS", "matcher", "global_config", "PluginDataManager"] diff --git a/core/plugin_manager.py b/core/plugin_manager.py index aa16f52..a80a197 100644 --- a/core/plugin_manager.py +++ b/core/plugin_manager.py @@ -3,7 +3,9 @@ 负责扫描、加载和管理 `base_plugins` 目录下的所有插件。 """ + import importlib +import json import os import pkgutil import sys @@ -19,10 +21,12 @@ def load_all_plugins(): 该函数会遍历 `plugins` 目录下的所有模块: 1. 如果模块已加载,则执行 reload 操作(用于热重载)。 2. 如果模块未加载,则执行 import 操作。 - + 加载过程中会提取插件元数据 `__plugin_meta__` 并注册到 CommandManager。 """ - plugin_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "plugins") + plugin_dir = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "..", "plugins" + ) package_name = "plugins" logger.info(f"正在从 {package_name} 加载插件...") @@ -37,13 +41,84 @@ def load_all_plugins(): else: module = importlib.import_module(full_module_name) action = "加载" - + # 提取插件元数据 if hasattr(module, "__plugin_meta__"): meta = getattr(module, "__plugin_meta__") matcher.plugins[full_module_name] = meta - + type_str = "包" if is_pkg else "文件" logger.success(f" [{type_str}] 成功{action}: {module_name}") except Exception as e: - logger.error(f" {action if 'action' in locals() else '加载'}插件 {module_name} 失败: {e}") + print( + f" {action if 'action' in locals() else '加载'}插件 {module_name} 失败: {e}" + ) + + +class PluginDataManager: + """ + 用于管理插件产生的数据文件的类 + """ + + def __init__(self, plugin_name: str): + """ + 初始化插件数据管理器 + + :param plugin_name: 插件名称 + """ + self.plugin_name = plugin_name + self.data_file = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "..", + "plugins", + "data", + self.plugin_name + ".json", + ) + self.data = {} + self.load() + + def load(self): + """读取配置文件""" + if not os.path.exists(self.data_file): + with open(self.data_file, "w", encoding="utf-8") as f: + self.set(self.plugin_name, []) + try: + with open(self.data_file, "r", encoding="utf-8") as f: + self.data = json.load(f) + except json.JSONDecodeError: + self.data = {} + + def save(self): + """保存配置到文件""" + with open(self.data_file, "w", encoding="utf-8") as f: + json.dump(self.data, f, indent=2, ensure_ascii=False) + + def get(self, key, default=None): + """获取配置项""" + return self.data.get(key, default) + + def set(self, key, value): + """设置配置项""" + self.data[key] = value + self.save() + + def add(self, key, value): + """添加配置项""" + if key not in self.data: + self.data[key] = [] + self.data[key].append(value) + self.save() + + def remove(self, key): + """删除配置项""" + if key in self.data: + del self.data[key] + self.save() + + def clear(self): + """清空所有配置""" + self.data.clear() + self.save() + + def get_all(self): + return self.data.copy() diff --git a/models/__init__.py b/models/__init__.py index a25a0af..3541531 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -1,20 +1,47 @@ from .events.base import OneBotEvent -from .events.message import MessageEvent, PrivateMessageEvent, GroupMessageEvent, MessageSegment -from .events.notice import ( - NoticeEvent, FriendAddNoticeEvent, FriendRecallNoticeEvent, - GroupRecallNoticeEvent, GroupIncreaseNoticeEvent, - GroupDecreaseNoticeEvent, GroupAdminNoticeEvent, GroupBanNoticeEvent, - GroupUploadNoticeEvent, GroupUploadFile, - NotifyNoticeEvent, PokeNotifyEvent, LuckyKingNotifyEvent, HonorNotifyEvent, - GroupCardNoticeEvent, OfflineFileNoticeEvent, OfflineFile, - ClientStatusNoticeEvent, ClientStatus, EssenceNoticeEvent -) -from .events.request import RequestEvent, FriendRequestEvent, GroupRequestEvent -from .events.meta import MetaEvent, HeartbeatEvent, LifeCycleEvent, HeartbeatStatus from .events.factory import EventFactory +from .events.message import ( + GroupMessageEvent, + MessageEvent, + MessageSegment, + PrivateMessageEvent, +) +from .events.meta import HeartbeatEvent, HeartbeatStatus, LifeCycleEvent, MetaEvent +from .events.notice import ( + ClientStatus, + ClientStatusNoticeEvent, + EssenceNoticeEvent, + FriendAddNoticeEvent, + FriendRecallNoticeEvent, + GroupAdminNoticeEvent, + GroupBanNoticeEvent, + GroupCardNoticeEvent, + GroupDecreaseNoticeEvent, + GroupIncreaseNoticeEvent, + GroupRecallNoticeEvent, + GroupUploadFile, + GroupUploadNoticeEvent, + HonorNotifyEvent, + LuckyKingNotifyEvent, + NoticeEvent, + NotifyNoticeEvent, + OfflineFile, + OfflineFileNoticeEvent, + PokeNotifyEvent, +) +from .events.request import FriendRequestEvent, GroupRequestEvent, RequestEvent from .objects import ( - GroupInfo, GroupMemberInfo, FriendInfo, StrangerInfo, LoginInfo, - VersionInfo, Status, EssenceMessage, GroupHonorInfo, CurrentTalkative, HonorInfo + CurrentTalkative, + EssenceMessage, + FriendInfo, + GroupHonorInfo, + GroupInfo, + GroupMemberInfo, + HonorInfo, + LoginInfo, + Status, + StrangerInfo, + VersionInfo, ) # Alias for backward compatibility diff --git a/models/message.py b/models/message.py index ee7f701..f79d05f 100644 --- a/models/message.py +++ b/models/message.py @@ -4,6 +4,7 @@ 该模块定义了 `MessageSegment` 类,用于构建和表示 OneBot v11 协议中的消息段。 通过此类,可以方便地创建文本、图片、At 等不同类型的消息内容,并支持链式操作。 """ + from dataclasses import dataclass from typing import Any, Dict @@ -67,7 +68,7 @@ class MessageSegment: # --- 快捷构造方法 --- @staticmethod - def text(text: str) -> "MessageSegment": + def text(text: str) -> "MessageSegment": # noqa: F811 """ 创建一个文本消息段。 diff --git a/plugins/admin.py b/plugins/admin.py new file mode 100644 index 0000000..8e1b0e6 --- /dev/null +++ b/plugins/admin.py @@ -0,0 +1,115 @@ +from core import PluginDataManager +from core.bot import Bot +from core.command_manager import matcher +from models import GroupMessageEvent + +__plugin_meta__ = { + "name": "admin", + "description": "机器人权限管理插件", + "usage": "/admin", +} + +data = PluginDataManager("admin") + + +@matcher.command("admin") +async def handle_permission(bot: Bot, event: GroupMessageEvent, args: list[str]): + if not args: + await event.reply( + "机器人权限管理插件指令:\n/admin list 列出所有权限\n/admin add member 添加群成员权限\n/admin remove member 删除群成员权限\n/admin add group <群号> 添加群权限\n/admin remove group <群号> 删除群权限\n/admin clear member 清空群成员权限\n/admin clear group 清空群权限\n/admin clear all 清空所有权限" + ) + return + + if str(event.user_id) not in data.get("members", []): + await event.reply("你没有权限使用此命令。") + return + if str(event.group_id) not in data.get("groups", []): + await event.reply("群聊不在权限中") + return + + action = args[0].lower() + + # ensure storage keys exist + members = data.get("members", []) or [] + groups = data.get("groups", []) or [] + + if action == "list": + msg_lines = ["当前权限列表:"] + msg_lines.append( + f"群成员权限 ({len(members)}): {', '.join(members) if members else '无'}" + ) + msg_lines.append( + f"群权限 ({len(groups)}): {', '.join(groups) if groups else '无'}" + ) + await event.reply("\n".join(msg_lines)) + return + + if action in ("add", "remove"): + if len(args) < 3: + await event.reply("参数错误,示例:/admin add member 123456") + return + + target = args[1].lower() + value = args[2] + + if target == "member": + # operate on members list + if action == "add": + if str(value) in members: + await event.reply(f"成员 {value} 已存在,无需重复添加。") + return + members.append(str(value)) + data.set("members", members) + await event.reply(f"已添加群成员权限:{value}") + return + else: # remove + if str(value) not in members: + await event.reply(f"成员 {value} 不在权限列表中。") + return + members = [m for m in members if m != str(value)] + data.set("members", members) + await event.reply(f"已移除群成员权限:{value}") + return + + if target == "group": + if action == "add": + if str(value) in groups: + await event.reply(f"群 {value} 已存在,无需重复添加。") + return + groups.append(str(value)) + data.set("groups", groups) + await event.reply(f"已添加群权限:{value}") + return + else: # remove + if str(value) not in groups: + await event.reply(f"群 {value} 不在权限列表中。") + return + groups = [g for g in groups if g != str(value)] + data.set("groups", groups) + await event.reply(f"已移除群权限:{value}") + return + + await event.reply("未知目标类型,请使用 member 或 group") + return + + if action == "clear": + if len(args) < 2: + await event.reply("参数错误,示例:/admin clear member") + return + target = args[1].lower() + if target == "member": + data.set("members", []) + await event.reply("已清空群成员权限。") + return + if target == "group": + data.set("groups", []) + await event.reply("已清空群权限。") + return + if target == "all": + data.clear() + await event.reply("已清空所有权限。") + return + await event.reply("未知清空目标,请使用 member/group/all") + return + + await event.reply("未知指令,使用 /admin 查看帮助") diff --git a/plugins/data/admin.json b/plugins/data/admin.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/plugins/data/admin.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/plugins/jrcd.py b/plugins/jrcd.py index 4b7e318..ee82e7f 100644 --- a/plugins/jrcd.py +++ b/plugins/jrcd.py @@ -10,6 +10,12 @@ from core.bot import Bot from core.command_manager import matcher from models import MessageEvent, MessageSegment +__plugin_meta__ = { + "name": "jrcd", + "description": "来看看你的长度吧!", + "usage": "/jrcd\n/bbcd [@某人]", +} + # jrcd JRCDMSG_1 = [ "今天的长度是%scm,可以让我一口吃掉吗罒ω罒", @@ -28,15 +34,15 @@ JRCDMSG_3 = [ "今天的长度是%scm,单是看到哥哥的长度就....(〃w〃)", ] -# bbcd short -BBCDMSG1 = ["还行,可以尝试一下(๑‾ ꇴ ‾๑)"] -BBCDMSG2 = ["差的不多,富贵险中求一下(*°ー°)v?"] -BBCDMSG3 = ["快逃!!!!!!!!(o(*°▽°*)o)"] - # bbcd long -BBCDMSG4 = ["差的不多,富贵险中求一下(*°ー°)v?", "还行,可以尝试一下(๑‾ ꇴ ‾๑)"] -BBCDMSG5 = ["不错的成绩,努力一下或许可以让他受孕哦..(〃w〃)"] -BBCDMSG6 = ["好猛,试试强制让他受孕吧!!!(((o(*°▽°*)o)))"] +BBCDMSG1 = ["还行,可以尝试一下(๑‾ ꇴ ‾๑)"] +BBCDMSG2 = ["不错的成绩,努力一下或许可以让他受孕哦..(〃w〃)"] +BBCDMSG3 = ["好猛,试试强制让他受孕吧!!!(((o(*°▽°*)o)))"] + +# bbcd short +BBCDMSG4 = ["差的不多,富贵险中求一下(*°ー°)v?"] +BBCDMSG5 = ["还行,可以尝试一下(๑‾ ꇴ ‾๑)"] +BBCDMSG6 = ["快逃!!!!!!!!(o(*°▽°*)o)"] # bbcd equal BBCDMSG7 = ["试试刺刀看看谁能赢吧!"] diff --git a/plugins/thpic.py b/plugins/thpic.py index a586533..da0784d 100644 --- a/plugins/thpic.py +++ b/plugins/thpic.py @@ -9,6 +9,12 @@ from core.bot import Bot from core.command_manager import matcher from models import MessageEvent, MessageSegment +__plugin_meta__ = { + "name": "thpic", + "description": "来看看东方Project的图片吧!", + "usage": "/thpic", +} + @matcher.command("thpic") async def handle_echo(bot: Bot, event: MessageEvent, args: list[str]):