zhenxun_bot/zhenxun/builtin_plugins/admin/welcome_message/data_source.py

274 lines
8.5 KiB
Python
Raw Normal View History

import os
from pathlib import Path
import re
import shutil
import uuid
import nonebot
from nonebot_plugin_alconna import UniMessage, UniMsg
from nonebot_plugin_uninfo import Uninfo
import ujson as json
from zhenxun.configs.path_config import DATA_PATH
from zhenxun.services.log import logger
from zhenxun.utils._build_image import BuildImage
from zhenxun.utils._image_template import ImageTemplate
from zhenxun.utils.http_utils import AsyncHttpx
:sparkles: 首次启动时提供使用web ui方式完全配置 (#1870) * :sparkles: 添加全局优先级hook * :sparkles: 添加基础配置api * :sparkles: 添加数据库连接测试 * :speech_balloon: 提示重启 * :adhesive_bandage: 填充过配置时友好提示 * :bug: 首次生成简易配置后自动加载 * :sparkles: 添加配置后重启接口 * :sparkles: 添加重启标志文件 * :sparkles: 添加重启脚本命令 * :sparkles: 添加重启系统限制 * :sparkles: 首次配置判断是否为win系统 * :fire: 移除bat * :sparkles: 添加关于菜单 * :sparkles: 支持整合包插件安装和添加整合包文档 * :adhesive_bandage: 检测数据库路径 * :adhesive_bandage: 修改数据库路径检测 * :adhesive_bandage: 修改数据库路径检测 * :adhesive_bandage: 修复路径注入 * :art: 显示添加优先级 * :bug: 修改PriorityLifecycle字典类名称 * :zap: 修复路径问题 * :zap: 修复路径检测 * ✨ 新增路径验证功能,确保用户输入的路径安全并在项目根目录内 * ✨ 优化路径验证功能,增加对非法字符和路径长度的检查,确保用户输入的路径更加安全 * :rotating_light: auto fix by pre-commit hooks * ✨ 优化获取文件列表的代码格式 * :memo: 修改README中webui示例图 * ✨ 更新PriorityLifecycle.on_startup装饰器 * ✨ 简化安装依赖的命令构建逻辑 * :rotating_light: auto fix by pre-commit hooks --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-06-16 09:11:41 +08:00
from zhenxun.utils.manager.priority_manager import PriorityLifecycle
from zhenxun.utils.platform import PlatformUtils
BASE_PATH = DATA_PATH / "welcome_message"
BASE_PATH.mkdir(parents=True, exist_ok=True)
driver = nonebot.get_driver()
def __migrate():
"""首次数据迁移"""
old_file = DATA_PATH / "custom_welcome_msg" / "custom_welcome_msg.json"
if not old_file.exists():
return
try:
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)
json.dump(
{
uuid.uuid4(): {
"at": "[at]" in message,
"status": True,
"message": message.replace("[at]", ""),
}
},
file.open("w", encoding="utf8"),
ensure_ascii=False,
indent=4,
)
logger.debug("群欢迎消息数据迁移", group_id=group_id)
shutil.rmtree(old_file.parent.absolute())
except Exception as e:
logger.error("群欢迎消息数据迁移失败...", e=e)
def migrate(path: Path):
"""数据迁移
参数:
path: 路径
"""
__migrate()
text_file = path / "text.json"
if not text_file.exists():
return
with text_file.open(encoding="utf8") as f:
json_data = json.load(f)
new_data = {}
if "at" in json_data:
split_msg = re.split(r"\[image:\d\]", str(json_data["message"]))
data = []
for i in range(len(split_msg)):
msg = split_msg[i]
data.append(
{
"type": "text",
"text": msg,
}
)
image_file = path / f"{i}.png"
if image_file.exists():
data.append(
{
"type": "image",
"path": str(image_file),
}
)
new_data[uuid.uuid4()] = {
"at": json_data.get("at", False),
"status": json_data.get("status", True),
"message": data,
}
with text_file.open("w", encoding="utf8") as f:
json.dump(new_data, f, ensure_ascii=False, indent=4)
:sparkles: 首次启动时提供使用web ui方式完全配置 (#1870) * :sparkles: 添加全局优先级hook * :sparkles: 添加基础配置api * :sparkles: 添加数据库连接测试 * :speech_balloon: 提示重启 * :adhesive_bandage: 填充过配置时友好提示 * :bug: 首次生成简易配置后自动加载 * :sparkles: 添加配置后重启接口 * :sparkles: 添加重启标志文件 * :sparkles: 添加重启脚本命令 * :sparkles: 添加重启系统限制 * :sparkles: 首次配置判断是否为win系统 * :fire: 移除bat * :sparkles: 添加关于菜单 * :sparkles: 支持整合包插件安装和添加整合包文档 * :adhesive_bandage: 检测数据库路径 * :adhesive_bandage: 修改数据库路径检测 * :adhesive_bandage: 修改数据库路径检测 * :adhesive_bandage: 修复路径注入 * :art: 显示添加优先级 * :bug: 修改PriorityLifecycle字典类名称 * :zap: 修复路径问题 * :zap: 修复路径检测 * ✨ 新增路径验证功能,确保用户输入的路径安全并在项目根目录内 * ✨ 优化路径验证功能,增加对非法字符和路径长度的检查,确保用户输入的路径更加安全 * :rotating_light: auto fix by pre-commit hooks * ✨ 优化获取文件列表的代码格式 * :memo: 修改README中webui示例图 * ✨ 更新PriorityLifecycle.on_startup装饰器 * ✨ 简化安装依赖的命令构建逻辑 * :rotating_light: auto fix by pre-commit hooks --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-06-16 09:11:41 +08:00
@PriorityLifecycle.on_startup(priority=5)
def _():
"""数据迁移
参数:
path: 存储路径
json_data: 存储数据
"""
flag_file = BASE_PATH / "flag.txt"
if flag_file.exists():
return
logger.info("开始迁移群欢迎消息数据...")
base_path = BASE_PATH
path_list = []
for platform in os.listdir(BASE_PATH):
base_path = base_path / platform
for group_id in os.listdir(base_path):
group_path = base_path / group_id
is_channel = False
for file in os.listdir(group_path):
inner_file = group_path / file
if inner_file.is_dir():
path_list.append(inner_file)
is_channel = True
if not is_channel:
path_list.append(group_path)
if path_list:
for path in path_list:
migrate(path)
if not flag_file.exists():
flag_file.touch()
logger.success("迁移群欢迎消息数据完成!", "")
class Manager:
@classmethod
def __get_data(cls, session: Uninfo) -> dict | None:
"""获取存储数据
参数:
session: Uninfo
返回:
dict | None: 欢迎消息数据
"""
if not session.group:
return None
path = cls.get_path(session)
if not path:
return None
file = path / "text.json"
if not file.exists():
return None
with file.open(encoding="utf8") as f:
return json.load(f)
@classmethod
def get_path(cls, session: Uninfo) -> Path | None:
"""根据Session获取存储路径
参数:
session: Uninfo:
返回:
Path: 存储路径
"""
if not session.group:
return None
platform = PlatformUtils.get_platform(session)
path = BASE_PATH / f"{platform}" / f"{session.group.id}"
if session.group.parent:
path = (
BASE_PATH
/ f"{platform}"
/ f"{session.group.parent.id}"
/ f"{session.group.id}"
)
path.mkdir(parents=True, exist_ok=True)
return path
@classmethod
async def save(cls, path: Path, message: UniMsg):
"""保存群欢迎消息
参数:
path: 存储路径
message: 消息内容
"""
file = path / "text.json"
json_data = {}
if file.exists():
with file.open(encoding="utf8") as f:
json_data = json.load(f)
data = []
is_at = False
for msg in message.dump(True):
if msg["type"] == "image":
image_file = path / f"{uuid.uuid4()}.png"
await AsyncHttpx.download_file(msg["url"], image_file)
msg["path"] = str(image_file)
if not is_at and msg["type"] == "text" and "-at" in msg["text"]:
msg["text"] = msg["text"].replace("-at", "", 1).strip()
is_at = True
data.append(msg)
json_data[str(uuid.uuid4())] = {"at": is_at, "status": True, "message": data}
with file.open("w", encoding="utf8") as f:
json.dump(json_data, f, ensure_ascii=False, indent=4)
@classmethod
async def get_group_message(
cls, session: Uninfo, idx: int | None
) -> BuildImage | UniMessage | None:
"""获取群欢迎消息
参数:
session: Uninfo
idx: 指定id
返回:
list: 消息内容
"""
if not session.group:
return None
json_data = cls.__get_data(session)
if not json_data:
return None
if idx is not None:
key_list = list(json_data.keys())
if idx < 0 or idx > len(key_list):
return None
return UniMessage().load(json_data[key_list[idx]]["message"])
else:
msg_list = []
for i, uid in enumerate(json_data):
msg_data = json_data[uid]
msg_list.append(
[
i,
"开启" if msg_data["status"] else "关闭",
"" if msg_data["at"] else "",
str(UniMessage().load(msg_data["message"])),
]
)
if not msg_list:
return None
column_name = ["ID", "状态", "是否@", "消息"]
return await ImageTemplate.table_page(
"群欢迎消息", session.group.id, column_name, msg_list
)
@classmethod
async def delete_group_message(cls, session: Uninfo, idx: int) -> str | None:
"""获取群欢迎消息
参数:
session: EventSession:
id: 消息ID
返回:
list: 消息内容
"""
path = cls.get_path(session)
json_data = cls.__get_data(session)
if not json_data or not path:
return None
file = path / "text.json"
key_list = list(json_data.keys())
if idx < 0 or idx >= len(key_list):
return None
old_msg = str(UniMessage().load(json_data[key_list[idx]]["message"]))
for msg in json_data[key_list[idx]]["message"]:
if msg["type"] == "image" and msg["path"]:
image_path = Path(msg["path"])
if image_path.exists():
image_path.unlink()
del json_data[key_list[idx]]
with file.open("w", encoding="utf8") as f:
json.dump(json_data, f, ensure_ascii=False, indent=4)
return f"删除群组欢迎消息成功!消息内容: {old_msg}"