mirror of
https://github.com/TriM-Organization/LiteyukiBot-TriM.git
synced 2026-06-14 00:22:26 +00:00
🔀手动Merge轻雪主仓库a77f97f
This commit is contained in:
+3
-43
@@ -1,13 +1,9 @@
|
||||
import json
|
||||
import os.path
|
||||
import platform
|
||||
import sys
|
||||
import time
|
||||
|
||||
import nonebot
|
||||
|
||||
__NAME__ = "尹灵温|轻雪-睿乐"
|
||||
__VERSION__ = "6.3.4" # 60201
|
||||
__VERSION__ = "6.3.9" # 60201
|
||||
|
||||
import requests
|
||||
|
||||
@@ -15,36 +11,10 @@ from src.utils.base.config import load_from_yaml, config
|
||||
from src.utils.base.log import init_log
|
||||
from git import Repo
|
||||
|
||||
|
||||
major, minor, patch = map(int, __VERSION__.split("."))
|
||||
__VERSION_I__ = 99000000 + major * 10000 + minor * 100 + patch
|
||||
|
||||
|
||||
def register_bot():
|
||||
url = "https://api.liteyuki.icu/register"
|
||||
data = {
|
||||
"name": __NAME__,
|
||||
"version": __VERSION__,
|
||||
"version_i": __VERSION_I__,
|
||||
"python": f"{platform.python_implementation()} {platform.python_version()}",
|
||||
"os": f"{platform.system()} {platform.version()} {platform.machine()}",
|
||||
}
|
||||
try:
|
||||
nonebot.logger.info("正在等待 Liteyuki 注册服务器…")
|
||||
resp = requests.post(url, json=data, timeout=(10, 15))
|
||||
if resp.status_code == 200:
|
||||
data = resp.json()
|
||||
if liteyuki_id := data.get("liteyuki_id"):
|
||||
with open("data/liteyuki/liteyuki.json", "wb") as f:
|
||||
f.write(json.dumps(data).encode("utf-8"))
|
||||
nonebot.logger.success(f"成功将 {liteyuki_id} 注册到 Liteyuki 服务器")
|
||||
else:
|
||||
raise ValueError(f"无法向 Liteyuki 服务器注册:{data}")
|
||||
|
||||
except Exception as e:
|
||||
nonebot.logger.warning(f"向 Liteyuki 服务器注册失败,但无所谓:{e}")
|
||||
|
||||
|
||||
def init():
|
||||
"""
|
||||
初始化
|
||||
@@ -64,25 +34,15 @@ def init():
|
||||
repo = Repo(".")
|
||||
except Exception as e:
|
||||
nonebot.logger.error(
|
||||
f"无法读取 Git 仓库 {e},你是否是从仓库内下载的Zip文件?请使用git clone。"
|
||||
f"无法读取 Git 仓库 `{e}`,你是否是从仓库直接下载的Zip文件?请使用git clone。"
|
||||
)
|
||||
|
||||
# temp_data: TempConfig = common_db.where_one(TempConfig(), default=TempConfig())
|
||||
# temp_data.data["start_time"] = time.time()
|
||||
# common_db.save(temp_data)
|
||||
|
||||
# 在加载完成语言后再初始化日志
|
||||
nonebot.logger.info("尹灵温 正在初始化…")
|
||||
|
||||
if not os.path.exists("data/liteyuki/liteyuki.json"):
|
||||
register_bot()
|
||||
|
||||
if not os.path.exists("pyproject.toml"):
|
||||
with open("pyproject.toml", "w", encoding="utf-8") as f:
|
||||
f.write("[tool.nonebot]\n")
|
||||
|
||||
nonebot.logger.info(
|
||||
"正在 {} Python{}.{}.{} 上运行 尹灵温".format(
|
||||
"正在 {} Python{}.{}.{} 上运行 尹灵温-NoneBot".format(
|
||||
sys.executable,
|
||||
sys.version_info.major,
|
||||
sys.version_info.minor,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import threading
|
||||
|
||||
from nonebot import logger
|
||||
from liteyuki.comm.channel import get_channel
|
||||
from liteyuki.comm.channel import active_channel
|
||||
|
||||
|
||||
def reload(delay: float = 0.0, receiver: str = "nonebot"):
|
||||
@@ -14,13 +13,9 @@ def reload(delay: float = 0.0, receiver: str = "nonebot"):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
chan = get_channel(receiver + "-active")
|
||||
if chan is None:
|
||||
logger.error(f"未见 Channel {receiver}-active 以至无法重载")
|
||||
return
|
||||
|
||||
if delay > 0:
|
||||
threading.Timer(delay, chan.send, args=(1,)).start()
|
||||
threading.Timer(delay, active_channel.send, args=(1,)).start()
|
||||
return
|
||||
else:
|
||||
chan.send(1)
|
||||
active_channel.send(1)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import os
|
||||
import platform
|
||||
from typing import List
|
||||
|
||||
import nonebot
|
||||
@@ -7,6 +8,7 @@ from pydantic import BaseModel
|
||||
|
||||
from ..message.tools import random_hex_string
|
||||
|
||||
|
||||
config = {} # 全局配置,确保加载后读取
|
||||
|
||||
|
||||
@@ -29,23 +31,37 @@ class BasicConfig(BaseModel):
|
||||
superusers: list[str] = []
|
||||
command_start: list[str] = ["/", ""]
|
||||
nickname: list[str] = [f"灵温-{random_hex_string(6)}"]
|
||||
default_language: str = "zh-WY"
|
||||
satori: SatoriConfig = SatoriConfig()
|
||||
data_path: str = "data/liteyuki"
|
||||
chromium_path: str = (
|
||||
"/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome" # type: ignore
|
||||
if platform.system() == "Darwin"
|
||||
else (
|
||||
"C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe"
|
||||
if platform.system() == "Windows"
|
||||
else "/usr/bin/chromium-browser"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def load_from_yaml(file: str) -> dict:
|
||||
def load_from_yaml(file_: str) -> dict:
|
||||
global config
|
||||
nonebot.logger.debug("Loading config from %s" % file)
|
||||
if not os.path.exists(file):
|
||||
nonebot.logger.warning(f"未找到配置文件 {file} ,已创建默认配置,请修改后重启。")
|
||||
with open(file, "w", encoding="utf-8") as f:
|
||||
nonebot.logger.debug("正在从 {} 中加载配置项".format(file_))
|
||||
if not os.path.exists(file_):
|
||||
nonebot.logger.warning(
|
||||
f"未寻得配置文件 {file_} ,已以默认配置创建,请在重启后更改为你所需的内容。"
|
||||
)
|
||||
with open(file_, "w", encoding="utf-8") as f:
|
||||
yaml.dump(BasicConfig().dict(), f, default_flow_style=False)
|
||||
|
||||
with open(file, "r", encoding="utf-8") as f:
|
||||
with open(file_, "r", encoding="utf-8") as f:
|
||||
conf = init_conf(yaml.load(f, Loader=yaml.FullLoader))
|
||||
config = conf
|
||||
if conf is None:
|
||||
nonebot.logger.warning(f"配置文件 {file} 为空,已创建默认配置,请修改后重启。")
|
||||
nonebot.logger.warning(
|
||||
f"配置文件 {file_} 为空,已以默认配置创建,请在重启后更改为你所需的内容。"
|
||||
)
|
||||
conf = BasicConfig().dict()
|
||||
return conf
|
||||
|
||||
@@ -75,7 +91,6 @@ def get_config(key: str, default=None):
|
||||
return default
|
||||
|
||||
|
||||
|
||||
def init_conf(conf: dict) -> dict:
|
||||
"""
|
||||
初始化配置文件,确保配置文件中的必要字段存在,且不会冲突
|
||||
|
||||
@@ -30,7 +30,7 @@ class Database:
|
||||
os.makedirs(os.path.dirname(db_name))
|
||||
|
||||
self.db_name = db_name
|
||||
self.conn = sqlite3.connect(db_name)
|
||||
self.conn = sqlite3.connect(db_name, check_same_thread=False)
|
||||
self.cursor = self.conn.cursor()
|
||||
|
||||
self._on_save_callbacks = []
|
||||
@@ -94,7 +94,7 @@ class Database:
|
||||
f"数据库 Selecting {model.TABLE_NAME} WHERE {condition.replace('?', '%s') % args}"
|
||||
)
|
||||
if not table_name:
|
||||
raise ValueError(f"数据模型{model_type.__name__}未提供表名")
|
||||
raise ValueError(f"数据模型 {model_type.__name__} 未提供表名")
|
||||
|
||||
# condition = f"WHERE {condition}"
|
||||
# print(f"SELECT * FROM {table_name} {condition}", args)
|
||||
@@ -118,7 +118,7 @@ class Database:
|
||||
]
|
||||
|
||||
def save(self, *args: LiteModel):
|
||||
"""增/改操作
|
||||
self.returns_ = """增/改操作
|
||||
Args:
|
||||
*args:
|
||||
Returns:
|
||||
@@ -126,7 +126,7 @@ class Database:
|
||||
table_list = [
|
||||
item[0]
|
||||
for item in self.cursor.execute(
|
||||
"SELECT name FROM sqlite_master WHERE type='table'"
|
||||
"SELECT name FROM sqlite_master WHERE type ='table'"
|
||||
).fetchall()
|
||||
]
|
||||
for model in args:
|
||||
@@ -158,7 +158,7 @@ class Database:
|
||||
new_obj[field] = value
|
||||
else:
|
||||
raise ValueError(
|
||||
f"数据模型{table_name}包含不支持的数据类型,字段:{field} 值:{value} 值类型:{type(value)}"
|
||||
f"数据模型 {table_name} 包含不支持的数据类型,字段:{field} 值:{value} 值类型:{type(value)}"
|
||||
)
|
||||
if table_name:
|
||||
fields, values = [], []
|
||||
@@ -273,9 +273,9 @@ class Database:
|
||||
|
||||
"""
|
||||
table_name = model.TABLE_NAME
|
||||
logger.debug(f"数据库 Deleting {model} WHERE {condition} {args}")
|
||||
logger.debug(f"Deleting {model} WHERE {condition} {args}")
|
||||
if not table_name:
|
||||
raise ValueError(f"数据模型{model.__class__.__name__}未提供表名")
|
||||
raise ValueError(f"数据模型 {model.__class__.__name__} 未提供表名")
|
||||
if model.id is not None:
|
||||
condition = f"id = {model.id}"
|
||||
if not condition and not allow_empty:
|
||||
@@ -297,7 +297,7 @@ class Database:
|
||||
"""
|
||||
for model in args:
|
||||
if not model.TABLE_NAME:
|
||||
raise ValueError(f"数据模型{type(model).__name__}未提供表名")
|
||||
raise ValueError(f"数据模型 {type(model).__name__} 未提供表名")
|
||||
|
||||
# 若无则创建表
|
||||
self.cursor.execute(
|
||||
|
||||
@@ -2,9 +2,8 @@ import os
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from .data import Database, LiteModel, Database
|
||||
from .data import Database, LiteModel
|
||||
|
||||
print("导入数据库模块")
|
||||
DATA_PATH = "data/liteyuki"
|
||||
user_db: Database = Database(os.path.join(DATA_PATH, "users.ldb"))
|
||||
group_db: Database = Database(os.path.join(DATA_PATH, "groups.ldb"))
|
||||
@@ -64,7 +63,7 @@ def auto_migrate():
|
||||
user_db.auto_migrate(User())
|
||||
group_db.auto_migrate(Group())
|
||||
plugin_db.auto_migrate(InstalledPlugin(), GlobalPlugin())
|
||||
common_db.auto_migrate(GlobalPlugin(), StoredConfig(), TempConfig())
|
||||
common_db.auto_migrate(GlobalPlugin(), TempConfig())
|
||||
|
||||
|
||||
auto_migrate()
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
import json
|
||||
import os.path
|
||||
import platform
|
||||
|
||||
import aiohttp
|
||||
import nonebot
|
||||
import psutil
|
||||
import requests
|
||||
|
||||
from .config import load_from_yaml
|
||||
from .. import __NAME__, __VERSION_I__, __VERSION__
|
||||
|
||||
|
||||
class LiteyukiAPI:
|
||||
def __init__(self):
|
||||
self.liteyuki_id = None
|
||||
if os.path.exists("data/liteyuki/liteyuki.json"):
|
||||
with open("data/liteyuki/liteyuki.json", "rb") as f:
|
||||
self.data = json.loads(f.read())
|
||||
self.liteyuki_id = self.data.get("liteyuki_id")
|
||||
self.report = load_from_yaml("config.yml").get("auto_report", True)
|
||||
|
||||
if self.report:
|
||||
nonebot.logger.info("已启用自动上报")
|
||||
|
||||
@property
|
||||
def device_info(self) -> dict:
|
||||
"""
|
||||
获取设备信息
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return {
|
||||
"name": __NAME__,
|
||||
"version": __VERSION__,
|
||||
"version_i": __VERSION_I__,
|
||||
"python": f"{platform.python_implementation()} {platform.python_version()}",
|
||||
"os": f"{platform.system()} {platform.version()} {platform.machine()}",
|
||||
"cpu": f"{psutil.cpu_count(logical=False)}c{psutil.cpu_count()}t{psutil.cpu_freq().current}MHz",
|
||||
"memory_total": f"{psutil.virtual_memory().total / 1024 ** 3:.2f}吉字节",
|
||||
"memory_used": f"{psutil.virtual_memory().used / 1024 ** 3:.2f}吉字节",
|
||||
"memory_bot": f"{psutil.Process(os.getpid()).memory_info().rss / 1024 ** 2:.2f}兆字节",
|
||||
"disk": f"{psutil.disk_usage('/').total / 1024 ** 3:.2f}吉字节",
|
||||
}
|
||||
|
||||
def bug_report(self, content: str):
|
||||
"""
|
||||
提交bug报告
|
||||
Args:
|
||||
content:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if self.report:
|
||||
nonebot.logger.warning(f"正在上报查误:{content}")
|
||||
url = "https://api.liteyuki.icu/bug_report"
|
||||
data = {
|
||||
"liteyuki_id": self.liteyuki_id,
|
||||
"content": content,
|
||||
"device_info": self.device_info,
|
||||
}
|
||||
resp = requests.post(url, json=data)
|
||||
if resp.status_code == 200:
|
||||
nonebot.logger.success(
|
||||
f"成功上报差误信息,报文ID为:{resp.json().get('report_id')}"
|
||||
)
|
||||
else:
|
||||
nonebot.logger.error(f"差误上报错误:{resp.text}")
|
||||
else:
|
||||
nonebot.logger.warning(f"已禁用自动上报:{content}")
|
||||
|
||||
def register(self):
|
||||
pass
|
||||
|
||||
async def heartbeat_report(self):
|
||||
"""
|
||||
提交心跳,预留接口
|
||||
Returns:
|
||||
|
||||
"""
|
||||
url = "https://api.liteyuki.icu/heartbeat"
|
||||
data = {
|
||||
"liteyuki_id": self.liteyuki_id,
|
||||
"version": __VERSION__,
|
||||
}
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(url, json=data) as resp:
|
||||
if resp.status == 200:
|
||||
nonebot.logger.success("心跳成功送达。")
|
||||
else:
|
||||
nonebot.logger.error("休克:{}".format(await resp.text()))
|
||||
+127
-34
@@ -3,7 +3,9 @@ import os
|
||||
import shutil
|
||||
import zipfile
|
||||
from typing import Any
|
||||
from pathlib import Path
|
||||
|
||||
# import aiofiles
|
||||
import nonebot
|
||||
import yaml
|
||||
|
||||
@@ -12,8 +14,8 @@ from .language import Language, get_default_lang_code
|
||||
from .ly_function import loaded_functions
|
||||
|
||||
_loaded_resource_packs: list["ResourceMetadata"] = [] # 按照加载顺序排序
|
||||
temp_resource_root = "data/liteyuki/resources"
|
||||
temp_extract_root = "data/liteyuki/temp"
|
||||
temp_resource_root = Path("data/liteyuki/resources")
|
||||
temp_extract_root = Path("data/liteyuki/temp")
|
||||
lang = Language(get_default_lang_code())
|
||||
|
||||
|
||||
@@ -50,60 +52,139 @@ def load_resource_from_dir(path: str):
|
||||
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))
|
||||
copy_file(
|
||||
os.path.join(root, file),
|
||||
os.path.join(temp_resource_root, relative_path),
|
||||
)
|
||||
metadata["path"] = path
|
||||
metadata["folder"] = os.path.basename(path)
|
||||
|
||||
if os.path.exists(os.path.join(path, "lang")):
|
||||
# 加载语言
|
||||
from src.utils.base.language import load_from_dir
|
||||
|
||||
load_from_dir(os.path.join(path, "lang"))
|
||||
|
||||
if os.path.exists(os.path.join(path, "functions")):
|
||||
# 加载功能
|
||||
from src.utils.base.ly_function import load_from_dir
|
||||
|
||||
load_from_dir(os.path.join(path, "functions"))
|
||||
|
||||
if os.path.exists(os.path.join(path, "word_bank")):
|
||||
# 加载词库
|
||||
from src.utils.base.word_bank import load_from_dir
|
||||
|
||||
load_from_dir(os.path.join(path, "word_bank"))
|
||||
|
||||
_loaded_resource_packs.insert(0, ResourceMetadata(**metadata))
|
||||
|
||||
|
||||
def get_path(path: str, abs_path: bool = True, default: Any = None, debug: bool = False) -> str | Any:
|
||||
def get_path(
|
||||
path: os.PathLike[str,] | Path | str,
|
||||
abs_path: bool = True,
|
||||
default: Any = None,
|
||||
debug: bool = False,
|
||||
) -> str | Any:
|
||||
"""
|
||||
获取资源包中的文件
|
||||
获取资源包中的路径,且该路径必须存在
|
||||
Args:
|
||||
debug: 启用调试,每次都会先重载资源
|
||||
path: 相对路径
|
||||
abs_path: 是否返回绝对路径
|
||||
default: 默认
|
||||
path: 文件相对路径
|
||||
Returns: 文件绝对路径
|
||||
default: 默认解,当该路径不存在时使用
|
||||
debug: 启用调试,每次都会先重载资源
|
||||
Returns: 所需求之路径
|
||||
"""
|
||||
if debug:
|
||||
nonebot.logger.debug("Enable resource debug, Reloading resources")
|
||||
nonebot.logger.debug("由于已启用资源路径调试,正在重载资源")
|
||||
load_resources()
|
||||
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
|
||||
resource_relative_path = temp_resource_root / path
|
||||
if resource_relative_path.exists():
|
||||
return str(
|
||||
resource_relative_path.resolve() if abs_path else resource_relative_path
|
||||
)
|
||||
else:
|
||||
return default
|
||||
|
||||
|
||||
def get_files(path: str, abs_path: bool = False) -> list[str]:
|
||||
def get_resource_path(
|
||||
path: os.PathLike[str,] | Path | str,
|
||||
abs_path: bool = True,
|
||||
only_exist: bool = False,
|
||||
default: Any = None,
|
||||
debug: bool = False,
|
||||
) -> Path:
|
||||
"""
|
||||
获取资源包中一个文件夹的所有文件
|
||||
获取资源包中的路径
|
||||
Args:
|
||||
abs_path:
|
||||
path: 文件夹相对路径
|
||||
Returns: 文件绝对路径
|
||||
path: 相对路径
|
||||
abs_path: 是否返回绝对路径
|
||||
only_exist: 检查该路径是否存在
|
||||
default: [当 `only_exist` 为 **真** 时启用]默认解,当该路径不存在时使用
|
||||
debug: 启用调试,每次都会先重载资源
|
||||
Returns: 所需求之路径
|
||||
"""
|
||||
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)]
|
||||
if debug:
|
||||
nonebot.logger.debug("由于已启用资源路径调试,正在重载资源")
|
||||
load_resources()
|
||||
resource_relative_path = (
|
||||
(temp_resource_root / path).resolve()
|
||||
if abs_path
|
||||
else (temp_resource_root / path)
|
||||
)
|
||||
if only_exist:
|
||||
if resource_relative_path.exists():
|
||||
return resource_relative_path
|
||||
else:
|
||||
return default
|
||||
else:
|
||||
return resource_relative_path
|
||||
|
||||
|
||||
def get_files(
|
||||
path: os.PathLike[str,] | Path | str, abs_path: bool = False
|
||||
) -> list[str]:
|
||||
"""
|
||||
获取资源包中一个目录的所有内容
|
||||
Args:
|
||||
path: 该目录的相对路径
|
||||
abs_path: 是否返回绝对路径
|
||||
Returns: 目录内容路径所构成之列表
|
||||
"""
|
||||
resource_relative_path = temp_resource_root / path
|
||||
if resource_relative_path.exists():
|
||||
return [
|
||||
(
|
||||
str((resource_relative_path / file_).resolve())
|
||||
if abs_path
|
||||
else str((resource_relative_path / file_))
|
||||
)
|
||||
for file_ in os.listdir(resource_relative_path)
|
||||
]
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
def get_resource_files(
|
||||
path: os.PathLike[str,] | Path | str, abs_path: bool = False
|
||||
) -> list[Path]:
|
||||
"""
|
||||
获取资源包中一个目录的所有内容
|
||||
Args:
|
||||
path: 该目录的相对路径
|
||||
abs_path: 是否返回绝对路径
|
||||
Returns: 目录内容路径所构成之列表
|
||||
"""
|
||||
resource_relative_path = temp_resource_root / path
|
||||
if resource_relative_path.exists():
|
||||
return [
|
||||
(
|
||||
(resource_relative_path / file_).resolve()
|
||||
if abs_path
|
||||
else (resource_relative_path / file_)
|
||||
)
|
||||
for file_ in os.listdir(resource_relative_path)
|
||||
]
|
||||
else:
|
||||
return []
|
||||
|
||||
@@ -150,7 +231,9 @@ def load_resources():
|
||||
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"))
|
||||
resource_index: list[str] = json.load(
|
||||
open("resources/index.json", "r", encoding="utf-8")
|
||||
)
|
||||
resource_index.reverse() # 优先级高的后加载,但是排在前面
|
||||
for resource in resource_index:
|
||||
load_resource_from_dir(os.path.join("resources", resource))
|
||||
@@ -174,7 +257,9 @@ def check_exist(name: str) -> bool:
|
||||
Returns: 是否存在
|
||||
"""
|
||||
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"))
|
||||
return os.path.exists(os.path.join(path, "metadata.yml")) or (
|
||||
os.path.isfile(path) and name.endswith(".zip")
|
||||
)
|
||||
|
||||
|
||||
def add_resource_pack(name: str) -> bool:
|
||||
@@ -185,17 +270,19 @@ def add_resource_pack(name: str) -> bool:
|
||||
Returns:
|
||||
"""
|
||||
if check_exist(name):
|
||||
old_index: list[str] = json.load(open("resources/index.json", "r", encoding="utf-8"))
|
||||
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))
|
||||
nonebot.logger.warning("资源包 {} 已存在,无需添加".format(name))
|
||||
return False
|
||||
else:
|
||||
nonebot.logger.warning(lang.get("liteyuki.resource_not_exist", name=name))
|
||||
nonebot.logger.warning("资源包 {} 不存在,无法添加".format(name))
|
||||
return False
|
||||
|
||||
|
||||
@@ -207,16 +294,18 @@ def remove_resource_pack(name: str) -> bool:
|
||||
Returns:
|
||||
"""
|
||||
if check_exist(name):
|
||||
old_index: list[str] = json.load(open("resources/index.json", "r", encoding="utf-8"))
|
||||
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))
|
||||
nonebot.logger.warning("资源包 {} 不存在,无需移除".format(name))
|
||||
return False
|
||||
else:
|
||||
nonebot.logger.warning(lang.get("liteyuki.resource_not_exist", name=name))
|
||||
nonebot.logger.warning("资源包 {} 不存在,无法移除".format(name))
|
||||
return False
|
||||
|
||||
|
||||
@@ -229,7 +318,9 @@ def change_priority(name: str, delta: int) -> bool:
|
||||
Returns:
|
||||
"""
|
||||
# 正数表示前移,负数表示后移
|
||||
old_resource_list: list[str] = json.load(open("resources/index.json", "r", encoding="utf-8"))
|
||||
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)
|
||||
@@ -237,13 +328,15 @@ def change_priority(name: str, delta: int) -> bool:
|
||||
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"))
|
||||
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")
|
||||
nonebot.logger.warning("无法更改优先级为 {} ,优先级超出范围".format(delta))
|
||||
return False
|
||||
else:
|
||||
nonebot.logger.debug("Priority change failed, resource not loaded")
|
||||
nonebot.logger.debug("资源包 {} 未加载,无法更改优先级".format(name))
|
||||
return False
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from nonebot.adapters import satori
|
||||
|
||||
from src.utils.base.ly_typing import T_MessageEvent
|
||||
from nonebot.adapters import onebot
|
||||
from src.utils.base.ly_typing import T_MessageEvent, T_GroupMessageEvent
|
||||
|
||||
|
||||
def get_user_id(event: T_MessageEvent):
|
||||
@@ -10,11 +10,13 @@ def get_user_id(event: T_MessageEvent):
|
||||
return event.user_id
|
||||
|
||||
|
||||
def get_group_id(event: T_MessageEvent):
|
||||
def get_group_id(event: T_GroupMessageEvent):
|
||||
if isinstance(event, satori.event.Event):
|
||||
return event.guild.id
|
||||
else:
|
||||
elif isinstance(event, onebot.v11.GroupMessageEvent):
|
||||
return event.group_id
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def get_message_type(event: T_MessageEvent) -> str:
|
||||
|
||||
+16
-163
@@ -1,140 +1,20 @@
|
||||
import os.path
|
||||
|
||||
# import time
|
||||
from os import getcwd
|
||||
|
||||
import os
|
||||
import aiofiles
|
||||
import nonebot
|
||||
|
||||
from nonebot_plugin_htmlrender import * # type: ignore
|
||||
from nonebot import require
|
||||
|
||||
require("nonebot_plugin_htmlrender")
|
||||
|
||||
from nonebot_plugin_htmlrender import (
|
||||
template_to_html,
|
||||
template_to_pic,
|
||||
md_to_pic,
|
||||
init,
|
||||
)
|
||||
|
||||
from .tools import random_hex_string
|
||||
|
||||
# import imgkit
|
||||
# from typing import Any, Dict, Literal, Optional, Union
|
||||
# import uuid
|
||||
|
||||
# import jinja2
|
||||
# from pathlib import Path
|
||||
|
||||
# TEMPLATES_PATH = str(Path(__file__).parent / "templates")
|
||||
# env = jinja2.Environment( # noqa: S701
|
||||
# extensions=["jinja2.ext.loopcontrols"],
|
||||
# loader=jinja2.FileSystemLoader(TEMPLATES_PATH),
|
||||
# enable_async=True,
|
||||
# )
|
||||
|
||||
|
||||
# async def template_to_html(
|
||||
# template_path: str,
|
||||
# template_name: str,
|
||||
# **kwargs,
|
||||
# ) -> str:
|
||||
# """使用jinja2模板引擎通过html生成图片
|
||||
|
||||
# Args:
|
||||
# template_path (str): 模板路径
|
||||
# template_name (str): 模板名
|
||||
# **kwargs: 模板内容
|
||||
# Returns:
|
||||
# str: html
|
||||
# """
|
||||
|
||||
# template_env = jinja2.Environment( # noqa: S701
|
||||
# loader=jinja2.FileSystemLoader(template_path),
|
||||
# enable_async=True,
|
||||
# )
|
||||
# template = template_env.get_template(template_name)
|
||||
|
||||
# return await template.render_async(**kwargs)
|
||||
|
||||
|
||||
# async def template_to_pic(
|
||||
# template_path: str,
|
||||
# template_name: str,
|
||||
# templates: Dict[Any, Any],
|
||||
# pages: Optional[Dict[Any, Any]] = None,
|
||||
# wait: int = 0,
|
||||
# type: Literal["jpeg", "png"] = "png", # noqa: A002
|
||||
# quality: Union[int, None] = None,
|
||||
# device_scale_factor: float = 2,
|
||||
# ) -> bytes:
|
||||
# """使用jinja2模板引擎通过html生成图片
|
||||
|
||||
# Args:
|
||||
# template_path (str): 模板路径
|
||||
# template_name (str): 模板名
|
||||
# templates (Dict[Any, Any]): 模板内参数 如: {"name": "abc"}
|
||||
# pages (Optional[Dict[Any, Any]]): 网页参数 Defaults to
|
||||
# {"base_url": f"file://{getcwd()}", "viewport": {"width": 500, "height": 10}}
|
||||
# wait (int, optional): 网页载入等待时间. Defaults to 0.
|
||||
# type (Literal["jpeg", "png"]): 图片类型, 默认 png
|
||||
# quality (int, optional): 图片质量 0-100 当为`png`时无效
|
||||
# device_scale_factor: 缩放比例,类型为float,值越大越清晰(真正想让图片清晰更优先请调整此选项)
|
||||
# Returns:
|
||||
# bytes: 图片 可直接发送
|
||||
# """
|
||||
# if pages is None:
|
||||
# pages = {
|
||||
# "viewport": {"width": 500, "height": 10},
|
||||
# "base_url": f"file://{getcwd()}", # noqa: PTH109
|
||||
# }
|
||||
|
||||
# template_env = jinja2.Environment( # noqa: S701
|
||||
# loader=jinja2.FileSystemLoader(template_path),
|
||||
# enable_async=True,
|
||||
# )
|
||||
# template = template_env.get_template(template_name)
|
||||
|
||||
# open(
|
||||
# filename := os.path.join(
|
||||
# template_path,
|
||||
# str(uuid.uuid4())+".html",
|
||||
# ),
|
||||
# "w",
|
||||
# ).write(await template.render_async(**templates))
|
||||
|
||||
# print(pages,filename)
|
||||
|
||||
|
||||
# img = imgkit.from_file(
|
||||
# filename,
|
||||
# output_path=False,
|
||||
# options={
|
||||
# "format": type,
|
||||
# "quality": quality if (quality and type == "jpeg") else 94,
|
||||
# "allow": pages["base_url"],
|
||||
# # "viewport-size": "{} {}".format(pages["viewport"]["width"],pages["viewport"]["height"]),
|
||||
# "zoom": device_scale_factor,
|
||||
# # "load-error-handling": "ignore",
|
||||
# "enable-local-file-access": None,
|
||||
# "no-stop-slow-scripts": None,
|
||||
# "transparent": None,
|
||||
# },
|
||||
# ) # type: ignore
|
||||
|
||||
|
||||
# # os.remove(filename)
|
||||
|
||||
# return img
|
||||
|
||||
# return await html_to_pic(
|
||||
# template_path=f"file://{template_path}",
|
||||
# html=await template.render_async(**templates),
|
||||
# wait=wait,
|
||||
# type=type,
|
||||
# quality=quality,
|
||||
# device_scale_factor=device_scale_factor,
|
||||
# **pages,
|
||||
# )
|
||||
|
||||
|
||||
async def html2image(
|
||||
html: str,
|
||||
wait: int = 0,
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
async def template2html(
|
||||
template: str,
|
||||
templates: dict,
|
||||
@@ -174,8 +54,8 @@ async def template2image(
|
||||
if pages is None:
|
||||
pages = {
|
||||
"viewport": {"width": 1080, "height": 10},
|
||||
"base_url": f"file://{getcwd()}",
|
||||
}
|
||||
|
||||
template_path = os.path.dirname(template)
|
||||
template_name = os.path.basename(template)
|
||||
|
||||
@@ -197,36 +77,9 @@ async def template2image(
|
||||
template_name=template_name,
|
||||
template_path=template_path,
|
||||
templates=templates,
|
||||
pages=pages,
|
||||
wait=wait,
|
||||
###
|
||||
pages=pages,
|
||||
device_scale_factor=scale_factor,
|
||||
###
|
||||
)
|
||||
|
||||
|
||||
# async def url2image(
|
||||
# url: str,
|
||||
# wait: int = 0,
|
||||
# scale_factor: float = 1,
|
||||
# type: str = "png",
|
||||
# quality: int = 100,
|
||||
# **kwargs
|
||||
# ) -> bytes:
|
||||
# """
|
||||
# Args:
|
||||
# quality:
|
||||
# type:
|
||||
# url: str: URL
|
||||
# wait: int: 等待时间
|
||||
# scale_factor: float: 缩放因子
|
||||
# **kwargs: page 参数
|
||||
# Returns:
|
||||
# 图片二进制数据
|
||||
# """
|
||||
# async with get_new_page(scale_factor) as page:
|
||||
# await page.goto(url)
|
||||
# await page.wait_for_timeout(wait)
|
||||
# return await page.screenshot(
|
||||
# full_page=True,
|
||||
# type=type,
|
||||
# quality=quality
|
||||
# )
|
||||
|
||||
+17
-117
@@ -1,33 +1,20 @@
|
||||
import base64
|
||||
import io
|
||||
from typing import Any
|
||||
from urllib.parse import quote
|
||||
|
||||
import aiofiles
|
||||
from PIL import Image
|
||||
import aiohttp
|
||||
import nonebot
|
||||
from nonebot import require
|
||||
from nonebot.adapters import satori
|
||||
from PIL import Image
|
||||
from nonebot.adapters.onebot import v11
|
||||
from typing import Any, Type
|
||||
|
||||
from nonebot.internal.adapter import MessageSegment
|
||||
from nonebot.internal.adapter.message import TM
|
||||
|
||||
from .html_tool import md_to_pic
|
||||
from .. import load_from_yaml
|
||||
from ..base.ly_typing import T_Bot, T_Message, T_MessageEvent
|
||||
|
||||
require("nonebot_plugin_htmlrender")
|
||||
from nonebot_plugin_htmlrender import md_to_pic
|
||||
|
||||
config = load_from_yaml("config.yml")
|
||||
|
||||
can_send_markdown = {} # 用于存储机器人是否支持发送markdown消息,id->bool
|
||||
|
||||
|
||||
class TencentBannedMarkdownError(BaseException):
|
||||
pass
|
||||
|
||||
|
||||
async def broadcast_to_superusers(message: str | T_Message, markdown: bool = False):
|
||||
"""广播消息给超级用户"""
|
||||
@@ -49,9 +36,6 @@ class MarkdownMessage:
|
||||
*,
|
||||
message_type: str = None,
|
||||
session_id: str | int = None,
|
||||
event: T_MessageEvent = None,
|
||||
retry_as_image: bool = True,
|
||||
**kwargs,
|
||||
) -> dict[str, Any] | None:
|
||||
"""
|
||||
发送Markdown消息,支持自动转为图片发送
|
||||
@@ -60,86 +44,20 @@ class MarkdownMessage:
|
||||
bot:
|
||||
message_type:
|
||||
session_id:
|
||||
event:
|
||||
retry_as_image: 发送失败后是否尝试以图片形式发送,否则失败返回None
|
||||
**kwargs:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
formatted_md = v11.unescape(markdown).replace("\n", r"\n").replace('"', r"\\\"")
|
||||
if event is not None and message_type is None:
|
||||
if isinstance(event, satori.event.Event):
|
||||
message_type = "private" if event.guild is None else "group"
|
||||
group_id = event.guild.id if event.guild is not None else None
|
||||
else:
|
||||
assert event is not None
|
||||
message_type = event.message_type
|
||||
group_id = event.group_id if message_type == "group" else None
|
||||
user_id = (
|
||||
event.user.id
|
||||
if isinstance(event, satori.event.Event)
|
||||
else event.user_id
|
||||
)
|
||||
session_id = user_id if message_type == "private" else group_id
|
||||
|
||||
# try:
|
||||
# raise TencentBannedMarkdownError("Tencent banned markdown")
|
||||
# forward_id = await bot.call_api(
|
||||
# "send_private_forward_msg",
|
||||
# messages=[
|
||||
# {
|
||||
# "type": "node",
|
||||
# "data": {
|
||||
# "content": [
|
||||
# {
|
||||
# "data": {
|
||||
# "content": "{\"content\":\"%s\"}" % formatted_md,
|
||||
# },
|
||||
# "type": "markdown"
|
||||
# }
|
||||
# ],
|
||||
# "name": "[]",
|
||||
# "uin": bot.self_id
|
||||
# }
|
||||
# }
|
||||
# ],
|
||||
# user_id=bot.self_id
|
||||
|
||||
# )
|
||||
# data = await bot.send_msg(
|
||||
# user_id=session_id,
|
||||
# group_id=session_id,
|
||||
# message_type=message_type,
|
||||
# message=[
|
||||
# {
|
||||
# "type": "longmsg",
|
||||
# "data": {
|
||||
# "id": forward_id
|
||||
# }
|
||||
# },
|
||||
# ],
|
||||
# **kwargs
|
||||
# )
|
||||
# except BaseException as e:
|
||||
|
||||
nonebot.logger.error(f"因未能发送Markdown消息,已转为图片发送。")
|
||||
# 发送失败,渲染为图片发送
|
||||
# if not retry_as_image:
|
||||
# return None
|
||||
|
||||
# plain_markdown = markdown.replace("[🔗", "[")
|
||||
md_image_bytes = await md_to_pic(md=markdown, width=540, device_scale_factor=4)
|
||||
if isinstance(bot, satori.Bot):
|
||||
msg_seg = satori.MessageSegment.image(raw=md_image_bytes, mime="image/png")
|
||||
data = await bot.send(event=event, message=msg_seg)
|
||||
else:
|
||||
data = await bot.send_msg(
|
||||
message_type=message_type,
|
||||
group_id=session_id,
|
||||
user_id=session_id,
|
||||
message=v11.MessageSegment.image(md_image_bytes),
|
||||
)
|
||||
plain_markdown = markdown.replace("[🔗", "[")
|
||||
md_image_bytes = await md_to_pic(
|
||||
md=plain_markdown, width=540, device_scale_factor=4
|
||||
)
|
||||
print(md_image_bytes)
|
||||
data = await bot.send_msg(
|
||||
message_type=message_type,
|
||||
group_id=session_id,
|
||||
user_id=session_id,
|
||||
message=v11.MessageSegment.image(md_image_bytes),
|
||||
)
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
@@ -157,38 +75,25 @@ class MarkdownMessage:
|
||||
Args:
|
||||
image: 图片字节流或图片本地路径,链接请使用Markdown.image_async方法获取后通过send_md发送
|
||||
bot: bot instance
|
||||
message_type: message type
|
||||
message_type: message message_type
|
||||
session_id: session id
|
||||
event: event
|
||||
kwargs: other arguments
|
||||
Returns:
|
||||
dict: response data
|
||||
|
||||
"""
|
||||
if isinstance(image, str):
|
||||
async with aiofiles.open(image, "rb") as f:
|
||||
image = await f.read()
|
||||
method = 2
|
||||
# 1.轻雪图床方案
|
||||
# if method == 1:
|
||||
# image_url = await liteyuki_api.upload_image(image)
|
||||
# image_size = Image.open(io.BytesIO(image)).size
|
||||
# image_md = Markdown.image(image_url, image_size)
|
||||
# data = await Markdown.send_md(image_md, bot, message_type=message_type, session_id=session_id, event=event,
|
||||
# retry_as_image=False,
|
||||
# **kwargs)
|
||||
|
||||
# Lagrange.OneBot方案
|
||||
if method == 2:
|
||||
base64_string = base64.b64encode(image).decode("utf-8")
|
||||
data = await bot.call_api("upload_image", file=f"base64://{base64_string}")
|
||||
await MarkdownMessage.send_md(
|
||||
MarkdownMessage.image(data, Image.open(io.BytesIO(image)).size),
|
||||
bot,
|
||||
event=event,
|
||||
message_type=message_type,
|
||||
session_id=session_id,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
# 其他实现端方案
|
||||
@@ -204,12 +109,7 @@ class MarkdownMessage:
|
||||
image_size = Image.open(io.BytesIO(image)).size
|
||||
image_md = MarkdownMessage.image(image_url, image_size)
|
||||
return await MarkdownMessage.send_md(
|
||||
image_md,
|
||||
bot,
|
||||
message_type=message_type,
|
||||
session_id=session_id,
|
||||
event=event,
|
||||
**kwargs,
|
||||
image_md, bot, message_type=message_type, session_id=session_id
|
||||
)
|
||||
|
||||
if data is None:
|
||||
@@ -293,7 +193,7 @@ class MarkdownMessage:
|
||||
image = Image.open(io.BytesIO(await resp.read()))
|
||||
return MarkdownMessage.image(url, image.size)
|
||||
except Exception as e:
|
||||
nonebot.logger.error(f"get image error: {e}")
|
||||
nonebot.logger.error(f"获取图片错误:{e}")
|
||||
return "[Image Error]"
|
||||
|
||||
@staticmethod
|
||||
|
||||
Reference in New Issue
Block a user