初始化提交

This commit is contained in:
2026-04-07 14:11:45 +08:00
parent 46342134b6
commit c412e4de5f
69 changed files with 19321 additions and 1 deletions
View File
+182
View File
@@ -0,0 +1,182 @@
# ///
# callback.py
# 描述:回调接口,用于重置和检测节点微信状态
# 作者:AI Generated
# 创建日期:2026-04-05
# ///
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from typing import Optional, Dict, Any
from services.node_manager import node_manager
from services.log_service import log_service
from database_service import get_db_service
from utils import require_api_key
router = APIRouter(prefix="/callback", tags=["回调接口"])
class NodeResetRequest(BaseModel):
node_id: str
class NodeCallbackRequest(BaseModel):
node_id: str
async def sync_node_from_db(node_id: str) -> bool:
db_service = get_db_service()
if not db_service:
return False
node = db_service.get_node(node_id)
if not node:
return False
existing = node_manager.nodes.get(node_id)
if existing:
existing.api_url = node.api_url
existing.api_key = node.api_key
else:
await node_manager.add_node(
node_id=node.node_id,
name=node.name,
api_url=node.api_url,
api_key=node.api_key,
description=node.description or "",
group=node.group or "default"
)
return True
@router.post("/node/reset", summary="重置节点微信状态")
async def reset_node_wechat_status(request: NodeResetRequest, _=Depends(require_api_key)) -> Dict[str, Any]:
"""
重置节点的微信状态为online
通过发送消息到文件传输助手来检测微信是否正常
如果发送成功,则重置状态为online
"""
node_id = request.node_id
await sync_node_from_db(node_id)
node = await node_manager.get_node(node_id)
if not node:
raise HTTPException(status_code=404, detail=f"Node '{node_id}' not found")
await node_manager.call_node_api(node_id, "/api/wechat/initialize", method="POST")
result = await node_manager.call_node_api(
node_id,
"/api/message/send",
method="POST",
data={"receiver": "文件传输助手", "message": "WXAuto Center 状态检测消息"}
)
if result.get("success"):
node.wechat_status = "online"
db_service = get_db_service()
if db_service:
db_service.update_node(node_id, wechat_status="online")
log_service.success(f"Node {node_id} WeChat status reset to online", "Callback")
return {
"success": True,
"message": "WeChat status reset",
"data": {
"node_id": node_id,
"wechat_status": "online",
"check_result": "success"
}
}
else:
node.wechat_status = "offline"
db_service = get_db_service()
if db_service:
db_service.update_node(node_id, wechat_status="offline")
error_msg = result.get("error", "Unknown error")
log_service.error(f"Node {node_id} WeChat status check failed: {error_msg}", "Callback")
return {
"success": False,
"message": "WeChat status check failed",
"data": {
"node_id": node_id,
"wechat_status": "offline",
"check_result": "failed",
"error": error_msg
}
}
@router.post("/node/check", summary="检测节点微信状态")
async def check_node_wechat_status(request: NodeCallbackRequest, _=Depends(require_api_key)) -> Dict[str, Any]:
"""
检测节点的微信状态
通过发送消息到文件传输助手来检测微信是否正常
返回检测结果
"""
node_id = request.node_id
await sync_node_from_db(node_id)
node = await node_manager.get_node(node_id)
if not node:
raise HTTPException(status_code=404, detail=f"Node '{node_id}' not found")
await node_manager.call_node_api(node_id, "/api/wechat/initialize", method="POST")
result = await node_manager.call_node_api(
node_id,
"/api/message/send",
method="POST",
data={"receiver": "文件传输助手", "message": "WXAuto Center 状态检测消息"}
)
if result.get("success"):
node.wechat_status = "online"
db_service = get_db_service()
if db_service:
db_service.update_node(node_id, wechat_status="online")
return {
"success": True,
"message": "WeChat is normal",
"data": {
"node_id": node_id,
"wechat_status": "online",
"api_status": node.status
}
}
else:
node.wechat_status = "offline"
db_service = get_db_service()
if db_service:
db_service.update_node(node_id, wechat_status="offline")
return {
"success": False,
"message": "WeChat is abnormal",
"data": {
"node_id": node_id,
"wechat_status": "offline",
"api_status": node.status,
"error": result.get("error", "Unknown error")
}
}
@router.get("/node/{node_id}/status", summary="获取节点回调状态")
async def get_node_callback_status(node_id: str, _=Depends(require_api_key)) -> Dict[str, Any]:
"""
获取节点的当前状态,用于回调确认
"""
node = await node_manager.get_node(node_id)
if not node:
raise HTTPException(status_code=404, detail=f"Node '{node_id}' not found")
return {
"success": True,
"data": {
"node_id": node.node_id,
"name": node.name,
"api_url": node.api_url,
"api_status": node.status,
"wechat_status": node.wechat_status,
"is_healthy": node.is_healthy,
"enabled": node.enabled,
"group": node.group
}
}
+176
View File
@@ -0,0 +1,176 @@
# ///
# external.py
# 描述:外部扩展接口,供其他程序调用中控系统
# 作者:AI Generated
# 创建日期:2026-04-05
# ///
from fastapi import APIRouter, HTTPException, Depends
from typing import List, Optional, Dict, Any
from pydantic import BaseModel
from services.node_manager import node_manager
from plugins.base import PluginRegistry
from utils import require_external_key
router = APIRouter(prefix="/external", tags=["外部扩展接口"])
class ExternalMessageRequest(BaseModel):
node_id: str
who: str
msg: str
msg_type: str = "text"
class DataTriggerRequest(BaseModel):
node_id: str
source: str
event: str
data: Dict[str, Any]
auto_send: bool = True
target: Optional[str] = None
class PluginExecuteRequest(BaseModel):
plugin_name: str
action: str
params: Dict[str, Any] = {}
async def sync_node_from_db(node_id: str) -> bool:
from database_service import get_db_service
db_service = get_db_service()
if not db_service:
return False
node = db_service.get_node(node_id)
if not node:
return False
existing = node_manager.nodes.get(node_id)
if existing:
existing.api_url = node.api_url
existing.api_key = node.api_key
else:
await node_manager.add_node(
node_id=node.node_id,
name=node.name,
api_url=node.api_url,
api_key=node.api_key,
description=node.description or "",
group=node.group or "default"
)
return True
@router.post("/send", summary="外部接口-发送消息")
async def external_send_message(request: ExternalMessageRequest,
_=Depends(require_external_key)) -> Dict[str, Any]:
await sync_node_from_db(request.node_id)
result = await node_manager.send_message(
node_id=request.node_id,
who=request.who,
msg=request.msg,
msg_type=request.msg_type
)
return result
@router.post("/trigger", summary="外部接口-触发数据处理")
async def external_trigger(request: DataTriggerRequest,
_=Depends(require_external_key)) -> Dict[str, Any]:
plugin = PluginRegistry.get(request.source)
if not plugin:
raise HTTPException(status_code=404, detail=f"Plugin '{request.source}' not found")
if not plugin.enabled:
raise HTTPException(status_code=400, detail=f"Plugin '{request.source}' is disabled")
execute_result = plugin.execute({
"event": request.event,
"data": request.data
})
if request.auto_send and request.target:
await sync_node_from_db(request.node_id)
msg = request.data.get("message", str(request.data))
send_result = await node_manager.send_message(
node_id=request.node_id,
who=request.target,
msg=msg
)
execute_result["send_result"] = send_result
return {
"success": True,
"plugin_result": execute_result,
"data_triggered": True
}
@router.post("/plugin/execute", summary="外部接口-执行插件")
async def execute_plugin(request: PluginExecuteRequest,
_=Depends(require_external_key)) -> Dict[str, Any]:
plugin = PluginRegistry.get(request.plugin_name)
if not plugin:
raise HTTPException(status_code=404, detail=f"Plugin '{request.plugin_name}' not found")
if not plugin.enabled:
raise HTTPException(status_code=400, detail=f"Plugin '{request.plugin_name}' is disabled")
result = plugin.execute(request.params)
return {"success": True, "result": result}
@router.get("/plugins", summary="外部接口-获取可用插件列表")
async def get_plugins(_=Depends(require_external_key)) -> Dict[str, Any]:
plugins = PluginRegistry.get_all()
enabled_plugins = [p for p in plugins if p.enabled]
return {
"success": True,
"plugins": [p.get_info() for p in enabled_plugins],
"count": len(enabled_plugins)
}
@router.get("/nodes", summary="外部接口-获取可用节点")
async def get_available_nodes(_=Depends(require_external_key)) -> Dict[str, Any]:
from database_service import get_db_service
db_service = get_db_service()
if db_service:
nodes = db_service.get_enabled_nodes()
return {"success": True, "nodes": [n.to_dict() for n in nodes], "count": len(nodes)}
nodes = await node_manager.get_enabled_nodes()
return {"success": True, "nodes": [n.to_dict() for n in nodes], "count": len(nodes)}
@router.post("/ai/process", summary="外部接口-AI处理消息")
async def ai_process_message(
input_text: str,
context: Optional[Dict[str, Any]] = None,
model: Optional[str] = None,
_=Depends(require_external_key)
) -> Dict[str, Any]:
ai_plugin = PluginRegistry.get("simple_ai_agent")
if not ai_plugin or not ai_plugin.enabled:
raise HTTPException(status_code=400, detail="AI agent not available")
if model:
ai_plugin.model = model
result = ai_plugin.process(input_text, context or {})
return {"success": True, "result": result}
@router.post("/webhook/{event}", summary="外部接口-Webhook接收")
async def receive_webhook(event: str,
payload: Dict[str, Any],
_=Depends(require_external_key)) -> Dict[str, Any]:
webhook_plugin = PluginRegistry.get("webhook_trigger")
if webhook_plugin and webhook_plugin.enabled:
result = webhook_plugin.execute({
"event": event,
"data": payload
})
return {"success": True, "result": result}
return {"success": True, "message": "Webhook received", "event": event}
+148
View File
@@ -0,0 +1,148 @@
# ///
# messages.py
# 描述:消息发送API路由
# 作者:AI Generated
# 创建日期:2026-04-05
# ///
from fastapi import APIRouter, HTTPException, Depends
from typing import List, Dict, Any
from pydantic import BaseModel
from services.node_manager import node_manager
from utils import require_api_key, check_node_health
router = APIRouter(prefix="/messages", tags=["消息管理"])
class SendMessageRequest(BaseModel):
node_id: str
who: str
msg: str
msg_type: str = "text"
class SendGroupMessageRequest(BaseModel):
node_id: str
group_name: str
msg: str
msg_type: str = "text"
class BatchMessageRequest(BaseModel):
messages: List[Dict[str, Any]]
async def sync_node_from_db(node_id: str) -> bool:
from database_service import get_db_service
db_service = get_db_service()
if not db_service:
return False
node = db_service.get_node(node_id)
if not node:
return False
existing = node_manager.nodes.get(node_id)
if existing:
existing.api_url = node.api_url
existing.api_key = node.api_key
else:
await node_manager.add_node(
node_id=node.node_id,
name=node.name,
api_url=node.api_url,
api_key=node.api_key,
description=node.description or "",
group=node.group or "default"
)
return True
@router.post("/send", summary="发送消息")
async def send_message(request: SendMessageRequest,
_=Depends(require_api_key)) -> Dict[str, Any]:
await sync_node_from_db(request.node_id)
await check_node_health(request.node_id)
result = await node_manager.send_message(
node_id=request.node_id,
who=request.who,
msg=request.msg,
msg_type=request.msg_type
)
return result
@router.post("/send_group", summary="发送群消息")
async def send_group_message(request: SendGroupMessageRequest,
_=Depends(require_api_key)) -> Dict[str, Any]:
await sync_node_from_db(request.node_id)
await check_node_health(request.node_id)
result = await node_manager.send_message_to_group(
node_id=request.node_id,
group_name=request.group_name,
msg=request.msg,
msg_type=request.msg_type
)
return result
@router.post("/batch", summary="批量发送消息")
async def batch_send(request: BatchMessageRequest,
_=Depends(require_api_key)) -> Dict[str, Any]:
results = []
for msg_item in request.messages:
node_id = msg_item.get("node_id")
who = msg_item.get("who")
msg = msg_item.get("msg")
msg_type = msg_item.get("msg_type", "text")
if not all([node_id, who, msg]):
results.append({
"success": False,
"error": "Missing required fields",
"item": msg_item
})
continue
is_healthy, reason = await node_manager.is_node_healthy(node_id)
if not is_healthy:
results.append({
"success": False,
"error": f"Node not healthy: {reason}",
"node_id": node_id,
"who": who
})
continue
result = await node_manager.send_message(node_id, who, msg, msg_type)
results.append({"node_id": node_id, "who": who, "result": result})
return {"success": True, "results": results, "total": len(results)}
@router.post("/template/{node_id}/{who}", summary="发送模板消息")
async def send_template_message(node_id: str, who: str,
template: str,
params: Dict[str, str],
_=Depends(require_api_key)) -> Dict[str, Any]:
await check_node_health(node_id)
try:
msg = template.format(**params)
except KeyError as e:
raise HTTPException(status_code=400, detail=f"Missing template parameter: {e}")
result = await node_manager.send_message(node_id, who, msg)
return result
@router.get("/history/{node_id}/{chat_name}", summary="获取消息历史")
async def get_message_history(node_id: str, chat_name: str,
limit: int = 50,
_=Depends(require_api_key)) -> Dict[str, Any]:
await check_node_health(node_id)
result = await node_manager.get_messages(node_id, chat_name, limit)
return result
+201
View File
@@ -0,0 +1,201 @@
# ///
# nodes.py
# 描述:节点管理API路由
# 作者:AI Generated
# 创建日期:2026-04-05
# ///
from fastapi import APIRouter, HTTPException, Depends
from typing import List, Optional, Dict, Any
from pydantic import BaseModel
from services.node_manager import node_manager
from config import save_config_to_file, get_settings, NodeConfig
from utils import require_api_key
router = APIRouter(prefix="/nodes", tags=["节点管理"])
class NodeAddRequest(BaseModel):
node_id: str
name: str
api_url: str
api_key: str
description: str = ""
group: str = "default"
class NodeUpdateRequest(BaseModel):
name: Optional[str] = None
api_url: Optional[str] = None
api_key: Optional[str] = None
description: Optional[str] = None
group: Optional[str] = None
enabled: Optional[bool] = None
def save_nodes_config():
nodes_data = node_manager.to_config()
get_settings().nodes = [NodeConfig(**n) for n in nodes_data]
save_config_to_file()
@router.get("/", summary="获取所有节点")
async def get_nodes(group: Optional[str] = None, _=Depends(require_api_key)) -> Dict[str, Any]:
from database_service import get_db_service
db_service = get_db_service()
if db_service:
nodes = db_service.get_all_nodes()
result_nodes = [n.to_dict() for n in nodes]
return {"success": True, "data": result_nodes, "count": len(result_nodes)}
if group:
nodes = await node_manager.get_nodes_by_group(group)
else:
nodes = await node_manager.get_all_nodes()
result_nodes = []
for node in nodes:
node_dict = node.to_dict()
result_nodes.append(node_dict)
return {
"success": True,
"data": result_nodes,
"count": len(result_nodes)
}
@router.get("/{node_id}", summary="获取指定节点信息")
async def get_node(node_id: str, _=Depends(require_api_key)) -> Dict[str, Any]:
from database_service import get_db_service
db_service = get_db_service()
if db_service:
node = db_service.get_node(node_id)
if node:
return {"success": True, "data": node.to_dict()}
node = await node_manager.get_node(node_id)
if not node:
raise HTTPException(status_code=404, detail=f"Node '{node_id}' not found")
return {"success": True, "data": node.to_dict()}
@router.post("/", summary="添加节点")
async def add_node(request: NodeAddRequest, _=Depends(require_api_key)) -> Dict[str, Any]:
from database_service import get_db_service
db_service = get_db_service()
if db_service:
db_service.add_node(
node_id=request.node_id,
name=request.name,
api_url=request.api_url,
api_key=request.api_key,
description=request.description or "",
group=request.group or "default"
)
success = await node_manager.add_node(
node_id=request.node_id,
name=request.name,
api_url=request.api_url,
api_key=request.api_key,
description=request.description,
group=request.group
)
if not success:
raise HTTPException(status_code=400, detail=f"Node '{request.node_id}' already exists")
save_nodes_config()
return {"success": True, "message": f"Node '{request.node_id}' added successfully"}
@router.put("/{node_id}", summary="更新节点信息")
async def update_node(node_id: str, request: NodeUpdateRequest,
_=Depends(require_api_key)) -> Dict[str, Any]:
update_data = request.model_dump(exclude_unset=True)
from database_service import get_db_service
db_service = get_db_service()
if db_service:
db_service.update_node(node_id, **update_data)
success = await node_manager.update_node(node_id, **update_data)
if not success:
raise HTTPException(status_code=404, detail=f"Node '{node_id}' not found")
save_nodes_config()
return {"success": True, "message": f"Node '{node_id}' updated successfully"}
@router.delete("/{node_id}", summary="删除节点")
async def delete_node(node_id: str, _=Depends(require_api_key)) -> Dict[str, Any]:
from database_service import get_db_service
db_service = get_db_service()
if db_service:
db_service.delete_node(node_id)
success = await node_manager.remove_node(node_id)
if not success:
raise HTTPException(status_code=404, detail=f"Node '{node_id}' not found")
save_nodes_config()
return {"success": True, "message": f"Node '{node_id}' deleted successfully"}
@router.get("/{node_id}/status", summary="检查节点状态")
async def check_node_status(node_id: str, _=Depends(require_api_key)) -> Dict[str, Any]:
api_status = await node_manager.check_node_status(node_id)
is_healthy, health_msg = await node_manager.is_node_healthy(node_id)
return {
"success": True,
"data": {
"node_id": node_id,
"api_status": api_status,
"is_healthy": is_healthy,
"health_message": health_msg
}
}
@router.post("/{node_id}/enable", summary="启用节点")
async def enable_node(node_id: str, _=Depends(require_api_key)) -> Dict[str, Any]:
from database_service import get_db_service
db_service = get_db_service()
if db_service:
db_service.update_node(node_id, enabled=True)
success = await node_manager.update_node(node_id, enabled=True)
if not success:
raise HTTPException(status_code=404, detail=f"Node '{node_id}' not found")
save_nodes_config()
return {"success": True, "message": f"Node '{node_id}' enabled"}
@router.post("/{node_id}/disable", summary="禁用节点")
async def disable_node(node_id: str, _=Depends(require_api_key)) -> Dict[str, Any]:
from database_service import get_db_service
db_service = get_db_service()
if db_service:
db_service.update_node(node_id, enabled=False)
success = await node_manager.update_node(node_id, enabled=False)
if not success:
raise HTTPException(status_code=404, detail=f"Node '{node_id}' not found")
save_nodes_config()
return {"success": True, "message": f"Node '{node_id}' disabled"}
@router.get("/{node_id}/friends", summary="获取节点好友列表")
async def get_friends(node_id: str, _=Depends(require_api_key)) -> Dict[str, Any]:
result = await node_manager.get_friends_list(node_id)
return result
@router.get("/{node_id}/groups", summary="获取节点群列表")
async def get_groups(node_id: str, _=Depends(require_api_key)) -> Dict[str, Any]:
result = await node_manager.get_groups_list(node_id)
return result
@router.get("/{node_id}/messages/{chat_name}", summary="获取聊天消息")
async def get_messages(node_id: str, chat_name: str, limit: int = 20,
_=Depends(require_api_key)) -> Dict[str, Any]:
result = await node_manager.get_messages(node_id, chat_name, limit)
return result
+412
View File
@@ -0,0 +1,412 @@
# ///
# plugins.py
# 描述:插件管理API路由
# 作者:AI Generated
# 创建日期:2026-04-05
# ///
from fastapi import APIRouter, HTTPException, Depends
from typing import List, Optional, Dict, Any
from pydantic import BaseModel
from plugins.base import PluginRegistry, PluginType
from utils import require_api_key
from database_service import get_db_service
router = APIRouter(prefix="/plugins", tags=["插件管理"])
class PluginConfigRequest(BaseModel):
name: str
config: Dict[str, Any]
def _sync_plugin_from_db(plugin_name: str):
db_service = get_db_service()
if not db_service:
return
plugin_config = db_service.get_plugin_config(plugin_name)
plugin = PluginRegistry.get(plugin_name)
if not plugin:
return
if plugin_config:
import json
plugin.initialize(json.loads(plugin_config.config_json) if plugin_config.config_json else {})
if plugin_config.enabled:
plugin.enable()
else:
plugin.disable()
@router.get("/", summary="获取所有插件")
async def get_plugins(_=Depends(require_api_key)) -> Dict[str, Any]:
import json
plugins = PluginRegistry.get_all()
result = []
for p in plugins:
info = p.get_info()
db_service = get_db_service()
if db_service:
pc = db_service.get_plugin_config(p.plugin_name)
if pc:
info["config"] = json.loads(pc.config_json) if pc.config_json else {}
info["enabled"] = pc.enabled
result.append(info)
return {
"success": True,
"plugins": result,
"count": len(result)
}
@router.get("/hotloaded", summary="获取热加载插件列表")
async def get_hotloaded_plugins(_=Depends(require_api_key)) -> Dict[str, Any]:
try:
from plugins.plugin_loader import hot_plugin_loader
plugins = hot_plugin_loader.get_loaded_plugins()
return {
"success": True,
"plugins": plugins,
"count": len(plugins),
"plugin_dir": hot_plugin_loader.plugin_dir
}
except Exception as e:
return {"success": False, "error": str(e)}
@router.post("/hotreload", summary="手动触发插件热重载")
async def trigger_hot_reload(_=Depends(require_api_key)) -> Dict[str, Any]:
try:
from plugins.plugin_loader import hot_plugin_loader
hot_plugin_loader.scan_and_load_plugins()
plugins = hot_plugin_loader.get_loaded_plugins()
return {
"success": True,
"message": "Hot reload triggered",
"plugins": plugins
}
except Exception as e:
return {"success": False, "error": str(e)}
@router.get("/type/{plugin_type}", summary="按类型获取插件")
async def get_plugins_by_type(plugin_type: str, _=Depends(require_api_key)) -> Dict[str, Any]:
try:
ptype = PluginType(plugin_type)
except ValueError:
raise HTTPException(status_code=400, detail=f"Invalid plugin type: {plugin_type}")
plugins = PluginRegistry.get_by_type(ptype)
return {
"success": True,
"plugins": [p.get_info() for p in plugins],
"count": len(plugins)
}
@router.get("/custom/list", summary="列出自定义插件文件")
async def list_custom_plugins(_=Depends(require_api_key)) -> Dict[str, Any]:
try:
import os
custom_dir = "/app/plugins/custom"
if not os.path.exists(custom_dir):
return {"success": True, "plugins": [], "count": 0}
files = []
for f in os.listdir(custom_dir):
if f.endswith('.py') and not f.startswith('_'):
filepath = os.path.join(custom_dir, f)
stat = os.stat(filepath)
files.append({
"name": f,
"size": stat.st_size,
"modified": stat.st_mtime,
"path": filepath
})
return {
"success": True,
"plugins": files,
"count": len(files),
"directory": custom_dir
}
except Exception as e:
return {"success": False, "error": str(e)}
@router.post("/custom/create", summary="创建自定义插件文件")
async def create_custom_plugin(name: str, code: str, _=Depends(require_api_key)) -> Dict[str, Any]:
try:
import os
custom_dir = "/app/plugins/custom"
os.makedirs(custom_dir, exist_ok=True)
safe_name = name.replace("..", "").replace("/", "").replace("\\", "")
if not safe_name.endswith(".py"):
safe_name += ".py"
filepath = os.path.join(custom_dir, safe_name)
with open(filepath, 'w') as f:
f.write(code)
from plugins.plugin_loader import hot_plugin_loader
hot_plugin_loader.scan_and_load_plugins()
return {
"success": True,
"message": f"Plugin {safe_name} created",
"path": filepath
}
except Exception as e:
return {"success": False, "error": str(e)}
@router.delete("/custom/{plugin_name}", summary="删除自定义插件")
async def delete_custom_plugin(plugin_name: str, _=Depends(require_api_key)) -> Dict[str, Any]:
try:
import os
custom_dir = "/app/plugins/custom"
safe_name = plugin_name.replace("..", "").replace("/", "").replace("\\", "")
if not safe_name.endswith(".py"):
safe_name += ".py"
filepath = os.path.join(custom_dir, safe_name)
if os.path.exists(filepath):
os.remove(filepath)
from plugins.plugin_loader import hot_plugin_loader
hot_plugin_loader.scan_and_load_plugins()
return {"success": True, "message": f"Plugin {safe_name} deleted"}
else:
return {"success": False, "error": "Plugin file not found"}
except Exception as e:
return {"success": False, "error": str(e)}
@router.get("/custom/{plugin_name}/download", summary="下载自定义插件源码")
async def download_custom_plugin(plugin_name: str, _=Depends(require_api_key)) -> Dict[str, Any]:
try:
import os
custom_dir = "/app/plugins/custom"
safe_name = plugin_name.replace("..", "").replace("/", "").replace("\\", "")
if not safe_name.endswith(".py"):
safe_name += ".py"
filepath = os.path.join(custom_dir, safe_name)
if os.path.exists(filepath):
with open(filepath, 'r') as f:
code = f.read()
return {
"success": True,
"name": safe_name,
"code": code
}
else:
return {"success": False, "error": "Plugin file not found"}
except Exception as e:
return {"success": False, "error": str(e)}
@router.get("/{plugin_name}", summary="获取插件详情")
async def get_plugin(plugin_name: str, _=Depends(require_api_key)) -> Dict[str, Any]:
import json
plugin = PluginRegistry.get(plugin_name)
if not plugin:
raise HTTPException(status_code=404, detail=f"Plugin '{plugin_name}' not found")
info = plugin.get_info()
db_service = get_db_service()
if db_service:
pc = db_service.get_plugin_config(plugin_name)
if pc:
info["config"] = json.loads(pc.config_json) if pc.config_json else {}
info["enabled"] = pc.enabled
return {"success": True, "plugin": info}
@router.post("/{plugin_name}/enable", summary="启用插件")
async def enable_plugin(plugin_name: str, _=Depends(require_api_key)) -> Dict[str, Any]:
plugin = PluginRegistry.get(plugin_name)
if not plugin:
raise HTTPException(status_code=404, detail=f"Plugin '{plugin_name}' not found")
PluginRegistry.enable(plugin_name)
db_service = get_db_service()
if db_service:
pc = db_service.get_plugin_config(plugin_name)
if pc:
db_service.update_plugin_enabled(plugin_name, True)
else:
db_service.save_plugin_config(plugin_name, plugin.config or {}, True)
return {"success": True, "message": f"Plugin '{plugin_name}' enabled"}
@router.post("/{plugin_name}/disable", summary="禁用插件")
async def disable_plugin(plugin_name: str, _=Depends(require_api_key)) -> Dict[str, Any]:
plugin = PluginRegistry.get(plugin_name)
if not plugin:
raise HTTPException(status_code=404, detail=f"Plugin '{plugin_name}' not found")
PluginRegistry.disable(plugin_name)
db_service = get_db_service()
if db_service:
pc = db_service.get_plugin_config(plugin_name)
if pc:
db_service.update_plugin_enabled(plugin_name, False)
else:
db_service.save_plugin_config(plugin_name, plugin.config or {}, False)
return {"success": True, "message": f"Plugin '{plugin_name}' disabled"}
@router.put("/{plugin_name}/config", summary="更新插件配置")
async def update_plugin_config(plugin_name: str,
request: PluginConfigRequest,
_=Depends(require_api_key)) -> Dict[str, Any]:
plugin = PluginRegistry.get(plugin_name)
if not plugin:
raise HTTPException(status_code=404, detail=f"Plugin '{plugin_name}' not found")
success = plugin.initialize(request.config)
if not success:
raise HTTPException(status_code=400, detail=f"Failed to initialize plugin '{plugin_name}'")
db_service = get_db_service()
if db_service:
pc = db_service.get_plugin_config(plugin_name)
enabled = pc.enabled if pc else False
db_service.save_plugin_config(plugin_name, request.config, enabled)
return {"success": True, "message": f"Plugin '{plugin_name}' config updated"}
@router.post("/{plugin_name}/execute", summary="执行插件")
async def execute_plugin(plugin_name: str,
request: Optional[Dict[str, Any]] = None,
_=Depends(require_api_key)) -> Dict[str, Any]:
plugin = PluginRegistry.get(plugin_name)
if not plugin:
raise HTTPException(status_code=404, detail=f"Plugin '{plugin_name}' not found")
if not plugin.enabled:
return {"success": False, "error": f"Plugin '{plugin_name}' is disabled"}
params = request or {}
result = plugin.execute(params)
return {"success": True, "result": result}
@router.get("/{plugin_name}/logs", summary="获取插件日志")
async def get_plugin_logs(
plugin_name: str,
lines: int = 100,
level: Optional[str] = None,
_=Depends(require_api_key)
) -> Dict[str, Any]:
try:
import logging
import os
plugin = PluginRegistry.get(plugin_name)
if not plugin:
raise HTTPException(status_code=404, detail=f"Plugin '{plugin_name}' not found")
log_dir = "/app/data/logs"
if not os.path.exists(log_dir):
os.makedirs(log_dir, exist_ok=True)
log_file = os.path.join(log_dir, f"{plugin_name}.log")
app_log = os.path.join(log_dir, "app.log")
all_logs = []
if os.path.exists(log_file):
with open(log_file, 'r', encoding='utf-8') as f:
all_logs.extend(f.readlines())
if os.path.exists(app_log):
with open(app_log, 'r', encoding='utf-8') as f:
for line in f:
if plugin_name in line or f"[{plugin_name}]" in line:
all_logs.append(line)
filtered_logs = []
for line in all_logs:
if level:
if level.upper() in line.upper():
filtered_logs.append(line.strip())
else:
filtered_logs.append(line.strip())
filtered_logs = filtered_logs[-lines:] if len(filtered_logs) > lines else filtered_logs
return {
"success": True,
"plugin_name": plugin_name,
"log_file": log_file,
"count": len(filtered_logs),
"logs": filtered_logs
}
except Exception as e:
return {"success": False, "error": str(e)}
@router.get("/{plugin_name}/schema", summary="获取插件配置表单Schema")
async def get_plugin_schema(plugin_name: str, _=Depends(require_api_key)) -> Dict[str, Any]:
try:
plugin = PluginRegistry.get(plugin_name)
if not plugin:
raise HTTPException(status_code=404, detail=f"Plugin '{plugin_name}' not found")
schema = None
if hasattr(plugin, 'get_config_schema'):
schema = plugin.get_config_schema()
elif hasattr(plugin, 'config_schema'):
schema = plugin.config_schema
if schema:
if callable(schema):
schema = schema()
if hasattr(schema, 'to_form_schema'):
return {
"success": True,
"schema": schema.to_form_schema(),
"has_form": True
}
return {
"success": True,
"schema": schema,
"has_form": True
}
return {
"success": True,
"schema": None,
"has_form": False,
"message": "此插件暂无表单配置"
}
except Exception as e:
return {"success": False, "error": str(e)}
@router.post("/{plugin_name}/logs/clear", summary="清空插件日志")
async def clear_plugin_logs(plugin_name: str, _=Depends(require_api_key)) -> Dict[str, Any]:
try:
import os
plugin = PluginRegistry.get(plugin_name)
if not plugin:
raise HTTPException(status_code=404, detail=f"Plugin '{plugin_name}' not found")
log_file = f"/app/data/logs/{plugin_name}.log"
if os.path.exists(log_file):
with open(log_file, 'w') as f:
f.write("")
return {
"success": True,
"message": f"Plugin '{plugin_name}' logs cleared"
}
except Exception as e:
return {"success": False, "error": str(e)}
+137
View File
@@ -0,0 +1,137 @@
# ///
# webhook.py
# 描述:Webhook管理API路由
# 作者:AI Generated
# 创建日期:2026-04-06
# ///
from fastapi import APIRouter, HTTPException, Depends
from typing import List, Dict, Any
from pydantic import BaseModel
from database_service import get_db_service
from utils import require_api_key
router = APIRouter(prefix="/webhook", tags=["Webhook管理"])
class WebhookAddRequest(BaseModel):
name: str
url: str
format: str = "bark"
event_types: List[str] = []
class WebhookUpdateRequest(BaseModel):
name: str = None
url: str = None
format: str = None
enabled: bool = None
event_types: List[str] = None
@router.get("/", summary="获取所有Webhook")
async def get_webhooks(_=Depends(require_api_key)) -> Dict[str, Any]:
db_service = get_db_service()
if not db_service:
raise HTTPException(status_code=500, detail="Database not available")
webhooks = db_service.get_all_webhooks()
return {
"success": True,
"data": [w.to_dict() for w in webhooks],
"count": len(webhooks)
}
@router.get("/{webhook_id}", summary="获取指定Webhook")
async def get_webhook(webhook_id: int, _=Depends(require_api_key)) -> Dict[str, Any]:
db_service = get_db_service()
if not db_service:
raise HTTPException(status_code=500, detail="Database not available")
webhook = db_service.get_webhook(webhook_id)
if not webhook:
raise HTTPException(status_code=404, detail=f"Webhook {webhook_id} not found")
return {"success": True, "data": webhook.to_dict()}
@router.post("/", summary="添加Webhook")
async def add_webhook(request: WebhookAddRequest,
_=Depends(require_api_key)) -> Dict[str, Any]:
db_service = get_db_service()
if not db_service:
raise HTTPException(status_code=500, detail="Database not available")
success = db_service.add_webhook(
name=request.name,
url=request.url,
format=request.format,
event_types=request.event_types
)
if not success:
raise HTTPException(status_code=400, detail="Failed to add webhook")
return {"success": True, "message": "Webhook added successfully"}
@router.put("/{webhook_id}", summary="更新Webhook")
async def update_webhook(webhook_id: int,
request: WebhookUpdateRequest,
_=Depends(require_api_key)) -> Dict[str, Any]:
db_service = get_db_service()
if not db_service:
raise HTTPException(status_code=500, detail="Database not available")
update_data = request.model_dump(exclude_unset=True)
success = db_service.update_webhook(webhook_id, **update_data)
if not success:
raise HTTPException(status_code=404, detail=f"Webhook {webhook_id} not found")
return {"success": True, "message": "Webhook updated successfully"}
@router.delete("/{webhook_id}", summary="删除Webhook")
async def delete_webhook(webhook_id: int,
_=Depends(require_api_key)) -> Dict[str, Any]:
db_service = get_db_service()
if not db_service:
raise HTTPException(status_code=500, detail="Database not available")
success = db_service.delete_webhook(webhook_id)
if not success:
raise HTTPException(status_code=404, detail=f"Webhook {webhook_id} not found")
return {"success": True, "message": "Webhook deleted successfully"}
@router.post("/{webhook_id}/enable", summary="启用Webhook")
async def enable_webhook(webhook_id: int,
_=Depends(require_api_key)) -> Dict[str, Any]:
db_service = get_db_service()
if not db_service:
raise HTTPException(status_code=500, detail="Database not available")
success = db_service.update_webhook(webhook_id, enabled=True)
if not success:
raise HTTPException(status_code=404, detail=f"Webhook {webhook_id} not found")
return {"success": True, "message": "Webhook enabled"}
@router.post("/{webhook_id}/disable", summary="禁用Webhook")
async def disable_webhook(webhook_id: int,
_=Depends(require_api_key)) -> Dict[str, Any]:
db_service = get_db_service()
if not db_service:
raise HTTPException(status_code=500, detail="Database not available")
success = db_service.update_webhook(webhook_id, enabled=False)
if not success:
raise HTTPException(status_code=404, detail=f"Webhook {webhook_id} not found")
return {"success": True, "message": "Webhook disabled"}