From a3671aa9b6fe22eb7a0e16cce93895caabb3d770 Mon Sep 17 00:00:00 2001 From: HibiKier <775757368@qq.com> Date: Tue, 15 Jul 2025 16:57:50 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=E6=96=B0=E5=A2=9E=E8=87=AA?= =?UTF-8?q?=E6=88=91=E4=BB=8B=E7=BB=8D=E5=8A=9F=E8=83=BD=E5=8F=8A=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8F=91=E9=80=81=E5=9B=BE=E7=89=87=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 bot_profile.py 中实现自我介绍指令及重载功能 - 在 group_handle 中添加自动发送自我介绍图片的逻辑 - 在 fg_request 中实现添加好友时自动发送自我介绍图片 - 新增 bot_profile_manager.py 管理 BOT 自我介绍及图片生成 - 更新 models.py 以支持插件自我介绍和注意事项字段 --- zhenxun/builtin_plugins/bot_profile.py | 53 +++++ .../platform/qq/group_handle/data_source.py | 14 +- .../superuser/request_manage.py | 11 +- zhenxun/configs/utils/models.py | 4 + zhenxun/models/fg_request.py | 24 +++ zhenxun/utils/manager/bot_profile_manager.py | 185 ++++++++++++++++++ 6 files changed, 287 insertions(+), 4 deletions(-) create mode 100644 zhenxun/builtin_plugins/bot_profile.py create mode 100644 zhenxun/utils/manager/bot_profile_manager.py diff --git a/zhenxun/builtin_plugins/bot_profile.py b/zhenxun/builtin_plugins/bot_profile.py new file mode 100644 index 00000000..7864a63e --- /dev/null +++ b/zhenxun/builtin_plugins/bot_profile.py @@ -0,0 +1,53 @@ +from nonebot.permission import SUPERUSER +from nonebot.plugin import PluginMetadata +from nonebot.rule import to_me +from nonebot_plugin_alconna import Alconna, Arparma, on_alconna +from nonebot_plugin_uninfo import Uninfo + +from zhenxun.configs.config import BotConfig +from zhenxun.configs.utils import PluginExtraData +from zhenxun.services.log import logger +from zhenxun.utils.manager.bot_profile_manager import BotProfileManager +from zhenxun.utils.message import MessageUtils + +__plugin_meta__ = PluginMetadata( + name="自我介绍", + description=f"这是{BotConfig.self_nickname}的深情告白", + usage=""" + 指令: + 自我介绍 + """.strip(), + extra=PluginExtraData( + author="HibiKier", + version="0.1", + menu_type="其他", + superuser_help="重载自我介绍", + ).to_dict(), +) + + +_matcher = on_alconna(Alconna("自我介绍"), priority=5, block=True, rule=to_me()) + +_reload_matcher = on_alconna( + Alconna("重载自我介绍"), priority=1, block=True, permission=SUPERUSER +) + + +@_matcher.handle() +async def _(session: Uninfo, arparma: Arparma): + file_path = await BotProfileManager.build_bot_profile_image(session.self_id) + if not file_path: + await MessageUtils.build_message( + f"{BotConfig.self_nickname}当前没有自我简介哦" + ).finish(reply_to=True) + await MessageUtils.build_message(file_path).send() + logger.info("BOT自我介绍", arparma.header_result, session=session) + + +@_reload_matcher.handle() +async def _(session: Uninfo, arparma: Arparma): + BotProfileManager.clear_profile_image(session.self_id) + await MessageUtils.build_message(f"重载{BotConfig.self_nickname}自我介绍成功").send( + reply_to=True + ) + logger.info("重载BOT自我介绍", arparma.header_result, session=session) diff --git a/zhenxun/builtin_plugins/platform/qq/group_handle/data_source.py b/zhenxun/builtin_plugins/platform/qq/group_handle/data_source.py index 9e8d7ea2..e2a1c640 100644 --- a/zhenxun/builtin_plugins/platform/qq/group_handle/data_source.py +++ b/zhenxun/builtin_plugins/platform/qq/group_handle/data_source.py @@ -10,7 +10,7 @@ from nonebot_plugin_uninfo import Uninfo import ujson as json from zhenxun.builtin_plugins.platform.qq.exception import ForceAddGroupError -from zhenxun.configs.config import Config +from zhenxun.configs.config import BotConfig, Config from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH from zhenxun.models.fg_request import FgRequest from zhenxun.models.group_console import GroupConsole @@ -20,6 +20,7 @@ from zhenxun.models.plugin_info import PluginInfo from zhenxun.services.log import logger from zhenxun.utils.common_utils import CommonUtils from zhenxun.utils.enum import RequestHandleType +from zhenxun.utils.manager.bot_profile_manager import BotProfileManager from zhenxun.utils.message import MessageUtils from zhenxun.utils.platform import PlatformUtils from zhenxun.utils.utils import FreqLimiter @@ -153,6 +154,17 @@ class GroupManager: await cls.__handle_add_group(bot, group_id, group) """刷新群管理员权限""" await cls.__refresh_level(bot, group_id) + if BotProfileManager.is_auto_send_profile(): + file_path = await BotProfileManager.build_bot_profile_image(bot.self_id) + if file_path: + await MessageUtils.build_message( + [ + f"嗨,大家好,我是{BotConfig.self_nickname}, " + "希望我们可以友好相处(眨眼眨眼)!", + file_path, + ] + ).send() + logger.info("加入群组自动发送BOT自我介绍图片", session=group_id) @classmethod def get_path(cls, session: Uninfo) -> Path | None: diff --git a/zhenxun/builtin_plugins/superuser/request_manage.py b/zhenxun/builtin_plugins/superuser/request_manage.py index e6eb6b77..d0e9beb0 100644 --- a/zhenxun/builtin_plugins/superuser/request_manage.py +++ b/zhenxun/builtin_plugins/superuser/request_manage.py @@ -163,15 +163,20 @@ async def _( req = await FgRequest.ignore(handle_id) except NotFoundError: await MessageUtils.build_message("未发现此id的请求...").finish(reply_to=True) - except Exception: - await MessageUtils.build_message("其他错误, 可能flag已失效...").finish( + except Exception as e: + logger.error(f"处理请求失败 ID: {handle_id}", session=session, e=e) + await MessageUtils.build_message(f"其他错误, 可能flag已失效...: {e}").finish( reply_to=True ) logger.info( f"处理请求 Id: {req.id if req else ''}", arparma.header_result, session=session ) await MessageUtils.build_message("成功处理请求!").send(reply_to=True) - if req and handle_type == RequestHandleType.APPROVE: + if ( + req + and req.request_type == RequestType.GROUP + and handle_type == RequestHandleType.APPROVE + ): await bot.send_private_msg( user_id=req.user_id, message=f"管理员已同意此次群组邀请,请不要让{BotConfig.self_nickname}受委屈哦(狠狠监控)" diff --git a/zhenxun/configs/utils/models.py b/zhenxun/configs/utils/models.py index bc850cc4..0af9ab7f 100644 --- a/zhenxun/configs/utils/models.py +++ b/zhenxun/configs/utils/models.py @@ -263,6 +263,10 @@ class PluginExtraData(BaseModel): """是否显示在菜单中""" smart_tools: list[AICallableTag] | None = None """智能模式函数工具集""" + introduction: str | None = None + """BOT自我介绍时插件的自我介绍""" + precautions: list[str] | None = None + """BOT自我介绍时插件的注意事项""" def to_dict(self, **kwargs): return model_dump(self, **kwargs) diff --git a/zhenxun/models/fg_request.py b/zhenxun/models/fg_request.py index 4362a7d3..4b27ebd4 100644 --- a/zhenxun/models/fg_request.py +++ b/zhenxun/models/fg_request.py @@ -6,9 +6,13 @@ from tortoise import fields from zhenxun.configs.config import BotConfig from zhenxun.models.group_console import GroupConsole from zhenxun.services.db_context import Model +from zhenxun.services.log import logger from zhenxun.utils.common_utils import SqlUtils from zhenxun.utils.enum import RequestHandleType, RequestType from zhenxun.utils.exception import NotFoundError +from zhenxun.utils.manager.bot_profile_manager import BotProfileManager +from zhenxun.utils.message import MessageUtils +from zhenxun.utils.platform import PlatformUtils class FgRequest(Model): @@ -123,6 +127,26 @@ class FgRequest(Model): await bot.set_friend_add_request( flag=req.flag, approve=handle_type == RequestHandleType.APPROVE ) + if BotProfileManager.is_auto_send_profile(): + file_path = await BotProfileManager.build_bot_profile_image( + bot.self_id + ) + if file_path: + await PlatformUtils.send_message( + bot, + req.user_id, + None, + MessageUtils.build_message( + [ + f"你好,我是{BotConfig.self_nickname}, " + "初次见面,希望我们可以好好相处!", + file_path, + ] + ), + ) + logger.info( + "添加好友自动发送BOT自我介绍图片", session=req.user_id + ) else: await GroupConsole.update_or_create( group_id=req.group_id, defaults={"group_flag": 1} diff --git a/zhenxun/utils/manager/bot_profile_manager.py b/zhenxun/utils/manager/bot_profile_manager.py new file mode 100644 index 00000000..bdd4cad6 --- /dev/null +++ b/zhenxun/utils/manager/bot_profile_manager.py @@ -0,0 +1,185 @@ +import asyncio +import os +from pathlib import Path +from typing import ClassVar + +import aiofiles +import nonebot +from nonebot.compat import model_dump +from nonebot_plugin_htmlrender import template_to_pic +from pydantic import BaseModel + +from zhenxun.configs.config import BotConfig, Config +from zhenxun.configs.path_config import DATA_PATH, TEMPLATE_PATH +from zhenxun.configs.utils.models import PluginExtraData +from zhenxun.models.statistics import Statistics +from zhenxun.models.user_console import UserConsole +from zhenxun.utils._build_image import BuildImage +from zhenxun.utils.platform import PlatformUtils + +DIR_PATH = DATA_PATH / "bot_profile" + +PROFILE_PATH = DIR_PATH / "profile" +PROFILE_PATH.mkdir(parents=True, exist_ok=True) + +PROFILE_IMAGE_PATH = DIR_PATH / "image" +PROFILE_IMAGE_PATH.mkdir(parents=True, exist_ok=True) + + +Config.add_plugin_config( + "bot_profile", + "AUTO_SEND_PROFILE", + True, + help="在添加好友/群组时是否自动发送BOT自我介绍图片", + default_value=True, + type=bool, +) + + +class Profile(BaseModel): + bot_id: str + """BOT ID""" + introduction: str + """BOT自我介绍""" + avatar: Path | None + """BOT头像""" + name: str + """BOT名称""" + + +class PluginProfile(BaseModel): + name: str + """插件名称""" + introduction: str + """插件自我介绍""" + precautions: list[str] | None = None + """BOT自我介绍时插件的注意事项""" + + +class BotProfileManager: + """BOT自我介绍管理器""" + + _bot_data: ClassVar[dict[str, Profile]] = {} + + _plugin_data: ClassVar[dict[str, PluginProfile]] = {} + + @classmethod + def clear_profile_image(cls, bot_id: str | None = None): + """清除BOT自我介绍图片""" + if bot_id: + file_path = PROFILE_IMAGE_PATH / f"{bot_id}.png" + if file_path.exists(): + file_path.unlink() + else: + for f in os.listdir(PROFILE_IMAGE_PATH): + _f = PROFILE_IMAGE_PATH / f + if _f.is_file(): + _f.unlink() + + @classmethod + async def _read_profile(cls, bot_id: str): + """读取BOT自我介绍 + + 参数: + bot_id: BOT ID + + 异常: + FileNotFoundError: 文件不存在 + """ + bot_file_path = PROFILE_PATH / f"{bot_id}" + bot_file_path.mkdir(parents=True, exist_ok=True) + bot_profile_file = bot_file_path / "profile.txt" + async with aiofiles.open(bot_profile_file, encoding="utf-8") as f: + introduction = await f.read() + avatar = bot_file_path / f"{bot_id}.png" + if not avatar.exists(): + avatar = None + bot = await PlatformUtils.get_user(nonebot.get_bot(bot_id), bot_id) + name = bot.name if bot else "未知" + cls._bot_data[bot_id] = Profile( + bot_id=bot_id, introduction=introduction, avatar=avatar, name=name + ) + + @classmethod + async def get_bot_profile(cls, bot_id: str) -> Profile | None: + if bot_id not in cls._bot_data: + await cls._read_profile(bot_id) + return cls._bot_data.get(bot_id) + + @classmethod + def load_plugin_profile(cls): + """加载插件自我介绍""" + for plugin in nonebot.get_loaded_plugins(): + if plugin.module_name in cls._plugin_data: + continue + metadata = plugin.metadata + if not metadata: + continue + extra = metadata.extra + if not extra: + continue + extra_data = PluginExtraData(**extra) + if extra_data.introduction or extra_data.precautions: + cls._plugin_data[plugin.name] = PluginProfile( + name=metadata.name, + introduction=extra_data.introduction or "", + precautions=extra_data.precautions or [], + ) + + @classmethod + def get_plugin_profile(cls) -> list[dict]: + """获取插件自我介绍""" + if not cls._plugin_data: + cls.load_plugin_profile() + return [model_dump(e) for e in cls._plugin_data.values()] + + @classmethod + def is_auto_send_profile(cls) -> bool: + """是否自动发送BOT自我介绍图片""" + return Config.get_config("bot_profile", "AUTO_SEND_PROFILE") + + @classmethod + async def build_bot_profile_image( + cls, bot_id: str, tags: list[dict[str, str]] | None = None + ) -> Path | None: + """构建BOT自我介绍图片""" + file_path = PROFILE_IMAGE_PATH / f"{bot_id}.png" + if file_path.exists(): + return file_path + profile, service_count, call_count = await asyncio.gather( + cls.get_bot_profile(bot_id), + UserConsole.get_new_uid(), + Statistics.filter(bot_id=bot_id).count(), + ) + if not profile: + return None + if not tags: + tags = [ + {"text": f"服务人数: {service_count}", "color": "#5e92e0"}, + {"text": f"调用次数: {call_count}", "color": "#31e074"}, + ] + image_bytes = await template_to_pic( + template_path=str((TEMPLATE_PATH / "bot_profile").absolute()), + template_name="main.html", + templates={ + "avatar": str(profile.avatar.absolute()) if profile.avatar else None, + "bot_name": profile.name, + "bot_description": profile.introduction, + "service_count": service_count, + "call_count": call_count, + "plugin_list": cls.get_plugin_profile(), + "tags": tags, + "title": f"{BotConfig.self_nickname}简介", + }, + pages={ + "viewport": {"width": 1077, "height": 1000}, + "base_url": f"file://{TEMPLATE_PATH}", + }, + wait=2, + ) + image = BuildImage.open(image_bytes) + await image.save(file_path) + return file_path + + +BotProfileManager.clear_profile_image()