mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-10-29 08:00:03 +08:00
版本更新 (#1666)
* ✨ 父级插件加载 * ✅ 添加测试:更新与添加插件 (#1594) * ✅ 测试更新与添加插件 * ✅ Sourcery建议 * 👷 添加pytest * 🎨 优化代码 * 🐛 bug修复 * 🐛修复添加插件返回403的问题 (#1595) * 完善测试方法 * vscode测试配置 * 重构插件安装过程 * 🎨 修改readme * Update README.md * 🐛 修改bug与版本锁定 * 🐛 修复超级用户对群组功能开关 * 🐛 修复插件商店检查插件更新问题 (#1597) * 🐛 修复插件商店检查插件更新问题 * 🐛 恶意命令检测问题 * 🐛 增加插件状态检查 (#1598) * ✅ 优化测试用例 * 🐛 更改插件更新与安装逻辑 * 🐛 修复更新群组成员信息 * 🎨 代码优化 * 🚀 更新Dockerfile (#1599) * 🎨 更新requirements * ➕ 添加依赖aiocache * ⚡ 添加github镜像 * ✨ 添加仓库目录多获取渠道 * 🐛 修复测试用例 * ✨ 添加API缓存 * 🎨 采取Sourcery建议 * 🐛 文件下载逻辑修改 * 🎨 优化代码 * 🐛 修复插件开关有时出现错误 * ✨ 重构自检ui * 🐛 自检html修正 * 修复签到逻辑bug,并使代码更灵活以适应签到好感度等级配置 (#1606) * 修复签到功能已知问题 * 修复签到功能已知问题 * 修改参数名称 * 修改uid判断 --------- Co-authored-by: HibiKier <45528451+HibiKier@users.noreply.github.com> * 🎨 代码结构优化 * 🐛 私聊时修改插件时删除私聊帮助 * 🐛 过滤父插件 * 🐛 修复自检在ARM上的问题 (#1607) * 🐛 修复自检在ARM上的问题 * ✅ 优化测试 * ✨ 支持mysql,psql,sqlite随机函数 * 🔧 VSCode配置修改 * 🔧 VSCode配置修改 * ✨ 添加金币排行 Co-Authored-By: HibiKier <45528451+HibiKier@users.noreply.github.com> * 📝 修改README Co-Authored-By: HibiKier <45528451+HibiKier@users.noreply.github.com> * 🔨 提取GitHub相关操作 (#1609) * 🔨 提取GitHub相关操作 * 🔨 重构API策略 * ✨ 签到/金币排行限制最大数量 (#1616) * ✨ 签到/金币排行限制最大数量 * 🐛 修复超级用户id获取问题 * 🐛 修复路径解压与挂载 (#1619) * 🐛 修复功能少时zhenxun帮助图片排序问题 (#1620) * 🐛 签到文本适应 (#1622) * 🐛 好感度排行提供默认值 (#1624) * 🎈 优先使用github api (#1625) * ✨ 重构帮助,限制普通用户查询管理插件 (#1626) * 🐛 修复群权限与插件等级匹配 (#1627) * ✨ 当管理员尝试ban真寻时将被反杀 (#1628) * ✨ 群组发言时间检测提供开关配置 (#1630) * 🐳 chore: 支持自动修改版本号 (#1629) * 🎈 perf(github_utils): 支持github url下载遍历 (#1632) * 🎈 perf(github_utils): 支持github url下载遍历 * 🐞 fix(http_utils): 修复一些下载问题 * 🦄 refactor(http_utils): 部分重构 * chore(version): Update version to v0.2.2-e6f17c4 --------- Co-authored-by: AkashiCoin <AkashiCoin@users.noreply.github.com> * 🧪 test(auto_update): 修复测试用例 (#1633) * 🐛 修复商店商品为空时报错 (#1634) * 🐛 修复群权限与插件等级匹配 (#1635) * ✨ message_build支持AtAll (#1639) * 🎈 perf: 使用commit号下载插件 (#1641) * 🎈 perf: 使用commit号下载插件 * chore(version): Update version to v0.2.2-f9c7360 --------- Co-authored-by: AkashiCoin <AkashiCoin@users.noreply.github.com> * 🐳 chore: 修改运行检查触发路径 (#1642) * 🐳 chore: 修改运行检查触发路径 * 🐳 chore: 添加tests目录 * ✨ 重构qq群事件处理 (#1643) * 🐛 签到名称自适应 (#1644) * 🎨 更新README (#1645) * 🐛 fix(http_utils): 流式下载Content-Length错误 (#1647) * 🐛 修复群组中帮助功能状态显示问题 (#1650) * 🐛 修复群欢迎消息设置 (#1651) * 🐛 修复webui下载后首次启动错误 (#1652) * 🐛 修复webui下载后首次启动错误 * chore(version): Update version to v0.2.2-4a8ef85 --------- Co-authored-by: HibiKier <HibiKier@users.noreply.github.com> * ✨ 移除默认图片文件夹:爬 (#1653) * ✨ 安装/移除插件提供插件安装/卸载方法用于插件初始化 (#1654) * ✨ 新增超级用户与管理员帮助模板 (#1655) * ✨ 新增个人信息命令 (#1657) * ✨ 修改个人信息菜单名称 (#1658) * ✨ 新增插件商店api (#1659) * ✨ 新增插件商店api * chore(version): Update version to v0.2.2-7e15f20 --------- Co-authored-by: HibiKier <HibiKier@users.noreply.github.com> * ✨ 将cd,block,count限制复原配置文件 (#1662) * 🎨 修改README (#1663) * 🎨 修改版本号 (#1664) * 🎨 修改requirements (#1665) --------- Co-authored-by: AkashiCoin <l1040186796@gmail.com> Co-authored-by: fanyinrumeng <42991257+fanyinrumeng@users.noreply.github.com> Co-authored-by: AkashiCoin <i@loli.vet> Co-authored-by: Elaga <1728903318@qq.com> Co-authored-by: AkashiCoin <AkashiCoin@users.noreply.github.com> Co-authored-by: HibiKier <HibiKier@users.noreply.github.com>
This commit is contained in:
co-authored by
AkashiCoin
HibiKier
AkashiCoin
fanyinrumeng
AkashiCoin
Elaga
parent
daa40ae8b8
commit
4e33bf3a50
@@ -1,13 +1,14 @@
|
||||
from pathlib import Path
|
||||
|
||||
from nonebot.plugin import PluginMetadata
|
||||
import aiofiles
|
||||
from nonebot.rule import to_me
|
||||
from nonebot_plugin_alconna import Alconna, Arparma, on_alconna
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot_plugin_session import EventSession
|
||||
from nonebot_plugin_alconna import Alconna, Arparma, on_alconna
|
||||
|
||||
from zhenxun.configs.utils import PluginExtraData
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.message import MessageUtils
|
||||
from zhenxun.configs.utils import PluginExtraData
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="关于",
|
||||
@@ -28,8 +29,9 @@ async def _(session: EventSession, arparma: Arparma):
|
||||
ver_file = Path() / "__version__"
|
||||
version = None
|
||||
if ver_file.exists():
|
||||
with open(ver_file, "r", encoding="utf8") as f:
|
||||
version = f.read().split(":")[-1].strip()
|
||||
async with aiofiles.open(ver_file, encoding="utf8") as f:
|
||||
if text := await f.read():
|
||||
version = text.split(":")[-1].strip()
|
||||
info = f"""
|
||||
『绪山真寻Bot』
|
||||
版本:{version}
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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": "通过 开启/关闭群被动 来控制群被<br>----------<br>"
|
||||
+ "<br>".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", "<br>"),
|
||||
"usage": data.metadata.usage.replace("\n", "<br>"),
|
||||
}
|
||||
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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -2,23 +2,23 @@ from arclet.alconna import Args
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot_plugin_session import EventSession
|
||||
from nonebot_plugin_alconna import (
|
||||
Alconna,
|
||||
Arparma,
|
||||
At,
|
||||
Match,
|
||||
Option,
|
||||
Alconna,
|
||||
Arparma,
|
||||
on_alconna,
|
||||
store_true,
|
||||
)
|
||||
from nonebot_plugin_session import EventSession
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import PluginType
|
||||
from zhenxun.utils.message import MessageUtils
|
||||
from zhenxun.utils.rules import admin_check
|
||||
from zhenxun.utils.message import MessageUtils
|
||||
from zhenxun.configs.config import Config, BotConfig
|
||||
from zhenxun.configs.utils import RegisterConfig, PluginExtraData
|
||||
|
||||
from ._data_source import BanManage
|
||||
|
||||
@@ -160,7 +160,9 @@ async def _(
|
||||
duration: Match[int],
|
||||
group_id: Match[str],
|
||||
):
|
||||
user_id = None
|
||||
user_id = ""
|
||||
if not session.id1:
|
||||
await MessageUtils.build_message("用户id为空...").finish(reply_to=True)
|
||||
if user.available:
|
||||
if isinstance(user.result, At):
|
||||
user_id = user.result.target
|
||||
@@ -169,14 +171,35 @@ async def _(
|
||||
await MessageUtils.build_message("权限不足捏...").finish(reply_to=True)
|
||||
user_id = user.result
|
||||
_duration = duration.result * 60 if duration.available else -1
|
||||
_duration_text = f"{duration.result} 分钟" if duration.available else " 到世界湮灭"
|
||||
if (gid := session.id3 or session.id2) and not group_id.available:
|
||||
if group_id.available:
|
||||
gid = group_id.result
|
||||
if (
|
||||
not user_id
|
||||
or user_id == bot.self_id
|
||||
and session.id1 not in bot.config.superusers
|
||||
):
|
||||
_duration = 0.5
|
||||
await MessageUtils.build_message("倒反天罡,小小管理速速退下!").send()
|
||||
await BanManage.ban(session.id1, gid, 30, session, True)
|
||||
_duration_text = "半 分钟"
|
||||
logger.info(
|
||||
f"尝试ban {BotConfig.self_nickname} 反被拿下",
|
||||
arparma.header_result,
|
||||
session=session,
|
||||
)
|
||||
await MessageUtils.build_message(
|
||||
[
|
||||
"对 ",
|
||||
At(flag="user", target=session.id1),
|
||||
" 狠狠惩戒了一番,一脚踢进了小黑屋!"
|
||||
f" 在里面乖乖呆 {_duration_text}吧!",
|
||||
]
|
||||
).finish(reply_to=True)
|
||||
await BanManage.ban(
|
||||
user_id, gid, _duration, session, session.id1 in bot.config.superusers
|
||||
)
|
||||
logger.info(
|
||||
f"管理员Ban",
|
||||
"管理员Ban",
|
||||
arparma.header_result,
|
||||
session=session,
|
||||
target=f"{gid}:{user_id}",
|
||||
@@ -184,20 +207,25 @@ async def _(
|
||||
await MessageUtils.build_message(
|
||||
[
|
||||
"对 ",
|
||||
At(flag="user", target=user_id) if isinstance(user.result, At) else user_id, # type: ignore
|
||||
f" 狠狠惩戒了一番,一脚踢进了小黑屋!",
|
||||
(
|
||||
At(flag="user", target=user_id)
|
||||
if isinstance(user.result, At)
|
||||
else user_id
|
||||
), # type: ignore
|
||||
" 狠狠惩戒了一番,一脚踢进了小黑屋!",
|
||||
f" 在里面乖乖呆 {_duration_text} 吧!",
|
||||
]
|
||||
).finish(reply_to=True)
|
||||
elif session.id1 in bot.config.superusers:
|
||||
_group_id = group_id.result if group_id.available else None
|
||||
await BanManage.ban(user_id, _group_id, _duration, session, True)
|
||||
logger.info(
|
||||
f"超级用户Ban",
|
||||
"超级用户Ban",
|
||||
arparma.header_result,
|
||||
session=session,
|
||||
target=f"{_group_id}:{user_id}",
|
||||
)
|
||||
at_msg = user_id if user_id else f"群组:{_group_id}"
|
||||
at_msg = user_id or f"群组:{_group_id}"
|
||||
await MessageUtils.build_message(
|
||||
f"对 {at_msg} 狠狠惩戒了一番,一脚踢进了小黑屋!"
|
||||
).finish(reply_to=True)
|
||||
@@ -211,7 +239,7 @@ async def _(
|
||||
user: Match[str | At],
|
||||
group_id: Match[str],
|
||||
):
|
||||
user_id = None
|
||||
user_id = ""
|
||||
if user.available:
|
||||
if isinstance(user.result, At):
|
||||
user_id = user.result.target
|
||||
@@ -228,7 +256,7 @@ async def _(
|
||||
user_id, gid, session, session.id1 in bot.config.superusers
|
||||
):
|
||||
logger.info(
|
||||
f"管理员UnBan",
|
||||
"管理员UnBan",
|
||||
arparma.header_result,
|
||||
session=session,
|
||||
target=f"{gid}:{user_id}",
|
||||
@@ -236,28 +264,32 @@ async def _(
|
||||
await MessageUtils.build_message(
|
||||
[
|
||||
"将 ",
|
||||
At(flag="user", target=user_id) if isinstance(user.result, At) else u_d, # type: ignore
|
||||
f" 从黑屋中拉了出来并急救了一下!",
|
||||
(
|
||||
At(flag="user", target=user_id)
|
||||
if isinstance(user.result, At)
|
||||
else u_d
|
||||
), # type: ignore
|
||||
" 从黑屋中拉了出来并急救了一下!",
|
||||
]
|
||||
).finish(reply_to=True)
|
||||
else:
|
||||
await MessageUtils.build_message(f"该用户不在黑名单中捏...").finish(
|
||||
await MessageUtils.build_message("该用户不在黑名单中捏...").finish(
|
||||
reply_to=True
|
||||
)
|
||||
elif session.id1 in bot.config.superusers:
|
||||
_group_id = group_id.result if group_id.available else None
|
||||
if await BanManage.unban(user_id, _group_id, session, True):
|
||||
logger.info(
|
||||
f"超级用户UnBan",
|
||||
"超级用户UnBan",
|
||||
arparma.header_result,
|
||||
session=session,
|
||||
target=f"{_group_id}:{user_id}",
|
||||
)
|
||||
at_msg = user_id if user_id else f"群组:{_group_id}"
|
||||
at_msg = user_id or f"群组:{_group_id}"
|
||||
await MessageUtils.build_message(
|
||||
f"对 {at_msg} 从黑屋中拉了出来并急救了一下!"
|
||||
).finish(reply_to=True)
|
||||
else:
|
||||
await MessageUtils.build_message(f"该用户不在黑名单中捏...").finish(
|
||||
await MessageUtils.build_message("该用户不在黑名单中捏...").finish(
|
||||
reply_to=True
|
||||
)
|
||||
|
||||
@@ -3,13 +3,12 @@ from typing import Literal
|
||||
|
||||
from nonebot_plugin_session import EventSession
|
||||
|
||||
from zhenxun.models.ban_console import BanConsole
|
||||
from zhenxun.models.level_user import LevelUser
|
||||
from zhenxun.models.ban_console import BanConsole
|
||||
from zhenxun.utils.image_utils import BuildImage, ImageTemplate
|
||||
|
||||
|
||||
class BanManage:
|
||||
|
||||
@classmethod
|
||||
async def build_ban_image(
|
||||
cls,
|
||||
|
||||
@@ -19,6 +19,10 @@ class MemberUpdateManage:
|
||||
|
||||
@classmethod
|
||||
async def update(cls, bot: Bot, group_id: str):
|
||||
if not group_id:
|
||||
return logger.warning(
|
||||
f"bot: {bot.self_id},group_id为空,无法更新群成员信息..."
|
||||
)
|
||||
if isinstance(bot, v11Bot):
|
||||
await cls.v11(bot, group_id)
|
||||
elif isinstance(bot, v12Bot):
|
||||
@@ -145,14 +149,18 @@ class MemberUpdateManage:
|
||||
if delete_list:
|
||||
await GroupInfoUser.filter(id__in=delete_list).delete()
|
||||
logger.debug(f"删除重复数据 Ids: {delete_list}", "更新群组成员信息")
|
||||
if delete_member_list := list(
|
||||
set(exist_member_list).difference(set(db_user_uid))
|
||||
):
|
||||
|
||||
if delete_member_list := [
|
||||
uid for uid in db_user_uid if uid not in exist_member_list
|
||||
]:
|
||||
await GroupInfoUser.filter(
|
||||
user_id__in=delete_member_list, group_id=group_id
|
||||
).delete()
|
||||
logger.info(
|
||||
"删除已退群用户", "更新群组成员信息", group_id=group_id, platform="qq"
|
||||
f"删除已退群用户 {len(delete_member_list)} 条",
|
||||
"更新群组成员信息",
|
||||
group_id=group_id,
|
||||
platform="qq",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot_plugin_alconna import AlconnaQuery, Arparma, Match, Query
|
||||
from nonebot_plugin_session import EventSession
|
||||
from nonebot_plugin_alconna import Match, Query, Arparma, AlconnaQuery
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import BlockType, PluginType
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.utils.message import MessageUtils
|
||||
from zhenxun.utils.enum import BlockType, PluginType
|
||||
from zhenxun.configs.utils import RegisterConfig, PluginExtraData
|
||||
|
||||
from ._data_source import PluginManage, build_plugin, build_task
|
||||
from .command import _group_status_matcher, _status_matcher
|
||||
from .command import _status_matcher, _group_status_matcher
|
||||
from ._data_source import PluginManage, build_task, build_plugin, delete_help_image
|
||||
|
||||
base_config = Config.get("plugin_switch")
|
||||
|
||||
@@ -40,36 +40,35 @@ __plugin_meta__ = PluginMetadata(
|
||||
version="0.1",
|
||||
plugin_type=PluginType.SUPER_AND_ADMIN,
|
||||
superuser_help="""
|
||||
超级管理员额外命令
|
||||
格式:
|
||||
插件列表
|
||||
开启/关闭[功能名称] ?[-t ["private", "p", "group", "g"](关闭类型)] ?[-g 群组Id]
|
||||
格式:
|
||||
插件列表
|
||||
开启/关闭[功能名称] ?[-t ["private", "p", "group", "g"](关闭类型)] ?[-g 群组Id]
|
||||
|
||||
开启/关闭插件df[功能名称]: 开启/关闭指定插件进群默认状态
|
||||
开启/关闭所有插件df: 开启/关闭所有插件进群默认状态
|
||||
开启/关闭所有插件:
|
||||
私聊中: 开启/关闭所有插件全局状态
|
||||
群组中: 开启/关闭当前群组所有插件状态
|
||||
开启/关闭插件df[功能名称]: 开启/关闭指定插件进群默认状态
|
||||
开启/关闭所有插件df: 开启/关闭所有插件进群默认状态
|
||||
开启/关闭所有插件:
|
||||
私聊中: 开启/关闭所有插件全局状态
|
||||
群组中: 开启/关闭当前群组所有插件状态
|
||||
|
||||
开启/关闭群被动[name] ?[-g [group_id]]
|
||||
私聊中: 开启/关闭全局指定的被动状态
|
||||
群组中: 开启/关闭当前群组指定的被动状态
|
||||
示例:
|
||||
关闭群被动早晚安
|
||||
关闭群被动早晚安 -g 12355555
|
||||
开启/关闭群被动[name] ?[-g [group_id]]
|
||||
私聊中: 开启/关闭全局指定的被动状态
|
||||
群组中: 开启/关闭当前群组指定的被动状态
|
||||
示例:
|
||||
关闭群被动早晚安
|
||||
关闭群被动早晚安 -g 12355555
|
||||
|
||||
开启/关闭所有群被动 ?[-g [group_id]]
|
||||
私聊中: 开启/关闭全局或指定群组被动状态
|
||||
示例:
|
||||
开启所有群被动: 开启全局所有被动
|
||||
开启所有群被动 -g 12345678: 开启群组12345678所有被动
|
||||
开启/关闭所有群被动 ?[-g [group_id]]
|
||||
私聊中: 开启/关闭全局或指定群组被动状态
|
||||
示例:
|
||||
开启所有群被动: 开启全局所有被动
|
||||
开启所有群被动 -g 12345678: 开启群组12345678所有被动
|
||||
|
||||
私聊下:
|
||||
示例:
|
||||
开启签到 : 全局开启签到
|
||||
关闭签到 : 全局关闭签到
|
||||
关闭签到 p : 全局私聊关闭签到
|
||||
关闭签到 -g 12345678 : 关闭群组12345678的签到功能(普通管理员无法开启)
|
||||
私聊下:
|
||||
示例:
|
||||
开启签到 : 全局开启签到
|
||||
关闭签到 : 全局关闭签到
|
||||
关闭签到 p : 全局私聊关闭签到
|
||||
关闭签到 -g 12345678 : 关闭群组12345678的签到功能(普通管理员无法开启)
|
||||
""",
|
||||
admin_level=base_config.get("CHANGE_GROUP_SWITCH_LEVEL", 2),
|
||||
configs=[
|
||||
@@ -94,7 +93,7 @@ async def _(
|
||||
if session.id1 in bot.config.superusers:
|
||||
image = await build_plugin()
|
||||
logger.info(
|
||||
f"查看功能列表",
|
||||
"查看功能列表",
|
||||
arparma.header_result,
|
||||
session=session,
|
||||
)
|
||||
@@ -117,43 +116,39 @@ async def _(
|
||||
if not all.result and not plugin_name.available:
|
||||
await MessageUtils.build_message("请输入功能名称").finish(reply_to=True)
|
||||
name = plugin_name.result
|
||||
gid = session.id3 or session.id2
|
||||
if gid:
|
||||
if gid := session.id3 or session.id2:
|
||||
"""修改当前群组的数据"""
|
||||
if task.result:
|
||||
if all.result:
|
||||
result = await PluginManage.unblock_group_all_task(gid)
|
||||
logger.info(f"开启所有群组被动", arparma.header_result, session=session)
|
||||
logger.info("开启所有群组被动", arparma.header_result, session=session)
|
||||
else:
|
||||
result = await PluginManage.unblock_group_task(name, gid)
|
||||
logger.info(
|
||||
f"开启群组被动 {name}", arparma.header_result, session=session
|
||||
)
|
||||
elif session.id1 in bot.config.superusers and default_status.result:
|
||||
"""单个插件的进群默认修改"""
|
||||
result = await PluginManage.set_default_status(name, True)
|
||||
logger.info(
|
||||
f"超级用户开启 {name} 功能进群默认开关",
|
||||
arparma.header_result,
|
||||
session=session,
|
||||
)
|
||||
elif all.result:
|
||||
"""所有插件"""
|
||||
result = await PluginManage.set_all_plugin_status(
|
||||
True, default_status.result, gid
|
||||
)
|
||||
logger.info(
|
||||
"开启群组中全部功能",
|
||||
arparma.header_result,
|
||||
session=session,
|
||||
)
|
||||
else:
|
||||
if session.id1 in bot.config.superusers and default_status.result:
|
||||
"""单个插件的进群默认修改"""
|
||||
result = await PluginManage.set_default_status(name, True)
|
||||
logger.info(
|
||||
f"超级用户开启 {name} 功能进群默认开关",
|
||||
arparma.header_result,
|
||||
session=session,
|
||||
)
|
||||
else:
|
||||
if all.result:
|
||||
"""所有插件"""
|
||||
result = await PluginManage.set_all_plugin_status(
|
||||
True, default_status.result, gid
|
||||
)
|
||||
logger.info(
|
||||
f"开启群组中全部功能",
|
||||
arparma.header_result,
|
||||
session=session,
|
||||
)
|
||||
else:
|
||||
result = await PluginManage.unblock_group_plugin(name, gid)
|
||||
logger.info(
|
||||
f"开启功能 {name}", arparma.header_result, session=session
|
||||
)
|
||||
result = await PluginManage.unblock_group_plugin(name, gid)
|
||||
logger.info(f"开启功能 {name}", arparma.header_result, session=session)
|
||||
delete_help_image(gid)
|
||||
await MessageUtils.build_message(result).finish(reply_to=True)
|
||||
elif session.id1 in bot.config.superusers:
|
||||
"""私聊"""
|
||||
@@ -170,7 +165,8 @@ async def _(
|
||||
True, default_status.result, group_id
|
||||
)
|
||||
logger.info(
|
||||
f"超级用户开启全部功能全局开关 {f'指定群组: {group_id}' if group_id else ''}",
|
||||
"超级用户开启全部功能全局开关"
|
||||
f" {f'指定群组: {group_id}' if group_id else ''}",
|
||||
arparma.header_result,
|
||||
session=session,
|
||||
)
|
||||
@@ -204,16 +200,16 @@ async def _(
|
||||
arparma.header_result,
|
||||
session=session,
|
||||
)
|
||||
await MessageUtils.build_message(result).finish(reply_to=True)
|
||||
else:
|
||||
result = await PluginManage.superuser_block(name, None, group_id)
|
||||
result = await PluginManage.superuser_unblock(name, None, group_id)
|
||||
logger.info(
|
||||
f"超级用户开启功能 {name}",
|
||||
arparma.header_result,
|
||||
session=session,
|
||||
target=group_id,
|
||||
)
|
||||
await MessageUtils.build_message(result).finish(reply_to=True)
|
||||
delete_help_image()
|
||||
await MessageUtils.build_message(result).finish(reply_to=True)
|
||||
|
||||
|
||||
@_status_matcher.assign("close")
|
||||
@@ -231,43 +227,35 @@ async def _(
|
||||
if not all.result and not plugin_name.available:
|
||||
await MessageUtils.build_message("请输入功能名称").finish(reply_to=True)
|
||||
name = plugin_name.result
|
||||
gid = session.id3 or session.id2
|
||||
if gid:
|
||||
if gid := session.id3 or session.id2:
|
||||
"""修改当前群组的数据"""
|
||||
if task.result:
|
||||
if all.result:
|
||||
result = await PluginManage.block_group_all_task(gid)
|
||||
logger.info(f"开启所有群组被动", arparma.header_result, session=session)
|
||||
logger.info("开启所有群组被动", arparma.header_result, session=session)
|
||||
else:
|
||||
result = await PluginManage.block_group_task(name, gid)
|
||||
logger.info(
|
||||
f"关闭群组被动 {name}", arparma.header_result, session=session
|
||||
)
|
||||
elif session.id1 in bot.config.superusers and default_status.result:
|
||||
"""单个插件的进群默认修改"""
|
||||
result = await PluginManage.set_default_status(name, False)
|
||||
logger.info(
|
||||
f"超级用户开启 {name} 功能进群默认开关",
|
||||
arparma.header_result,
|
||||
session=session,
|
||||
)
|
||||
elif all.result:
|
||||
"""所有插件"""
|
||||
result = await PluginManage.set_all_plugin_status(
|
||||
False, default_status.result, gid
|
||||
)
|
||||
logger.info("关闭群组中全部功能", arparma.header_result, session=session)
|
||||
else:
|
||||
if session.id1 in bot.config.superusers and default_status.result:
|
||||
"""单个插件的进群默认修改"""
|
||||
result = await PluginManage.set_default_status(name, False)
|
||||
logger.info(
|
||||
f"超级用户开启 {name} 功能进群默认开关",
|
||||
arparma.header_result,
|
||||
session=session,
|
||||
)
|
||||
else:
|
||||
if all.result:
|
||||
"""所有插件"""
|
||||
result = await PluginManage.set_all_plugin_status(
|
||||
False, default_status.result, gid
|
||||
)
|
||||
logger.info(
|
||||
f"关闭群组中全部功能",
|
||||
arparma.header_result,
|
||||
session=session,
|
||||
)
|
||||
else:
|
||||
result = await PluginManage.block_group_plugin(name, gid)
|
||||
logger.info(
|
||||
f"关闭功能 {name}", arparma.header_result, session=session
|
||||
)
|
||||
result = await PluginManage.block_group_plugin(name, gid)
|
||||
logger.info(f"关闭功能 {name}", arparma.header_result, session=session)
|
||||
delete_help_image(gid)
|
||||
await MessageUtils.build_message(result).finish(reply_to=True)
|
||||
elif session.id1 in bot.config.superusers:
|
||||
group_id = group.result if group.available else None
|
||||
@@ -283,7 +271,8 @@ async def _(
|
||||
False, default_status.result, group_id
|
||||
)
|
||||
logger.info(
|
||||
f"超级用户关闭全部功能全局开关 {f'指定群组: {group_id}' if group_id else ''}",
|
||||
"超级用户关闭全部功能全局开关"
|
||||
f" {f'指定群组: {group_id}' if group_id else ''}",
|
||||
arparma.header_result,
|
||||
session=session,
|
||||
)
|
||||
@@ -317,13 +306,13 @@ async def _(
|
||||
arparma.header_result,
|
||||
session=session,
|
||||
)
|
||||
await MessageUtils.build_message(result).finish(reply_to=True)
|
||||
else:
|
||||
_type = BlockType.ALL
|
||||
if block_type.available:
|
||||
if block_type.result in ["p", "private"]:
|
||||
if block_type.result in ["p", "private"]:
|
||||
if block_type.available:
|
||||
_type = BlockType.PRIVATE
|
||||
elif block_type.result in ["g", "group"]:
|
||||
elif block_type.result in ["g", "group"]:
|
||||
if block_type.available:
|
||||
_type = BlockType.GROUP
|
||||
result = await PluginManage.superuser_block(name, _type, group_id)
|
||||
logger.info(
|
||||
@@ -332,7 +321,8 @@ async def _(
|
||||
session=session,
|
||||
target=group_id,
|
||||
)
|
||||
await MessageUtils.build_message(result).finish(reply_to=True)
|
||||
delete_help_image()
|
||||
await MessageUtils.build_message(result).finish(reply_to=True)
|
||||
|
||||
|
||||
@_group_status_matcher.handle()
|
||||
@@ -362,11 +352,7 @@ async def _(
|
||||
):
|
||||
image = await build_task(session.id3 or session.id2)
|
||||
if image:
|
||||
logger.info(
|
||||
f"查看群被动列表",
|
||||
arparma.header_result,
|
||||
session=session,
|
||||
)
|
||||
logger.info("查看群被动列表", arparma.header_result, session=session)
|
||||
await MessageUtils.build_message(image).finish(reply_to=True)
|
||||
else:
|
||||
await MessageUtils.build_message("获取群被动任务失败...").finish(reply_to=True)
|
||||
|
||||
@@ -1,9 +1,27 @@
|
||||
from zhenxun.models.group_console import GroupConsole
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.models.task_info import TaskInfo
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.utils.enum import BlockType, PluginType
|
||||
from zhenxun.models.group_console import GroupConsole
|
||||
from zhenxun.utils.exception import GroupInfoNotFound
|
||||
from zhenxun.utils.image_utils import BuildImage, ImageTemplate, RowStyle
|
||||
from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH
|
||||
from zhenxun.utils.image_utils import RowStyle, BuildImage, ImageTemplate
|
||||
|
||||
HELP_FILE = IMAGE_PATH / "SIMPLE_HELP.png"
|
||||
|
||||
GROUP_HELP_PATH = DATA_PATH / "group_help"
|
||||
|
||||
|
||||
def delete_help_image(gid: str | None = None):
|
||||
"""删除帮助图片"""
|
||||
if gid:
|
||||
file = GROUP_HELP_PATH / f"{gid}.png"
|
||||
if file.exists():
|
||||
file.unlink()
|
||||
else:
|
||||
if HELP_FILE.exists():
|
||||
HELP_FILE.unlink()
|
||||
for file in GROUP_HELP_PATH.iterdir():
|
||||
file.unlink()
|
||||
|
||||
|
||||
def plugin_row_style(column: str, text: str) -> RowStyle:
|
||||
@@ -17,16 +35,16 @@ def plugin_row_style(column: str, text: str) -> RowStyle:
|
||||
RowStyle: RowStyle
|
||||
"""
|
||||
style = RowStyle()
|
||||
if column == "全局状态":
|
||||
if text == "开启":
|
||||
style.font_color = "#67C23A"
|
||||
else:
|
||||
style.font_color = "#F56C6C"
|
||||
if column == "加载状态":
|
||||
if text == "SUCCESS":
|
||||
style.font_color = "#67C23A"
|
||||
else:
|
||||
style.font_color = "#F56C6C"
|
||||
if (
|
||||
column == "全局状态"
|
||||
and text == "开启"
|
||||
or column != "全局状态"
|
||||
and column == "加载状态"
|
||||
and text == "SUCCESS"
|
||||
):
|
||||
style.font_color = "#67C23A"
|
||||
elif column in {"全局状态", "加载状态"}:
|
||||
style.font_color = "#F56C6C"
|
||||
return style
|
||||
|
||||
|
||||
@@ -44,22 +62,21 @@ async def build_plugin() -> BuildImage:
|
||||
"金币花费",
|
||||
]
|
||||
plugin_list = await PluginInfo.filter(plugin_type__not=PluginType.HIDDEN).all()
|
||||
column_data = []
|
||||
for plugin in plugin_list:
|
||||
column_data.append(
|
||||
[
|
||||
plugin.id,
|
||||
plugin.module,
|
||||
plugin.name,
|
||||
"开启" if plugin.status else "关闭",
|
||||
plugin.block_type,
|
||||
"SUCCESS" if plugin.load_status else "ERROR",
|
||||
plugin.menu_type,
|
||||
plugin.author,
|
||||
plugin.version,
|
||||
plugin.cost_gold,
|
||||
]
|
||||
)
|
||||
column_data = [
|
||||
[
|
||||
plugin.id,
|
||||
plugin.module,
|
||||
plugin.name,
|
||||
"开启" if plugin.status else "关闭",
|
||||
plugin.block_type,
|
||||
"SUCCESS" if plugin.load_status else "ERROR",
|
||||
plugin.menu_type,
|
||||
plugin.author,
|
||||
plugin.version,
|
||||
plugin.cost_gold,
|
||||
]
|
||||
for plugin in plugin_list
|
||||
]
|
||||
return await ImageTemplate.table_page(
|
||||
"Plugin",
|
||||
"插件状态",
|
||||
@@ -80,11 +97,8 @@ def task_row_style(column: str, text: str) -> RowStyle:
|
||||
RowStyle: RowStyle
|
||||
"""
|
||||
style = RowStyle()
|
||||
if column in ["群组状态", "全局状态"]:
|
||||
if text == "开启":
|
||||
style.font_color = "#67C23A"
|
||||
else:
|
||||
style.font_color = "#F56C6C"
|
||||
if column in {"群组状态", "全局状态"}:
|
||||
style.font_color = "#67C23A" if text == "开启" else "#F56C6C"
|
||||
return style
|
||||
|
||||
|
||||
@@ -144,7 +158,6 @@ async def build_task(group_id: str | None) -> BuildImage:
|
||||
|
||||
|
||||
class PluginManage:
|
||||
|
||||
@classmethod
|
||||
async def set_default_status(cls, plugin_name: str, status: bool) -> str:
|
||||
"""设置插件进群默认状态
|
||||
@@ -159,12 +172,15 @@ class PluginManage:
|
||||
if plugin_name.isdigit():
|
||||
plugin = await PluginInfo.get_or_none(id=int(plugin_name))
|
||||
else:
|
||||
plugin = await PluginInfo.get_or_none(name=plugin_name)
|
||||
plugin = await PluginInfo.get_or_none(
|
||||
name=plugin_name, load_status=True, plugin_type__not=PluginType.PARENT
|
||||
)
|
||||
if plugin:
|
||||
plugin.default_status = status
|
||||
await plugin.save(update_fields=["default_status"])
|
||||
return f'成功将 {plugin.name} 进群默认状态修改为: {"开启" if status else "关闭"}'
|
||||
return f"没有找到这个功能喔..."
|
||||
status_text = "开启" if status else "关闭"
|
||||
return f"成功将 {plugin.name} 进群默认状态修改为: {status_text}"
|
||||
return "没有找到这个功能喔..."
|
||||
|
||||
@classmethod
|
||||
async def set_all_plugin_status(
|
||||
@@ -206,7 +222,7 @@ class PluginManage:
|
||||
return f'成功将此群组所有功能状态修改为: {"开启" if status else "关闭"}'
|
||||
return "获取群组失败..."
|
||||
await PluginInfo.filter(plugin_type=PluginType.NORMAL).update(
|
||||
status=status, block_type=BlockType.ALL if not status else None
|
||||
status=status, block_type=None if status else BlockType.ALL
|
||||
)
|
||||
return f'成功将所有功能全局状态修改为: {"开启" if status else "关闭"}'
|
||||
|
||||
@@ -417,19 +433,18 @@ class PluginManage:
|
||||
group.block_task = group.block_task.replace(f"{module},", "")
|
||||
await group.save(update_fields=["block_task"])
|
||||
return f"已成功{status_str}全部被动技能!"
|
||||
else:
|
||||
if task := await TaskInfo.get_or_none(name=task_name):
|
||||
group, _ = await GroupConsole.get_or_create(
|
||||
group_id=group_id, channel_id__isnull=True
|
||||
)
|
||||
if status:
|
||||
group.block_task += f"{task.module},"
|
||||
else:
|
||||
if f"super:{task.module}," in group.block_task:
|
||||
return f"{status_str} {task_name} 被动技能失败,当前群组该被动已被管理员禁用"
|
||||
group.block_task = group.block_task.replace(f"{task.module},", "")
|
||||
await group.save(update_fields=["block_task"])
|
||||
return f"已成功{status_str} {task_name} 被动技能!"
|
||||
elif task := await TaskInfo.get_or_none(name=task_name):
|
||||
group, _ = await GroupConsole.get_or_create(
|
||||
group_id=group_id, channel_id__isnull=True
|
||||
)
|
||||
if status:
|
||||
group.block_task += f"{task.module},"
|
||||
elif f"super:{task.module}," in group.block_task:
|
||||
return f"{status_str} {task_name} 被动技能失败,当前群组该被动已被管理员禁用" # noqa: E501
|
||||
else:
|
||||
group.block_task = group.block_task.replace(f"{task.module},", "")
|
||||
await group.save(update_fields=["block_task"])
|
||||
return f"已成功{status_str} {task_name} 被动技能!"
|
||||
return "没有找到这个被动技能喔..."
|
||||
|
||||
@classmethod
|
||||
@@ -450,12 +465,14 @@ class PluginManage:
|
||||
if plugin_name.isdigit():
|
||||
plugin = await PluginInfo.get_or_none(id=int(plugin_name))
|
||||
else:
|
||||
plugin = await PluginInfo.get_or_none(name=plugin_name)
|
||||
status_str = "开启" if status else "关闭"
|
||||
plugin = await PluginInfo.get_or_none(
|
||||
name=plugin_name, load_status=True, plugin_type__not=PluginType.PARENT
|
||||
)
|
||||
if plugin:
|
||||
group, _ = await GroupConsole.get_or_create(
|
||||
group_id=group_id, channel_id__isnull=True
|
||||
)
|
||||
status_str = "开启" if status else "关闭"
|
||||
if status:
|
||||
if plugin.module in group.block_plugin:
|
||||
group.block_plugin = group.block_plugin.replace(
|
||||
@@ -463,11 +480,10 @@ class PluginManage:
|
||||
)
|
||||
await group.save(update_fields=["block_plugin"])
|
||||
return f"已成功{status_str} {plugin.name} 功能!"
|
||||
else:
|
||||
if plugin.module not in group.block_plugin:
|
||||
group.block_plugin += f"{plugin.module},"
|
||||
await group.save(update_fields=["block_plugin"])
|
||||
return f"已成功{status_str} {plugin.name} 功能!"
|
||||
elif plugin.module not in group.block_plugin:
|
||||
group.block_plugin += f"{plugin.module},"
|
||||
await group.save(update_fields=["block_plugin"])
|
||||
return f"已成功{status_str} {plugin.name} 功能!"
|
||||
return f"该功能已经{status_str}了喔,不要重复{status_str}..."
|
||||
return "没有找到这个功能喔..."
|
||||
|
||||
@@ -485,22 +501,20 @@ class PluginManage:
|
||||
返回:
|
||||
str: 返回信息
|
||||
"""
|
||||
if task := await TaskInfo.get_or_none(name=task_name):
|
||||
if not (task := await TaskInfo.get_or_none(name=task_name)):
|
||||
return "没有找到这个功能喔..."
|
||||
if group_id:
|
||||
group, _ = await GroupConsole.get_or_create(
|
||||
group_id=group_id, channel_id__isnull=True
|
||||
)
|
||||
if status:
|
||||
group.block_task = group.block_task.replace(f"super:{task.module},", "")
|
||||
else:
|
||||
group.block_task += f"super:{task.module},"
|
||||
await group.save(update_fields=["block_task"])
|
||||
status_str = "开启" if status else "关闭"
|
||||
if group_id:
|
||||
group, _ = await GroupConsole.get_or_create(
|
||||
group_id=group_id, channel_id__isnull=True
|
||||
)
|
||||
if status:
|
||||
group.block_task = group.block_task.replace(
|
||||
f"super:{task.module},", ""
|
||||
)
|
||||
else:
|
||||
group.block_task += f"super:{task.module},"
|
||||
await group.save(update_fields=["block_task"])
|
||||
return f"已成功将群组 {group_id} 被动技能 {task_name} {status_str}!"
|
||||
return "没有找到这个群组喔..."
|
||||
return "没有找到这个功能喔..."
|
||||
return f"已成功将群组 {group_id} 被动技能 {task_name} {status_str}!"
|
||||
return "没有找到这个群组喔..."
|
||||
|
||||
@classmethod
|
||||
async def superuser_block(
|
||||
@@ -519,7 +533,9 @@ class PluginManage:
|
||||
if plugin_name.isdigit():
|
||||
plugin = await PluginInfo.get_or_none(id=int(plugin_name))
|
||||
else:
|
||||
plugin = await PluginInfo.get_or_none(name=plugin_name)
|
||||
plugin = await PluginInfo.get_or_none(
|
||||
name=plugin_name, load_status=True, plugin_type__not=PluginType.PARENT
|
||||
)
|
||||
if plugin:
|
||||
if group_id:
|
||||
if group := await GroupConsole.get_or_none(
|
||||
@@ -538,11 +554,58 @@ class PluginManage:
|
||||
await plugin.save(update_fields=["status", "block_type"])
|
||||
if not block_type:
|
||||
return f"已成功将 {plugin.name} 全局启用!"
|
||||
else:
|
||||
if block_type == BlockType.ALL:
|
||||
return f"已成功将 {plugin.name} 全局关闭!"
|
||||
if block_type == BlockType.GROUP:
|
||||
return f"已成功将 {plugin.name} 全局群组关闭!"
|
||||
if block_type == BlockType.PRIVATE:
|
||||
return f"已成功将 {plugin.name} 全局私聊关闭!"
|
||||
if block_type == BlockType.ALL:
|
||||
return f"已成功将 {plugin.name} 全局关闭!"
|
||||
if block_type == BlockType.GROUP:
|
||||
return f"已成功将 {plugin.name} 全局群组关闭!"
|
||||
if block_type == BlockType.PRIVATE:
|
||||
return f"已成功将 {plugin.name} 全局私聊关闭!"
|
||||
return "没有找到这个功能喔..."
|
||||
|
||||
@classmethod
|
||||
async def superuser_unblock(
|
||||
cls, plugin_name: str, block_type: BlockType | None, group_id: str | None
|
||||
) -> str:
|
||||
"""超级用户开启插件
|
||||
|
||||
参数:
|
||||
plugin_name: 插件名称
|
||||
block_type: 禁用类型
|
||||
group_id: 群组id
|
||||
|
||||
返回:
|
||||
str: 返回信息
|
||||
"""
|
||||
if plugin_name.isdigit():
|
||||
plugin = await PluginInfo.get_or_none(id=int(plugin_name))
|
||||
else:
|
||||
plugin = await PluginInfo.get_or_none(
|
||||
name=plugin_name, load_status=True, plugin_type__not=PluginType.PARENT
|
||||
)
|
||||
if plugin:
|
||||
if group_id:
|
||||
if group := await GroupConsole.get_or_none(
|
||||
group_id=group_id, channel_id__isnull=True
|
||||
):
|
||||
if f"super:{plugin.module}," in group.block_plugin:
|
||||
group.block_plugin = group.block_plugin.replace(
|
||||
f"super:{plugin.module},", ""
|
||||
)
|
||||
await group.save(update_fields=["block_plugin"])
|
||||
return (
|
||||
f"已成功开启群组 {group.group_name} 的 {plugin_name} 功能!"
|
||||
)
|
||||
return "此群组该功能已被超级用户开启,不要重复开启..."
|
||||
return "群组信息未更新,请先更新群组信息..."
|
||||
plugin.block_type = block_type
|
||||
plugin.status = not bool(block_type)
|
||||
await plugin.save(update_fields=["status", "block_type"])
|
||||
if not block_type:
|
||||
return f"已成功将 {plugin.name} 全局启用!"
|
||||
if block_type == BlockType.ALL:
|
||||
return f"已成功将 {plugin.name} 全局开启!"
|
||||
if block_type == BlockType.GROUP:
|
||||
return f"已成功将 {plugin.name} 全局群组开启!"
|
||||
if block_type == BlockType.PRIVATE:
|
||||
return f"已成功将 {plugin.name} 全局私聊开启!"
|
||||
return "没有找到这个功能喔..."
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
import os
|
||||
import shutil
|
||||
from typing import Annotated, Dict
|
||||
from pathlib import Path
|
||||
from typing import Annotated
|
||||
|
||||
import ujson as json
|
||||
from nonebot import on_command
|
||||
from nonebot.params import Command
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot_plugin_alconna import Image
|
||||
from nonebot_plugin_alconna import Text as alcText
|
||||
from nonebot_plugin_alconna import UniMsg
|
||||
from nonebot_plugin_session import EventSession
|
||||
from nonebot_plugin_alconna import Text, Image, UniMsg
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.configs.path_config import DATA_PATH
|
||||
from zhenxun.configs.utils import PluginExtraData, RegisterConfig
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.utils.enum import PluginType
|
||||
from zhenxun.utils.http_utils import AsyncHttpx
|
||||
from zhenxun.configs.path_config import DATA_PATH
|
||||
from zhenxun.utils.rules import admin_check, ensure_group
|
||||
from zhenxun.configs.utils import RegisterConfig, PluginExtraData
|
||||
|
||||
base_config = Config.get("admin_bot_manage")
|
||||
|
||||
@@ -25,7 +23,8 @@ __plugin_meta__ = PluginMetadata(
|
||||
name="自定义群欢迎消息",
|
||||
description="自定义群欢迎消息",
|
||||
usage="""
|
||||
设置欢迎消息 欢迎新人!
|
||||
设置群欢迎消息,当消息中包含 -at 时会at入群用户
|
||||
设置欢迎消息 欢迎新人![图片]
|
||||
设置欢迎消息 欢迎你 -at
|
||||
""".strip(),
|
||||
extra=PluginExtraData(
|
||||
@@ -61,7 +60,7 @@ BASE_PATH.mkdir(parents=True, exist_ok=True)
|
||||
old_file = DATA_PATH / "custom_welcome_msg" / "custom_welcome_msg.json"
|
||||
if old_file.exists():
|
||||
try:
|
||||
old_data: Dict[str, str] = json.load(old_file.open(encoding="utf8"))
|
||||
old_data: dict[str, str] = json.load(old_file.open(encoding="utf8"))
|
||||
for group_id, message in old_data.items():
|
||||
file = BASE_PATH / "qq" / f"{group_id}" / "text.json"
|
||||
file.parent.mkdir(parents=True, exist_ok=True)
|
||||
@@ -74,15 +73,18 @@ if old_file.exists():
|
||||
logger.debug("群欢迎消息数据迁移", group_id=group_id)
|
||||
shutil.rmtree(old_file.parent.absolute())
|
||||
except Exception as e:
|
||||
pass
|
||||
logger.error("群欢迎消息数据迁移失败...", e=e)
|
||||
|
||||
|
||||
@_matcher.handle()
|
||||
async def _(
|
||||
session: EventSession,
|
||||
message: UniMsg,
|
||||
command: Annotated[tuple[str, ...], Command()],
|
||||
):
|
||||
def get_path(session: EventSession) -> Path:
|
||||
"""根据Session获取存储路径
|
||||
|
||||
参数:
|
||||
session: EventSession:
|
||||
|
||||
返回:
|
||||
Path: 存储路径
|
||||
"""
|
||||
path = BASE_PATH / f"{session.platform or session.bot_type}" / f"{session.id2}"
|
||||
if session.id3:
|
||||
path = (
|
||||
@@ -91,32 +93,53 @@ async def _(
|
||||
/ f"{session.id3}"
|
||||
/ f"{session.id2}"
|
||||
)
|
||||
file = path / "text.json"
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
for f in path.iterdir():
|
||||
f.unlink()
|
||||
return path
|
||||
|
||||
|
||||
async def save(path: Path, message: UniMsg) -> str:
|
||||
"""保存群欢迎消息
|
||||
|
||||
参数:
|
||||
path: 存储路径
|
||||
message: 消息内容
|
||||
|
||||
返回:
|
||||
str: 消息内容文本格式
|
||||
"""
|
||||
idx = 0
|
||||
text = ""
|
||||
for f in os.listdir(path):
|
||||
(path / f).unlink()
|
||||
message[0].text = message[0].text.replace(command[0], "").strip()
|
||||
file = path / "text.json"
|
||||
for msg in message:
|
||||
if isinstance(msg, alcText):
|
||||
if isinstance(msg, Text):
|
||||
text += msg.text
|
||||
elif isinstance(msg, Image):
|
||||
if msg.url:
|
||||
text += f"[image:{idx}]"
|
||||
await AsyncHttpx.download_file(msg.url, path / f"{idx}.png")
|
||||
idx += 1
|
||||
if await AsyncHttpx.download_file(msg.url, path / f"{idx}.png"):
|
||||
idx += 1
|
||||
else:
|
||||
logger.debug("图片 URL 为空...", command[0])
|
||||
if not file.exists():
|
||||
file.parent.mkdir(exist_ok=True, parents=True)
|
||||
is_at = "-at" in message
|
||||
text = text.replace("-at", "")
|
||||
logger.warning("图片 URL 为空...", "设置欢迎消息")
|
||||
json.dump(
|
||||
{"at": is_at, "message": text},
|
||||
file.open("w"),
|
||||
{"at": "-at" in text, "message": text.replace("-at", "", 1)},
|
||||
file.open("w", encoding="utf-8"),
|
||||
ensure_ascii=False,
|
||||
indent=4,
|
||||
)
|
||||
uni_msg = alcText("设置欢迎消息成功: \n") + message
|
||||
return text
|
||||
|
||||
|
||||
@_matcher.handle()
|
||||
async def _(
|
||||
session: EventSession,
|
||||
message: UniMsg,
|
||||
command: Annotated[tuple[str, ...], Command()],
|
||||
):
|
||||
path = get_path(session)
|
||||
message[0].text = message[0].text.replace(command[0], "").strip()
|
||||
text = await save(path, message)
|
||||
uni_msg = Text("设置欢迎消息成功: \n") + message
|
||||
await uni_msg.send()
|
||||
logger.info(f"设置群欢迎消息成功: {text}", command[0], session=session)
|
||||
|
||||
@@ -10,10 +10,10 @@ from nonebot.utils import run_sync
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.http_utils import AsyncHttpx
|
||||
from zhenxun.utils.platform import PlatformUtils
|
||||
from zhenxun.utils.github_utils import GithubUtils
|
||||
from zhenxun.utils.github_utils.models import RepoInfo
|
||||
|
||||
from .config import (
|
||||
DEV_URL,
|
||||
MAIN_URL,
|
||||
TMP_PATH,
|
||||
BASE_PATH,
|
||||
BACKUP_PATH,
|
||||
@@ -25,6 +25,7 @@ from .config import (
|
||||
BASE_PATH_STRING,
|
||||
DOWNLOAD_GZ_FILE,
|
||||
DOWNLOAD_ZIP_FILE,
|
||||
DEFAULT_GITHUB_URL,
|
||||
PYPROJECT_LOCK_FILE,
|
||||
REQ_TXT_FILE_STRING,
|
||||
PYPROJECT_FILE_STRING,
|
||||
@@ -117,14 +118,14 @@ def _file_handle(latest_version: str | None):
|
||||
shutil.move(src_folder_path, dest_folder_path)
|
||||
else:
|
||||
logger.debug(f"源文件夹不存在: {src_folder_path}", "检查更新")
|
||||
if tf:
|
||||
tf.close()
|
||||
if download_file.exists():
|
||||
logger.debug(f"删除下载文件: {download_file}", "检查更新")
|
||||
download_file.unlink()
|
||||
if extract_path.exists():
|
||||
logger.debug(f"删除解压文件夹: {extract_path}", "检查更新")
|
||||
shutil.rmtree(extract_path)
|
||||
if tf:
|
||||
tf.close()
|
||||
if TMP_PATH.exists():
|
||||
shutil.rmtree(TMP_PATH)
|
||||
if latest_version:
|
||||
@@ -169,23 +170,19 @@ class UpdateManage:
|
||||
cur_version = cls.__get_version()
|
||||
url = None
|
||||
new_version = None
|
||||
if version_type == "dev":
|
||||
url = DEV_URL
|
||||
new_version = await cls.__get_version_from_branch("dev")
|
||||
if new_version:
|
||||
new_version = new_version.split(":")[-1].strip()
|
||||
elif version_type == "main":
|
||||
url = MAIN_URL
|
||||
new_version = await cls.__get_version_from_branch("main")
|
||||
repo_info = GithubUtils.parse_github_url(DEFAULT_GITHUB_URL)
|
||||
if version_type in {"dev", "main"}:
|
||||
repo_info.branch = version_type
|
||||
new_version = await cls.__get_version_from_repo(repo_info)
|
||||
if new_version:
|
||||
new_version = new_version.split(":")[-1].strip()
|
||||
url = await repo_info.get_archive_download_urls()
|
||||
elif version_type == "release":
|
||||
data = await cls.__get_latest_data()
|
||||
if not data:
|
||||
return "获取更新版本失败..."
|
||||
url = data.get("tarball_url")
|
||||
new_version = data.get("name")
|
||||
url = (await AsyncHttpx.get(url)).headers.get("Location") # type: ignore
|
||||
new_version = data.get("name", "")
|
||||
url = await repo_info.get_release_source_download_urls_tgz(new_version)
|
||||
if not url:
|
||||
return "获取版本下载链接失败..."
|
||||
if TMP_PATH.exists():
|
||||
@@ -203,7 +200,7 @@ class UpdateManage:
|
||||
download_file = (
|
||||
DOWNLOAD_GZ_FILE if version_type == "release" else DOWNLOAD_ZIP_FILE
|
||||
)
|
||||
if await AsyncHttpx.download_file(url, download_file):
|
||||
if await AsyncHttpx.download_file(url, download_file, stream=True):
|
||||
logger.debug("下载真寻最新版文件完成...", "检查更新")
|
||||
await _file_handle(new_version)
|
||||
return (
|
||||
@@ -247,7 +244,7 @@ class UpdateManage:
|
||||
return {}
|
||||
|
||||
@classmethod
|
||||
async def __get_version_from_branch(cls, branch: str) -> str:
|
||||
async def __get_version_from_repo(cls, repo_info: RepoInfo) -> str:
|
||||
"""从指定分支获取版本号
|
||||
|
||||
参数:
|
||||
@@ -256,11 +253,11 @@ class UpdateManage:
|
||||
返回:
|
||||
str: 版本号
|
||||
"""
|
||||
version_url = f"https://raw.githubusercontent.com/HibiKier/zhenxun_bot/{branch}/__version__"
|
||||
version_url = await repo_info.get_raw_download_urls(path="__version__")
|
||||
try:
|
||||
res = await AsyncHttpx.get(version_url)
|
||||
if res.status_code == 200:
|
||||
return res.text.strip()
|
||||
except Exception as e:
|
||||
logger.error(f"获取 {branch} 分支版本失败", e=e)
|
||||
logger.error(f"获取 {repo_info.branch} 分支版本失败", e=e)
|
||||
return "未知版本"
|
||||
|
||||
@@ -2,8 +2,7 @@ from pathlib import Path
|
||||
|
||||
from zhenxun.configs.path_config import TEMP_PATH
|
||||
|
||||
DEV_URL = "https://ghproxy.cc/https://github.com/HibiKier/zhenxun_bot/archive/refs/heads/dev.zip"
|
||||
MAIN_URL = "https://ghproxy.cc/https://github.com/HibiKier/zhenxun_bot/archive/refs/heads/main.zip"
|
||||
DEFAULT_GITHUB_URL = "https://github.com/HibiKier/zhenxun_bot/tree/main"
|
||||
RELEASE_URL = "https://api.github.com/repos/HibiKier/zhenxun_bot/releases/latest"
|
||||
|
||||
VERSION_FILE_STRING = "__version__"
|
||||
@@ -23,8 +22,10 @@ TMP_PATH = TEMP_PATH / "auto_update"
|
||||
|
||||
BACKUP_PATH = Path() / "backup"
|
||||
|
||||
DOWNLOAD_GZ_FILE = TMP_PATH / "download_latest_file.tar.gz"
|
||||
DOWNLOAD_ZIP_FILE = TMP_PATH / "download_latest_file.zip"
|
||||
DOWNLOAD_GZ_FILE_STRING = "download_latest_file.tar.gz"
|
||||
DOWNLOAD_ZIP_FILE_STRING = "download_latest_file.zip"
|
||||
DOWNLOAD_GZ_FILE = TMP_PATH / DOWNLOAD_GZ_FILE_STRING
|
||||
DOWNLOAD_ZIP_FILE = TMP_PATH / DOWNLOAD_ZIP_FILE_STRING
|
||||
|
||||
REPLACE_FOLDERS = [
|
||||
"builtin_plugins",
|
||||
|
||||
@@ -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_htmlrender import template_to_pic
|
||||
from nonebot_plugin_session import EventSession
|
||||
|
||||
from zhenxun.configs.path_config import TEMPLATE_PATH
|
||||
from zhenxun.configs.utils import PluginExtraData
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import PluginType
|
||||
from zhenxun.utils.message import MessageUtils
|
||||
|
||||
from .data_source import get_status_info
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="服务器自我检查",
|
||||
description="查看服务器当前状态",
|
||||
usage="""
|
||||
查看服务器当前状态
|
||||
指令:
|
||||
自检
|
||||
""".strip(),
|
||||
extra=PluginExtraData(
|
||||
author="HibiKier", version="0.1", plugin_type=PluginType.SUPERUSER
|
||||
).dict(),
|
||||
)
|
||||
|
||||
|
||||
_matcher = on_alconna(
|
||||
Alconna("自检"), rule=to_me(), permission=SUPERUSER, block=True, priority=1
|
||||
)
|
||||
|
||||
|
||||
@_matcher.handle()
|
||||
async def _(session: EventSession, arparma: Arparma):
|
||||
try:
|
||||
data = await get_status_info()
|
||||
image = await template_to_pic(
|
||||
template_path=str((TEMPLATE_PATH / "check").absolute()),
|
||||
template_name="main.html",
|
||||
templates={"data": data},
|
||||
pages={
|
||||
"viewport": {"width": 195, "height": 750},
|
||||
"base_url": f"file://{TEMPLATE_PATH}",
|
||||
},
|
||||
wait=2,
|
||||
)
|
||||
await MessageUtils.build_message(image).send()
|
||||
logger.info("自检", arparma.header_result, session=session)
|
||||
except Exception as e:
|
||||
await MessageUtils.build_message(f"自检失败: {e}").send()
|
||||
logger.error("自检失败", arparma.header_result, session=session, e=e)
|
||||
@@ -0,0 +1,209 @@
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from dataclasses import dataclass
|
||||
|
||||
import psutil
|
||||
import cpuinfo
|
||||
import nonebot
|
||||
from pydantic import BaseModel
|
||||
from nonebot.utils import run_sync
|
||||
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.configs.config import BotConfig
|
||||
from zhenxun.utils.http_utils import AsyncHttpx
|
||||
|
||||
BAIDU_URL = "https://www.baidu.com/"
|
||||
GOOGLE_URL = "https://www.google.com/"
|
||||
|
||||
VERSION_FILE = Path() / "__version__"
|
||||
ARM_KEY = "aarch64"
|
||||
|
||||
|
||||
@dataclass
|
||||
class CPUInfo:
|
||||
core: int
|
||||
"""CPU 物理核心数"""
|
||||
usage: float
|
||||
"""CPU 占用百分比,取值范围(0,100]"""
|
||||
freq: float
|
||||
"""CPU 的时钟速度(单位:GHz)"""
|
||||
|
||||
@classmethod
|
||||
def get_cpu_info(cls):
|
||||
cpu_core = psutil.cpu_count(logical=False)
|
||||
cpu_usage = psutil.cpu_percent(interval=0.1)
|
||||
if _cpu_freq := psutil.cpu_freq():
|
||||
cpu_freq = round(_cpu_freq.current / 1000, 2)
|
||||
else:
|
||||
cpu_freq = 0
|
||||
return CPUInfo(core=cpu_core, usage=cpu_usage, freq=cpu_freq)
|
||||
|
||||
|
||||
@dataclass
|
||||
class RAMInfo:
|
||||
"""RAM 信息(单位:GB)"""
|
||||
|
||||
total: float
|
||||
"""RAM 总量"""
|
||||
usage: float
|
||||
"""当前 RAM 占用量/GB"""
|
||||
|
||||
@classmethod
|
||||
def get_ram_info(cls):
|
||||
ram_total = round(psutil.virtual_memory().total / (1024**3), 2)
|
||||
ram_usage = round(psutil.virtual_memory().used / (1024**3), 2)
|
||||
|
||||
return RAMInfo(total=ram_total, usage=ram_usage)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SwapMemory:
|
||||
"""Swap 信息(单位:GB)"""
|
||||
|
||||
total: float
|
||||
"""Swap 总量"""
|
||||
usage: float
|
||||
"""当前 Swap 占用量/GB"""
|
||||
|
||||
@classmethod
|
||||
def get_swap_info(cls):
|
||||
swap_total = round(psutil.swap_memory().total / (1024**3), 2)
|
||||
swap_usage = round(psutil.swap_memory().used / (1024**3), 2)
|
||||
|
||||
return SwapMemory(total=swap_total, usage=swap_usage)
|
||||
|
||||
|
||||
@dataclass
|
||||
class DiskInfo:
|
||||
"""硬盘信息"""
|
||||
|
||||
total: float
|
||||
"""硬盘总量"""
|
||||
usage: float
|
||||
"""当前硬盘占用量/GB"""
|
||||
|
||||
@classmethod
|
||||
def get_disk_info(cls):
|
||||
disk_total = round(psutil.disk_usage("/").total / (1024**3), 2)
|
||||
disk_usage = round(psutil.disk_usage("/").used / (1024**3), 2)
|
||||
|
||||
return DiskInfo(total=disk_total, usage=disk_usage)
|
||||
|
||||
|
||||
class SystemInfo(BaseModel):
|
||||
"""系统信息"""
|
||||
|
||||
cpu: CPUInfo
|
||||
"""CPU信息"""
|
||||
ram: RAMInfo
|
||||
"""RAM信息"""
|
||||
swap: SwapMemory
|
||||
"""SWAP信息"""
|
||||
disk: DiskInfo
|
||||
"""DISK信息"""
|
||||
|
||||
def get_system_info(self):
|
||||
return {
|
||||
"cpu_info": f"{self.cpu.usage}% - {self.cpu.freq}Ghz "
|
||||
f"[{self.cpu.core} core]",
|
||||
"cpu_process": self.cpu.usage,
|
||||
"ram_info": f"{self.ram.usage} / {self.ram.total} GB",
|
||||
"ram_process": (
|
||||
0 if self.ram.total == 0 else (self.ram.usage / self.ram.total * 100)
|
||||
),
|
||||
"swap_info": f"{self.swap.usage} / {self.swap.total} GB",
|
||||
"swap_process": (
|
||||
0 if self.swap.total == 0 else (self.swap.usage / self.swap.total * 100)
|
||||
),
|
||||
"disk_info": f"{self.disk.usage} / {self.disk.total} GB",
|
||||
"disk_process": (
|
||||
0 if self.disk.total == 0 else (self.disk.usage / self.disk.total * 100)
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@run_sync
|
||||
def __build_status() -> SystemInfo:
|
||||
"""获取 `CPU` `RAM` `SWAP` `DISK` 信息"""
|
||||
cpu = CPUInfo.get_cpu_info()
|
||||
ram = RAMInfo.get_ram_info()
|
||||
swap = SwapMemory.get_swap_info()
|
||||
disk = DiskInfo.get_disk_info()
|
||||
|
||||
return SystemInfo(cpu=cpu, ram=ram, swap=swap, disk=disk)
|
||||
|
||||
|
||||
async def __get_network_info():
|
||||
"""网络请求"""
|
||||
baidu, google = True, True
|
||||
try:
|
||||
await AsyncHttpx.get(BAIDU_URL, timeout=5)
|
||||
except Exception as e:
|
||||
logger.warning("自检:百度无法访问...", e=e)
|
||||
baidu = False
|
||||
try:
|
||||
await AsyncHttpx.get(GOOGLE_URL, timeout=5)
|
||||
except Exception as e:
|
||||
logger.warning("自检:谷歌无法访问...", e=e)
|
||||
google = False
|
||||
return baidu, google
|
||||
|
||||
|
||||
def __get_version() -> str | None:
|
||||
"""获取版本信息"""
|
||||
if VERSION_FILE.exists():
|
||||
with open(VERSION_FILE, encoding="utf-8") as f:
|
||||
if text := f.read():
|
||||
return text.split(":")[-1]
|
||||
return None
|
||||
|
||||
|
||||
def __get_arm_cpu():
|
||||
env = os.environ.copy()
|
||||
env["LC_ALL"] = "en_US.UTF-8"
|
||||
cpu_info = subprocess.check_output(["lscpu"], env=env).decode()
|
||||
model_name = ""
|
||||
cpu_freq = 0
|
||||
for line in cpu_info.splitlines():
|
||||
if "Model name" in line:
|
||||
model_name = line.split(":")[1].strip()
|
||||
if "CPU MHz" in line:
|
||||
cpu_freq = float(line.split(":")[1].strip())
|
||||
return model_name, cpu_freq
|
||||
|
||||
|
||||
def __get_arm_oracle_cpu_freq():
|
||||
cpu_freq = subprocess.check_output(
|
||||
["dmidecode", "-s", "processor-frequency"]
|
||||
).decode()
|
||||
return round(float(cpu_freq.split()[0]) / 1000, 2)
|
||||
|
||||
|
||||
async def get_status_info() -> dict:
|
||||
"""获取信息"""
|
||||
data = await __build_status()
|
||||
|
||||
system = platform.uname()
|
||||
if system.machine == ARM_KEY and not (
|
||||
cpuinfo.get_cpu_info().get("brand_raw") and data.cpu.freq
|
||||
):
|
||||
model_name, cpu_freq = __get_arm_cpu()
|
||||
if not data.cpu.freq:
|
||||
data.cpu.freq = cpu_freq or __get_arm_oracle_cpu_freq()
|
||||
data = data.get_system_info()
|
||||
data["brand_raw"] = model_name
|
||||
else:
|
||||
data = data.get_system_info()
|
||||
data["brand_raw"] = cpuinfo.get_cpu_info().get("brand_raw", "Unknown")
|
||||
|
||||
baidu, google = await __get_network_info()
|
||||
data["baidu"] = "#8CC265" if baidu else "red"
|
||||
data["google"] = "#8CC265" if google else "red"
|
||||
|
||||
data["system"] = f"{system.system} {system.release}"
|
||||
data["version"] = __get_version()
|
||||
data["plugin_count"] = len(nonebot.get_loaded_plugins())
|
||||
data["nickname"] = BotConfig.self_nickname
|
||||
return data
|
||||
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -1,37 +1,83 @@
|
||||
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), load_status=True)
|
||||
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 = await PluginInfo.get_or_none(
|
||||
name__iexact=name, load_status=True, plugin_type__in=type_list
|
||||
)
|
||||
if plugin:
|
||||
_plugin = nonebot.get_plugin_by_module_name(plugin.module_path)
|
||||
if _plugin and _plugin.metadata:
|
||||
|
||||
@@ -1,332 +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 = None
|
||||
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
|
||||
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": 975},
|
||||
"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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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
|
||||
@@ -0,0 +1,142 @@
|
||||
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:
|
||||
plugin.name = f"{plugin.name}(不可用)"
|
||||
elif not group and plugin.block_type == BlockType.PRIVATE:
|
||||
plugin.name = f"{plugin.name}(不可用)"
|
||||
elif group and f"{plugin.module}," in group.block_plugin:
|
||||
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,
|
||||
)
|
||||
@@ -2,29 +2,30 @@ import os
|
||||
import random
|
||||
|
||||
from nonebot import on_message
|
||||
from nonebot.rule import to_me
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot.rule import to_me
|
||||
from nonebot_plugin_alconna import UniMsg
|
||||
from nonebot_plugin_session import EventSession
|
||||
|
||||
from zhenxun.configs.path_config import IMAGE_PATH
|
||||
from zhenxun.configs.utils import PluginExtraData
|
||||
from zhenxun.models.ban_console import BanConsole
|
||||
from zhenxun.models.group_console import GroupConsole
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import PluginType
|
||||
from zhenxun.utils.message import MessageUtils
|
||||
from zhenxun.configs.utils import PluginExtraData
|
||||
from zhenxun.models.ban_console import BanConsole
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.configs.path_config import IMAGE_PATH
|
||||
from zhenxun.models.group_console import GroupConsole
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="功能名称当命令检测",
|
||||
name="笨蛋检测",
|
||||
description="功能名称当命令检测",
|
||||
usage=f"""被动""".strip(),
|
||||
usage="""被动""".strip(),
|
||||
extra=PluginExtraData(
|
||||
author="HibiKier",
|
||||
version="0.1",
|
||||
plugin_type=PluginType.DEPENDANT,
|
||||
menu_type="其他",
|
||||
).dict(),
|
||||
)
|
||||
|
||||
@@ -57,10 +58,12 @@ async def _(matcher: Matcher, message: UniMsg, session: EventSession):
|
||||
if image:
|
||||
message_list.append(image)
|
||||
message_list.append(
|
||||
f"桀桀桀,预判到会有 '笨蛋' 把功能名称当命令用,特地前来嘲笑!但还是好心来帮帮你啦!\n请at我发送 '帮助{plugin.name}' 或者 '帮助{plugin.id}' 来获取该功能帮助!"
|
||||
"桀桀桀,预判到会有 '笨蛋' 把功能名称当命令用,特地前来嘲笑!"
|
||||
f"但还是好心来帮帮你啦!\n请at我发送 '帮助{plugin.name}' 或者"
|
||||
f" '帮助{plugin.id}' 来获取该功能帮助!"
|
||||
)
|
||||
logger.info(
|
||||
f"检测到功能名称当命令使用,已发送帮助信息", "功能帮助", session=session
|
||||
"检测到功能名称当命令使用,已发送帮助信息", "功能帮助", session=session
|
||||
)
|
||||
await MessageUtils.build_message(message_list).send(reply_to=True)
|
||||
matcher.stop_propagation()
|
||||
|
||||
@@ -1,35 +1,34 @@
|
||||
from nonebot.adapters import Bot, Event
|
||||
from nonebot.adapters.onebot.v11 import PokeNotifyEvent
|
||||
from nonebot.exception import IgnoredException
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot_plugin_alconna import At, UniMsg
|
||||
from nonebot_plugin_session import EventSession
|
||||
from pydantic import BaseModel
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.adapters import Bot, Event
|
||||
from nonebot_plugin_alconna import At, UniMsg
|
||||
from nonebot.exception import IgnoredException
|
||||
from tortoise.exceptions import IntegrityError
|
||||
from nonebot_plugin_session import EventSession
|
||||
from nonebot.adapters.onebot.v11 import PokeNotifyEvent
|
||||
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.models.group_console import GroupConsole
|
||||
from zhenxun.utils.message import MessageUtils
|
||||
from zhenxun.models.level_user import LevelUser
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.models.plugin_limit import PluginLimit
|
||||
from zhenxun.models.user_console import UserConsole
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.exception import InsufficientGold
|
||||
from zhenxun.models.group_console import GroupConsole
|
||||
from zhenxun.utils.utils import FreqLimiter, CountLimiter, UserBlockLimiter
|
||||
from zhenxun.utils.enum import (
|
||||
BlockType,
|
||||
GoldHandle,
|
||||
PluginType,
|
||||
LimitWatchType,
|
||||
PluginLimitType,
|
||||
PluginType,
|
||||
)
|
||||
from zhenxun.utils.exception import InsufficientGold
|
||||
from zhenxun.utils.message import MessageUtils
|
||||
from zhenxun.utils.utils import CountLimiter, FreqLimiter, UserBlockLimiter
|
||||
|
||||
base_config = Config.get("hook")
|
||||
|
||||
|
||||
class Limit(BaseModel):
|
||||
|
||||
limit: PluginLimit
|
||||
limiter: FreqLimiter | UserBlockLimiter | CountLimiter
|
||||
|
||||
@@ -38,12 +37,11 @@ class Limit(BaseModel):
|
||||
|
||||
|
||||
class LimitManage:
|
||||
add_module = [] # noqa: RUF012
|
||||
|
||||
add_module = []
|
||||
|
||||
cd_limit: dict[str, Limit] = {}
|
||||
block_limit: dict[str, Limit] = {}
|
||||
count_limit: dict[str, Limit] = {}
|
||||
cd_limit: dict[str, Limit] = {} # noqa: RUF012
|
||||
block_limit: dict[str, Limit] = {} # noqa: RUF012
|
||||
count_limit: dict[str, Limit] = {} # noqa: RUF012
|
||||
|
||||
@classmethod
|
||||
def add_limit(cls, limit: PluginLimit):
|
||||
@@ -90,7 +88,7 @@ class LimitManage:
|
||||
@classmethod
|
||||
async def check(
|
||||
cls,
|
||||
module: str,
|
||||
module_path: str,
|
||||
user_id: str,
|
||||
group_id: str | None,
|
||||
channel_id: str | None,
|
||||
@@ -108,17 +106,17 @@ class LimitManage:
|
||||
异常:
|
||||
IgnoredException: IgnoredException
|
||||
"""
|
||||
if limit_model := cls.cd_limit.get(module):
|
||||
if limit_model := cls.cd_limit.get(module_path):
|
||||
await cls.__check(limit_model, user_id, group_id, channel_id, session)
|
||||
if limit_model := cls.block_limit.get(module):
|
||||
if limit_model := cls.block_limit.get(module_path):
|
||||
await cls.__check(limit_model, user_id, group_id, channel_id, session)
|
||||
if limit_model := cls.count_limit.get(module):
|
||||
if limit_model := cls.count_limit.get(module_path):
|
||||
await cls.__check(limit_model, user_id, group_id, channel_id, session)
|
||||
|
||||
@classmethod
|
||||
async def __check(
|
||||
cls,
|
||||
limit_model: Limit,
|
||||
limit_model: Limit | None,
|
||||
user_id: str,
|
||||
group_id: str | None,
|
||||
channel_id: str | None,
|
||||
@@ -136,33 +134,34 @@ class LimitManage:
|
||||
异常:
|
||||
IgnoredException: IgnoredException
|
||||
"""
|
||||
if limit_model:
|
||||
limit = limit_model.limit
|
||||
limiter = limit_model.limiter
|
||||
is_limit = (
|
||||
LimitWatchType.ALL
|
||||
or (group_id and limit.watch_type == LimitWatchType.GROUP)
|
||||
or (not group_id and limit.watch_type == LimitWatchType.USER)
|
||||
if not limit_model:
|
||||
return
|
||||
limit = limit_model.limit
|
||||
limiter = limit_model.limiter
|
||||
is_limit = (
|
||||
LimitWatchType.ALL
|
||||
or (group_id and limit.watch_type == LimitWatchType.GROUP)
|
||||
or (not group_id and limit.watch_type == LimitWatchType.USER)
|
||||
)
|
||||
key_type = user_id
|
||||
if group_id and limit.watch_type == LimitWatchType.GROUP:
|
||||
key_type = channel_id or group_id
|
||||
if is_limit and not limiter.check(key_type):
|
||||
if limit.result:
|
||||
await MessageUtils.build_message(limit.result).send()
|
||||
logger.debug(
|
||||
f"{limit.module}({limit.limit_type}) 正在限制中...",
|
||||
"HOOK",
|
||||
session=session,
|
||||
)
|
||||
key_type = user_id
|
||||
if group_id and limit.watch_type == LimitWatchType.GROUP:
|
||||
key_type = channel_id or group_id
|
||||
if is_limit and not limiter.check(key_type):
|
||||
if limit.result:
|
||||
await MessageUtils.build_message(limit.result).send()
|
||||
logger.debug(
|
||||
f"{limit.module}({limit.limit_type}) 正在限制中...",
|
||||
"HOOK",
|
||||
session=session,
|
||||
)
|
||||
raise IgnoredException(f"{limit.module} 正在限制中...")
|
||||
else:
|
||||
if isinstance(limiter, FreqLimiter):
|
||||
limiter.start_cd(key_type)
|
||||
if isinstance(limiter, UserBlockLimiter):
|
||||
limiter.set_true(key_type)
|
||||
if isinstance(limiter, CountLimiter):
|
||||
limiter.increase(key_type)
|
||||
raise IgnoredException(f"{limit.module} 正在限制中...")
|
||||
else:
|
||||
if isinstance(limiter, FreqLimiter):
|
||||
limiter.start_cd(key_type)
|
||||
if isinstance(limiter, UserBlockLimiter):
|
||||
limiter.set_true(key_type)
|
||||
if isinstance(limiter, CountLimiter):
|
||||
limiter.increase(key_type)
|
||||
|
||||
|
||||
class IsSuperuserException(Exception):
|
||||
@@ -196,11 +195,7 @@ class AuthChecker:
|
||||
return False
|
||||
if plugin.plugin_type == PluginType.DEPENDANT:
|
||||
return False
|
||||
if not self._flmt_s.check(sid):
|
||||
return False
|
||||
if plugin.module == "ai":
|
||||
return False
|
||||
return True
|
||||
return plugin.module != "ai" if self._flmt_s.check(sid) else False
|
||||
|
||||
async def auth(
|
||||
self,
|
||||
@@ -255,7 +250,7 @@ class AuthChecker:
|
||||
await self.auth_limit(plugin, session)
|
||||
except IsSuperuserException:
|
||||
logger.debug(
|
||||
f"超级用户或被ban跳过权限检测...", "HOOK", session=session
|
||||
"超级用户或被ban跳过权限检测...", "HOOK", session=session
|
||||
)
|
||||
except IgnoredException:
|
||||
is_ignore = True
|
||||
@@ -296,12 +291,14 @@ class AuthChecker:
|
||||
if not group_id:
|
||||
group_id = channel_id
|
||||
channel_id = None
|
||||
limit_list: list[PluginLimit] = await plugin.plugin_limit.all() # type: ignore
|
||||
limit_list: list[PluginLimit] = await plugin.plugin_limit.filter(
|
||||
status=True
|
||||
).all() # type: ignore
|
||||
for limit in limit_list:
|
||||
LimitManage.add_limit(limit)
|
||||
if user_id:
|
||||
await LimitManage.check(
|
||||
plugin.module, user_id, group_id, channel_id, session
|
||||
plugin.module_path, user_id, group_id, channel_id, session
|
||||
)
|
||||
|
||||
async def auth_plugin(
|
||||
@@ -313,14 +310,13 @@ class AuthChecker:
|
||||
plugin: PluginInfo
|
||||
session: EventSession
|
||||
"""
|
||||
user_id = session.id1
|
||||
group_id = session.id3
|
||||
channel_id = session.id2
|
||||
is_poke = isinstance(event, PokeNotifyEvent)
|
||||
if not group_id:
|
||||
group_id = channel_id
|
||||
channel_id = None
|
||||
if user_id:
|
||||
if user_id := session.id1:
|
||||
is_poke = isinstance(event, PokeNotifyEvent)
|
||||
if group_id:
|
||||
sid = group_id or user_id
|
||||
if await GroupConsole.is_super_block_plugin(
|
||||
@@ -393,9 +389,8 @@ class AuthChecker:
|
||||
raise IgnoredException("该插件在私聊中已被禁用...")
|
||||
if not plugin.status and plugin.block_type == BlockType.ALL:
|
||||
"""全局状态"""
|
||||
if group_id:
|
||||
if await GroupConsole.is_super_group(group_id):
|
||||
raise IsSuperuserException()
|
||||
if group_id and await GroupConsole.is_super_group(group_id):
|
||||
raise IsSuperuserException()
|
||||
logger.debug(
|
||||
f"{plugin.name}({plugin.module}) 全局未开启此功能...",
|
||||
"HOOK",
|
||||
@@ -414,9 +409,8 @@ class AuthChecker:
|
||||
session: EventSession
|
||||
"""
|
||||
user_id = session.id1
|
||||
group_id = session.id3 or session.id2
|
||||
if user_id and plugin.admin_level:
|
||||
if group_id:
|
||||
if group_id := session.id3 or session.id2:
|
||||
if not await LevelUser.check_level(
|
||||
user_id, group_id, plugin.admin_level
|
||||
):
|
||||
@@ -426,7 +420,8 @@ class AuthChecker:
|
||||
await MessageUtils.build_message(
|
||||
[
|
||||
At(flag="user", target=user_id),
|
||||
f"你的权限不足喔,该功能需要的权限等级: {plugin.admin_level}",
|
||||
f"你的权限不足喔,"
|
||||
f"该功能需要的权限等级: {plugin.admin_level}",
|
||||
]
|
||||
).send(reply_to=True)
|
||||
except Exception as e:
|
||||
@@ -439,22 +434,21 @@ class AuthChecker:
|
||||
session=session,
|
||||
)
|
||||
raise IgnoredException("管理员权限不足...")
|
||||
else:
|
||||
if not await LevelUser.check_level(user_id, None, plugin.admin_level):
|
||||
try:
|
||||
await MessageUtils.build_message(
|
||||
f"你的权限不足喔,该功能需要的权限等级: {plugin.admin_level}"
|
||||
).send()
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"auth_admin 发送消息失败", "HOOK", session=session, e=e
|
||||
)
|
||||
logger.debug(
|
||||
f"{plugin.name}({plugin.module}) 管理员权限不足...",
|
||||
"HOOK",
|
||||
session=session,
|
||||
elif not await LevelUser.check_level(user_id, None, plugin.admin_level):
|
||||
try:
|
||||
await MessageUtils.build_message(
|
||||
f"你的权限不足喔,该功能需要的权限等级: {plugin.admin_level}"
|
||||
).send()
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"auth_admin 发送消息失败", "HOOK", session=session, e=e
|
||||
)
|
||||
raise IgnoredException("权限不足")
|
||||
logger.debug(
|
||||
f"{plugin.name}({plugin.module}) 管理员权限不足...",
|
||||
"HOOK",
|
||||
session=session,
|
||||
)
|
||||
raise IgnoredException("权限不足")
|
||||
|
||||
async def auth_group(
|
||||
self, plugin: PluginInfo, session: EventSession, message: UniMsg
|
||||
@@ -466,29 +460,35 @@ class AuthChecker:
|
||||
session: EventSession
|
||||
message: UniMsg
|
||||
"""
|
||||
if group_id := session.id3 or session.id2:
|
||||
text = message.extract_plain_text()
|
||||
group = await GroupConsole.get_group(group_id)
|
||||
if not group:
|
||||
"""群不存在"""
|
||||
raise IgnoredException("群不存在")
|
||||
if group.level < 0:
|
||||
"""群权限小于0"""
|
||||
logger.debug(
|
||||
f"群黑名单, 群权限-1...",
|
||||
"HOOK",
|
||||
session=session,
|
||||
)
|
||||
raise IgnoredException("群黑名单")
|
||||
if not group.status:
|
||||
"""群休眠"""
|
||||
if text.strip() != "醒来":
|
||||
logger.debug(
|
||||
f"群休眠状态...",
|
||||
"HOOK",
|
||||
session=session,
|
||||
)
|
||||
raise IgnoredException("群休眠状态")
|
||||
if not (group_id := session.id3 or session.id2):
|
||||
return
|
||||
text = message.extract_plain_text()
|
||||
group = await GroupConsole.get_group(group_id)
|
||||
if not group:
|
||||
"""群不存在"""
|
||||
raise IgnoredException("群不存在")
|
||||
if group.level < 0:
|
||||
"""群权限小于0"""
|
||||
logger.debug(
|
||||
"群黑名单, 群权限-1...",
|
||||
"HOOK",
|
||||
session=session,
|
||||
)
|
||||
raise IgnoredException("群黑名单")
|
||||
if not group.status:
|
||||
"""群休眠"""
|
||||
if text.strip() != "醒来":
|
||||
logger.debug("群休眠状态...", "HOOK", session=session)
|
||||
raise IgnoredException("群休眠状态")
|
||||
if plugin.level > group.level:
|
||||
"""插件等级大于群等级"""
|
||||
logger.debug(
|
||||
f"{plugin.name}({plugin.module}) 群等级限制.."
|
||||
f"该功能需要的群等级: {plugin.level}..",
|
||||
"HOOK",
|
||||
session=session,
|
||||
)
|
||||
raise IgnoredException(f"{plugin.name}({plugin.module}) 群等级限制...")
|
||||
|
||||
async def auth_cost(
|
||||
self, user: UserConsole, plugin: PluginInfo, session: EventSession
|
||||
@@ -512,7 +512,8 @@ class AuthChecker:
|
||||
except Exception as e:
|
||||
logger.error("auth_cost 发送消息失败", "HOOK", session=session, e=e)
|
||||
logger.debug(
|
||||
f"{plugin.name}({plugin.module}) 金币限制..该功能需要{plugin.cost_gold}金币..",
|
||||
f"{plugin.name}({plugin.module}) 金币限制.."
|
||||
f"该功能需要{plugin.cost_gold}金币..",
|
||||
"HOOK",
|
||||
session=session,
|
||||
)
|
||||
|
||||
@@ -2,19 +2,19 @@ import time
|
||||
from collections import defaultdict
|
||||
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.adapters.onebot.v11 import Bot
|
||||
from nonebot.exception import IgnoredException
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.message import run_preprocessor
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot_plugin_alconna import At
|
||||
from nonebot.adapters.onebot.v11 import Bot
|
||||
from nonebot.message import run_preprocessor
|
||||
from nonebot.exception import IgnoredException
|
||||
from nonebot_plugin_session import EventSession
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.models.ban_console import BanConsole
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.utils.enum import PluginType
|
||||
from zhenxun.utils.message import MessageUtils
|
||||
from zhenxun.models.ban_console import BanConsole
|
||||
|
||||
malicious_check_time = Config.get_config("hook", "MALICIOUS_CHECK_TIME")
|
||||
malicious_ban_count = Config.get_config("hook", "MALICIOUS_BAN_COUNT")
|
||||
@@ -36,12 +36,12 @@ class BanCheckLimiter:
|
||||
self.default_check_time = default_check_time
|
||||
self.default_count = default_count
|
||||
|
||||
def add(self, key: str | int | float):
|
||||
def add(self, key: str | float):
|
||||
if self.mint[key] == 1:
|
||||
self.mtime[key] = time.time()
|
||||
self.mint[key] += 1
|
||||
|
||||
def check(self, key: str | int | float) -> bool:
|
||||
def check(self, key: str | float) -> bool:
|
||||
if time.time() - self.mtime[key] > self.default_check_time:
|
||||
self.mtime[key] = time.time()
|
||||
self.mint[key] = 0
|
||||
@@ -76,6 +76,7 @@ async def _(
|
||||
PluginType.HIDDEN,
|
||||
PluginType.DEPENDANT,
|
||||
PluginType.ADMIN,
|
||||
PluginType.SUPERUSER,
|
||||
]:
|
||||
return
|
||||
else:
|
||||
@@ -101,7 +102,7 @@ async def _(
|
||||
await MessageUtils.build_message(
|
||||
[
|
||||
At(flag="user", target=user_id),
|
||||
f"检测到恶意触发命令,您将被封禁 30 分钟",
|
||||
"检测到恶意触发命令,您将被封禁 30 分钟",
|
||||
]
|
||||
).send()
|
||||
logger.debug(
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot_plugin_session import EventSession
|
||||
from nonebot_plugin_alconna import At, Args, Match, Alconna, Arparma, on_alconna
|
||||
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import PluginType
|
||||
from zhenxun.utils.depends import UserName
|
||||
from zhenxun.utils.message import MessageUtils
|
||||
from zhenxun.utils.platform import PlatformUtils
|
||||
from zhenxun.configs.utils import PluginExtraData
|
||||
from zhenxun.models.group_member_info import GroupInfoUser
|
||||
|
||||
from .my_info import get_user_info
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="查看信息",
|
||||
description="查看个人信息",
|
||||
usage="""
|
||||
查看个人/群组信息
|
||||
指令:
|
||||
我的信息 ?[at]
|
||||
""".strip(),
|
||||
extra=PluginExtraData(author="HibiKier", version="0.1").dict(),
|
||||
)
|
||||
|
||||
|
||||
_matcher = on_alconna(Alconna("我的信息", Args["at_user?", At]), priority=5, block=True)
|
||||
|
||||
|
||||
@_matcher.handle()
|
||||
async def _(
|
||||
bot: Bot,
|
||||
session: EventSession,
|
||||
arparma: Arparma,
|
||||
at_user: Match[At],
|
||||
nickname: str = UserName(),
|
||||
):
|
||||
user_id = session.id1
|
||||
if at_user.available:
|
||||
user_id = at_user.result.target
|
||||
if user := await GroupInfoUser.get_or_none(
|
||||
user_id=user_id, group_id=session.id2
|
||||
):
|
||||
nickname = user.user_name
|
||||
else:
|
||||
nickname = user_id
|
||||
if not user_id:
|
||||
await MessageUtils.build_message("用户id为空...").finish(reply_to=True)
|
||||
try:
|
||||
result = await get_user_info(bot, user_id, session.id2, nickname)
|
||||
await MessageUtils.build_message(result).send(at_sender=True)
|
||||
logger.info("获取用户信息", arparma.header_result, session=session)
|
||||
except Exception as e:
|
||||
logger.error("获取用户信息失败", arparma.header_result, session=session, e=e)
|
||||
await MessageUtils.build_message("获取用户信息失败...").finish(reply_to=True)
|
||||
@@ -0,0 +1,193 @@
|
||||
import random
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from nonebot.adapters import Bot
|
||||
from tortoise.functions import Count
|
||||
from tortoise.expressions import RawSQL
|
||||
from nonebot_plugin_htmlrender import template_to_pic
|
||||
|
||||
from zhenxun.models.sign_user import SignUser
|
||||
from zhenxun.models.level_user import LevelUser
|
||||
from zhenxun.models.statistics import Statistics
|
||||
from zhenxun.utils.platform import PlatformUtils
|
||||
from zhenxun.models.chat_history import ChatHistory
|
||||
from zhenxun.models.user_console import UserConsole
|
||||
from zhenxun.configs.path_config import TEMPLATE_PATH
|
||||
|
||||
RACE = [
|
||||
"龙族",
|
||||
"魅魔",
|
||||
"森林精灵",
|
||||
"血精灵",
|
||||
"暗夜精灵",
|
||||
"狗头人",
|
||||
"狼人",
|
||||
"猫人",
|
||||
"猪头人",
|
||||
"骷髅",
|
||||
"僵尸",
|
||||
"虫族",
|
||||
"人类",
|
||||
"天使",
|
||||
"恶魔",
|
||||
"甲壳虫",
|
||||
"猎猫",
|
||||
"人鱼",
|
||||
"哥布林",
|
||||
"地精",
|
||||
"泰坦",
|
||||
"矮人",
|
||||
"山巨人",
|
||||
"石巨人",
|
||||
]
|
||||
|
||||
SEX = ["男", "女", "雌", "雄"]
|
||||
|
||||
OCC = [
|
||||
"猎人",
|
||||
"战士",
|
||||
"魔法师",
|
||||
"狂战士",
|
||||
"魔战士",
|
||||
"盗贼",
|
||||
"术士",
|
||||
"牧师",
|
||||
"骑士",
|
||||
"刺客",
|
||||
"游侠",
|
||||
"召唤师",
|
||||
"圣骑士",
|
||||
"魔使",
|
||||
"龙骑士",
|
||||
"赏金猎手",
|
||||
"吟游诗人",
|
||||
"德鲁伊",
|
||||
"祭司",
|
||||
"符文师",
|
||||
"狂暴术士",
|
||||
"萨满",
|
||||
"裁决者",
|
||||
"角斗士",
|
||||
]
|
||||
|
||||
lik2level = {
|
||||
400: 8,
|
||||
270: 7,
|
||||
200: 6,
|
||||
140: 5,
|
||||
90: 4,
|
||||
50: 3,
|
||||
25: 2,
|
||||
10: 1,
|
||||
0: 0,
|
||||
}
|
||||
|
||||
|
||||
def get_level(impression: float) -> int:
|
||||
"""获取好感度等级"""
|
||||
return next((level for imp, level in lik2level.items() if impression >= imp), 0)
|
||||
|
||||
|
||||
async def get_chat_history(
|
||||
user_id: str, group_id: str | None
|
||||
) -> tuple[list[str], list[str]]:
|
||||
"""获取用户聊天记录
|
||||
|
||||
参数:
|
||||
user_id: 用户id
|
||||
group_id: 群id
|
||||
|
||||
返回:
|
||||
tuple[list[str], list[str]]: 日期列表, 次数列表
|
||||
|
||||
"""
|
||||
now = datetime.now()
|
||||
filter_date = now - timedelta(days=7, hours=now.hour, minutes=now.minute)
|
||||
date_list = (
|
||||
await ChatHistory.filter(
|
||||
user_id=user_id, group_id=group_id, create_time__gte=filter_date
|
||||
)
|
||||
.annotate(date=RawSQL("DATE(create_time)"), count=Count("id"))
|
||||
.group_by("date")
|
||||
.values("date", "count")
|
||||
)
|
||||
chart_date = []
|
||||
count_list = []
|
||||
date2cnt = {str(date["date"]): date["count"] for date in date_list}
|
||||
date = now.date()
|
||||
for _ in range(7):
|
||||
if str(date) in date2cnt:
|
||||
count_list.append(date2cnt[str(date)])
|
||||
else:
|
||||
count_list.append(0)
|
||||
chart_date.append(str(date))
|
||||
date -= timedelta(days=1)
|
||||
for c in chart_date:
|
||||
chart_date[chart_date.index(c)] = c[5:]
|
||||
chart_date.reverse()
|
||||
count_list.reverse()
|
||||
return chart_date, count_list
|
||||
|
||||
|
||||
async def get_user_info(
|
||||
bot: Bot, user_id: str, group_id: str | None, nickname: str
|
||||
) -> bytes:
|
||||
"""获取用户个人信息
|
||||
|
||||
参数:
|
||||
bot: Bot
|
||||
user_id: 用户id
|
||||
group_id: 群id
|
||||
nickname: 用户昵称
|
||||
|
||||
返回:
|
||||
bytes: 图片数据
|
||||
"""
|
||||
platform = PlatformUtils.get_platform(bot) or ""
|
||||
ava_url = PlatformUtils.get_user_avatar_url(user_id, platform)
|
||||
user = await UserConsole.get_user(user_id, platform)
|
||||
level = await LevelUser.get_user_level(user_id, group_id)
|
||||
sign_level = 0
|
||||
if sign_user := await SignUser.get_or_none(user_id=user_id):
|
||||
sign_level = get_level(float(sign_user.impression))
|
||||
chat_count = await ChatHistory.filter(user_id=user_id, group_id=group_id).count()
|
||||
stat_count = await Statistics.filter(user_id=user_id, group_id=group_id).count()
|
||||
select_index = ["" for _ in range(9)]
|
||||
select_index[sign_level] = "select"
|
||||
uid = f"{user.uid}".rjust(8, "0")
|
||||
uid = f"{uid[:4]} {uid[4:]}"
|
||||
now = datetime.now()
|
||||
weather = "moon" if now.hour < 6 or now.hour > 19 else "sun"
|
||||
chart_date, count_list = await get_chat_history(user_id, group_id)
|
||||
data = {
|
||||
"date": now.date(),
|
||||
"weather": weather,
|
||||
"ava_url": ava_url,
|
||||
"nickname": nickname,
|
||||
"title": "勇 者",
|
||||
"race": random.choice(RACE),
|
||||
"sex": random.choice(SEX),
|
||||
"occ": random.choice(OCC),
|
||||
"uid": uid,
|
||||
"description": "这是一个传奇的故事,"
|
||||
"人类的赞歌是勇气的赞歌,人类的伟大是勇气的伟译。",
|
||||
"sign_level": sign_level,
|
||||
"level": level,
|
||||
"gold": user.gold,
|
||||
"prop": len(user.props),
|
||||
"call": stat_count,
|
||||
"say": chat_count,
|
||||
"select_index": select_index,
|
||||
"chart_date": chart_date,
|
||||
"count_list": count_list,
|
||||
}
|
||||
return await template_to_pic(
|
||||
template_path=str((TEMPLATE_PATH / "my_info").absolute()),
|
||||
template_name="main.html",
|
||||
templates={"data": data},
|
||||
pages={
|
||||
"viewport": {"width": 1754, "height": 1240},
|
||||
"base_url": f"file://{TEMPLATE_PATH}",
|
||||
},
|
||||
wait=2,
|
||||
)
|
||||
@@ -39,5 +39,5 @@ async def _(bot: Bot):
|
||||
await GroupConsole.filter(group_id__in=update_id).update(group_flag=1)
|
||||
logger.debug(
|
||||
f"更新Bot: {bot.self_id} 的群认证完成,共创建 {len(create_list)} 条数据,"
|
||||
"共修改 {len(update_id)} 条数据..."
|
||||
f"共修改 {len(update_id)} 条数据..."
|
||||
)
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
from pathlib import Path
|
||||
|
||||
import nonebot
|
||||
from nonebot import get_loaded_plugins
|
||||
from nonebot.drivers import Driver
|
||||
from nonebot.plugin import Plugin
|
||||
from ruamel.yaml import YAML
|
||||
from nonebot.plugin import Plugin
|
||||
from nonebot.drivers import Driver
|
||||
from nonebot import get_loaded_plugins
|
||||
from ruamel.yaml.comments import CommentedMap
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.configs.path_config import DATA_PATH
|
||||
from zhenxun.configs.utils import RegisterConfig
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.configs.utils import RegisterConfig
|
||||
from zhenxun.configs.path_config import DATA_PATH
|
||||
|
||||
_yaml = YAML(pure=True)
|
||||
_yaml.allow_unicode = True
|
||||
@@ -72,15 +72,14 @@ def _generate_simple_config():
|
||||
Config.set_config(module, k, _data[module][k])
|
||||
_tmp_data[module][k] = Config.get_config(module, k)
|
||||
except AttributeError as e:
|
||||
raise AttributeError(f"{e}\n" + "可能为config.yaml配置文件填写不规范")
|
||||
raise AttributeError(f"{e}\n可能为config.yaml配置文件填写不规范") from e
|
||||
Config.save()
|
||||
temp_file = DATA_PATH / "temp_config.yaml"
|
||||
# 重新生成简易配置文件
|
||||
try:
|
||||
with open(temp_file, "w", encoding="utf8") as wf:
|
||||
# yaml.dump(_tmp_data, wf, Dumper=yaml.RoundTripDumper, allow_unicode=True)
|
||||
_yaml.dump(_tmp_data, wf)
|
||||
with open(temp_file, "r", encoding="utf8") as rf:
|
||||
with open(temp_file, encoding="utf8") as rf:
|
||||
_data = _yaml.load(rf)
|
||||
# 添加注释
|
||||
for module in _data.keys():
|
||||
@@ -93,7 +92,7 @@ def _generate_simple_config():
|
||||
with SIMPLE_CONFIG_FILE.open("w", encoding="utf8") as wf:
|
||||
_yaml.dump(_data, wf)
|
||||
except Exception as e:
|
||||
logger.error(f"生成简易配置注释错误...", e=e)
|
||||
logger.error("生成简易配置注释错误...", e=e)
|
||||
if temp_file.exists():
|
||||
temp_file.unlink()
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ import nonebot
|
||||
import aiofiles
|
||||
import ujson as json
|
||||
from ruamel.yaml import YAML
|
||||
from nonebot.plugin import Plugin
|
||||
from nonebot.drivers import Driver
|
||||
from nonebot import get_loaded_plugins
|
||||
from nonebot.plugin import Plugin, PluginMetadata
|
||||
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.models.task_info import TaskInfo
|
||||
@@ -21,6 +21,8 @@ from zhenxun.utils.enum import (
|
||||
PluginLimitType,
|
||||
)
|
||||
|
||||
from .manager import manager
|
||||
|
||||
_yaml = YAML(pure=True)
|
||||
_yaml.allow_unicode = True
|
||||
_yaml.indent = 2
|
||||
@@ -41,64 +43,68 @@ async def _handle_setting(
|
||||
plugin_list: 插件列表
|
||||
limit_list: 插件限制列表
|
||||
"""
|
||||
if metadata := plugin.metadata:
|
||||
extra = metadata.extra
|
||||
extra_data = PluginExtraData(**extra)
|
||||
logger.debug(f"{metadata.name}:{plugin.name} -> {extra}", "初始化插件数据")
|
||||
setting = extra_data.setting or PluginSetting()
|
||||
if metadata.type == "library":
|
||||
extra_data.plugin_type = PluginType.HIDDEN
|
||||
if (
|
||||
extra_data.plugin_type
|
||||
== PluginType.HIDDEN
|
||||
# and extra_data.plugin_type != "功能"
|
||||
):
|
||||
extra_data.menu_type = ""
|
||||
plugin_list.append(
|
||||
PluginInfo(
|
||||
metadata = plugin.metadata
|
||||
if not metadata:
|
||||
if not plugin.sub_plugins:
|
||||
return
|
||||
"""父插件"""
|
||||
metadata = PluginMetadata(name=plugin.name, description="", usage="")
|
||||
extra = metadata.extra
|
||||
extra_data = PluginExtraData(**extra)
|
||||
logger.debug(f"{metadata.name}:{plugin.name} -> {extra}", "初始化插件数据")
|
||||
setting = extra_data.setting or PluginSetting()
|
||||
if metadata.type == "library":
|
||||
extra_data.plugin_type = PluginType.HIDDEN
|
||||
if extra_data.plugin_type == PluginType.HIDDEN:
|
||||
extra_data.menu_type = ""
|
||||
if plugin.sub_plugins:
|
||||
extra_data.plugin_type = PluginType.PARENT
|
||||
plugin_list.append(
|
||||
PluginInfo(
|
||||
module=plugin.name,
|
||||
module_path=plugin.module_name,
|
||||
name=metadata.name,
|
||||
author=extra_data.author,
|
||||
version=extra_data.version,
|
||||
level=setting.level,
|
||||
default_status=setting.default_status,
|
||||
limit_superuser=setting.limit_superuser,
|
||||
menu_type=extra_data.menu_type,
|
||||
cost_gold=setting.cost_gold,
|
||||
plugin_type=extra_data.plugin_type,
|
||||
admin_level=extra_data.admin_level,
|
||||
parent=(plugin.parent_plugin.module_name if plugin.parent_plugin else None),
|
||||
)
|
||||
)
|
||||
if extra_data.limits:
|
||||
limit_list.extend(
|
||||
PluginLimit(
|
||||
module=plugin.name,
|
||||
module_path=plugin.module_name,
|
||||
name=metadata.name,
|
||||
author=extra_data.author,
|
||||
version=extra_data.version,
|
||||
level=setting.level,
|
||||
default_status=setting.default_status,
|
||||
limit_superuser=setting.limit_superuser,
|
||||
menu_type=extra_data.menu_type,
|
||||
cost_gold=setting.cost_gold,
|
||||
plugin_type=extra_data.plugin_type,
|
||||
admin_level=extra_data.admin_level,
|
||||
limit_type=limit._type,
|
||||
watch_type=limit.watch_type,
|
||||
status=limit.status,
|
||||
check_type=limit.check_type,
|
||||
result=limit.result,
|
||||
cd=getattr(limit, "cd", None),
|
||||
max_count=getattr(limit, "max_count", None),
|
||||
)
|
||||
for limit in extra_data.limits
|
||||
)
|
||||
if extra_data.limits:
|
||||
limit_list.extend(
|
||||
PluginLimit(
|
||||
module=plugin.name,
|
||||
module_path=plugin.module_name,
|
||||
limit_type=limit._type,
|
||||
watch_type=limit.watch_type,
|
||||
status=limit.status,
|
||||
check_type=limit.check_type,
|
||||
result=limit.result,
|
||||
cd=getattr(limit, "cd", None),
|
||||
max_count=getattr(limit, "max_count", None),
|
||||
)
|
||||
for limit in extra_data.limits
|
||||
)
|
||||
if extra_data.tasks:
|
||||
task_list.extend(
|
||||
(
|
||||
task.create_status,
|
||||
TaskInfo(
|
||||
module=task.module,
|
||||
name=task.name,
|
||||
status=task.status,
|
||||
run_time=task.run_time,
|
||||
default_status=task.default_status,
|
||||
),
|
||||
)
|
||||
for task in extra_data.tasks
|
||||
if extra_data.tasks:
|
||||
task_list.extend(
|
||||
(
|
||||
task.create_status,
|
||||
TaskInfo(
|
||||
module=task.module,
|
||||
name=task.name,
|
||||
status=task.status,
|
||||
run_time=task.run_time,
|
||||
default_status=task.default_status,
|
||||
),
|
||||
)
|
||||
for task in extra_data.tasks
|
||||
)
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
@@ -115,8 +121,7 @@ async def _():
|
||||
module2id = {m["module_path"]: m["id"] for m in module_list}
|
||||
for plugin in get_loaded_plugins():
|
||||
load_plugin.append(plugin.module_name)
|
||||
if plugin.metadata:
|
||||
await _handle_setting(plugin, plugin_list, limit_list, task_list)
|
||||
await _handle_setting(plugin, plugin_list, limit_list, task_list)
|
||||
create_list = []
|
||||
update_list = []
|
||||
for plugin in plugin_list:
|
||||
@@ -145,23 +150,25 @@ async def _():
|
||||
# ["name", "author", "version", "admin_level", "plugin_type"],
|
||||
# 10,
|
||||
# )
|
||||
if limit_list:
|
||||
limit_create = []
|
||||
plugins = []
|
||||
if module_path_list := [limit.module_path for limit in limit_list]:
|
||||
plugins = await PluginInfo.filter(module_path__in=module_path_list).all()
|
||||
if plugins:
|
||||
for limit in limit_list:
|
||||
if lmt := [p for p in plugins if p.module_path == limit.module_path]:
|
||||
plugin = lmt[0]
|
||||
limit_type_list = [
|
||||
_limit.limit_type for _limit in await plugin.plugin_limit.all() # type: ignore
|
||||
]
|
||||
if limit.limit_type not in limit_type_list:
|
||||
limit.plugin = plugin
|
||||
limit_create.append(limit)
|
||||
if limit_create:
|
||||
await PluginLimit.bulk_create(limit_create, 10)
|
||||
# for limit in limit_list:
|
||||
# limit_create = []
|
||||
# plugins = []
|
||||
# if module_path_list := [limit.module_path for limit in limit_list]:
|
||||
# plugins = await PluginInfo.get_plugins(module_path__in=module_path_list)
|
||||
# if plugins:
|
||||
# for limit in limit_list:
|
||||
# if lmt := [p for p in plugins if p.module_path == limit.module_path]:
|
||||
# plugin = lmt[0]
|
||||
# """不在数据库中"""
|
||||
# limit_type_list = [
|
||||
# _limit.limit_type
|
||||
# for _limit in await plugin.plugin_limit.all() # type: ignore
|
||||
# ]
|
||||
# if limit.limit_type not in limit_type_list:
|
||||
# limit.plugin = plugin
|
||||
# limit_create.append(limit)
|
||||
# if limit_create:
|
||||
# await PluginLimit.bulk_create(limit_create, 10)
|
||||
if task_list:
|
||||
module_dict = {
|
||||
t[1]: t[0] for t in await TaskInfo.all().values_list("id", "module")
|
||||
@@ -192,10 +199,18 @@ async def _():
|
||||
await data_migration()
|
||||
await PluginInfo.filter(module_path__in=load_plugin).update(load_status=True)
|
||||
await PluginInfo.filter(module_path__not_in=load_plugin).update(load_status=False)
|
||||
manager.init()
|
||||
if limit_list:
|
||||
for limit in limit_list:
|
||||
if not manager.exist(limit.module_path, limit.limit_type):
|
||||
"""不存在,添加"""
|
||||
manager.add(limit.module_path, limit)
|
||||
manager.save_file()
|
||||
await manager.load_to_db()
|
||||
|
||||
|
||||
async def data_migration():
|
||||
await limit_migration()
|
||||
# await limit_migration()
|
||||
await plugin_migration()
|
||||
await group_migration()
|
||||
|
||||
|
||||
@@ -0,0 +1,417 @@
|
||||
from copy import deepcopy
|
||||
|
||||
from ruamel.yaml import YAML
|
||||
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.configs.path_config import DATA_PATH
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.models.plugin_limit import PluginLimit
|
||||
from zhenxun.utils.enum import BlockType, LimitCheckType, PluginLimitType
|
||||
from zhenxun.configs.utils import BaseBlock, PluginCdBlock, PluginCountBlock
|
||||
|
||||
_yaml = YAML(pure=True)
|
||||
_yaml.indent = 2
|
||||
_yaml.allow_unicode = True
|
||||
|
||||
|
||||
CD_TEST = """需要cd的功能
|
||||
自定义的功能需要cd也可以在此配置
|
||||
key:模块名称
|
||||
cd:cd 时长(秒)
|
||||
status:此限制的开关状态
|
||||
check_type:'PRIVATE'/'GROUP'/'ALL',限制私聊/群聊/全部
|
||||
watch_type:监听对象,以user_id或group_id作为键来限制,'USER':用户id,'GROUP':群id
|
||||
示例:'USER':用户N秒内触发1次,'GROUP':群N秒内触发1次
|
||||
result:回复的话,可以添加[at],[uname],[nickname]来对应艾特,用户群名称,昵称系统昵称
|
||||
result 为 "" 或 None 时则不回复
|
||||
result示例:"[uname]你冲的太快了,[nickname]先生,请稍后再冲[at]"
|
||||
result回复:"老色批你冲的太快了,欧尼酱先生,请稍后再冲@老色批"
|
||||
用户昵称↑ 昵称系统的昵称↑ 艾特用户↑"""
|
||||
|
||||
|
||||
BLOCK_TEST = """用户调用阻塞
|
||||
即 当用户调用此功能还未结束时
|
||||
用发送消息阻止用户重复调用此命令直到该命令结束
|
||||
key:模块名称
|
||||
status:此限制的开关状态
|
||||
check_type:'PRIVATE'/'GROUP'/'ALL',限制私聊/群聊/全部
|
||||
watch_type:监听对象,以user_id或group_id作为键来限制,'USER':用户id,'GROUP':群id
|
||||
示例:'USER':阻塞用户,'group':阻塞群聊
|
||||
result:回复的话,可以添加[at],[uname],[nickname]来对应艾特,用户群名称,昵称系统昵称
|
||||
result 为 "" 或 None 时则不回复
|
||||
result示例:"[uname]你冲的太快了,[nickname]先生,请稍后再冲[at]"
|
||||
result回复:"老色批你冲的太快了,欧尼酱先生,请稍后再冲@老色批"
|
||||
用户昵称↑ 昵称系统的昵称↑ 艾特用户↑"""
|
||||
|
||||
COUNT_TEST = """命令每日次数限制
|
||||
即 用户/群聊 每日可调用命令的次数 [数据内存存储,重启将会重置]
|
||||
每日调用直到 00:00 刷新
|
||||
key:模块名称
|
||||
max_count: 每日调用上限
|
||||
status:此限制的开关状态
|
||||
watch_type:监听对象,以user_id或group_id作为键来限制,'USER':用户id,'GROUP':群id
|
||||
示例:'USER':用户上限,'group':群聊上限
|
||||
result:回复的话,可以添加[at],[uname],[nickname]来对应艾特,用户群名称,昵称系统昵称
|
||||
result 为 "" 或 None 时则不回复
|
||||
result示例:"[uname]你冲的太快了,[nickname]先生,请稍后再冲[at]"
|
||||
result回复:"老色批你冲的太快了,欧尼酱先生,请稍后再冲@老色批"
|
||||
用户昵称↑ 昵称系统的昵称↑ 艾特用户↑"""
|
||||
|
||||
|
||||
class Manager:
|
||||
"""
|
||||
插件命令 cd 管理器
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.cd_file = DATA_PATH / "configs" / "plugins2cd.yaml"
|
||||
self.block_file = DATA_PATH / "configs" / "plugins2block.yaml"
|
||||
self.count_file = DATA_PATH / "configs" / "plugins2count.yaml"
|
||||
self.cd_data = {}
|
||||
self.block_data = {}
|
||||
self.count_data = {}
|
||||
|
||||
def add(
|
||||
self,
|
||||
module_path: str,
|
||||
data: BaseBlock | PluginCdBlock | PluginCountBlock | PluginLimit,
|
||||
):
|
||||
"""添加限制"""
|
||||
if isinstance(data, PluginLimit):
|
||||
check_type = BlockType.ALL
|
||||
if LimitCheckType.GROUP == data.check_type:
|
||||
check_type = BlockType.GROUP
|
||||
elif LimitCheckType.PRIVATE == data.check_type:
|
||||
check_type = BlockType.PRIVATE
|
||||
if data.limit_type == PluginLimitType.CD:
|
||||
data = PluginCdBlock(
|
||||
status=data.status,
|
||||
check_type=check_type,
|
||||
watch_type=data.watch_type,
|
||||
result=data.result,
|
||||
cd=data.cd,
|
||||
)
|
||||
elif data.limit_type == PluginLimitType.BLOCK:
|
||||
data = BaseBlock(
|
||||
status=data.status,
|
||||
check_type=check_type,
|
||||
watch_type=data.watch_type,
|
||||
result=data.result,
|
||||
)
|
||||
elif data.limit_type == PluginLimitType.COUNT:
|
||||
data = PluginCountBlock(
|
||||
status=data.status,
|
||||
watch_type=data.watch_type,
|
||||
result=data.result,
|
||||
max_count=data.max_count,
|
||||
)
|
||||
if isinstance(data, PluginCdBlock):
|
||||
self.cd_data[module_path] = data
|
||||
elif isinstance(data, PluginCountBlock):
|
||||
self.count_data[module_path] = data
|
||||
elif isinstance(data, BaseBlock):
|
||||
self.block_data[module_path] = data
|
||||
|
||||
def exist(self, module_path: str, type: PluginLimitType):
|
||||
"""是否存在"""
|
||||
if type == PluginLimitType.CD:
|
||||
return module_path in self.cd_data
|
||||
elif type == PluginLimitType.BLOCK:
|
||||
return module_path in self.block_data
|
||||
elif type == PluginLimitType.COUNT:
|
||||
return module_path in self.count_data
|
||||
|
||||
def init(self):
|
||||
if not self.cd_file.exists():
|
||||
self.save_cd_file()
|
||||
if not self.block_file.exists():
|
||||
self.save_block_file()
|
||||
if not self.count_file.exists():
|
||||
self.save_count_file()
|
||||
self.__load_file()
|
||||
|
||||
def __load_file(self):
|
||||
self.__load_block_file()
|
||||
self.__load_cd_file()
|
||||
self.__load_count_file()
|
||||
|
||||
def save_file(self):
|
||||
"""保存文件"""
|
||||
self.save_cd_file()
|
||||
self.save_block_file()
|
||||
self.save_count_file()
|
||||
|
||||
def save_cd_file(self):
|
||||
"""保存文件"""
|
||||
self._extracted_from_save_file_3("PluginCdLimit", CD_TEST, self.cd_data)
|
||||
|
||||
def save_block_file(self):
|
||||
"""保存文件"""
|
||||
self._extracted_from_save_file_3(
|
||||
"PluginBlockLimit", BLOCK_TEST, self.block_data
|
||||
)
|
||||
|
||||
def save_count_file(self):
|
||||
"""保存文件"""
|
||||
self._extracted_from_save_file_3(
|
||||
"PluginCountLimit", COUNT_TEST, self.count_data
|
||||
)
|
||||
|
||||
def _extracted_from_save_file_3(self, type_: str, after: str, data: dict):
|
||||
"""保存文件
|
||||
|
||||
参数:
|
||||
type_: 类型参数
|
||||
after: 备注
|
||||
"""
|
||||
temp_data = deepcopy(data)
|
||||
if not temp_data:
|
||||
temp_data = {
|
||||
"test": {
|
||||
"status": False,
|
||||
"check_type": "ALL",
|
||||
"limit_type": "USER",
|
||||
"result": "你冲的太快了,请稍后再冲",
|
||||
}
|
||||
}
|
||||
if type_ == "PluginCdLimit":
|
||||
temp_data["test"]["cd"] = 5
|
||||
elif type_ == "PluginCountLimit":
|
||||
temp_data["test"]["max_count"] = 5
|
||||
del temp_data["test"]["check_type"]
|
||||
else:
|
||||
for v in temp_data:
|
||||
temp_data[v] = temp_data[v].dict()
|
||||
if check_type := temp_data[v].get("check_type"):
|
||||
temp_data[v]["check_type"] = str(check_type)
|
||||
if watch_type := temp_data[v].get("watch_type"):
|
||||
temp_data[v]["watch_type"] = str(watch_type)
|
||||
if type_ == "PluginCountLimit":
|
||||
del temp_data[v]["check_type"]
|
||||
file = self.block_file
|
||||
if type_ == "PluginCdLimit":
|
||||
file = self.cd_file
|
||||
elif type_ == "PluginCountLimit":
|
||||
file = self.count_file
|
||||
with open(file, "w", encoding="utf8") as f:
|
||||
_yaml.dump({type_: temp_data}, f)
|
||||
with open(file, encoding="utf8") as rf:
|
||||
_data = _yaml.load(rf)
|
||||
_data.yaml_set_comment_before_after_key(after=after, key=type_)
|
||||
with open(file, "w", encoding="utf8") as wf:
|
||||
_yaml.dump(_data, wf)
|
||||
|
||||
def __load_cd_file(self):
|
||||
self.cd_data: dict[str, PluginCdBlock] = {}
|
||||
if self.cd_file.exists():
|
||||
with open(self.cd_file, encoding="utf8") as f:
|
||||
temp = _yaml.load(f)
|
||||
if "PluginCdLimit" in temp.keys():
|
||||
for k, v in temp["PluginCdLimit"].items():
|
||||
self.cd_data[k] = PluginCdBlock.parse_obj(v)
|
||||
|
||||
def __load_block_file(self):
|
||||
self.block_data: dict[str, BaseBlock] = {}
|
||||
if self.block_file.exists():
|
||||
with open(self.block_file, encoding="utf8") as f:
|
||||
temp = _yaml.load(f)
|
||||
if "PluginBlockLimit" in temp.keys():
|
||||
for k, v in temp["PluginBlockLimit"].items():
|
||||
self.block_data[k] = BaseBlock.parse_obj(v)
|
||||
|
||||
def __load_count_file(self):
|
||||
self.count_data: dict[str, PluginCountBlock] = {}
|
||||
if self.count_file.exists():
|
||||
with open(self.count_file, encoding="utf8") as f:
|
||||
temp = _yaml.load(f)
|
||||
if "PluginCountLimit" in temp.keys():
|
||||
for k, v in temp["PluginCountLimit"].items():
|
||||
self.count_data[k] = PluginCountBlock.parse_obj(v)
|
||||
|
||||
def __replace_data(
|
||||
self,
|
||||
db_data: PluginLimit | None,
|
||||
limit: PluginCdBlock | BaseBlock | PluginCountBlock,
|
||||
) -> PluginLimit:
|
||||
"""替换数据"""
|
||||
if not db_data:
|
||||
db_data = PluginLimit()
|
||||
db_data.status = limit.status
|
||||
check_type = LimitCheckType.ALL
|
||||
if BlockType.GROUP == limit.check_type:
|
||||
check_type = LimitCheckType.GROUP
|
||||
elif BlockType.PRIVATE == limit.check_type:
|
||||
check_type = LimitCheckType.PRIVATE
|
||||
db_data.check_type = check_type
|
||||
db_data.watch_type = limit.watch_type
|
||||
db_data.result = limit.result or ""
|
||||
return db_data
|
||||
|
||||
def __set_data(
|
||||
self,
|
||||
k: str,
|
||||
db_data: PluginLimit | None,
|
||||
limit: PluginCdBlock | BaseBlock | PluginCountBlock,
|
||||
limit_type: PluginLimitType,
|
||||
module2plugin: dict[str, PluginInfo],
|
||||
) -> tuple[PluginLimit, bool]:
|
||||
"""设置数据
|
||||
|
||||
参数:
|
||||
k: 模块名
|
||||
db_data: 数据库数据
|
||||
limit: 文件数据
|
||||
limit_type: 限制类型
|
||||
module2plugin: 模块:插件信息
|
||||
|
||||
返回:
|
||||
tuple[PluginLimit, bool]: PluginLimit,是否创建
|
||||
"""
|
||||
if not db_data:
|
||||
return (
|
||||
PluginLimit(
|
||||
module=k.split(".")[-1],
|
||||
module_path=k,
|
||||
limit_type=limit_type,
|
||||
plugin=module2plugin.get(k),
|
||||
cd=getattr(limit, "cd", None),
|
||||
max_count=getattr(limit, "max_count", None),
|
||||
status=limit.status,
|
||||
check_type=limit.check_type,
|
||||
watch_type=limit.watch_type,
|
||||
result=limit.result,
|
||||
),
|
||||
True,
|
||||
)
|
||||
db_data = self.__replace_data(db_data, limit)
|
||||
if limit_type == PluginLimitType.CD:
|
||||
db_data.cd = limit.cd # type: ignore
|
||||
if limit_type == PluginLimitType.COUNT:
|
||||
db_data.max_count = limit.max_count # type: ignore
|
||||
return db_data, False
|
||||
|
||||
def __get_file_data(self, limit_type: PluginLimitType) -> dict:
|
||||
"""获取文件数据
|
||||
|
||||
参数:
|
||||
limit_type: 限制类型
|
||||
|
||||
返回:
|
||||
dict: 文件数据
|
||||
"""
|
||||
if limit_type == PluginLimitType.CD:
|
||||
return self.cd_data
|
||||
elif limit_type == PluginLimitType.COUNT:
|
||||
return self.count_data
|
||||
else:
|
||||
return self.block_data
|
||||
|
||||
def __set_db_limits(
|
||||
self,
|
||||
db_limits: list[PluginLimit],
|
||||
module2plugin: dict[str, PluginInfo],
|
||||
limit_type: PluginLimitType,
|
||||
) -> tuple[list[PluginLimit], list[PluginLimit], list[int]]:
|
||||
"""更新cd限制数据
|
||||
|
||||
参数:
|
||||
db_limits: 数据库limits
|
||||
module2plugin: 模块:插件信息
|
||||
|
||||
返回:
|
||||
tuple[list[PluginLimit], list[PluginLimit]]: 创建列表,更新列表
|
||||
"""
|
||||
update_list = []
|
||||
create_list = []
|
||||
delete_list = []
|
||||
db_type_limits = [
|
||||
limit for limit in db_limits if limit.limit_type == limit_type
|
||||
]
|
||||
if data := self.__get_file_data(limit_type):
|
||||
db_type_limit_modules = [
|
||||
(limit.module_path, limit.id) for limit in db_type_limits
|
||||
]
|
||||
delete_list.extend(
|
||||
id
|
||||
for module_path, id in db_type_limit_modules
|
||||
if module_path not in data.keys()
|
||||
)
|
||||
for k, v in data.items():
|
||||
if not module2plugin.get(k):
|
||||
if k != "test":
|
||||
logger.warning(
|
||||
f"插件模块 {k} 未加载,已过滤当前 {v._type} 限制..."
|
||||
)
|
||||
continue
|
||||
db_data = [limit for limit in db_type_limits if limit.module_path == k]
|
||||
db_data, is_create = self.__set_data(
|
||||
k, db_data[0] if db_data else None, v, limit_type, module2plugin
|
||||
)
|
||||
if is_create:
|
||||
create_list.append(db_data)
|
||||
else:
|
||||
update_list.append(db_data)
|
||||
else:
|
||||
delete_list = [limit.id for limit in db_type_limits]
|
||||
return create_list, update_list, delete_list
|
||||
|
||||
async def __set_all_limit(
|
||||
self,
|
||||
) -> tuple[list[PluginLimit], list[PluginLimit], list[int]]:
|
||||
"""获取所有插件限制数据
|
||||
|
||||
返回:
|
||||
tuple[list[PluginLimit], list[PluginLimit]]: 创建列表,更新列表
|
||||
"""
|
||||
db_limits = await PluginLimit.all()
|
||||
modules = set(
|
||||
list(self.cd_data.keys())
|
||||
+ list(self.block_data.keys())
|
||||
+ list(self.count_data.keys())
|
||||
)
|
||||
plugins = await PluginInfo.get_plugins(module_path__in=modules)
|
||||
module2plugin = {p.module_path: p for p in plugins}
|
||||
create_list, update_list, delete_list = self.__set_db_limits(
|
||||
db_limits, module2plugin, PluginLimitType.CD
|
||||
)
|
||||
create_list1, update_list1, delete_list1 = self.__set_db_limits(
|
||||
db_limits, module2plugin, PluginLimitType.COUNT
|
||||
)
|
||||
create_list2, update_list2, delete_list2 = self.__set_db_limits(
|
||||
db_limits, module2plugin, PluginLimitType.BLOCK
|
||||
)
|
||||
all_create = create_list + create_list1 + create_list2
|
||||
all_update = update_list + update_list1 + update_list2
|
||||
all_delete = delete_list + delete_list1 + delete_list2
|
||||
return all_create, all_update, all_delete
|
||||
|
||||
async def load_to_db(self):
|
||||
"""读取配置文件"""
|
||||
|
||||
create_list, update_list, delete_list = await self.__set_all_limit()
|
||||
if create_list:
|
||||
await PluginLimit.bulk_create(create_list)
|
||||
if update_list:
|
||||
for limit in update_list:
|
||||
await limit.save(
|
||||
update_fields=[
|
||||
"status",
|
||||
"check_type",
|
||||
"watch_type",
|
||||
"result",
|
||||
"cd",
|
||||
"max_count",
|
||||
]
|
||||
)
|
||||
# TODO: tortoise.exceptions.OperationalError:syntax error at or near "GROUP"
|
||||
# await PluginLimit.bulk_update(
|
||||
# update_list,
|
||||
# ["status", "check_type", "watch_type", "result", "cd", "max_count"],
|
||||
# )
|
||||
if delete_list:
|
||||
await PluginLimit.filter(id__in=delete_list).delete()
|
||||
cnt = await PluginLimit.filter(status=True).count()
|
||||
logger.info(f"已经加载 {cnt} 个插件限制.")
|
||||
|
||||
|
||||
manager = Manager()
|
||||
@@ -0,0 +1,11 @@
|
||||
class ForceAddGroupError(Exception):
|
||||
"""
|
||||
强制拉群
|
||||
"""
|
||||
|
||||
def __init__(self, info: str):
|
||||
super().__init__(self)
|
||||
self._info = info
|
||||
|
||||
def get_info(self) -> str:
|
||||
return self._info
|
||||
@@ -105,7 +105,7 @@ add_group = on_request(priority=1, block=False)
|
||||
|
||||
@group_increase_handle.handle()
|
||||
async def _(bot: Bot, event: GroupIncreaseNoticeEvent | GroupMemberIncreaseEvent):
|
||||
superuser = BotConfig.get_superuser("qq")
|
||||
superusers = BotConfig.get_superuser("qq")
|
||||
user_id = str(event.user_id)
|
||||
group_id = str(event.group_id)
|
||||
if user_id == bot.self_id:
|
||||
@@ -123,10 +123,11 @@ async def _(bot: Bot, event: GroupIncreaseNoticeEvent | GroupMemberIncreaseEvent
|
||||
group_id=event.group_id, message=result_msg
|
||||
)
|
||||
await bot.set_group_leave(group_id=event.group_id)
|
||||
await bot.send_private_msg(
|
||||
user_id=int(superuser),
|
||||
message=f"触发强制入群保护,已成功退出群聊 {group_id}...",
|
||||
)
|
||||
if superusers:
|
||||
await bot.send_private_msg(
|
||||
user_id=int(superusers[0]),
|
||||
message=f"触发强制入群保护,已成功退出群聊 {group_id}...",
|
||||
)
|
||||
logger.info(
|
||||
"强制拉群或未有群信息,退出群聊成功",
|
||||
"入群检测",
|
||||
@@ -144,10 +145,12 @@ async def _(bot: Bot, event: GroupIncreaseNoticeEvent | GroupMemberIncreaseEvent
|
||||
group_id=event.group_id,
|
||||
e=e,
|
||||
)
|
||||
await bot.send_private_msg(
|
||||
user_id=int(superuser),
|
||||
message=f"触发强制入群保护,退出群聊 {event.group_id} 失败...",
|
||||
)
|
||||
if superusers:
|
||||
await bot.send_private_msg(
|
||||
user_id=int(superusers[0]),
|
||||
message="触发强制入群保护,"
|
||||
f"退出群聊 {event.group_id} 失败...",
|
||||
)
|
||||
await GroupConsole.filter(group_id=group_id).delete()
|
||||
else:
|
||||
"""允许群组并设置群认证,默认群功能开关"""
|
||||
@@ -274,8 +277,8 @@ async def _(bot: Bot, event: GroupDecreaseNoticeEvent | GroupMemberDecreaseEvent
|
||||
operator_name = "None"
|
||||
group = await GroupConsole.filter(group_id=str(group_id)).first()
|
||||
group_name = group.group_name if group else ""
|
||||
if superuser := BotConfig.get_superuser("qq"):
|
||||
coffee = int(superuser)
|
||||
if superusers := BotConfig.get_superuser("qq"):
|
||||
coffee = int(superusers[0])
|
||||
await bot.send_private_msg(
|
||||
user_id=coffee,
|
||||
message=f"****呜..一份踢出报告****\n"
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot import on_notice, on_request
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot.adapters.onebot.v11 import (
|
||||
GroupDecreaseNoticeEvent,
|
||||
GroupIncreaseNoticeEvent,
|
||||
)
|
||||
from nonebot.adapters.onebot.v12 import (
|
||||
GroupMemberDecreaseEvent,
|
||||
GroupMemberIncreaseEvent,
|
||||
)
|
||||
|
||||
from zhenxun.utils.enum import PluginType
|
||||
from zhenxun.utils.platform import PlatformUtils
|
||||
from zhenxun.utils.common_utils import CommonUtils
|
||||
from zhenxun.configs.config import Config, BotConfig
|
||||
from zhenxun.models.group_console import GroupConsole
|
||||
from zhenxun.configs.utils import Task, RegisterConfig, PluginExtraData
|
||||
|
||||
from .data_source import GroupManager
|
||||
from ..exception import ForceAddGroupError
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="QQ群事件处理",
|
||||
description="群事件处理",
|
||||
usage="",
|
||||
extra=PluginExtraData(
|
||||
author="HibiKier",
|
||||
version="0.1",
|
||||
plugin_type=PluginType.HIDDEN,
|
||||
configs=[
|
||||
RegisterConfig(
|
||||
module="invite_manager",
|
||||
key="message",
|
||||
value=f"请不要未经同意就拉{BotConfig.self_nickname}入群!告辞!",
|
||||
help="强制拉群后进群回复的内容",
|
||||
),
|
||||
RegisterConfig(
|
||||
module="invite_manager",
|
||||
key="flag",
|
||||
value=True,
|
||||
help="强制拉群后进群退出并回复内容",
|
||||
default_value=True,
|
||||
type=bool,
|
||||
),
|
||||
RegisterConfig(
|
||||
module="invite_manager",
|
||||
key="welcome_msg_cd",
|
||||
value=5,
|
||||
help="群欢迎消息cd",
|
||||
default_value=5,
|
||||
type=int,
|
||||
),
|
||||
RegisterConfig(
|
||||
module="_task",
|
||||
key="DEFAULT_GROUP_WELCOME",
|
||||
value=True,
|
||||
help="被动 进群欢迎 进群默认开关状态",
|
||||
default_value=True,
|
||||
type=bool,
|
||||
),
|
||||
RegisterConfig(
|
||||
module="_task",
|
||||
key="DEFAULT_REFUND_GROUP_REMIND",
|
||||
value=True,
|
||||
help="被动 退群提醒 进群默认开关状态",
|
||||
default_value=True,
|
||||
type=bool,
|
||||
),
|
||||
],
|
||||
tasks=[
|
||||
Task(module="group_welcome", name="进群欢迎"),
|
||||
Task(module="refund_group_remind", name="退群提醒"),
|
||||
],
|
||||
).dict(),
|
||||
)
|
||||
|
||||
|
||||
base_config = Config.get("invite_manager")
|
||||
|
||||
|
||||
limit_cd = base_config.get("welcome_msg_cd")
|
||||
|
||||
|
||||
group_increase_handle = on_notice(priority=1, block=False)
|
||||
"""群员增加处理"""
|
||||
group_decrease_handle = on_notice(priority=1, block=False)
|
||||
"""群员减少处理"""
|
||||
add_group = on_request(priority=1, block=False)
|
||||
"""加群同意请求"""
|
||||
|
||||
|
||||
@group_increase_handle.handle()
|
||||
async def _(bot: Bot, event: GroupIncreaseNoticeEvent | GroupMemberIncreaseEvent):
|
||||
user_id = str(event.user_id)
|
||||
group_id = str(event.group_id)
|
||||
if user_id == bot.self_id:
|
||||
"""新成员为bot本身"""
|
||||
group, _ = await GroupConsole.get_or_create(
|
||||
group_id=group_id, channel_id__isnull=True
|
||||
)
|
||||
if group.group_flag == 0:
|
||||
try:
|
||||
await GroupManager.add_bot(bot, str(event.operator_id), group_id, group)
|
||||
except ForceAddGroupError as e:
|
||||
await PlatformUtils.send_superuser(bot, e.get_info())
|
||||
else:
|
||||
await GroupManager.add_user(bot, user_id, group_id)
|
||||
|
||||
|
||||
@group_decrease_handle.handle()
|
||||
async def _(bot: Bot, event: GroupDecreaseNoticeEvent | GroupMemberDecreaseEvent):
|
||||
user_id = str(event.user_id)
|
||||
group_id = str(event.group_id)
|
||||
if event.sub_type == "kick_me":
|
||||
"""踢出Bot"""
|
||||
await GroupManager.kick_bot(bot, user_id, group_id)
|
||||
elif event.sub_type in ["leave", "kick"]:
|
||||
result = await GroupManager.run_user(
|
||||
bot, user_id, group_id, str(event.operator_id), event.sub_type
|
||||
)
|
||||
if result and not await CommonUtils.task_is_block(
|
||||
"refund_group_remind", group_id
|
||||
):
|
||||
await group_decrease_handle.send(result)
|
||||
@@ -0,0 +1,294 @@
|
||||
import os
|
||||
import re
|
||||
import random
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
import ujson as json
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot_plugin_alconna import At
|
||||
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.utils.utils import FreqLimiter
|
||||
from zhenxun.utils.message import MessageUtils
|
||||
from zhenxun.models.fg_request import FgRequest
|
||||
from zhenxun.models.level_user import LevelUser
|
||||
from zhenxun.utils.enum import RequestHandleType
|
||||
from zhenxun.utils.platform import PlatformUtils
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.utils.common_utils import CommonUtils
|
||||
from zhenxun.models.group_console import GroupConsole
|
||||
from zhenxun.models.group_member_info import GroupInfoUser
|
||||
from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH
|
||||
|
||||
from ..exception import ForceAddGroupError
|
||||
|
||||
base_config = Config.get("invite_manager")
|
||||
|
||||
limit_cd = base_config.get("welcome_msg_cd")
|
||||
|
||||
WELCOME_PATH = DATA_PATH / "welcome_message" / "qq"
|
||||
|
||||
DEFAULT_IMAGE_PATH = IMAGE_PATH / "qxz"
|
||||
|
||||
|
||||
class GroupManager:
|
||||
_flmt = FreqLimiter(limit_cd)
|
||||
|
||||
@classmethod
|
||||
async def __handle_add_group(
|
||||
cls, bot: Bot, group_id: str, group: GroupConsole | None
|
||||
):
|
||||
"""允许群组并设置群认证,默认群功能开关
|
||||
|
||||
参数:
|
||||
bot: Bot
|
||||
group_id: 群组id
|
||||
group: GroupConsole
|
||||
"""
|
||||
if group:
|
||||
await GroupConsole.filter(
|
||||
group_id=group_id, channel_id__isnull=True
|
||||
).update(group_flag=1)
|
||||
else:
|
||||
block_plugin = ""
|
||||
if plugin_list := await PluginInfo.filter(default_status=False).all():
|
||||
for plugin in plugin_list:
|
||||
block_plugin += f"{plugin.module},"
|
||||
group_info = await bot.get_group_info(group_id=group_id)
|
||||
await GroupConsole.create(
|
||||
group_id=group_info["group_id"],
|
||||
group_name=group_info["group_name"],
|
||||
max_member_count=group_info["max_member_count"],
|
||||
member_count=group_info["member_count"],
|
||||
group_flag=1,
|
||||
block_plugin=block_plugin,
|
||||
platform="qq",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def __refresh_level(cls, bot: Bot, group_id: str):
|
||||
"""刷新权限
|
||||
|
||||
参数:
|
||||
bot: Bot
|
||||
group_id: 群组id
|
||||
"""
|
||||
admin_default_auth = Config.get_config("admin_bot_manage", "ADMIN_DEFAULT_AUTH")
|
||||
member_list = await bot.get_group_member_list(group_id=group_id)
|
||||
member_id_list = [str(user_info["user_id"]) for user_info in member_list]
|
||||
flag2u = await LevelUser.filter(
|
||||
user_id__in=member_id_list, group_id=group_id, group_flag=1
|
||||
).values_list("user_id", flat=True)
|
||||
# 即刻刷新权限
|
||||
for user_info in member_list:
|
||||
user_id = user_info["user_id"]
|
||||
role = user_info["role"]
|
||||
if user_id in bot.config.superusers:
|
||||
await LevelUser.set_level(user_id, user_info["group_id"], 9)
|
||||
logger.debug(
|
||||
"添加超级用户权限: 9",
|
||||
"入群检测",
|
||||
session=user_id,
|
||||
group_id=user_info["group_id"],
|
||||
)
|
||||
elif (
|
||||
admin_default_auth is not None
|
||||
and role in ["owner", "admin"]
|
||||
and user_id not in flag2u
|
||||
):
|
||||
await LevelUser.set_level(
|
||||
user_id,
|
||||
user_info["group_id"],
|
||||
admin_default_auth,
|
||||
)
|
||||
logger.debug(
|
||||
f"添加默认群管理员权限: {admin_default_auth}",
|
||||
"入群检测",
|
||||
session=user_id,
|
||||
group_id=user_info["group_id"],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def add_bot(
|
||||
cls, bot: Bot, operator_id: str, group_id: str, group: GroupConsole | None
|
||||
):
|
||||
"""拉入bot
|
||||
|
||||
参数:
|
||||
bot: Bot
|
||||
operator_id: 操作者id
|
||||
group_id: 群组id
|
||||
group: GroupConsole
|
||||
"""
|
||||
if base_config.get("flag") and operator_id not in bot.config.superusers:
|
||||
"""退出群组"""
|
||||
try:
|
||||
if result_msg := base_config.get("message"):
|
||||
await bot.send_group_msg(group_id=int(group_id), message=result_msg)
|
||||
await bot.set_group_leave(group_id=int(group_id))
|
||||
logger.info(
|
||||
"强制拉群或未有群信息,退出群聊成功", "入群检测", group_id=group_id
|
||||
)
|
||||
await FgRequest.filter(
|
||||
group_id=group_id, handle_type__isnull=True
|
||||
).update(handle_type=RequestHandleType.IGNORE)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"强制拉群或未有群信息,退出群聊失败",
|
||||
"入群检测",
|
||||
group_id=group_id,
|
||||
e=e,
|
||||
)
|
||||
raise ForceAddGroupError("强制拉群或未有群信息,退出群聊失败...") from e
|
||||
await GroupConsole.filter(group_id=group_id).delete()
|
||||
raise ForceAddGroupError(f"触发强制入群保护,已成功退出群聊 {group_id}...")
|
||||
else:
|
||||
await cls.__handle_add_group(bot, group_id, group)
|
||||
"""刷新群管理员权限"""
|
||||
await cls.__refresh_level(bot, group_id)
|
||||
|
||||
@classmethod
|
||||
def __build_welcome_message(cls, user_id: str, path: Path) -> list[At | Path | str]:
|
||||
"""构造群欢迎消息
|
||||
|
||||
参数:
|
||||
user_id: 用户id
|
||||
path: 群欢迎消息存储路径
|
||||
|
||||
返回:
|
||||
list[At | Path | str]: 消息列表
|
||||
"""
|
||||
file = path / "text.json"
|
||||
data = json.load(file.open(encoding="utf-8"))
|
||||
message = data["message"]
|
||||
msg_split = re.split(r"\[image:\d+\]", message)
|
||||
msg_list = []
|
||||
if data["at"]:
|
||||
msg_list.append(At(flag="user", target=user_id))
|
||||
for i, text in enumerate(msg_split):
|
||||
msg_list.append(text)
|
||||
img_file = path / f"{i}.png"
|
||||
if img_file.exists():
|
||||
msg_list.append(img_file)
|
||||
return msg_list
|
||||
|
||||
@classmethod
|
||||
async def __send_welcome_message(cls, user_id: str, group_id: str):
|
||||
"""发送群欢迎消息
|
||||
|
||||
参数:
|
||||
user_id: 用户id
|
||||
group_id: 群组id
|
||||
"""
|
||||
cls._flmt.start_cd(group_id)
|
||||
path = WELCOME_PATH / f"{group_id}"
|
||||
file = path / "text.json"
|
||||
if file.exists():
|
||||
msg_list = cls.__build_welcome_message(user_id, path)
|
||||
logger.info("发送群欢迎消息...", "入群检测", group_id=group_id)
|
||||
if msg_list:
|
||||
await MessageUtils.build_message(msg_list).send() # type: ignore
|
||||
else:
|
||||
image = DEFAULT_IMAGE_PATH / random.choice(
|
||||
os.listdir(DEFAULT_IMAGE_PATH)
|
||||
)
|
||||
await MessageUtils.build_message(
|
||||
[
|
||||
"新人快跑啊!!本群现状↓(快使用自定义!)",
|
||||
image,
|
||||
]
|
||||
).send()
|
||||
|
||||
@classmethod
|
||||
async def add_user(cls, bot: Bot, user_id: str, group_id: str):
|
||||
"""拉入用户
|
||||
|
||||
参数:
|
||||
bot: Bot
|
||||
user_id: 用户id
|
||||
group_id: 群组id
|
||||
"""
|
||||
join_time = datetime.now()
|
||||
user_info = await bot.get_group_member_info(group_id=group_id, user_id=user_id)
|
||||
await GroupInfoUser.update_or_create(
|
||||
user_id=str(user_info["user_id"]),
|
||||
group_id=str(user_info["group_id"]),
|
||||
defaults={"user_name": user_info["nickname"], "user_join_time": join_time},
|
||||
)
|
||||
logger.info(f"用户{user_info['user_id']} 所属{user_info['group_id']} 更新成功")
|
||||
if not await CommonUtils.task_is_block(
|
||||
"group_welcome", group_id
|
||||
) and cls._flmt.check(group_id):
|
||||
await cls.__send_welcome_message(user_id, group_id)
|
||||
|
||||
@classmethod
|
||||
async def kick_bot(cls, bot: Bot, group_id: str, operator_id: str):
|
||||
"""踢出bot
|
||||
|
||||
参数:
|
||||
bot: Bot
|
||||
group_id: 群组id
|
||||
operator_id: 操作员id
|
||||
"""
|
||||
if user := await GroupInfoUser.get_or_none(
|
||||
user_id=operator_id, group_id=group_id
|
||||
):
|
||||
operator_name = user.user_name
|
||||
else:
|
||||
operator_name = "None"
|
||||
group = await GroupConsole.get_group(group_id)
|
||||
group_name = group.group_name if group else ""
|
||||
if group:
|
||||
await group.delete()
|
||||
await PlatformUtils.send_superuser(
|
||||
bot,
|
||||
f"****呜..一份踢出报告****\n"
|
||||
f"我被 {operator_name}({operator_id})\n"
|
||||
f"踢出了 {group_name}({group_id})\n"
|
||||
f"日期:{str(datetime.now()).split('.')[0]}",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def run_user(
|
||||
cls,
|
||||
bot: Bot,
|
||||
user_id: str,
|
||||
group_id: str,
|
||||
operator_id: str,
|
||||
sub_type: str,
|
||||
) -> str | None:
|
||||
"""踢出用户或用户离开
|
||||
|
||||
参数:
|
||||
bot: Bot
|
||||
user_id: 用户id
|
||||
group_id: 群组id
|
||||
operator_id: 操作员id
|
||||
sub_type: 类型
|
||||
|
||||
返回:
|
||||
str | None: 返回消息
|
||||
"""
|
||||
if user := await GroupInfoUser.get_or_none(user_id=user_id, group_id=group_id):
|
||||
user_name = user.user_name
|
||||
else:
|
||||
user_name = f"{user_id}"
|
||||
if user:
|
||||
await user.delete()
|
||||
logger.info(
|
||||
f"名称: {user_name} 退出群聊",
|
||||
"group_decrease_handle",
|
||||
session=user_id,
|
||||
group_id=group_id,
|
||||
)
|
||||
if sub_type == "kick":
|
||||
operator = await bot.get_group_member_info(
|
||||
user_id=int(operator_id), group_id=int(group_id)
|
||||
)
|
||||
operator_name = operator["card"] or operator["nickname"]
|
||||
return f"{user_name} 被 {operator_name} 送走了."
|
||||
elif sub_type == "leave":
|
||||
return f"{user_name}离开了我们..."
|
||||
return None
|
||||
@@ -1,12 +1,12 @@
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot_plugin_alconna import Alconna, Args, Subcommand, on_alconna
|
||||
from nonebot_plugin_session import EventSession
|
||||
from nonebot_plugin_alconna import Args, Alconna, Subcommand, on_alconna
|
||||
|
||||
from zhenxun.configs.utils import PluginExtraData
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import PluginType
|
||||
from zhenxun.utils.message import MessageUtils
|
||||
from zhenxun.configs.utils import PluginExtraData
|
||||
|
||||
from .data_source import ShopManage
|
||||
|
||||
@@ -68,6 +68,7 @@ _matcher.shortcut(
|
||||
prefix=True,
|
||||
)
|
||||
|
||||
|
||||
@_matcher.assign("$main")
|
||||
async def _(session: EventSession):
|
||||
try:
|
||||
@@ -82,9 +83,7 @@ async def _(session: EventSession):
|
||||
@_matcher.assign("add")
|
||||
async def _(session: EventSession, plugin_id: int):
|
||||
try:
|
||||
await MessageUtils.build_message(
|
||||
f"正在添加插件 Id: {plugin_id}"
|
||||
).send()
|
||||
await MessageUtils.build_message(f"正在添加插件 Id: {plugin_id}").send()
|
||||
result = await ShopManage.add_plugin(plugin_id)
|
||||
except Exception as e:
|
||||
logger.error(f"添加插件 Id: {plugin_id}失败", "插件商店", session=session, e=e)
|
||||
@@ -107,24 +106,29 @@ async def _(session: EventSession, plugin_id: int):
|
||||
logger.info(f"移除插件 Id: {plugin_id}", "插件商店", session=session)
|
||||
await MessageUtils.build_message(result).send()
|
||||
|
||||
|
||||
@_matcher.assign("search")
|
||||
async def _(session: EventSession, plugin_name_or_author: str):
|
||||
try:
|
||||
result = await ShopManage.search_plugin(plugin_name_or_author)
|
||||
except Exception as e:
|
||||
logger.error(f"搜索插件 name: {plugin_name_or_author}失败", "插件商店", session=session, e=e)
|
||||
logger.error(
|
||||
f"搜索插件 name: {plugin_name_or_author}失败",
|
||||
"插件商店",
|
||||
session=session,
|
||||
e=e,
|
||||
)
|
||||
await MessageUtils.build_message(
|
||||
f"搜索插件 name: {plugin_name_or_author} 失败 e: {e}"
|
||||
).finish()
|
||||
logger.info(f"搜索插件 name: {plugin_name_or_author}", "插件商店", session=session)
|
||||
await MessageUtils.build_message(result).send()
|
||||
|
||||
|
||||
@_matcher.assign("update")
|
||||
async def _(session: EventSession, plugin_id: int):
|
||||
try:
|
||||
await MessageUtils.build_message(
|
||||
f"正在更新插件 Id: {plugin_id}"
|
||||
).send()
|
||||
await MessageUtils.build_message(f"正在更新插件 Id: {plugin_id}").send()
|
||||
result = await ShopManage.update_plugin(plugin_id)
|
||||
except Exception as e:
|
||||
logger.error(f"更新插件 Id: {plugin_id}失败", "插件商店", session=session, e=e)
|
||||
|
||||
@@ -4,16 +4,8 @@ BASE_PATH = Path() / "zhenxun"
|
||||
BASE_PATH.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
CONFIG_URL = "https://cdn.jsdelivr.net/gh/zhenxun-org/zhenxun_bot_plugins/plugins.json"
|
||||
"""插件信息文件"""
|
||||
DEFAULT_GITHUB_URL = "https://github.com/zhenxun-org/zhenxun_bot_plugins/tree/main"
|
||||
"""伴生插件github仓库地址"""
|
||||
|
||||
CONFIG_INDEX_URL = "https://raw.githubusercontent.com/zhenxun-org/zhenxun_bot_plugins_index/index/plugins.json"
|
||||
"""插件索引库信息文件"""
|
||||
|
||||
CONFIG_INDEX_CDN_URL = "https://cdn.jsdelivr.net/gh/zhenxun-org/zhenxun_bot_plugins_index@index/plugins.json"
|
||||
"""插件索引库信息文件cdn"""
|
||||
|
||||
DOWNLOAD_URL = (
|
||||
"https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/contents/{}?ref=main"
|
||||
)
|
||||
"""插件下载地址"""
|
||||
EXTRA_GITHUB_URL = "https://github.com/zhenxun-org/zhenxun_bot_plugins_index/tree/index"
|
||||
"""插件库索引github仓库地址"""
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import aiofiles
|
||||
import ujson as json
|
||||
from aiocache import cached
|
||||
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.http_utils import AsyncHttpx
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.utils.github_utils import GithubUtils
|
||||
from zhenxun.utils.github_utils.models import RepoAPI
|
||||
from zhenxun.services.plugin_init import PluginInitManager
|
||||
from zhenxun.builtin_plugins.plugin_store.models import StorePluginInfo
|
||||
from zhenxun.utils.image_utils import RowStyle, BuildImage, ImageTemplate
|
||||
from zhenxun.builtin_plugins.auto_update.config import REQ_TXT_FILE_STRING
|
||||
|
||||
from .config import (
|
||||
BASE_PATH,
|
||||
CONFIG_URL,
|
||||
DOWNLOAD_URL,
|
||||
CONFIG_INDEX_URL,
|
||||
CONFIG_INDEX_CDN_URL,
|
||||
)
|
||||
from .config import BASE_PATH, EXTRA_GITHUB_URL, DEFAULT_GITHUB_URL
|
||||
|
||||
|
||||
def row_style(column: str, text: str) -> RowStyle:
|
||||
@@ -36,68 +34,6 @@ def row_style(column: str, text: str) -> RowStyle:
|
||||
return style
|
||||
|
||||
|
||||
async def recurrence_get_url(
|
||||
url: str,
|
||||
data_list: list[tuple[str, str]],
|
||||
ignore_list: list[str] | None = None,
|
||||
api_url: str | None = None,
|
||||
):
|
||||
"""递归获取目录下所有文件
|
||||
|
||||
参数:
|
||||
url: 信息url
|
||||
data_list: 数据列表
|
||||
|
||||
异常:
|
||||
ValueError: 访问错误
|
||||
"""
|
||||
if ignore_list is None:
|
||||
ignore_list = []
|
||||
logger.debug(f"访问插件下载信息 URL: {url}", "插件管理")
|
||||
res = await AsyncHttpx.get(url)
|
||||
if res.status_code != 200:
|
||||
raise ValueError(f"访问错误, code: {res.status_code}")
|
||||
json_data = res.json()
|
||||
if isinstance(json_data, list):
|
||||
data_list.extend((v.get("download_url"), v["path"]) for v in json_data)
|
||||
else:
|
||||
data_list.append((json_data.get("download_url"), json_data["path"]))
|
||||
for download_url, path in data_list:
|
||||
if not download_url:
|
||||
_url = api_url + path if api_url else DOWNLOAD_URL.format(path)
|
||||
if _url not in ignore_list:
|
||||
ignore_list.append(_url)
|
||||
await recurrence_get_url(_url, data_list, ignore_list, api_url)
|
||||
|
||||
|
||||
async def download_file(url: str, _is: bool = False, api_url: str | None = None):
|
||||
"""下载文件
|
||||
|
||||
参数:
|
||||
url: 插件详情url
|
||||
_is: 是否为第三方插件
|
||||
url_start : 第三方插件url
|
||||
|
||||
异常:
|
||||
ValueError: 下载失败
|
||||
"""
|
||||
data_list = []
|
||||
await recurrence_get_url(url, data_list, api_url=api_url)
|
||||
for download_url, path in data_list:
|
||||
if download_url and "." in path:
|
||||
logger.debug(f"下载文件: {path}", "插件管理")
|
||||
base_path = "zhenxun/plugins/" if _is else "zhenxun/"
|
||||
file = Path(f"{base_path}{path}")
|
||||
file.parent.mkdir(parents=True, exist_ok=True)
|
||||
r = await AsyncHttpx.get(download_url)
|
||||
if r.status_code != 200:
|
||||
raise ValueError(f"文件下载错误, code: {r.status_code}")
|
||||
content = r.text.replace("\r\n", "\n") # 统一换行符为 UNIX 风格
|
||||
async with aiofiles.open(file, "w", encoding="utf8") as f:
|
||||
logger.debug(f"写入文件: {file}", "插件管理")
|
||||
await f.write(content)
|
||||
|
||||
|
||||
def install_requirement(plugin_path: Path):
|
||||
requirement_files = ["requirement.txt", "requirements.txt"]
|
||||
requirement_paths = [plugin_path / file for file in requirement_files]
|
||||
@@ -132,17 +68,9 @@ def install_requirement(plugin_path: Path):
|
||||
|
||||
|
||||
class ShopManage:
|
||||
type2name = { # noqa: RUF012
|
||||
"NORMAL": "普通插件",
|
||||
"ADMIN": "管理员插件",
|
||||
"SUPERUSER": "超级用户插件",
|
||||
"ADMIN_SUPERUSER": "管理员/超级用户插件",
|
||||
"DEPENDANT": "依赖插件",
|
||||
"HIDDEN": "其他插件",
|
||||
}
|
||||
|
||||
@classmethod
|
||||
async def __get_data(cls) -> dict:
|
||||
@cached(60)
|
||||
async def get_data(cls) -> dict[str, StorePluginInfo]:
|
||||
"""获取插件信息数据
|
||||
|
||||
异常:
|
||||
@@ -151,12 +79,14 @@ class ShopManage:
|
||||
返回:
|
||||
dict: 插件信息数据
|
||||
"""
|
||||
res = await AsyncHttpx.get(CONFIG_URL)
|
||||
res2 = await AsyncHttpx.get(CONFIG_INDEX_URL)
|
||||
|
||||
if res2.status_code != 200:
|
||||
logger.info("访问第三方插件信息文件失败,改为进行cdn访问")
|
||||
res2 = await AsyncHttpx.get(CONFIG_INDEX_CDN_URL)
|
||||
default_github_url = await GithubUtils.parse_github_url(
|
||||
DEFAULT_GITHUB_URL
|
||||
).get_raw_download_urls("plugins.json")
|
||||
extra_github_url = await GithubUtils.parse_github_url(
|
||||
EXTRA_GITHUB_URL
|
||||
).get_raw_download_urls("plugins.json")
|
||||
res = await AsyncHttpx.get(default_github_url)
|
||||
res2 = await AsyncHttpx.get(extra_github_url)
|
||||
|
||||
# 检查请求结果
|
||||
if res.status_code != 200 or res2.status_code != 200:
|
||||
@@ -165,36 +95,53 @@ class ShopManage:
|
||||
# 解析并合并返回的 JSON 数据
|
||||
data1 = json.loads(res.text)
|
||||
data2 = json.loads(res2.text)
|
||||
return {**data1, **data2}
|
||||
return {
|
||||
name: StorePluginInfo(**detail)
|
||||
for name, detail in {**data1, **data2}.items()
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def version_check(cls, plugin_info: dict, suc_plugin: dict[str, str]):
|
||||
module = plugin_info["module"]
|
||||
if module in suc_plugin and plugin_info["version"] != suc_plugin[module]:
|
||||
return f"{suc_plugin[module]} (有更新->{plugin_info['version']})"
|
||||
return plugin_info["version"]
|
||||
def version_check(cls, plugin_info: StorePluginInfo, suc_plugin: dict[str, str]):
|
||||
"""版本检查
|
||||
|
||||
参数:
|
||||
plugin_info: StorePluginInfo
|
||||
suc_plugin: dict[str, str]
|
||||
|
||||
返回:
|
||||
str: 版本号
|
||||
"""
|
||||
module = plugin_info.module
|
||||
if suc_plugin.get(module) and not cls.check_version_is_new(
|
||||
plugin_info, suc_plugin
|
||||
):
|
||||
return f"{suc_plugin[module]} (有更新->{plugin_info.version})"
|
||||
return plugin_info.version
|
||||
|
||||
@classmethod
|
||||
def get_url_path(cls, module_path: str, is_dir: bool) -> str:
|
||||
url_path = None
|
||||
path = BASE_PATH
|
||||
module_path_split = module_path.split(".")
|
||||
if len(module_path_split) == 2:
|
||||
"""单个文件或文件夹"""
|
||||
if is_dir:
|
||||
url_path = "/".join(module_path_split)
|
||||
else:
|
||||
url_path = "/".join(module_path_split) + ".py"
|
||||
else:
|
||||
"""嵌套文件或文件夹"""
|
||||
for p in module_path_split[:-1]:
|
||||
path = path / p
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
if is_dir:
|
||||
url_path = f"{'/'.join(module_path_split)}"
|
||||
else:
|
||||
url_path = f"{'/'.join(module_path_split)}.py"
|
||||
return url_path
|
||||
def check_version_is_new(
|
||||
cls, plugin_info: StorePluginInfo, suc_plugin: dict[str, str]
|
||||
):
|
||||
"""检查版本是否有更新
|
||||
|
||||
参数:
|
||||
plugin_info: StorePluginInfo
|
||||
suc_plugin: dict[str, str]
|
||||
|
||||
返回:
|
||||
bool: 是否有更新
|
||||
"""
|
||||
module = plugin_info.module
|
||||
return suc_plugin.get(module) and plugin_info.version == suc_plugin[module]
|
||||
|
||||
@classmethod
|
||||
async def get_loaded_plugins(cls, *args) -> list[tuple[str, str]]:
|
||||
"""获取已加载的插件
|
||||
|
||||
返回:
|
||||
list[str]: 已加载的插件
|
||||
"""
|
||||
return await PluginInfo.filter(load_status=True).values_list(*args)
|
||||
|
||||
@classmethod
|
||||
async def get_plugins_info(cls) -> BuildImage | str:
|
||||
@@ -203,30 +150,25 @@ class ShopManage:
|
||||
返回:
|
||||
BuildImage | str: 返回消息
|
||||
"""
|
||||
data: dict = await cls.__get_data()
|
||||
data: dict[str, StorePluginInfo] = await cls.get_data()
|
||||
column_name = ["-", "ID", "名称", "简介", "作者", "版本", "类型"]
|
||||
for k in data.copy():
|
||||
if data[k]["plugin_type"]:
|
||||
data[k]["plugin_type"] = cls.type2name[data[k]["plugin_type"]]
|
||||
plugin_list = await PluginInfo.filter(load_status=True).values_list(
|
||||
"module", "version"
|
||||
)
|
||||
suc_plugin = {p[0]: p[1] for p in plugin_list if p[1]}
|
||||
plugin_list = await cls.get_loaded_plugins("module", "version")
|
||||
suc_plugin = {p[0]: (p[1] or "0.1") for p in plugin_list}
|
||||
data_list = [
|
||||
[
|
||||
"已安装" if plugin_info[1]["module"] in suc_plugin else "",
|
||||
"已安装" if plugin_info[1].module in suc_plugin else "",
|
||||
id,
|
||||
plugin_info[0],
|
||||
plugin_info[1]["description"],
|
||||
plugin_info[1]["author"],
|
||||
plugin_info[1].description,
|
||||
plugin_info[1].author,
|
||||
cls.version_check(plugin_info[1], suc_plugin),
|
||||
plugin_info[1]["plugin_type"],
|
||||
plugin_info[1].plugin_type_name,
|
||||
]
|
||||
for id, plugin_info in enumerate(data.items())
|
||||
]
|
||||
return await ImageTemplate.table_page(
|
||||
"插件列表",
|
||||
"通过安装/卸载插件 ID 来管理插件",
|
||||
"通过添加/移除插件 ID 来管理插件",
|
||||
column_name,
|
||||
data_list,
|
||||
text_style=row_style,
|
||||
@@ -242,57 +184,86 @@ class ShopManage:
|
||||
返回:
|
||||
str: 返回消息
|
||||
"""
|
||||
data: dict = await cls.__get_data()
|
||||
data: dict[str, StorePluginInfo] = await cls.get_data()
|
||||
if plugin_id < 0 or plugin_id >= len(data):
|
||||
return "插件ID不存在..."
|
||||
plugin_key = list(data.keys())[plugin_id]
|
||||
plugin_list = await cls.get_loaded_plugins("module")
|
||||
plugin_info = data[plugin_key]
|
||||
module_path_split = plugin_info["module_path"].split(".")
|
||||
url_path = cls.get_url_path(plugin_info["module_path"], plugin_info["is_dir"])
|
||||
if not url_path and plugin_info["module_path"]:
|
||||
return "插件下载地址构建失败..."
|
||||
logger.debug(f"尝试下载插件 URL: {url_path}", "插件管理")
|
||||
github_url = plugin_info.get("github_url")
|
||||
if github_url:
|
||||
if not (r := re.search(r"github\.com/([^/]+/[^/]+)", github_url)):
|
||||
return "github地址格式错误"
|
||||
github_path = r[1]
|
||||
api_url = f"https://api.github.com/repos/{github_path}/contents/"
|
||||
download_url = f"{api_url}{url_path}?ref=main"
|
||||
else:
|
||||
download_url = DOWNLOAD_URL.format(url_path)
|
||||
api_url = None
|
||||
|
||||
await download_file(download_url, bool(github_url), api_url)
|
||||
|
||||
# 安装依赖
|
||||
plugin_path = BASE_PATH / "/".join(module_path_split)
|
||||
if url_path and github_url and api_url:
|
||||
plugin_path = BASE_PATH / "plugins" / "/".join(module_path_split)
|
||||
res = await AsyncHttpx.get(api_url)
|
||||
if res.status_code != 200:
|
||||
return f"访问错误, code: {res.status_code}"
|
||||
json_data = res.json()
|
||||
if requirement_file := next(
|
||||
(
|
||||
v
|
||||
for v in json_data
|
||||
if v["name"] in ["requirements.txt", "requirement.txt"]
|
||||
),
|
||||
None,
|
||||
):
|
||||
r = await AsyncHttpx.get(requirement_file.get("download_url"))
|
||||
if r.status_code != 200:
|
||||
raise ValueError(f"文件下载错误, code: {r.status_code}")
|
||||
requirement_path = plugin_path / requirement_file["name"]
|
||||
async with aiofiles.open(requirement_path, "w", encoding="utf8") as f:
|
||||
logger.debug(f"写入文件: {requirement_path}", "插件管理")
|
||||
await f.write(r.text)
|
||||
|
||||
install_requirement(plugin_path)
|
||||
|
||||
if plugin_info.module in [p[0] for p in plugin_list]:
|
||||
return f"插件 {plugin_key} 已安装,无需重复安装"
|
||||
is_external = True
|
||||
if plugin_info.github_url is None:
|
||||
plugin_info.github_url = DEFAULT_GITHUB_URL
|
||||
is_external = False
|
||||
version_split = plugin_info.version.split("-")
|
||||
if len(version_split) > 1:
|
||||
github_url_split = plugin_info.github_url.split("/tree/")
|
||||
plugin_info.github_url = f"{github_url_split[0]}/tree/{version_split[1]}"
|
||||
logger.info(f"正在安装插件 {plugin_key}...")
|
||||
await cls.install_plugin_with_repo(
|
||||
plugin_info.github_url,
|
||||
plugin_info.module_path,
|
||||
plugin_info.is_dir,
|
||||
is_external,
|
||||
)
|
||||
return f"插件 {plugin_key} 安装成功! 重启后生效"
|
||||
|
||||
@classmethod
|
||||
async def install_plugin_with_repo(
|
||||
cls, github_url: str, module_path: str, is_dir: bool, is_external: bool = False
|
||||
):
|
||||
files: list[str]
|
||||
repo_api: RepoAPI
|
||||
repo_info = GithubUtils.parse_github_url(github_url)
|
||||
logger.debug(f"成功获取仓库信息: {repo_info}", "插件管理")
|
||||
for repo_api in GithubUtils.iter_api_strategies():
|
||||
try:
|
||||
await repo_api.parse_repo_info(repo_info)
|
||||
break
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"获取插件文件失败: {e} | API类型: {repo_api.strategy}", "插件管理"
|
||||
)
|
||||
continue
|
||||
else:
|
||||
raise ValueError("所有API获取插件文件失败,请检查网络连接")
|
||||
files = repo_api.get_files(
|
||||
module_path=module_path.replace(".", "/") + ("" if is_dir else ".py"),
|
||||
is_dir=is_dir,
|
||||
)
|
||||
download_urls = [await repo_info.get_raw_download_urls(file) for file in files]
|
||||
base_path = BASE_PATH / "plugins" if is_external else BASE_PATH
|
||||
download_paths: list[Path | str] = [base_path / file for file in files]
|
||||
logger.debug(f"插件下载路径: {download_paths}", "插件管理")
|
||||
result = await AsyncHttpx.gather_download_file(download_urls, download_paths)
|
||||
for _id, success in enumerate(result):
|
||||
if not success:
|
||||
break
|
||||
else:
|
||||
# 安装依赖
|
||||
plugin_path = base_path / "/".join(module_path.split("."))
|
||||
req_files = repo_api.get_files(REQ_TXT_FILE_STRING, False)
|
||||
req_files.extend(repo_api.get_files("requirement.txt", False))
|
||||
logger.debug(f"获取插件依赖文件列表: {req_files}", "插件管理")
|
||||
req_download_urls = [
|
||||
await repo_info.get_raw_download_urls(file) for file in req_files
|
||||
]
|
||||
req_paths: list[Path | str] = [plugin_path / file for file in req_files]
|
||||
logger.debug(f"插件依赖文件下载路径: {req_paths}", "插件管理")
|
||||
if req_files:
|
||||
result = await AsyncHttpx.gather_download_file(
|
||||
req_download_urls, req_paths
|
||||
)
|
||||
for _id, success in enumerate(result):
|
||||
if not success:
|
||||
raise Exception("插件依赖文件下载失败")
|
||||
else:
|
||||
logger.debug(f"插件依赖文件列表: {req_paths}", "插件管理")
|
||||
install_requirement(plugin_path)
|
||||
return True
|
||||
raise Exception("插件下载失败")
|
||||
|
||||
@classmethod
|
||||
async def remove_plugin(cls, plugin_id: int) -> str:
|
||||
"""移除插件
|
||||
@@ -303,25 +274,26 @@ class ShopManage:
|
||||
返回:
|
||||
str: 返回消息
|
||||
"""
|
||||
data: dict = await cls.__get_data()
|
||||
data: dict[str, StorePluginInfo] = await cls.get_data()
|
||||
if plugin_id < 0 or plugin_id >= len(data):
|
||||
return "插件ID不存在..."
|
||||
plugin_key = list(data.keys())[plugin_id]
|
||||
plugin_info = data[plugin_key]
|
||||
plugin_info = data[plugin_key] # type: ignore
|
||||
path = BASE_PATH
|
||||
if plugin_info.get("github_url"):
|
||||
if plugin_info.github_url:
|
||||
path = BASE_PATH / "plugins"
|
||||
for p in plugin_info["module_path"].split("."):
|
||||
for p in plugin_info.module_path.split("."):
|
||||
path = path / p
|
||||
if not plugin_info["is_dir"]:
|
||||
if not plugin_info.is_dir:
|
||||
path = Path(f"{path}.py")
|
||||
if not path.exists():
|
||||
return f"插件 {plugin_key} 不存在..."
|
||||
logger.debug(f"尝试移除插件 {plugin_key} 文件: {path}", "插件管理")
|
||||
if plugin_info["is_dir"]:
|
||||
if plugin_info.is_dir:
|
||||
shutil.rmtree(path)
|
||||
else:
|
||||
path.unlink()
|
||||
await PluginInitManager.remove(f"zhenxun.{plugin_info.module_path}")
|
||||
return f"插件 {plugin_key} 移除成功! 重启后生效"
|
||||
|
||||
@classmethod
|
||||
@@ -334,30 +306,25 @@ class ShopManage:
|
||||
返回:
|
||||
BuildImage | str: 返回消息
|
||||
"""
|
||||
data: dict = await cls.__get_data()
|
||||
for k in data.copy():
|
||||
if data[k]["plugin_type"]:
|
||||
data[k]["plugin_type"] = cls.type2name[data[k]["plugin_type"]]
|
||||
plugin_list = await PluginInfo.filter(load_status=True).values_list(
|
||||
"module", "version"
|
||||
)
|
||||
suc_plugin = {p[0]: p[1] for p in plugin_list if p[1]}
|
||||
data: dict[str, StorePluginInfo] = await cls.get_data()
|
||||
plugin_list = await cls.get_loaded_plugins("module", "version")
|
||||
suc_plugin = {p[0]: (p[1] or "Unknown") for p in plugin_list}
|
||||
filtered_data = [
|
||||
(id, plugin_info)
|
||||
for id, plugin_info in enumerate(data.items())
|
||||
if plugin_name_or_author.lower() in plugin_info[0].lower()
|
||||
or plugin_name_or_author.lower() in plugin_info[1]["author"].lower()
|
||||
or plugin_name_or_author.lower() in plugin_info[1].author.lower()
|
||||
]
|
||||
|
||||
data_list = [
|
||||
[
|
||||
"已安装" if plugin_info[1]["module"] in suc_plugin else "",
|
||||
"已安装" if plugin_info[1].module in suc_plugin else "",
|
||||
id,
|
||||
plugin_info[0],
|
||||
plugin_info[1]["description"],
|
||||
plugin_info[1]["author"],
|
||||
plugin_info[1].description,
|
||||
plugin_info[1].author,
|
||||
cls.version_check(plugin_info[1], suc_plugin),
|
||||
plugin_info[1]["plugin_type"],
|
||||
plugin_info[1].plugin_type_name,
|
||||
]
|
||||
for id, plugin_info in filtered_data
|
||||
]
|
||||
@@ -382,53 +349,27 @@ class ShopManage:
|
||||
返回:
|
||||
str: 返回消息
|
||||
"""
|
||||
data: dict = await cls.__get_data()
|
||||
data: dict[str, StorePluginInfo] = await cls.get_data()
|
||||
if plugin_id < 0 or plugin_id >= len(data):
|
||||
return "插件ID不存在..."
|
||||
plugin_key = list(data.keys())[plugin_id]
|
||||
logger.info(f"尝试更新插件 {plugin_key}", "插件管理")
|
||||
plugin_info = data[plugin_key]
|
||||
module_path_split = plugin_info["module_path"].split(".")
|
||||
url_path = cls.get_url_path(plugin_info["module_path"], plugin_info["is_dir"])
|
||||
if not url_path and plugin_info["module_path"]:
|
||||
return "插件下载地址构建失败..."
|
||||
logger.debug(f"尝试下载插件 URL: {url_path}", "插件管理")
|
||||
github_url = plugin_info.get("github_url")
|
||||
if github_url:
|
||||
if not (r := re.search(r"github\.com/([^/]+/[^/]+)", github_url)):
|
||||
return "github地址格式错误..."
|
||||
github_path = r[1]
|
||||
api_url = f"https://api.github.com/repos/{github_path}/contents/"
|
||||
download_url = f"{api_url}{url_path}?ref=main"
|
||||
else:
|
||||
download_url = DOWNLOAD_URL.format(url_path)
|
||||
api_url = None
|
||||
|
||||
await download_file(download_url, bool(github_url), api_url)
|
||||
|
||||
# 安装依赖
|
||||
plugin_path = BASE_PATH / "/".join(module_path_split)
|
||||
if url_path and github_url and api_url:
|
||||
plugin_path = BASE_PATH / "plugins" / "/".join(module_path_split)
|
||||
res = await AsyncHttpx.get(api_url)
|
||||
if res.status_code != 200:
|
||||
return f"访问错误, code: {res.status_code}"
|
||||
json_data = res.json()
|
||||
if requirement_file := next(
|
||||
(
|
||||
v
|
||||
for v in json_data
|
||||
if v["name"] in ["requirements.txt", "requirement.txt"]
|
||||
),
|
||||
None,
|
||||
):
|
||||
r = await AsyncHttpx.get(requirement_file.get("download_url"))
|
||||
if r.status_code != 200:
|
||||
raise ValueError(f"文件下载错误, code: {r.status_code}")
|
||||
requirement_path = plugin_path / requirement_file["name"]
|
||||
async with aiofiles.open(requirement_path, "w", encoding="utf8") as f:
|
||||
logger.debug(f"写入文件: {requirement_path}", "插件管理")
|
||||
await f.write(r.text)
|
||||
|
||||
install_requirement(plugin_path)
|
||||
|
||||
plugin_list = await cls.get_loaded_plugins("module", "version")
|
||||
suc_plugin = {p[0]: (p[1] or "Unknown") for p in plugin_list}
|
||||
if plugin_info.module not in [p[0] for p in plugin_list]:
|
||||
return f"插件 {plugin_key} 未安装,无法更新"
|
||||
logger.debug(f"当前插件列表: {suc_plugin}", "插件管理")
|
||||
if cls.check_version_is_new(plugin_info, suc_plugin):
|
||||
return f"插件 {plugin_key} 已是最新版本"
|
||||
is_external = True
|
||||
if plugin_info.github_url is None:
|
||||
plugin_info.github_url = DEFAULT_GITHUB_URL
|
||||
is_external = False
|
||||
await cls.install_plugin_with_repo(
|
||||
plugin_info.github_url,
|
||||
plugin_info.module_path,
|
||||
plugin_info.is_dir,
|
||||
is_external,
|
||||
)
|
||||
return f"插件 {plugin_key} 更新成功! 重启后生效"
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
from zhenxun.utils.enum import PluginType
|
||||
|
||||
type2name: dict[str, str] = {
|
||||
"NORMAL": "普通插件",
|
||||
"ADMIN": "管理员插件",
|
||||
"SUPERUSER": "超级用户插件",
|
||||
"ADMIN_SUPERUSER": "管理员/超级用户插件",
|
||||
"DEPENDANT": "依赖插件",
|
||||
"HIDDEN": "其他插件",
|
||||
}
|
||||
|
||||
|
||||
class StorePluginInfo(BaseModel):
|
||||
"""插件信息"""
|
||||
|
||||
module: str
|
||||
"""模块名"""
|
||||
module_path: str
|
||||
"""模块路径"""
|
||||
description: str
|
||||
"""简介"""
|
||||
usage: str
|
||||
"""用法"""
|
||||
author: str
|
||||
"""作者"""
|
||||
version: str
|
||||
"""版本"""
|
||||
plugin_type: PluginType
|
||||
"""插件类型"""
|
||||
is_dir: bool
|
||||
"""是否为文件夹插件"""
|
||||
github_url: str | None
|
||||
"""github链接"""
|
||||
|
||||
@property
|
||||
def plugin_type_name(self):
|
||||
return type2name[self.plugin_type.value]
|
||||
@@ -14,7 +14,6 @@ from nonebot.adapters.onebot.v11 import (
|
||||
)
|
||||
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.message import MessageUtils
|
||||
from zhenxun.models.fg_request import FgRequest
|
||||
from zhenxun.utils.platform import PlatformUtils
|
||||
from zhenxun.models.friend_user import FriendUser
|
||||
@@ -69,7 +68,6 @@ _t = on_message(priority=999, block=False, rule=lambda: False)
|
||||
|
||||
@friend_req.handle()
|
||||
async def _(bot: v12Bot | v11Bot, event: FriendRequestEvent, session: EventSession):
|
||||
superuser = BotConfig.get_superuser("qq")
|
||||
if event.user_id and Timer.check(event.user_id):
|
||||
logger.debug("收录好友请求...", "好友请求", target=event.user_id)
|
||||
user = await bot.get_stranger_info(user_id=event.user_id)
|
||||
@@ -77,14 +75,6 @@ async def _(bot: v12Bot | v11Bot, event: FriendRequestEvent, session: EventSessi
|
||||
# sex = user["sex"]
|
||||
# age = str(user["age"])
|
||||
comment = event.comment
|
||||
if superuser:
|
||||
await MessageUtils.build_message(
|
||||
f"*****一份好友申请*****\n"
|
||||
f"昵称:{nickname}({event.user_id})\n"
|
||||
f"自动同意:{'√' if base_config.get('AUTO_ADD_FRIEND') else '×'}\n"
|
||||
f"日期:{str(datetime.now()).split('.')[0]}\n"
|
||||
f"备注:{event.comment}"
|
||||
).send(target=PlatformUtils.get_target(bot, superuser))
|
||||
if base_config.get("AUTO_ADD_FRIEND"):
|
||||
logger.debug(
|
||||
"已开启好友请求自动同意,成功通过该请求",
|
||||
@@ -102,7 +92,7 @@ async def _(bot: v12Bot | v11Bot, event: FriendRequestEvent, session: EventSessi
|
||||
user_id=str(event.user_id),
|
||||
handle_type__isnull=True,
|
||||
).update(handle_type=RequestHandleType.EXPIRE)
|
||||
await FgRequest.create(
|
||||
f = await FgRequest.create(
|
||||
request_type=RequestType.FRIEND,
|
||||
platform=session.platform,
|
||||
bot_id=bot.self_id,
|
||||
@@ -111,97 +101,107 @@ async def _(bot: v12Bot | v11Bot, event: FriendRequestEvent, session: EventSessi
|
||||
nickname=nickname,
|
||||
comment=comment,
|
||||
)
|
||||
await PlatformUtils.send_superuser(
|
||||
bot,
|
||||
f"*****一份好友申请*****\n"
|
||||
f"ID: {f.id}"
|
||||
f"昵称:{nickname}({event.user_id})\n"
|
||||
f"自动同意:{'√' if base_config.get('AUTO_ADD_FRIEND') else '×'}\n"
|
||||
f"日期:{str(datetime.now()).split('.')[0]}\n"
|
||||
f"备注:{event.comment}",
|
||||
)
|
||||
else:
|
||||
logger.debug("好友请求五分钟内重复, 已忽略", "好友请求", target=event.user_id)
|
||||
|
||||
|
||||
@group_req.handle()
|
||||
async def _(bot: v12Bot | v11Bot, event: GroupRequestEvent, session: EventSession):
|
||||
superuser = BotConfig.get_superuser("qq")
|
||||
if event.sub_type == "invite":
|
||||
if str(event.user_id) in bot.config.superusers:
|
||||
try:
|
||||
logger.debug(
|
||||
"超级用户自动同意加入群聊",
|
||||
"群聊请求",
|
||||
session=event.user_id,
|
||||
target=event.group_id,
|
||||
)
|
||||
group, _ = await GroupConsole.update_or_create(
|
||||
group_id=str(event.group_id),
|
||||
defaults={
|
||||
"group_name": "",
|
||||
"max_member_count": 0,
|
||||
"member_count": 0,
|
||||
"group_flag": 1,
|
||||
},
|
||||
)
|
||||
await bot.set_group_add_request(
|
||||
flag=event.flag, sub_type="invite", approve=True
|
||||
)
|
||||
if isinstance(bot, v11Bot):
|
||||
group_info = await bot.get_group_info(group_id=event.group_id)
|
||||
max_member_count = group_info["max_member_count"]
|
||||
member_count = group_info["member_count"]
|
||||
else:
|
||||
group_info = await bot.get_group_info(group_id=str(event.group_id))
|
||||
max_member_count = 0
|
||||
member_count = 0
|
||||
group.max_member_count = max_member_count
|
||||
group.member_count = member_count
|
||||
group.group_name = group_info["group_name"]
|
||||
await group.save(
|
||||
update_fields=["group_name", "max_member_count", "member_count"]
|
||||
)
|
||||
except ActionFailed as e:
|
||||
logger.error(
|
||||
"超级用户自动同意加入群聊发生错误",
|
||||
"群聊请求",
|
||||
session=event.user_id,
|
||||
target=event.group_id,
|
||||
e=e,
|
||||
)
|
||||
elif Timer.check(f"{event.user_id}:{event.group_id}"):
|
||||
if event.sub_type != "invite":
|
||||
return
|
||||
if str(event.user_id) in bot.config.superusers:
|
||||
try:
|
||||
logger.debug(
|
||||
f"收录 用户[{event.user_id}] 群聊[{event.group_id}] 群聊请求",
|
||||
"超级用户自动同意加入群聊",
|
||||
"群聊请求",
|
||||
session=event.user_id,
|
||||
target=event.group_id,
|
||||
)
|
||||
nickname = await FriendUser.get_user_name(str(event.user_id))
|
||||
await PlatformUtils.send_superuser(
|
||||
bot,
|
||||
f"*****一份入群申请*****\n申请人:{nickname}({event.user_id})\n群聊:"
|
||||
f"{event.group_id}\n邀请日期:{datetime.now().replace(microsecond=0)}",
|
||||
superuser,
|
||||
)
|
||||
await bot.send_private_msg(
|
||||
user_id=event.user_id,
|
||||
message=f"想要邀请我偷偷入群嘛~已经提醒{BotConfig.self_nickname}的管理员大人了\n"
|
||||
"请确保已经群主或群管理沟通过!\n"
|
||||
"等待管理员处理吧!",
|
||||
)
|
||||
# 旧请求全部设置为过期
|
||||
await FgRequest.filter(
|
||||
request_type=RequestType.GROUP,
|
||||
user_id=str(event.user_id),
|
||||
group_id=str(event.group_id),
|
||||
handle_type__isnull=True,
|
||||
).update(handle_type=RequestHandleType.EXPIRE)
|
||||
await FgRequest.create(
|
||||
request_type=RequestType.GROUP,
|
||||
platform=session.platform,
|
||||
bot_id=bot.self_id,
|
||||
flag=event.flag,
|
||||
user_id=str(event.user_id),
|
||||
nickname=nickname,
|
||||
group, _ = await GroupConsole.update_or_create(
|
||||
group_id=str(event.group_id),
|
||||
defaults={
|
||||
"group_name": "",
|
||||
"max_member_count": 0,
|
||||
"member_count": 0,
|
||||
"group_flag": 1,
|
||||
},
|
||||
)
|
||||
else:
|
||||
logger.debug(
|
||||
"群聊请求五分钟内重复, 已忽略",
|
||||
await bot.set_group_add_request(
|
||||
flag=event.flag, sub_type="invite", approve=True
|
||||
)
|
||||
if isinstance(bot, v11Bot):
|
||||
group_info = await bot.get_group_info(group_id=event.group_id)
|
||||
max_member_count = group_info["max_member_count"]
|
||||
member_count = group_info["member_count"]
|
||||
else:
|
||||
group_info = await bot.get_group_info(group_id=str(event.group_id))
|
||||
max_member_count = 0
|
||||
member_count = 0
|
||||
group.max_member_count = max_member_count
|
||||
group.member_count = member_count
|
||||
group.group_name = group_info["group_name"]
|
||||
await group.save(
|
||||
update_fields=["group_name", "max_member_count", "member_count"]
|
||||
)
|
||||
except ActionFailed as e:
|
||||
logger.error(
|
||||
"超级用户自动同意加入群聊发生错误",
|
||||
"群聊请求",
|
||||
target=f"{event.user_id}:{event.group_id}",
|
||||
session=event.user_id,
|
||||
target=event.group_id,
|
||||
e=e,
|
||||
)
|
||||
elif Timer.check(f"{event.user_id}:{event.group_id}"):
|
||||
logger.debug(
|
||||
f"收录 用户[{event.user_id}] 群聊[{event.group_id}] 群聊请求",
|
||||
"群聊请求",
|
||||
target=event.group_id,
|
||||
)
|
||||
nickname = await FriendUser.get_user_name(str(event.user_id))
|
||||
await bot.send_private_msg(
|
||||
user_id=event.user_id,
|
||||
message=f"想要邀请我偷偷入群嘛~已经提醒{BotConfig.self_nickname}的管理员大人了\n"
|
||||
"请确保已经群主或群管理沟通过!\n"
|
||||
"等待管理员处理吧!",
|
||||
)
|
||||
# 旧请求全部设置为过期
|
||||
await FgRequest.filter(
|
||||
request_type=RequestType.GROUP,
|
||||
user_id=str(event.user_id),
|
||||
group_id=str(event.group_id),
|
||||
handle_type__isnull=True,
|
||||
).update(handle_type=RequestHandleType.EXPIRE)
|
||||
f = await FgRequest.create(
|
||||
request_type=RequestType.GROUP,
|
||||
platform=session.platform,
|
||||
bot_id=bot.self_id,
|
||||
flag=event.flag,
|
||||
user_id=str(event.user_id),
|
||||
nickname=nickname,
|
||||
group_id=str(event.group_id),
|
||||
)
|
||||
await PlatformUtils.send_superuser(
|
||||
bot,
|
||||
f"*****一份入群申请*****\n"
|
||||
f"ID:{f.id}\n"
|
||||
f"申请人:{nickname}({event.user_id})\n群聊:"
|
||||
f"{event.group_id}\n邀请日期:{datetime.now().replace(microsecond=0)}",
|
||||
)
|
||||
else:
|
||||
logger.debug(
|
||||
"群聊请求五分钟内重复, 已忽略",
|
||||
"群聊请求",
|
||||
target=f"{event.user_id}:{event.group_id}",
|
||||
)
|
||||
|
||||
|
||||
@scheduler.scheduled_job(
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import nonebot
|
||||
import pytz
|
||||
import nonebot
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.models.task_info import TaskInfo
|
||||
from zhenxun.utils.platform import PlatformUtils
|
||||
from zhenxun.models.chat_history import ChatHistory
|
||||
from zhenxun.models.group_console import GroupConsole
|
||||
from zhenxun.models.task_info import TaskInfo
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.platform import PlatformUtils
|
||||
|
||||
Config.add_plugin_config(
|
||||
"chat_check",
|
||||
"STATUS",
|
||||
True,
|
||||
help="是否开启群组两日内未发送任何消息,关闭该群全部被动",
|
||||
default_value=True,
|
||||
type=bool,
|
||||
)
|
||||
|
||||
|
||||
@scheduler.scheduled_job(
|
||||
@@ -17,15 +27,18 @@ from zhenxun.utils.platform import PlatformUtils
|
||||
minute=40,
|
||||
)
|
||||
async def _():
|
||||
if not Config.get_config("chat_history", "FLAG"):
|
||||
logger.debug("未开启历史发言记录,过滤群组发言检测...")
|
||||
return
|
||||
if not Config.get_config("chat_check", "STATUS"):
|
||||
logger.debug("未开启群组聊天时间检查,过滤群组发言检测...")
|
||||
return
|
||||
"""检测群组发言时间并禁用全部被动"""
|
||||
update_list = []
|
||||
if modules := await TaskInfo.annotate().values_list(
|
||||
"module", flat=True
|
||||
):
|
||||
if modules := await TaskInfo.annotate().values_list("module", flat=True):
|
||||
for bot in nonebot.get_bots().values():
|
||||
group_list, _ = await PlatformUtils.get_group_list(bot)
|
||||
group_list = [g for g in group_list if g.channel_id == None]
|
||||
|
||||
group_list = [g for g in group_list if g.channel_id is None]
|
||||
for group in group_list:
|
||||
try:
|
||||
last_message = (
|
||||
@@ -47,7 +60,7 @@ async def _():
|
||||
"Chat检测",
|
||||
target=_group.group_id,
|
||||
)
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
logger.error(
|
||||
"检测群组发言时间失败...", "Chat检测", target=group.group_id
|
||||
)
|
||||
|
||||
@@ -5,7 +5,6 @@ from nonebot_plugin_apscheduler import scheduler
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import PluginType
|
||||
from zhenxun.configs.config import BotConfig
|
||||
from zhenxun.models.task_info import TaskInfo
|
||||
from zhenxun.utils.message import MessageUtils
|
||||
from zhenxun.configs.path_config import IMAGE_PATH
|
||||
from zhenxun.utils.common_utils import CommonUtils
|
||||
@@ -27,16 +26,6 @@ __plugin_meta__ = PluginMetadata(
|
||||
driver = nonebot.get_driver()
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
async def _():
|
||||
if not await TaskInfo.exists(module="morning_goodnight"):
|
||||
await TaskInfo.create(
|
||||
module="morning_goodnight",
|
||||
name="早晚安",
|
||||
status=True,
|
||||
)
|
||||
|
||||
|
||||
async def check(group_id: str) -> bool:
|
||||
return not await CommonUtils.task_is_block("morning_goodnight", group_id)
|
||||
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
from nonebot.adapters import Bot, Event
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot_plugin_session import EventSession
|
||||
from nonebot_plugin_userinfo import UserInfo, EventUserInfo
|
||||
from nonebot_plugin_alconna import (
|
||||
Alconna,
|
||||
Args,
|
||||
Query,
|
||||
Option,
|
||||
UniMsg,
|
||||
Alconna,
|
||||
Arparma,
|
||||
Subcommand,
|
||||
UniMessage,
|
||||
UniMsg,
|
||||
AlconnaQuery,
|
||||
on_alconna,
|
||||
store_true,
|
||||
)
|
||||
from nonebot_plugin_session import EventSession
|
||||
from nonebot_plugin_userinfo import EventUserInfo, UserInfo
|
||||
|
||||
from zhenxun.configs.utils import BaseBlock, PluginExtraData
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import BlockType, PluginType
|
||||
from zhenxun.utils.exception import GoodsNotFound
|
||||
from zhenxun.utils.message import MessageUtils
|
||||
from zhenxun.utils.exception import GoodsNotFound
|
||||
from zhenxun.utils.enum import BlockType, PluginType
|
||||
from zhenxun.configs.utils import BaseBlock, PluginExtraData
|
||||
|
||||
from ._data_source import ShopManage
|
||||
from ._data_source import ShopManage, gold_rank
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="商店",
|
||||
@@ -30,6 +34,8 @@ __plugin_meta__ = PluginMetadata(
|
||||
我的道具
|
||||
使用道具 [名称/Id]
|
||||
购买道具 [名称/Id]
|
||||
金币排行 ?[num=10]
|
||||
金币总排行 ?[num=10]
|
||||
""".strip(),
|
||||
extra=PluginExtraData(
|
||||
author="HibiKier",
|
||||
@@ -44,10 +50,12 @@ __plugin_meta__ = PluginMetadata(
|
||||
_matcher = on_alconna(
|
||||
Alconna(
|
||||
"商店",
|
||||
Option("--all", action=store_true),
|
||||
Subcommand("my-cost", help_text="我的金币"),
|
||||
Subcommand("my-props", help_text="我的道具"),
|
||||
Subcommand("buy", Args["name", str]["num", int, 1], help_text="购买道具"),
|
||||
Subcommand("use", Args["name", str]["num?", int, 1], help_text="使用道具"),
|
||||
Subcommand("gold-list", Args["num?", int], help_text="金币排行"),
|
||||
),
|
||||
priority=5,
|
||||
block=True,
|
||||
@@ -81,6 +89,20 @@ _matcher.shortcut(
|
||||
prefix=True,
|
||||
)
|
||||
|
||||
_matcher.shortcut(
|
||||
"金币排行",
|
||||
command="商店",
|
||||
arguments=["gold-list"],
|
||||
prefix=True,
|
||||
)
|
||||
|
||||
_matcher.shortcut(
|
||||
r"金币总排行",
|
||||
command="商店",
|
||||
arguments=["--all", "gold-list"],
|
||||
prefix=True,
|
||||
)
|
||||
|
||||
|
||||
@_matcher.assign("$main")
|
||||
async def _(session: EventSession, arparma: Arparma):
|
||||
@@ -96,7 +118,7 @@ async def _(session: EventSession, arparma: Arparma):
|
||||
gold = await ShopManage.my_cost(session.id1, session.platform)
|
||||
await MessageUtils.build_message(f"你的当前余额: {gold}").send(reply_to=True)
|
||||
else:
|
||||
await MessageUtils.build_message(f"用户id为空...").send(reply_to=True)
|
||||
await MessageUtils.build_message("用户id为空...").send(reply_to=True)
|
||||
|
||||
|
||||
@_matcher.assign("my-props")
|
||||
@@ -111,11 +133,9 @@ async def _(
|
||||
session.platform,
|
||||
):
|
||||
await MessageUtils.build_message(image.pic2bytes()).finish(reply_to=True)
|
||||
return await MessageUtils.build_message(f"你的道具为空捏...").send(
|
||||
reply_to=True
|
||||
)
|
||||
return await MessageUtils.build_message("你的道具为空捏...").send(reply_to=True)
|
||||
else:
|
||||
await MessageUtils.build_message(f"用户id为空...").send(reply_to=True)
|
||||
await MessageUtils.build_message("用户id为空...").send(reply_to=True)
|
||||
|
||||
|
||||
@_matcher.assign("buy")
|
||||
@@ -129,7 +149,7 @@ async def _(session: EventSession, arparma: Arparma, name: str, num: int):
|
||||
result = await ShopManage.buy_prop(session.id1, name, num, session.platform)
|
||||
await MessageUtils.build_message(result).send(reply_to=True)
|
||||
else:
|
||||
await MessageUtils.build_message(f"用户id为空...").send(reply_to=True)
|
||||
await MessageUtils.build_message("用户id为空...").send(reply_to=True)
|
||||
|
||||
|
||||
@_matcher.assign("use")
|
||||
@@ -155,3 +175,28 @@ async def _(
|
||||
await MessageUtils.build_message(f"没有找到道具 {name} 或道具数量不足...").send(
|
||||
reply_to=True
|
||||
)
|
||||
|
||||
|
||||
@_matcher.assign("gold-list")
|
||||
async def _(
|
||||
session: EventSession, arparma: Arparma, num: Query[int] = AlconnaQuery("num", 10)
|
||||
):
|
||||
if num.result > 50:
|
||||
await MessageUtils.build_message("排行榜人数不能超过50哦...").finish()
|
||||
if session.id1:
|
||||
gid = session.id3 or session.id2
|
||||
if not arparma.find("all") and not gid:
|
||||
await MessageUtils.build_message(
|
||||
"私聊中无法查看 '金币排行',请发送 '金币总排行'"
|
||||
).finish()
|
||||
if arparma.find("all"):
|
||||
gid = None
|
||||
result = await gold_rank(session.id1, gid, num.result, session.platform)
|
||||
logger.info(
|
||||
"查看金币排行",
|
||||
arparma.header_result,
|
||||
session=session,
|
||||
)
|
||||
await MessageUtils.build_message(result).send(reply_to=True)
|
||||
else:
|
||||
await MessageUtils.build_message("用户id为空...").send(reply_to=True)
|
||||
|
||||
@@ -1,28 +1,40 @@
|
||||
import time
|
||||
import asyncio
|
||||
import inspect
|
||||
import time
|
||||
from typing import Any, Literal
|
||||
from types import MappingProxyType
|
||||
from typing import Any, Callable, Literal
|
||||
from collections.abc import Callable
|
||||
|
||||
from nonebot.adapters import Bot, Event
|
||||
from nonebot_plugin_alconna import UniMessage, UniMsg
|
||||
from nonebot_plugin_session import EventSession
|
||||
from pydantic import BaseModel, create_model
|
||||
from nonebot_plugin_session import EventSession
|
||||
from nonebot_plugin_alconna import UniMsg, UniMessage
|
||||
|
||||
from zhenxun.configs.path_config import IMAGE_PATH
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.models.goods_info import GoodsInfo
|
||||
from zhenxun.utils.platform import PlatformUtils
|
||||
from zhenxun.models.friend_user import FriendUser
|
||||
from zhenxun.configs.path_config import IMAGE_PATH
|
||||
from zhenxun.models.user_console import UserConsole
|
||||
from zhenxun.models.user_gold_log import UserGoldLog
|
||||
from zhenxun.models.user_props_log import UserPropsLog
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import GoldHandle, PropHandle
|
||||
from zhenxun.models.user_props_log import UserPropsLog
|
||||
from zhenxun.models.group_member_info import GroupInfoUser
|
||||
from zhenxun.utils.image_utils import BuildImage, ImageTemplate, text2image
|
||||
|
||||
ICON_PATH = IMAGE_PATH / "shop_icon"
|
||||
|
||||
RANK_ICON_PATH = IMAGE_PATH / "_icon"
|
||||
|
||||
PLATFORM_PATH = {
|
||||
"dodo": RANK_ICON_PATH / "dodo.png",
|
||||
"discord": RANK_ICON_PATH / "discord.png",
|
||||
"kaiheila": RANK_ICON_PATH / "kook.png",
|
||||
"qq": RANK_ICON_PATH / "qq.png",
|
||||
}
|
||||
|
||||
|
||||
class Goods(BaseModel):
|
||||
|
||||
name: str
|
||||
"""商品名称"""
|
||||
before_handle: list[Callable] = []
|
||||
@@ -44,7 +56,6 @@ class Goods(BaseModel):
|
||||
|
||||
|
||||
class ShopParam(BaseModel):
|
||||
|
||||
goods_name: str
|
||||
"""商品名称"""
|
||||
user_id: int
|
||||
@@ -65,11 +76,55 @@ class ShopParam(BaseModel):
|
||||
"""单次使用最大次数"""
|
||||
session: EventSession | None = None
|
||||
"""EventSession"""
|
||||
message: UniMsg
|
||||
"""UniMessage"""
|
||||
|
||||
|
||||
async def gold_rank(user_id: str, group_id: str | None, num: int, platform: str):
|
||||
query = UserConsole
|
||||
if group_id:
|
||||
uid_list = await GroupInfoUser.filter(group_id=group_id).values_list(
|
||||
"user_id", flat=True
|
||||
)
|
||||
query = query.filter(user_id__in=uid_list)
|
||||
user_list = await query.annotate().order_by("-gold").values_list("user_id", "gold")
|
||||
user_id_list = [user[0] for user in user_list]
|
||||
index = user_id_list.index(user_id) + 1
|
||||
user_list = user_list[:num] if num < len(user_list) else user_list
|
||||
friend_user = await FriendUser.filter(user_id__in=user_id_list).values_list(
|
||||
"user_id", "user_name"
|
||||
)
|
||||
uid2name = {user[0]: user[1] for user in friend_user}
|
||||
if diff_id := set(user_id_list).difference(set(uid2name.keys())):
|
||||
group_user = await GroupInfoUser.filter(user_id__in=diff_id).values_list(
|
||||
"user_id", "user_name"
|
||||
)
|
||||
for g in group_user:
|
||||
uid2name[g[0]] = g[1]
|
||||
column_name = ["排名", "-", "名称", "金币", "平台"]
|
||||
data_list = []
|
||||
for i, user in enumerate(user_list):
|
||||
ava_bytes = await PlatformUtils.get_user_avatar(user[0], platform)
|
||||
data_list.append(
|
||||
[
|
||||
f"{i+1}",
|
||||
(ava_bytes, 30, 30) if platform == "qq" else "",
|
||||
uid2name.get(user[0]),
|
||||
user[1],
|
||||
(PLATFORM_PATH.get(platform), 30, 30),
|
||||
]
|
||||
)
|
||||
if group_id:
|
||||
title = "金币群组内排行"
|
||||
tip = f"你的排名在本群第 {index} 位哦!"
|
||||
else:
|
||||
title = "金币全局排行"
|
||||
tip = f"你的排名在全局第 {index} 位哦!"
|
||||
return await ImageTemplate.table_page(title, tip, column_name, data_list)
|
||||
|
||||
|
||||
class ShopManage:
|
||||
|
||||
uuid2goods: dict[str, Goods] = {}
|
||||
uuid2goods: dict[str, Goods] = {} # noqa: RUF012
|
||||
|
||||
@classmethod
|
||||
def __build_params(
|
||||
@@ -102,6 +157,7 @@ class ShopManage:
|
||||
"num": num,
|
||||
"text": text,
|
||||
"session": session,
|
||||
"message": message,
|
||||
}
|
||||
)
|
||||
return model, {
|
||||
@@ -113,6 +169,7 @@ class ShopManage:
|
||||
"num": num,
|
||||
"text": text,
|
||||
"goods_name": goods.name,
|
||||
"message": message,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
@@ -121,7 +178,6 @@ class ShopManage:
|
||||
args: MappingProxyType,
|
||||
param: ShopParam,
|
||||
session: EventSession,
|
||||
message: UniMsg,
|
||||
**kwargs,
|
||||
) -> list[Any]:
|
||||
"""解析参数
|
||||
@@ -144,7 +200,7 @@ class ShopManage:
|
||||
elif par in ["session"]:
|
||||
param_list.append(session)
|
||||
elif par in ["message"]:
|
||||
param_list.append(message)
|
||||
param_list.append(kwargs.get("message"))
|
||||
elif par not in ["args", "kwargs"]:
|
||||
param_list.append(param_json.get(par))
|
||||
if kwargs.get(par) is not None:
|
||||
@@ -170,16 +226,15 @@ class ShopManage:
|
||||
if fun_list:
|
||||
for func in fun_list:
|
||||
args = inspect.signature(func).parameters
|
||||
if args and list(args.keys())[0] != "kwargs":
|
||||
if args and next(iter(args.keys())) != "kwargs":
|
||||
if asyncio.iscoroutinefunction(func):
|
||||
await func(*cls.__parse_args(args, param, **kwargs))
|
||||
else:
|
||||
func(*cls.__parse_args(args, param, **kwargs))
|
||||
elif asyncio.iscoroutinefunction(func):
|
||||
await func(**kwargs)
|
||||
else:
|
||||
if asyncio.iscoroutinefunction(func):
|
||||
await func(**kwargs)
|
||||
else:
|
||||
func(**kwargs)
|
||||
func(**kwargs)
|
||||
|
||||
@classmethod
|
||||
async def __run(
|
||||
@@ -187,7 +242,6 @@ class ShopManage:
|
||||
goods: Goods,
|
||||
param: ShopParam,
|
||||
session: EventSession,
|
||||
message: UniMsg,
|
||||
**kwargs,
|
||||
) -> str | UniMessage | None:
|
||||
"""运行道具函数
|
||||
@@ -201,22 +255,18 @@ class ShopManage:
|
||||
"""
|
||||
args = inspect.signature(goods.func).parameters # type: ignore
|
||||
if goods.func:
|
||||
if args and list(args.keys())[0] != "kwargs":
|
||||
if asyncio.iscoroutinefunction(goods.func):
|
||||
return await goods.func(
|
||||
*cls.__parse_args(args, param, session, message, **kwargs)
|
||||
)
|
||||
else:
|
||||
return goods.func(
|
||||
*cls.__parse_args(args, param, session, message, **kwargs)
|
||||
)
|
||||
if args and next(iter(args.keys())) != "kwargs":
|
||||
return (
|
||||
await goods.func(*cls.__parse_args(args, param, session, **kwargs))
|
||||
if asyncio.iscoroutinefunction(goods.func)
|
||||
else goods.func(*cls.__parse_args(args, param, session, **kwargs))
|
||||
)
|
||||
if asyncio.iscoroutinefunction(goods.func):
|
||||
return await goods.func(
|
||||
**kwargs,
|
||||
)
|
||||
else:
|
||||
if asyncio.iscoroutinefunction(goods.func):
|
||||
return await goods.func(
|
||||
**kwargs,
|
||||
)
|
||||
else:
|
||||
return goods.func(**kwargs)
|
||||
return goods.func(**kwargs)
|
||||
|
||||
@classmethod
|
||||
async def use(
|
||||
@@ -252,17 +302,17 @@ class ShopManage:
|
||||
if not goods_info:
|
||||
return f"{goods_name} 不存在..."
|
||||
if goods_info.is_passive:
|
||||
return f"{goods_name} 是被动道具, 无法使用..."
|
||||
return f"{goods_info.goods_name} 是被动道具, 无法使用..."
|
||||
goods = cls.uuid2goods.get(goods_info.uuid)
|
||||
if not goods or not goods.func:
|
||||
return f"{goods_name} 未注册使用函数, 无法使用..."
|
||||
return f"{goods_info.goods_name} 未注册使用函数, 无法使用..."
|
||||
param, kwargs = cls.__build_params(
|
||||
bot, event, session, message, goods, num, text
|
||||
)
|
||||
if num > param.max_num_limit:
|
||||
return f"{goods_info.goods_name} 单次使用最大数量为{param.max_num_limit}..."
|
||||
await cls.run_before_after(goods, param, "before", **kwargs)
|
||||
result = await cls.__run(goods, param, session, message, **kwargs)
|
||||
result = await cls.__run(goods, param, session, **kwargs)
|
||||
await UserConsole.use_props(session.id1, goods_info.uuid, num, session.platform) # type: ignore
|
||||
await cls.run_before_after(goods, param, "after", **kwargs)
|
||||
if not result and param.send_success_msg:
|
||||
@@ -334,11 +384,10 @@ class ShopManage:
|
||||
]
|
||||
if name.isdigit():
|
||||
goods = goods_list[int(name) - 1]
|
||||
elif filter_goods := [g for g in goods_list if g.goods_name == name]:
|
||||
goods = filter_goods[0]
|
||||
else:
|
||||
if filter_goods := [g for g in goods_list if g.goods_name == name]:
|
||||
goods = filter_goods[0]
|
||||
else:
|
||||
return "道具名称不存在..."
|
||||
return "道具名称不存在..."
|
||||
user = await UserConsole.get_user(user_id, platform)
|
||||
price = goods.goods_price * num * goods.goods_discount
|
||||
if user.gold < price:
|
||||
@@ -423,13 +472,12 @@ class ShopManage:
|
||||
BuildImage: 商店图片
|
||||
"""
|
||||
goods_lst = await GoodsInfo.get_all_goods()
|
||||
_dc = {}
|
||||
font_h = BuildImage.get_text_size("正")[1]
|
||||
h = 10
|
||||
_list: list[GoodsInfo] = []
|
||||
for goods in goods_lst:
|
||||
if goods.goods_limit_time == 0 or time.time() < goods.goods_limit_time:
|
||||
_list.append(goods)
|
||||
_list: list[GoodsInfo] = [
|
||||
goods
|
||||
for goods in goods_lst
|
||||
if goods.goods_limit_time == 0 or time.time() < goods.goods_limit_time
|
||||
]
|
||||
# A = BuildImage(1100, h, color="#f9f6f2")
|
||||
total_n = 0
|
||||
image_list = []
|
||||
@@ -471,7 +519,7 @@ class ShopManage:
|
||||
440 + _tmp.width,
|
||||
0,
|
||||
),
|
||||
f" 金币",
|
||||
" 金币",
|
||||
center_type="height",
|
||||
)
|
||||
des_image = None
|
||||
@@ -541,7 +589,7 @@ class ShopManage:
|
||||
).split()
|
||||
y_m_d = limit_time[0]
|
||||
_h_m = limit_time[1].split(":")
|
||||
h_m = _h_m[0] + "时 " + _h_m[1] + "分"
|
||||
h_m = f"{_h_m[0]}时 {_h_m[1]}分"
|
||||
await bk.text((_w + 55, 38), str(y_m_d))
|
||||
await bk.text((_w + 65, 57), str(h_m))
|
||||
_w += 140
|
||||
@@ -575,8 +623,7 @@ class ShopManage:
|
||||
f"{goods.daily_limit}", size=30
|
||||
)
|
||||
await bk.paste(_tmp, (_w + 72, 45))
|
||||
if total_n < n:
|
||||
total_n = n
|
||||
total_n = max(total_n, n)
|
||||
if n:
|
||||
await bk.line((650, -1, 650 + n, -1), "#a29ad6", 5)
|
||||
# await bk.aline((650, 80, 650 + n, 80), "#a29ad6", 5)
|
||||
@@ -585,10 +632,8 @@ class ShopManage:
|
||||
image_list.append(bk)
|
||||
# await A.apaste(bk, (0, current_h), True)
|
||||
# current_h += 90
|
||||
h = 0
|
||||
current_h = 0
|
||||
for img in image_list:
|
||||
h += img.height + 10
|
||||
h = sum(img.height + 10 for img in image_list) or 400
|
||||
A = BuildImage(1100, h, color="#f9f6f2")
|
||||
for img in image_list:
|
||||
await A.paste(img, (0, current_h))
|
||||
@@ -597,7 +642,7 @@ class ShopManage:
|
||||
if total_n:
|
||||
w += total_n
|
||||
h = A.height + 230 + 100
|
||||
h = 1000 if h < 1000 else h
|
||||
h = max(h, 1000)
|
||||
shop_logo = BuildImage(100, 100, background=f"{IMAGE_PATH}/other/shop_text.png")
|
||||
shop = BuildImage(w, h, font_size=20, color="#f9f6f2")
|
||||
await shop.paste(A, (20, 230))
|
||||
|
||||
@@ -3,9 +3,11 @@ from nonebot_plugin_session import EventSession
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
from nonebot_plugin_alconna import (
|
||||
Args,
|
||||
Query,
|
||||
Option,
|
||||
Alconna,
|
||||
Arparma,
|
||||
AlconnaQuery,
|
||||
on_alconna,
|
||||
store_true,
|
||||
)
|
||||
@@ -17,6 +19,7 @@ from zhenxun.configs.utils import PluginCdBlock, RegisterConfig, PluginExtraData
|
||||
|
||||
from ._data_source import SignManage
|
||||
from .utils import clear_sign_data_pic
|
||||
from .goods_register import driver # noqa: F401
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="签到",
|
||||
@@ -27,8 +30,8 @@ __plugin_meta__ = PluginMetadata(
|
||||
指令:
|
||||
签到
|
||||
我的签到
|
||||
好感度排行
|
||||
好感度总排行
|
||||
好感度排行 ?[num=10]
|
||||
好感度总排行 ?[num=10]
|
||||
* 签到时有 3% 概率 * 2 *
|
||||
""".strip(),
|
||||
extra=PluginExtraData(
|
||||
@@ -89,7 +92,7 @@ _sign_matcher = on_alconna(
|
||||
Option("--my", action=store_true, help_text="我的签到"),
|
||||
Option(
|
||||
"-l|--list",
|
||||
Args["num", int, 10],
|
||||
Args["num?", int],
|
||||
help_text="好感度排行",
|
||||
),
|
||||
Option("-g|--global", action=store_true, help_text="全局排行"),
|
||||
@@ -115,7 +118,7 @@ _sign_matcher.shortcut(
|
||||
_sign_matcher.shortcut(
|
||||
"好感度总排行",
|
||||
command="签到",
|
||||
arguments=["--list", "--global"],
|
||||
arguments=["--global", "--list"],
|
||||
prefix=True,
|
||||
)
|
||||
|
||||
@@ -139,7 +142,11 @@ async def _(session: EventSession, arparma: Arparma, nickname: str = UserName())
|
||||
|
||||
|
||||
@_sign_matcher.assign("list")
|
||||
async def _(session: EventSession, arparma: Arparma, num: int):
|
||||
async def _(
|
||||
session: EventSession, arparma: Arparma, num: Query[int] = AlconnaQuery("num", 10)
|
||||
):
|
||||
if num.result > 50:
|
||||
await MessageUtils.build_message("排行榜人数不能超过50哦...").finish()
|
||||
gid = session.id3 or session.id2
|
||||
if not arparma.find("global") and not gid:
|
||||
await MessageUtils.build_message(
|
||||
@@ -148,7 +155,7 @@ async def _(session: EventSession, arparma: Arparma, num: int):
|
||||
if session.id1:
|
||||
if arparma.find("global"):
|
||||
gid = None
|
||||
if image := await SignManage.rank(session.id1, num, gid):
|
||||
if image := await SignManage.rank(session.id1, num.result, gid):
|
||||
logger.info("查看签到排行", arparma.header_result, session=session)
|
||||
await MessageUtils.build_message(image).finish()
|
||||
return MessageUtils.build_message("用户id为空...").send()
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import random
|
||||
import secrets
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
import pytz
|
||||
from nonebot_plugin_session import EventSession
|
||||
|
||||
from zhenxun.configs.path_config import IMAGE_PATH
|
||||
from zhenxun.models.friend_user import FriendUser
|
||||
from zhenxun.models.group_member_info import GroupInfoUser
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.models.sign_log import SignLog
|
||||
from zhenxun.models.sign_user import SignUser
|
||||
from zhenxun.models.user_console import UserConsole
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.image_utils import BuildImage, ImageTemplate
|
||||
from zhenxun.utils.utils import get_user_avatar
|
||||
from zhenxun.models.friend_user import FriendUser
|
||||
from zhenxun.configs.path_config import IMAGE_PATH
|
||||
from zhenxun.models.user_console import UserConsole
|
||||
from zhenxun.models.group_member_info import GroupInfoUser
|
||||
from zhenxun.utils.image_utils import BuildImage, ImageTemplate
|
||||
|
||||
from ._random_event import random_event
|
||||
from .utils import get_card
|
||||
from ._random_event import random_event
|
||||
|
||||
ICON_PATH = IMAGE_PATH / "_icon"
|
||||
|
||||
@@ -30,11 +30,10 @@ PLATFORM_PATH = {
|
||||
|
||||
|
||||
class SignManage:
|
||||
|
||||
@classmethod
|
||||
async def rank(
|
||||
cls, user_id: str, num: int, group_id: str | None = None
|
||||
) -> BuildImage:
|
||||
) -> BuildImage: # sourcery skip: avoid-builtin-shadow
|
||||
"""好感度排行
|
||||
|
||||
参数:
|
||||
@@ -51,35 +50,36 @@ class SignManage:
|
||||
"user_id", flat=True
|
||||
)
|
||||
query = query.filter(user_id__in=user_list)
|
||||
all_list = (
|
||||
user_list = (
|
||||
await query.annotate()
|
||||
.order_by("-impression")
|
||||
.values_list("user_id", flat=True)
|
||||
.values_list("user_id", "impression", "sign_count", "platform")
|
||||
)
|
||||
index = all_list.index(user_id) + 1 # type: ignore
|
||||
user_list = await query.annotate().order_by("-impression").limit(num).all()
|
||||
user_id_list = [u.user_id for u in user_list]
|
||||
user_id_list = [user[0] for user in user_list]
|
||||
index = user_id_list.index(user_id) + 1
|
||||
user_list = user_list[:num] if num < len(user_list) else user_list
|
||||
column_name = ["排名", "-", "名称", "好感度", "签到次数", "平台"]
|
||||
friend_list = await FriendUser.filter(user_id__in=user_id_list).values_list(
|
||||
"user_id", "user_name"
|
||||
)
|
||||
uid2name = {f[0]: f[1] for f in friend_list}
|
||||
group_member_list = await GroupInfoUser.filter(
|
||||
user_id__in=user_id_list
|
||||
).values_list("user_id", "user_name")
|
||||
for gm in group_member_list:
|
||||
uid2name[gm[0]] = gm[1]
|
||||
if diff_id := set(user_id_list).difference(set(uid2name.keys())):
|
||||
group_user = await GroupInfoUser.filter(user_id__in=diff_id).values_list(
|
||||
"user_id", "user_name"
|
||||
)
|
||||
for g in group_user:
|
||||
uid2name[g[0]] = g[1]
|
||||
data_list = []
|
||||
for i, user in enumerate(user_list):
|
||||
bytes = await get_user_avatar(user.user_id)
|
||||
bytes = await get_user_avatar(user[0])
|
||||
data_list.append(
|
||||
[
|
||||
f"{i+1}",
|
||||
(bytes, 30, 30) if user.platform == "qq" else "",
|
||||
uid2name.get(user.user_id),
|
||||
user.impression,
|
||||
user.sign_count,
|
||||
(PLATFORM_PATH.get(user.platform), 30, 30),
|
||||
(bytes, 30, 30) if user[3] == "qq" else "",
|
||||
uid2name.get(user[0]),
|
||||
user[1],
|
||||
user[2],
|
||||
(PLATFORM_PATH.get(user[3]), 30, 30),
|
||||
]
|
||||
)
|
||||
if group_id:
|
||||
@@ -120,9 +120,8 @@ class SignManage:
|
||||
log_time = new_log.create_time.astimezone(
|
||||
pytz.timezone("Asia/Shanghai")
|
||||
).date()
|
||||
if not is_card_view:
|
||||
if not new_log or (log_time and log_time != now.date()):
|
||||
return await cls._handle_sign_in(user, nickname, session)
|
||||
if not is_card_view and (not new_log or (log_time and log_time != now.date())):
|
||||
return await cls._handle_sign_in(user, nickname, session)
|
||||
return await get_card(
|
||||
user, nickname, -1, user_console.gold, "", is_card_view=is_card_view
|
||||
)
|
||||
@@ -148,9 +147,7 @@ class SignManage:
|
||||
rand = random.random()
|
||||
add_probability = float(user.add_probability)
|
||||
specify_probability = user.specify_probability
|
||||
if rand + add_probability > 0.97:
|
||||
impression_added *= 2
|
||||
elif rand < specify_probability:
|
||||
if rand + add_probability > 0.97 or rand < specify_probability:
|
||||
impression_added *= 2
|
||||
await SignUser.sign(user, impression_added, session.bot_id, session.platform)
|
||||
gold = random.randint(1, 100)
|
||||
|
||||
@@ -36,7 +36,6 @@ level2attitude = {
|
||||
weekdays = {1: "Mon", 2: "Tue", 3: "Wed", 4: "Thu", 5: "Fri", 6: "Sat", 7: "Sun"}
|
||||
|
||||
lik2level = {
|
||||
9999: "9",
|
||||
400: "8",
|
||||
270: "7",
|
||||
200: "6",
|
||||
|
||||
@@ -6,7 +6,7 @@ from nonebot_plugin_session import EventSession
|
||||
|
||||
from zhenxun.models.sign_user import SignUser
|
||||
from zhenxun.models.user_console import UserConsole
|
||||
from zhenxun.utils.decorator.shop import NotMeetUseConditionsException, shop_register
|
||||
from zhenxun.utils.decorator.shop import shop_register
|
||||
|
||||
driver: Driver = nonebot.get_driver()
|
||||
|
||||
@@ -32,9 +32,13 @@ driver: Driver = nonebot.get_driver()
|
||||
"favorability_card_2.png",
|
||||
"favorability_card_3.png",
|
||||
),
|
||||
**{"好感度双倍加持卡Ⅰ_prob": 0.1, "好感度双倍加持卡Ⅱ_prob": 0.2, "好感度双倍加持卡Ⅲ_prob": 0.3}, # type: ignore
|
||||
**{
|
||||
"好感度双倍加持卡Ⅰ_prob": 0.1,
|
||||
"好感度双倍加持卡Ⅱ_prob": 0.2,
|
||||
"好感度双倍加持卡Ⅲ_prob": 0.3,
|
||||
}, # type: ignore
|
||||
)
|
||||
async def _(session: EventSession, user_id: int, group_id: int, prob: float):
|
||||
async def _(session: EventSession, user_id: int, prob: float):
|
||||
if session.id1:
|
||||
user_console = await UserConsole.get_user(session.id1, session.platform)
|
||||
user, _ = await SignUser.get_or_create(
|
||||
@@ -53,20 +57,24 @@ async def _(session: EventSession, user_id: int, group_id: int, prob: float):
|
||||
icon="sword.png",
|
||||
)
|
||||
async def _(user_id: int, group_id: int):
|
||||
print(user_id, group_id, "使用测试道具")
|
||||
# print(user_id, group_id, "使用测试道具")
|
||||
pass
|
||||
|
||||
|
||||
@shop_register.before_handle(name="测试道具A", load_status=False)
|
||||
async def _(user_id: int, group_id: int):
|
||||
print(user_id, group_id, "第一个使用前函数(before handle)")
|
||||
# print(user_id, group_id, "第一个使用前函数(before handle)")
|
||||
pass
|
||||
|
||||
|
||||
@shop_register.before_handle(name="测试道具A", load_status=False)
|
||||
async def _(user_id: int, group_id: int):
|
||||
print(user_id, group_id, "第二个使用前函数(before handle)222")
|
||||
raise NotMeetUseConditionsException("太笨了!") # 抛出异常,阻断使用,并返回信息
|
||||
# print(user_id, group_id, "第二个使用前函数(before handle)222")
|
||||
# raise NotMeetUseConditionsException("太笨了!") # 抛出异常,阻断使用,并返回信息
|
||||
pass
|
||||
|
||||
|
||||
@shop_register.after_handle(name="测试道具A", load_status=False)
|
||||
async def _(user_id: int, group_id: int):
|
||||
print(user_id, group_id, "第一个使用后函数(after handle)")
|
||||
# print(user_id, group_id, "第一个使用后函数(after handle)")
|
||||
pass
|
||||
|
||||
@@ -1,31 +1,36 @@
|
||||
import os
|
||||
import random
|
||||
from datetime import datetime
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
import nonebot
|
||||
import pytz
|
||||
import nonebot
|
||||
from nonebot.drivers import Driver
|
||||
from nonebot_plugin_htmlrender import template_to_pic
|
||||
|
||||
from zhenxun.configs.config import BotConfig, Config
|
||||
from zhenxun.configs.path_config import IMAGE_PATH, TEMPLATE_PATH
|
||||
from zhenxun.models.sign_log import SignLog
|
||||
from zhenxun.models.sign_user import SignUser
|
||||
from zhenxun.utils.image_utils import BuildImage
|
||||
from zhenxun.utils.utils import get_user_avatar
|
||||
from zhenxun.utils.image_utils import BuildImage
|
||||
from zhenxun.utils.platform import PlatformUtils
|
||||
from zhenxun.configs.config import Config, BotConfig
|
||||
from zhenxun.configs.path_config import IMAGE_PATH, TEMPLATE_PATH
|
||||
|
||||
from .config import (
|
||||
SIGN_BACKGROUND_PATH,
|
||||
SIGN_BORDER_PATH,
|
||||
SIGN_RESOURCE_PATH,
|
||||
SIGN_BACKGROUND_PATH,
|
||||
SIGN_TODAY_CARD_PATH,
|
||||
level2attitude,
|
||||
lik2level,
|
||||
lik2relation,
|
||||
level2attitude,
|
||||
)
|
||||
|
||||
assert (
|
||||
len(level2attitude) == len(lik2level) == len(lik2relation)
|
||||
), "好感度态度、等级、关系长度不匹配!"
|
||||
|
||||
AVA_URL = "http://q1.qlogo.cn/g?b=qq&nk={}&s=160"
|
||||
|
||||
driver: Driver = nonebot.get_driver()
|
||||
@@ -87,26 +92,26 @@ async def get_card(
|
||||
card_file = Path(SIGN_TODAY_CARD_PATH) / file_name
|
||||
if card_file.exists():
|
||||
return IMAGE_PATH / "sign" / "today_card" / file_name
|
||||
else:
|
||||
if add_impression == -1:
|
||||
card_file = Path(SIGN_TODAY_CARD_PATH) / view_name
|
||||
if card_file.exists():
|
||||
return card_file
|
||||
is_card_view = True
|
||||
if base_config.get("IMAGE_STYLE") == "zhenxun":
|
||||
return await _generate_html_card(
|
||||
user, nickname, add_impression, gold, gift, is_double, is_card_view
|
||||
)
|
||||
else:
|
||||
return await _generate_card(
|
||||
user, nickname, add_impression, gold, gift, is_double, is_card_view
|
||||
)
|
||||
if add_impression == -1:
|
||||
card_file = Path(SIGN_TODAY_CARD_PATH) / view_name
|
||||
if card_file.exists():
|
||||
return card_file
|
||||
is_card_view = True
|
||||
return (
|
||||
await _generate_html_card(
|
||||
user, nickname, add_impression, gold, gift, is_double, is_card_view
|
||||
)
|
||||
if base_config.get("IMAGE_STYLE") == "zhenxun"
|
||||
else await _generate_card(
|
||||
user, nickname, add_impression, gold, gift, is_double, is_card_view
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def _generate_card(
|
||||
user: SignUser,
|
||||
nickname: str,
|
||||
impression: float,
|
||||
add_impression: float,
|
||||
gold: int | None,
|
||||
gift: str,
|
||||
is_double: bool = False,
|
||||
@@ -117,7 +122,7 @@ async def _generate_card(
|
||||
参数:
|
||||
user: SignUser
|
||||
nickname: 用户昵称
|
||||
impression: 新增的好感度
|
||||
add_impression: 新增的好感度
|
||||
gold: 金币
|
||||
gift: 礼物
|
||||
is_double: 是否触发双倍.
|
||||
@@ -139,16 +144,12 @@ async def _generate_card(
|
||||
await ava.circle()
|
||||
await ava_bk.paste(ava, (19, 18))
|
||||
await ava_bk.paste(ava_border, center_type="center")
|
||||
add_impression = impression
|
||||
impression = float(user.impression)
|
||||
info_img = BuildImage(250, 150, color=(255, 255, 255, 0), font_size=15)
|
||||
level, next_impression, previous_impression = get_level_and_next_impression(
|
||||
impression
|
||||
)
|
||||
interpolation = next_impression - impression
|
||||
if level == "9":
|
||||
level = "8"
|
||||
interpolation = 0
|
||||
await info_img.text((0, 0), f"· 好感度等级:{level} [{lik2relation[level]}]")
|
||||
await info_img.text(
|
||||
(0, 20), f"· {BotConfig.self_nickname}对你的态度:{level2attitude[level]}"
|
||||
@@ -157,16 +158,12 @@ async def _generate_card(
|
||||
|
||||
bar_bk = BuildImage(220, 20, background=SIGN_RESOURCE_PATH / "bar_white.png")
|
||||
bar = BuildImage(220, 20, background=SIGN_RESOURCE_PATH / "bar.png")
|
||||
ratio = 1 - (next_impression - user.impression) / (
|
||||
next_impression - previous_impression
|
||||
)
|
||||
ratio = 1 - (next_impression - impression) / (next_impression - previous_impression)
|
||||
if next_impression == 0:
|
||||
ratio = 0
|
||||
await bar.resize(width=int(bar.width * ratio) or 1, height=bar.height)
|
||||
await bar_bk.paste(bar)
|
||||
font_size = 30
|
||||
if "好感度双倍加持卡" in gift:
|
||||
font_size = 20
|
||||
font_size = 20 if "好感度双倍加持卡" in gift else 30
|
||||
gift_border = BuildImage(
|
||||
270,
|
||||
100,
|
||||
@@ -191,9 +188,9 @@ async def _generate_card(
|
||||
nickname, size=50, font_color=(255, 255, 255)
|
||||
)
|
||||
user_console = await user.user_console
|
||||
if user_console and user_console.uid:
|
||||
if user_console and user_console.uid is not None:
|
||||
uid = f"{user_console.uid}".rjust(12, "0")
|
||||
uid = uid[:4] + " " + uid[4:8] + " " + uid[8:]
|
||||
uid = f"{uid[:4]} {uid[4:8]} {uid[8:]}"
|
||||
else:
|
||||
uid = "XXXX XXXX XXXX"
|
||||
uid_img = await BuildImage.build_text_image(
|
||||
@@ -249,9 +246,12 @@ async def _generate_card(
|
||||
default_setu_prob = (
|
||||
Config.get_config("send_setu", "INITIAL_SETU_PROBABILITY") * 100 # type: ignore
|
||||
)
|
||||
setu_prob = (
|
||||
default_setu_prob + float(user.impression) if user.impression < 100 else 100
|
||||
)
|
||||
await today_data.text(
|
||||
(0, 50),
|
||||
f"色图概率:{(default_setu_prob + float(user.impression) if user.impression < 100 else 100):.2f}%",
|
||||
f"色图概率:{setu_prob:.2f}%",
|
||||
)
|
||||
await today_data.text((0, 75), f"开箱次数:{(20 + int(user.impression / 3))}")
|
||||
_type = "view"
|
||||
@@ -311,11 +311,11 @@ async def generate_progress_bar_pic():
|
||||
step_g = (bg_2[1] - bg_1[1]) / width
|
||||
step_b = (bg_2[2] - bg_1[2]) / width
|
||||
|
||||
for y in range(0, width):
|
||||
for y in range(width):
|
||||
bg_r = round(bg_1[0] + step_r * y)
|
||||
bg_g = round(bg_1[1] + step_g * y)
|
||||
bg_b = round(bg_1[2] + step_b * y)
|
||||
for x in range(0, height):
|
||||
for x in range(height):
|
||||
await A.point((y, x), fill=(bg_r, bg_g, bg_b))
|
||||
await bk.paste(img_y, (0, 0))
|
||||
await bk.paste(A, (25, 0))
|
||||
@@ -336,22 +336,33 @@ async def generate_progress_bar_pic():
|
||||
await bk.save(SIGN_RESOURCE_PATH / "bar_white.png")
|
||||
|
||||
|
||||
def get_level_and_next_impression(impression: float) -> tuple[str, int, int]:
|
||||
def get_level_and_next_impression(impression: float) -> tuple[str, int | float, int]:
|
||||
"""获取当前好感等级与下一等级的差距
|
||||
|
||||
参数:
|
||||
impression: 好感度
|
||||
|
||||
返回:
|
||||
tuple[str, int, int]: 好感度等级中文,好感度等级,下一等级好感差距
|
||||
tuple[str, int, int]: 好感度等级,下一等级好感度要求,已达到的好感度要求
|
||||
"""
|
||||
if impression == 0:
|
||||
return lik2level[10], 10, 0
|
||||
|
||||
keys = list(lik2level.keys())
|
||||
level, next_impression, previous_impression = (
|
||||
lik2level[keys[-1]],
|
||||
keys[-2],
|
||||
keys[-1],
|
||||
)
|
||||
for i in range(len(keys)):
|
||||
if impression > keys[i]:
|
||||
return lik2level[keys[i]], keys[i - 1], keys[i]
|
||||
return lik2level[10], 10, 0
|
||||
if impression >= keys[i]:
|
||||
level, next_impression, previous_impression = (
|
||||
lik2level[keys[i]],
|
||||
keys[i - 1],
|
||||
keys[i],
|
||||
)
|
||||
if i == 0:
|
||||
next_impression = impression
|
||||
break
|
||||
return level, next_impression, previous_impression
|
||||
|
||||
|
||||
def clear_sign_data_pic():
|
||||
@@ -367,7 +378,7 @@ def clear_sign_data_pic():
|
||||
async def _generate_html_card(
|
||||
user: SignUser,
|
||||
nickname: str,
|
||||
impression: float,
|
||||
add_impression: float,
|
||||
gold: int | None,
|
||||
gift: str,
|
||||
is_double: bool = False,
|
||||
@@ -378,7 +389,7 @@ async def _generate_html_card(
|
||||
参数:
|
||||
user: SignUser
|
||||
nickname: 用户昵称
|
||||
impression: 新增的好感度
|
||||
add_impression: 新增的好感度
|
||||
gold: 金币
|
||||
gift: 礼物
|
||||
is_double: 是否触发双倍.
|
||||
@@ -387,41 +398,36 @@ async def _generate_html_card(
|
||||
返回:
|
||||
Path: 卡片路径
|
||||
"""
|
||||
impression = float(user.impression)
|
||||
user_console = await user.user_console
|
||||
if user_console and user_console.uid:
|
||||
if user_console and user_console.uid is not None:
|
||||
uid = f"{user_console.uid}".rjust(12, "0")
|
||||
uid = uid[:4] + " " + uid[4:8] + " " + uid[8:]
|
||||
uid = f"{uid[:4]} {uid[4:8]} {uid[8:]}"
|
||||
else:
|
||||
uid = "XXXX XXXX XXXX"
|
||||
level, next_impression, previous_impression = get_level_and_next_impression(
|
||||
float(user.impression)
|
||||
impression
|
||||
)
|
||||
interpolation = next_impression - impression
|
||||
if level == "9":
|
||||
level = "8"
|
||||
interpolation = 0
|
||||
message = f"{BotConfig.self_nickname}希望你开心!"
|
||||
hour = datetime.now().hour
|
||||
if hour > 6 and hour < 10:
|
||||
message = random.choice(MORNING_MESSAGE)
|
||||
elif hour >= 0 and hour < 6:
|
||||
message = random.choice(LG_MESSAGE)
|
||||
_impression = impression
|
||||
if is_double:
|
||||
_impression = f"{impression}(×2)"
|
||||
process = 1 - (next_impression - user.impression) / (
|
||||
_impression = f"{add_impression}(×2)" if is_double else add_impression
|
||||
process = 1 - (next_impression - impression) / (
|
||||
next_impression - previous_impression
|
||||
)
|
||||
if next_impression == 0:
|
||||
process = 0
|
||||
now = datetime.now()
|
||||
ava_url = PlatformUtils.get_user_avatar_url(user.user_id, "qq")
|
||||
data = {
|
||||
"ava": AVA_URL.format(user.user_id),
|
||||
"ava_url": ava_url,
|
||||
"name": nickname,
|
||||
"uid": uid,
|
||||
"sign_count": f"{user.sign_count}",
|
||||
"message": f"{BotConfig.self_nickname}说: {message}",
|
||||
"cur_impression": f"{user.impression:.2f}",
|
||||
"cur_impression": f"{impression:.2f}",
|
||||
"impression": f"好感度+{_impression}",
|
||||
"gold": f"金币+{gold}",
|
||||
"gift": gift,
|
||||
@@ -429,7 +435,7 @@ async def _generate_html_card(
|
||||
"attitude": f"对你的态度: {level2attitude[level]}",
|
||||
"interpolation": f"{interpolation:.2f}",
|
||||
"heart2": [1 for _ in range(int(level))],
|
||||
"heart1": [1 for _ in range(9 - int(level))],
|
||||
"heart1": [1 for _ in range(len(lik2level) - int(level) - 1)],
|
||||
"process": process * 100,
|
||||
"date": str(now.replace(microsecond=0)),
|
||||
"font_size": 45,
|
||||
|
||||
@@ -224,8 +224,8 @@ async def _(
|
||||
await _id_img.circle_corner(10)
|
||||
await background.paste(_id_img, (10, 0), center_type="height")
|
||||
img_list.append(background)
|
||||
A = await BuildImage.auto_paste(img_list, 1)
|
||||
if A:
|
||||
if img_list:
|
||||
A = await BuildImage.auto_paste(img_list, 1)
|
||||
result_image = BuildImage(
|
||||
A.width, A.height + 30, color=(255, 255, 255), font_size=20
|
||||
)
|
||||
@@ -237,8 +237,8 @@ async def _(
|
||||
await MessageUtils.build_message("没有任何请求喔...").finish(reply_to=True)
|
||||
if len(req_image_list) == 1:
|
||||
await MessageUtils.build_message(req_image_list[0]).finish()
|
||||
width = sum([img.width for img in req_image_list])
|
||||
height = max([img.height for img in req_image_list])
|
||||
width = sum(img.width for img in req_image_list)
|
||||
height = max(img.height for img in req_image_list)
|
||||
background = BuildImage(width, height)
|
||||
await background.paste(req_image_list[0])
|
||||
await req_image_list[1].line((0, 10, 1, req_image_list[1].height - 10), width=1)
|
||||
|
||||
@@ -1,35 +1,36 @@
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot_plugin_session import EventSession, SessionLevel
|
||||
from nonebot_plugin_alconna import (
|
||||
Alconna,
|
||||
Args,
|
||||
Arparma,
|
||||
At,
|
||||
Args,
|
||||
Match,
|
||||
Option,
|
||||
Alconna,
|
||||
Arparma,
|
||||
Subcommand,
|
||||
on_alconna,
|
||||
)
|
||||
from nonebot_plugin_session import EventSession, SessionLevel
|
||||
|
||||
from zhenxun.configs.utils import PluginExtraData
|
||||
from zhenxun.models.level_user import LevelUser
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import PluginType
|
||||
from zhenxun.utils.message import MessageUtils
|
||||
from zhenxun.models.level_user import LevelUser
|
||||
from zhenxun.configs.utils import PluginExtraData
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="用户权限管理",
|
||||
description="设置用户权限",
|
||||
usage="""
|
||||
权限设置 add [level: 权限等级] [at: at对象或用户id] [gid: 群组]
|
||||
权限设置 delete [at: at对象或用户id]
|
||||
|
||||
权限设置 add 5 @user
|
||||
权限设置 add 5 422 352352
|
||||
权限设置 add [level: 权限等级] [at: at对象或用户id] ?[-g gid: 群组]
|
||||
权限设置 delete [at: at对象或用户id] ?[-g gid: 群组]
|
||||
|
||||
添加权限 5 @user
|
||||
权限设置 add 5 422 -g 352352
|
||||
|
||||
删除权限 @user
|
||||
删除权限 1234123 -g 123123
|
||||
|
||||
权限设置 delete @user
|
||||
权限设置 delete 123456
|
||||
|
||||
""".strip(),
|
||||
extra=PluginExtraData(
|
||||
author="HibiKier",
|
||||
@@ -44,16 +45,31 @@ _matcher = on_alconna(
|
||||
"权限设置",
|
||||
Subcommand(
|
||||
"add",
|
||||
Args["level", int]["uid", [str, At]]["gid?", str],
|
||||
Args["level", int]["uid", [str, At]],
|
||||
help_text="添加权限",
|
||||
),
|
||||
Subcommand("delete", Args["uid", [str, At]]["gid?", str], help_text="删除权限"),
|
||||
Subcommand("delete", Args["uid", [str, At]], help_text="删除权限"),
|
||||
Option("-g|--group", Args["gid", str], help_text="指定群组"),
|
||||
),
|
||||
permission=SUPERUSER,
|
||||
priority=5,
|
||||
block=True,
|
||||
)
|
||||
|
||||
_matcher.shortcut(
|
||||
"添加权限",
|
||||
command="权限设置",
|
||||
arguments=["add", "{%0}"],
|
||||
prefix=True,
|
||||
)
|
||||
|
||||
_matcher.shortcut(
|
||||
"删除权限",
|
||||
command="权限设置",
|
||||
arguments=["delete", "{%0}"],
|
||||
prefix=True,
|
||||
)
|
||||
|
||||
|
||||
@_matcher.assign("add")
|
||||
async def _(
|
||||
@@ -82,9 +98,10 @@ async def _(
|
||||
]
|
||||
).finish(reply_to=True)
|
||||
await MessageUtils.build_message(
|
||||
f"成功为 \n群组:{group_id}\n用户:{uid} \n设置权限!\n权限:{old_level} -> {level}"
|
||||
f"成功为 \n群组:{group_id}\n用户:{uid} \n"
|
||||
f"设置权限!\n权限:{old_level} -> {level}"
|
||||
).finish()
|
||||
await MessageUtils.build_message(f"设置权限时群组不能为空...").finish()
|
||||
await MessageUtils.build_message("设置权限时群组不能为空...").finish()
|
||||
|
||||
|
||||
@_matcher.assign("delete")
|
||||
@@ -107,7 +124,7 @@ async def _(
|
||||
session=session,
|
||||
)
|
||||
await MessageUtils.build_message(
|
||||
["成功删除 ", At(flag="user", target=uid), f" 的权限等级!"]
|
||||
["成功删除 ", At(flag="user", target=uid), " 的权限等级!"]
|
||||
).finish(reply_to=True)
|
||||
logger.info(
|
||||
f"删除群组用户权限: {user.user_level} -> 0",
|
||||
@@ -115,7 +132,8 @@ async def _(
|
||||
session=session,
|
||||
)
|
||||
await MessageUtils.build_message(
|
||||
f"成功删除 \n群组:{group_id}\n用户:{uid} \n的权限等级!\n权限:{user.user_level} -> 0"
|
||||
f"成功删除 \n群组:{group_id}\n用户:{uid} \n"
|
||||
f"的权限等级!\n权限:{user.user_level} -> 0"
|
||||
).finish()
|
||||
await MessageUtils.build_message(f"对方目前暂无权限喔...").finish()
|
||||
await MessageUtils.build_message(f"设置权限时群组不能为空...").finish()
|
||||
await MessageUtils.build_message("对方目前暂无权限喔...").finish()
|
||||
await MessageUtils.build_message("设置权限时群组不能为空...").finish()
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
import nonebot
|
||||
from nonebot.permission import SUPERUSER
|
||||
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.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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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": "通过 开启/关闭群被动 来控制群被动 <br> ---------- <br> "
|
||||
+ "<br>".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"<br>以下为超级用户额外命令<br>{superuser_help}"
|
||||
plugin_list.append(
|
||||
{
|
||||
"name": data.plugin.name,
|
||||
"description": data.metadata.description.replace("\n", "<br>"),
|
||||
"usage": data.metadata.usage.replace("\n", "<br>"),
|
||||
}
|
||||
)
|
||||
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)
|
||||
@@ -20,8 +20,10 @@ from .api.tabs.manage import router as manage_router
|
||||
from .api.tabs.system import router as system_router
|
||||
from .api.tabs.main import ws_router as status_routes
|
||||
from .api.tabs.database import router as database_router
|
||||
from .api.tabs.dashboard import router as dashboard_router
|
||||
from .api.tabs.manage.chat import ws_router as chat_routes
|
||||
from .api.tabs.plugin_manage import router as plugin_router
|
||||
from .api.tabs.plugin_manage.store import router as store_router
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="WebUi",
|
||||
@@ -71,6 +73,8 @@ BaseApiRouter = APIRouter(prefix="/zhenxun/api")
|
||||
|
||||
|
||||
BaseApiRouter.include_router(auth_router)
|
||||
BaseApiRouter.include_router(store_router)
|
||||
BaseApiRouter.include_router(dashboard_router)
|
||||
BaseApiRouter.include_router(main_router)
|
||||
BaseApiRouter.include_router(manage_router)
|
||||
BaseApiRouter.include_router(database_router)
|
||||
|
||||
@@ -1 +1 @@
|
||||
from .tabs import *
|
||||
from .tabs import * # noqa: F403
|
||||
|
||||
@@ -1 +1 @@
|
||||
from .logs import *
|
||||
from .logs import * # noqa: F403
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import asyncio
|
||||
from typing import Awaitable, Callable, Generic, TypeVar
|
||||
|
||||
PATTERN = r"\x1b(\[.*?[@-~]|\].*?(\x07|\x1b\\))"
|
||||
from typing import Generic, TypeVar
|
||||
from collections.abc import Callable, Awaitable
|
||||
|
||||
_T = TypeVar("_T")
|
||||
LogListener = Callable[[_T], Awaitable[None]]
|
||||
@@ -22,14 +21,13 @@ class LogStorage(Generic[_T]):
|
||||
self.logs[seq] = log
|
||||
asyncio.get_running_loop().call_later(self.rotation, self.remove, seq)
|
||||
await asyncio.gather(
|
||||
*map(lambda listener: listener(log), self.listeners),
|
||||
*(listener(log) for listener in self.listeners),
|
||||
return_exceptions=True,
|
||||
)
|
||||
return seq
|
||||
|
||||
def remove(self, seq: int):
|
||||
del self.logs[seq]
|
||||
return
|
||||
|
||||
|
||||
LOG_STORAGE: LogStorage[str] = LogStorage[str]()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from fastapi import APIRouter, WebSocket
|
||||
from loguru import logger
|
||||
from fastapi import APIRouter
|
||||
from nonebot.utils import escape_tag
|
||||
from starlette.websockets import WebSocket, WebSocketDisconnect, WebSocketState
|
||||
from starlette.websockets import WebSocket, WebSocketState, WebSocketDisconnect
|
||||
|
||||
from .log_manager import LOG_STORAGE
|
||||
|
||||
@@ -27,4 +27,3 @@ async def system_logs_realtime(websocket: WebSocket):
|
||||
pass
|
||||
finally:
|
||||
LOG_STORAGE.listeners.remove(log_listener)
|
||||
return
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
from nonebot import require
|
||||
from fastapi import APIRouter
|
||||
|
||||
from ....base_model import Result
|
||||
from .data_source import BotManage
|
||||
from ....utils import authentication
|
||||
|
||||
require("plugin_store")
|
||||
|
||||
router = APIRouter(prefix="/dashboard")
|
||||
|
||||
|
||||
@router.get(
|
||||
"/get_bot_list",
|
||||
dependencies=[authentication()],
|
||||
deprecated="获取bot列表", # type: ignore
|
||||
)
|
||||
async def _() -> Result:
|
||||
try:
|
||||
return Result.ok(await BotManage.get_bot_list(), "拿到信息啦!")
|
||||
except Exception as e:
|
||||
return Result.fail(f"发生了一点错误捏 {type(e)}: {e}")
|
||||
@@ -0,0 +1,81 @@
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import nonebot
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.drivers import Driver
|
||||
|
||||
from zhenxun.models.statistics import Statistics
|
||||
from zhenxun.utils.platform import PlatformUtils
|
||||
from zhenxun.models.chat_history import ChatHistory
|
||||
|
||||
from .model import BotInfo
|
||||
from ..main.data_source import bot_live
|
||||
|
||||
driver: Driver = nonebot.get_driver()
|
||||
|
||||
|
||||
CONNECT_TIME = 0
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
async def _():
|
||||
global CONNECT_TIME
|
||||
CONNECT_TIME = int(time.time())
|
||||
|
||||
|
||||
class BotManage:
|
||||
@classmethod
|
||||
async def __build_bot_info(cls, bot: Bot) -> BotInfo:
|
||||
"""构建Bot信息
|
||||
|
||||
参数:
|
||||
bot: Bot
|
||||
|
||||
返回:
|
||||
BotInfo: Bot信息
|
||||
"""
|
||||
now = datetime.now()
|
||||
platform = PlatformUtils.get_platform(bot) or ""
|
||||
if platform == "qq":
|
||||
login_info = await bot.get_login_info()
|
||||
nickname = login_info["nickname"]
|
||||
ava_url = PlatformUtils.get_user_avatar_url(bot.self_id, "qq") or ""
|
||||
else:
|
||||
nickname = bot.self_id
|
||||
ava_url = ""
|
||||
bot_info = BotInfo(
|
||||
self_id=bot.self_id, nickname=nickname, ava_url=ava_url, platform=platform
|
||||
)
|
||||
group_list, _ = await PlatformUtils.get_group_list(bot)
|
||||
group_list = [g for g in group_list if g.channel_id is None]
|
||||
friend_list = await PlatformUtils.get_friend_list(bot)
|
||||
bot_info.group_count = len(group_list)
|
||||
bot_info.friend_count = len(friend_list)
|
||||
bot_info.day_call = await Statistics.filter(
|
||||
create_time__gte=now - timedelta(hours=now.hour, minutes=now.minute)
|
||||
).count()
|
||||
bot_info.received_messages = await ChatHistory.filter(
|
||||
bot_id=bot_info.self_id,
|
||||
create_time__gte=now - timedelta(hours=now.hour, minutes=now.minute),
|
||||
).count()
|
||||
bot_info.connect_time = bot_live.get(bot.self_id) or 0
|
||||
if bot_info.connect_time:
|
||||
connect_date = datetime.fromtimestamp(CONNECT_TIME)
|
||||
connect_date_str = connect_date.strftime("%Y-%m-%d %H:%M:%S")
|
||||
bot_info.connect_date = datetime.strptime(
|
||||
connect_date_str, "%Y-%m-%d %H:%M:%S"
|
||||
)
|
||||
return bot_info
|
||||
|
||||
@classmethod
|
||||
async def get_bot_list(cls) -> list[BotInfo]:
|
||||
"""获取bot列表
|
||||
|
||||
返回:
|
||||
list[BotInfo]: Bot列表
|
||||
"""
|
||||
bot_list: list[BotInfo] = []
|
||||
for _, bot in nonebot.get_bots().items():
|
||||
bot_list.append(await cls.__build_bot_info(bot))
|
||||
return bot_list
|
||||
@@ -0,0 +1,26 @@
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class BotInfo(BaseModel):
|
||||
self_id: str
|
||||
"""SELF ID"""
|
||||
nickname: str
|
||||
"""昵称"""
|
||||
ava_url: str
|
||||
"""头像url"""
|
||||
platform: str
|
||||
"""平台"""
|
||||
friend_count: int = 0
|
||||
"""好友数量"""
|
||||
group_count: int = 0
|
||||
"""群聊数量"""
|
||||
received_messages: int = 0
|
||||
"""今日消息接收"""
|
||||
day_call: int = 0
|
||||
"""今日调用插件次数"""
|
||||
connect_time: int = 0
|
||||
"""连接时间"""
|
||||
connect_date: datetime | None = None
|
||||
"""连接日期"""
|
||||
@@ -1,16 +1,16 @@
|
||||
import nonebot
|
||||
from fastapi import APIRouter, Request
|
||||
from nonebot.drivers import Driver
|
||||
from tortoise import Tortoise
|
||||
from nonebot.drivers import Driver
|
||||
from fastapi import Request, APIRouter
|
||||
from tortoise.exceptions import OperationalError
|
||||
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.models.task_info import TaskInfo
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
|
||||
from ....base_model import BaseResultModel, QueryModel, Result
|
||||
from ....utils import authentication
|
||||
from .models.model import SqlModel, SqlText
|
||||
from .models.sql_log import SqlLog
|
||||
from ....utils import authentication
|
||||
from .models.model import SqlText, SqlModel
|
||||
from ....base_model import Result, QueryModel, BaseResultModel
|
||||
|
||||
router = APIRouter(prefix="/database")
|
||||
|
||||
@@ -24,7 +24,8 @@ SQL_DICT = {}
|
||||
SELECT_TABLE_SQL = """
|
||||
select a.tablename as name,d.description as desc from pg_tables a
|
||||
left join pg_class c on relname=tablename
|
||||
left join pg_description d on oid=objoid and objsubid=0 where a.schemaname = 'public'
|
||||
left join pg_description d on oid=objoid
|
||||
and objsubid=0 where a.schemaname = 'public'
|
||||
"""
|
||||
|
||||
SELECT_TABLE_COLUMN_SQL = """
|
||||
@@ -57,10 +58,7 @@ async def _():
|
||||
module2name = {r[0]: r[1] for r in result}
|
||||
for s in SQL_DICT:
|
||||
module = SQL_DICT[s].module
|
||||
if module in module2name:
|
||||
SQL_DICT[s].name = module2name[module]
|
||||
else:
|
||||
SQL_DICT[s].name = module
|
||||
SQL_DICT[s].name = module2name.get(module, module)
|
||||
|
||||
|
||||
@router.get(
|
||||
@@ -77,7 +75,7 @@ async def _() -> Result:
|
||||
)
|
||||
async def _(table_name: str) -> Result:
|
||||
db = Tortoise.get_connection("default")
|
||||
print(SELECT_TABLE_COLUMN_SQL.format(table_name))
|
||||
# print(SELECT_TABLE_COLUMN_SQL.format(table_name))
|
||||
query = await db.execute_query_dict(SELECT_TABLE_COLUMN_SQL.format(table_name))
|
||||
return Result.ok(query)
|
||||
|
||||
@@ -92,7 +90,7 @@ async def _(sql: SqlText, request: Request) -> Result:
|
||||
await SqlLog.add(ip or "0.0.0.0", sql.sql, "")
|
||||
return Result.ok(res, "执行成功啦!")
|
||||
else:
|
||||
result = await TestSQL.raw(sql.sql)
|
||||
result = await TaskInfo.raw(sql.sql)
|
||||
await SqlLog.add(ip or "0.0.0.0", sql.sql, str(result))
|
||||
return Result.ok(info="执行成功啦!")
|
||||
except OperationalError as e:
|
||||
|
||||
@@ -4,7 +4,6 @@ from zhenxun.services.db_context import Model
|
||||
|
||||
|
||||
class SqlLog(Model):
|
||||
|
||||
id = fields.IntField(pk=True, generated=True, auto_increment=True)
|
||||
"""自增id"""
|
||||
ip = fields.CharField(255)
|
||||
@@ -18,7 +17,7 @@ class SqlLog(Model):
|
||||
create_time = fields.DatetimeField(auto_now_add=True)
|
||||
"""创建时间"""
|
||||
|
||||
class Meta:
|
||||
class Meta: # type: ignore
|
||||
table = "sql_log"
|
||||
table_description = "sql执行日志"
|
||||
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
import asyncio
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
import asyncio
|
||||
import contextlib
|
||||
from pathlib import Path
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import nonebot
|
||||
from fastapi import APIRouter, WebSocket
|
||||
from starlette.websockets import WebSocket, WebSocketDisconnect, WebSocketState
|
||||
from fastapi import APIRouter
|
||||
from tortoise.functions import Count
|
||||
from websockets.exceptions import ConnectionClosedError, ConnectionClosedOK
|
||||
from websockets.exceptions import ConnectionClosedOK, ConnectionClosedError
|
||||
from starlette.websockets import WebSocket, WebSocketState, WebSocketDisconnect
|
||||
|
||||
from zhenxun.models.chat_history import ChatHistory
|
||||
from zhenxun.models.group_info import GroupInfo
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.models.statistics import Statistics
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.models.group_info import GroupInfo
|
||||
from zhenxun.models.statistics import Statistics
|
||||
from zhenxun.utils.platform import PlatformUtils
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.models.chat_history import ChatHistory
|
||||
|
||||
from ....base_model import Result
|
||||
from ....config import AVA_URL, GROUP_AVA_URL, QueryDateType
|
||||
from ....utils import authentication, get_system_status
|
||||
from .data_source import bot_live
|
||||
from .model import ActiveGroup, BaseInfo, ChatHistoryCount, HotPlugin
|
||||
from ....utils import authentication, get_system_status
|
||||
from ....config import AVA_URL, GROUP_AVA_URL, QueryDateType
|
||||
from .model import BaseInfo, HotPlugin, ActiveGroup, ChatHistoryCount
|
||||
|
||||
run_time = time.time()
|
||||
|
||||
@@ -131,7 +132,7 @@ async def _(bot_id: str) -> Result:
|
||||
"/get_ch_count", dependencies=[authentication()], description="获取接收消息数量"
|
||||
)
|
||||
async def _(bot_id: str, query_type: QueryDateType | None = None) -> Result:
|
||||
if bots := nonebot.get_bots():
|
||||
if nonebot.get_bot(bot_id):
|
||||
if not query_type:
|
||||
return Result.ok(await ChatHistory.filter(bot_id=bot_id).count())
|
||||
now = datetime.now()
|
||||
@@ -210,7 +211,6 @@ async def _(date_type: QueryDateType | None = None) -> Result:
|
||||
.limit(5)
|
||||
.values_list("group_id", "count")
|
||||
)
|
||||
active_group_list = []
|
||||
id2name = {}
|
||||
if data_list:
|
||||
if info_list := await GroupInfo.filter(
|
||||
@@ -218,15 +218,15 @@ async def _(date_type: QueryDateType | None = None) -> Result:
|
||||
).all():
|
||||
for group_info in info_list:
|
||||
id2name[group_info.group_id] = group_info.group_name
|
||||
for data in data_list:
|
||||
active_group_list.append(
|
||||
ActiveGroup(
|
||||
group_id=data[0],
|
||||
name=id2name.get(data[0]) or data[0],
|
||||
chat_num=data[1],
|
||||
ava_img=GROUP_AVA_URL.format(data[0], data[0]),
|
||||
)
|
||||
active_group_list = [
|
||||
ActiveGroup(
|
||||
group_id=data[0],
|
||||
name=id2name.get(data[0]) or data[0],
|
||||
chat_num=data[1],
|
||||
ava_img=GROUP_AVA_URL.format(data[0], data[0]),
|
||||
)
|
||||
for data in data_list
|
||||
]
|
||||
active_group_list = sorted(
|
||||
active_group_list, key=lambda x: x.chat_num, reverse=True
|
||||
)
|
||||
@@ -263,13 +263,7 @@ async def _(date_type: QueryDateType | None = None) -> Result:
|
||||
for data in data_list:
|
||||
module = data[0]
|
||||
name = module2name.get(module) or module
|
||||
hot_plugin_list.append(
|
||||
HotPlugin(
|
||||
module=data[0],
|
||||
name=name,
|
||||
count=data[1],
|
||||
)
|
||||
)
|
||||
hot_plugin_list.append(HotPlugin(module=module, name=name, count=data[1]))
|
||||
hot_plugin_list = sorted(hot_plugin_list, key=lambda x: x.count, reverse=True)
|
||||
if len(hot_plugin_list) > 5:
|
||||
hot_plugin_list = hot_plugin_list[:5]
|
||||
@@ -280,11 +274,11 @@ async def _(date_type: QueryDateType | None = None) -> Result:
|
||||
async def system_logs_realtime(websocket: WebSocket, sleep: int = 5):
|
||||
await websocket.accept()
|
||||
logger.debug("ws system_status is connect")
|
||||
try:
|
||||
with contextlib.suppress(
|
||||
WebSocketDisconnect, ConnectionClosedError, ConnectionClosedOK
|
||||
):
|
||||
while websocket.client_state == WebSocketState.CONNECTED:
|
||||
system_status = await get_system_status()
|
||||
await websocket.send_text(system_status.json())
|
||||
await asyncio.sleep(sleep)
|
||||
except (WebSocketDisconnect, ConnectionClosedError, ConnectionClosedOK):
|
||||
pass
|
||||
return
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
import nonebot
|
||||
from fastapi import APIRouter
|
||||
from nonebot.adapters.onebot.v11 import ActionFailed
|
||||
from tortoise.functions import Count
|
||||
from nonebot.adapters.onebot.v11 import ActionFailed
|
||||
|
||||
from zhenxun.configs.config import BotConfig
|
||||
from zhenxun.models.ban_console import BanConsole
|
||||
from zhenxun.models.chat_history import ChatHistory
|
||||
from zhenxun.models.fg_request import FgRequest
|
||||
from zhenxun.models.group_console import GroupConsole
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.models.statistics import Statistics
|
||||
from zhenxun.models.task_info import TaskInfo
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import RequestHandleType, RequestType
|
||||
from zhenxun.utils.exception import NotFoundError
|
||||
from zhenxun.configs.config import BotConfig
|
||||
from zhenxun.models.task_info import TaskInfo
|
||||
from zhenxun.models.fg_request import FgRequest
|
||||
from zhenxun.models.statistics import Statistics
|
||||
from zhenxun.utils.platform import PlatformUtils
|
||||
from zhenxun.models.ban_console import BanConsole
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.utils.exception import NotFoundError
|
||||
from zhenxun.models.chat_history import ChatHistory
|
||||
from zhenxun.models.group_console import GroupConsole
|
||||
from zhenxun.utils.enum import RequestType, RequestHandleType
|
||||
|
||||
from ....base_model import Result
|
||||
from ....config import AVA_URL, GROUP_AVA_URL
|
||||
from ....utils import authentication
|
||||
from ....config import AVA_URL, GROUP_AVA_URL
|
||||
from .model import (
|
||||
ClearRequest,
|
||||
DeleteFriend,
|
||||
Task,
|
||||
Friend,
|
||||
FriendRequestResult,
|
||||
GroupDetail,
|
||||
GroupRequestResult,
|
||||
GroupResult,
|
||||
HandleRequest,
|
||||
LeaveGroup,
|
||||
Plugin,
|
||||
ReqResult,
|
||||
SendMessage,
|
||||
Task,
|
||||
UpdateGroup,
|
||||
LeaveGroup,
|
||||
UserDetail,
|
||||
GroupDetail,
|
||||
GroupResult,
|
||||
SendMessage,
|
||||
UpdateGroup,
|
||||
ClearRequest,
|
||||
DeleteFriend,
|
||||
HandleRequest,
|
||||
GroupRequestResult,
|
||||
FriendRequestResult,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/manage")
|
||||
@@ -47,21 +47,21 @@ async def _(bot_id: str) -> Result:
|
||||
"""
|
||||
获取群信息
|
||||
"""
|
||||
if bots := nonebot.get_bots():
|
||||
if bot_id not in bots:
|
||||
return Result.warning_("指定Bot未连接...")
|
||||
group_list_result = []
|
||||
try:
|
||||
group_list = await bots[bot_id].get_group_list()
|
||||
for g in group_list:
|
||||
gid = g["group_id"]
|
||||
g["ava_url"] = GROUP_AVA_URL.format(gid, gid)
|
||||
group_list_result.append(GroupResult(**g))
|
||||
except Exception as e:
|
||||
logger.error("调用API错误", "/get_group_list", e=e)
|
||||
return Result.fail(f"{type(e)}: {e}")
|
||||
return Result.ok(group_list_result, "拿到了新鲜出炉的数据!")
|
||||
return Result.warning_("无Bot连接...")
|
||||
if not (bots := nonebot.get_bots()):
|
||||
return Result.warning_("无Bot连接...")
|
||||
if bot_id not in bots:
|
||||
return Result.warning_("指定Bot未连接...")
|
||||
group_list_result = []
|
||||
try:
|
||||
group_list = await bots[bot_id].get_group_list()
|
||||
for g in group_list:
|
||||
gid = g["group_id"]
|
||||
g["ava_url"] = GROUP_AVA_URL.format(gid, gid)
|
||||
group_list_result.append(GroupResult(**g))
|
||||
except Exception as e:
|
||||
logger.error("调用API错误", "/get_group_list", e=e)
|
||||
return Result.fail(f"{type(e)}: {e}")
|
||||
return Result.ok(group_list_result, "拿到了新鲜出炉的数据!")
|
||||
|
||||
|
||||
@router.post(
|
||||
@@ -77,12 +77,8 @@ async def _(group: UpdateGroup) -> Result:
|
||||
if group.close_plugins:
|
||||
db_group.block_plugin = ",".join(group.close_plugins) + ","
|
||||
if group.task:
|
||||
block_task = []
|
||||
for t in task_list:
|
||||
if t not in group.task:
|
||||
block_task.append(t)
|
||||
if block_task:
|
||||
db_group.block_task = ",".join(block_task) + ","
|
||||
if block_task := [t for t in task_list if t not in group.task]:
|
||||
db_group.block_task = ",".join(block_task) + "," # type: ignore
|
||||
await db_group.save(
|
||||
update_fields=["level", "status", "block_plugin", "block_task"]
|
||||
)
|
||||
@@ -199,7 +195,7 @@ async def _(parma: HandleRequest) -> Result:
|
||||
return Result.warning_("指定Bot未连接...")
|
||||
try:
|
||||
await FgRequest.refused(bots[bot_id], parma.id)
|
||||
except ActionFailed as e:
|
||||
except ActionFailed:
|
||||
await FgRequest.expire(parma.id)
|
||||
return Result.warning_("请求失败,可能该请求已失效或请求数据错误...")
|
||||
except NotFoundError:
|
||||
@@ -226,22 +222,21 @@ async def _(parma: HandleRequest) -> Result:
|
||||
bot_id = parma.bot_id
|
||||
if bot_id not in nonebot.get_bots():
|
||||
return Result.warning_("指定Bot未连接...")
|
||||
if req := await FgRequest.get_or_none(id=parma.id):
|
||||
if req.request_type == RequestType.GROUP:
|
||||
if group := await GroupConsole.get_group(group_id=req.group_id):
|
||||
group.group_flag = 1
|
||||
await group.save(update_fields=["group_flag"])
|
||||
else:
|
||||
await GroupConsole.update_or_create(
|
||||
group_id=req.group_id,
|
||||
defaults={"group_flag": 1},
|
||||
)
|
||||
else:
|
||||
if not (req := await FgRequest.get_or_none(id=parma.id)):
|
||||
return Result.warning_("未找到此Id请求...")
|
||||
if req.request_type == RequestType.GROUP:
|
||||
if group := await GroupConsole.get_group(group_id=req.group_id):
|
||||
group.group_flag = 1
|
||||
await group.save(update_fields=["group_flag"])
|
||||
else:
|
||||
await GroupConsole.update_or_create(
|
||||
group_id=req.group_id,
|
||||
defaults={"group_flag": 1},
|
||||
)
|
||||
try:
|
||||
await FgRequest.approve(bots[bot_id], parma.id)
|
||||
return Result.ok(info="成功处理了请求!")
|
||||
except ActionFailed as e:
|
||||
except ActionFailed:
|
||||
await FgRequest.expire(parma.id)
|
||||
return Result.warning_("请求失败,可能该请求已失效或请求数据错误...")
|
||||
return Result.warning_("无Bot连接...")
|
||||
@@ -335,99 +330,98 @@ async def _(bot_id: str, user_id: str) -> Result:
|
||||
"/get_group_detail", dependencies=[authentication()], description="获取群组详情"
|
||||
)
|
||||
async def _(bot_id: str, group_id: str) -> Result:
|
||||
if bots := nonebot.get_bots():
|
||||
if bot_id in bots:
|
||||
group = await GroupConsole.get_or_none(group_id=group_id)
|
||||
if not group:
|
||||
return Result.warning_("指定群组未被收录...")
|
||||
like_plugin_list = (
|
||||
await Statistics.filter(group_id=group_id)
|
||||
.annotate(count=Count("id"))
|
||||
.group_by("plugin_name")
|
||||
.order_by("-count")
|
||||
.limit(5)
|
||||
.values_list("plugin_name", "count")
|
||||
if not (bots := nonebot.get_bots()):
|
||||
return Result.warning_("无Bot连接...")
|
||||
if bot_id not in bots:
|
||||
return Result.warning_("未添加指定群组...")
|
||||
group = await GroupConsole.get_or_none(group_id=group_id)
|
||||
if not group:
|
||||
return Result.warning_("指定群组未被收录...")
|
||||
like_plugin_list = (
|
||||
await Statistics.filter(group_id=group_id)
|
||||
.annotate(count=Count("id"))
|
||||
.group_by("plugin_name")
|
||||
.order_by("-count")
|
||||
.limit(5)
|
||||
.values_list("plugin_name", "count")
|
||||
)
|
||||
like_plugin = {}
|
||||
plugins = await PluginInfo.all()
|
||||
module2name = {p.module: p.name for p in plugins}
|
||||
for data in like_plugin_list:
|
||||
name = module2name.get(data[0]) or data[0]
|
||||
like_plugin[name] = data[1]
|
||||
close_plugins = []
|
||||
if group.block_plugin:
|
||||
for module in group.block_plugin.split(","):
|
||||
module_ = module.replace(":super", "")
|
||||
is_super_block = module.endswith(":super")
|
||||
plugin = Plugin(
|
||||
module=module_,
|
||||
plugin_name=module,
|
||||
is_super_block=is_super_block,
|
||||
)
|
||||
like_plugin = {}
|
||||
plugins = await PluginInfo.all()
|
||||
module2name = {p.module: p.name for p in plugins}
|
||||
for data in like_plugin_list:
|
||||
name = module2name.get(data[0]) or data[0]
|
||||
like_plugin[name] = data[1]
|
||||
close_plugins = []
|
||||
if group.block_plugin:
|
||||
for module in group.block_plugin.split(","):
|
||||
module_ = module.replace(":super", "")
|
||||
is_super_block = module.endswith(":super")
|
||||
plugin = Plugin(
|
||||
module=module_,
|
||||
plugin_name=module,
|
||||
is_super_block=is_super_block,
|
||||
)
|
||||
plugin.plugin_name = module2name.get(module) or module
|
||||
close_plugins.append(plugin)
|
||||
all_task = await TaskInfo.annotate().values_list("module", "name")
|
||||
task_module2name = {x[0]: x[1] for x in all_task}
|
||||
task_list = []
|
||||
if group.block_task:
|
||||
split_task = group.block_task.split(",")
|
||||
for task in all_task:
|
||||
task_list.append(
|
||||
Task(
|
||||
name=task[0],
|
||||
zh_name=task_module2name.get(task[0]) or task[0],
|
||||
status=task[0] not in split_task,
|
||||
)
|
||||
)
|
||||
else:
|
||||
for task in all_task:
|
||||
task_list.append(
|
||||
Task(
|
||||
name=task[0],
|
||||
zh_name=task_module2name.get(task[0]) or task[0],
|
||||
status=True,
|
||||
)
|
||||
)
|
||||
group_detail = GroupDetail(
|
||||
group_id=group_id,
|
||||
ava_url=GROUP_AVA_URL.format(group_id, group_id),
|
||||
name=group.group_name,
|
||||
member_count=group.member_count,
|
||||
max_member_count=group.max_member_count,
|
||||
chat_count=await ChatHistory.filter(group_id=group_id).count(),
|
||||
call_count=await Statistics.filter(group_id=group_id).count(),
|
||||
like_plugin=like_plugin,
|
||||
level=group.level,
|
||||
status=group.status,
|
||||
close_plugins=close_plugins,
|
||||
task=task_list,
|
||||
plugin.plugin_name = module2name.get(module) or module
|
||||
close_plugins.append(plugin)
|
||||
all_task = await TaskInfo.annotate().values_list("module", "name")
|
||||
task_module2name = {x[0]: x[1] for x in all_task}
|
||||
task_list = []
|
||||
if group.block_task:
|
||||
split_task = group.block_task.split(",")
|
||||
for task in all_task:
|
||||
task_list.append(
|
||||
Task(
|
||||
name=task[0],
|
||||
zh_name=task_module2name.get(task[0]) or task[0],
|
||||
status=task[0] not in split_task,
|
||||
)
|
||||
)
|
||||
return Result.ok(group_detail)
|
||||
else:
|
||||
return Result.warning_("未添加指定群组...")
|
||||
return Result.warning_("无Bot连接...")
|
||||
else:
|
||||
for task in all_task:
|
||||
task_list.append(
|
||||
Task(
|
||||
name=task[0],
|
||||
zh_name=task_module2name.get(task[0]) or task[0],
|
||||
status=True,
|
||||
)
|
||||
)
|
||||
group_detail = GroupDetail(
|
||||
group_id=group_id,
|
||||
ava_url=GROUP_AVA_URL.format(group_id, group_id),
|
||||
name=group.group_name,
|
||||
member_count=group.member_count,
|
||||
max_member_count=group.max_member_count,
|
||||
chat_count=await ChatHistory.filter(group_id=group_id).count(),
|
||||
call_count=await Statistics.filter(group_id=group_id).count(),
|
||||
like_plugin=like_plugin,
|
||||
level=group.level,
|
||||
status=group.status,
|
||||
close_plugins=close_plugins,
|
||||
task=task_list,
|
||||
)
|
||||
return Result.ok(group_detail)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/send_message", dependencies=[authentication()], description="获取群组详情"
|
||||
)
|
||||
async def _(param: SendMessage) -> Result:
|
||||
if bots := nonebot.get_bots():
|
||||
if param.bot_id in bots:
|
||||
platform = PlatformUtils.get_platform(bots[param.bot_id])
|
||||
if platform != "qq":
|
||||
return Result.warning_("暂不支持该平台...")
|
||||
try:
|
||||
if param.user_id:
|
||||
await bots[param.bot_id].send_private_msg(
|
||||
user_id=str(param.user_id), message=param.message
|
||||
)
|
||||
else:
|
||||
await bots[param.bot_id].send_group_msg(
|
||||
group_id=str(param.group_id), message=param.message
|
||||
)
|
||||
except Exception as e:
|
||||
return Result.fail(str(e))
|
||||
return Result.ok("发送成功!")
|
||||
return Result.warning_("指定Bot未连接...")
|
||||
return Result.warning_("无Bot连接...")
|
||||
if not (bots := nonebot.get_bots()):
|
||||
return Result.warning_("无Bot连接...")
|
||||
if param.bot_id in bots:
|
||||
platform = PlatformUtils.get_platform(bots[param.bot_id])
|
||||
if platform != "qq":
|
||||
return Result.warning_("暂不支持该平台...")
|
||||
try:
|
||||
if param.user_id:
|
||||
await bots[param.bot_id].send_private_msg(
|
||||
user_id=str(param.user_id), message=param.message
|
||||
)
|
||||
else:
|
||||
await bots[param.bot_id].send_group_msg(
|
||||
group_id=str(param.group_id), message=param.message
|
||||
)
|
||||
except Exception as e:
|
||||
return Result.fail(str(e))
|
||||
return Result.ok("发送成功!")
|
||||
return Result.warning_("指定Bot未连接...")
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
import re
|
||||
from typing import Literal
|
||||
|
||||
import nonebot
|
||||
from fastapi import APIRouter
|
||||
from nonebot import on_message
|
||||
from nonebot.adapters.onebot.v11 import MessageEvent
|
||||
from nonebot_plugin_alconna import At, Emoji, Hyper, Image, Text, UniMessage, UniMsg
|
||||
from nonebot_plugin_session import EventSession
|
||||
from starlette.websockets import WebSocket, WebSocketDisconnect, WebSocketState
|
||||
from nonebot.adapters.onebot.v11 import MessageEvent
|
||||
from nonebot_plugin_alconna import At, Text, Hyper, Image, UniMsg
|
||||
from starlette.websockets import WebSocket, WebSocketState, WebSocketDisconnect
|
||||
|
||||
from zhenxun.models.friend_user import FriendUser
|
||||
from zhenxun.models.group_console import GroupConsole
|
||||
from zhenxun.models.group_member_info import GroupInfoUser
|
||||
from zhenxun.utils.depends import UserName
|
||||
from zhenxun.models.group_member_info import GroupInfoUser
|
||||
|
||||
from ....config import AVA_URL, GROUP_AVA_URL
|
||||
from ....config import AVA_URL
|
||||
from .model import Message, MessageItem
|
||||
|
||||
driver = nonebot.get_driver()
|
||||
@@ -27,7 +22,8 @@ ID_LIST = []
|
||||
|
||||
ws_router = APIRouter()
|
||||
|
||||
matcher = on_message(block=False, priority=1)
|
||||
|
||||
matcher = on_message(block=False, priority=1, rule=lambda: bool(ws_conn))
|
||||
|
||||
|
||||
@driver.on_shutdown
|
||||
@@ -44,7 +40,7 @@ async def _(websocket: WebSocket):
|
||||
ws_conn = websocket
|
||||
try:
|
||||
while websocket.client_state == WebSocketState.CONNECTED:
|
||||
recv = await websocket.receive()
|
||||
await websocket.receive()
|
||||
except WebSocketDisconnect:
|
||||
ws_conn = None
|
||||
|
||||
@@ -55,7 +51,7 @@ async def message_handle(
|
||||
):
|
||||
messages = []
|
||||
for m in message:
|
||||
if isinstance(m, (Text, str)):
|
||||
if isinstance(m, Text | str):
|
||||
messages.append(MessageItem(type="text", msg=str(m)))
|
||||
elif isinstance(m, Image):
|
||||
if m.url:
|
||||
@@ -70,18 +66,15 @@ async def message_handle(
|
||||
ID2NAME[group_id] = {}
|
||||
if m.target in ID2NAME[group_id]:
|
||||
uname = ID2NAME[group_id][m.target]
|
||||
else:
|
||||
if group_user := await GroupInfoUser.get_or_none(
|
||||
user_id=m.target, group_id=group_id
|
||||
):
|
||||
uname = group_user.user_name
|
||||
if m.target not in ID2NAME[group_id]:
|
||||
ID2NAME[group_id][m.target] = uname
|
||||
elif group_user := await GroupInfoUser.get_or_none(
|
||||
user_id=m.target, group_id=group_id
|
||||
):
|
||||
uname = group_user.user_name
|
||||
if m.target not in ID2NAME[group_id]:
|
||||
ID2NAME[group_id][m.target] = uname
|
||||
messages.append(MessageItem(type="at", msg=f"@{uname}"))
|
||||
# elif isinstance(m, Emoji):
|
||||
# messages.append(MessageItem(type="text", msg=f"[emoji]"))
|
||||
elif isinstance(m, Hyper):
|
||||
messages.append(MessageItem(type="text", msg=f"[分享消息]"))
|
||||
messages.append(MessageItem(type="text", msg="[分享消息]"))
|
||||
return messages
|
||||
|
||||
|
||||
@@ -91,7 +84,6 @@ async def _(
|
||||
):
|
||||
global ws_conn, ID2NAME, ID_LIST
|
||||
uid = session.id1
|
||||
gid = session.id3 or session.id2
|
||||
if ws_conn and ws_conn.client_state == WebSocketState.CONNECTED and uid:
|
||||
msg_id = event.message_id
|
||||
if msg_id in ID_LIST:
|
||||
@@ -99,6 +91,7 @@ async def _(
|
||||
ID_LIST.append(msg_id)
|
||||
if len(ID_LIST) > 50:
|
||||
ID_LIST = ID_LIST[40:]
|
||||
gid = session.id3 or session.id2
|
||||
messages = await message_handle(message, gid)
|
||||
data = Message(
|
||||
object_id=gid or uid,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from zhenxun.utils.enum import RequestType
|
||||
@@ -232,7 +230,6 @@ class GroupDetail(BaseModel):
|
||||
|
||||
|
||||
class MessageItem(BaseModel):
|
||||
|
||||
type: str
|
||||
"""消息类型"""
|
||||
msg: str
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import re
|
||||
|
||||
import cattrs
|
||||
from fastapi import APIRouter, Query
|
||||
from fastapi import Query, APIRouter
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.models.plugin_info import PluginInfo as DbPluginInfo
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.utils.enum import BlockType, PluginType
|
||||
from zhenxun.models.plugin_info import PluginInfo as DbPluginInfo
|
||||
|
||||
from ....base_model import Result
|
||||
from ....utils import authentication
|
||||
from .model import (
|
||||
PluginConfig,
|
||||
PluginCount,
|
||||
PluginDetail,
|
||||
PluginInfo,
|
||||
PluginCount,
|
||||
PluginConfig,
|
||||
PluginDetail,
|
||||
PluginSwitch,
|
||||
UpdatePlugin,
|
||||
)
|
||||
@@ -23,7 +23,9 @@ router = APIRouter(prefix="/plugin")
|
||||
|
||||
|
||||
@router.get(
|
||||
"/get_plugin_list", dependencies=[authentication()], deprecated="获取插件列表" # type: ignore
|
||||
"/get_plugin_list",
|
||||
dependencies=[authentication()],
|
||||
deprecated="获取插件列表", # type: ignore
|
||||
)
|
||||
async def _(
|
||||
plugin_type: list[PluginType] = Query(None), menu_type: str | None = None
|
||||
@@ -57,7 +59,9 @@ async def _(
|
||||
|
||||
|
||||
@router.get(
|
||||
"/get_plugin_count", dependencies=[authentication()], deprecated="获取插件数量" # type: ignore
|
||||
"/get_plugin_count",
|
||||
dependencies=[authentication()],
|
||||
deprecated="获取插件数量", # type: ignore
|
||||
)
|
||||
async def _() -> Result:
|
||||
plugin_count = PluginCount()
|
||||
@@ -93,10 +97,7 @@ async def _(plugin: UpdatePlugin) -> Result:
|
||||
db_plugin.level = plugin.level
|
||||
db_plugin.menu_type = plugin.menu_type
|
||||
db_plugin.block_type = plugin.block_type
|
||||
if plugin.block_type == BlockType.ALL:
|
||||
db_plugin.status = False
|
||||
else:
|
||||
db_plugin.status = True
|
||||
db_plugin.status = plugin.block_type != BlockType.ALL
|
||||
await db_plugin.save()
|
||||
# 配置项
|
||||
if plugin.configs and (configs := Config.get(plugin.module)):
|
||||
@@ -149,19 +150,15 @@ async def _(module: str) -> Result:
|
||||
for cfg in config.configs:
|
||||
type_str = ""
|
||||
type_inner = None
|
||||
x = str(config.configs[cfg].type)
|
||||
r = re.search(r"<class '(.*)'>", str(config.configs[cfg].type))
|
||||
if r:
|
||||
type_str = r.group(1)
|
||||
else:
|
||||
r = re.search(r"typing\.(.*)\[(.*)\]", str(config.configs[cfg].type))
|
||||
if r:
|
||||
type_str = r.group(1)
|
||||
if type_str:
|
||||
type_str = type_str.lower()
|
||||
type_inner = r.group(2)
|
||||
if type_inner:
|
||||
type_inner = [x.strip() for x in type_inner.split(",")]
|
||||
if r := re.search(r"<class '(.*)'>", str(config.configs[cfg].type)):
|
||||
type_str = r[1]
|
||||
elif r := re.search(r"typing\.(.*)\[(.*)\]", str(config.configs[cfg].type)):
|
||||
type_str = r[1]
|
||||
if type_str:
|
||||
type_str = type_str.lower()
|
||||
type_inner = r[2]
|
||||
if type_inner:
|
||||
type_inner = [x.strip() for x in type_inner.split(",")]
|
||||
config_list.append(
|
||||
PluginConfig(
|
||||
module=module,
|
||||
|
||||
@@ -123,3 +123,8 @@ class PluginDetail(PluginInfo):
|
||||
"""
|
||||
|
||||
config_list: list[PluginConfig]
|
||||
|
||||
|
||||
class PluginIr(BaseModel):
|
||||
id: int
|
||||
"""插件id"""
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
from nonebot import require
|
||||
from fastapi import APIRouter
|
||||
|
||||
from .model import PluginIr
|
||||
from ....base_model import Result
|
||||
from ....utils import authentication
|
||||
|
||||
require("plugin_store")
|
||||
from zhenxun.builtin_plugins.plugin_store import ShopManage
|
||||
|
||||
router = APIRouter(prefix="/store")
|
||||
|
||||
|
||||
@router.get(
|
||||
"/get_plugin_store",
|
||||
dependencies=[authentication()],
|
||||
deprecated="获取插件商店插件信息", # type: ignore
|
||||
)
|
||||
async def _() -> Result:
|
||||
try:
|
||||
data = await ShopManage.get_data()
|
||||
return Result.ok(data)
|
||||
except Exception as e:
|
||||
return Result.fail(f"获取插件商店插件信息失败: {type(e)}: {e}")
|
||||
|
||||
|
||||
@router.post(
|
||||
"/install_plugin",
|
||||
dependencies=[authentication()],
|
||||
deprecated="安装插件", # type: ignore
|
||||
)
|
||||
async def _(param: PluginIr) -> Result:
|
||||
try:
|
||||
result = await ShopManage.add_plugin(param.id) # type: ignore
|
||||
return Result.ok(result)
|
||||
except Exception as e:
|
||||
return Result.fail(f"安装插件失败: {type(e)}: {e}")
|
||||
|
||||
|
||||
@router.post(
|
||||
"/remove_plugin",
|
||||
dependencies=[authentication()],
|
||||
deprecated="移除插件", # type: ignore
|
||||
)
|
||||
async def _(param: PluginIr) -> Result:
|
||||
try:
|
||||
result = await ShopManage.remove_plugin(param.id) # type: ignore
|
||||
return Result.ok(result)
|
||||
except Exception as e:
|
||||
return Result.fail(f"移除插件失败: {type(e)}: {e}")
|
||||
@@ -1,15 +1,15 @@
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
import aiofiles
|
||||
from fastapi import APIRouter
|
||||
|
||||
from zhenxun.utils._build_image import BuildImage
|
||||
|
||||
from ....base_model import Result
|
||||
from ....utils import authentication, get_system_disk
|
||||
from .model import AddFile, DeleteFile, DirFile, RenameFile, SaveFile
|
||||
from .model import AddFile, DirFile, SaveFile, DeleteFile, RenameFile
|
||||
|
||||
router = APIRouter(prefix="/system")
|
||||
|
||||
@@ -19,16 +19,12 @@ IMAGE_TYPE = ["jpg", "jpeg", "png", "gif", "bmp", "webp", "svg"]
|
||||
@router.get(
|
||||
"/get_dir_list", dependencies=[authentication()], description="获取文件列表"
|
||||
)
|
||||
async def _(path: Optional[str] = None) -> Result:
|
||||
async def _(path: str | None = None) -> Result:
|
||||
base_path = Path(path) if path else Path()
|
||||
data_list = []
|
||||
for file in os.listdir(base_path):
|
||||
file_path = base_path / file
|
||||
is_image = False
|
||||
for t in IMAGE_TYPE:
|
||||
if file.endswith(f".{t}"):
|
||||
is_image = True
|
||||
break
|
||||
is_image = any(file.endswith(f".{t}") for t in IMAGE_TYPE)
|
||||
data_list.append(
|
||||
DirFile(
|
||||
is_file=not file_path.is_dir(),
|
||||
@@ -43,7 +39,7 @@ async def _(path: Optional[str] = None) -> Result:
|
||||
@router.get(
|
||||
"/get_resources_size", dependencies=[authentication()], description="获取文件列表"
|
||||
)
|
||||
async def _(full_path: Optional[str] = None) -> Result:
|
||||
async def _(full_path: str | None = None) -> Result:
|
||||
return Result.ok(await get_system_disk(full_path))
|
||||
|
||||
|
||||
@@ -56,7 +52,7 @@ async def _(param: DeleteFile) -> Result:
|
||||
path.unlink()
|
||||
return Result.ok("删除成功!")
|
||||
except Exception as e:
|
||||
return Result.warning_("删除失败: " + str(e))
|
||||
return Result.warning_(f"删除失败: {e!s}")
|
||||
|
||||
|
||||
@router.post(
|
||||
@@ -70,7 +66,7 @@ async def _(param: DeleteFile) -> Result:
|
||||
shutil.rmtree(path.absolute())
|
||||
return Result.ok("删除成功!")
|
||||
except Exception as e:
|
||||
return Result.warning_("删除失败: " + str(e))
|
||||
return Result.warning_(f"删除失败: {e!s}")
|
||||
|
||||
|
||||
@router.post("/rename_file", dependencies=[authentication()], description="重命名文件")
|
||||
@@ -84,7 +80,7 @@ async def _(param: RenameFile) -> Result:
|
||||
path.rename(path.parent / param.name)
|
||||
return Result.ok("重命名成功!")
|
||||
except Exception as e:
|
||||
return Result.warning_("重命名失败: " + str(e))
|
||||
return Result.warning_(f"重命名失败: {e!s}")
|
||||
|
||||
|
||||
@router.post(
|
||||
@@ -101,7 +97,7 @@ async def _(param: RenameFile) -> Result:
|
||||
shutil.move(path.absolute(), new_path.absolute())
|
||||
return Result.ok("重命名成功!")
|
||||
except Exception as e:
|
||||
return Result.warning_("重命名失败: " + str(e))
|
||||
return Result.warning_(f"重命名失败: {e!s}")
|
||||
|
||||
|
||||
@router.post("/add_file", dependencies=[authentication()], description="新建文件")
|
||||
@@ -113,7 +109,7 @@ async def _(param: AddFile) -> Result:
|
||||
path.open("w")
|
||||
return Result.ok("新建文件成功!")
|
||||
except Exception as e:
|
||||
return Result.warning_("新建文件失败: " + str(e))
|
||||
return Result.warning_(f"新建文件失败: {e!s}")
|
||||
|
||||
|
||||
@router.post("/add_folder", dependencies=[authentication()], description="新建文件夹")
|
||||
@@ -125,7 +121,7 @@ async def _(param: AddFile) -> Result:
|
||||
path.mkdir()
|
||||
return Result.ok("新建文件夹成功!")
|
||||
except Exception as e:
|
||||
return Result.warning_("新建文件夹失败: " + str(e))
|
||||
return Result.warning_(f"新建文件夹失败: {e!s}")
|
||||
|
||||
|
||||
@router.get("/read_file", dependencies=[authentication()], description="读取文件")
|
||||
@@ -137,18 +133,18 @@ async def _(full_path: str) -> Result:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
return Result.ok(text)
|
||||
except Exception as e:
|
||||
return Result.warning_("读取文件失败: " + str(e))
|
||||
return Result.warning_(f"读取文件失败: {e!s}")
|
||||
|
||||
|
||||
@router.post("/save_file", dependencies=[authentication()], description="读取文件")
|
||||
async def _(param: SaveFile) -> Result:
|
||||
path = Path(param.full_path)
|
||||
try:
|
||||
with path.open("w") as f:
|
||||
f.write(param.content)
|
||||
async with aiofiles.open(path, "w", encoding="utf-8") as f:
|
||||
await f.write(param.content)
|
||||
return Result.ok("更新成功!")
|
||||
except Exception as e:
|
||||
return Result.warning_("保存文件失败: " + str(e))
|
||||
return Result.warning_(f"保存文件失败: {e!s}")
|
||||
|
||||
|
||||
@router.get("/get_image", dependencies=[authentication()], description="读取图片base64")
|
||||
@@ -159,4 +155,4 @@ async def _(full_path: str) -> Result:
|
||||
try:
|
||||
return Result.ok(BuildImage.open(path).pic2bs4())
|
||||
except Exception as e:
|
||||
return Result.warning_("获取图片失败: " + str(e))
|
||||
return Result.warning_(f"获取图片失败: {e!s}")
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from datetime import datetime
|
||||
from typing import Any, Generic, Optional, TypeVar
|
||||
from typing_extensions import Self
|
||||
from typing import Any, Generic, TypeVar
|
||||
|
||||
from pydantic import BaseModel, validator
|
||||
from typing_extensions import Self
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
@@ -28,7 +28,7 @@ class Result(BaseModel):
|
||||
"""code"""
|
||||
info: str = "操作成功"
|
||||
"""info"""
|
||||
warning: Optional[str] = None
|
||||
warning: str | None = None
|
||||
"""警告信息"""
|
||||
data: Any = None
|
||||
"""返回数据"""
|
||||
@@ -102,7 +102,7 @@ class SystemFolderSize(BaseModel):
|
||||
"""名称"""
|
||||
size: float
|
||||
"""大小"""
|
||||
full_path: Optional[str]
|
||||
full_path: str | None
|
||||
"""完整路径"""
|
||||
is_dir: bool
|
||||
"""是否为文件夹"""
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
import nonebot
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from pydantic import BaseModel
|
||||
from strenum import StrEnum
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
from zhenxun.configs.path_config import DATA_PATH, TEMP_PATH
|
||||
|
||||
WEBUI_STRING = "web_ui"
|
||||
PUBLIC_STRING = "public"
|
||||
|
||||
WEBUI_DATA_PATH = DATA_PATH / WEBUI_STRING
|
||||
PUBLIC_PATH = WEBUI_DATA_PATH / PUBLIC_STRING
|
||||
TMP_PATH = TEMP_PATH / WEBUI_STRING
|
||||
|
||||
WEBUI_DIST_GITHUB_URL = "https://github.com/HibiKier/zhenxun_bot_webui/tree/dist"
|
||||
|
||||
app = nonebot.get_app()
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
from fastapi import APIRouter, FastAPI
|
||||
from fastapi import FastAPI, APIRouter
|
||||
from fastapi.responses import FileResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
from .config import PUBLIC_PATH
|
||||
from .data_source import update_webui_assets
|
||||
from ..config import PUBLIC_PATH
|
||||
from .data_source import COMMAND_NAME, update_webui_assets
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -20,16 +20,24 @@ async def favicon():
|
||||
return FileResponse(PUBLIC_PATH / "favicon.ico")
|
||||
|
||||
|
||||
@router.get("/79edfa81f3308a9f.jfif")
|
||||
async def _():
|
||||
return FileResponse(PUBLIC_PATH / "79edfa81f3308a9f.jfif")
|
||||
|
||||
|
||||
async def init_public(app: FastAPI):
|
||||
try:
|
||||
if not PUBLIC_PATH.exists():
|
||||
await update_webui_assets()
|
||||
folders = await update_webui_assets()
|
||||
else:
|
||||
folders = [x.name for x in PUBLIC_PATH.iterdir() if x.is_dir()]
|
||||
app.include_router(router)
|
||||
for pathname in ["css", "js", "fonts", "img"]:
|
||||
for pathname in folders:
|
||||
logger.debug(f"挂载文件夹: {pathname}")
|
||||
app.mount(
|
||||
f"/{pathname}",
|
||||
StaticFiles(directory=PUBLIC_PATH / pathname, check_dir=True),
|
||||
name=f"public_{pathname}",
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"初始化 web ui assets 失败", "Web UI assets", e=e)
|
||||
logger.error("初始化 WebUI资源 失败", COMMAND_NAME, e=e)
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
from datetime import datetime
|
||||
from pydantic import BaseModel
|
||||
from zhenxun.configs.path_config import DATA_PATH, TEMP_PATH
|
||||
|
||||
|
||||
class PublicData(BaseModel):
|
||||
etag: str
|
||||
update_time: datetime
|
||||
|
||||
|
||||
COMMAND_NAME = "webui_update_assets"
|
||||
|
||||
WEBUI_DATA_PATH = DATA_PATH / "web_ui"
|
||||
PUBLIC_PATH = WEBUI_DATA_PATH / "public"
|
||||
TMP_PATH = TEMP_PATH / "web_ui"
|
||||
|
||||
GITHUB_API_COMMITS = "https://api.github.com/repos/HibiKier/zhenxun_bot_webui/commits"
|
||||
WEBUI_ASSETS_DOWNLOAD_URL = (
|
||||
"https://github.com/HibiKier/zhenxun_bot_webui/archive/refs/heads/dist.zip"
|
||||
)
|
||||
@@ -1,4 +1,3 @@
|
||||
import os
|
||||
import shutil
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
@@ -7,14 +6,20 @@ from nonebot.utils import run_sync
|
||||
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.http_utils import AsyncHttpx
|
||||
from zhenxun.utils.github_utils import GithubUtils
|
||||
|
||||
from .config import COMMAND_NAME, PUBLIC_PATH, TMP_PATH, WEBUI_ASSETS_DOWNLOAD_URL
|
||||
from ..config import TMP_PATH, PUBLIC_PATH, WEBUI_DIST_GITHUB_URL
|
||||
|
||||
COMMAND_NAME = "WebUI资源管理"
|
||||
|
||||
|
||||
async def update_webui_assets():
|
||||
webui_assets_path = TMP_PATH / "webui_assets.zip"
|
||||
download_url = await GithubUtils.parse_github_url(
|
||||
WEBUI_DIST_GITHUB_URL
|
||||
).get_archive_download_urls()
|
||||
if await AsyncHttpx.download_file(
|
||||
WEBUI_ASSETS_DOWNLOAD_URL, webui_assets_path, follow_redirects=True
|
||||
download_url, webui_assets_path, follow_redirects=True
|
||||
):
|
||||
logger.info("下载 webui_assets 成功...", COMMAND_NAME)
|
||||
return await _file_handle(webui_assets_path)
|
||||
@@ -30,10 +35,9 @@ def _file_handle(webui_assets_path: Path):
|
||||
logger.debug("解压 webui_assets 成功...", COMMAND_NAME)
|
||||
else:
|
||||
raise Exception("解压 webui_assets 失败,文件不存在...", COMMAND_NAME)
|
||||
download_file_path = (
|
||||
TMP_PATH / [x for x in os.listdir(TMP_PATH) if (TMP_PATH / x).is_dir()][0]
|
||||
)
|
||||
download_file_path = next(f for f in TMP_PATH.iterdir() if f.is_dir())
|
||||
shutil.rmtree(PUBLIC_PATH, ignore_errors=True)
|
||||
shutil.copytree(download_file_path / "dist", PUBLIC_PATH, dirs_exist_ok=True)
|
||||
logger.debug("复制 webui_assets 成功...", COMMAND_NAME)
|
||||
shutil.rmtree(TMP_PATH, ignore_errors=True)
|
||||
return [x.name for x in PUBLIC_PATH.iterdir() if x.is_dir()]
|
||||
|
||||
Reference in New Issue
Block a user