🚀 资源文件单独下载,分离被动任务初始化

This commit is contained in:
Flern 2024-12-27 17:26:27 +08:00
parent 35014e4048
commit a80ebf964f
13 changed files with 302 additions and 64 deletions

View File

@ -16,6 +16,7 @@ from zhenxun.models.sign_user import SignUser
from zhenxun.models.user_console import UserConsole from zhenxun.models.user_console import UserConsole
from zhenxun.services.log import logger from zhenxun.services.log import logger
from zhenxun.utils.decorator.shop import shop_register from zhenxun.utils.decorator.shop import shop_register
from zhenxun.utils.manager.resource_manager import ResourceManager
from zhenxun.utils.platform import PlatformUtils from zhenxun.utils.platform import PlatformUtils
driver: Driver = nonebot.get_driver() driver: Driver = nonebot.get_driver()
@ -65,6 +66,7 @@ from public.bag_users t1
@driver.on_startup @driver.on_startup
async def _(): async def _():
await ResourceManager.init_resources()
"""签到与用户的数据迁移""" """签到与用户的数据迁移"""
if goods_list := await GoodsInfo.filter(uuid__isnull=True).all(): if goods_list := await GoodsInfo.filter(uuid__isnull=True).all():
for goods in goods_list: for goods in goods_list:

View File

@ -34,7 +34,6 @@ async def _handle_setting(
plugin: Plugin, plugin: Plugin,
plugin_list: list[PluginInfo], plugin_list: list[PluginInfo],
limit_list: list[PluginLimit], limit_list: list[PluginLimit],
task_list: list[tuple[bool, TaskInfo]],
): ):
"""处理插件设置 """处理插件设置
@ -91,20 +90,6 @@ async def _handle_setting(
) )
for limit in extra_data.limits 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
)
@driver.on_startup @driver.on_startup
@ -114,14 +99,13 @@ async def _():
""" """
plugin_list: list[PluginInfo] = [] plugin_list: list[PluginInfo] = []
limit_list: list[PluginLimit] = [] limit_list: list[PluginLimit] = []
task_list = []
module2id = {} module2id = {}
load_plugin = [] load_plugin = []
if module_list := await PluginInfo.all().values("id", "module_path"): if module_list := await PluginInfo.all().values("id", "module_path"):
module2id = {m["module_path"]: m["id"] for m in module_list} module2id = {m["module_path"]: m["id"] for m in module_list}
for plugin in get_loaded_plugins(): for plugin in get_loaded_plugins():
load_plugin.append(plugin.module_name) load_plugin.append(plugin.module_name)
await _handle_setting(plugin, plugin_list, limit_list, task_list) await _handle_setting(plugin, plugin_list, limit_list)
create_list = [] create_list = []
update_list = [] update_list = []
for plugin in plugin_list: for plugin in plugin_list:
@ -170,33 +154,6 @@ async def _():
# limit_create.append(limit) # limit_create.append(limit)
# if limit_create: # if limit_create:
# await PluginLimit.bulk_create(limit_create, 10) # 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")
}
create_list = []
update_list = []
for status, task in task_list:
if task.module not in module_dict:
create_list.append((status, task))
else:
task.id = module_dict[task.module]
update_list.append(task)
if create_list:
_create_list = [t[1] for t in create_list]
await TaskInfo.bulk_create(_create_list, 10)
if block := [t[1].module for t in create_list if not t[0]]:
block_task = ",".join(block) + ","
if group_list := await GroupConsole.all():
for group in group_list:
group.block_task += block_task
await GroupConsole.bulk_update(group_list, ["block_task"], 10)
if update_list:
await TaskInfo.bulk_update(
update_list,
["run_time", "name"],
10,
)
await data_migration() await data_migration()
await PluginInfo.filter(module_path__in=load_plugin).update(load_status=True) 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) await PluginInfo.filter(module_path__not_in=load_plugin).update(load_status=False)

View File

@ -0,0 +1,164 @@
import nonebot
from nonebot import get_loaded_plugins
from nonebot.drivers import Driver
from nonebot.plugin import Plugin
from nonebot.utils import is_coroutine_callable
from nonebot_plugin_apscheduler import scheduler
from zhenxun.configs.utils import PluginExtraData, Task
from zhenxun.models.group_console import GroupConsole
from zhenxun.models.task_info import TaskInfo
from zhenxun.services.log import logger
from zhenxun.utils.common_utils import CommonUtils
driver: Driver = nonebot.get_driver()
async def _handle_setting(
plugin: Plugin,
task_info_list: list[tuple[bool, TaskInfo]],
task_list: list[Task],
):
"""处理插件设置
参数:
plugin: Plugin
task_info_list: 被动技能db数据列表
task_list: 被动技能列表
"""
metadata = plugin.metadata
if not metadata:
return
extra = metadata.extra
extra_data = PluginExtraData(**extra)
if extra_data.tasks:
task_info_list.extend(
(
task.create_status,
TaskInfo(
module=task.module,
name=task.name,
status=task.status,
default_status=task.default_status,
),
)
for task in extra_data.tasks
)
task_list.extend(extra_data.tasks)
async def update_to_group(create_list: list[tuple[bool, TaskInfo]]):
"""根据创建时状态对群组进行被动技能更新
参数:
create_list: 被动技能创建列表
"""
if blocks := [t[1].module for t in create_list if not t[0]]:
if group_list := await GroupConsole.all():
for group in group_list:
block_tasks = list(
set(CommonUtils.convert_module_format(group.block_task) + blocks)
)
group.block_task = CommonUtils.convert_module_format(block_tasks)
await GroupConsole.bulk_update(group_list, ["block_task"], 10)
async def to_db(
load_task: list[str],
create_list: list[tuple[bool, TaskInfo]],
update_list: list[TaskInfo],
):
"""将被动技能保存至数据库
参数:
load_task: 已加载的被动技能模块
create_list: 被动技能创建列表
update_list: 被动技能更新列表
"""
if create_list:
_create_list = [t[1] for t in create_list]
await TaskInfo.bulk_create(_create_list, 10)
await update_to_group(create_list)
if update_list:
await TaskInfo.bulk_update(
update_list,
["run_time", "name"],
10,
)
if load_task:
await TaskInfo.filter(module__in=load_task).update(load_status=True)
await TaskInfo.filter(module__not_in=load_task).update(load_status=False)
async def get_run_task(task: Task, *args, **kwargs):
is_run = False
if task.check:
if is_coroutine_callable(task.check):
if await task.check(*task.check_args):
is_run = True
elif task.check(*task.check_args):
is_run = True
else:
bot = task.check_args[0]
group_id = task.check_args[1]
if not await CommonUtils.task_is_block(bot, task.module, group_id):
is_run = True
if is_run and task.run_func:
if is_coroutine_callable(task.run_func):
await task.run_func(*args, **kwargs)
else:
task.run_func(*args, **kwargs)
async def create_schedule(task: Task):
scheduler_model = task.scheduler
if not scheduler_model or not task.run_func:
return
try:
scheduler.add_job(
get_run_task,
scheduler_model.trigger,
run_date=scheduler_model.run_date,
hour=scheduler_model.hour,
minute=scheduler_model.minute,
second=scheduler_model.second,
id=scheduler_model.id,
max_instances=scheduler_model.max_instances,
args=scheduler_model.args,
kwargs=scheduler_model.kwargs,
)
logger.debug(f"成功动态创建定时任务: {task.name}({task.module})")
except Exception as e:
logger.error(f"动态创建定时任务 {task.name}({task.module}) 失败", e=e)
@driver.on_startup
async def _():
"""
初始化插件数据配置
"""
task_list: list[Task] = []
task_info_list: list[tuple[bool, TaskInfo]] = []
for plugin in get_loaded_plugins():
await _handle_setting(plugin, task_info_list, task_list)
if not task_info_list:
await TaskInfo.all().update(load_status=False)
return
module_dict = {t[1]: t[0] for t in await TaskInfo.all().values_list("id", "module")}
load_task = []
create_list = []
update_list = []
for status, task in task_info_list:
if task.module not in module_dict:
create_list.append((status, task))
else:
task.id = module_dict[task.module]
update_list.append(task)
load_task.append(task.module)
await to_db(load_task, create_list, update_list)
# db_task = await TaskInfo.filter(load_status=True, status=True).values_list(
# "module", flat=True
# )
# task_list = [t for t in task_list if t.module in db_task]
# for task in task_list:
# create_schedule(task)

View File

