import asyncio from pathlib import Path from typing import ClassVar import aiofiles import nonebot from pydantic import BaseModel from zhenxun.configs.config import BotConfig, Config from zhenxun.configs.path_config import DATA_PATH from zhenxun.configs.utils.models import PluginExtraData from zhenxun.models.statistics import Statistics from zhenxun.models.user_console import UserConsole from zhenxun.services import renderer_service from zhenxun.services.log import logger from zhenxun.utils.platform import PlatformUtils from zhenxun.utils.pydantic_compat import model_dump DIR_PATH = DATA_PATH / "bot_profile" PROFILE_PATH = DIR_PATH / "profile" PROFILE_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: if bot_id in cls._bot_data: del cls._bot_data[bot_id] else: cls._bot_data.clear() @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" if not bot_profile_file.exists(): logger.debug(f"BOT自我介绍文件不存在: {bot_profile_file}, 跳过读取") bot_file_path.touch() return 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 ) -> bytes | None: """构建BOT自我介绍图片""" 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"}, ] profile_data = { "avatar": profile.avatar.absolute().as_uri() 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}简介", } return await renderer_service.render( "pages/builtin/bot_profile", data=profile_data ) BotProfileManager.clear_profile_image()