mirror of https://github.com/Mai-with-u/MaiBot.git
Merge branch 'dev' of https://github.com/Mai-with-u/MaiBot into dev
commit
5d9e00c5ea
13
bot.py
13
bot.py
|
|
@ -235,12 +235,21 @@ if __name__ == "__main__":
|
|||
loop.run_until_complete(main_tasks)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
# loop.run_until_complete(get_global_api().stop())
|
||||
logger.warning("收到中断信号,正在优雅关闭...")
|
||||
|
||||
# 取消主任务
|
||||
if 'main_tasks' in locals() and main_tasks and not main_tasks.done():
|
||||
main_tasks.cancel()
|
||||
try:
|
||||
loop.run_until_complete(main_tasks)
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
# 执行优雅关闭
|
||||
if loop and not loop.is_closed():
|
||||
try:
|
||||
loop.run_until_complete(graceful_shutdown())
|
||||
except Exception as ge: # 捕捉优雅关闭时可能发生的错误
|
||||
except Exception as ge:
|
||||
logger.error(f"优雅关闭时发生错误: {ge}")
|
||||
# 新增:检测外部请求关闭
|
||||
|
||||
|
|
|
|||
|
|
@ -8,18 +8,25 @@
|
|||
"url": "https://github.com/MaiM-with-u"
|
||||
},
|
||||
"license": "GPL-v3.0-or-later",
|
||||
|
||||
"host_application": {
|
||||
"min_version": "0.10.3"
|
||||
},
|
||||
"homepage_url": "https://github.com/SengokuCola/BetterFrequency",
|
||||
"repository_url": "https://github.com/SengokuCola/BetterFrequency",
|
||||
"keywords": ["frequency", "control", "talk_frequency", "plugin", "shortcut"],
|
||||
"categories": ["Chat", "Frequency", "Control"],
|
||||
|
||||
"keywords": [
|
||||
"frequency",
|
||||
"control",
|
||||
"talk_frequency",
|
||||
"plugin",
|
||||
"shortcut"
|
||||
],
|
||||
"categories": [
|
||||
"Chat",
|
||||
"Frequency",
|
||||
"Control"
|
||||
],
|
||||
"default_locale": "zh-CN",
|
||||
"locales_path": "_locales",
|
||||
|
||||
"plugin_info": {
|
||||
"is_built_in": false,
|
||||
"plugin_type": "frequency",
|
||||
|
|
@ -46,5 +53,6 @@
|
|||
"支持完整命令和简化命令",
|
||||
"快速操作支持"
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "SengokuCola.BetterFrequency"
|
||||
}
|
||||
|
|
@ -8,18 +8,22 @@
|
|||
"url": "https://github.com/SengokuCola"
|
||||
},
|
||||
"license": "GPL-v3.0-or-later",
|
||||
|
||||
"host_application": {
|
||||
"min_version": "0.10.4"
|
||||
},
|
||||
"homepage_url": "https://github.com/SengokuCola/BetterEmoji",
|
||||
"repository_url": "https://github.com/SengokuCola/BetterEmoji",
|
||||
"keywords": ["emoji", "manage", "plugin"],
|
||||
"categories": ["Examples", "Tutorial"],
|
||||
|
||||
"keywords": [
|
||||
"emoji",
|
||||
"manage",
|
||||
"plugin"
|
||||
],
|
||||
"categories": [
|
||||
"Examples",
|
||||
"Tutorial"
|
||||
],
|
||||
"default_locale": "zh-CN",
|
||||
"locales_path": "_locales",
|
||||
|
||||
"plugin_info": {
|
||||
"is_built_in": false,
|
||||
"plugin_type": "emoji_manage",
|
||||
|
|
@ -31,10 +35,17 @@
|
|||
},
|
||||
{
|
||||
"type": "action",
|
||||
"name": "bye_greeting",
|
||||
"name": "bye_greeting",
|
||||
"description": "向用户发送告别消息",
|
||||
"activation_modes": ["keyword"],
|
||||
"keywords": ["再见", "bye", "88", "拜拜"]
|
||||
"activation_modes": [
|
||||
"keyword"
|
||||
],
|
||||
"keywords": [
|
||||
"再见",
|
||||
"bye",
|
||||
"88",
|
||||
"拜拜"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
|
|
@ -49,5 +60,6 @@
|
|||
"配置文件示例",
|
||||
"新手教程代码"
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "SengokuCola.BetterEmoji"
|
||||
}
|
||||
|
|
@ -8,18 +8,24 @@
|
|||
"url": "https://github.com/MaiM-with-u"
|
||||
},
|
||||
"license": "GPL-v3.0-or-later",
|
||||
|
||||
"host_application": {
|
||||
"min_version": "0.8.0"
|
||||
},
|
||||
"homepage_url": "https://github.com/MaiM-with-u/maibot",
|
||||
"repository_url": "https://github.com/MaiM-with-u/maibot",
|
||||
"keywords": ["demo", "example", "hello", "greeting", "tutorial"],
|
||||
"categories": ["Examples", "Tutorial"],
|
||||
|
||||
"keywords": [
|
||||
"demo",
|
||||
"example",
|
||||
"hello",
|
||||
"greeting",
|
||||
"tutorial"
|
||||
],
|
||||
"categories": [
|
||||
"Examples",
|
||||
"Tutorial"
|
||||
],
|
||||
"default_locale": "zh-CN",
|
||||
"locales_path": "_locales",
|
||||
|
||||
"plugin_info": {
|
||||
"is_built_in": false,
|
||||
"plugin_type": "example",
|
||||
|
|
@ -31,10 +37,17 @@
|
|||
},
|
||||
{
|
||||
"type": "action",
|
||||
"name": "bye_greeting",
|
||||
"name": "bye_greeting",
|
||||
"description": "向用户发送告别消息",
|
||||
"activation_modes": ["keyword"],
|
||||
"keywords": ["再见", "bye", "88", "拜拜"]
|
||||
"activation_modes": [
|
||||
"keyword"
|
||||
],
|
||||
"keywords": [
|
||||
"再见",
|
||||
"bye",
|
||||
"88",
|
||||
"拜拜"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
|
|
@ -49,5 +62,6 @@
|
|||
"配置文件示例",
|
||||
"新手教程代码"
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "MaiBot开发团队.maibot"
|
||||
}
|
||||
|
|
@ -156,7 +156,7 @@ class MainSystem:
|
|||
|
||||
async def schedule_tasks(self):
|
||||
"""调度定时任务"""
|
||||
while True:
|
||||
try:
|
||||
tasks = [
|
||||
get_emoji_manager().start_periodic_check_register(),
|
||||
self.app.run(),
|
||||
|
|
@ -168,6 +168,9 @@ class MainSystem:
|
|||
tasks.append(self.webui_server.start())
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
except asyncio.CancelledError:
|
||||
logger.info("调度任务已取消")
|
||||
raise
|
||||
|
||||
# async def forget_memory_task(self):
|
||||
# """记忆遗忘任务"""
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from fastapi import APIRouter, HTTPException, Body
|
|||
from typing import Any
|
||||
|
||||
from src.common.logger import get_logger
|
||||
from src.config.config import Config, APIAdapterConfig, CONFIG_DIR
|
||||
from src.config.config import Config, APIAdapterConfig, CONFIG_DIR, PROJECT_ROOT
|
||||
from src.config.official_configs import (
|
||||
BotConfig,
|
||||
PersonalityConfig,
|
||||
|
|
@ -421,6 +421,38 @@ async def update_model_config_section(section_name: str, section_data: Any = Bod
|
|||
# ===== 适配器配置管理接口 =====
|
||||
|
||||
|
||||
def _normalize_adapter_path(path: str) -> str:
|
||||
"""将路径转换为绝对路径(如果是相对路径,则相对于项目根目录)"""
|
||||
if not path:
|
||||
return path
|
||||
|
||||
# 如果已经是绝对路径,直接返回
|
||||
if os.path.isabs(path):
|
||||
return path
|
||||
|
||||
# 相对路径,转换为相对于项目根目录的绝对路径
|
||||
return os.path.normpath(os.path.join(PROJECT_ROOT, path))
|
||||
|
||||
|
||||
def _to_relative_path(path: str) -> str:
|
||||
"""尝试将绝对路径转换为相对于项目根目录的相对路径,如果无法转换则返回原路径"""
|
||||
if not path or not os.path.isabs(path):
|
||||
return path
|
||||
|
||||
try:
|
||||
# 尝试获取相对路径
|
||||
rel_path = os.path.relpath(path, PROJECT_ROOT)
|
||||
# 如果相对路径不是以 .. 开头(说明文件在项目目录内),则返回相对路径
|
||||
if not rel_path.startswith('..'):
|
||||
return rel_path
|
||||
except (ValueError, TypeError):
|
||||
# 在 Windows 上,如果路径在不同驱动器,relpath 会抛出 ValueError
|
||||
pass
|
||||
|
||||
# 无法转换为相对路径,返回绝对路径
|
||||
return path
|
||||
|
||||
|
||||
@router.get("/adapter-config/path")
|
||||
async def get_adapter_config_path():
|
||||
"""获取保存的适配器配置文件路径"""
|
||||
|
|
@ -438,13 +470,19 @@ async def get_adapter_config_path():
|
|||
if not adapter_config_path:
|
||||
return {"success": True, "path": None}
|
||||
|
||||
# 将路径规范化为绝对路径
|
||||
abs_path = _normalize_adapter_path(adapter_config_path)
|
||||
|
||||
# 检查文件是否存在并返回最后修改时间
|
||||
if os.path.exists(adapter_config_path):
|
||||
if os.path.exists(abs_path):
|
||||
import datetime
|
||||
mtime = os.path.getmtime(adapter_config_path)
|
||||
mtime = os.path.getmtime(abs_path)
|
||||
last_modified = datetime.datetime.fromtimestamp(mtime).isoformat()
|
||||
return {"success": True, "path": adapter_config_path, "lastModified": last_modified}
|
||||
# 返回相对路径(如果可能)
|
||||
display_path = _to_relative_path(abs_path)
|
||||
return {"success": True, "path": display_path, "lastModified": last_modified}
|
||||
else:
|
||||
# 文件不存在,返回原路径
|
||||
return {"success": True, "path": adapter_config_path, "lastModified": None}
|
||||
|
||||
except Exception as e:
|
||||
|
|
@ -471,15 +509,21 @@ async def save_adapter_config_path(data: dict[str, str] = Body(...)):
|
|||
else:
|
||||
webui_data = {}
|
||||
|
||||
# 将路径规范化为绝对路径
|
||||
abs_path = _normalize_adapter_path(path)
|
||||
|
||||
# 尝试转换为相对路径保存(如果文件在项目目录内)
|
||||
save_path = _to_relative_path(abs_path)
|
||||
|
||||
# 更新路径
|
||||
webui_data["adapter_config_path"] = path
|
||||
webui_data["adapter_config_path"] = save_path
|
||||
|
||||
# 保存
|
||||
os.makedirs("data", exist_ok=True)
|
||||
with open(webui_data_path, "w", encoding="utf-8") as f:
|
||||
json.dump(webui_data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
logger.info(f"适配器配置路径已保存: {path}")
|
||||
logger.info(f"适配器配置路径已保存: {save_path}(绝对路径: {abs_path})")
|
||||
return {"success": True, "message": "路径已保存"}
|
||||
|
||||
except HTTPException:
|
||||
|
|
@ -496,19 +540,22 @@ async def get_adapter_config(path: str):
|
|||
if not path:
|
||||
raise HTTPException(status_code=400, detail="路径参数不能为空")
|
||||
|
||||
# 将路径规范化为绝对路径
|
||||
abs_path = _normalize_adapter_path(path)
|
||||
|
||||
# 检查文件是否存在
|
||||
if not os.path.exists(path):
|
||||
if not os.path.exists(abs_path):
|
||||
raise HTTPException(status_code=404, detail=f"配置文件不存在: {path}")
|
||||
|
||||
# 检查文件扩展名
|
||||
if not path.endswith(".toml"):
|
||||
if not abs_path.endswith(".toml"):
|
||||
raise HTTPException(status_code=400, detail="只支持 .toml 格式的配置文件")
|
||||
|
||||
# 读取文件内容
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
with open(abs_path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
logger.info(f"已读取适配器配置: {path}")
|
||||
logger.info(f"已读取适配器配置: {path} (绝对路径: {abs_path})")
|
||||
return {"success": True, "content": content}
|
||||
|
||||
except HTTPException:
|
||||
|
|
@ -530,8 +577,11 @@ async def save_adapter_config(data: dict[str, str] = Body(...)):
|
|||
if content is None:
|
||||
raise HTTPException(status_code=400, detail="配置内容不能为空")
|
||||
|
||||
# 将路径规范化为绝对路径
|
||||
abs_path = _normalize_adapter_path(path)
|
||||
|
||||
# 检查文件扩展名
|
||||
if not path.endswith(".toml"):
|
||||
if not abs_path.endswith(".toml"):
|
||||
raise HTTPException(status_code=400, detail="只支持 .toml 格式的配置文件")
|
||||
|
||||
# 验证 TOML 格式
|
||||
|
|
@ -542,13 +592,15 @@ async def save_adapter_config(data: dict[str, str] = Body(...)):
|
|||
raise HTTPException(status_code=400, detail=f"TOML 格式错误: {str(e)}")
|
||||
|
||||
# 确保目录存在
|
||||
os.makedirs(os.path.dirname(path) if os.path.dirname(path) else ".", exist_ok=True)
|
||||
dir_path = os.path.dirname(abs_path)
|
||||
if dir_path:
|
||||
os.makedirs(dir_path, exist_ok=True)
|
||||
|
||||
# 保存文件
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
with open(abs_path, "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
|
||||
logger.info(f"适配器配置已保存: {path}")
|
||||
logger.info(f"适配器配置已保存: {path} (绝对路径: {abs_path})")
|
||||
return {"success": True, "message": "配置已保存"}
|
||||
|
||||
except HTTPException:
|
||||
|
|
|
|||
|
|
@ -394,11 +394,9 @@ async def register_emoji(emoji_id: int, authorization: Optional[str] = Header(No
|
|||
if emoji.is_registered:
|
||||
raise HTTPException(status_code=400, detail="该表情包已经注册")
|
||||
|
||||
if emoji.is_banned:
|
||||
raise HTTPException(status_code=400, detail="该表情包已被禁用,无法注册")
|
||||
|
||||
# 注册表情包
|
||||
# 注册表情包(如果已封禁,自动解除封禁)
|
||||
emoji.is_registered = True
|
||||
emoji.is_banned = False # 注册时自动解除封禁
|
||||
emoji.register_time = time.time()
|
||||
emoji.save()
|
||||
|
||||
|
|
|
|||
|
|
@ -516,10 +516,14 @@ async def install_plugin(request: InstallPluginRequest, authorization: Optional[
|
|||
plugins_dir = Path("plugins")
|
||||
plugins_dir.mkdir(exist_ok=True)
|
||||
|
||||
target_path = plugins_dir / request.plugin_id
|
||||
# 将插件 ID 中的点替换为下划线作为文件夹名称(避免文件系统问题)
|
||||
# 例如: SengokuCola.Mute-Plugin -> SengokuCola_Mute-Plugin
|
||||
folder_name = request.plugin_id.replace(".", "_")
|
||||
target_path = plugins_dir / folder_name
|
||||
|
||||
# 检查插件是否已安装
|
||||
if target_path.exists():
|
||||
# 检查插件是否已安装(需要检查两种格式:新格式下划线和旧格式点)
|
||||
old_format_path = plugins_dir / request.plugin_id
|
||||
if target_path.exists() or old_format_path.exists():
|
||||
await update_progress(
|
||||
stage="error",
|
||||
progress=0,
|
||||
|
|
@ -607,6 +611,12 @@ async def install_plugin(request: InstallPluginRequest, authorization: Optional[
|
|||
for field in required_fields:
|
||||
if field not in manifest:
|
||||
raise ValueError(f"缺少必需字段: {field}")
|
||||
|
||||
# 将插件 ID 写入 manifest(用于后续准确识别)
|
||||
# 这样即使文件夹名称改变,也能通过 manifest 准确识别插件
|
||||
manifest["id"] = request.plugin_id
|
||||
with open(manifest_path, "w", encoding="utf-8") as f:
|
||||
json_module.dump(manifest, f, ensure_ascii=False, indent=2)
|
||||
|
||||
except Exception as e:
|
||||
# 清理失败的安装
|
||||
|
|
@ -686,20 +696,28 @@ async def uninstall_plugin(
|
|||
plugin_id=request.plugin_id,
|
||||
)
|
||||
|
||||
# 1. 检查插件是否存在
|
||||
# 1. 检查插件是否存在(支持新旧两种格式)
|
||||
plugins_dir = Path("plugins")
|
||||
plugin_path = plugins_dir / request.plugin_id
|
||||
|
||||
# 新格式:下划线
|
||||
folder_name = request.plugin_id.replace(".", "_")
|
||||
plugin_path = plugins_dir / folder_name
|
||||
# 旧格式:点
|
||||
old_format_path = plugins_dir / request.plugin_id
|
||||
|
||||
# 优先使用新格式,如果不存在则尝试旧格式
|
||||
if not plugin_path.exists():
|
||||
await update_progress(
|
||||
stage="error",
|
||||
progress=0,
|
||||
message="插件不存在",
|
||||
operation="uninstall",
|
||||
plugin_id=request.plugin_id,
|
||||
error="插件未安装或已被删除",
|
||||
)
|
||||
raise HTTPException(status_code=404, detail="插件未安装")
|
||||
if old_format_path.exists():
|
||||
plugin_path = old_format_path
|
||||
else:
|
||||
await update_progress(
|
||||
stage="error",
|
||||
progress=0,
|
||||
message="插件不存在",
|
||||
operation="uninstall",
|
||||
plugin_id=request.plugin_id,
|
||||
error="插件未安装或已被删除",
|
||||
)
|
||||
raise HTTPException(status_code=404, detail="插件未安装")
|
||||
|
||||
await update_progress(
|
||||
stage="loading",
|
||||
|
|
@ -812,25 +830,32 @@ async def update_plugin(request: UpdatePluginRequest, authorization: Optional[st
|
|||
plugin_id=request.plugin_id,
|
||||
)
|
||||
|
||||
# 1. 检查插件是否已安装
|
||||
# 1. 检查插件是否已安装(支持新旧两种格式)
|
||||
plugins_dir = Path("plugins")
|
||||
plugin_path = plugins_dir / request.plugin_id
|
||||
|
||||
# 新格式:下划线
|
||||
folder_name = request.plugin_id.replace(".", "_")
|
||||
plugin_path = plugins_dir / folder_name
|
||||
# 旧格式:点
|
||||
old_format_path = plugins_dir / request.plugin_id
|
||||
|
||||
# 优先使用新格式,如果不存在则尝试旧格式
|
||||
if not plugin_path.exists():
|
||||
await update_progress(
|
||||
stage="error",
|
||||
progress=0,
|
||||
message="插件不存在",
|
||||
operation="update",
|
||||
plugin_id=request.plugin_id,
|
||||
error="插件未安装,请先安装",
|
||||
)
|
||||
raise HTTPException(status_code=404, detail="插件未安装")
|
||||
if old_format_path.exists():
|
||||
plugin_path = old_format_path
|
||||
else:
|
||||
await update_progress(
|
||||
stage="error",
|
||||
progress=0,
|
||||
message="插件不存在",
|
||||
operation="update",
|
||||
plugin_id=request.plugin_id,
|
||||
error="插件未安装,请先安装",
|
||||
)
|
||||
raise HTTPException(status_code=404, detail="插件未安装")
|
||||
|
||||
# 2. 读取旧版本信息
|
||||
manifest_path = plugin_path / "_manifest.json"
|
||||
old_version = "unknown"
|
||||
plugin_name = request.plugin_id
|
||||
|
||||
if manifest_path.exists():
|
||||
try:
|
||||
|
|
@ -839,7 +864,6 @@ async def update_plugin(request: UpdatePluginRequest, authorization: Optional[st
|
|||
with open(manifest_path, "r", encoding="utf-8") as f:
|
||||
manifest = json_module.load(f)
|
||||
old_version = manifest.get("version", "unknown")
|
||||
_plugin_name = manifest.get("name", request.plugin_id)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
|
@ -1032,18 +1056,18 @@ async def get_installed_plugins(authorization: Optional[str] = Header(None)) ->
|
|||
if not plugin_path.is_dir():
|
||||
continue
|
||||
|
||||
# 目录名即为插件 ID
|
||||
plugin_id = plugin_path.name
|
||||
# 目录名(可能是下划线格式、点格式或其他格式)
|
||||
folder_name = plugin_path.name
|
||||
|
||||
# 跳过隐藏目录和特殊目录
|
||||
if plugin_id.startswith(".") or plugin_id.startswith("__"):
|
||||
if folder_name.startswith(".") or folder_name.startswith("__"):
|
||||
continue
|
||||
|
||||
# 读取 _manifest.json
|
||||
manifest_path = plugin_path / "_manifest.json"
|
||||
|
||||
if not manifest_path.exists():
|
||||
logger.warning(f"插件 {plugin_id} 缺少 _manifest.json,跳过")
|
||||
logger.warning(f"插件文件夹 {folder_name} 缺少 _manifest.json,跳过")
|
||||
continue
|
||||
|
||||
try:
|
||||
|
|
@ -1054,9 +1078,58 @@ async def get_installed_plugins(authorization: Optional[str] = Header(None)) ->
|
|||
|
||||
# 基本验证
|
||||
if "name" not in manifest or "version" not in manifest:
|
||||
logger.warning(f"插件 {plugin_id} 的 _manifest.json 格式无效,跳过")
|
||||
logger.warning(f"插件文件夹 {folder_name} 的 _manifest.json 格式无效,跳过")
|
||||
continue
|
||||
|
||||
# 获取插件 ID(优先从 manifest,否则从文件夹名推断)
|
||||
if "id" in manifest:
|
||||
# 优先使用 manifest 中的 id(最准确)
|
||||
plugin_id = manifest["id"]
|
||||
else:
|
||||
# 从 manifest 信息构建 ID
|
||||
# 尝试从 author.name 和 repository_url 构建标准 ID
|
||||
author_name = None
|
||||
repo_name = None
|
||||
|
||||
# 获取作者名
|
||||
if "author" in manifest:
|
||||
if isinstance(manifest["author"], dict) and "name" in manifest["author"]:
|
||||
author_name = manifest["author"]["name"]
|
||||
elif isinstance(manifest["author"], str):
|
||||
author_name = manifest["author"]
|
||||
|
||||
# 从 repository_url 获取仓库名
|
||||
if "repository_url" in manifest:
|
||||
repo_url = manifest["repository_url"].rstrip("/")
|
||||
if repo_url.endswith(".git"):
|
||||
repo_url = repo_url[:-4]
|
||||
repo_name = repo_url.split("/")[-1]
|
||||
|
||||
# 构建 ID
|
||||
if author_name and repo_name:
|
||||
# 标准格式: Author.RepoName
|
||||
plugin_id = f"{author_name}.{repo_name}"
|
||||
elif author_name:
|
||||
# 如果只有作者,使用 Author.FolderName
|
||||
plugin_id = f"{author_name}.{folder_name}"
|
||||
else:
|
||||
# 从文件夹名推断
|
||||
if "_" in folder_name and "." not in folder_name:
|
||||
# 假设格式为 Author_PluginName,转换为 Author.PluginName
|
||||
plugin_id = folder_name.replace("_", ".", 1)
|
||||
else:
|
||||
# 直接使用文件夹名
|
||||
plugin_id = folder_name
|
||||
|
||||
# 将推断的 ID 写入 manifest(方便下次识别)
|
||||
logger.info(f"为插件 {folder_name} 自动生成 ID: {plugin_id}")
|
||||
manifest["id"] = plugin_id
|
||||
try:
|
||||
with open(manifest_path, "w", encoding="utf-8") as f:
|
||||
json_module.dump(manifest, f, ensure_ascii=False, indent=2)
|
||||
except Exception as write_error:
|
||||
logger.warning(f"无法写入 ID 到 manifest: {write_error}")
|
||||
|
||||
# 添加到已安装列表(返回完整的 manifest 信息)
|
||||
installed_plugins.append(
|
||||
{
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -5,13 +5,13 @@
|
|||
<link rel="icon" type="image/x-icon" href="/maimai.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>MaiBot Dashboard</title>
|
||||
<script type="module" crossorigin src="/assets/index-Du48JcWB.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-0AuPNinr.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/react-vendor-Dtc2IqVY.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/router-SinpzM5S.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/charts-BH1Uno6i.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/ui-vendor-BLBhIcJ8.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/icons-COIni9ke.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Dq6na-LB.css">
|
||||
<link rel="modulepreload" crossorigin href="/assets/icons-zVsyMy4K.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Dlctrk5R.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue