2024-04-07 00:35:53 +08:00
|
|
|
|
import json
|
2024-03-19 00:27:40 +08:00
|
|
|
|
import os
|
2024-04-06 08:48:21 +08:00
|
|
|
|
import shutil
|
2024-05-31 19:17:25 +08:00
|
|
|
|
import zipfile
|
2024-04-07 00:35:53 +08:00
|
|
|
|
from typing import Any
|
2024-03-19 00:27:40 +08:00
|
|
|
|
|
|
|
|
|
import nonebot
|
|
|
|
|
import yaml
|
|
|
|
|
|
2024-04-07 00:35:53 +08:00
|
|
|
|
from .data import LiteModel
|
2024-04-15 20:21:50 +08:00
|
|
|
|
from .language import Language, get_default_lang_code
|
2024-05-31 22:42:04 +08:00
|
|
|
|
from .ly_function import loaded_functions
|
2024-03-19 00:27:40 +08:00
|
|
|
|
|
2024-04-06 08:48:21 +08:00
|
|
|
|
_loaded_resource_packs: list["ResourceMetadata"] = [] # 按照加载顺序排序
|
|
|
|
|
temp_resource_root = "data/liteyuki/resources"
|
2024-05-31 19:17:25 +08:00
|
|
|
|
temp_extract_root = "data/liteyuki/temp"
|
2024-04-15 20:21:50 +08:00
|
|
|
|
lang = Language(get_default_lang_code())
|
2024-03-19 00:27:40 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ResourceMetadata(LiteModel):
|
|
|
|
|
name: str = "Unknown"
|
|
|
|
|
version: str = "0.0.1"
|
|
|
|
|
description: str = "Unknown"
|
2024-04-07 00:35:53 +08:00
|
|
|
|
path: str = ""
|
|
|
|
|
folder: str = ""
|
2024-03-19 00:27:40 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def load_resource_from_dir(path: str):
|
|
|
|
|
"""
|
2024-04-06 08:48:21 +08:00
|
|
|
|
把资源包按照文件相对路径复制到运行临时文件夹data/liteyuki/resources
|
2024-03-19 00:27:40 +08:00
|
|
|
|
Args:
|
|
|
|
|
path: 资源文件夹
|
|
|
|
|
Returns:
|
|
|
|
|
"""
|
|
|
|
|
if os.path.exists(os.path.join(path, "metadata.yml")):
|
|
|
|
|
with open(os.path.join(path, "metadata.yml"), "r", encoding="utf-8") as f:
|
|
|
|
|
metadata = yaml.safe_load(f)
|
2024-05-31 19:17:25 +08:00
|
|
|
|
elif os.path.isfile(path) and path.endswith(".zip"):
|
|
|
|
|
# zip文件
|
|
|
|
|
# 临时解压并读取metadata.yml
|
|
|
|
|
with zipfile.ZipFile(path, "r") as zip_ref:
|
|
|
|
|
# 解压至临时目录 data/liteyuki/temp/{pack_name}.zip
|
|
|
|
|
zip_ref.extractall(os.path.join(temp_extract_root, os.path.basename(path)))
|
|
|
|
|
with zip_ref.open("metadata.yml") as f:
|
|
|
|
|
metadata = yaml.safe_load(f)
|
|
|
|
|
path = os.path.join(temp_extract_root, os.path.basename(path))
|
2024-03-19 00:27:40 +08:00
|
|
|
|
else:
|
2024-04-06 04:23:01 +08:00
|
|
|
|
# 没有metadata.yml文件,不是一个资源包
|
|
|
|
|
return
|
2024-04-06 08:48:21 +08:00
|
|
|
|
for root, dirs, files in os.walk(path):
|
|
|
|
|
for file in files:
|
|
|
|
|
relative_path = os.path.relpath(os.path.join(root, file), path)
|
|
|
|
|
copy_file(os.path.join(root, file), os.path.join(temp_resource_root, relative_path))
|
2024-03-19 00:27:40 +08:00
|
|
|
|
metadata["path"] = path
|
2024-04-07 00:35:53 +08:00
|
|
|
|
metadata["folder"] = os.path.basename(path)
|
2024-05-31 19:17:25 +08:00
|
|
|
|
|
2024-03-19 00:27:40 +08:00
|
|
|
|
if os.path.exists(os.path.join(path, "lang")):
|
2024-05-31 19:17:25 +08:00
|
|
|
|
# 加载语言
|
2024-05-12 02:47:14 +08:00
|
|
|
|
from liteyuki.utils.base.language import load_from_dir
|
2024-03-19 00:27:40 +08:00
|
|
|
|
load_from_dir(os.path.join(path, "lang"))
|
2024-05-31 19:17:25 +08:00
|
|
|
|
|
|
|
|
|
if os.path.exists(os.path.join(path, "functions")):
|
|
|
|
|
# 加载功能
|
|
|
|
|
from liteyuki.utils.base.ly_function import load_from_dir
|
|
|
|
|
load_from_dir(os.path.join(path, "functions"))
|
|
|
|
|
|
2024-06-02 01:32:52 +08:00
|
|
|
|
if os.path.exists(os.path.join(path, "word_bank")):
|
|
|
|
|
# 加载词库
|
|
|
|
|
from liteyuki.utils.base.word_bank import load_from_dir
|
|
|
|
|
load_from_dir(os.path.join(path, "word_bank"))
|
|
|
|
|
|
2024-04-07 00:35:53 +08:00
|
|
|
|
_loaded_resource_packs.insert(0, ResourceMetadata(**metadata))
|
2024-03-21 01:20:18 +08:00
|
|
|
|
|
|
|
|
|
|
2024-05-31 19:17:25 +08:00
|
|
|
|
def get_path(path: str, abs_path: bool = True, default: Any = None, debug: bool = False) -> str | Any:
|
2024-03-21 01:20:18 +08:00
|
|
|
|
"""
|
|
|
|
|
获取资源包中的文件
|
|
|
|
|
Args:
|
2024-04-11 13:15:29 +08:00
|
|
|
|
debug: 启用调试,每次都会先重载资源
|
2024-03-31 06:22:53 +08:00
|
|
|
|
abs_path: 是否返回绝对路径
|
2024-03-21 01:20:18 +08:00
|
|
|
|
default: 默认
|
|
|
|
|
path: 文件相对路径
|
|
|
|
|
Returns: 文件绝对路径
|
|
|
|
|
"""
|
2024-04-11 13:15:29 +08:00
|
|
|
|
if debug:
|
|
|
|
|
nonebot.logger.debug("Enable resource debug, Reloading resources")
|
|
|
|
|
load_resources()
|
2024-04-06 08:48:21 +08:00
|
|
|
|
resource_relative_path = os.path.join(temp_resource_root, path)
|
|
|
|
|
if os.path.exists(resource_relative_path):
|
|
|
|
|
return os.path.abspath(resource_relative_path) if abs_path else resource_relative_path
|
|
|
|
|
else:
|
|
|
|
|
return default
|
2024-03-31 06:22:53 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_files(path: str, abs_path: bool = False) -> list[str]:
|
|
|
|
|
"""
|
|
|
|
|
获取资源包中一个文件夹的所有文件
|
|
|
|
|
Args:
|
|
|
|
|
abs_path:
|
|
|
|
|
path: 文件夹相对路径
|
|
|
|
|
Returns: 文件绝对路径
|
|
|
|
|
"""
|
2024-04-06 08:48:21 +08:00
|
|
|
|
resource_relative_path = os.path.join(temp_resource_root, path)
|
|
|
|
|
if os.path.exists(resource_relative_path):
|
|
|
|
|
return [os.path.abspath(os.path.join(resource_relative_path, file)) if abs_path else os.path.join(resource_relative_path, file) for file in
|
|
|
|
|
os.listdir(resource_relative_path)]
|
|
|
|
|
else:
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_loaded_resource_packs() -> list[ResourceMetadata]:
|
|
|
|
|
"""
|
2024-04-07 00:35:53 +08:00
|
|
|
|
获取已加载的资源包,优先级从前到后
|
2024-04-06 08:48:21 +08:00
|
|
|
|
Returns: 资源包列表
|
|
|
|
|
"""
|
|
|
|
|
return _loaded_resource_packs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def copy_file(src, dst):
|
|
|
|
|
# 获取目标文件的目录
|
|
|
|
|
dst_dir = os.path.dirname(dst)
|
|
|
|
|
# 如果目标目录不存在,创建它
|
|
|
|
|
if not os.path.exists(dst_dir):
|
|
|
|
|
os.makedirs(dst_dir)
|
|
|
|
|
# 复制文件
|
|
|
|
|
shutil.copy(src, dst)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def load_resources():
|
|
|
|
|
"""用于外部主程序调用的资源加载函数
|
|
|
|
|
Returns:
|
|
|
|
|
"""
|
|
|
|
|
# 加载默认资源和语言
|
|
|
|
|
# 清空临时资源包路径data/liteyuki/resources
|
|
|
|
|
_loaded_resource_packs.clear()
|
2024-05-31 22:42:04 +08:00
|
|
|
|
loaded_functions.clear()
|
2024-04-06 08:48:21 +08:00
|
|
|
|
if os.path.exists(temp_resource_root):
|
|
|
|
|
shutil.rmtree(temp_resource_root)
|
|
|
|
|
os.makedirs(temp_resource_root, exist_ok=True)
|
|
|
|
|
|
2024-04-26 15:11:31 +08:00
|
|
|
|
# 加载内置资源
|
|
|
|
|
standard_resources_path = "liteyuki/resources"
|
|
|
|
|
for resource_dir in os.listdir(standard_resources_path):
|
|
|
|
|
load_resource_from_dir(os.path.join(standard_resources_path, resource_dir))
|
2024-04-07 00:35:53 +08:00
|
|
|
|
|
2024-04-26 15:11:31 +08:00
|
|
|
|
# 加载其他资源包
|
2024-04-07 00:35:53 +08:00
|
|
|
|
if not os.path.exists("resources"):
|
|
|
|
|
os.makedirs("resources", exist_ok=True)
|
|
|
|
|
|
|
|
|
|
if not os.path.exists("resources/index.json"):
|
|
|
|
|
json.dump([], open("resources/index.json", "w", encoding="utf-8"))
|
|
|
|
|
|
|
|
|
|
resource_index: list[str] = json.load(open("resources/index.json", "r", encoding="utf-8"))
|
2024-05-31 19:17:25 +08:00
|
|
|
|
resource_index.reverse() # 优先级高的后加载,但是排在前面
|
2024-04-07 00:35:53 +08:00
|
|
|
|
for resource in resource_index:
|
|
|
|
|
load_resource_from_dir(os.path.join("resources", resource))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_status(name: str) -> bool:
|
|
|
|
|
"""
|
|
|
|
|
检查资源包是否已加载
|
|
|
|
|
Args:
|
|
|
|
|
name: 资源包名称,文件夹名
|
|
|
|
|
Returns: 是否已加载
|
|
|
|
|
"""
|
|
|
|
|
return name in [rp.folder for rp in get_loaded_resource_packs()]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_exist(name: str) -> bool:
|
|
|
|
|
"""
|
|
|
|
|
检查资源包文件夹是否存在于resources文件夹
|
|
|
|
|
Args:
|
|
|
|
|
name: 资源包名称,文件夹名
|
|
|
|
|
Returns: 是否存在
|
|
|
|
|
"""
|
2024-05-31 19:17:25 +08:00
|
|
|
|
path = os.path.join("resources", name)
|
|
|
|
|
return os.path.exists(os.path.join(path, "metadata.yml")) or (os.path.isfile(path) and name.endswith(".zip"))
|
2024-04-07 00:35:53 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def add_resource_pack(name: str) -> bool:
|
|
|
|
|
"""
|
|
|
|
|
添加资源包,该操作仅修改index.json文件,不会加载资源包,要生效请重载资源
|
|
|
|
|
Args:
|
|
|
|
|
name: 资源包名称,文件夹名
|
|
|
|
|
Returns:
|
|
|
|
|
"""
|
|
|
|
|
if check_exist(name):
|
|
|
|
|
old_index: list[str] = json.load(open("resources/index.json", "r", encoding="utf-8"))
|
|
|
|
|
if name not in old_index:
|
|
|
|
|
old_index.append(name)
|
|
|
|
|
json.dump(old_index, open("resources/index.json", "w", encoding="utf-8"))
|
|
|
|
|
load_resource_from_dir(os.path.join("resources", name))
|
|
|
|
|
return True
|
|
|
|
|
else:
|
|
|
|
|
nonebot.logger.warning(lang.get("liteyuki.resource_loaded", name=name))
|
|
|
|
|
return False
|
|
|
|
|
else:
|
|
|
|
|
nonebot.logger.warning(lang.get("liteyuki.resource_not_exist", name=name))
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def remove_resource_pack(name: str) -> bool:
|
|
|
|
|
"""
|
|
|
|
|
移除资源包,该操作仅修改加载索引,要生效请重载资源
|
|
|
|
|
Args:
|
|
|
|
|
name: 资源包名称,文件夹名
|
|
|
|
|
Returns:
|
|
|
|
|
"""
|
|
|
|
|
if check_exist(name):
|
|
|
|
|
old_index: list[str] = json.load(open("resources/index.json", "r", encoding="utf-8"))
|
|
|
|
|
if name in old_index:
|
|
|
|
|
old_index.remove(name)
|
|
|
|
|
json.dump(old_index, open("resources/index.json", "w", encoding="utf-8"))
|
|
|
|
|
return True
|
|
|
|
|
else:
|
|
|
|
|
nonebot.logger.warning(lang.get("liteyuki.resource_not_loaded", name=name))
|
|
|
|
|
return False
|
|
|
|
|
else:
|
|
|
|
|
nonebot.logger.warning(lang.get("liteyuki.resource_not_exist", name=name))
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def change_priority(name: str, delta: int) -> bool:
|
|
|
|
|
"""
|
|
|
|
|
修改资源包优先级
|
|
|
|
|
Args:
|
|
|
|
|
name: 资源包名称,文件夹名
|
|
|
|
|
delta: 优先级变化,正数表示后移,负数表示前移,0表示移到最前
|
|
|
|
|
Returns:
|
|
|
|
|
"""
|
|
|
|
|
# 正数表示前移,负数表示后移
|
|
|
|
|
old_resource_list: list[str] = json.load(open("resources/index.json", "r", encoding="utf-8"))
|
|
|
|
|
new_resource_list = old_resource_list.copy()
|
|
|
|
|
if name in old_resource_list:
|
|
|
|
|
index = old_resource_list.index(name)
|
|
|
|
|
if 0 <= index + delta < len(old_resource_list):
|
|
|
|
|
new_index = index + delta
|
|
|
|
|
new_resource_list.remove(name)
|
|
|
|
|
new_resource_list.insert(new_index, name)
|
|
|
|
|
json.dump(new_resource_list, open("resources/index.json", "w", encoding="utf-8"))
|
|
|
|
|
return True
|
|
|
|
|
else:
|
|
|
|
|
nonebot.logger.warning("Priority change failed, out of range")
|
|
|
|
|
return False
|
|
|
|
|
else:
|
|
|
|
|
nonebot.logger.debug("Priority change failed, resource not loaded")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_resource_metadata(name: str) -> ResourceMetadata:
|
|
|
|
|
"""
|
|
|
|
|
获取资源包元数据
|
|
|
|
|
Args:
|
|
|
|
|
name: 资源包名称,文件夹名
|
|
|
|
|
Returns:
|
|
|
|
|
"""
|
|
|
|
|
for rp in get_loaded_resource_packs():
|
|
|
|
|
if rp.folder == name:
|
|
|
|
|
return rp
|
|
|
|
|
return ResourceMetadata()
|