@ -57,7 +57,7 @@ LG_MESSAGE = [
async def init_image(): async def init_image():
SIGN_RESOURCE_PATH.mkdir(parents=True, exist_ok=True) SIGN_RESOURCE_PATH.mkdir(parents=True, exist_ok=True)
SIGN_TODAY_CARD_PATH.mkdir(exist_ok=True, parents=True) SIGN_TODAY_CARD_PATH.mkdir(exist_ok=True, parents=True)
await generate_progress_bar_pic() # await generate_progress_bar_pic()
clear_sign_data_pic() clear_sign_data_pic()
@ -86,6 +86,7 @@ async def get_card(
返回: 返回:
Path: 卡片路径 Path: 卡片路径
""" """
await generate_progress_bar_pic()
user_id = user.user_id user_id = user.user_id
date = datetime.now().date() date = datetime.now().date()
_type = "view" if is_card_view else "sign" _type = "view" if is_card_view else "sign"
@ -296,6 +297,10 @@ async def generate_progress_bar_pic():
""" """
初始化进度条图片 初始化进度条图片
""" """
bar_white_file = SIGN_RESOURCE_PATH / "bar_white.png"
if bar_white_file.exists():
return
bg_2 = (254, 1, 254) bg_2 = (254, 1, 254)
bg_1 = (0, 245, 246) bg_1 = (0, 245, 246)
@ -335,7 +340,7 @@ async def generate_progress_bar_pic():
await bk.paste(img_y, (0, 0)) await bk.paste(img_y, (0, 0))
await bk.paste(A, (25, 0)) await bk.paste(A, (25, 0))
await bk.paste(img_x, (975, 0)) await bk.paste(img_x, (975, 0))
await bk.save(SIGN_RESOURCE_PATH / "bar_white.png") await bk.save(bar_white_file)
def get_level_and_next_impression(impression: float) -> tuple[str, int | float, int]: def get_level_and_next_impression(impression: float) -> tuple[str, int | float, int]:

View File

@ -16,7 +16,7 @@ router = APIRouter(prefix="/menu")
dependencies=[authentication()], dependencies=[authentication()],
response_model=Result[list[MenuData]], response_model=Result[list[MenuData]],
response_class=JSONResponse, response_class=JSONResponse,
deprecated="获取菜单列表", # type: ignore description="获取菜单列表",
) )
async def _() -> Result[list[MenuData]]: async def _() -> Result[list[MenuData]]:
try: try:

View File

@ -23,7 +23,7 @@ driver = nonebot.get_driver()
dependencies=[authentication()], dependencies=[authentication()],
response_model=Result[list[BotInfo]], response_model=Result[list[BotInfo]],
response_class=JSONResponse, response_class=JSONResponse,
deprecated="获取bot列表", # type: ignore description="获取bot列表", # type: ignore
) )
async def _() -> Result[list[BotInfo]]: async def _() -> Result[list[BotInfo]]:
try: try:
@ -74,7 +74,7 @@ async def _(bot_id: str | None = None) -> Result[AllChatAndCallCount]:
dependencies=[authentication()], dependencies=[authentication()],
response_model=Result[ChatCallMonthCount], response_model=Result[ChatCallMonthCount],
response_class=JSONResponse, response_class=JSONResponse,
deprecated="获取聊天/调用记录的一个月数量", # type: ignore description="获取聊天/调用记录的一个月数量", # type: ignore
) )
async def _(bot_id: str | None = None) -> Result[ChatCallMonthCount]: async def _(bot_id: str | None = None) -> Result[ChatCallMonthCount]:
try: try:
@ -91,7 +91,7 @@ async def _(bot_id: str | None = None) -> Result[ChatCallMonthCount]:
dependencies=[authentication()], dependencies=[authentication()],
response_model=Result[BaseResultModel], response_model=Result[BaseResultModel],
response_class=JSONResponse, response_class=JSONResponse,
deprecated="获取Bot连接记录", # type: ignore description="获取Bot连接记录", # type: ignore
) )
async def _(query: QueryModel) -> Result[BaseResultModel]: async def _(query: QueryModel) -> Result[BaseResultModel]:
try: try:
@ -106,7 +106,7 @@ async def _(query: QueryModel) -> Result[BaseResultModel]:
dependencies=[authentication()], dependencies=[authentication()],
response_model=Result[Config], response_model=Result[Config],
response_class=JSONResponse, response_class=JSONResponse,
deprecated="获取nb配置", # type: ignore description="获取nb配置", # type: ignore
) )
async def _() -> Result[Config]: async def _() -> Result[Config]:
return Result.ok(driver.config) return Result.ok(driver.config)

View File

