新增自我介绍功能及自动发送图片支持

- 在 bot_profile.py 中实现自我介绍指令及重载功能
- 在 group_handle 中添加自动发送自我介绍图片的逻辑
- 在 fg_request 中实现添加好友时自动发送自我介绍图片
- 新增 bot_profile_manager.py 管理 BOT 自我介绍及图片生成
- 更新 models.py 以支持插件自我介绍和注意事项字段
This commit is contained in:
HibiKier 2025-07-15 16:57:50 +08:00
parent faa91b8bd4
commit a3671aa9b6
6 changed files with 287 additions and 4 deletions

View File

@ -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)

View File

@ -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:

View File

@ -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}受委屈哦(狠狠监控)"

View File

@ -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)

View File

@ -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}

View File

@ -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()