WebUI 后端类型注解补全,使用全 typing 库类型注解

This commit is contained in:
DrSmoothl
2026-03-16 13:09:12 +08:00
parent df088205dd
commit e7ac064a80
47 changed files with 572 additions and 365 deletions

View File

@@ -14,4 +14,4 @@ router.include_router(config_router)
set_update_progress_callback(update_progress)
__all__ = ["get_progress_router", "router"]
__all__ = ["get_progress_router", "router"]

View File

@@ -1,6 +1,5 @@
from typing import Any, Optional
import json
from typing import Any, Dict, Optional
from fastapi import APIRouter, Cookie, HTTPException
@@ -28,7 +27,7 @@ logger = get_logger("webui.plugin_routes")
router = APIRouter()
def _mirror_to_response(mirror: dict[str, Any]) -> MirrorConfigResponse:
def _mirror_to_response(mirror: Dict[str, Any]) -> MirrorConfigResponse:
return MirrorConfigResponse(
id=mirror["id"],
name=mirror["name"],
@@ -116,7 +115,7 @@ async def update_mirror(
@router.delete("/mirrors/{mirror_id}")
async def delete_mirror(mirror_id: str, maibot_session: Optional[str] = Cookie(None)) -> dict[str, Any]:
async def delete_mirror(mirror_id: str, maibot_session: Optional[str] = Cookie(None)) -> Dict[str, Any]:
require_plugin_token(maibot_session)
service = get_git_mirror_service()
@@ -172,12 +171,16 @@ async def fetch_raw_file(
loaded_plugins=total,
)
except Exception:
await update_progress(stage="success", progress=100, message="加载完成", total_plugins=0, loaded_plugins=0)
await update_progress(
stage="success", progress=100, message="加载完成", total_plugins=0, loaded_plugins=0
)
return FetchRawFileResponse(**result)
except Exception as e:
logger.error(f"获取 Raw 文件失败: {e}")
await update_progress(stage="error", progress=0, message="加载失败", error=str(e), total_plugins=0, loaded_plugins=0)
await update_progress(
stage="error", progress=0, message="加载失败", error=str(e), total_plugins=0, loaded_plugins=0
)
raise HTTPException(status_code=500, detail=f"服务器错误: {str(e)}") from e
@@ -204,4 +207,4 @@ async def clone_repository(
return CloneRepositoryResponse(**result)
except Exception as e:
logger.error(f"克隆仓库失败: {e}")
raise HTTPException(status_code=500, detail=f"服务器错误: {str(e)}") from e
raise HTTPException(status_code=500, detail=f"服务器错误: {str(e)}") from e

View File

@@ -1,8 +1,7 @@
from typing import Any, Optional, cast
import json
import tomlkit
from typing import Any, Dict, Optional, cast
import tomlkit
from fastapi import APIRouter, Cookie, HTTPException
from src.common.logger import get_logger
@@ -24,8 +23,8 @@ logger = get_logger("webui.plugin_routes")
router = APIRouter()
def _build_schema_from_current_config(plugin_id: str, current_config: Any) -> dict[str, Any]:
schema: dict[str, Any] = {
def _build_schema_from_current_config(plugin_id: str, current_config: Any) -> Dict[str, Any]:
schema: Dict[str, Any] = {
"plugin_id": plugin_id,
"plugin_info": {
"name": plugin_id,
@@ -41,7 +40,7 @@ def _build_schema_from_current_config(plugin_id: str, current_config: Any) -> di
for section_name, section_data in current_config.items():
if not isinstance(section_data, dict):
continue
section_fields: dict[str, Any] = {}
section_fields: Dict[str, Any] = {}
for field_name, field_value in section_data.items():
field_type = type(field_value).__name__
ui_type = "text"
@@ -121,7 +120,7 @@ def _build_schema_from_current_config(plugin_id: str, current_config: Any) -> di
@router.get("/config/{plugin_id}/schema")
async def get_plugin_config_schema(plugin_id: str, maibot_session: Optional[str] = Cookie(None)) -> dict[str, Any]:
async def get_plugin_config_schema(plugin_id: str, maibot_session: Optional[str] = Cookie(None)) -> Dict[str, Any]:
require_plugin_token(maibot_session)
logger.info(f"获取插件配置 Schema: {plugin_id}")
@@ -157,7 +156,7 @@ async def get_plugin_config_schema(plugin_id: str, maibot_session: Optional[str]
@router.get("/config/{plugin_id}/raw")
async def get_plugin_config_raw(plugin_id: str, maibot_session: Optional[str] = Cookie(None)) -> dict[str, Any]:
async def get_plugin_config_raw(plugin_id: str, maibot_session: Optional[str] = Cookie(None)) -> Dict[str, Any]:
require_plugin_token(maibot_session)
logger.info(f"获取插件原始配置: {plugin_id}")
@@ -184,7 +183,7 @@ async def update_plugin_config_raw(
plugin_id: str,
request: UpdatePluginRawConfigRequest,
maibot_session: Optional[str] = Cookie(None),
) -> dict[str, Any]:
) -> Dict[str, Any]:
require_plugin_token(maibot_session)
logger.info(f"更新插件原始配置: {plugin_id}")
@@ -216,7 +215,7 @@ async def update_plugin_config_raw(
@router.get("/config/{plugin_id}")
async def get_plugin_config(plugin_id: str, maibot_session: Optional[str] = Cookie(None)) -> dict[str, Any]:
async def get_plugin_config(plugin_id: str, maibot_session: Optional[str] = Cookie(None)) -> Dict[str, Any]:
require_plugin_token(maibot_session)
logger.info(f"获取插件配置: {plugin_id}")
@@ -244,7 +243,7 @@ async def update_plugin_config(
plugin_id: str,
request: UpdatePluginConfigRequest,
maibot_session: Optional[str] = Cookie(None),
) -> dict[str, Any]:
) -> Dict[str, Any]:
require_plugin_token(maibot_session)
logger.info(f"更新插件配置: {plugin_id}")
@@ -276,7 +275,7 @@ async def update_plugin_config(
@router.post("/config/{plugin_id}/reset")
async def reset_plugin_config(plugin_id: str, maibot_session: Optional[str] = Cookie(None)) -> dict[str, Any]:
async def reset_plugin_config(plugin_id: str, maibot_session: Optional[str] = Cookie(None)) -> Dict[str, Any]:
require_plugin_token(maibot_session)
logger.info(f"重置插件配置: {plugin_id}")
@@ -300,7 +299,7 @@ async def reset_plugin_config(plugin_id: str, maibot_session: Optional[str] = Co
@router.post("/config/{plugin_id}/toggle")
async def toggle_plugin(plugin_id: str, maibot_session: Optional[str] = Cookie(None)) -> dict[str, Any]:
async def toggle_plugin(plugin_id: str, maibot_session: Optional[str] = Cookie(None)) -> Dict[str, Any]:
require_plugin_token(maibot_session)
logger.info(f"切换插件状态: {plugin_id}")
@@ -326,9 +325,14 @@ async def toggle_plugin(plugin_id: str, maibot_session: Optional[str] = Cookie(N
status = "启用" if new_enabled else "禁用"
logger.info(f"{status}插件: {plugin_id}")
return {"success": True, "enabled": new_enabled, "message": f"插件已{status}", "note": "状态更改将在下次加载插件时生效"}
return {
"success": True,
"enabled": new_enabled,
"message": f"插件已{status}",
"note": "状态更改将在下次加载插件时生效",
}
except HTTPException:
raise
except Exception as e:
logger.error(f"切换插件状态失败: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=f"服务器错误: {str(e)}") from e
raise HTTPException(status_code=500, detail=f"服务器错误: {str(e)}") from e

View File

@@ -1,7 +1,6 @@
from pathlib import Path
from typing import Any, Optional
import json
from pathlib import Path
from typing import Any, Dict, List, Optional
from fastapi import APIRouter, Cookie, HTTPException
@@ -18,8 +17,8 @@ from .support import (
parse_repository_url,
remove_tree,
require_plugin_token,
resolve_plugin_file_path,
resolve_installed_plugin_path,
resolve_plugin_file_path,
validate_plugin_id,
)
@@ -28,7 +27,7 @@ logger = get_logger("webui.plugin_routes")
router = APIRouter()
def _infer_plugin_id(folder_name: str, manifest: dict[str, Any], manifest_path: Path) -> str:
def _infer_plugin_id(folder_name: str, manifest: Dict[str, Any], manifest_path: Path) -> str:
if "id" in manifest:
return str(manifest["id"])
@@ -66,43 +65,87 @@ def _infer_plugin_id(folder_name: str, manifest: dict[str, Any], manifest_path:
@router.post("/install")
async def install_plugin(request: InstallPluginRequest, maibot_session: Optional[str] = Cookie(None)) -> dict[str, Any]:
async def install_plugin(request: InstallPluginRequest, maibot_session: Optional[str] = Cookie(None)) -> Dict[str, Any]:
require_plugin_token(maibot_session)
logger.info(f"收到安装插件请求: {request.plugin_id}")
plugin_id = request.plugin_id
try:
plugin_id = validate_plugin_id(request.plugin_id)
await update_progress(stage="loading", progress=5, message=f"开始安装插件: {plugin_id}", operation="install", plugin_id=plugin_id)
await update_progress(
stage="loading", progress=5, message=f"开始安装插件: {plugin_id}", operation="install", plugin_id=plugin_id
)
repo_url, owner, repo = parse_repository_url(request.repository_url)
await update_progress(stage="loading", progress=10, message=f"解析仓库信息: {owner}/{repo}", operation="install", plugin_id=plugin_id)
await update_progress(
stage="loading",
progress=10,
message=f"解析仓库信息: {owner}/{repo}",
operation="install",
plugin_id=plugin_id,
)
target_path, old_format_path = get_plugin_candidate_paths(plugin_id)
if target_path.exists() or old_format_path.exists():
await update_progress(stage="error", progress=0, message="插件已存在", operation="install", plugin_id=plugin_id, error="插件已安装,请先卸载")
await update_progress(
stage="error",
progress=0,
message="插件已存在",
operation="install",
plugin_id=plugin_id,
error="插件已安装,请先卸载",
)
raise HTTPException(status_code=400, detail="插件已安装")
await update_progress(stage="loading", progress=15, message=f"准备克隆到: {target_path}", operation="install", plugin_id=plugin_id)
await update_progress(
stage="loading", progress=15, message=f"准备克隆到: {target_path}", operation="install", plugin_id=plugin_id
)
service = get_git_mirror_service()
if "github.com" in repo_url:
result = await service.clone_repository(owner=owner, repo=repo, target_path=target_path, branch=request.branch, mirror_id=request.mirror_id, depth=1)
result = await service.clone_repository(
owner=owner,
repo=repo,
target_path=target_path,
branch=request.branch,
mirror_id=request.mirror_id,
depth=1,
)
else:
result = await service.clone_repository(owner=owner, repo=repo, target_path=target_path, branch=request.branch, custom_url=repo_url, depth=1)
result = await service.clone_repository(
owner=owner, repo=repo, target_path=target_path, branch=request.branch, custom_url=repo_url, depth=1
)
if not result.get("success"):
error_msg = str(result.get("error", "克隆失败"))
await update_progress(stage="error", progress=0, message="克隆仓库失败", operation="install", plugin_id=plugin_id, error=error_msg)
await update_progress(
stage="error",
progress=0,
message="克隆仓库失败",
operation="install",
plugin_id=plugin_id,
error=error_msg,
)
raise HTTPException(status_code=int(result.get("status_code", 500)), detail=error_msg)
await update_progress(stage="loading", progress=85, message="验证插件文件...", operation="install", plugin_id=plugin_id)
await update_progress(
stage="loading", progress=85, message="验证插件文件...", operation="install", plugin_id=plugin_id
)
manifest_path = resolve_plugin_file_path(target_path, "_manifest.json")
if not manifest_path.exists():
remove_tree(target_path)
await update_progress(stage="error", progress=0, message="插件缺少 _manifest.json", operation="install", plugin_id=plugin_id, error="无效的插件格式")
await update_progress(
stage="error",
progress=0,
message="插件缺少 _manifest.json",
operation="install",
plugin_id=plugin_id,
error="无效的插件格式",
)
raise HTTPException(status_code=400, detail="无效的插件:缺少 _manifest.json")
await update_progress(stage="loading", progress=90, message="读取插件配置...", operation="install", plugin_id=plugin_id)
await update_progress(
stage="loading", progress=90, message="读取插件配置...", operation="install", plugin_id=plugin_id
)
try:
with open(manifest_path, "r", encoding="utf-8") as file_obj:
manifest = json.load(file_obj)
@@ -114,91 +157,199 @@ async def install_plugin(request: InstallPluginRequest, maibot_session: Optional
json.dump(manifest, file_obj, ensure_ascii=False, indent=2)
except Exception as e:
remove_tree(target_path)
await update_progress(stage="error", progress=0, message="_manifest.json 无效", operation="install", plugin_id=plugin_id, error=str(e))
await update_progress(
stage="error",
progress=0,
message="_manifest.json 无效",
operation="install",
plugin_id=plugin_id,
error=str(e),
)
raise HTTPException(status_code=400, detail=f"无效的 _manifest.json: {e}") from e
await update_progress(stage="success", progress=100, message=f"成功安装插件: {manifest['name']} v{manifest['version']}", operation="install", plugin_id=plugin_id)
return {"success": True, "message": "插件安装成功", "plugin_id": plugin_id, "plugin_name": manifest["name"], "version": manifest["version"], "path": str(target_path)}
await update_progress(
stage="success",
progress=100,
message=f"成功安装插件: {manifest['name']} v{manifest['version']}",
operation="install",
plugin_id=plugin_id,
)
return {
"success": True,
"message": "插件安装成功",
"plugin_id": plugin_id,
"plugin_name": manifest["name"],
"version": manifest["version"],
"path": str(target_path),
}
except HTTPException:
raise
except Exception as e:
logger.error(f"安装插件失败: {e}", exc_info=True)
await update_progress(stage="error", progress=0, message="安装失败", operation="install", plugin_id=plugin_id, error=str(e))
await update_progress(
stage="error", progress=0, message="安装失败", operation="install", plugin_id=plugin_id, error=str(e)
)
raise HTTPException(status_code=500, detail=f"服务器错误: {str(e)}") from e
@router.post("/uninstall")
async def uninstall_plugin(request: UninstallPluginRequest, maibot_session: Optional[str] = Cookie(None)) -> dict[str, Any]:
async def uninstall_plugin(
request: UninstallPluginRequest, maibot_session: Optional[str] = Cookie(None)
) -> Dict[str, Any]:
require_plugin_token(maibot_session)
logger.info(f"收到卸载插件请求: {request.plugin_id}")
plugin_id = request.plugin_id
try:
plugin_id = validate_plugin_id(request.plugin_id)
await update_progress(stage="loading", progress=10, message=f"开始卸载插件: {plugin_id}", operation="uninstall", plugin_id=plugin_id)
await update_progress(
stage="loading",
progress=10,
message=f"开始卸载插件: {plugin_id}",
operation="uninstall",
plugin_id=plugin_id,
)
plugin_path = resolve_installed_plugin_path(plugin_id)
if plugin_path is None:
await update_progress(stage="error", progress=0, message="插件不存在", operation="uninstall", plugin_id=plugin_id, error="插件未安装或已被删除")
await update_progress(
stage="error",
progress=0,
message="插件不存在",
operation="uninstall",
plugin_id=plugin_id,
error="插件未安装或已被删除",
)
raise HTTPException(status_code=404, detail="插件未安装")
await update_progress(stage="loading", progress=30, message=f"正在删除插件文件: {plugin_path}", operation="uninstall", plugin_id=plugin_id)
await update_progress(
stage="loading",
progress=30,
message=f"正在删除插件文件: {plugin_path}",
operation="uninstall",
plugin_id=plugin_id,
)
manifest = load_manifest_json(resolve_plugin_file_path(plugin_path, "_manifest.json"))
plugin_name = str(manifest.get("name", plugin_id)) if manifest is not None else plugin_id
await update_progress(stage="loading", progress=50, message=f"正在删除 {plugin_name}...", operation="uninstall", plugin_id=plugin_id)
await update_progress(
stage="loading",
progress=50,
message=f"正在删除 {plugin_name}...",
operation="uninstall",
plugin_id=plugin_id,
)
remove_tree(plugin_path)
logger.info(f"成功卸载插件: {plugin_id} ({plugin_name})")
await update_progress(stage="success", progress=100, message=f"成功卸载插件: {plugin_name}", operation="uninstall", plugin_id=plugin_id)
await update_progress(
stage="success",
progress=100,
message=f"成功卸载插件: {plugin_name}",
operation="uninstall",
plugin_id=plugin_id,
)
return {"success": True, "message": "插件卸载成功", "plugin_id": plugin_id, "plugin_name": plugin_name}
except HTTPException:
raise
except PermissionError as e:
logger.error(f"卸载插件失败(权限错误): {e}")
await update_progress(stage="error", progress=0, message="卸载失败", operation="uninstall", plugin_id=plugin_id, error="权限不足,无法删除插件文件")
await update_progress(
stage="error",
progress=0,
message="卸载失败",
operation="uninstall",
plugin_id=plugin_id,
error="权限不足,无法删除插件文件",
)
raise HTTPException(status_code=500, detail="权限不足,无法删除插件文件") from e
except Exception as e:
logger.error(f"卸载插件失败: {e}", exc_info=True)
await update_progress(stage="error", progress=0, message="卸载失败", operation="uninstall", plugin_id=plugin_id, error=str(e))
await update_progress(
stage="error", progress=0, message="卸载失败", operation="uninstall", plugin_id=plugin_id, error=str(e)
)
raise HTTPException(status_code=500, detail=f"服务器错误: {str(e)}") from e
@router.post("/update")
async def update_plugin(request: UpdatePluginRequest, maibot_session: Optional[str] = Cookie(None)) -> dict[str, Any]:
async def update_plugin(request: UpdatePluginRequest, maibot_session: Optional[str] = Cookie(None)) -> Dict[str, Any]:
require_plugin_token(maibot_session)
logger.info(f"收到更新插件请求: {request.plugin_id}")
plugin_id = request.plugin_id
try:
plugin_id = validate_plugin_id(request.plugin_id)
await update_progress(stage="loading", progress=5, message=f"开始更新插件: {plugin_id}", operation="update", plugin_id=plugin_id)
await update_progress(
stage="loading", progress=5, message=f"开始更新插件: {plugin_id}", operation="update", plugin_id=plugin_id
)
plugin_path = resolve_installed_plugin_path(plugin_id)
if plugin_path is None:
await update_progress(stage="error", progress=0, message="插件不存在", operation="update", plugin_id=plugin_id, error="插件未安装,请先安装")
await update_progress(
stage="error",
progress=0,
message="插件不存在",
operation="update",
plugin_id=plugin_id,
error="插件未安装,请先安装",
)
raise HTTPException(status_code=404, detail="插件未安装")
manifest = load_manifest_json(resolve_plugin_file_path(plugin_path, "_manifest.json"))
old_version = str(manifest.get("version", "unknown")) if manifest is not None else "unknown"
await update_progress(stage="loading", progress=10, message=f"当前版本: {old_version},准备更新...", operation="update", plugin_id=plugin_id)
await update_progress(stage="loading", progress=20, message="正在删除旧版本...", operation="update", plugin_id=plugin_id)
await update_progress(
stage="loading",
progress=10,
message=f"当前版本: {old_version},准备更新...",
operation="update",
plugin_id=plugin_id,
)
await update_progress(
stage="loading", progress=20, message="正在删除旧版本...", operation="update", plugin_id=plugin_id
)
remove_tree(plugin_path)
await update_progress(stage="loading", progress=30, message="正在准备下载新版本...", operation="update", plugin_id=plugin_id)
await update_progress(
stage="loading", progress=30, message="正在准备下载新版本...", operation="update", plugin_id=plugin_id
)
repo_url, owner, repo = parse_repository_url(request.repository_url)
service = get_git_mirror_service()
if "github.com" in repo_url:
result = await service.clone_repository(owner=owner, repo=repo, target_path=plugin_path, branch=request.branch, mirror_id=request.mirror_id, depth=1)
result = await service.clone_repository(
owner=owner,
repo=repo,
target_path=plugin_path,
branch=request.branch,
mirror_id=request.mirror_id,
depth=1,
)
else:
result = await service.clone_repository(owner=owner, repo=repo, target_path=plugin_path, branch=request.branch, custom_url=repo_url, depth=1)
result = await service.clone_repository(
owner=owner, repo=repo, target_path=plugin_path, branch=request.branch, custom_url=repo_url, depth=1
)
if not result.get("success"):
error_msg = str(result.get("error", "克隆失败"))
await update_progress(stage="error", progress=0, message="下载新版本失败", operation="update", plugin_id=plugin_id, error=error_msg)
await update_progress(
stage="error",
progress=0,
message="下载新版本失败",
operation="update",
plugin_id=plugin_id,
error=error_msg,
)
raise HTTPException(status_code=int(result.get("status_code", 500)), detail=error_msg)
await update_progress(stage="loading", progress=90, message="验证新版本...", operation="update", plugin_id=plugin_id)
await update_progress(
stage="loading", progress=90, message="验证新版本...", operation="update", plugin_id=plugin_id
)
new_manifest_path = resolve_plugin_file_path(plugin_path, "_manifest.json")
if not new_manifest_path.exists():
remove_tree(plugin_path)
await update_progress(stage="error", progress=0, message="新版本缺少 _manifest.json", operation="update", plugin_id=plugin_id, error="无效的插件格式")
await update_progress(
stage="error",
progress=0,
message="新版本缺少 _manifest.json",
operation="update",
plugin_id=plugin_id,
error="无效的插件格式",
)
raise HTTPException(status_code=400, detail="无效的插件:缺少 _manifest.json")
try:
@@ -207,27 +358,49 @@ async def update_plugin(request: UpdatePluginRequest, maibot_session: Optional[s
new_version = str(new_manifest.get("version", "unknown"))
new_name = str(new_manifest.get("name", plugin_id))
logger.info(f"成功更新插件: {plugin_id} {old_version}{new_version}")
await update_progress(stage="success", progress=100, message=f"成功更新 {new_name}: {old_version}{new_version}", operation="update", plugin_id=plugin_id)
return {"success": True, "message": "插件更新成功", "plugin_id": plugin_id, "plugin_name": new_name, "old_version": old_version, "new_version": new_version}
await update_progress(
stage="success",
progress=100,
message=f"成功更新 {new_name}: {old_version}{new_version}",
operation="update",
plugin_id=plugin_id,
)
return {
"success": True,
"message": "插件更新成功",
"plugin_id": plugin_id,
"plugin_name": new_name,
"old_version": old_version,
"new_version": new_version,
}
except Exception as e:
remove_tree(plugin_path)
await update_progress(stage="error", progress=0, message="_manifest.json 无效", operation="update", plugin_id=plugin_id, error=str(e))
await update_progress(
stage="error",
progress=0,
message="_manifest.json 无效",
operation="update",
plugin_id=plugin_id,
error=str(e),
)
raise HTTPException(status_code=400, detail=f"无效的 _manifest.json: {e}") from e
except HTTPException:
raise
except Exception as e:
logger.error(f"更新插件失败: {e}", exc_info=True)
await update_progress(stage="error", progress=0, message="更新失败", operation="update", plugin_id=plugin_id, error=str(e))
await update_progress(
stage="error", progress=0, message="更新失败", operation="update", plugin_id=plugin_id, error=str(e)
)
raise HTTPException(status_code=500, detail=f"服务器错误: {str(e)}") from e
@router.get("/installed")
async def get_installed_plugins(maibot_session: Optional[str] = Cookie(None)) -> dict[str, Any]:
async def get_installed_plugins(maibot_session: Optional[str] = Cookie(None)) -> Dict[str, Any]:
require_plugin_token(maibot_session)
logger.info("收到获取已安装插件列表请求")
try:
installed_plugins: list[dict[str, Any]] = []
installed_plugins: List[Dict[str, Any]] = []
for plugin_path in iter_plugin_directories():
folder_name = plugin_path.name
if folder_name.startswith(".") or folder_name.startswith("__"):
@@ -253,9 +426,9 @@ async def get_installed_plugins(maibot_session: Optional[str] = Cookie(None)) ->
except Exception as e:
logger.error(f"读取插件 {folder_name} 信息时出错: {e}")
seen_ids: dict[str, str] = {}
unique_plugins: list[dict[str, Any]] = []
duplicates: list[dict[str, Any]] = []
seen_ids: Dict[str, str] = {}
unique_plugins: List[Dict[str, Any]] = []
duplicates: List[Dict[str, Any]] = []
for plugin in installed_plugins:
plugin_id = str(plugin["id"])
plugin_path = str(plugin["path"])
@@ -277,7 +450,7 @@ async def get_installed_plugins(maibot_session: Optional[str] = Cookie(None)) ->
@router.get("/local-readme/{plugin_id}")
async def get_local_plugin_readme(plugin_id: str, maibot_session: Optional[str] = Cookie(None)) -> dict[str, Any]:
async def get_local_plugin_readme(plugin_id: str, maibot_session: Optional[str] = Cookie(None)) -> Dict[str, Any]:
require_plugin_token(maibot_session)
logger.info(f"获取本地插件 README: {plugin_id}")
@@ -300,4 +473,4 @@ async def get_local_plugin_readme(plugin_id: str, maibot_session: Optional[str]
return {"success": False, "error": "本地未找到 README 文件"}
except Exception as e:
logger.error(f"获取本地 README 失败: {e}", exc_info=True)
return {"success": False, "error": str(e)}
return {"success": False, "error": str(e)}

View File

@@ -1,7 +1,6 @@
import asyncio
import json
from typing import Any, Optional, Set
from typing import Any, Dict, Optional, Set
from fastapi import APIRouter, Query, WebSocket, WebSocketDisconnect
@@ -14,7 +13,7 @@ logger = get_logger("webui.plugin_progress")
router = APIRouter()
active_connections: Set[WebSocket] = set()
current_progress: dict[str, Any] = {
current_progress: Dict[str, Any] = {
"operation": "idle",
"stage": "idle",
"progress": 0,
@@ -26,7 +25,7 @@ current_progress: dict[str, Any] = {
}
async def broadcast_progress(progress_data: dict[str, Any]) -> None:
async def broadcast_progress(progress_data: Dict[str, Any]) -> None:
global current_progress
current_progress = progress_data.copy()
@@ -34,7 +33,7 @@ async def broadcast_progress(progress_data: dict[str, Any]) -> None:
return
message = json.dumps(progress_data, ensure_ascii=False)
disconnected: set[WebSocket] = set()
disconnected: Set[WebSocket] = set()
for websocket in active_connections:
try:
@@ -119,4 +118,4 @@ async def websocket_plugin_progress(websocket: WebSocket, token: Optional[str] =
def get_progress_router() -> APIRouter:
return router
return router

View File

@@ -1,4 +1,4 @@
from typing import Any, Optional
from typing import Any, Dict, List, Optional
from pydantic import BaseModel, Field
@@ -51,8 +51,8 @@ class MirrorConfigResponse(BaseModel):
class AvailableMirrorsResponse(BaseModel):
mirrors: list[MirrorConfigResponse] = Field(..., description="镜像源列表")
default_priority: list[str] = Field(..., description="默认优先级顺序ID 列表)")
mirrors: List[MirrorConfigResponse] = Field(..., description="镜像源列表")
default_priority: List[str] = Field(..., description="默认优先级顺序ID 列表)")
class AddMirrorRequest(BaseModel):
@@ -106,8 +106,8 @@ class UpdatePluginRequest(BaseModel):
class UpdatePluginConfigRequest(BaseModel):
enabled: Optional[bool] = None
config: Optional[dict[str, Any]] = None
config: Optional[Dict[str, Any]] = None
class UpdatePluginRawConfigRequest(BaseModel):
config: str = Field(..., description="原始 TOML 配置内容")
config: str = Field(..., description="原始 TOML 配置内容")

View File

@@ -1,12 +1,11 @@
from datetime import datetime
from pathlib import Path
from typing import Any, Optional, cast, get_origin
import json
import os
import re
import shutil
import stat
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple, cast, get_origin
from fastapi import HTTPException
@@ -53,10 +52,7 @@ def _resolve_safe_plugin_directory(plugin_path: Path, plugins_dir: Path, strict:
resolved_plugin_path = plugin_path.resolve()
resolved_plugin_path.relative_to(resolved_plugins_dir)
if not resolved_plugin_path.is_dir():
return None
return resolved_plugin_path
return resolved_plugin_path if resolved_plugin_path.is_dir() else None
except HTTPException:
if strict:
raise
@@ -64,7 +60,7 @@ def _resolve_safe_plugin_directory(plugin_path: Path, plugins_dir: Path, strict:
return None
except (OSError, RuntimeError, ValueError):
if strict:
raise HTTPException(status_code=400, detail="插件目录超出允许范围")
raise HTTPException(status_code=400, detail="插件目录超出允许范围") from None
logger.warning(f"已跳过越界的插件目录: {plugin_path}")
return None
@@ -109,7 +105,7 @@ def validate_plugin_id(plugin_id: str) -> str:
return plugin_id
def parse_version(version_str: str) -> tuple[int, int, int]:
def parse_version(version_str: str) -> Tuple[int, int, int]:
base_version = re.split(r"[-.](?:snapshot|dev|alpha|beta|rc)", version_str, flags=re.IGNORECASE)[0]
parts = base_version.split(".")
if len(parts) < 3:
@@ -122,7 +118,7 @@ def parse_version(version_str: str) -> tuple[int, int, int]:
return 0, 0, 0
def deep_merge(dst: dict[str, Any], src: dict[str, Any]) -> None:
def deep_merge(dst: Dict[str, Any], src: Dict[str, Any]) -> None:
for key, value in src.items():
if key in dst and isinstance(dst[key], dict) and isinstance(value, dict):
deep_merge(dst[key], value)
@@ -130,9 +126,9 @@ def deep_merge(dst: dict[str, Any], src: dict[str, Any]) -> None:
dst[key] = value
def normalize_dotted_keys(obj: dict[str, Any]) -> dict[str, Any]:
result: dict[str, Any] = {}
dotted_items: list[tuple[str, Any]] = []
def normalize_dotted_keys(obj: Dict[str, Any]) -> Dict[str, Any]:
result: Dict[str, Any] = {}
dotted_items: List[Tuple[str, Any]] = []
for key, value in obj.items():
if "." in key:
@@ -167,7 +163,7 @@ def normalize_dotted_keys(obj: dict[str, Any]) -> dict[str, Any]:
return result
def coerce_types(schema_part: dict[str, Any], config_part: dict[str, Any]) -> None:
def coerce_types(schema_part: Dict[str, Any], config_part: Dict[str, Any]) -> None:
def is_list_type(tp: Any) -> bool:
origin = get_origin(tp)
return tp is list or origin is list
@@ -200,7 +196,7 @@ def get_plugins_dir() -> Path:
return plugins_dir
def get_plugin_candidate_paths(plugin_id: str) -> tuple[Path, Path]:
def get_plugin_candidate_paths(plugin_id: str) -> Tuple[Path, Path]:
plugins_dir = get_plugins_dir()
folder_name = plugin_id.replace(".", "_")
return validate_safe_path(folder_name, plugins_dir), validate_safe_path(plugin_id, plugins_dir)
@@ -217,7 +213,7 @@ def resolve_installed_plugin_path(plugin_id: str) -> Optional[Path]:
return None
def parse_repository_url(repository_url: str) -> tuple[str, str, str]:
def parse_repository_url(repository_url: str) -> Tuple[str, str, str]:
repo_url = repository_url.rstrip("/").removesuffix(".git")
parts = repo_url.split("/")
if len(parts) < 2:
@@ -225,7 +221,7 @@ def parse_repository_url(repository_url: str) -> tuple[str, str, str]:
return repo_url, parts[-2], parts[-1]
def load_manifest_json(manifest_path: Path) -> Optional[dict[str, Any]]:
def load_manifest_json(manifest_path: Path) -> Optional[Dict[str, Any]]:
if not manifest_path.exists():
return None
@@ -246,9 +242,9 @@ def load_manifest_json(manifest_path: Path) -> Optional[dict[str, Any]]:
return None
def iter_plugin_directories() -> list[Path]:
def iter_plugin_directories() -> List[Path]:
plugins_dir = get_plugins_dir()
plugin_directories: list[Path] = []
plugin_directories: List[Path] = []
for path in plugins_dir.iterdir():
safe_path = _resolve_safe_plugin_directory(path, plugins_dir, strict=False)
if safe_path is not None:
@@ -286,4 +282,4 @@ def remove_tree(path: Path) -> None:
os.chmod(target_path, stat.S_IWRITE)
func(target_path)
shutil.rmtree(path, onerror=remove_readonly)
shutil.rmtree(path, onerror=remove_readonly)