@ -85,9 +85,6 @@ async def _(group: UpdateGroup) -> Result[str]:
description="获取好友列表", description="获取好友列表",
) )
async def _(bot_id: str) -> Result[list[Friend]]: async def _(bot_id: str) -> Result[list[Friend]]:
"""
获取群信息
"""
try: try:
bot = nonebot.get_bot(bot_id) bot = nonebot.get_bot(bot_id)
friend_list, _ = await PlatformUtils.get_friend_list(bot) friend_list, _ = await PlatformUtils.get_friend_list(bot)
@ -95,7 +92,7 @@ async def _(bot_id: str) -> Result[list[Friend]]:
for f in friend_list: for f in friend_list:
ava_url = AVA_URL.format(f.user_id) ava_url = AVA_URL.format(f.user_id)
result_list.append( result_list.append(
Friend(user_id=f.user_id, nickname=f.nickname, ava_url=ava_url) Friend(user_id=f.user_id, nickname=f.user_name or "", ava_url=ava_url)
) )
return Result.ok( return Result.ok(
result_list, result_list,

View File

@ -24,7 +24,7 @@ router = APIRouter(prefix="/plugin")
dependencies=[authentication()], dependencies=[authentication()],
response_model=Result[list[PluginInfo]], response_model=Result[list[PluginInfo]],
response_class=JSONResponse, response_class=JSONResponse,
deprecated="获取插件列表", # type: ignore description="获取插件列表", # type: ignore
) )
async def _( async def _(
plugin_type: list[PluginType] = Query(None), menu_type: str | None = None plugin_type: list[PluginType] = Query(None), menu_type: str | None = None
@ -43,7 +43,7 @@ async def _(
dependencies=[authentication()], dependencies=[authentication()],
response_model=Result[int], response_model=Result[int],
response_class=JSONResponse, response_class=JSONResponse,
deprecated="获取插件数量", # type: ignore description="获取插件数量", # type: ignore
) )
async def _() -> Result[int]: async def _() -> Result[int]:
try: try:

View File

@ -16,7 +16,7 @@ router = APIRouter(prefix="/store")
dependencies=[authentication()], dependencies=[authentication()],
response_model=Result[dict], response_model=Result[dict],
response_class=JSONResponse, response_class=JSONResponse,
deprecated="获取插件商店插件信息", # type: ignore description="获取插件商店插件信息", # type: ignore
) )
async def _() -> Result[dict]: async def _() -> Result[dict]:
try: try:
@ -41,7 +41,7 @@ async def _() -> Result[dict]:
dependencies=[authentication()], dependencies=[authentication()],
response_model=Result, response_model=Result,
response_class=JSONResponse, response_class=JSONResponse,
deprecated="安装插件", # type: ignore description="安装插件", # type: ignore
) )
async def _(param: PluginIr) -> Result: async def _(param: PluginIr) -> Result:
try: try:
@ -59,7 +59,7 @@ async def _(param: PluginIr) -> Result:
dependencies=[authentication()], dependencies=[authentication()],
response_model=Result, response_model=Result,
response_class=JSONResponse, response_class=JSONResponse,
deprecated="更新插件", # type: ignore description="更新插件", # type: ignore
) )
async def _(param: PluginIr) -> Result: async def _(param: PluginIr) -> Result:
try: try:
@ -77,7 +77,7 @@ async def _(param: PluginIr) -> Result:
dependencies=[authentication()], dependencies=[authentication()],
response_model=Result, response_model=Result,
response_class=JSONResponse, response_class=JSONResponse,
deprecated="移除插件", # type: ignore description="移除插件", # type: ignore
) )
async def _(param: PluginIr) -> Result: async def _(param: PluginIr) -> Result:
try: try:

View File

@ -1,7 +1,8 @@
from collections.abc import Callable from collections.abc import Callable
import copy import copy
from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any, Literal
import cattrs import cattrs
from pydantic import BaseModel from pydantic import BaseModel
@ -157,6 +158,29 @@ class PluginSetting(BaseModel):
"""调用插件花费金币""" """调用插件花费金币"""
class SchedulerModel(BaseModel):
trigger: Literal["date", "interval", "cron"]
"""trigger"""
day: int | None = None
"""天数"""
hour: int | None = None
"""小时"""
minute: int | None = None
"""分钟"""
second: int | None = None
""""""
run_date: datetime | None = None
"""运行日期"""
id: str | None = None
"""id"""
max_instances: int | None = None
"""最大运行实例"""
args: list | None = None
"""参数"""
kwargs: dict | None = None
"""参数"""
class Task(BaseBlock): class Task(BaseBlock):
module: str module: str
"""被动技能模块名""" """被动技能模块名"""
@ -168,8 +192,14 @@ class Task(BaseBlock):
"""初次加载默认开关状态""" """初次加载默认开关状态"""
default_status: bool = True default_status: bool = True
"""进群时默认状态""" """进群时默认状态"""
run_time: str | None = None scheduler: SchedulerModel | None = None
"""运行时间""" """定时任务配置"""
run_func: Callable | None = None
"""运行函数"""
check: Callable | None = None
"""检查函数"""
check_args: list = []
"""检查函数参数"""
class PluginExtraData(BaseModel): class PluginExtraData(BaseModel):

