Merge branch 'feature/refactor' of github.com:AUTO-MAS-Project/AUTO-MAS into feature/refactor
This commit is contained in:
@@ -28,6 +28,7 @@ from fastapi import APIRouter, Body
|
||||
from app.core import Config
|
||||
from app.services import System, Notify
|
||||
from app.models.schema import *
|
||||
import uuid
|
||||
|
||||
router = APIRouter(prefix="/api/setting", tags=["全局设置"])
|
||||
|
||||
@@ -95,3 +96,164 @@ async def test_notify() -> OutBase:
|
||||
code=500, status="error", message=f"{type(e).__name__}: {str(e)}"
|
||||
)
|
||||
return OutBase()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/webhook/create", summary="创建自定义Webhook", response_model=OutBase, status_code=200
|
||||
)
|
||||
async def create_webhook(webhook_data: dict = Body(...)) -> OutBase:
|
||||
"""创建自定义Webhook"""
|
||||
|
||||
try:
|
||||
# 生成唯一ID
|
||||
webhook_id = str(uuid.uuid4())
|
||||
|
||||
# 创建webhook配置
|
||||
webhook_config = {
|
||||
"id": webhook_id,
|
||||
"name": webhook_data.get("name", ""),
|
||||
"url": webhook_data.get("url", ""),
|
||||
"template": webhook_data.get("template", ""),
|
||||
"enabled": webhook_data.get("enabled", True),
|
||||
"headers": webhook_data.get("headers", {}),
|
||||
"method": webhook_data.get("method", "POST")
|
||||
}
|
||||
|
||||
# 获取当前配置
|
||||
current_config = await Config.get_setting()
|
||||
custom_webhooks = current_config.get("Notify", {}).get("CustomWebhooks", [])
|
||||
|
||||
# 添加新webhook
|
||||
custom_webhooks.append(webhook_config)
|
||||
|
||||
# 更新配置
|
||||
update_data = {
|
||||
"Notify": {
|
||||
"CustomWebhooks": custom_webhooks
|
||||
}
|
||||
}
|
||||
await Config.update_setting(update_data)
|
||||
|
||||
return OutBase(message=f"Webhook '{webhook_config['name']}' 创建成功")
|
||||
|
||||
except Exception as e:
|
||||
return OutBase(
|
||||
code=500, status="error", message=f"{type(e).__name__}: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/webhook/update", summary="更新自定义Webhook", response_model=OutBase, status_code=200
|
||||
)
|
||||
async def update_webhook(webhook_data: dict = Body(...)) -> OutBase:
|
||||
"""更新自定义Webhook"""
|
||||
|
||||
try:
|
||||
webhook_id = webhook_data.get("id")
|
||||
if not webhook_id:
|
||||
return OutBase(code=400, status="error", message="缺少Webhook ID")
|
||||
|
||||
# 获取当前配置
|
||||
current_config = await Config.get_setting()
|
||||
custom_webhooks = current_config.get("Notify", {}).get("CustomWebhooks", [])
|
||||
|
||||
# 查找并更新webhook
|
||||
updated = False
|
||||
for i, webhook in enumerate(custom_webhooks):
|
||||
if webhook.get("id") == webhook_id:
|
||||
custom_webhooks[i].update({
|
||||
"name": webhook_data.get("name", webhook.get("name", "")),
|
||||
"url": webhook_data.get("url", webhook.get("url", "")),
|
||||
"template": webhook_data.get("template", webhook.get("template", "")),
|
||||
"enabled": webhook_data.get("enabled", webhook.get("enabled", True)),
|
||||
"headers": webhook_data.get("headers", webhook.get("headers", {})),
|
||||
"method": webhook_data.get("method", webhook.get("method", "POST"))
|
||||
})
|
||||
updated = True
|
||||
break
|
||||
|
||||
if not updated:
|
||||
return OutBase(code=404, status="error", message="Webhook不存在")
|
||||
|
||||
# 更新配置
|
||||
update_data = {
|
||||
"Notify": {
|
||||
"CustomWebhooks": custom_webhooks
|
||||
}
|
||||
}
|
||||
await Config.update_setting(update_data)
|
||||
|
||||
return OutBase(message="Webhook更新成功")
|
||||
|
||||
except Exception as e:
|
||||
return OutBase(
|
||||
code=500, status="error", message=f"{type(e).__name__}: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/webhook/delete", summary="删除自定义Webhook", response_model=OutBase, status_code=200
|
||||
)
|
||||
async def delete_webhook(webhook_data: dict = Body(...)) -> OutBase:
|
||||
"""删除自定义Webhook"""
|
||||
|
||||
try:
|
||||
webhook_id = webhook_data.get("id")
|
||||
if not webhook_id:
|
||||
return OutBase(code=400, status="error", message="缺少Webhook ID")
|
||||
|
||||
# 获取当前配置
|
||||
current_config = await Config.get_setting()
|
||||
custom_webhooks = current_config.get("Notify", {}).get("CustomWebhooks", [])
|
||||
|
||||
# 查找并删除webhook
|
||||
original_length = len(custom_webhooks)
|
||||
custom_webhooks = [w for w in custom_webhooks if w.get("id") != webhook_id]
|
||||
|
||||
if len(custom_webhooks) == original_length:
|
||||
return OutBase(code=404, status="error", message="Webhook不存在")
|
||||
|
||||
# 更新配置
|
||||
update_data = {
|
||||
"Notify": {
|
||||
"CustomWebhooks": custom_webhooks
|
||||
}
|
||||
}
|
||||
await Config.update_setting(update_data)
|
||||
|
||||
return OutBase(message="Webhook删除成功")
|
||||
|
||||
except Exception as e:
|
||||
return OutBase(
|
||||
code=500, status="error", message=f"{type(e).__name__}: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/webhook/test", summary="测试自定义Webhook", response_model=OutBase, status_code=200
|
||||
)
|
||||
async def test_webhook(webhook_data: dict = Body(...)) -> OutBase:
|
||||
"""测试自定义Webhook"""
|
||||
|
||||
try:
|
||||
webhook_config = {
|
||||
"name": webhook_data.get("name", "测试Webhook"),
|
||||
"url": webhook_data.get("url", ""),
|
||||
"template": webhook_data.get("template", ""),
|
||||
"enabled": True,
|
||||
"headers": webhook_data.get("headers", {}),
|
||||
"method": webhook_data.get("method", "POST")
|
||||
}
|
||||
|
||||
await Notify.CustomWebhookPush(
|
||||
"AUTO-MAS Webhook测试",
|
||||
"这是一条测试消息,如果您收到此消息,说明Webhook配置正确!",
|
||||
webhook_config
|
||||
)
|
||||
|
||||
return OutBase(message="Webhook测试成功")
|
||||
|
||||
except Exception as e:
|
||||
return OutBase(
|
||||
code=500, status="error", message=f"Webhook测试失败: {str(e)}"
|
||||
)
|
||||
|
||||
@@ -105,10 +105,7 @@ class GlobalConfig(ConfigBase):
|
||||
Notify_ToAddress = ConfigItem("Notify", "ToAddress", "")
|
||||
Notify_IfServerChan = ConfigItem("Notify", "IfServerChan", False, BoolValidator())
|
||||
Notify_ServerChanKey = ConfigItem("Notify", "ServerChanKey", "")
|
||||
Notify_IfCompanyWebHookBot = ConfigItem(
|
||||
"Notify", "IfCompanyWebHookBot", False, BoolValidator()
|
||||
)
|
||||
Notify_CompanyWebHookBotUrl = ConfigItem("Notify", "CompanyWebHookBotUrl", "")
|
||||
Notify_CustomWebhooks = ConfigItem("Notify", "CustomWebhooks", [])
|
||||
|
||||
Update_IfAutoUpdate = ConfigItem("Update", "IfAutoUpdate", False, BoolValidator())
|
||||
Update_Source = ConfigItem(
|
||||
@@ -348,12 +345,7 @@ class MaaUserConfig(ConfigBase):
|
||||
"Notify", "IfServerChan", False, BoolValidator()
|
||||
)
|
||||
self.Notify_ServerChanKey = ConfigItem("Notify", "ServerChanKey", "")
|
||||
self.Notify_IfCompanyWebHookBot = ConfigItem(
|
||||
"Notify", "IfCompanyWebHookBot", False, BoolValidator()
|
||||
)
|
||||
self.Notify_CompanyWebHookBotUrl = ConfigItem(
|
||||
"Notify", "CompanyWebHookBotUrl", ""
|
||||
)
|
||||
self.Notify_CustomWebhooks = ConfigItem("Notify", "CustomWebhooks", [])
|
||||
|
||||
def get_plan_info(self) -> Dict[str, Union[str, int]]:
|
||||
"""获取当前的计划下信息"""
|
||||
@@ -541,12 +533,7 @@ class GeneralUserConfig(ConfigBase):
|
||||
"Notify", "IfServerChan", False, BoolValidator()
|
||||
)
|
||||
self.Notify_ServerChanKey = ConfigItem("Notify", "ServerChanKey", "")
|
||||
self.Notify_IfCompanyWebHookBot = ConfigItem(
|
||||
"Notify", "IfCompanyWebHookBot", False, BoolValidator()
|
||||
)
|
||||
self.Notify_CompanyWebHookBotUrl = ConfigItem(
|
||||
"Notify", "CompanyWebHookBotUrl", ""
|
||||
)
|
||||
self.Notify_CustomWebhooks = ConfigItem("Notify", "CustomWebhooks", [])
|
||||
|
||||
|
||||
class GeneralConfig(ConfigBase):
|
||||
|
||||
@@ -110,6 +110,16 @@ class GlobalConfig_UI(BaseModel):
|
||||
IfToTray: Optional[bool] = Field(default=None, description="是否最小化到托盘")
|
||||
|
||||
|
||||
class CustomWebhook(BaseModel):
|
||||
id: str = Field(..., description="Webhook唯一标识")
|
||||
name: str = Field(..., description="Webhook名称")
|
||||
url: str = Field(..., description="Webhook URL")
|
||||
template: str = Field(..., description="消息模板")
|
||||
enabled: bool = Field(default=True, description="是否启用")
|
||||
headers: Optional[Dict[str, str]] = Field(default=None, description="自定义请求头")
|
||||
method: Optional[Literal["POST", "GET"]] = Field(default="POST", description="请求方法")
|
||||
|
||||
|
||||
class GlobalConfig_Notify(BaseModel):
|
||||
SendTaskResultTime: Optional[Literal["不推送", "任何时刻", "仅失败时"]] = Field(
|
||||
default=None, description="任务结果推送时机"
|
||||
@@ -130,11 +140,8 @@ class GlobalConfig_Notify(BaseModel):
|
||||
default=None, description="是否使用ServerChan推送"
|
||||
)
|
||||
ServerChanKey: Optional[str] = Field(default=None, description="ServerChan推送密钥")
|
||||
IfCompanyWebHookBot: Optional[bool] = Field(
|
||||
default=None, description="是否使用企微Webhook推送"
|
||||
)
|
||||
CompanyWebHookBotUrl: Optional[str] = Field(
|
||||
default=None, description="企微Webhook Bot URL"
|
||||
CustomWebhooks: Optional[List[CustomWebhook]] = Field(
|
||||
default=None, description="自定义Webhook列表"
|
||||
)
|
||||
|
||||
|
||||
@@ -303,11 +310,8 @@ class MaaUserConfig_Notify(BaseModel):
|
||||
default=None, description="是否使用Server酱推送"
|
||||
)
|
||||
ServerChanKey: Optional[str] = Field(default=None, description="ServerChanKey")
|
||||
IfCompanyWebHookBot: Optional[bool] = Field(
|
||||
default=None, description="是否使用Webhook推送"
|
||||
)
|
||||
CompanyWebHookBotUrl: Optional[str] = Field(
|
||||
default=None, description="企微Webhook Bot URL"
|
||||
CustomWebhooks: Optional[List[CustomWebhook]] = Field(
|
||||
default=None, description="用户自定义Webhook列表"
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -170,9 +170,134 @@ class Notification:
|
||||
else:
|
||||
raise Exception(f"ServerChan 推送通知失败: {response.text}")
|
||||
|
||||
async def CustomWebhookPush(self, title, content, webhook_config) -> None:
|
||||
"""
|
||||
自定义 Webhook 推送通知
|
||||
|
||||
:param title: 通知标题
|
||||
:param content: 通知内容
|
||||
:param webhook_config: Webhook配置对象
|
||||
"""
|
||||
|
||||
if not webhook_config.get("url"):
|
||||
raise ValueError("Webhook URL 不能为空")
|
||||
|
||||
if not webhook_config.get("enabled", True):
|
||||
logger.info(f"Webhook {webhook_config.get('name', 'Unknown')} 已禁用,跳过推送")
|
||||
return
|
||||
|
||||
# 解析模板
|
||||
template = webhook_config.get("template", '{"title": "{title}", "content": "{content}"}')
|
||||
|
||||
# 替换模板变量
|
||||
try:
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
# 准备模板变量
|
||||
template_vars = {
|
||||
"title": title,
|
||||
"content": content,
|
||||
"datetime": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"date": datetime.now().strftime("%Y-%m-%d"),
|
||||
"time": datetime.now().strftime("%H:%M:%S")
|
||||
}
|
||||
|
||||
logger.debug(f"原始模板: {template}")
|
||||
logger.debug(f"模板变量: {template_vars}")
|
||||
|
||||
# 先尝试作为JSON模板处理
|
||||
try:
|
||||
# 解析模板为JSON对象,然后替换其中的变量
|
||||
template_obj = json.loads(template)
|
||||
|
||||
# 递归替换JSON对象中的变量
|
||||
def replace_variables(obj):
|
||||
if isinstance(obj, dict):
|
||||
return {k: replace_variables(v) for k, v in obj.items()}
|
||||
elif isinstance(obj, list):
|
||||
return [replace_variables(item) for item in obj]
|
||||
elif isinstance(obj, str):
|
||||
result = obj
|
||||
for key, value in template_vars.items():
|
||||
result = result.replace(f"{{{key}}}", str(value))
|
||||
return result
|
||||
else:
|
||||
return obj
|
||||
|
||||
data = replace_variables(template_obj)
|
||||
logger.debug(f"成功解析JSON模板: {data}")
|
||||
|
||||
except json.JSONDecodeError:
|
||||
# 如果不是有效的JSON,作为字符串模板处理
|
||||
logger.debug("模板不是有效JSON,作为字符串模板处理")
|
||||
formatted_template = template
|
||||
for key, value in template_vars.items():
|
||||
# 转义特殊字符以避免JSON解析错误
|
||||
safe_value = str(value).replace('"', '\\"').replace('\n', '\\n').replace('\r', '\\r')
|
||||
formatted_template = formatted_template.replace(f"{{{key}}}", safe_value)
|
||||
|
||||
# 再次尝试解析为JSON
|
||||
try:
|
||||
data = json.loads(formatted_template)
|
||||
logger.debug(f"字符串模板解析为JSON成功: {data}")
|
||||
except json.JSONDecodeError:
|
||||
# 最终作为纯文本发送
|
||||
data = formatted_template
|
||||
logger.debug(f"作为纯文本发送: {data}")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"模板解析失败,使用默认格式: {e}")
|
||||
data = {"title": title, "content": content}
|
||||
|
||||
# 准备请求头
|
||||
headers = {"Content-Type": "application/json"}
|
||||
if webhook_config.get("headers"):
|
||||
headers.update(webhook_config["headers"])
|
||||
|
||||
# 发送请求
|
||||
method = webhook_config.get("method", "POST").upper()
|
||||
|
||||
try:
|
||||
if method == "POST":
|
||||
if isinstance(data, dict):
|
||||
response = requests.post(
|
||||
url=webhook_config["url"],
|
||||
json=data,
|
||||
headers=headers,
|
||||
timeout=10,
|
||||
proxies=Config.get_proxies()
|
||||
)
|
||||
else:
|
||||
response = requests.post(
|
||||
url=webhook_config["url"],
|
||||
data=data,
|
||||
headers=headers,
|
||||
timeout=10,
|
||||
proxies=Config.get_proxies()
|
||||
)
|
||||
else: # GET
|
||||
params = data if isinstance(data, dict) else {"message": data}
|
||||
response = requests.get(
|
||||
url=webhook_config["url"],
|
||||
params=params,
|
||||
headers=headers,
|
||||
timeout=10,
|
||||
proxies=Config.get_proxies()
|
||||
)
|
||||
|
||||
# 检查响应
|
||||
if response.status_code == 200:
|
||||
logger.success(f"自定义Webhook推送成功: {webhook_config.get('name', 'Unknown')} - {title}")
|
||||
else:
|
||||
raise Exception(f"HTTP {response.status_code}: {response.text}")
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"自定义Webhook推送失败 ({webhook_config.get('name', 'Unknown')}): {str(e)}")
|
||||
|
||||
async def WebHookPush(self, title, content, webhook_url) -> None:
|
||||
"""
|
||||
WebHook 推送通知
|
||||
WebHook 推送通知 (兼容旧版企业微信格式)
|
||||
|
||||
:param title: 通知标题
|
||||
:param content: 通知内容
|
||||
@@ -264,17 +389,22 @@ class Notification:
|
||||
Config.get("Notify", "ServerChanKey"),
|
||||
)
|
||||
|
||||
# 发送WebHook通知
|
||||
if Config.get("Notify", "IfCompanyWebHookBot"):
|
||||
await self.WebHookPush(
|
||||
# 发送自定义Webhook通知
|
||||
try:
|
||||
custom_webhooks = Config.get("Notify", "CustomWebhooks")
|
||||
except AttributeError:
|
||||
custom_webhooks = []
|
||||
if custom_webhooks:
|
||||
for webhook in custom_webhooks:
|
||||
if webhook.get("enabled", True):
|
||||
try:
|
||||
await self.CustomWebhookPush(
|
||||
"AUTO-MAS测试通知",
|
||||
"这是 AUTO-MAS 外部通知测试信息。如果你看到了这段内容, 说明 AUTO-MAS 的通知功能已经正确配置且可以正常工作!",
|
||||
Config.get("Notify", "CompanyWebHookBotUrl"),
|
||||
)
|
||||
await self.CompanyWebHookBotPushImage(
|
||||
Path.cwd() / "res/images/notification/test_notify.png",
|
||||
Config.get("Notify", "CompanyWebHookBotUrl"),
|
||||
webhook
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"自定义Webhook测试失败 ({webhook.get('name', 'Unknown')}): {e}")
|
||||
|
||||
logger.success("测试通知发送完成")
|
||||
|
||||
|
||||
@@ -1906,12 +1906,22 @@ class MaaManager:
|
||||
Config.get("Notify", "ServerChanKey"),
|
||||
)
|
||||
|
||||
if Config.get("Notify", "IfCompanyWebHookBot"):
|
||||
await Notify.WebHookPush(
|
||||
# 发送自定义Webhook通知
|
||||
try:
|
||||
custom_webhooks = Config.get("Notify", "CustomWebhooks")
|
||||
except AttributeError:
|
||||
custom_webhooks = []
|
||||
if custom_webhooks:
|
||||
for webhook in custom_webhooks:
|
||||
if webhook.get("enabled", True):
|
||||
try:
|
||||
await Notify.CustomWebhookPush(
|
||||
title,
|
||||
f"{message_text}\n\nAUTO-MAS 敬上",
|
||||
Config.get("Notify", "CompanyWebHookBotUrl"),
|
||||
webhook
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"自定义Webhook推送失败 ({webhook.get('name', 'Unknown')}): {e}")
|
||||
|
||||
elif mode == "统计信息":
|
||||
|
||||
@@ -1962,12 +1972,22 @@ class MaaManager:
|
||||
Config.get("Notify", "ServerChanKey"),
|
||||
)
|
||||
|
||||
if Config.get("Notify", "IfCompanyWebHookBot"):
|
||||
await Notify.WebHookPush(
|
||||
# 发送自定义Webhook通知
|
||||
try:
|
||||
custom_webhooks = Config.get("Notify", "CustomWebhooks")
|
||||
except AttributeError:
|
||||
custom_webhooks = []
|
||||
if custom_webhooks:
|
||||
for webhook in custom_webhooks:
|
||||
if webhook.get("enabled", True):
|
||||
try:
|
||||
await Notify.CustomWebhookPush(
|
||||
title,
|
||||
f"{message_text}\n\nAUTO-MAS 敬上",
|
||||
Config.get("Notify", "CompanyWebHookBotUrl"),
|
||||
webhook
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"自定义Webhook推送失败 ({webhook.get('name', 'Unknown')}): {e}")
|
||||
|
||||
# 发送用户单独通知
|
||||
if self.cur_user_data.get("Notify", "Enabled") and self.cur_user_data.get(
|
||||
@@ -2000,13 +2020,19 @@ class MaaManager:
|
||||
)
|
||||
|
||||
# 推送CompanyWebHookBot通知
|
||||
if self.cur_user_data.get("Notify", "IfCompanyWebHookBot"):
|
||||
if self.cur_user_data.get("Notify", "CompanyWebHookBotUrl"):
|
||||
await Notify.WebHookPush(
|
||||
# 发送用户自定义Webhook通知
|
||||
user_webhooks = self.cur_user_data.get("Notify", {}).get("CustomWebhooks", [])
|
||||
if user_webhooks:
|
||||
for webhook in user_webhooks:
|
||||
if webhook.get("enabled", True):
|
||||
try:
|
||||
await Notify.CustomWebhookPush(
|
||||
title,
|
||||
f"{message_text}\n\nAUTO-MAS 敬上",
|
||||
self.cur_user_data.get("Notify", "CompanyWebHookBotUrl"),
|
||||
webhook
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"用户自定义Webhook推送失败 ({webhook.get('name', 'Unknown')}): {e}")
|
||||
else:
|
||||
logger.error(
|
||||
"用户CompanyWebHookBot密钥为空, 无法发送用户单独的CompanyWebHookBot通知"
|
||||
@@ -2034,16 +2060,22 @@ class MaaManager:
|
||||
Config.get("Notify", "ServerChanKey"),
|
||||
)
|
||||
|
||||
if Config.get("Notify", "IfCompanyWebHookBot"):
|
||||
await Notify.WebHookPush(
|
||||
# 发送自定义Webhook通知(六星喜报)
|
||||
try:
|
||||
custom_webhooks = Config.get("Notify", "CustomWebhooks")
|
||||
except AttributeError:
|
||||
custom_webhooks = []
|
||||
if custom_webhooks:
|
||||
for webhook in custom_webhooks:
|
||||
if webhook.get("enabled", True):
|
||||
try:
|
||||
await Notify.CustomWebhookPush(
|
||||
title,
|
||||
"好羡慕~\n\nAUTO-MAS 敬上",
|
||||
Config.get("Notify", "CompanyWebHookBotUrl"),
|
||||
)
|
||||
await Notify.CompanyWebHookBotPushImage(
|
||||
Path.cwd() / "res/images/notification/six_star.png",
|
||||
Config.get("Notify", "CompanyWebHookBotUrl"),
|
||||
webhook
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"自定义Webhook推送失败 ({webhook.get('name', 'Unknown')}): {e}")
|
||||
|
||||
# 发送用户单独通知
|
||||
if self.cur_user_data.get("Notify", "Enabled") and self.cur_user_data.get(
|
||||
@@ -2077,17 +2109,19 @@ class MaaManager:
|
||||
)
|
||||
|
||||
# 推送CompanyWebHookBot通知
|
||||
if self.cur_user_data.get("Notify", "IfCompanyWebHookBot"):
|
||||
if self.cur_user_data.get("Notify", "CompanyWebHookBotUrl"):
|
||||
await Notify.WebHookPush(
|
||||
# 发送用户自定义Webhook通知(六星喜报)
|
||||
user_webhooks = self.cur_user_data.get("Notify", {}).get("CustomWebhooks", [])
|
||||
if user_webhooks:
|
||||
for webhook in user_webhooks:
|
||||
if webhook.get("enabled", True):
|
||||
try:
|
||||
await Notify.CustomWebhookPush(
|
||||
title,
|
||||
"好羡慕~\n\nAUTO-MAS 敬上",
|
||||
self.cur_user_data.get("Notify", "CompanyWebHookBotUrl"),
|
||||
)
|
||||
await Notify.CompanyWebHookBotPushImage(
|
||||
Path.cwd() / "res/images/notification/six_star.png",
|
||||
self.cur_user_data.get("Notify", "CompanyWebHookBotUrl"),
|
||||
webhook
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"用户自定义Webhook推送失败 ({webhook.get('name', 'Unknown')}): {e}")
|
||||
else:
|
||||
logger.error(
|
||||
"用户CompanyWebHookBot密钥为空, 无法发送用户单独的CompanyWebHookBot通知"
|
||||
|
||||
@@ -982,12 +982,22 @@ class GeneralManager:
|
||||
Config.get("Notify", "ServerChanKey"),
|
||||
)
|
||||
|
||||
if Config.get("Notify", "IfCompanyWebHookBot"):
|
||||
await Notify.WebHookPush(
|
||||
# 发送自定义Webhook通知
|
||||
try:
|
||||
custom_webhooks = Config.get("Notify", "CustomWebhooks")
|
||||
except AttributeError:
|
||||
custom_webhooks = []
|
||||
if custom_webhooks:
|
||||
for webhook in custom_webhooks:
|
||||
if webhook.get("enabled", True):
|
||||
try:
|
||||
await Notify.CustomWebhookPush(
|
||||
title,
|
||||
f"{message_text}\n\nAUTO-MAS 敬上",
|
||||
Config.get("Notify", "CompanyWebHookBotUrl"),
|
||||
webhook
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"自定义Webhook推送失败 ({webhook.get('name', 'Unknown')}): {e}")
|
||||
|
||||
elif mode == "统计信息":
|
||||
|
||||
@@ -1019,12 +1029,22 @@ class GeneralManager:
|
||||
Config.get("Notify", "ServerChanKey"),
|
||||
)
|
||||
|
||||
if Config.get("Notify", "IfCompanyWebHookBot"):
|
||||
await Notify.WebHookPush(
|
||||
# 发送自定义Webhook通知
|
||||
try:
|
||||
custom_webhooks = Config.get("Notify", "CustomWebhooks")
|
||||
except AttributeError:
|
||||
custom_webhooks = []
|
||||
if custom_webhooks:
|
||||
for webhook in custom_webhooks:
|
||||
if webhook.get("enabled", True):
|
||||
try:
|
||||
await Notify.CustomWebhookPush(
|
||||
title,
|
||||
f"{message_text}\n\nAUTO-MAS 敬上",
|
||||
Config.get("Notify", "CompanyWebHookBotUrl"),
|
||||
webhook
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"自定义Webhook推送失败 ({webhook.get('name', 'Unknown')}): {e}")
|
||||
|
||||
# 发送用户单独通知
|
||||
if self.cur_user_data.get("Notify", "Enabled") and self.cur_user_data.get(
|
||||
@@ -1057,13 +1077,19 @@ class GeneralManager:
|
||||
)
|
||||
|
||||
# 推送CompanyWebHookBot通知
|
||||
if self.cur_user_data.get("Notify", "IfCompanyWebHookBot"):
|
||||
if self.cur_user_data.get("Notify", "CompanyWebHookBotUrl"):
|
||||
await Notify.WebHookPush(
|
||||
# 发送用户自定义Webhook通知
|
||||
user_webhooks = self.cur_user_data.get("Notify", {}).get("CustomWebhooks", [])
|
||||
if user_webhooks:
|
||||
for webhook in user_webhooks:
|
||||
if webhook.get("enabled", True):
|
||||
try:
|
||||
await Notify.CustomWebhookPush(
|
||||
title,
|
||||
f"{message_text}\n\nAUTO-MAS 敬上",
|
||||
self.cur_user_data.get("Notify", "CompanyWebHookBotUrl"),
|
||||
webhook
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"用户自定义Webhook推送失败 ({webhook.get('name', 'Unknown')}): {e}")
|
||||
else:
|
||||
logger.error(
|
||||
"用户CompanyWebHookBot密钥为空, 无法发送用户单独的CompanyWebHookBot通知"
|
||||
|
||||
@@ -9,19 +9,22 @@ export type { OpenAPIConfig } from './core/OpenAPI';
|
||||
|
||||
export type { ComboBoxItem } from './models/ComboBoxItem';
|
||||
export type { ComboBoxOut } from './models/ComboBoxOut';
|
||||
export type { CustomWebhook } from './models/CustomWebhook';
|
||||
export type { DispatchIn } from './models/DispatchIn';
|
||||
export type { GeneralConfig } from './models/GeneralConfig';
|
||||
export type { GeneralConfig_Game } from './models/GeneralConfig_Game';
|
||||
export type { GeneralConfig_Info } from './models/GeneralConfig_Info';
|
||||
export type { GeneralConfig_Run } from './models/GeneralConfig_Run';
|
||||
export type { GeneralConfig_Script } from './models/GeneralConfig_Script';
|
||||
export type { GeneralUserConfig } from './models/GeneralUserConfig';
|
||||
export type { GeneralUserConfig_Data } from './models/GeneralUserConfig_Data';
|
||||
export type { GeneralUserConfig_Info } from './models/GeneralUserConfig_Info';
|
||||
export type { GeneralUserConfig_Input } from './models/GeneralUserConfig_Input';
|
||||
export type { GeneralUserConfig_Output } from './models/GeneralUserConfig_Output';
|
||||
export { GetStageIn } from './models/GetStageIn';
|
||||
export type { GlobalConfig } from './models/GlobalConfig';
|
||||
export type { GlobalConfig_Function } from './models/GlobalConfig_Function';
|
||||
export type { GlobalConfig_Input } from './models/GlobalConfig_Input';
|
||||
export type { GlobalConfig_Notify } from './models/GlobalConfig_Notify';
|
||||
export type { GlobalConfig_Output } from './models/GlobalConfig_Output';
|
||||
export type { GlobalConfig_Start } from './models/GlobalConfig_Start';
|
||||
export type { GlobalConfig_UI } from './models/GlobalConfig_UI';
|
||||
export type { GlobalConfig_Update } from './models/GlobalConfig_Update';
|
||||
@@ -40,9 +43,10 @@ export type { MaaConfig_Run } from './models/MaaConfig_Run';
|
||||
export type { MaaPlanConfig } from './models/MaaPlanConfig';
|
||||
export type { MaaPlanConfig_Info } from './models/MaaPlanConfig_Info';
|
||||
export type { MaaPlanConfig_Item } from './models/MaaPlanConfig_Item';
|
||||
export type { MaaUserConfig } from './models/MaaUserConfig';
|
||||
export type { MaaUserConfig_Data } from './models/MaaUserConfig_Data';
|
||||
export type { MaaUserConfig_Info } from './models/MaaUserConfig_Info';
|
||||
export type { MaaUserConfig_Input } from './models/MaaUserConfig_Input';
|
||||
export type { MaaUserConfig_Output } from './models/MaaUserConfig_Output';
|
||||
export type { MaaUserConfig_Task } from './models/MaaUserConfig_Task';
|
||||
export type { NoticeOut } from './models/NoticeOut';
|
||||
export type { OutBase } from './models/OutBase';
|
||||
|
||||
35
frontend/src/api/models/CustomWebhook.ts
Normal file
35
frontend/src/api/models/CustomWebhook.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export type CustomWebhook = {
|
||||
/**
|
||||
* Webhook唯一标识
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Webhook名称
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Webhook URL
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* 消息模板
|
||||
*/
|
||||
template: string;
|
||||
/**
|
||||
* 是否启用
|
||||
*/
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* 自定义请求头
|
||||
*/
|
||||
headers?: (Record<string, string> | null);
|
||||
/**
|
||||
* 请求方法
|
||||
*/
|
||||
method?: ('POST' | 'GET' | null);
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import type { GeneralUserConfig_Data } from './GeneralUserConfig_Data';
|
||||
import type { GeneralUserConfig_Info } from './GeneralUserConfig_Info';
|
||||
import type { UserConfig_Notify } from './UserConfig_Notify';
|
||||
export type GeneralUserConfig = {
|
||||
export type GeneralUserConfig_Input = {
|
||||
/**
|
||||
* 用户信息
|
||||
*/
|
||||
22
frontend/src/api/models/GeneralUserConfig_Output.ts
Normal file
22
frontend/src/api/models/GeneralUserConfig_Output.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { GeneralUserConfig_Data } from './GeneralUserConfig_Data';
|
||||
import type { GeneralUserConfig_Info } from './GeneralUserConfig_Info';
|
||||
import type { UserConfig_Notify } from './UserConfig_Notify';
|
||||
export type GeneralUserConfig_Output = {
|
||||
/**
|
||||
* 用户信息
|
||||
*/
|
||||
Info?: (GeneralUserConfig_Info | null);
|
||||
/**
|
||||
* 用户数据
|
||||
*/
|
||||
Data?: (GeneralUserConfig_Data | null);
|
||||
/**
|
||||
* 单独通知
|
||||
*/
|
||||
Notify?: (UserConfig_Notify | null);
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ import type { GlobalConfig_Start } from './GlobalConfig_Start';
|
||||
import type { GlobalConfig_UI } from './GlobalConfig_UI';
|
||||
import type { GlobalConfig_Update } from './GlobalConfig_Update';
|
||||
import type { GlobalConfig_Voice } from './GlobalConfig_Voice';
|
||||
export type GlobalConfig = {
|
||||
export type GlobalConfig_Input = {
|
||||
/**
|
||||
* 功能相关配置
|
||||
*/
|
||||
@@ -2,6 +2,7 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { CustomWebhook } from './CustomWebhook';
|
||||
export type GlobalConfig_Notify = {
|
||||
/**
|
||||
* 任务结果推送时机
|
||||
@@ -48,12 +49,8 @@ export type GlobalConfig_Notify = {
|
||||
*/
|
||||
ServerChanKey?: (string | null);
|
||||
/**
|
||||
* 是否使用企微Webhook推送
|
||||
* 自定义Webhook列表
|
||||
*/
|
||||
IfCompanyWebHookBot?: (boolean | null);
|
||||
/**
|
||||
* 企微Webhook Bot URL
|
||||
*/
|
||||
CompanyWebHookBotUrl?: (string | null);
|
||||
CustomWebhooks?: (Array<CustomWebhook> | null);
|
||||
};
|
||||
|
||||
|
||||
37
frontend/src/api/models/GlobalConfig_Output.ts
Normal file
37
frontend/src/api/models/GlobalConfig_Output.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { GlobalConfig_Function } from './GlobalConfig_Function';
|
||||
import type { GlobalConfig_Notify } from './GlobalConfig_Notify';
|
||||
import type { GlobalConfig_Start } from './GlobalConfig_Start';
|
||||
import type { GlobalConfig_UI } from './GlobalConfig_UI';
|
||||
import type { GlobalConfig_Update } from './GlobalConfig_Update';
|
||||
import type { GlobalConfig_Voice } from './GlobalConfig_Voice';
|
||||
export type GlobalConfig_Output = {
|
||||
/**
|
||||
* 功能相关配置
|
||||
*/
|
||||
Function?: (GlobalConfig_Function | null);
|
||||
/**
|
||||
* 语音相关配置
|
||||
*/
|
||||
Voice?: (GlobalConfig_Voice | null);
|
||||
/**
|
||||
* 启动相关配置
|
||||
*/
|
||||
Start?: (GlobalConfig_Start | null);
|
||||
/**
|
||||
* 界面相关配置
|
||||
*/
|
||||
UI?: (GlobalConfig_UI | null);
|
||||
/**
|
||||
* 通知相关配置
|
||||
*/
|
||||
Notify?: (GlobalConfig_Notify | null);
|
||||
/**
|
||||
* 更新相关配置
|
||||
*/
|
||||
Update?: (GlobalConfig_Update | null);
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { MaaUserConfig_Data } from './MaaUserConfig_Data';
|
||||
import type { MaaUserConfig_Info } from './MaaUserConfig_Info';
|
||||
import type { MaaUserConfig_Task } from './MaaUserConfig_Task';
|
||||
import type { UserConfig_Notify } from './UserConfig_Notify';
|
||||
export type MaaUserConfig = {
|
||||
export type MaaUserConfig_Input = {
|
||||
/**
|
||||
* 基础信息
|
||||
*/
|
||||
27
frontend/src/api/models/MaaUserConfig_Output.ts
Normal file
27
frontend/src/api/models/MaaUserConfig_Output.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { MaaUserConfig_Data } from './MaaUserConfig_Data';
|
||||
import type { MaaUserConfig_Info } from './MaaUserConfig_Info';
|
||||
import type { MaaUserConfig_Task } from './MaaUserConfig_Task';
|
||||
import type { UserConfig_Notify } from './UserConfig_Notify';
|
||||
export type MaaUserConfig_Output = {
|
||||
/**
|
||||
* 基础信息
|
||||
*/
|
||||
Info?: (MaaUserConfig_Info | null);
|
||||
/**
|
||||
* 用户数据
|
||||
*/
|
||||
Data?: (MaaUserConfig_Data | null);
|
||||
/**
|
||||
* 任务列表
|
||||
*/
|
||||
Task?: (MaaUserConfig_Task | null);
|
||||
/**
|
||||
* 单独通知
|
||||
*/
|
||||
Notify?: (UserConfig_Notify | null);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { GlobalConfig } from './GlobalConfig';
|
||||
import type { GlobalConfig_Output } from './GlobalConfig_Output';
|
||||
export type SettingGetOut = {
|
||||
/**
|
||||
* 状态码
|
||||
@@ -19,6 +19,6 @@ export type SettingGetOut = {
|
||||
/**
|
||||
* 全局设置数据
|
||||
*/
|
||||
data: GlobalConfig;
|
||||
data: GlobalConfig_Output;
|
||||
};
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { GlobalConfig } from './GlobalConfig';
|
||||
import type { GlobalConfig_Input } from './GlobalConfig_Input';
|
||||
export type SettingUpdateIn = {
|
||||
/**
|
||||
* 全局设置需要更新的数据
|
||||
*/
|
||||
data: GlobalConfig;
|
||||
data: GlobalConfig_Input;
|
||||
};
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { CustomWebhook } from './CustomWebhook';
|
||||
export type UserConfig_Notify = {
|
||||
/**
|
||||
* 是否启用通知
|
||||
@@ -32,12 +33,8 @@ export type UserConfig_Notify = {
|
||||
*/
|
||||
ServerChanKey?: (string | null);
|
||||
/**
|
||||
* 是否使用Webhook推送
|
||||
* 用户自定义Webhook列表
|
||||
*/
|
||||
IfCompanyWebHookBot?: (boolean | null);
|
||||
/**
|
||||
* 企微Webhook Bot URL
|
||||
*/
|
||||
CompanyWebHookBotUrl?: (string | null);
|
||||
CustomWebhooks?: (Array<CustomWebhook> | null);
|
||||
};
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { GeneralUserConfig } from './GeneralUserConfig';
|
||||
import type { MaaUserConfig } from './MaaUserConfig';
|
||||
import type { GeneralUserConfig_Output } from './GeneralUserConfig_Output';
|
||||
import type { MaaUserConfig_Output } from './MaaUserConfig_Output';
|
||||
export type UserCreateOut = {
|
||||
/**
|
||||
* 状态码
|
||||
@@ -24,6 +24,6 @@ export type UserCreateOut = {
|
||||
/**
|
||||
* 用户配置数据
|
||||
*/
|
||||
data: (MaaUserConfig | GeneralUserConfig);
|
||||
data: (MaaUserConfig_Output | GeneralUserConfig_Output);
|
||||
};
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { GeneralUserConfig } from './GeneralUserConfig';
|
||||
import type { MaaUserConfig } from './MaaUserConfig';
|
||||
import type { GeneralUserConfig_Output } from './GeneralUserConfig_Output';
|
||||
import type { MaaUserConfig_Output } from './MaaUserConfig_Output';
|
||||
import type { UserIndexItem } from './UserIndexItem';
|
||||
export type UserGetOut = {
|
||||
/**
|
||||
@@ -25,6 +25,6 @@ export type UserGetOut = {
|
||||
/**
|
||||
* 用户数据字典, key来自于index列表的uid
|
||||
*/
|
||||
data: Record<string, (MaaUserConfig | GeneralUserConfig)>;
|
||||
data: Record<string, (MaaUserConfig_Output | GeneralUserConfig_Output)>;
|
||||
};
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { GeneralUserConfig } from './GeneralUserConfig';
|
||||
import type { MaaUserConfig } from './MaaUserConfig';
|
||||
import type { GeneralUserConfig_Input } from './GeneralUserConfig_Input';
|
||||
import type { MaaUserConfig_Input } from './MaaUserConfig_Input';
|
||||
export type UserUpdateIn = {
|
||||
/**
|
||||
* 所属脚本ID
|
||||
@@ -16,6 +16,6 @@ export type UserUpdateIn = {
|
||||
/**
|
||||
* 用户更新数据
|
||||
*/
|
||||
data: (MaaUserConfig | GeneralUserConfig);
|
||||
data: (MaaUserConfig_Input | GeneralUserConfig_Input);
|
||||
};
|
||||
|
||||
|
||||
@@ -994,6 +994,86 @@ export class Service {
|
||||
url: '/api/setting/test_notify',
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 创建自定义Webhook
|
||||
* 创建自定义Webhook
|
||||
* @param requestBody
|
||||
* @returns OutBase Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static createWebhookApiSettingWebhookCreatePost(
|
||||
requestBody: Record<string, any>,
|
||||
): CancelablePromise<OutBase> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'POST',
|
||||
url: '/api/setting/webhook/create',
|
||||
body: requestBody,
|
||||
mediaType: 'application/json',
|
||||
errors: {
|
||||
422: `Validation Error`,
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 更新自定义Webhook
|
||||
* 更新自定义Webhook
|
||||
* @param requestBody
|
||||
* @returns OutBase Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static updateWebhookApiSettingWebhookUpdatePost(
|
||||
requestBody: Record<string, any>,
|
||||
): CancelablePromise<OutBase> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'POST',
|
||||
url: '/api/setting/webhook/update',
|
||||
body: requestBody,
|
||||
mediaType: 'application/json',
|
||||
errors: {
|
||||
422: `Validation Error`,
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 删除自定义Webhook
|
||||
* 删除自定义Webhook
|
||||
* @param requestBody
|
||||
* @returns OutBase Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static deleteWebhookApiSettingWebhookDeletePost(
|
||||
requestBody: Record<string, any>,
|
||||
): CancelablePromise<OutBase> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'POST',
|
||||
url: '/api/setting/webhook/delete',
|
||||
body: requestBody,
|
||||
mediaType: 'application/json',
|
||||
errors: {
|
||||
422: `Validation Error`,
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 测试自定义Webhook
|
||||
* 测试自定义Webhook
|
||||
* @param requestBody
|
||||
* @returns OutBase Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static testWebhookApiSettingWebhookTestPost(
|
||||
requestBody: Record<string, any>,
|
||||
): CancelablePromise<OutBase> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'POST',
|
||||
url: '/api/setting/webhook/test',
|
||||
body: requestBody,
|
||||
mediaType: 'application/json',
|
||||
errors: {
|
||||
422: `Validation Error`,
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 检查更新
|
||||
* @param requestBody
|
||||
|
||||
553
frontend/src/components/WebhookManager.vue
Normal file
553
frontend/src/components/WebhookManager.vue
Normal file
@@ -0,0 +1,553 @@
|
||||
<template>
|
||||
<div class="webhook-manager">
|
||||
<div class="webhook-header">
|
||||
<h3>自定义 Webhook 通知</h3>
|
||||
<a-button type="primary" @click="showAddModal" size="middle">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
添加 Webhook
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<!-- Webhook 列表 -->
|
||||
<div class="webhook-list" v-if="webhooks.length > 0">
|
||||
<div
|
||||
v-for="webhook in webhooks"
|
||||
:key="webhook.id"
|
||||
class="webhook-item"
|
||||
:class="{ 'webhook-disabled': !webhook.enabled }"
|
||||
>
|
||||
<div class="webhook-info">
|
||||
<div class="webhook-name">
|
||||
<span class="name-text">{{ webhook.name }}</span>
|
||||
<a-tag :color="webhook.enabled ? 'green' : 'red'" size="small">
|
||||
{{ webhook.enabled ? '启用' : '禁用' }}
|
||||
</a-tag>
|
||||
</div>
|
||||
<div class="webhook-url">{{ webhook.url }}</div>
|
||||
</div>
|
||||
<div class="webhook-actions">
|
||||
<a-switch
|
||||
v-model:checked="webhook.enabled"
|
||||
@change="toggleWebhookEnabled(webhook)"
|
||||
size="small"
|
||||
:checked-children="'启用'"
|
||||
:un-checked-children="'禁用'"
|
||||
class="webhook-switch"
|
||||
/>
|
||||
<a-button type="text" size="small" @click="testWebhook(webhook)" :loading="testingWebhooks[webhook.id]">
|
||||
<template #icon>
|
||||
<PlayCircleOutlined />
|
||||
</template>
|
||||
测试
|
||||
</a-button>
|
||||
<a-button type="text" size="small" @click="editWebhook(webhook)">
|
||||
<template #icon>
|
||||
<EditOutlined />
|
||||
</template>
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button type="text" size="small" danger @click="deleteWebhook(webhook)">
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
删除
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="empty-state">
|
||||
<div class="empty-icon">
|
||||
<ApiOutlined />
|
||||
</div>
|
||||
<div class="empty-text">暂无自定义 Webhook</div>
|
||||
<div class="empty-description">点击上方按钮添加您的第一个 Webhook</div>
|
||||
</div>
|
||||
|
||||
<!-- 添加/编辑 Webhook 弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="modalVisible"
|
||||
:title="isEditing ? '编辑 Webhook' : '添加 Webhook'"
|
||||
width="800px"
|
||||
:ok-text="isEditing ? '更新' : '添加'"
|
||||
@ok="handleSubmit"
|
||||
@cancel="handleCancel"
|
||||
:confirm-loading="submitting"
|
||||
>
|
||||
<a-form :model="formData" layout="vertical" ref="formRef">
|
||||
<!-- 模板选择放在最上面 -->
|
||||
<a-form-item label="选择模板">
|
||||
<a-select
|
||||
v-model:value="selectedTemplate"
|
||||
placeholder="选择预设模板或自定义"
|
||||
@change="applyTemplate"
|
||||
allow-clear
|
||||
>
|
||||
<a-select-option v-for="template in WEBHOOK_TEMPLATES" :key="template.name" :value="template.name">
|
||||
{{ template.name }} - {{ template.description }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="Webhook 名称" name="name" :rules="[{ required: true, message: '请输入 Webhook 名称' }]">
|
||||
<a-input v-model:value="formData.name" placeholder="请输入 Webhook 名称" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="请求方法" name="method">
|
||||
<a-select v-model:value="formData.method">
|
||||
<a-select-option value="POST">POST</a-select-option>
|
||||
<a-select-option value="GET">GET</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="Webhook URL" name="url" :rules="[{ required: true, message: '请输入 Webhook URL' }]">
|
||||
<a-input v-model:value="formData.url" placeholder="https://your-webhook-url.com/api/notify" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="消息模板">
|
||||
<a-textarea
|
||||
v-model:value="formData.template"
|
||||
:rows="6"
|
||||
placeholder="请输入消息模板,支持变量: {title}, {content}, {datetime}, {date}, {time}"
|
||||
/>
|
||||
<div class="template-help">
|
||||
<a-typography-text type="secondary" style="font-size: 12px;">
|
||||
支持的变量:
|
||||
<a-tag size="small" v-for="variable in TEMPLATE_VARIABLES" :key="variable.name">
|
||||
{{ variable.name }}
|
||||
</a-tag>
|
||||
</a-typography-text>
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="自定义请求头 (可选)">
|
||||
<div class="headers-input">
|
||||
<div v-for="(header, index) in formData.headersList" :key="index" class="header-row">
|
||||
<a-input
|
||||
v-model:value="header.key"
|
||||
placeholder="Header 名称"
|
||||
style="width: 40%; margin-right: 8px;"
|
||||
/>
|
||||
<a-input
|
||||
v-model:value="header.value"
|
||||
placeholder="Header 值"
|
||||
style="width: 40%; margin-right: 8px;"
|
||||
/>
|
||||
<a-button type="text" danger @click="removeHeader(index)" size="small">
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</div>
|
||||
<a-button type="dashed" @click="addHeader" size="small" style="width: 100%; margin-top: 8px;">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
添加请求头
|
||||
</a-button>
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-checkbox v-model:checked="formData.enabled">启用此 Webhook</a-checkbox>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import {
|
||||
PlusOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
PlayCircleOutlined,
|
||||
ApiOutlined,
|
||||
CheckCircleOutlined,
|
||||
StopOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import type { CustomWebhook } from '@/types/settings'
|
||||
import { WEBHOOK_TEMPLATES, TEMPLATE_VARIABLES } from '@/utils/webhookTemplates'
|
||||
import { Service } from '@/api/services/Service'
|
||||
|
||||
const props = defineProps<{
|
||||
webhooks: CustomWebhook[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:webhooks': [webhooks: CustomWebhook[]]
|
||||
'change': []
|
||||
}>()
|
||||
|
||||
// 响应式数据
|
||||
const modalVisible = ref(false)
|
||||
const isEditing = ref(false)
|
||||
const submitting = ref(false)
|
||||
const selectedTemplate = ref<string>()
|
||||
const testingWebhooks = ref<Record<string, boolean>>({})
|
||||
const formRef = ref()
|
||||
|
||||
// 表单数据
|
||||
const formData = reactive({
|
||||
id: '',
|
||||
name: '',
|
||||
url: '',
|
||||
template: '',
|
||||
method: 'POST' as 'POST' | 'GET',
|
||||
enabled: true,
|
||||
headersList: [] as Array<{ key: string; value: string }>
|
||||
})
|
||||
|
||||
// 计算属性
|
||||
const webhooks = computed({
|
||||
get: () => props.webhooks || [],
|
||||
set: (value) => emit('update:webhooks', value)
|
||||
})
|
||||
|
||||
// 监听 webhooks 变化
|
||||
watch(() => props.webhooks, (newWebhooks) => {
|
||||
// 可以在这里处理 webhooks 变化的逻辑
|
||||
}, { deep: true })
|
||||
|
||||
// 显示添加弹窗
|
||||
const showAddModal = () => {
|
||||
isEditing.value = false
|
||||
resetForm()
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
// 编辑 Webhook
|
||||
const editWebhook = (webhook: CustomWebhook) => {
|
||||
isEditing.value = true
|
||||
formData.id = webhook.id
|
||||
formData.name = webhook.name
|
||||
formData.url = webhook.url
|
||||
formData.template = webhook.template
|
||||
formData.method = webhook.method || 'POST'
|
||||
formData.enabled = webhook.enabled
|
||||
|
||||
// 转换 headers 为列表格式
|
||||
formData.headersList = webhook.headers
|
||||
? Object.entries(webhook.headers).map(([key, value]) => ({ key, value }))
|
||||
: []
|
||||
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
// 切换 Webhook 启用状态
|
||||
const toggleWebhookEnabled = (webhook: CustomWebhook) => {
|
||||
// 由于 a-switch 已经改变了 webhook.enabled 的值,我们需要根据新值来更新
|
||||
const newEnabled = webhook.enabled
|
||||
const newWebhooks = webhooks.value.map(w =>
|
||||
w.id === webhook.id
|
||||
? { ...w, enabled: newEnabled }
|
||||
: w
|
||||
)
|
||||
webhooks.value = newWebhooks
|
||||
emit('change')
|
||||
message.success(`Webhook "${webhook.name}" 已${newEnabled ? '启用' : '禁用'}`)
|
||||
}
|
||||
|
||||
// 删除 Webhook
|
||||
const deleteWebhook = (webhook: CustomWebhook) => {
|
||||
const newWebhooks = webhooks.value.filter(w => w.id !== webhook.id)
|
||||
webhooks.value = newWebhooks
|
||||
emit('change')
|
||||
message.success('Webhook 删除成功')
|
||||
}
|
||||
|
||||
// 测试 Webhook
|
||||
const testWebhook = async (webhook: CustomWebhook) => {
|
||||
testingWebhooks.value[webhook.id] = true
|
||||
|
||||
try {
|
||||
const response = await Service.testWebhookApiSettingWebhookTestPost({
|
||||
id: webhook.id,
|
||||
name: webhook.name,
|
||||
url: webhook.url,
|
||||
template: webhook.template,
|
||||
method: webhook.method || 'POST',
|
||||
enabled: webhook.enabled,
|
||||
headers: webhook.headers || {}
|
||||
})
|
||||
|
||||
if (response.code === 200) {
|
||||
message.success(`Webhook "${webhook.name}" 测试成功`)
|
||||
} else {
|
||||
message.error(`Webhook 测试失败: ${response.message || '未知错误'}`)
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Webhook测试错误:', error)
|
||||
message.error(`Webhook 测试失败: ${error.response?.data?.message || error.message || '网络错误'}`)
|
||||
} finally {
|
||||
testingWebhooks.value[webhook.id] = false
|
||||
}
|
||||
}
|
||||
|
||||
// 应用模板
|
||||
const applyTemplate = (templateName: string) => {
|
||||
if (!templateName) {
|
||||
// 清空模板时不做任何操作
|
||||
return
|
||||
}
|
||||
|
||||
const template = WEBHOOK_TEMPLATES.find(t => t.name === templateName)
|
||||
if (template) {
|
||||
// 强制清空所有内容再应用新模板
|
||||
formData.name = ''
|
||||
formData.url = template.example || ''
|
||||
formData.template = template.template
|
||||
formData.method = template.method
|
||||
formData.headersList = []
|
||||
|
||||
// 设置默认请求头
|
||||
if (template.headers) {
|
||||
formData.headersList = Object.entries(template.headers).map(([key, value]) => ({ key, value }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加请求头
|
||||
const addHeader = () => {
|
||||
formData.headersList.push({ key: '', value: '' })
|
||||
}
|
||||
|
||||
// 删除请求头
|
||||
const removeHeader = (index: number) => {
|
||||
formData.headersList.splice(index, 1)
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
formData.id = ''
|
||||
formData.name = ''
|
||||
formData.url = ''
|
||||
formData.template = ''
|
||||
formData.method = 'POST'
|
||||
formData.enabled = true
|
||||
formData.headersList = []
|
||||
selectedTemplate.value = undefined
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
submitting.value = true
|
||||
|
||||
// 转换 headersList 为 headers 对象
|
||||
const headers: Record<string, string> = {}
|
||||
formData.headersList.forEach(header => {
|
||||
if (header.key && header.value) {
|
||||
headers[header.key] = header.value
|
||||
}
|
||||
})
|
||||
|
||||
const webhookData: CustomWebhook = {
|
||||
id: formData.id || `webhook_${Date.now()}`,
|
||||
name: formData.name,
|
||||
url: formData.url,
|
||||
template: formData.template,
|
||||
method: formData.method,
|
||||
enabled: formData.enabled,
|
||||
headers: Object.keys(headers).length > 0 ? headers : undefined
|
||||
}
|
||||
|
||||
let newWebhooks: CustomWebhook[]
|
||||
|
||||
if (isEditing.value) {
|
||||
// 更新现有 Webhook
|
||||
newWebhooks = webhooks.value.map(w =>
|
||||
w.id === webhookData.id ? webhookData : w
|
||||
)
|
||||
message.success('Webhook 更新成功')
|
||||
} else {
|
||||
// 添加新 Webhook
|
||||
newWebhooks = [...webhooks.value, webhookData]
|
||||
message.success('Webhook 添加成功')
|
||||
}
|
||||
|
||||
webhooks.value = newWebhooks
|
||||
emit('change')
|
||||
modalVisible.value = false
|
||||
|
||||
} catch (error) {
|
||||
console.error('表单验证失败:', error)
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 取消操作
|
||||
const handleCancel = () => {
|
||||
modalVisible.value = false
|
||||
resetForm()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.webhook-manager {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.webhook-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.webhook-header h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.webhook-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.webhook-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
border: 1px solid var(--ant-color-border);
|
||||
border-radius: 8px;
|
||||
background: var(--ant-color-bg-container);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.webhook-item:hover {
|
||||
border-color: var(--ant-color-primary);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.webhook-item.webhook-disabled {
|
||||
opacity: 0.6;
|
||||
background: var(--ant-color-bg-layout);
|
||||
}
|
||||
|
||||
.webhook-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.webhook-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.name-text {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.webhook-url {
|
||||
font-size: 12px;
|
||||
color: var(--ant-color-text-secondary);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.webhook-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.webhook-switch {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 48px 24px;
|
||||
color: var(--ant-color-text-secondary);
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.empty-description {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.template-help {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.headers-input {
|
||||
border: 1px solid var(--ant-color-border);
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
background: var(--ant-color-bg-layout);
|
||||
}
|
||||
|
||||
.header-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.header-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* 深色模式适配 */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.webhook-item {
|
||||
border-color: var(--ant-color-border-secondary);
|
||||
}
|
||||
|
||||
.webhook-item.webhook-disabled {
|
||||
background: var(--ant-color-bg-base);
|
||||
}
|
||||
|
||||
.headers-input {
|
||||
background: var(--ant-color-bg-base);
|
||||
border-color: var(--ant-color-border-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.webhook-item {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.webhook-actions {
|
||||
width: 100%;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.webhook-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -154,8 +154,7 @@ export function useScriptApi() {
|
||||
ToAddress: maaUserData.Notify?.ToAddress !== undefined ? maaUserData.Notify.ToAddress : '',
|
||||
IfServerChan: maaUserData.Notify?.IfServerChan !== undefined ? maaUserData.Notify.IfServerChan : false,
|
||||
ServerChanKey: maaUserData.Notify?.ServerChanKey !== undefined ? maaUserData.Notify.ServerChanKey : '',
|
||||
IfCompanyWebHookBot: maaUserData.Notify?.IfCompanyWebHookBot !== undefined ? maaUserData.Notify.IfCompanyWebHookBot : false,
|
||||
CompanyWebHookBotUrl: maaUserData.Notify?.CompanyWebHookBotUrl !== undefined ? maaUserData.Notify.CompanyWebHookBotUrl : '',
|
||||
CustomWebhooks: maaUserData.Notify?.CustomWebhooks !== undefined ? maaUserData.Notify.CustomWebhooks : [],
|
||||
},
|
||||
Data: {
|
||||
LastAnnihilationDate: maaUserData.Data?.LastAnnihilationDate !== undefined ? maaUserData.Data.LastAnnihilationDate : '',
|
||||
@@ -188,8 +187,7 @@ export function useScriptApi() {
|
||||
ToAddress: generalUserData.Notify?.ToAddress !== undefined ? generalUserData.Notify.ToAddress : '',
|
||||
IfServerChan: generalUserData.Notify?.IfServerChan !== undefined ? generalUserData.Notify.IfServerChan : false,
|
||||
ServerChanKey: generalUserData.Notify?.ServerChanKey !== undefined ? generalUserData.Notify.ServerChanKey : '',
|
||||
IfCompanyWebHookBot: generalUserData.Notify?.IfCompanyWebHookBot !== undefined ? generalUserData.Notify.IfCompanyWebHookBot : false,
|
||||
CompanyWebHookBotUrl: generalUserData.Notify?.CompanyWebHookBotUrl !== undefined ? generalUserData.Notify.CompanyWebHookBotUrl : '',
|
||||
CustomWebhooks: generalUserData.Notify?.CustomWebhooks !== undefined ? generalUserData.Notify.CustomWebhooks : [],
|
||||
},
|
||||
Data: {
|
||||
LastProxyDate: generalUserData.Data?.LastProxyDate !== undefined ? generalUserData.Data.LastProxyDate : '',
|
||||
|
||||
@@ -109,11 +109,18 @@ export interface User {
|
||||
Status: boolean
|
||||
}
|
||||
Notify: {
|
||||
CompanyWebHookBotUrl: string
|
||||
Enabled: boolean
|
||||
IfCompanyWebHookBot: boolean
|
||||
IfSendMail: boolean
|
||||
IfSendSixStar: boolean
|
||||
CustomWebhooks: Array<{
|
||||
id: string
|
||||
name: string
|
||||
url: string
|
||||
template: string
|
||||
enabled: boolean
|
||||
headers?: Record<string, string>
|
||||
method?: 'POST' | 'GET'
|
||||
}>
|
||||
IfSendStatistic: boolean
|
||||
IfServerChan: boolean
|
||||
ServerChanChannel: string
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
// 自定义Webhook配置
|
||||
export interface CustomWebhook {
|
||||
id: string
|
||||
name: string
|
||||
url: string
|
||||
template: string
|
||||
enabled: boolean
|
||||
headers?: Record<string, string>
|
||||
method?: 'POST' | 'GET'
|
||||
}
|
||||
|
||||
// 设置相关类型定义
|
||||
export interface SettingsData {
|
||||
UI: {
|
||||
@@ -26,8 +37,7 @@ export interface SettingsData {
|
||||
ServerChanKey: string
|
||||
ServerChanChannel: string
|
||||
ServerChanTag: string
|
||||
IfCompanyWebHookBot: boolean
|
||||
CompanyWebHookBotUrl: string
|
||||
CustomWebhooks: CustomWebhook[]
|
||||
}
|
||||
Update: {
|
||||
IfAutoUpdate: boolean
|
||||
|
||||
119
frontend/src/utils/webhookTemplates.ts
Normal file
119
frontend/src/utils/webhookTemplates.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
// Webhook 模板配置
|
||||
export interface WebhookTemplate {
|
||||
name: string
|
||||
description: string
|
||||
template: string
|
||||
headers?: Record<string, string>
|
||||
method: 'POST' | 'GET'
|
||||
example?: string
|
||||
}
|
||||
|
||||
export const WEBHOOK_TEMPLATES: WebhookTemplate[] = [
|
||||
{
|
||||
name: 'Bark (iOS推送)',
|
||||
description: 'Bark是一款iOS推送通知应用',
|
||||
template: '{"title": "{title}", "body": "{content}", "sound": "default"}',
|
||||
method: 'POST',
|
||||
example: 'https://api.day.app/your_key/',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Server酱 (微信推送)',
|
||||
description: 'Server酱微信推送服务',
|
||||
template: '{"title": "{title}", "desp": "{content}"}',
|
||||
method: 'POST',
|
||||
example: 'https://sctapi.ftqq.com/your_key.send',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '企业微信机器人',
|
||||
description: '企业微信群机器人推送',
|
||||
template: '{"msgtype": "text", "text": {"content": "{title}\\n{content}"}}',
|
||||
method: 'POST',
|
||||
example: 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=your_key',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'DingTalk (钉钉机器人)',
|
||||
description: '钉钉群机器人推送',
|
||||
template: '{"msgtype": "text", "text": {"content": "{title}\\n{content}"}}',
|
||||
method: 'POST',
|
||||
example: 'https://oapi.dingtalk.com/robot/send?access_token=your_token',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Telegram Bot',
|
||||
description: 'Telegram机器人推送',
|
||||
template: '{"chat_id": "your_chat_id", "text": "{title}\\n{content}"}',
|
||||
method: 'POST',
|
||||
example: 'https://api.telegram.org/bot{your_bot_token}/sendMessage',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Discord Webhook',
|
||||
description: 'Discord频道Webhook推送',
|
||||
template: '{"content": "**{title}**\\n{content}"}',
|
||||
method: 'POST',
|
||||
example: 'https://discord.com/api/webhooks/your_webhook_url',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Slack Webhook',
|
||||
description: 'Slack频道Webhook推送',
|
||||
template: '{"text": "{title}\\n{content}"}',
|
||||
method: 'POST',
|
||||
example: 'https://hooks.slack.com/services/your/webhook/url',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'PushPlus (微信推送)',
|
||||
description: 'PushPlus微信推送服务',
|
||||
template: '{"token": "your_token", "title": "{title}", "content": "{content}"}',
|
||||
method: 'POST',
|
||||
example: 'http://www.pushplus.plus/send',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '自定义JSON',
|
||||
description: '自定义JSON格式推送',
|
||||
template: '{"message": "{title}: {content}", "timestamp": "{datetime}"}',
|
||||
method: 'POST',
|
||||
example: 'https://your-api.com/webhook',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '自定义GET请求',
|
||||
description: '通过GET请求发送通知',
|
||||
template: 'title={title}&content={content}&time={datetime}',
|
||||
method: 'GET',
|
||||
example: 'https://your-api.com/notify',
|
||||
headers: {}
|
||||
}
|
||||
]
|
||||
|
||||
// 获取模板变量说明
|
||||
export const TEMPLATE_VARIABLES = [
|
||||
{ name: '{title}', description: '通知标题' },
|
||||
{ name: '{content}', description: '通知内容' },
|
||||
{ name: '{datetime}', description: '完整日期时间 (YYYY-MM-DD HH:MM:SS)' },
|
||||
{ name: '{date}', description: '日期 (YYYY-MM-DD)' },
|
||||
{ name: '{time}', description: '时间 (HH:MM:SS)' }
|
||||
]
|
||||
@@ -326,28 +326,13 @@
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 企业微信群机器人通知 -->
|
||||
<a-row :gutter="24" style="margin-top: 16px">
|
||||
<a-col :span="6">
|
||||
<a-checkbox
|
||||
v-model:checked="formData.Notify.IfCompanyWebHookBot"
|
||||
:disabled="loading || !formData.Notify.Enabled"
|
||||
>企业微信群机器人
|
||||
</a-checkbox>
|
||||
</a-col>
|
||||
<a-col :span="18">
|
||||
<a-input
|
||||
v-model:value="formData.Notify.CompanyWebHookBotUrl"
|
||||
placeholder="请输入机器人Webhook地址"
|
||||
:disabled="
|
||||
loading || !formData.Notify.Enabled || !formData.Notify.IfCompanyWebHookBot
|
||||
"
|
||||
size="large"
|
||||
style="width: 100%"
|
||||
class="modern-input"
|
||||
<!-- 自定义 Webhook 通知 -->
|
||||
<div style="margin-top: 16px">
|
||||
<WebhookManager
|
||||
v-model:webhooks="formData.Notify.CustomWebhooks"
|
||||
@change="handleWebhookChange"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div>
|
||||
</a-form>
|
||||
</a-card>
|
||||
@@ -384,6 +369,7 @@ import { useScriptApi } from '@/composables/useScriptApi'
|
||||
import { useWebSocket } from '@/composables/useWebSocket'
|
||||
import { Service } from '@/api'
|
||||
import { TaskCreateIn } from '@/api/models/TaskCreateIn'
|
||||
import WebhookManager from '@/components/WebhookManager.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
@@ -426,11 +412,10 @@ const getDefaultGeneralUserData = () => ({
|
||||
IfSendMail: false,
|
||||
IfSendStatistic: false,
|
||||
IfServerChan: false,
|
||||
IfCompanyWebHookBot: false,
|
||||
ServerChanKey: '',
|
||||
ServerChanChannel: '',
|
||||
ServerChanTag: '',
|
||||
CompanyWebHookBotUrl: '',
|
||||
CustomWebhooks: [],
|
||||
},
|
||||
Data: {
|
||||
LastProxyDate: '2000-01-01',
|
||||
@@ -639,7 +624,8 @@ const handleGeneralConfig = async () => {
|
||||
message.success(`已开始配置用户 ${formData.userName} 的通用设置`)
|
||||
|
||||
// 设置 30 分钟超时自动断开
|
||||
generalConfigTimeout = window.setTimeout(() => {
|
||||
generalConfigTimeout = window.setTimeout(
|
||||
() => {
|
||||
if (generalWebsocketId.value) {
|
||||
const id = generalWebsocketId.value
|
||||
unsubscribe(id)
|
||||
@@ -648,7 +634,9 @@ const handleGeneralConfig = async () => {
|
||||
message.info(`用户 ${formData.userName} 的配置会话已超时断开`)
|
||||
}
|
||||
generalConfigTimeout = null
|
||||
}, 30 * 60 * 1000)
|
||||
},
|
||||
30 * 60 * 1000
|
||||
)
|
||||
} else {
|
||||
message.error(response?.message || '启动通用配置失败')
|
||||
}
|
||||
@@ -724,6 +712,13 @@ const selectScriptAfterTask = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 处理 Webhook 变化
|
||||
const handleWebhookChange = () => {
|
||||
// 这里可以添加额外的处理逻辑,比如验证或保存
|
||||
console.log('User webhooks changed:', formData.Notify.CustomWebhooks)
|
||||
// 注意:实际保存会在用户点击保存按钮时进行,这里只是更新本地数据
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
if (generalWebsocketId.value) {
|
||||
unsubscribe(generalWebsocketId.value)
|
||||
|
||||
@@ -41,7 +41,13 @@
|
||||
|
||||
<div class="user-edit-content">
|
||||
<a-card class="config-card">
|
||||
<a-form ref="formRef" :model="formData" :rules="rules" layout="vertical" class="config-form">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
layout="vertical"
|
||||
class="config-form"
|
||||
>
|
||||
<!-- 基本信息组件 -->
|
||||
<BasicInfoSection
|
||||
:form-data="formData"
|
||||
@@ -91,22 +97,13 @@
|
||||
/>
|
||||
|
||||
<!-- 任务配置组件 -->
|
||||
<TaskConfigSection
|
||||
:form-data="formData"
|
||||
:loading="loading"
|
||||
/>
|
||||
<TaskConfigSection :form-data="formData" :loading="loading" />
|
||||
|
||||
<!-- 森空岛配置组件 -->
|
||||
<SkylandConfigSection
|
||||
:form-data="formData"
|
||||
:loading="loading"
|
||||
/>
|
||||
<SkylandConfigSection :form-data="formData" :loading="loading" />
|
||||
|
||||
<!-- 通知配置组件 -->
|
||||
<NotifyConfigSection
|
||||
:form-data="formData"
|
||||
:loading="loading"
|
||||
/>
|
||||
<NotifyConfigSection :form-data="formData" :loading="loading" />
|
||||
</a-form>
|
||||
</a-card>
|
||||
</div>
|
||||
@@ -459,11 +456,10 @@ const getDefaultMAAUserData = () => ({
|
||||
IfSendSixStar: false,
|
||||
IfSendStatistic: false,
|
||||
IfServerChan: false,
|
||||
IfCompanyWebHookBot: false,
|
||||
ServerChanKey: '',
|
||||
ServerChanChannel: '',
|
||||
ServerChanTag: '',
|
||||
CompanyWebHookBotUrl: '',
|
||||
CustomWebhooks: [],
|
||||
},
|
||||
Data: {
|
||||
CustomInfrastPlanIndex: '',
|
||||
@@ -790,7 +786,10 @@ const handleMAAConfig = async () => {
|
||||
subscribe(wsId, {
|
||||
onMessage: (wsMessage: any) => {
|
||||
if (wsMessage.type === 'error') {
|
||||
console.error(`用户 ${formData.Info?.Name || formData.userName} MAA配置错误:`, wsMessage.data)
|
||||
console.error(
|
||||
`用户 ${formData.Info?.Name || formData.userName} MAA配置错误:`,
|
||||
wsMessage.data
|
||||
)
|
||||
message.error(`MAA配置连接失败: ${wsMessage.data}`)
|
||||
unsubscribe(wsId)
|
||||
maaWebsocketId.value = null
|
||||
@@ -812,7 +811,8 @@ const handleMAAConfig = async () => {
|
||||
message.success(`已开始配置用户 ${formData.Info?.Name || formData.userName} 的MAA设置`)
|
||||
|
||||
// 设置 30 分钟超时自动断开
|
||||
maaConfigTimeout = window.setTimeout(() => {
|
||||
maaConfigTimeout = window.setTimeout(
|
||||
() => {
|
||||
if (maaWebsocketId.value) {
|
||||
const id = maaWebsocketId.value
|
||||
unsubscribe(id)
|
||||
@@ -821,7 +821,9 @@ const handleMAAConfig = async () => {
|
||||
message.info(`用户 ${formData.Info?.Name || formData.userName} 的配置会话已超时断开`)
|
||||
}
|
||||
maaConfigTimeout = null
|
||||
}, 30 * 60 * 1000)
|
||||
},
|
||||
30 * 60 * 1000
|
||||
)
|
||||
} else {
|
||||
message.error(response?.message || '启动MAA配置失败')
|
||||
}
|
||||
@@ -1017,7 +1019,6 @@ const updateStageRemain = (value: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 初始化加载
|
||||
onMounted(() => {
|
||||
if (!scriptId) {
|
||||
|
||||
@@ -71,36 +71,30 @@
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 企业微信群机器人通知 -->
|
||||
<a-row :gutter="24" style="margin-top: 16px">
|
||||
<a-col :span="6">
|
||||
<a-checkbox
|
||||
v-model:checked="formData.Notify.IfCompanyWebHookBot"
|
||||
:disabled="loading || !formData.Notify.Enabled"
|
||||
>企业微信群机器人
|
||||
</a-checkbox>
|
||||
</a-col>
|
||||
<a-col :span="18">
|
||||
<a-input
|
||||
v-model:value="formData.Notify.CompanyWebHookBotUrl"
|
||||
placeholder="请输入机器人Webhook地址"
|
||||
:disabled="
|
||||
loading || !formData.Notify.Enabled || !formData.Notify.IfCompanyWebHookBot
|
||||
"
|
||||
size="large"
|
||||
style="width: 100%"
|
||||
class="modern-input"
|
||||
<!-- 自定义 Webhook 通知 -->
|
||||
<div style="margin-top: 16px">
|
||||
<WebhookManager
|
||||
v-model:webhooks="formData.Notify.CustomWebhooks"
|
||||
@change="handleWebhookChange"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import WebhookManager from '@/components/WebhookManager.vue'
|
||||
|
||||
defineProps<{
|
||||
formData: any
|
||||
loading: boolean
|
||||
}>()
|
||||
|
||||
// 处理 Webhook 变化
|
||||
const handleWebhookChange = () => {
|
||||
// 这里可以添加额外的处理逻辑,比如验证或保存
|
||||
console.log('User webhooks changed:', formData.Notify.CustomWebhooks)
|
||||
// 注意:实际保存会在用户点击保存按钮时进行,这里只是更新本地数据
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { QuestionCircleOutlined } from '@ant-design/icons-vue'
|
||||
import type { SettingsData } from '@/types/settings'
|
||||
import WebhookManager from '@/components/WebhookManager.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
settings: SettingsData
|
||||
@@ -11,6 +12,11 @@ const props = defineProps<{
|
||||
}>()
|
||||
|
||||
const { settings, sendTaskResultTimeOptions, handleSettingChange, testNotify, testingNotify } = props
|
||||
|
||||
// 处理 Webhook 变化
|
||||
const handleWebhookChange = async () => {
|
||||
await handleSettingChange('Notify', 'CustomWebhooks', settings.Notify.CustomWebhooks)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -273,55 +279,21 @@ const { settings, sendTaskResultTimeOptions, handleSettingChange, testNotify, te
|
||||
|
||||
<div class="form-section">
|
||||
<div class="section-header">
|
||||
<h3>企业微信机器人通知</h3>
|
||||
<h3>自定义 Webhook 通知</h3>
|
||||
<a
|
||||
href="https://doc.auto-mas.top/docs/advanced-features.html#%E4%BC%81%E4%B8%9A%E5%BE%AE%E4%BF%A1%E7%BE%A4%E6%9C%BA%E5%99%A8%E4%BA%BA%E9%80%9A%E7%9F%A5%E6%8E%A8%E9%80%81%E6%B8%A0%E9%81%93"
|
||||
href="https://doc.auto-mas.top/docs/advanced-features.html#%E8%87%AA%E5%AE%9A%E4%B9%89webhook%E9%80%9A%E7%9F%A5"
|
||||
target="_blank"
|
||||
class="section-doc-link"
|
||||
title="查看企业微信机器人配置文档"
|
||||
title="查看自定义Webhook配置文档"
|
||||
>
|
||||
文档
|
||||
</a>
|
||||
</div>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<div class="form-item-vertical">
|
||||
<div class="form-label-wrapper">
|
||||
<span class="form-label">启用企业微信机器人通知</span>
|
||||
<a-tooltip title="使用企业微信机器人推送通知">
|
||||
<QuestionCircleOutlined class="help-icon" />
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<a-select
|
||||
v-model:value="settings.Notify.IfCompanyWebHookBot"
|
||||
@change="(checked: any) => handleSettingChange('Notify', 'IfCompanyWebHookBot', checked)"
|
||||
size="large"
|
||||
style="width: 100%"
|
||||
>
|
||||
<a-select-option :value="true">是</a-select-option>
|
||||
<a-select-option :value="false">否</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<div class="form-item-vertical">
|
||||
<div class="form-label-wrapper">
|
||||
<span class="form-label">Webhook URL</span>
|
||||
<a-tooltip title="企业微信机器人的Webhook地址">
|
||||
<QuestionCircleOutlined class="help-icon" />
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<a-input
|
||||
v-model:value="settings.Notify.CompanyWebHookBotUrl"
|
||||
@blur="handleSettingChange('Notify', 'CompanyWebHookBotUrl', settings.Notify.CompanyWebHookBotUrl)"
|
||||
:disabled="!settings.Notify.IfCompanyWebHookBot"
|
||||
placeholder="请输入Webhook URL"
|
||||
size="large"
|
||||
<WebhookManager
|
||||
v-model:webhooks="settings.Notify.CustomWebhooks"
|
||||
@change="handleWebhookChange"
|
||||
/>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 测试按钮已移至“通知内容”标题右侧 -->
|
||||
</div>
|
||||
|
||||
@@ -72,8 +72,7 @@ const settings = reactive<SettingsData>({
|
||||
ServerChanKey: '',
|
||||
ServerChanChannel: '',
|
||||
ServerChanTag: '',
|
||||
IfCompanyWebHookBot: false,
|
||||
CompanyWebHookBotUrl: '',
|
||||
CustomWebhooks: [],
|
||||
},
|
||||
Voice: { Enabled: false, Type: 'simple' },
|
||||
Start: { IfSelfStart: false, IfMinimizeDirectly: false },
|
||||
|
||||
Reference in New Issue
Block a user