mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2025-12-15 14:22:55 +08:00
🎨
This commit is contained in:
parent
2ec600f217
commit
3ad1080339
4
.github/workflows/update_version_pr.yml
vendored
4
.github/workflows/update_version_pr.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
|||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Read current version
|
- name: Read current version
|
||||||
id: read_version
|
id: read_version
|
||||||
@ -62,7 +62,7 @@ jobs:
|
|||||||
if: steps.check_diff.outputs.version_changed == 'false'
|
if: steps.check_diff.outputs.version_changed == 'false'
|
||||||
uses: peter-evans/create-pull-request@v7
|
uses: peter-evans/create-pull-request@v7
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
branch: create-pr/update_version
|
branch: create-pr/update_version
|
||||||
title: ":tada: chore(version): 自动更新版本到 ${{ steps.update_version.outputs.new_version }}"
|
title: ":tada: chore(version): 自动更新版本到 ${{ steps.update_version.outputs.new_version }}"
|
||||||
body: "This PR updates the version file."
|
body: "This PR updates the version file."
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@ -155,6 +155,6 @@ bot.py
|
|||||||
.idea/
|
.idea/
|
||||||
resources/
|
resources/
|
||||||
/configs/config.py
|
/configs/config.py
|
||||||
configs/config.yaml
|
configs/config.yaml
|
||||||
.vscode/launch.json
|
.vscode/launch.json
|
||||||
plugins_/
|
plugins_/
|
||||||
|
|||||||
@ -97,14 +97,15 @@ async def _handle_setting(
|
|||||||
|
|
||||||
async def fix_db_schema():
|
async def fix_db_schema():
|
||||||
"""修复数据库架构问题"""
|
"""修复数据库架构问题"""
|
||||||
from tortoise import connections
|
from tortoise.connection import connections
|
||||||
|
|
||||||
conn = connections.get("default")
|
conn = connections.get("default")
|
||||||
# 检查是否存在superuser列并处理
|
# 检查是否存在superuser列并处理
|
||||||
try:
|
try:
|
||||||
await conn.execute_query("""
|
await conn.execute_query("""
|
||||||
DO $$
|
DO $$
|
||||||
BEGIN
|
BEGIN
|
||||||
IF EXISTS (SELECT 1 FROM information_schema.columns
|
IF EXISTS (SELECT 1 FROM information_schema.columns
|
||||||
WHERE table_name='plugin_info' AND column_name='superuser') THEN
|
WHERE table_name='plugin_info' AND column_name='superuser') THEN
|
||||||
ALTER TABLE plugin_info DROP COLUMN superuser;
|
ALTER TABLE plugin_info DROP COLUMN superuser;
|
||||||
END IF;
|
END IF;
|
||||||
@ -122,7 +123,7 @@ async def _():
|
|||||||
"""
|
"""
|
||||||
# 修复数据库架构问题
|
# 修复数据库架构问题
|
||||||
await fix_db_schema()
|
await fix_db_schema()
|
||||||
|
|
||||||
plugin_list: list[PluginInfo] = []
|
plugin_list: list[PluginInfo] = []
|
||||||
limit_list: list[PluginLimit] = []
|
limit_list: list[PluginLimit] = []
|
||||||
module2id = {}
|
module2id = {}
|
||||||
@ -150,26 +151,31 @@ async def _():
|
|||||||
"is_show",
|
"is_show",
|
||||||
"ignore_prompt",
|
"ignore_prompt",
|
||||||
# 移除了 menu_type
|
# 移除了 menu_type
|
||||||
# 确保 level, default_status, limit_superuser, cost_gold, impression, status, block_type 等用户配置不在此列表
|
# 确保 level, default_status, limit_superuser,
|
||||||
|
# cost_gold, impression, status, block_type 等用户配置不在此列表
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
# # 验证更新是否成功
|
# # 验证更新是否成功
|
||||||
# updated_plugin = await PluginInfo.get(id=plugin.id)
|
# updated_plugin = await PluginInfo.get(id=plugin.id)
|
||||||
# if updated_plugin.menu_type != plugin.menu_type:
|
# if updated_plugin.menu_type != plugin.menu_type:
|
||||||
# logger.warning(f"插件 {plugin.name} 的menu_type更新失败: 期望值 '{plugin.menu_type}', 实际值 '{updated_plugin.menu_type}'")
|
# logger.warning(
|
||||||
|
# f"插件 {plugin.name} 的menu_type更新失败: "
|
||||||
|
# f"期望值 '{plugin.menu_type}', "
|
||||||
|
# f"实际值 '{updated_plugin.menu_type}'"
|
||||||
|
# )
|
||||||
# # 尝试单独更新menu_type
|
# # 尝试单独更新menu_type
|
||||||
# updated_plugin.menu_type = plugin.menu_type
|
# updated_plugin.menu_type = plugin.menu_type
|
||||||
# await updated_plugin.save(update_fields=["menu_type"])
|
# await updated_plugin.save(update_fields=["menu_type"])
|
||||||
|
|
||||||
update_list.append(plugin)
|
update_list.append(plugin)
|
||||||
|
|
||||||
if create_list:
|
if create_list:
|
||||||
await PluginInfo.bulk_create(create_list, 10)
|
await PluginInfo.bulk_create(create_list, 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)
|
||||||
@ -441,4 +447,4 @@ async def group_migration():
|
|||||||
if create_list:
|
if create_list:
|
||||||
await GroupConsole.bulk_create(create_list, 10)
|
await GroupConsole.bulk_create(create_list, 10)
|
||||||
group_file.unlink()
|
group_file.unlink()
|
||||||
logger.info("迁移群组数据完成!")
|
logger.info("迁移群组数据完成!")
|
||||||
|
|||||||
@ -93,6 +93,8 @@ WsApiRouter.include_router(chat_routes)
|
|||||||
async def _():
|
async def _():
|
||||||
try:
|
try:
|
||||||
|
|
||||||
|
_tasks = []
|
||||||
|
|
||||||
async def log_sink(message: str):
|
async def log_sink(message: str):
|
||||||
loop = None
|
loop = None
|
||||||
if not loop:
|
if not loop:
|
||||||
@ -102,7 +104,10 @@ async def _():
|
|||||||
logger.warning("Web Ui log_sink", e=e)
|
logger.warning("Web Ui log_sink", e=e)
|
||||||
if not loop:
|
if not loop:
|
||||||
loop = asyncio.new_event_loop()
|
loop = asyncio.new_event_loop()
|
||||||
loop.create_task(LOG_STORAGE.add(message.rstrip("\n")))
|
task = loop.create_task(LOG_STORAGE.add(message.rstrip("\n")))
|
||||||
|
_tasks.append(task)
|
||||||
|
while _tasks and _tasks[0].done():
|
||||||
|
_tasks.pop(0)
|
||||||
|
|
||||||
logger_.add(
|
logger_.add(
|
||||||
log_sink, colorize=True, filter=default_filter, format=default_format
|
log_sink, colorize=True, filter=default_filter, format=default_format
|
||||||
|
|||||||
@ -9,13 +9,13 @@ from ....base_model import Result
|
|||||||
from ....utils import authentication
|
from ....utils import authentication
|
||||||
from .data_source import ApiDataSource
|
from .data_source import ApiDataSource
|
||||||
from .model import (
|
from .model import (
|
||||||
|
BatchUpdatePlugins,
|
||||||
PluginCount,
|
PluginCount,
|
||||||
PluginDetail,
|
PluginDetail,
|
||||||
PluginInfo,
|
PluginInfo,
|
||||||
PluginSwitch,
|
PluginSwitch,
|
||||||
UpdatePlugin,
|
|
||||||
BatchUpdatePlugins,
|
|
||||||
RenameMenuTypePayload,
|
RenameMenuTypePayload,
|
||||||
|
UpdatePlugin,
|
||||||
)
|
)
|
||||||
|
|
||||||
router = APIRouter(prefix="/plugin")
|
router = APIRouter(prefix="/plugin")
|
||||||
@ -165,23 +165,31 @@ async def batch_update_plugin_config_api(params: BatchUpdatePlugins):
|
|||||||
# 这里我们返回包含错误详情的 200 OK,让前端处理
|
# 这里我们返回包含错误详情的 200 OK,让前端处理
|
||||||
# 或者可以抛出 HTTPException
|
# 或者可以抛出 HTTPException
|
||||||
# from fastapi import HTTPException
|
# from fastapi import HTTPException
|
||||||
# raise HTTPException(status_code=400, detail={"message": "部分插件更新失败", "errors": result["errors"]})
|
# raise HTTPException(
|
||||||
|
# status_code=400,
|
||||||
|
# detail={"message": "部分插件更新失败", "errors": result["errors"]}
|
||||||
|
# )
|
||||||
pass # 暂时只返回结果字典
|
pass # 暂时只返回结果字典
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
# 新增:重命名菜单类型路由
|
# 新增:重命名菜单类型路由
|
||||||
@router.put(
|
@router.put(
|
||||||
"/menu_type/rename",
|
"/menu_type/rename",
|
||||||
dependencies=[authentication()],
|
dependencies=[authentication()],
|
||||||
response_model=Result,
|
response_model=Result,
|
||||||
summary="重命名菜单类型"
|
summary="重命名菜单类型"
|
||||||
)
|
)
|
||||||
async def rename_menu_type_api(payload: RenameMenuTypePayload) -> Result:
|
async def rename_menu_type_api(payload: RenameMenuTypePayload) -> Result:
|
||||||
try:
|
try:
|
||||||
result = await ApiDataSource.rename_menu_type(old_name=payload.old_name, new_name=payload.new_name)
|
result = await ApiDataSource.rename_menu_type(
|
||||||
|
old_name=payload.old_name, new_name=payload.new_name)
|
||||||
if result.get("success"):
|
if result.get("success"):
|
||||||
return Result.ok(info=result.get("info", f"成功将 {result.get('updated_count', 0)} 个插件的菜单类型从 '{payload.old_name}' 修改为 '{payload.new_name}'"))
|
return Result.ok(info=result.get(
|
||||||
|
"info",
|
||||||
|
f"成功将 {result.get('updated_count', 0)} 个插件的菜单类型"
|
||||||
|
f"从 '{payload.old_name}' 修改为 '{payload.new_name}'"
|
||||||
|
))
|
||||||
else:
|
else:
|
||||||
# 这种情况理论上不会发生,因为 rename_menu_type 失败会抛异常
|
# 这种情况理论上不会发生,因为 rename_menu_type 失败会抛异常
|
||||||
return Result.fail(info=result.get("info", "重命名失败"))
|
return Result.fail(info=result.get("info", "重命名失败"))
|
||||||
|
|||||||
@ -2,12 +2,12 @@ import re
|
|||||||
|
|
||||||
import cattrs
|
import cattrs
|
||||||
from fastapi import Query
|
from fastapi import Query
|
||||||
|
from tortoise.exceptions import DoesNotExist
|
||||||
|
|
||||||
from zhenxun.configs.config import Config
|
from zhenxun.configs.config import Config
|
||||||
from zhenxun.configs.utils import ConfigGroup
|
from zhenxun.configs.utils import ConfigGroup
|
||||||
from zhenxun.models.plugin_info import PluginInfo as DbPluginInfo
|
from zhenxun.models.plugin_info import PluginInfo as DbPluginInfo
|
||||||
from zhenxun.utils.enum import BlockType, PluginType
|
from zhenxun.utils.enum import BlockType, PluginType
|
||||||
from tortoise.exceptions import DoesNotExist
|
|
||||||
|
|
||||||
from .model import (
|
from .model import (
|
||||||
BatchUpdatePlugins,
|
BatchUpdatePlugins,
|
||||||
@ -134,7 +134,7 @@ class ApiDataSource:
|
|||||||
errors.append(
|
errors.append(
|
||||||
{
|
{
|
||||||
"module": item.module,
|
"module": item.module,
|
||||||
"error": f"Save block_type failed: {str(e_save)}",
|
"error": f"Save block_type failed: {e_save!s}",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
plugin_changed_other = False
|
plugin_changed_other = False
|
||||||
@ -158,7 +158,7 @@ class ApiDataSource:
|
|||||||
errors.append(
|
errors.append(
|
||||||
{
|
{
|
||||||
"module": "batch_update_other",
|
"module": "batch_update_other",
|
||||||
"error": f"Bulk update failed: {str(e_bulk)}",
|
"error": f"Bulk update failed: {e_bulk!s}",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -217,8 +217,12 @@ class ApiDataSource:
|
|||||||
if not old_name or not new_name:
|
if not old_name or not new_name:
|
||||||
raise ValueError("旧名称和新名称都不能为空")
|
raise ValueError("旧名称和新名称都不能为空")
|
||||||
if old_name == new_name:
|
if old_name == new_name:
|
||||||
return {"success": True, "updated_count": 0, "info": "新旧名称相同,无需更新"}
|
return {
|
||||||
|
"success": True,
|
||||||
|
"updated_count": 0,
|
||||||
|
"info": "新旧名称相同,无需更新"
|
||||||
|
}
|
||||||
|
|
||||||
# 检查新名称是否已存在(理论上前端会校验,后端再保险一次)
|
# 检查新名称是否已存在(理论上前端会校验,后端再保险一次)
|
||||||
exists = await DbPluginInfo.filter(menu_type=new_name).exists()
|
exists = await DbPluginInfo.filter(menu_type=new_name).exists()
|
||||||
if exists:
|
if exists:
|
||||||
@ -226,11 +230,12 @@ class ApiDataSource:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# 使用 filter().update() 进行批量更新
|
# 使用 filter().update() 进行批量更新
|
||||||
updated_count = await DbPluginInfo.filter(menu_type=old_name).update(menu_type=new_name)
|
updated_count = await DbPluginInfo.filter(menu_type=old_name).update(
|
||||||
|
menu_type=new_name)
|
||||||
return {"success": True, "updated_count": updated_count}
|
return {"success": True, "updated_count": updated_count}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# 可以添加更详细的日志记录
|
# 可以添加更详细的日志记录
|
||||||
raise RuntimeError(f"数据库更新菜单类型失败: {str(e)}")
|
raise RuntimeError(f"数据库更新菜单类型失败: {e!s}")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def get_plugin_detail(cls, module: str) -> PluginDetail:
|
async def get_plugin_detail(cls, module: str) -> PluginDetail:
|
||||||
|
|||||||
@ -113,11 +113,13 @@ class BatchUpdatePluginItem(BaseModel):
|
|||||||
module: str = Field(..., description="插件模块名")
|
module: str = Field(..., description="插件模块名")
|
||||||
default_status: bool | None = Field(None, description="默认状态(开关)")
|
default_status: bool | None = Field(None, description="默认状态(开关)")
|
||||||
menu_type: str | None = Field(None, description="菜单类型")
|
menu_type: str | None = Field(None, description="菜单类型")
|
||||||
block_type: BlockType | None = Field(None, description="插件禁用状态 (None: 启用, ALL: 禁用)")
|
block_type: BlockType | None = Field(
|
||||||
|
None, description="插件禁用状态 (None: 启用, ALL: 禁用)")
|
||||||
|
|
||||||
|
|
||||||
class BatchUpdatePlugins(BaseModel):
|
class BatchUpdatePlugins(BaseModel):
|
||||||
updates: list[BatchUpdatePluginItem] = Field(..., description="要批量更新的插件列表")
|
updates: list[BatchUpdatePluginItem] = Field(
|
||||||
|
..., description="要批量更新的插件列表")
|
||||||
|
|
||||||
|
|
||||||
class PluginDetail(PluginInfo):
|
class PluginDetail(PluginInfo):
|
||||||
|
|||||||
@ -366,13 +366,13 @@ class ConfigsManager:
|
|||||||
|
|
||||||
if not module or not key:
|
if not module or not key:
|
||||||
raise ValueError("add_plugin_config: module和key不能为为空")
|
raise ValueError("add_plugin_config: module和key不能为为空")
|
||||||
|
|
||||||
key_upper = key.upper()
|
key_upper = key.upper()
|
||||||
self.add_module.append(f"{module}:{key}".lower())
|
self.add_module.append(f"{module}:{key}".lower())
|
||||||
|
|
||||||
# 检查配置是否已存在
|
# 检查配置是否已存在
|
||||||
config_exists = module in self._data and key_upper in self._data[module].configs
|
config_exists = module in self._data and key_upper in self._data[module].configs
|
||||||
|
|
||||||
# 如果配置已存在,仅更新元数据,保留原始值
|
# 如果配置已存在,仅更新元数据,保留原始值
|
||||||
if config_exists:
|
if config_exists:
|
||||||
config = self._data[module].configs[key_upper]
|
config = self._data[module].configs[key_upper]
|
||||||
@ -397,7 +397,7 @@ class ConfigsManager:
|
|||||||
default_value=default_value,
|
default_value=default_value,
|
||||||
type=type,
|
type=type,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 同时更新simple_data
|
# 同时更新simple_data
|
||||||
if module not in self._simple_data:
|
if module not in self._simple_data:
|
||||||
self._simple_data[module] = {}
|
self._simple_data[module] = {}
|
||||||
@ -512,7 +512,7 @@ class ConfigsManager:
|
|||||||
_yaml.dump(self._simple_data, f)
|
_yaml.dump(self._simple_data, f)
|
||||||
# 原子替换
|
# 原子替换
|
||||||
temp_simple_file.replace(self._simple_file)
|
temp_simple_file.replace(self._simple_file)
|
||||||
|
|
||||||
path = path or self.file
|
path = path or self.file
|
||||||
data = {}
|
data = {}
|
||||||
for module in self._data:
|
for module in self._data:
|
||||||
@ -522,7 +522,7 @@ class ConfigsManager:
|
|||||||
del value["type"]
|
del value["type"]
|
||||||
del value["arg_parser"]
|
del value["arg_parser"]
|
||||||
data[module][config] = value
|
data[module][config] = value
|
||||||
|
|
||||||
# 使用临时文件进行原子写入
|
# 使用临时文件进行原子写入
|
||||||
temp_file = Path(str(path) + ".tmp")
|
temp_file = Path(str(path) + ".tmp")
|
||||||
with open(temp_file, "w", encoding="utf8") as f:
|
with open(temp_file, "w", encoding="utf8") as f:
|
||||||
@ -530,7 +530,7 @@ class ConfigsManager:
|
|||||||
# 原子替换
|
# 原子替换
|
||||||
temp_file.replace(path)
|
temp_file.replace(path)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"保存配置文件失败: {str(e)}")
|
logger.error(f"保存配置文件失败: {e!s}")
|
||||||
|
|
||||||
def reload(self):
|
def reload(self):
|
||||||
"""重新加载配置文件"""
|
"""重新加载配置文件"""
|
||||||
@ -540,24 +540,24 @@ class ConfigsManager:
|
|||||||
if self._simple_file.exists():
|
if self._simple_file.exists():
|
||||||
with open(self._simple_file, encoding="utf8") as f:
|
with open(self._simple_file, encoding="utf8") as f:
|
||||||
self._simple_data = _yaml.load(f)
|
self._simple_data = _yaml.load(f)
|
||||||
|
|
||||||
# 检查加载的数据是否为None
|
# 检查加载的数据是否为None
|
||||||
if self._simple_data is None:
|
if self._simple_data is None:
|
||||||
logger.error("配置文件为空或格式错误,保留原有配置")
|
logger.error("配置文件为空或格式错误,保留原有配置")
|
||||||
self._simple_data = {}
|
self._simple_data = {}
|
||||||
|
|
||||||
# 更新配置值
|
# 更新配置值
|
||||||
for key in self._simple_data.keys():
|
for key in self._simple_data.keys():
|
||||||
for k in self._simple_data[key].keys():
|
for k in self._simple_data[key].keys():
|
||||||
if key in self._data and k in self._data[key].configs:
|
if key in self._data and k in self._data[key].configs:
|
||||||
self._data[key].configs[k].value = self._simple_data[key][k]
|
self._data[key].configs[k].value = self._simple_data[key][k]
|
||||||
|
|
||||||
self.save()
|
self.save()
|
||||||
except ScannerError as e:
|
except ScannerError as e:
|
||||||
logger.error(f"配置文件解析失败: {str(e)},保留现有配置")
|
logger.error(f"配置文件解析失败: {e!s},保留现有配置")
|
||||||
self._data = backup_data
|
self._data = backup_data
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"重新加载配置失败: {str(e)}")
|
logger.error(f"重新加载配置失败: {e!s}")
|
||||||
# 发生错误时恢复到备份配置
|
# 发生错误时恢复到备份配置
|
||||||
self._data = backup_data
|
self._data = backup_data
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user