版本更新 (#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:
HibiKier
2024-10-01 00:42:23 +08:00
committed by GitHub
co-authored by AkashiCoin HibiKier AkashiCoin fanyinrumeng AkashiCoin Elaga
parent daa40ae8b8
commit 4e33bf3a50
216 changed files with 10281 additions and 2538 deletions
+7 -5
View File
@@ -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}
-160
View File
@@ -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
+54 -22
View File
@@ -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",
+53
View File
@@ -0,0 +1,53 @@
from nonebot.permission import SUPERUSER
from nonebot.plugin import PluginMetadata
from nonebot.rule import to_me
from nonebot_plugin_alconna import Alconna, Arparma, on_alconna
from nonebot_plugin_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
+6 -11
View File
@@ -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()
+10 -10
View File
@@ -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")
+52 -6
View File
@@ -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:
+36 -323
View File
@@ -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
+136
View File
@@ -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,
)
+13 -10
View File
@@ -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()
+106 -105
View File
@@ -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,
)
+10 -9
View File
@@ -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(
+56
View File
@@ -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)
+193
View File
@@ -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,
)
+1 -1
View File
@@ -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)} 条数据..."
)
+9 -10
View File
@@ -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()
+89 -74
View File
@@ -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()
+417
View File
@@ -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 -12
View File
@@ -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]
+88 -88
View File
@@ -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(
+23 -10
View File
@@ -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)
+59 -14
View File
@@ -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)
+100 -55
View File
@@ -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))
+13 -6
View File
@@ -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()
+29 -32
View File
@@ -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
+68 -62
View File
@@ -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)
+40 -22
View File
@@ -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}")
+4 -4
View File
@@ -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
"""是否为文件夹"""
+12 -2
View File
@@ -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()]