View File

@ -12,6 +12,8 @@ class TaskInfo(Model):
"""被动技能名称""" """被动技能名称"""
status = fields.BooleanField(default=True, description="全局开关状态") status = fields.BooleanField(default=True, description="全局开关状态")
"""全局开关状态""" """全局开关状态"""
load_status = fields.BooleanField(default=True, description="进群默认开关状态")
"""加载状态"""
default_status = fields.BooleanField(default=True, description="进群默认开关状态") default_status = fields.BooleanField(default=True, description="进群默认开关状态")
"""全局开关状态""" """全局开关状态"""
run_time = fields.CharField(255, null=True, description="运行时间") run_time = fields.CharField(255, null=True, description="运行时间")
@ -27,5 +29,6 @@ class TaskInfo(Model):
async def _run_script(cls): async def _run_script(cls):
return [ return [
"ALTER TABLE task_info ADD default_status boolean DEFAULT true;", "ALTER TABLE task_info ADD default_status boolean DEFAULT true;",
"ALTER TABLE task_info ADD load_status boolean DEFAULT false;",
# 默认状态 # 默认状态
] ]

View File

@ -285,6 +285,7 @@ class AsyncHttpx:
response.raise_for_status() response.raise_for_status()
logger.info( logger.info(
f"开始下载 {path.name}.. " f"开始下载 {path.name}.. "
f"Url: {u}.. "
f"Path: {path.absolute()}" f"Path: {path.absolute()}"
) )
async with aiofiles.open(path, "wb") as wf: async with aiofiles.open(path, "wb") as wf:

View File

@ -0,0 +1,79 @@
import os
from pathlib import Path
import shutil
import zipfile
from zhenxun.configs.path_config import FONT_PATH
from zhenxun.services.log import logger
from zhenxun.utils.github_utils import GithubUtils
from zhenxun.utils.http_utils import AsyncHttpx
CMD_STRING = "ResourceManager"
class ResourceManager:
GITHUB_URL = "https://github.com/zhenxun-org/zhenxun-bot-resources/tree/main"
RESOURCE_PATH = Path() / "resources"
TMP_PATH = Path() / "_resource_tmp"
ZIP_FILE = TMP_PATH / "resources.zip"
UNZIP_PATH = None
@classmethod
async def init_resources(cls):
if FONT_PATH.exists() and os.listdir(FONT_PATH):
return
if cls.TMP_PATH.exists():
logger.debug(
"resources临时文件夹已存在移除resources临时文件夹", CMD_STRING
)
shutil.rmtree(cls.TMP_PATH)
cls.TMP_PATH.mkdir(parents=True, exist_ok=True)
try:
await cls.__download_resources()
cls.file_handle()
except Exception as e:
logger.error("获取resources资源包失败", CMD_STRING, e=e)
if cls.TMP_PATH.exists():
logger.debug("移除resources临时文件夹", CMD_STRING)
shutil.rmtree(cls.TMP_PATH)
@classmethod
def file_handle(cls):
if not cls.UNZIP_PATH:
return
cls.__recursive_folder(cls.UNZIP_PATH, "resources")
@classmethod
def __recursive_folder(cls, dir: Path, parent_path: str):
for file in dir.iterdir():
if file.is_dir():
cls.__recursive_folder(file, f"{parent_path}/{file.name}")
else:
res_file = Path(parent_path) / file.name
if res_file.exists():
res_file.unlink()
res_file.parent.mkdir(parents=True, exist_ok=True)
file.rename(res_file)
@classmethod
async def __download_resources(cls):
"""获取resources文件夹"""
repo_info = GithubUtils.parse_github_url(cls.GITHUB_URL)
url = await repo_info.get_archive_download_urls()
logger.debug("开始下载resources资源包...", CMD_STRING)
if not await AsyncHttpx.download_file(url, cls.ZIP_FILE, stream=True):
raise Exception("下载resources资源包失败...")
logger.debug("下载resources资源文件压缩包完成...", CMD_STRING)
tf = zipfile.ZipFile(cls.ZIP_FILE)
tf.extractall(cls.TMP_PATH)
logger.debug("解压文件压缩包完成...", CMD_STRING)
download_file_path = cls.TMP_PATH / next(
x for x in os.listdir(cls.TMP_PATH) if (cls.TMP_PATH / x).is_dir()
)
cls.UNZIP_PATH = download_file_path / "resources"
if tf:
tf.close()