diff --git a/resources/template/help/main.css b/resources/template/help/main.css new file mode 100644 index 00000000..1f2d7c0e --- /dev/null +++ b/resources/template/help/main.css @@ -0,0 +1,116 @@ + + +@font-face { + font-family: fzrzFont; + /* 导入的字体文件 */ + src: url("./res/font/fzrzExtraBold.ttf"); +} + +@font-face { + font-family: syhtFont; + /* 导入的字体文件 */ + src: url("./res/font/syht.otf"); +} + +@font-face { + font-family: systFont; + /* 导入的字体文件 */ + src: url("./res/font/syst.otf"); +} + + + + + +body { + position: absolute; + left: -8px; + top: -8px; +} + +.wrapper{ + width: 1400px; + position: relative; + background-image: url('res/img/bk.jpg'); + background-size: cover; + font-family: 'cr105Font'; + padding: 20px; +} + + +.title { + font-size: 60px; + font-family: 'fzrzFont'; + /* margin-left: 40px; */ + /* color: #F67186; */ + background: linear-gradient(to right, #F67186, #F7889C); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + text-align: center; +} + +.main { + background-image: url('res/img/main.png'); + background-size: 100% 100%; + /* background-size: cover; */ + height: 100%; + width: 1370px; + position: relative; + padding: 20px; + /* box-shadow: 5px 5px 10px 0 rgba(0,0,0,0.5); */ +} + +.items-border { + display: flex; + flex-wrap: wrap; + padding-left: 18px; +} + +.items { + border: #F67186 2px solid; + border-radius: 20px; + width: 675px; + padding: 30px; + max-width: 600px; +} + +.item-title { + background-image: url('res/img/title.png'); + background-size: cover; + background-repeat: no-repeat; + height: 55px; + width: 350px; + font-family: 'fzrzFont'; + display: flex; + justify-content: center; + align-items: center; + color: white; + font-size: 35px; + border-radius: 16px; + letter-spacing:4px; +} + +.usage-title { + font-size: 30px; + position: absolute; + top: -30px; +} + +.item-des { + color: #F78094; + border-radius: 20px; + font-family: 'fzrzFont'; + font-size: 30px; + padding: 10px; +} + +.item-usage { + color: #F78094; + border-radius: 20px; + font-family: 'fzrzFont'; + font-size: 20px; + border: #F67186 5px dotted; + padding: 60px 10px 10px 10px; + position: relative; +} \ No newline at end of file diff --git a/resources/template/help/main.html b/resources/template/help/main.html new file mode 100644 index 00000000..d289ddb6 --- /dev/null +++ b/resources/template/help/main.html @@ -0,0 +1,62 @@ + + + + + + + + test + + + + + +
+
+
+ {{data.nickname}}的{{data.help_name}}帮助 +
+
+ {% for plugin in data['plugin_list'] %} +
+
+ {{plugin.name}} +
+
+ 简介: {{plugin.description}} +
+
+

用法:

+ {{plugin.usage}} +
+
+ {% endfor %} +
+ +
+
+ + + + \ No newline at end of file diff --git a/resources/template/help/main.js b/resources/template/help/main.js new file mode 100644 index 00000000..e69de29b diff --git a/resources/template/help/res/font/fzrzExtraBold.ttf b/resources/template/help/res/font/fzrzExtraBold.ttf new file mode 100644 index 00000000..8afeafca Binary files /dev/null and b/resources/template/help/res/font/fzrzExtraBold.ttf differ diff --git a/resources/template/help/res/font/syht.otf b/resources/template/help/res/font/syht.otf new file mode 100644 index 00000000..630d5467 Binary files /dev/null and b/resources/template/help/res/font/syht.otf differ diff --git a/resources/template/help/res/font/syst.otf b/resources/template/help/res/font/syst.otf new file mode 100644 index 00000000..efa94cb5 Binary files /dev/null and b/resources/template/help/res/font/syst.otf differ diff --git a/resources/template/help/res/img/bk.jpg b/resources/template/help/res/img/bk.jpg new file mode 100644 index 00000000..2883c68e Binary files /dev/null and b/resources/template/help/res/img/bk.jpg differ diff --git a/resources/template/help/res/img/main.png b/resources/template/help/res/img/main.png new file mode 100644 index 00000000..e1f8110e Binary files /dev/null and b/resources/template/help/res/img/main.png differ diff --git a/resources/template/help/res/img/title.png b/resources/template/help/res/img/title.png new file mode 100644 index 00000000..d5417753 Binary files /dev/null and b/resources/template/help/res/img/title.png differ diff --git a/zhenxun/builtin_plugins/admin/admin_help.py b/zhenxun/builtin_plugins/admin/admin_help.py deleted file mode 100644 index e98df047..00000000 --- a/zhenxun/builtin_plugins/admin/admin_help.py +++ /dev/null @@ -1,160 +0,0 @@ -import nonebot -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Arparma, on_alconna -from nonebot_plugin_alconna.matcher import AlconnaMatcher -from nonebot_plugin_session import EventSession - -from zhenxun.configs.path_config import IMAGE_PATH -from zhenxun.configs.utils import PluginExtraData -from zhenxun.models.plugin_info import PluginInfo -from zhenxun.models.task_info import TaskInfo -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType -from zhenxun.utils.exception import EmptyError -from zhenxun.utils.image_utils import ( - BuildImage, - build_sort_image, - group_image, - text2image, -) -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.rules import admin_check, ensure_group - -__plugin_meta__ = PluginMetadata( - name="群组管理员帮助", - description="管理员帮助列表", - usage=""" - 管理员帮助 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - plugin_type=PluginType.ADMIN, - admin_level=1, - ).dict(), -) - -_matcher = on_alconna( - Alconna("管理员帮助"), - rule=admin_check(1) & ensure_group, - priority=5, - block=True, -) - - -ADMIN_HELP_IMAGE = IMAGE_PATH / "ADMIN_HELP.png" -if ADMIN_HELP_IMAGE.exists(): - ADMIN_HELP_IMAGE.unlink() - - -async def build_help() -> BuildImage: - """构造管理员帮助图片 - - 异常: - EmptyError: 管理员帮助为空 - - 返回: - BuildImage: 管理员帮助图片 - """ - plugin_list = await PluginInfo.filter( - plugin_type__in=[PluginType.ADMIN, PluginType.SUPER_AND_ADMIN] - ).all() - data_list = [] - for plugin in plugin_list: - if _plugin := nonebot.get_plugin_by_module_name(plugin.module_path): - if _plugin.metadata: - data_list.append({"plugin": plugin, "metadata": _plugin.metadata}) - font = BuildImage.load_font("HYWenHei-85W.ttf", 20) - image_list = [] - for data in data_list: - plugin = data["plugin"] - metadata = data["metadata"] - try: - usage = None - description = None - if metadata.usage: - usage = await text2image( - metadata.usage, - padding=5, - color=(255, 255, 255), - font_color=(0, 0, 0), - ) - if metadata.description: - description = await text2image( - metadata.description, - padding=5, - color=(255, 255, 255), - font_color=(0, 0, 0), - ) - width = 0 - height = 100 - if usage: - width = usage.width - height += usage.height - if description and description.width > width: - width = description.width - height += description.height - font_width, font_height = BuildImage.get_text_size( - plugin.name + f"[{plugin.level}]", font - ) - if font_width > width: - width = font_width - A = BuildImage(width + 30, height + 120, "#EAEDF2") - await A.text((15, 10), plugin.name + f"[{plugin.level}]") - await A.text((15, 70), "简介:") - if not description: - description = BuildImage(A.width - 30, 30, (255, 255, 255)) - await description.circle_corner(10) - await A.paste(description, (15, 100)) - if not usage: - usage = BuildImage(A.width - 30, 30, (255, 255, 255)) - await usage.circle_corner(10) - await A.text((15, description.height + 115), "用法:") - await A.paste(usage, (15, description.height + 145)) - await A.circle_corner(10) - image_list.append(A) - except Exception as e: - logger.warning( - f"获取群管理员插件 {plugin.module}: {plugin.name} 设置失败...", - "管理员帮助", - e=e, - ) - if task_list := await TaskInfo.all(): - task_str = "\n".join([task.name for task in task_list]) - task_str = "通过 开启/关闭群被动 来控制群被动\n----------\n" + task_str - task_image = await text2image(task_str, padding=5, color=(255, 255, 255)) - await task_image.circle_corner(10) - A = BuildImage(task_image.width + 50, task_image.height + 85, "#EAEDF2") - await A.text((25, 10), "被动技能") - await A.paste(task_image, (25, 50)) - await A.circle_corner(10) - image_list.append(A) - if not image_list: - raise EmptyError() - image_group, _ = group_image(image_list) - A = await build_sort_image(image_group, color=(255, 255, 255), padding_top=160) - text = await BuildImage.build_text_image( - "群管理员帮助", - size=40, - ) - tip = await BuildImage.build_text_image( - "注: ‘*’ 代表可有多个相同参数 ‘?’ 代表可省略该参数", size=25, font_color="red" - ) - await A.paste(text, (50, 30)) - await A.paste(tip, (50, 90)) - await A.save(ADMIN_HELP_IMAGE) - return BuildImage(1, 1) - - -@_matcher.handle() -async def _( - session: EventSession, - arparma: Arparma, -): - if not ADMIN_HELP_IMAGE.exists(): - try: - await build_help() - except EmptyError: - await MessageUtils.build_message("管理员帮助为空").finish(reply_to=True) - await MessageUtils.build_message(ADMIN_HELP_IMAGE).send() - logger.info("查看管理员帮助", arparma.header_result, session=session) diff --git a/zhenxun/builtin_plugins/admin/admin_help/__init__.py b/zhenxun/builtin_plugins/admin/admin_help/__init__.py new file mode 100644 index 00000000..7a8d501a --- /dev/null +++ b/zhenxun/builtin_plugins/admin/admin_help/__init__.py @@ -0,0 +1,63 @@ +from nonebot.plugin import PluginMetadata +from nonebot_plugin_session import EventSession +from nonebot_plugin_alconna import Alconna, Arparma, on_alconna + +from zhenxun.services.log import logger +from zhenxun.configs.config import Config +from zhenxun.utils.enum import PluginType +from zhenxun.utils.exception import EmptyError +from zhenxun.utils.message import MessageUtils +from zhenxun.utils.rules import admin_check, ensure_group +from zhenxun.configs.utils import RegisterConfig, PluginExtraData + +from .normal_help import build_help +from .config import ADMIN_HELP_IMAGE +from .html_help import build_html_help + +__plugin_meta__ = PluginMetadata( + name="群组管理员帮助", + description="管理员帮助列表", + usage=""" + 管理员帮助 + """.strip(), + extra=PluginExtraData( + author="HibiKier", + version="0.1", + plugin_type=PluginType.ADMIN, + admin_level=1, + configs=[ + RegisterConfig( + key="type", + value="zhenxun", + help="管理员帮助样式,normal, zhenxun", + default_value="zhenxun", + ) + ], + ).dict(), +) + +_matcher = on_alconna( + Alconna("管理员帮助"), + rule=admin_check(1) & ensure_group, + priority=5, + block=True, +) + + +@_matcher.handle() +async def _( + session: EventSession, + arparma: Arparma, +): + if not ADMIN_HELP_IMAGE.exists(): + try: + if Config.get_config("admin_help", "type") == "zhenxun": + await build_html_help() + else: + await build_help() + except EmptyError: + await MessageUtils.build_message("当前管理员帮助为空...").finish( + reply_to=True + ) + await MessageUtils.build_message(ADMIN_HELP_IMAGE).send() + logger.info("查看管理员帮助", arparma.header_result, session=session) diff --git a/zhenxun/builtin_plugins/admin/admin_help/config.py b/zhenxun/builtin_plugins/admin/admin_help/config.py new file mode 100644 index 00000000..dbc62efc --- /dev/null +++ b/zhenxun/builtin_plugins/admin/admin_help/config.py @@ -0,0 +1,23 @@ +from pydantic import BaseModel +from nonebot.plugin import PluginMetadata + +from zhenxun.models.plugin_info import PluginInfo +from zhenxun.configs.path_config import IMAGE_PATH + +ADMIN_HELP_IMAGE = IMAGE_PATH / "ADMIN_HELP.png" +if ADMIN_HELP_IMAGE.exists(): + ADMIN_HELP_IMAGE.unlink() + + +class PluginData(BaseModel): + """ + 插件信息 + """ + + plugin: PluginInfo + """插件信息""" + metadata: PluginMetadata + """元数据""" + + class Config: + arbitrary_types_allowed = True diff --git a/zhenxun/builtin_plugins/admin/admin_help/html_help.py b/zhenxun/builtin_plugins/admin/admin_help/html_help.py new file mode 100644 index 00000000..653dcff6 --- /dev/null +++ b/zhenxun/builtin_plugins/admin/admin_help/html_help.py @@ -0,0 +1,55 @@ +from nonebot_plugin_htmlrender import template_to_pic + +from zhenxun.configs.config import BotConfig +from zhenxun.models.task_info import TaskInfo +from zhenxun.utils._build_image import BuildImage +from zhenxun.configs.path_config import TEMPLATE_PATH +from zhenxun.builtin_plugins.admin.admin_help.config import ADMIN_HELP_IMAGE + +from .utils import get_plugins + + +async def get_task() -> dict[str, str] | None: + """获取被动技能帮助""" + if task_list := await TaskInfo.all(): + return { + "name": "被动技能", + "description": "控制群组中的被动技能状态", + "usage": "通过 开启/关闭群被动 来控制群被
----------
" + + "
".join([task.name for task in task_list]), + } + return None + + +async def build_html_help(): + """构建帮助图片""" + plugins = await get_plugins() + plugin_list = [ + { + "name": data.plugin.name, + "description": data.metadata.description.replace("\n", "
"), + "usage": data.metadata.usage.replace("\n", "
"), + } + for data in plugins + ] + if task := await get_task(): + plugin_list.append(task) + plugin_list.sort(key=lambda p: len(p["description"]) + len(p["usage"])) + pic = await template_to_pic( + template_path=str((TEMPLATE_PATH / "help").absolute()), + template_name="main.html", + templates={ + "data": { + "plugin_list": plugin_list, + "nickname": BotConfig.self_nickname, + "help_name": "群管理员", + } + }, + pages={ + "viewport": {"width": 1024, "height": 1024}, + "base_url": f"file://{TEMPLATE_PATH}", + }, + wait=2, + ) + result = await BuildImage.open(pic).resize(0.5) + await result.save(ADMIN_HELP_IMAGE) diff --git a/zhenxun/builtin_plugins/admin/admin_help/normal_help.py b/zhenxun/builtin_plugins/admin/admin_help/normal_help.py new file mode 100644 index 00000000..5a9bb923 --- /dev/null +++ b/zhenxun/builtin_plugins/admin/admin_help/normal_help.py @@ -0,0 +1,127 @@ +from PIL.ImageFont import FreeTypeFont +from nonebot.plugin import PluginMetadata + +from zhenxun.services.log import logger +from zhenxun.models.task_info import TaskInfo +from zhenxun.models.plugin_info import PluginInfo +from zhenxun.utils._build_image import BuildImage +from zhenxun.utils.image_utils import text2image, group_image, build_sort_image + +from .utils import get_plugins +from .config import ADMIN_HELP_IMAGE + + +async def build_usage_des_image( + metadata: PluginMetadata, +) -> tuple[BuildImage | None, BuildImage | None]: + """构建用法和描述图片 + + 参数: + metadata: PluginMetadata + + 返回: + tuple[BuildImage | None, BuildImage | None]: 用法和描述图片 + """ + usage = None + description = None + if metadata.usage: + usage = await text2image( + metadata.usage, + padding=5, + color=(255, 255, 255), + font_color=(0, 0, 0), + ) + if metadata.description: + description = await text2image( + metadata.description, + padding=5, + color=(255, 255, 255), + font_color=(0, 0, 0), + ) + return usage, description + + +async def build_image( + plugin: PluginInfo, metadata: PluginMetadata, font: FreeTypeFont +) -> BuildImage: + """构建帮助图片 + + 参数: + plugin: PluginInfo + metadata: PluginMetadata + font: FreeTypeFont + + 返回: + BuildImage: 帮助图片 + + """ + usage, description = await build_usage_des_image(metadata) + width = 0 + height = 100 + if usage: + width = usage.width + height += usage.height + if description and description.width > width: + width = description.width + height += description.height + font_width, _ = BuildImage.get_text_size(f"{plugin.name}[{plugin.level}]", font) + if font_width > width: + width = font_width + A = BuildImage(width + 30, height + 120, "#EAEDF2") + await A.text((15, 10), f"{plugin.name}[{plugin.level}]") + await A.text((15, 70), "简介:") + if not description: + description = BuildImage(A.width - 30, 30, (255, 255, 255)) + await description.circle_corner(10) + await A.paste(description, (15, 100)) + if not usage: + usage = BuildImage(A.width - 30, 30, (255, 255, 255)) + await usage.circle_corner(10) + await A.text((15, description.height + 115), "用法:") + await A.paste(usage, (15, description.height + 145)) + await A.circle_corner(10) + return A + + +async def build_help(): + """构造管理员帮助图片 + + 返回: + BuildImage: 管理员帮助图片 + """ + font = BuildImage.load_font("HYWenHei-85W.ttf", 20) + image_list = [] + for data in await get_plugins(): + plugin = data.plugin + metadata = data.metadata + try: + A = await build_image(plugin, metadata, font) + image_list.append(A) + except Exception as e: + logger.warning( + f"获取群管理员插件 {plugin.module}: {plugin.name} 设置失败...", + "管理员帮助", + e=e, + ) + if task_list := await TaskInfo.all(): + task_str = "\n".join([task.name for task in task_list]) + task_str = "通过 开启/关闭群被动 来控制群被动\n----------\n" + task_str + task_image = await text2image(task_str, padding=5, color=(255, 255, 255)) + await task_image.circle_corner(10) + A = BuildImage(task_image.width + 50, task_image.height + 85, "#EAEDF2") + await A.text((25, 10), "被动技能") + await A.paste(task_image, (25, 50)) + await A.circle_corner(10) + image_list.append(A) + image_group, _ = group_image(image_list) + A = await build_sort_image(image_group, color=(255, 255, 255), padding_top=160) + text = await BuildImage.build_text_image( + "群管理员帮助", + size=40, + ) + tip = await BuildImage.build_text_image( + "注: ‘*’ 代表可有多个相同参数 ‘?’ 代表可省略该参数", size=25, font_color="red" + ) + await A.paste(text, (50, 30)) + await A.paste(tip, (50, 90)) + await A.save(ADMIN_HELP_IMAGE) diff --git a/zhenxun/builtin_plugins/admin/admin_help/utils.py b/zhenxun/builtin_plugins/admin/admin_help/utils.py new file mode 100644 index 00000000..8efbf673 --- /dev/null +++ b/zhenxun/builtin_plugins/admin/admin_help/utils.py @@ -0,0 +1,22 @@ +import nonebot + +from zhenxun.utils.enum import PluginType +from zhenxun.utils.exception import EmptyError +from zhenxun.models.plugin_info import PluginInfo + +from .config import PluginData + + +async def get_plugins() -> list[PluginData]: + """获取插件数据""" + plugin_list = await PluginInfo.filter( + plugin_type__in=[PluginType.ADMIN, PluginType.SUPER_AND_ADMIN] + ).all() + data_list = [] + for plugin in plugin_list: + if _plugin := nonebot.get_plugin_by_module_name(plugin.module_path): + if _plugin.metadata: + data_list.append(PluginData(plugin=plugin, metadata=_plugin.metadata)) + if not data_list: + raise EmptyError() + return data_list diff --git a/zhenxun/builtin_plugins/help/detail_help.py b/zhenxun/builtin_plugins/help/detail_help.py new file mode 100644 index 00000000..e69de29b diff --git a/zhenxun/builtin_plugins/superuser/super_help.py b/zhenxun/builtin_plugins/superuser/super_help.py deleted file mode 100644 index 8d754d81..00000000 --- a/zhenxun/builtin_plugins/superuser/super_help.py +++ /dev/null @@ -1,158 +0,0 @@ -import nonebot -from nonebot.permission import SUPERUSER -from nonebot.plugin import PluginMetadata -from nonebot_plugin_session import EventSession -from nonebot_plugin_alconna.matcher import AlconnaMatcher -from nonebot_plugin_alconna import Alconna, Arparma, on_alconna - -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType -from zhenxun.models.task_info import TaskInfo -from zhenxun.utils.exception import EmptyError -from zhenxun.utils.message import MessageUtils -from zhenxun.configs.utils import PluginExtraData -from zhenxun.models.plugin_info import PluginInfo -from zhenxun.configs.path_config import IMAGE_PATH -from zhenxun.utils.image_utils import ( - BuildImage, - text2image, - group_image, - build_sort_image, -) - -__plugin_meta__ = PluginMetadata( - name="超级用户帮助", - description="超级用户帮助", - usage=""" - 超级用户帮助 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - plugin_type=PluginType.SUPERUSER, - ).dict(), -) - -_matcher = on_alconna( - Alconna("超级用户帮助"), - permission=SUPERUSER, - priority=5, - block=True, -) - - -SUPERUSER_HELP_IMAGE = IMAGE_PATH / "SUPERUSER_HELP.png" -if SUPERUSER_HELP_IMAGE.exists(): - SUPERUSER_HELP_IMAGE.unlink() - - -async def build_help() -> BuildImage: - """构造超级用户帮助图片 - - 异常: - EmptyError: 超级用户帮助为空 - - 返回: - BuildImage: 超级用户帮助图片 - """ - plugin_list = await PluginInfo.filter(plugin_type=PluginType.SUPERUSER).all() - data_list = [] - for plugin in plugin_list: - if _plugin := nonebot.get_plugin_by_module_name(plugin.module_path): - if _plugin.metadata: - data_list.append({"plugin": plugin, "metadata": _plugin.metadata}) - font = BuildImage.load_font("HYWenHei-85W.ttf", 20) - image_list = [] - for data in data_list: - plugin = data["plugin"] - metadata = data["metadata"] - try: - usage = None - description = None - if metadata.usage: - usage = await text2image( - metadata.usage, - padding=5, - color=(255, 255, 255), - font_color=(0, 0, 0), - ) - if metadata.description: - description = await text2image( - metadata.description, - padding=5, - color=(255, 255, 255), - font_color=(0, 0, 0), - ) - width = 0 - height = 100 - if usage: - width = usage.width - height += usage.height - if description and description.width > width: - width = description.width - height += description.height - font_width, font_height = BuildImage.get_text_size( - plugin.name + f"[{plugin.level}]", font - ) - if font_width > width: - width = font_width - A = BuildImage(width + 30, height + 120, "#EAEDF2") - await A.text((15, 10), plugin.name + f"[{plugin.level}]") - await A.text((15, 70), "简介:") - if not description: - description = BuildImage(A.width - 30, 30, (255, 255, 255)) - await description.circle_corner(10) - await A.paste(description, (15, 100)) - if not usage: - usage = BuildImage(A.width - 30, 30, (255, 255, 255)) - await usage.circle_corner(10) - await A.text((15, description.height + 115), "用法:") - await A.paste(usage, (15, description.height + 145)) - await A.circle_corner(10) - image_list.append(A) - except Exception as e: - logger.warning( - f"获取超级用户管理员插件 {plugin.module}: {plugin.name} 设置失败...", - "超级用户帮助", - e=e, - ) - if task_list := await TaskInfo.all(): - task_str = "\n".join([task.name for task in task_list]) - task_str = "通过 开启/关闭群被动 来控制群被动\n----------\n" + task_str - task_image = await text2image(task_str, padding=5, color=(255, 255, 255)) - await task_image.circle_corner(10) - A = BuildImage(task_image.width + 50, task_image.height + 85, "#EAEDF2") - await A.text((25, 10), "被动技能") - await A.paste(task_image, (25, 50)) - await A.circle_corner(10) - image_list.append(A) - if not image_list: - raise EmptyError() - image_group, _ = group_image(image_list) - A = await build_sort_image(image_group, color=(255, 255, 255), padding_top=160) - text = await BuildImage.build_text_image( - "超级用户帮助", - size=40, - ) - tip = await BuildImage.build_text_image( - "注: ‘*’ 代表可有多个相同参数 ‘?’ 代表可省略该参数", size=25, font_color="red" - ) - await A.paste(text, (50, 30)) - await A.paste(tip, (50, 90)) - await A.save(SUPERUSER_HELP_IMAGE) - return BuildImage(1, 1) - - -@_matcher.handle() -async def _( - session: EventSession, - matcher: AlconnaMatcher, - arparma: Arparma, -): - if not SUPERUSER_HELP_IMAGE.exists(): - try: - await build_help() - except EmptyError: - await MessageUtils.build_message("超级用户帮助为空").finish(reply_to=True) - await MessageUtils.build_message(SUPERUSER_HELP_IMAGE).send() - logger.info("查看超级用户帮助", arparma.header_result, session=session) diff --git a/zhenxun/builtin_plugins/superuser/super_help/__init__.py b/zhenxun/builtin_plugins/superuser/super_help/__init__.py new file mode 100644 index 00000000..d9b98097 --- /dev/null +++ b/zhenxun/builtin_plugins/superuser/super_help/__init__.py @@ -0,0 +1,59 @@ +from nonebot.permission import SUPERUSER +from nonebot.plugin import PluginMetadata +from nonebot_plugin_session import EventSession +from nonebot_plugin_alconna import Alconna, Arparma, on_alconna + +from zhenxun.services.log import logger +from zhenxun.configs.config import Config +from zhenxun.utils.enum import PluginType +from zhenxun.utils.exception import EmptyError +from zhenxun.utils.message import MessageUtils +from zhenxun.configs.utils import RegisterConfig, PluginExtraData + +from .normal_help import build_help +from .config import SUPERUSER_HELP_IMAGE +from .zhenxun_help import build_html_help + +__plugin_meta__ = PluginMetadata( + name="超级用户帮助", + description="超级用户帮助", + usage=""" + 超级用户帮助 + """.strip(), + extra=PluginExtraData( + author="HibiKier", + version="0.1", + plugin_type=PluginType.SUPERUSER, + configs=[ + RegisterConfig( + key="type", + value="zhenxun", + help="超级用户帮助样式,normal, zhenxun", + default_value="zhenxun", + ) + ], + ).dict(), +) + +_matcher = on_alconna( + Alconna("超级用户帮助"), + permission=SUPERUSER, + priority=5, + block=True, +) + + +@_matcher.handle() +async def _(session: EventSession, arparma: Arparma): + if not SUPERUSER_HELP_IMAGE.exists(): + try: + if Config.get_config("admin_help", "type") == "zhenxun": + await build_html_help() + else: + await build_help() + except EmptyError: + await MessageUtils.build_message("当前超级用户帮助为空...").finish( + reply_to=True + ) + await MessageUtils.build_message(SUPERUSER_HELP_IMAGE).send() + logger.info("查看超级用户帮助", arparma.header_result, session=session) diff --git a/zhenxun/builtin_plugins/superuser/super_help/config.py b/zhenxun/builtin_plugins/superuser/super_help/config.py new file mode 100644 index 00000000..55e32f51 --- /dev/null +++ b/zhenxun/builtin_plugins/superuser/super_help/config.py @@ -0,0 +1,23 @@ +from pydantic import BaseModel +from nonebot.plugin import PluginMetadata + +from zhenxun.models.plugin_info import PluginInfo +from zhenxun.configs.path_config import IMAGE_PATH + +SUPERUSER_HELP_IMAGE = IMAGE_PATH / "SUPERUSER_HELP.png" +if SUPERUSER_HELP_IMAGE.exists(): + SUPERUSER_HELP_IMAGE.unlink() + + +class PluginData(BaseModel): + """ + 插件信息 + """ + + plugin: PluginInfo + """插件信息""" + metadata: PluginMetadata + """元数据""" + + class Config: + arbitrary_types_allowed = True diff --git a/zhenxun/builtin_plugins/superuser/super_help/normal_help.py b/zhenxun/builtin_plugins/superuser/super_help/normal_help.py new file mode 100644 index 00000000..0fbdb774 --- /dev/null +++ b/zhenxun/builtin_plugins/superuser/super_help/normal_help.py @@ -0,0 +1,127 @@ +from PIL.ImageFont import FreeTypeFont +from nonebot.plugin import PluginMetadata + +from zhenxun.services.log import logger +from zhenxun.models.task_info import TaskInfo +from zhenxun.models.plugin_info import PluginInfo +from zhenxun.utils._build_image import BuildImage +from zhenxun.utils.image_utils import text2image, group_image, build_sort_image + +from .utils import get_plugins +from .config import SUPERUSER_HELP_IMAGE + + +async def build_usage_des_image( + metadata: PluginMetadata, +) -> tuple[BuildImage | None, BuildImage | None]: + """构建用法和描述图片 + + 参数: + metadata: PluginMetadata + + 返回: + tuple[BuildImage | None, BuildImage | None]: 用法和描述图片 + """ + usage = None + description = None + if metadata.usage: + usage = await text2image( + metadata.usage, + padding=5, + color=(255, 255, 255), + font_color=(0, 0, 0), + ) + if metadata.description: + description = await text2image( + metadata.description, + padding=5, + color=(255, 255, 255), + font_color=(0, 0, 0), + ) + return usage, description + + +async def build_image( + plugin: PluginInfo, metadata: PluginMetadata, font: FreeTypeFont +) -> BuildImage: + """构建帮助图片 + + 参数: + plugin: PluginInfo + metadata: PluginMetadata + font: FreeTypeFont + + 返回: + BuildImage: 帮助图片 + + """ + usage, description = await build_usage_des_image(metadata) + width = 0 + height = 100 + if usage: + width = usage.width + height += usage.height + if description and description.width > width: + width = description.width + height += description.height + font_width, _ = BuildImage.get_text_size(f"{plugin.name}[{plugin.level}]", font) + if font_width > width: + width = font_width + A = BuildImage(width + 30, height + 120, "#EAEDF2") + await A.text((15, 10), f"{plugin.name}[{plugin.level}]") + await A.text((15, 70), "简介:") + if not description: + description = BuildImage(A.width - 30, 30, (255, 255, 255)) + await description.circle_corner(10) + await A.paste(description, (15, 100)) + if not usage: + usage = BuildImage(A.width - 30, 30, (255, 255, 255)) + await usage.circle_corner(10) + await A.text((15, description.height + 115), "用法:") + await A.paste(usage, (15, description.height + 145)) + await A.circle_corner(10) + return A + + +async def build_help(): + """构造超级用户帮助图片 + + 返回: + BuildImage: 超级用户帮助图片 + """ + font = BuildImage.load_font("HYWenHei-85W.ttf", 20) + image_list = [] + for data in await get_plugins(): + plugin = data.plugin + metadata = data.metadata + try: + A = await build_image(plugin, metadata, font) + image_list.append(A) + except Exception as e: + logger.warning( + f"获取群超级用户插件 {plugin.module}: {plugin.name} 设置失败...", + "超级用户帮助", + e=e, + ) + if task_list := await TaskInfo.all(): + task_str = "\n".join([task.name for task in task_list]) + task_str = "通过 开启/关闭群被动 来控制群被动\n----------\n" + task_str + task_image = await text2image(task_str, padding=5, color=(255, 255, 255)) + await task_image.circle_corner(10) + A = BuildImage(task_image.width + 50, task_image.height + 85, "#EAEDF2") + await A.text((25, 10), "被动技能") + await A.paste(task_image, (25, 50)) + await A.circle_corner(10) + image_list.append(A) + image_group, _ = group_image(image_list) + A = await build_sort_image(image_group, color=(255, 255, 255), padding_top=160) + text = await BuildImage.build_text_image( + "群超级用户帮助", + size=40, + ) + tip = await BuildImage.build_text_image( + "注: ‘*’ 代表可有多个相同参数 ‘?’ 代表可省略该参数", size=25, font_color="red" + ) + await A.paste(text, (50, 30)) + await A.paste(tip, (50, 90)) + await A.save(SUPERUSER_HELP_IMAGE) diff --git a/zhenxun/builtin_plugins/superuser/super_help/utils.py b/zhenxun/builtin_plugins/superuser/super_help/utils.py new file mode 100644 index 00000000..43ea0e6f --- /dev/null +++ b/zhenxun/builtin_plugins/superuser/super_help/utils.py @@ -0,0 +1,22 @@ +import nonebot + +from zhenxun.utils.enum import PluginType +from zhenxun.utils.exception import EmptyError +from zhenxun.models.plugin_info import PluginInfo + +from .config import PluginData + + +async def get_plugins() -> list[PluginData]: + """获取插件数据""" + plugin_list = await PluginInfo.filter( + plugin_type__in=[PluginType.SUPERUSER, PluginType.SUPER_AND_ADMIN] + ).all() + data_list = [] + for plugin in plugin_list: + if _plugin := nonebot.get_plugin_by_module_name(plugin.module_path): + if _plugin.metadata: + data_list.append(PluginData(plugin=plugin, metadata=_plugin.metadata)) + if not data_list: + raise EmptyError() + return data_list diff --git a/zhenxun/builtin_plugins/superuser/super_help/zhenxun_help.py b/zhenxun/builtin_plugins/superuser/super_help/zhenxun_help.py new file mode 100644 index 00000000..cf389e87 --- /dev/null +++ b/zhenxun/builtin_plugins/superuser/super_help/zhenxun_help.py @@ -0,0 +1,59 @@ +from nonebot_plugin_htmlrender import template_to_pic + +from zhenxun.configs.config import BotConfig +from zhenxun.models.task_info import TaskInfo +from zhenxun.utils._build_image import BuildImage +from zhenxun.configs.path_config import TEMPLATE_PATH + +from .utils import get_plugins +from .config import SUPERUSER_HELP_IMAGE + + +async def get_task() -> dict[str, str] | None: + """获取被动技能帮助""" + if task_list := await TaskInfo.all(): + return { + "name": "被动技能", + "description": "控制群组中的被动技能状态", + "usage": "通过 开启/关闭群被动 来控制群被动
----------
" + + "
".join([task.name for task in task_list]), + } + return None + + +async def build_html_help(): + """构建帮助图片""" + plugins = await get_plugins() + plugin_list = [] + for data in plugins: + if data.metadata.extra: + if superuser_help := data.metadata.extra.get("superuser_help"): + data.metadata.usage += f"
以下为超级用户额外命令
{superuser_help}" + plugin_list.append( + { + "name": data.plugin.name, + "description": data.metadata.description.replace("\n", "
"), + "usage": data.metadata.usage.replace("\n", "
"), + } + ) + if task := await get_task(): + plugin_list.append(task) + plugin_list.sort(key=lambda p: len(p["description"]) + len(p["usage"])) + pic = await template_to_pic( + template_path=str((TEMPLATE_PATH / "help").absolute()), + template_name="main.html", + templates={ + "data": { + "plugin_list": plugin_list, + "nickname": BotConfig.self_nickname, + "help_name": "超级用户", + } + }, + pages={ + "viewport": {"width": 1024, "height": 1024}, + "base_url": f"file://{TEMPLATE_PATH}", + }, + wait=2, + ) + result = await BuildImage.open(pic).resize(0.5) + await result.save(SUPERUSER_HELP_IMAGE) diff --git a/zhenxun/configs/utils/__init__.py b/zhenxun/configs/utils/__init__.py index 82d7ebca..69d334a1 100644 --- a/zhenxun/configs/utils/__init__.py +++ b/zhenxun/configs/utils/__init__.py @@ -17,6 +17,32 @@ _yaml.indent = 2 _yaml.allow_unicode = True +class Example(BaseModel): + """ + 示例 + """ + + exec: str + """执行命令""" + description: str = "" + """命令描述""" + + +class Command(BaseModel): + """ + 具体参数说明 + """ + + command: str + """命令""" + params: list[str] = [] + """参数""" + description: str = "" + """描述""" + examples: list[Example] = [] + """示例列表""" + + class RegisterConfig(BaseModel): """ 注册配置项 @@ -167,6 +193,8 @@ class PluginExtraData(BaseModel): """插件基本配置""" limits: list[BaseBlock | PluginCdBlock | PluginCountBlock] | None = None """插件限制""" + commands: list[Command] = [] + """命令列表,用于说明帮助""" tasks: list[Task] | None = None """技能被动""" superuser_help: str | None = None diff --git a/zhenxun/services/plugin_init.py b/zhenxun/services/plugin_init.py index 0dbba539..7d6ac487 100644 --- a/zhenxun/services/plugin_init.py +++ b/zhenxun/services/plugin_init.py @@ -9,8 +9,6 @@ from zhenxun.services.log import logger driver = nonebot.get_driver() -PLUGINS_METHOD = [] - class PluginInit(ABC): """ diff --git a/zhenxun/utils/image_utils.py b/zhenxun/utils/image_utils.py index c193a565..eb245f48 100644 --- a/zhenxun/utils/image_utils.py +++ b/zhenxun/utils/image_utils.py @@ -1,23 +1,22 @@ import os -import random import re +import random from io import BytesIO from pathlib import Path -from typing import Awaitable, Callable +from collections.abc import Callable, Awaitable import cv2 import imagehash -from imagehash import ImageHash -from nonebot.utils import is_coroutine_callable from PIL import Image +from nonebot.utils import is_coroutine_callable -from zhenxun.configs.path_config import IMAGE_PATH, TEMP_PATH from zhenxun.services.log import logger from zhenxun.utils.http_utils import AsyncHttpx +from zhenxun.configs.path_config import TEMP_PATH, IMAGE_PATH from ._build_image import BuildImage, ColorAlias -from ._build_mat import BuildMat, MatType -from ._image_template import ImageTemplate, RowStyle +from ._build_mat import MatType, BuildMat # noqa: F401 +from ._image_template import RowStyle, ImageTemplate # noqa: F401 # TODO: text2image 长度错误 @@ -192,8 +191,9 @@ async def text2image( s.strip(), font, font_size, font_color ) ) + height = sum(img.height + 8 for img in image_list) + pw width += pw - height += ph + # height += ph A = BuildImage( width + left_padding, height + top_padding + 2, @@ -386,7 +386,7 @@ def get_img_hash(image_file: str | Path) -> str: with open(image_file, "rb") as fp: hash_value = imagehash.average_hash(Image.open(fp)) except Exception as e: - logger.warning(f"获取图片Hash出错", "禁言检测", e=e) + logger.warning("获取图片Hash出错", "禁言检测", e=e) return str(hash_value) @@ -407,7 +407,7 @@ async def get_download_image_hash(url: str, mark: str) -> str: img_hash = get_img_hash(TEMP_PATH / f"compare_download_{mark}_img.jpg") return str(img_hash) except Exception as e: - logger.warning(f"下载读取图片Hash出错", e=e) + logger.warning("下载读取图片Hash出错", e=e) return ""