diff --git a/.gitignore b/.gitignore index 73ffe947..12907baa 100644 --- a/.gitignore +++ b/.gitignore @@ -174,8 +174,6 @@ data/ /resources/image/prts/ /configs/config.py configs/config.yaml -./.env -./.env.dev plugins/csgo_server/ plugins/activity/ !/resources/image/genshin/alc/back.png diff --git a/zhenxun/builtin_plugins/help/__init__.py b/zhenxun/builtin_plugins/help/__init__.py index 7bbcdbf3..8bce868d 100644 --- a/zhenxun/builtin_plugins/help/__init__.py +++ b/zhenxun/builtin_plugins/help/__init__.py @@ -16,10 +16,9 @@ from nonebot_plugin_alconna import ( from zhenxun.services.log import logger from zhenxun.utils.enum import PluginType from zhenxun.utils.message import MessageUtils -from zhenxun.configs.path_config import IMAGE_PATH from zhenxun.configs.utils import RegisterConfig, PluginExtraData +from zhenxun.builtin_plugins.help._config import GROUP_HELP_PATH, SIMPLE_HELP_IMAGE -from ._utils import GROUP_HELP_PATH from ._data_source import create_help_img, get_plugin_help __plugin_meta__ = PluginMetadata( @@ -42,10 +41,6 @@ __plugin_meta__ = PluginMetadata( ) -SIMPLE_HELP_IMAGE = IMAGE_PATH / "SIMPLE_HELP.png" -if SIMPLE_HELP_IMAGE.exists(): - SIMPLE_HELP_IMAGE.unlink() - _matcher = on_alconna( Alconna( "功能", @@ -66,11 +61,13 @@ async def _( session: EventSession, is_superuser: Query[bool] = AlconnaQuery("superuser.value", False), ): + if not session.id1: + await MessageUtils.build_message("用户id为空...").finish() _is_superuser = is_superuser.result if is_superuser.available else False if name.available: if _is_superuser and session.id1 not in bot.config.superusers: _is_superuser = False - if result := await get_plugin_help(name.result, _is_superuser): + if result := await get_plugin_help(session.id1, name.result, _is_superuser): await MessageUtils.build_message(result).send(reply_to=True) else: await MessageUtils.build_message("没有此功能的帮助信息...").send( @@ -80,11 +77,9 @@ async def _( elif gid := session.id3 or session.id2: _image_path = GROUP_HELP_PATH / f"{gid}.png" if not _image_path.exists(): - await create_help_img(bot.self_id, gid) + await create_help_img(bot.self_id, gid, session.platform) await MessageUtils.build_message(_image_path).finish() else: if not SIMPLE_HELP_IMAGE.exists(): - if SIMPLE_HELP_IMAGE.exists(): - SIMPLE_HELP_IMAGE.unlink() - await create_help_img(bot.self_id, None) + await create_help_img(bot.self_id, None, session.platform) await MessageUtils.build_message(SIMPLE_HELP_IMAGE).finish() diff --git a/zhenxun/builtin_plugins/help/_config.py b/zhenxun/builtin_plugins/help/_config.py index b38bf066..fe8fff55 100644 --- a/zhenxun/builtin_plugins/help/_config.py +++ b/zhenxun/builtin_plugins/help/_config.py @@ -1,13 +1,13 @@ -from pydantic import BaseModel +from zhenxun.configs.config import Config +from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH +GROUP_HELP_PATH = DATA_PATH / "group_help" +GROUP_HELP_PATH.mkdir(exist_ok=True, parents=True) +for f in GROUP_HELP_PATH.iterdir(): + f.unlink() -class Item(BaseModel): - plugin_name: str - sta: int +SIMPLE_HELP_IMAGE = IMAGE_PATH / "SIMPLE_HELP.png" +if SIMPLE_HELP_IMAGE.exists(): + SIMPLE_HELP_IMAGE.unlink() - -class PluginList(BaseModel): - plugin_type: str - icon: str - logo: str - items: list[Item] +base_config = Config.get("help") diff --git a/zhenxun/builtin_plugins/help/_data_source.py b/zhenxun/builtin_plugins/help/_data_source.py index d7384009..68db4407 100644 --- a/zhenxun/builtin_plugins/help/_data_source.py +++ b/zhenxun/builtin_plugins/help/_data_source.py @@ -1,39 +1,82 @@ import nonebot from zhenxun.utils.enum import PluginType +from zhenxun.models.level_user import LevelUser from zhenxun.models.plugin_info import PluginInfo from zhenxun.configs.path_config import IMAGE_PATH from zhenxun.utils.image_utils import BuildImage, ImageTemplate -from ._utils import HelpImageBuild +from .html_help import build_html_image +from .normal_help import build_normal_image +from .zhenxun_help import build_zhenxun_image +from ._config import GROUP_HELP_PATH, SIMPLE_HELP_IMAGE, base_config random_bk_path = IMAGE_PATH / "background" / "help" / "simple_help" background = IMAGE_PATH / "background" / "0.png" -async def create_help_img(bot_id: str, group_id: str | None): +driver = nonebot.get_driver() + + +async def create_help_img(bot_id: str, group_id: str | None, platform: str): """生成帮助图片 参数: bot_id: bot id group_id: 群号 + platform: 平台 """ - await HelpImageBuild().build_image(bot_id, group_id) + help_type: str = base_config.get("type") + if help_type.lower() == "html": + result = BuildImage.open(await build_html_image(group_id)) + elif help_type.lower() == "zhenxun": + result = BuildImage.open(await build_zhenxun_image(bot_id, group_id, platform)) + else: + result = await build_normal_image(group_id) + if group_id: + await result.save(GROUP_HELP_PATH / f"{group_id}.png") + else: + await result.save(SIMPLE_HELP_IMAGE) -async def get_plugin_help(name: str, is_superuser: bool) -> str | BuildImage: +async def get_user_allow_help(user_id: str) -> list[PluginType]: + """获取用户可访问插件类型列表 + + 参数: + user_id: 用户id + + 返回: + list[PluginType]: 插件类型列表 + """ + type_list = [PluginType.NORMAL, PluginType.DEPENDANT] + for level in await LevelUser.filter(user_id=user_id).values_list( + "user_level", flat=True + ): + if level > 0: # type: ignore + type_list.extend((PluginType.ADMIN, PluginType.SUPER_AND_ADMIN)) + break + if user_id in driver.config.superusers: + type_list.append(PluginType.SUPERUSER) + return type_list + + +async def get_plugin_help( + user_id: str, name: str, is_superuser: bool +) -> str | BuildImage: """获取功能的帮助信息 参数: + user_id: 用户id name: 插件名称或id is_superuser: 是否为超级用户 """ + type_list = await get_user_allow_help(user_id) if name.isdigit(): - plugin = await PluginInfo.get_or_none(id=int(name)) + plugin = await PluginInfo.get_or_none(id=int(name), plugin_type__in=type_list) else: plugin = await PluginInfo.get_or_none( - name__iexact=name, load_status=True, plugin_type__not=PluginType.PARENT + name__iexact=name, load_status=True, plugin_type__in=type_list ) if plugin: _plugin = nonebot.get_plugin_by_module_name(plugin.module_path) diff --git a/zhenxun/builtin_plugins/help/_utils.py b/zhenxun/builtin_plugins/help/_utils.py index d2fabff2..378cacc3 100644 --- a/zhenxun/builtin_plugins/help/_utils.py +++ b/zhenxun/builtin_plugins/help/_utils.py @@ -1,347 +1,45 @@ -import os -import random +from collections.abc import Callable -import aiofiles -from nonebot_plugin_htmlrender import template_to_pic - -from zhenxun.configs.config import Config +from zhenxun.utils.enum import PluginType from zhenxun.models.plugin_info import PluginInfo -from zhenxun.utils.enum import BlockType, PluginType from zhenxun.models.group_console import GroupConsole -from zhenxun.builtin_plugins.sign_in.utils import AVA_URL -from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH, TEMPLATE_PATH -from zhenxun.utils.image_utils import BuildImage, group_image, build_sort_image - -from ._config import Item - -GROUP_HELP_PATH = DATA_PATH / "group_help" -GROUP_HELP_PATH.mkdir(exist_ok=True, parents=True) -for f in os.listdir(GROUP_HELP_PATH): - group_help_image = GROUP_HELP_PATH / f - group_help_image.unlink() - -BACKGROUND_PATH = IMAGE_PATH / "background" / "help" / "simple_help" - -LOGO_PATH = TEMPLATE_PATH / "menu" / "res" / "logo" -class HelpImageBuild: - def __init__(self): - self._data: list[PluginInfo] = [] - self._sort_data: dict[str, list[PluginInfo]] = {} - self._image_list = [] - self.icon2str = { - "normal": "fa fa-cog", - "原神相关": "fa fa-circle-o", - "常规插件": "fa fa-cubes", - "联系管理员": "fa fa-envelope-o", - "抽卡相关": "fa fa-credit-card-alt", - "来点好康的": "fa fa-picture-o", - "数据统计": "fa fa-bar-chart", - "一些工具": "fa fa-shopping-cart", - "商店": "fa fa-shopping-cart", - "其它": "fa fa-tags", - "群内小游戏": "fa fa-gamepad", - } +async def sort_type() -> dict[str, list[PluginInfo]]: + """ + 对插件按照菜单类型分类 + """ + data = await PluginInfo.filter( + menu_type__not="", + load_status=True, + plugin_type__in=[PluginType.NORMAL, PluginType.DEPENDANT], + ) + sort_data = {} + for plugin in data: + menu_type = plugin.menu_type or "normal" + if menu_type == "normal": + menu_type = "功能" + if not sort_data.get(menu_type): + sort_data[menu_type] = [] + sort_data[menu_type].append(plugin) + return sort_data - async def sort_type(self): - """ - 对插件按照菜单类型分类 - """ - if not self._data: - self._data = await PluginInfo.filter( - menu_type__not="", - load_status=True, - plugin_type__in=[PluginType.NORMAL, PluginType.DEPENDANT], - ) - if not self._sort_data: - for plugin in self._data: - menu_type = plugin.menu_type or "normal" - if menu_type == "normal": - menu_type = "功能" - if not self._sort_data.get(menu_type): - self._sort_data[menu_type] = [] - self._sort_data[menu_type].append(plugin) - async def build_image(self, bot_id: str, group_id: str | None): - if group_id: - help_image = GROUP_HELP_PATH / f"{group_id}.png" - else: - help_image = IMAGE_PATH / "SIMPLE_HELP.png" - build_type = Config.get_config("help", "TYPE") - if build_type == "HTML": - byt = await self.build_html_image(group_id) - async with aiofiles.open(help_image, "wb") as f: - await f.write(byt) - elif build_type == "zhenxun": - byt = await self.build_ss_image(bot_id, group_id) - async with aiofiles.open(help_image, "wb") as f: - await f.write(byt) - else: - img = await self.build_pil_image(group_id) - await img.save(help_image) +async def classify_plugin(group_id: str | None, handle: Callable) -> dict[str, list]: + """对插件进行分类并判断状态 - async def build_ss_image(self, bot_id: str, group_id: str | None) -> bytes: - """构造ss帮助图片 + 参数: + group_id: 群组id - 参数: - group_id: 群号 - """ - await self.sort_type() - classify = {} - for menu in self._sort_data: - self._sort_data[menu].sort(key=lambda k: len(k.name)) - for menu in self._sort_data: - for plugin in self._sort_data[menu]: - if not plugin.status: - if group_id and plugin.block_type in [ - BlockType.ALL, - BlockType.GROUP, - ]: - plugin.name = f"{plugin.name}(不可用)" - if not group_id and plugin.block_type in [ - BlockType.ALL, - BlockType.PRIVATE, - ]: - plugin.name = f"{plugin.name}(不可用)" - if not classify.get(menu): - classify[menu] = [] - classify[menu].append( - Item(plugin_name=f"{plugin.id}-{plugin.name}", sta=0) - ) - max_len = 0 - flag_index = -1 - max_data = {} - plugin_list = [] - for index, plu in enumerate(classify.keys()): - data = { - "name": "主要功能" if plu in ["normal", "功能"] else plu, - "items": classify[plu], - } - if len(classify[plu]) > max_len: - max_len = len(classify[plu]) - flag_index = index - max_data = data - plugin_list.append(data) - del plugin_list[flag_index] - # plugin_list.insert(0, max_data) - _data = [] - _left = 30 - _pu1 = [] - _pu2 = [] - for i in range(len(max_data["items"])): - if i % 2: - _pu1.append(max_data["items"][i]) - else: - _pu2.append(max_data["items"][i]) - _plugins = [(30, 50, _pu1), (0, 50, _pu2)] - _data.append( - { - "name": max_data["name"], - "items": [(30, 50, _pu1), (0, 50, _pu2)], - "width": 100, - } - ) - for plugin in plugin_list: - _plugins = [] - width = 50 - if len(plugin["items"]) // 2 > 6: - width = 100 - _pu1 = [] - _pu2 = [] - for i in range(len(plugin["items"])): - if i % 2: - _pu1.append(plugin["items"][i]) - else: - _pu2.append(plugin["items"][i]) - _plugins = [(30, 50, _pu1), (0, 50, _pu2)] - else: - _plugins = [(_left, 100, plugin["items"])] - _left = 15 if _left == 30 else 30 - _data.append({"name": plugin["name"], "items": _plugins, "width": width}) - return await template_to_pic( - template_path=str((TEMPLATE_PATH / "ss_menu").absolute()), - template_name="main.html", - templates={"data": {"plugin_list": _data, "ava": AVA_URL.format(bot_id)}}, - pages={ - "viewport": {"width": 637, "height": 453}, - "base_url": f"file://{TEMPLATE_PATH}", - }, - wait=2, - ) - - async def build_html_image(self, group_id: str | None) -> bytes: - """构造HTML帮助图片 - - 参数: - group_id: 群号 - """ - await self.sort_type() - classify = {} - for menu in self._sort_data: - for plugin in self._sort_data[menu]: - sta = 0 - if not plugin.status: - if group_id and plugin.block_type in [ - BlockType.ALL, - BlockType.GROUP, - ]: - sta = 2 - if not group_id and plugin.block_type in [ - BlockType.ALL, - BlockType.PRIVATE, - ]: - sta = 2 - if group_id and ( - group := await GroupConsole.get_or_none(group_id=group_id) - ): - if f"{plugin.module}:super," in group.block_plugin: - sta = 2 - if f"{plugin.module}," in group.block_plugin: - sta = 1 - if classify.get(menu): - classify[menu].append(Item(plugin_name=plugin.name, sta=sta)) - else: - classify[menu] = [Item(plugin_name=plugin.name, sta=sta)] - max_len = 0 - flag_index = -1 - max_data = None - plugin_list = [] - for index, plu in enumerate(classify.keys()): - if plu in self.icon2str.keys(): - icon = self.icon2str[plu] - else: - icon = "fa fa-pencil-square-o" - logo = LOGO_PATH / random.choice(os.listdir(LOGO_PATH)) - data = { - "name": plu if plu != "normal" else "功能", - "items": classify[plu], - "icon": icon, - "logo": str(logo.absolute()), - } - if len(classify[plu]) > max_len: - max_len = len(classify[plu]) - flag_index = index - max_data = data - plugin_list.append(data) - del plugin_list[flag_index] - plugin_list.insert(0, max_data) - return await template_to_pic( - template_path=str((TEMPLATE_PATH / "menu").absolute()), - template_name="zhenxun_menu.html", - templates={"plugin_list": plugin_list}, - pages={ - "viewport": {"width": 1903, "height": 975}, - "base_url": f"file://{TEMPLATE_PATH}", - }, - wait=2, - ) - - async def build_pil_image(self, group_id: str | None) -> BuildImage: - """构造PIL帮助图片 - - 参数: - group_id: 群号 - """ - self._image_list = [] - await self.sort_type() - font_size = 24 - build_type = Config.get_config("help", "TYPE") - font = BuildImage.load_font("HYWenHei-85W.ttf", 20) - for idx, menu_type in enumerate(self._sort_data.keys()): - plugin_list = self._sort_data[menu_type] - wh_list = [ - BuildImage.get_text_size(f"{x.id}.{x.name}", font) for x in plugin_list - ] - wh_list.append(BuildImage.get_text_size(menu_type, font)) - # sum_height = sum([x[1] for x in wh_list]) - if build_type == "VV": - sum_height = 50 * len(plugin_list) + 10 - else: - sum_height = (font_size + 6) * len(plugin_list) + 10 - max_width = max(x[0] for x in wh_list) + 30 - bk = BuildImage( - max_width + 40, - sum_height + 50, - font_size=30, - color="#a7d1fc", - font="CJGaoDeGuo.otf", - ) - title_size = bk.getsize(menu_type) - max_width = max_width if max_width > title_size[0] else title_size[0] - B = BuildImage( - max_width + 40, - sum_height, - font_size=font_size, - color="black" if idx % 2 else "white", - ) - curr_h = 10 - group = await GroupConsole.get_or_none(group_id=group_id) - for i, plugin in enumerate(plugin_list): - text_color = (255, 255, 255) if idx % 2 else (0, 0, 0) - if group and f"{plugin.module}," in group.block_plugin: - text_color = (252, 75, 13) - pos = None - # 禁用状态划线 - if plugin.block_type in [BlockType.ALL, BlockType.GROUP] or ( - group and f"super:{plugin.module}," in group.block_plugin - ): - w = curr_h + int(B.getsize(plugin.name)[1] / 2) + 2 - pos = ( - 7, - w, - B.getsize(plugin.name)[0] + 35, - w, - ) - if build_type == "VV": - name_image = await self.build_name_image( # type: ignore - max_width, - plugin.name, - "white" if idx % 2 else "black", - text_color, - pos, - ) - await B.paste(name_image, (0, curr_h), center_type="width") - curr_h += name_image.h + 5 - else: - await B.text((10, curr_h), f"{plugin.id}.{plugin.name}", text_color) - if pos: - await B.line(pos, (236, 66, 7), 3) - curr_h += font_size + 5 - await bk.text((0, 14), menu_type, center_type="width") - await bk.paste(B, (0, 50)) - await bk.transparent(2) - self._image_list.append(bk) - image_group, h = group_image(self._image_list) - - async def _a(image: BuildImage): - await image.filter("GaussianBlur", 5) - - B = await build_sort_image( - image_group, - h, - background_path=BACKGROUND_PATH, - background_handle=_a, - ) - w = 10 - h = 10 - for msg in [ - "目前支持的功能列表:", - "可以通过 ‘帮助 [功能名称或功能Id]’ 来获取对应功能的使用方法", - ]: - text = await BuildImage.build_text_image(msg, "HYWenHei-85W.ttf", 24) - await B.paste(text, (w, h)) - h += 50 - if msg == "目前支持的功能列表:": - w += 50 - text = await BuildImage.build_text_image( - "注: 红字代表功能被群管理员禁用,红线代表功能正在维护", - "HYWenHei-85W.ttf", - 24, - (231, 74, 57), - ) - await B.paste( - text, - (300, 10), - ) - return B + 返回: + dict[str, list[Item]]: 分类插件数据 + """ + sort_data = await sort_type() + classify: dict[str, list] = {} + group = await GroupConsole.get_or_none(group_id=group_id) if group_id else None + for menu, value in sort_data.items(): + for plugin in value: + if not classify.get(menu): + classify[menu] = [] + classify[menu].append(handle(plugin, group)) + return classify diff --git a/zhenxun/builtin_plugins/help/html_help.py b/zhenxun/builtin_plugins/help/html_help.py new file mode 100644 index 00000000..5e88fc28 --- /dev/null +++ b/zhenxun/builtin_plugins/help/html_help.py @@ -0,0 +1,136 @@ +import os +import random + +from pydantic import BaseModel +from nonebot_plugin_htmlrender import template_to_pic + +from zhenxun.utils.enum import BlockType +from zhenxun.models.plugin_info import PluginInfo +from zhenxun.configs.path_config import TEMPLATE_PATH +from zhenxun.models.group_console import GroupConsole + +from ._utils import classify_plugin + +LOGO_PATH = TEMPLATE_PATH / "menu" / "res" / "logo" + + +class Item(BaseModel): + plugin_name: str + """插件名称""" + sta: int + """插件状态""" + + +class PluginList(BaseModel): + plugin_type: str + """菜单名称""" + icon: str + """图标""" + logo: str + """logo""" + items: list[Item] + """插件列表""" + + +ICON2STR = { + "normal": "fa fa-cog", + "原神相关": "fa fa-circle-o", + "常规插件": "fa fa-cubes", + "联系管理员": "fa fa-envelope-o", + "抽卡相关": "fa fa-credit-card-alt", + "来点好康的": "fa fa-picture-o", + "数据统计": "fa fa-bar-chart", + "一些工具": "fa fa-shopping-cart", + "商店": "fa fa-shopping-cart", + "其它": "fa fa-tags", + "群内小游戏": "fa fa-gamepad", +} + + +def __handle_item(plugin: PluginInfo, group: GroupConsole | None) -> Item: + """构造Item + + 参数: + plugin: PluginInfo + group: 群组 + + 返回: + Item: Item + """ + sta = 0 + if not plugin.status: + if group and plugin.block_type in [ + BlockType.ALL, + BlockType.GROUP, + ]: + sta = 2 + if not group and plugin.block_type in [ + BlockType.ALL, + BlockType.PRIVATE, + ]: + sta = 2 + if group: + if f"{plugin.module}:super," in group.block_plugin: + sta = 2 + if f"{plugin.module}," in group.block_plugin: + sta = 1 + return Item(plugin_name=plugin.name, sta=sta) + + +def build_plugin_data(classify: dict[str, list[Item]]) -> list[dict[str, str]]: + """构建前端插件数据 + + 参数: + classify: 插件数据 + + 返回: + list[dict[str, str]]: 前端插件数据 + """ + lengths = [len(classify[c]) for c in classify] + index = lengths.index(max(lengths)) + menu_key = list(classify.keys())[index] + max_data = classify[menu_key] + del classify[menu_key] + plugin_list = [] + for menu_type in classify: + icon = "fa fa-pencil-square-o" + if menu_type in ICON2STR.keys(): + icon = ICON2STR[menu_type] + logo = LOGO_PATH / random.choice(os.listdir(LOGO_PATH)) + data = { + "name": menu_type if menu_type != "normal" else "功能", + "items": classify[menu_type], + "icon": icon, + "logo": str(logo.absolute()), + } + plugin_list.append(data) + plugin_list.insert( + 0, + { + "name": menu_key if menu_key != "normal" else "功能", + "items": max_data, + "icon": "fa fa-pencil-square-o", + "logo": str((LOGO_PATH / random.choice(os.listdir(LOGO_PATH))).absolute()), + }, + ) + return plugin_list + + +async def build_html_image(group_id: str | None) -> bytes: + """构造HTML帮助图片 + + 参数: + group_id: 群号 + """ + classify = await classify_plugin(group_id, __handle_item) + plugin_list = build_plugin_data(classify) + return await template_to_pic( + template_path=str((TEMPLATE_PATH / "menu").absolute()), + template_name="zhenxun_menu.html", + templates={"plugin_list": plugin_list}, + pages={ + "viewport": {"width": 1903, "height": 975}, + "base_url": f"file://{TEMPLATE_PATH}", + }, + wait=2, + ) diff --git a/zhenxun/builtin_plugins/help/normal_help.py b/zhenxun/builtin_plugins/help/normal_help.py new file mode 100644 index 00000000..5a937745 --- /dev/null +++ b/zhenxun/builtin_plugins/help/normal_help.py @@ -0,0 +1,99 @@ +from zhenxun.utils.enum import BlockType +from zhenxun.utils._build_image import BuildImage +from zhenxun.configs.path_config import IMAGE_PATH +from zhenxun.models.group_console import GroupConsole +from zhenxun.utils.image_utils import group_image, build_sort_image + +from ._utils import sort_type + +BACKGROUND_PATH = IMAGE_PATH / "background" / "help" / "simple_help" + + +async def build_normal_image(group_id: str | None) -> BuildImage: + """构造PIL帮助图片 + + 参数: + group_id: 群号 + """ + image_list = [] + font_size = 24 + font = BuildImage.load_font("HYWenHei-85W.ttf", 20) + sort_data = await sort_type() + for idx, menu_type in enumerate(sort_data): + plugin_list = sort_data[menu_type] + """拿到最大宽度和结算高度""" + wh_list = [ + BuildImage.get_text_size(f"{x.id}.{x.name}", font) for x in plugin_list + ] + wh_list.append(BuildImage.get_text_size(menu_type, font)) + sum_height = (font_size + 6) * len(plugin_list) + 10 + max_width = max(x[0] for x in wh_list) + 30 + bk = BuildImage( + max_width + 40, + sum_height + 50, + font_size=30, + color="#a7d1fc", + font="CJGaoDeGuo.otf", + ) + title_size = bk.getsize(menu_type) + max_width = max_width if max_width > title_size[0] else title_size[0] + row = BuildImage( + max_width + 40, + sum_height, + font_size=font_size, + color="black" if idx % 2 else "white", + ) + curr_h = 10 + group = await GroupConsole.get_or_none(group_id=group_id) + for _, plugin in enumerate(plugin_list): + text_color = (255, 255, 255) if idx % 2 else (0, 0, 0) + if group and f"{plugin.module}," in group.block_plugin: + text_color = (252, 75, 13) + pos = None + # 禁用状态划线 + if plugin.block_type in [BlockType.ALL, BlockType.GROUP] or ( + group and f"super:{plugin.module}," in group.block_plugin + ): + w = curr_h + int(row.getsize(plugin.name)[1] / 2) + 2 + line_width = row.getsize(plugin.name)[0] + 35 + pos = (7, w, line_width, w) + await row.text((10, curr_h), f"{plugin.id}.{plugin.name}", text_color) + if pos: + await row.line(pos, (236, 66, 7), 3) + curr_h += font_size + 5 + await bk.text((0, 14), menu_type, center_type="width") + await bk.paste(row, (0, 50)) + await bk.transparent(2) + image_list.append(bk) + image_group, h = group_image(image_list) + + async def _a(image: BuildImage): + await image.filter("GaussianBlur", 5) + + result = await build_sort_image( + image_group, + h, + background_path=BACKGROUND_PATH, + background_handle=_a, + ) + width, height = 10, 10 + for s in [ + "目前支持的功能列表:", + "可以通过 ‘帮助 [功能名称或功能Id]’ 来获取对应功能的使用方法", + ]: + text = await BuildImage.build_text_image(s, "HYWenHei-85W.ttf", 24) + await result.paste(text, (width, height)) + height += 50 + if s == "目前支持的功能列表:": + width += 50 + text = await BuildImage.build_text_image( + "注: 红字代表功能被群管理员禁用,红线代表功能正在维护", + "HYWenHei-85W.ttf", + 24, + (231, 74, 57), + ) + await result.paste( + text, + (300, 10), + ) + return result diff --git a/zhenxun/builtin_plugins/help/zhenxun_help.py b/zhenxun/builtin_plugins/help/zhenxun_help.py new file mode 100644 index 00000000..09ebd931 --- /dev/null +++ b/zhenxun/builtin_plugins/help/zhenxun_help.py @@ -0,0 +1,143 @@ +from pydantic import BaseModel +from nonebot_plugin_htmlrender import template_to_pic + +from zhenxun.utils.enum import BlockType +from zhenxun.utils.platform import PlatformUtils +from zhenxun.models.plugin_info import PluginInfo +from zhenxun.configs.path_config import TEMPLATE_PATH +from zhenxun.models.group_console import GroupConsole + +from ._utils import classify_plugin + + +class Item(BaseModel): + plugin_name: str + """插件名称""" + + +def __handle_item(plugin: PluginInfo, group: GroupConsole | None): + """构造Item + + 参数: + plugin: PluginInfo + group: 群组 + + 返回: + Item: Item + """ + if not plugin.status: + if plugin.block_type == BlockType.ALL: + plugin.name = f"{plugin.name}(不可用)" + elif ( + group + and plugin.block_type == BlockType.GROUP + or not group + and plugin.block_type == BlockType.PRIVATE + ): + plugin.name = f"{plugin.name}(不可用)" + return Item(plugin_name=f"{plugin.id}-{plugin.name}") + + +def build_plugin_data(classify: dict[str, list[Item]]) -> list[dict[str, str]]: + """构建前端插件数据 + + 参数: + classify: 插件数据 + + 返回: + list[dict[str, str]]: 前端插件数据 + """ + + lengths = [len(classify[c]) for c in classify] + index = lengths.index(max(lengths)) + menu_key = list(classify.keys())[index] + max_data = classify[menu_key] + del classify[menu_key] + plugin_list = [ + { + "name": "主要功能" if menu in ["normal", "功能"] else menu, + "items": value, + } + for menu, value in classify.items() + ] + plugin_list = build_line_data(plugin_list) + plugin_list.insert(0, build_plugin_line(menu_key, max_data, 30, 100)) + return plugin_list + + +def build_plugin_line( + name: str, items: list, left: int, width: int | None = None +) -> dict: + """构造插件行数据 + + 参数: + name: 菜单名称 + items: 插件名称列表 + left: 左边距 + width: 总插件长度. + + 返回: + dict: 插件数据 + """ + _plugins = [] + width = width or 50 + if len(items) // 2 > 6: + width = 100 + plugin_list1 = [] + plugin_list2 = [] + for i in range(len(items)): + if i % 2: + plugin_list1.append(items[i]) + else: + plugin_list2.append(items[i]) + _plugins = [(30, 50, plugin_list1), (0, 50, plugin_list2)] + else: + _plugins = [(left, 100, items)] + return {"name": name, "items": _plugins, "width": width} + + +def build_line_data(plugin_list: list[dict]) -> list[dict]: + """构造插件数据 + + 参数: + plugin_list: 插件列表 + + 返回: + list[dict]: 插件数据 + """ + left = 30 + data = [] + for plugin in plugin_list: + data.append(build_plugin_line(plugin["name"], plugin["items"], left)) + if len(plugin["items"]) // 2 <= 6: + left = 15 if left == 30 else 30 + return data + + +async def build_zhenxun_image( + bot_id: str, group_id: str | None, platform: str +) -> bytes: + """构造真寻帮助图片 + + 参数: + bot_id: bot_id + group_id: 群号 + platform: 平台 + """ + classify = await classify_plugin(group_id, __handle_item) + plugin_list = build_plugin_data(classify) + return await template_to_pic( + template_path=str((TEMPLATE_PATH / "ss_menu").absolute()), + template_name="main.html", + templates={ + "data": { + "plugin_list": plugin_list, + "ava": PlatformUtils.get_user_avatar_url(bot_id, platform), + } + }, + pages={ + "viewport": {"width": 637, "height": 453}, + "base_url": f"file://{TEMPLATE_PATH}", + }, + wait=2, + ) diff --git a/zhenxun/utils/platform.py b/zhenxun/utils/platform.py index c1213904..91ab8eb0 100644 --- a/zhenxun/utils/platform.py +++ b/zhenxun/utils/platform.py @@ -266,6 +266,18 @@ class PlatformUtils: ) return None + @classmethod + async def get_user_avatar_url(cls, user_id: str, platform: str) -> str | None: + """快捷获取用户头像url + + 参数: + user_id: 用户id + platform: 平台 + """ + if platform == "qq": + return f"http://q1.qlogo.cn/g?b=qq&nk={user_id}&s=160" + return None + @classmethod async def get_group_avatar(cls, gid: str, platform: str) -> bytes | None: """快捷获取用群头像