From d2066e9631834b1ecfe1a62f3e94e063b25f9b64 Mon Sep 17 00:00:00 2001 From: AoXuan Date: Sat, 27 Sep 2025 16:22:04 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat(notify):=20=E5=AE=9E=E7=8E=B0=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89Webhook=E9=80=9A=E7=9F=A5=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/setting.py | 162 +++++++++++++++++++++++++++++++++++ app/models/schema.py | 24 +++--- app/services/notification.py | 118 ++++++++++++++++++++++--- app/task/MAA.py | 105 ++++++++++++++--------- app/task/general.py | 58 +++++++++---- 5 files changed, 386 insertions(+), 81 deletions(-) diff --git a/app/api/setting.py b/app/api/setting.py index 33a5210..465b969 100644 --- a/app/api/setting.py +++ b/app/api/setting.py @@ -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)}" + ) diff --git a/app/models/schema.py b/app/models/schema.py index 9be5bb8..1317d70 100644 --- a/app/models/schema.py +++ b/app/models/schema.py @@ -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 UserConfig_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列表" ) diff --git a/app/services/notification.py b/app/services/notification.py index 146eb0d..4b580fe 100644 --- a/app/services/notification.py +++ b/app/services/notification.py @@ -170,9 +170,101 @@ 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") + } + + # 替换模板中的变量 + formatted_template = template.format(**template_vars) + + # 尝试解析为JSON + try: + data = json.loads(formatted_template) + except json.JSONDecodeError: + # 如果不是JSON格式,作为纯文本发送 + data = formatted_template + + 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 +356,19 @@ class Notification: Config.get("Notify", "ServerChanKey"), ) - # 发送WebHook通知 - if Config.get("Notify", "IfCompanyWebHookBot"): - await self.WebHookPush( - "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通知 + custom_webhooks = Config.get("Notify", "CustomWebhooks", []) + if custom_webhooks: + for webhook in custom_webhooks: + if webhook.get("enabled", True): + try: + await self.CustomWebhookPush( + "AUTO-MAS测试通知", + "这是 AUTO-MAS 外部通知测试信息。如果你看到了这段内容, 说明 AUTO-MAS 的通知功能已经正确配置且可以正常工作!", + webhook + ) + except Exception as e: + logger.error(f"自定义Webhook测试失败 ({webhook.get('name', 'Unknown')}): {e}") logger.success("测试通知发送完成") diff --git a/app/task/MAA.py b/app/task/MAA.py index 341d5b9..6580f4a 100644 --- a/app/task/MAA.py +++ b/app/task/MAA.py @@ -1906,12 +1906,19 @@ class MaaManager: Config.get("Notify", "ServerChanKey"), ) - if Config.get("Notify", "IfCompanyWebHookBot"): - await Notify.WebHookPush( - title, - f"{message_text}\n\nAUTO-MAS 敬上", - Config.get("Notify", "CompanyWebHookBotUrl"), - ) + # 发送自定义Webhook通知 + custom_webhooks = Config.get("Notify", "CustomWebhooks", []) + if custom_webhooks: + for webhook in custom_webhooks: + if webhook.get("enabled", True): + try: + await Notify.CustomWebhookPush( + title, + f"{message_text}\n\nAUTO-MAS 敬上", + webhook + ) + except Exception as e: + logger.error(f"自定义Webhook推送失败 ({webhook.get('name', 'Unknown')}): {e}") elif mode == "统计信息": @@ -1962,12 +1969,19 @@ class MaaManager: Config.get("Notify", "ServerChanKey"), ) - if Config.get("Notify", "IfCompanyWebHookBot"): - await Notify.WebHookPush( - title, - f"{message_text}\n\nAUTO-MAS 敬上", - Config.get("Notify", "CompanyWebHookBotUrl"), - ) + # 发送自定义Webhook通知 + custom_webhooks = Config.get("Notify", "CustomWebhooks", []) + if custom_webhooks: + for webhook in custom_webhooks: + if webhook.get("enabled", True): + try: + await Notify.CustomWebhookPush( + title, + f"{message_text}\n\nAUTO-MAS 敬上", + 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 +2014,19 @@ class MaaManager: ) # 推送CompanyWebHookBot通知 - if self.cur_user_data.get("Notify", "IfCompanyWebHookBot"): - if self.cur_user_data.get("Notify", "CompanyWebHookBotUrl"): - await Notify.WebHookPush( - title, - f"{message_text}\n\nAUTO-MAS 敬上", - self.cur_user_data.get("Notify", "CompanyWebHookBotUrl"), - ) + # 发送用户自定义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 敬上", + webhook + ) + except Exception as e: + logger.error(f"用户自定义Webhook推送失败 ({webhook.get('name', 'Unknown')}): {e}") else: logger.error( "用户CompanyWebHookBot密钥为空, 无法发送用户单独的CompanyWebHookBot通知" @@ -2034,16 +2054,19 @@ class MaaManager: Config.get("Notify", "ServerChanKey"), ) - if Config.get("Notify", "IfCompanyWebHookBot"): - await Notify.WebHookPush( - title, - "好羡慕~\n\nAUTO-MAS 敬上", - Config.get("Notify", "CompanyWebHookBotUrl"), - ) - await Notify.CompanyWebHookBotPushImage( - Path.cwd() / "res/images/notification/six_star.png", - Config.get("Notify", "CompanyWebHookBotUrl"), - ) + # 发送自定义Webhook通知(六星喜报) + custom_webhooks = Config.get("Notify", "CustomWebhooks", []) + if custom_webhooks: + for webhook in custom_webhooks: + if webhook.get("enabled", True): + try: + await Notify.CustomWebhookPush( + title, + "好羡慕~\n\nAUTO-MAS 敬上", + 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 +2100,19 @@ class MaaManager: ) # 推送CompanyWebHookBot通知 - if self.cur_user_data.get("Notify", "IfCompanyWebHookBot"): - if self.cur_user_data.get("Notify", "CompanyWebHookBotUrl"): - await Notify.WebHookPush( - 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通知(六星喜报) + 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 敬上", + webhook + ) + except Exception as e: + logger.error(f"用户自定义Webhook推送失败 ({webhook.get('name', 'Unknown')}): {e}") else: logger.error( "用户CompanyWebHookBot密钥为空, 无法发送用户单独的CompanyWebHookBot通知" diff --git a/app/task/general.py b/app/task/general.py index 5c656ea..4a6b794 100644 --- a/app/task/general.py +++ b/app/task/general.py @@ -982,12 +982,19 @@ class GeneralManager: Config.get("Notify", "ServerChanKey"), ) - if Config.get("Notify", "IfCompanyWebHookBot"): - await Notify.WebHookPush( - title, - f"{message_text}\n\nAUTO-MAS 敬上", - Config.get("Notify", "CompanyWebHookBotUrl"), - ) + # 发送自定义Webhook通知 + custom_webhooks = Config.get("Notify", "CustomWebhooks", []) + if custom_webhooks: + for webhook in custom_webhooks: + if webhook.get("enabled", True): + try: + await Notify.CustomWebhookPush( + title, + f"{message_text}\n\nAUTO-MAS 敬上", + webhook + ) + except Exception as e: + logger.error(f"自定义Webhook推送失败 ({webhook.get('name', 'Unknown')}): {e}") elif mode == "统计信息": @@ -1019,12 +1026,19 @@ class GeneralManager: Config.get("Notify", "ServerChanKey"), ) - if Config.get("Notify", "IfCompanyWebHookBot"): - await Notify.WebHookPush( - title, - f"{message_text}\n\nAUTO-MAS 敬上", - Config.get("Notify", "CompanyWebHookBotUrl"), - ) + # 发送自定义Webhook通知 + custom_webhooks = Config.get("Notify", "CustomWebhooks", []) + if custom_webhooks: + for webhook in custom_webhooks: + if webhook.get("enabled", True): + try: + await Notify.CustomWebhookPush( + title, + f"{message_text}\n\nAUTO-MAS 敬上", + 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 +1071,19 @@ class GeneralManager: ) # 推送CompanyWebHookBot通知 - if self.cur_user_data.get("Notify", "IfCompanyWebHookBot"): - if self.cur_user_data.get("Notify", "CompanyWebHookBotUrl"): - await Notify.WebHookPush( - title, - f"{message_text}\n\nAUTO-MAS 敬上", - self.cur_user_data.get("Notify", "CompanyWebHookBotUrl"), - ) + # 发送用户自定义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 敬上", + webhook + ) + except Exception as e: + logger.error(f"用户自定义Webhook推送失败 ({webhook.get('name', 'Unknown')}): {e}") else: logger.error( "用户CompanyWebHookBot密钥为空, 无法发送用户单独的CompanyWebHookBot通知" From 2d6dae3fc980fab16febac28ae77a7b22bff32fb Mon Sep 17 00:00:00 2001 From: AoXuan Date: Sat, 27 Sep 2025 16:32:54 +0800 Subject: [PATCH 2/5] =?UTF-8?q?refactor(config):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E9=80=9A=E7=9F=A5=E9=85=8D=E7=BD=AE=E9=A1=B9=E5=B9=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96Webhook=E8=8E=B7=E5=8F=96=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/config.py | 19 +++---------------- app/services/notification.py | 5 ++++- app/task/MAA.py | 15 ++++++++++++--- app/task/general.py | 10 ++++++++-- 4 files changed, 27 insertions(+), 22 deletions(-) diff --git a/app/core/config.py b/app/core/config.py index 472a331..0d19ac8 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -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): diff --git a/app/services/notification.py b/app/services/notification.py index 4b580fe..9c1ddb0 100644 --- a/app/services/notification.py +++ b/app/services/notification.py @@ -357,7 +357,10 @@ class Notification: ) # 发送自定义Webhook通知 - custom_webhooks = Config.get("Notify", "CustomWebhooks", []) + try: + custom_webhooks = Config.get("Notify", "CustomWebhooks") + except AttributeError: + custom_webhooks = [] if custom_webhooks: for webhook in custom_webhooks: if webhook.get("enabled", True): diff --git a/app/task/MAA.py b/app/task/MAA.py index 6580f4a..80e9d21 100644 --- a/app/task/MAA.py +++ b/app/task/MAA.py @@ -1907,7 +1907,10 @@ class MaaManager: ) # 发送自定义Webhook通知 - custom_webhooks = Config.get("Notify", "CustomWebhooks", []) + try: + custom_webhooks = Config.get("Notify", "CustomWebhooks") + except AttributeError: + custom_webhooks = [] if custom_webhooks: for webhook in custom_webhooks: if webhook.get("enabled", True): @@ -1970,7 +1973,10 @@ class MaaManager: ) # 发送自定义Webhook通知 - custom_webhooks = Config.get("Notify", "CustomWebhooks", []) + try: + custom_webhooks = Config.get("Notify", "CustomWebhooks") + except AttributeError: + custom_webhooks = [] if custom_webhooks: for webhook in custom_webhooks: if webhook.get("enabled", True): @@ -2055,7 +2061,10 @@ class MaaManager: ) # 发送自定义Webhook通知(六星喜报) - custom_webhooks = Config.get("Notify", "CustomWebhooks", []) + try: + custom_webhooks = Config.get("Notify", "CustomWebhooks") + except AttributeError: + custom_webhooks = [] if custom_webhooks: for webhook in custom_webhooks: if webhook.get("enabled", True): diff --git a/app/task/general.py b/app/task/general.py index 4a6b794..7bb3d61 100644 --- a/app/task/general.py +++ b/app/task/general.py @@ -983,7 +983,10 @@ class GeneralManager: ) # 发送自定义Webhook通知 - custom_webhooks = Config.get("Notify", "CustomWebhooks", []) + try: + custom_webhooks = Config.get("Notify", "CustomWebhooks") + except AttributeError: + custom_webhooks = [] if custom_webhooks: for webhook in custom_webhooks: if webhook.get("enabled", True): @@ -1027,7 +1030,10 @@ class GeneralManager: ) # 发送自定义Webhook通知 - custom_webhooks = Config.get("Notify", "CustomWebhooks", []) + try: + custom_webhooks = Config.get("Notify", "CustomWebhooks") + except AttributeError: + custom_webhooks = [] if custom_webhooks: for webhook in custom_webhooks: if webhook.get("enabled", True): From 28ce06c92de9bf8b06226991ef11718aa4e6cc9a Mon Sep 17 00:00:00 2001 From: AoXuan Date: Sat, 27 Sep 2025 16:48:46 +0800 Subject: [PATCH 3/5] =?UTF-8?q?refactor(notification):=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E6=A8=A1=E6=9D=BF=E8=A7=A3=E6=9E=90=E9=80=BB=E8=BE=91?= =?UTF-8?q?=EF=BC=8C=E6=94=AF=E6=8C=81JSON=E5=92=8C=E5=AD=97=E7=AC=A6?= =?UTF-8?q?=E4=B8=B2=E6=A8=A1=E6=9D=BF=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/services/notification.py | 45 +++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/app/services/notification.py b/app/services/notification.py index 9c1ddb0..aa31358 100644 --- a/app/services/notification.py +++ b/app/services/notification.py @@ -203,15 +203,48 @@ class Notification: "time": datetime.now().strftime("%H:%M:%S") } - # 替换模板中的变量 - formatted_template = template.format(**template_vars) + logger.debug(f"原始模板: {template}") + logger.debug(f"模板变量: {template_vars}") - # 尝试解析为JSON + # 先尝试作为JSON模板处理 try: - data = json.loads(formatted_template) + # 解析模板为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格式,作为纯文本发送 - data = formatted_template + # 如果不是有效的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}") From 14a8a308c50c44251ca01efaefc452fe4367e308 Mon Sep 17 00:00:00 2001 From: AoXuan Date: Sat, 27 Sep 2025 16:54:50 +0800 Subject: [PATCH 4/5] =?UTF-8?q?refactor(notify):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E9=80=9A=E7=9F=A5=E8=AE=BE=E7=BD=AE=EF=BC=8C=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89Webhook=E7=AE=A1=E7=90=86=E4=B8=8E?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/index.ts | 10 +- frontend/src/api/models/CustomWebhook.ts | 35 ++ ...erConfig.ts => GeneralUserConfig_Input.ts} | 2 +- .../api/models/GeneralUserConfig_Output.ts | 22 + ...{GlobalConfig.ts => GlobalConfig_Input.ts} | 2 +- .../src/api/models/GlobalConfig_Notify.ts | 9 +- .../src/api/models/GlobalConfig_Output.ts | 37 ++ ...aaUserConfig.ts => MaaUserConfig_Input.ts} | 2 +- .../src/api/models/MaaUserConfig_Output.ts | 27 + frontend/src/api/models/SettingGetOut.ts | 4 +- frontend/src/api/models/SettingUpdateIn.ts | 4 +- frontend/src/api/models/UserConfig_Notify.ts | 9 +- frontend/src/api/models/UserCreateOut.ts | 6 +- frontend/src/api/models/UserGetOut.ts | 6 +- frontend/src/api/models/UserUpdateIn.ts | 6 +- frontend/src/api/services/Service.ts | 80 +++ frontend/src/components/WebhookManager.vue | 524 ++++++++++++++++++ frontend/src/composables/useScriptApi.ts | 6 +- frontend/src/types/script.ts | 11 +- frontend/src/types/settings.ts | 14 +- frontend/src/utils/webhookTemplates.ts | 119 ++++ frontend/src/views/GeneralUserEdit.vue | 511 +++++++++-------- frontend/src/views/MAAUserEdit.vue | 55 +- .../views/MAAUserEdit/NotifyConfigSection.vue | 38 +- frontend/src/views/setting/TabNotify.vue | 54 +- frontend/src/views/setting/index.vue | 3 +- 26 files changed, 1207 insertions(+), 389 deletions(-) create mode 100644 frontend/src/api/models/CustomWebhook.ts rename frontend/src/api/models/{GeneralUserConfig.ts => GeneralUserConfig_Input.ts} (93%) create mode 100644 frontend/src/api/models/GeneralUserConfig_Output.ts rename frontend/src/api/models/{GlobalConfig.ts => GlobalConfig_Input.ts} (96%) create mode 100644 frontend/src/api/models/GlobalConfig_Output.ts rename frontend/src/api/models/{MaaUserConfig.ts => MaaUserConfig_Input.ts} (95%) create mode 100644 frontend/src/api/models/MaaUserConfig_Output.ts create mode 100644 frontend/src/components/WebhookManager.vue create mode 100644 frontend/src/utils/webhookTemplates.ts diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 8bdd32b..7338ec1 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -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'; diff --git a/frontend/src/api/models/CustomWebhook.ts b/frontend/src/api/models/CustomWebhook.ts new file mode 100644 index 0000000..cb39dc4 --- /dev/null +++ b/frontend/src/api/models/CustomWebhook.ts @@ -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 | null); + /** + * 请求方法 + */ + method?: ('POST' | 'GET' | null); +}; + diff --git a/frontend/src/api/models/GeneralUserConfig.ts b/frontend/src/api/models/GeneralUserConfig_Input.ts similarity index 93% rename from frontend/src/api/models/GeneralUserConfig.ts rename to frontend/src/api/models/GeneralUserConfig_Input.ts index 0bf20fb..26f2671 100644 --- a/frontend/src/api/models/GeneralUserConfig.ts +++ b/frontend/src/api/models/GeneralUserConfig_Input.ts @@ -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 = { /** * 用户信息 */ diff --git a/frontend/src/api/models/GeneralUserConfig_Output.ts b/frontend/src/api/models/GeneralUserConfig_Output.ts new file mode 100644 index 0000000..67e8295 --- /dev/null +++ b/frontend/src/api/models/GeneralUserConfig_Output.ts @@ -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); +}; + diff --git a/frontend/src/api/models/GlobalConfig.ts b/frontend/src/api/models/GlobalConfig_Input.ts similarity index 96% rename from frontend/src/api/models/GlobalConfig.ts rename to frontend/src/api/models/GlobalConfig_Input.ts index 548f600..e64e5d1 100644 --- a/frontend/src/api/models/GlobalConfig.ts +++ b/frontend/src/api/models/GlobalConfig_Input.ts @@ -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 = { /** * 功能相关配置 */ diff --git a/frontend/src/api/models/GlobalConfig_Notify.ts b/frontend/src/api/models/GlobalConfig_Notify.ts index cc2b68b..e1cc8b9 100644 --- a/frontend/src/api/models/GlobalConfig_Notify.ts +++ b/frontend/src/api/models/GlobalConfig_Notify.ts @@ -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 | null); }; diff --git a/frontend/src/api/models/GlobalConfig_Output.ts b/frontend/src/api/models/GlobalConfig_Output.ts new file mode 100644 index 0000000..95cea9a --- /dev/null +++ b/frontend/src/api/models/GlobalConfig_Output.ts @@ -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); +}; + diff --git a/frontend/src/api/models/MaaUserConfig.ts b/frontend/src/api/models/MaaUserConfig_Input.ts similarity index 95% rename from frontend/src/api/models/MaaUserConfig.ts rename to frontend/src/api/models/MaaUserConfig_Input.ts index a514d50..8ab6e94 100644 --- a/frontend/src/api/models/MaaUserConfig.ts +++ b/frontend/src/api/models/MaaUserConfig_Input.ts @@ -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 = { /** * 基础信息 */ diff --git a/frontend/src/api/models/MaaUserConfig_Output.ts b/frontend/src/api/models/MaaUserConfig_Output.ts new file mode 100644 index 0000000..4f73104 --- /dev/null +++ b/frontend/src/api/models/MaaUserConfig_Output.ts @@ -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); +}; + diff --git a/frontend/src/api/models/SettingGetOut.ts b/frontend/src/api/models/SettingGetOut.ts index e909ef6..be80afa 100644 --- a/frontend/src/api/models/SettingGetOut.ts +++ b/frontend/src/api/models/SettingGetOut.ts @@ -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; }; diff --git a/frontend/src/api/models/SettingUpdateIn.ts b/frontend/src/api/models/SettingUpdateIn.ts index 51e8991..4d11565 100644 --- a/frontend/src/api/models/SettingUpdateIn.ts +++ b/frontend/src/api/models/SettingUpdateIn.ts @@ -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; }; diff --git a/frontend/src/api/models/UserConfig_Notify.ts b/frontend/src/api/models/UserConfig_Notify.ts index dfca928..7bf9cbf 100644 --- a/frontend/src/api/models/UserConfig_Notify.ts +++ b/frontend/src/api/models/UserConfig_Notify.ts @@ -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 | null); }; diff --git a/frontend/src/api/models/UserCreateOut.ts b/frontend/src/api/models/UserCreateOut.ts index 630d5ac..560f95b 100644 --- a/frontend/src/api/models/UserCreateOut.ts +++ b/frontend/src/api/models/UserCreateOut.ts @@ -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); }; diff --git a/frontend/src/api/models/UserGetOut.ts b/frontend/src/api/models/UserGetOut.ts index 3b4d7bd..5d83ea4 100644 --- a/frontend/src/api/models/UserGetOut.ts +++ b/frontend/src/api/models/UserGetOut.ts @@ -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; + data: Record; }; diff --git a/frontend/src/api/models/UserUpdateIn.ts b/frontend/src/api/models/UserUpdateIn.ts index 91f6b06..ac6ab93 100644 --- a/frontend/src/api/models/UserUpdateIn.ts +++ b/frontend/src/api/models/UserUpdateIn.ts @@ -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); }; diff --git a/frontend/src/api/services/Service.ts b/frontend/src/api/services/Service.ts index 9936ba4..d44ed71 100644 --- a/frontend/src/api/services/Service.ts +++ b/frontend/src/api/services/Service.ts @@ -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, + ): CancelablePromise { + 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, + ): CancelablePromise { + 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, + ): CancelablePromise { + 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, + ): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/setting/webhook/test', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } /** * 检查更新 * @param requestBody diff --git a/frontend/src/components/WebhookManager.vue b/frontend/src/components/WebhookManager.vue new file mode 100644 index 0000000..821d577 --- /dev/null +++ b/frontend/src/components/WebhookManager.vue @@ -0,0 +1,524 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/composables/useScriptApi.ts b/frontend/src/composables/useScriptApi.ts index 26a76a7..fed4cd5 100644 --- a/frontend/src/composables/useScriptApi.ts +++ b/frontend/src/composables/useScriptApi.ts @@ -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 : '', diff --git a/frontend/src/types/script.ts b/frontend/src/types/script.ts index e9336d0..096108a 100644 --- a/frontend/src/types/script.ts +++ b/frontend/src/types/script.ts @@ -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 + method?: 'POST' | 'GET' + }> IfSendStatistic: boolean IfServerChan: boolean ServerChanChannel: string diff --git a/frontend/src/types/settings.ts b/frontend/src/types/settings.ts index c240ec7..f701501 100644 --- a/frontend/src/types/settings.ts +++ b/frontend/src/types/settings.ts @@ -1,3 +1,14 @@ +// 自定义Webhook配置 +export interface CustomWebhook { + id: string + name: string + url: string + template: string + enabled: boolean + headers?: Record + 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 diff --git a/frontend/src/utils/webhookTemplates.ts b/frontend/src/utils/webhookTemplates.ts new file mode 100644 index 0000000..1ad345c --- /dev/null +++ b/frontend/src/utils/webhookTemplates.ts @@ -0,0 +1,119 @@ +// Webhook 模板配置 +export interface WebhookTemplate { + name: string + description: string + template: string + headers?: Record + 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)' } +] \ No newline at end of file diff --git a/frontend/src/views/GeneralUserEdit.vue b/frontend/src/views/GeneralUserEdit.vue index d0a7b32..52105ce 100644 --- a/frontend/src/views/GeneralUserEdit.vue +++ b/frontend/src/views/GeneralUserEdit.vue @@ -85,85 +85,85 @@

基本信息

- - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + @@ -171,90 +171,90 @@

额外脚本

- - - - - - - - - + + + + - - - 选择文件 - - - - - - - - - - - - - - + + + + + + 选择文件 + + + + + + + + + + - - - 选择文件 - - - - - + + + + + + + 选择文件 + + + + + @@ -262,92 +262,77 @@

通知配置

- - - 启用通知 - - - - 启用后将发送任务通知 - - + + + 启用通知 + + + + 启用后将发送任务通知 + + - - - - 通知内容 - - - 统计信息 - - - + + + + 通知内容 + + + 统计信息 + + + - - - - 邮件通知 - - - - + + + 邮件通知 + + + + + + + + + + + Server酱 + + + + + + + + +
+ - - - - - - - Server酱 - - - - - - - - - - - 企业微信群机器人 - - - - - - +
@@ -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,16 +624,19 @@ const handleGeneralConfig = async () => { message.success(`已开始配置用户 ${formData.userName} 的通用设置`) // 设置 30 分钟超时自动断开 - generalConfigTimeout = window.setTimeout(() => { - if (generalWebsocketId.value) { - const id = generalWebsocketId.value - unsubscribe(id) - generalWebsocketId.value = null - showGeneralConfigMask.value = false - message.info(`用户 ${formData.userName} 的配置会话已超时断开`) - } - generalConfigTimeout = null - }, 30 * 60 * 1000) + generalConfigTimeout = window.setTimeout( + () => { + if (generalWebsocketId.value) { + const id = generalWebsocketId.value + unsubscribe(id) + generalWebsocketId.value = null + showGeneralConfigMask.value = false + message.info(`用户 ${formData.userName} 的配置会话已超时断开`) + } + generalConfigTimeout = null + }, + 30 * 60 * 1000 + ) } else { message.error(response?.message || '启动通用配置失败') } @@ -695,7 +683,7 @@ const selectScriptBeforeTask = async () => { { name: '脚本文件', extensions: ['py', 'js', 'sh'] }, { name: '所有文件', extensions: ['*'] }, ]) - + if (path && path.length > 0) { formData.Info.ScriptBeforeTask = path[0] message.success('任务前脚本路径选择成功') @@ -713,7 +701,7 @@ const selectScriptAfterTask = async () => { { name: '脚本文件', extensions: ['py', 'js', 'sh'] }, { name: '所有文件', extensions: ['*'] }, ]) - + if (path && path.length > 0) { formData.Info.ScriptAfterTask = path[0] message.success('任务后脚本路径选择成功') @@ -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) @@ -991,4 +986,4 @@ onMounted(() => { display: flex; justify-content: center; } - \ No newline at end of file + diff --git a/frontend/src/views/MAAUserEdit.vue b/frontend/src/views/MAAUserEdit.vue index 90758ea..0aafa70 100644 --- a/frontend/src/views/MAAUserEdit.vue +++ b/frontend/src/views/MAAUserEdit.vue @@ -41,7 +41,13 @@
- + - + - + - +
@@ -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,16 +811,19 @@ const handleMAAConfig = async () => { message.success(`已开始配置用户 ${formData.Info?.Name || formData.userName} 的MAA设置`) // 设置 30 分钟超时自动断开 - maaConfigTimeout = window.setTimeout(() => { - if (maaWebsocketId.value) { - const id = maaWebsocketId.value - unsubscribe(id) - maaWebsocketId.value = null - showMAAConfigMask.value = false - message.info(`用户 ${formData.Info?.Name || formData.userName} 的配置会话已超时断开`) - } - maaConfigTimeout = null - }, 30 * 60 * 1000) + maaConfigTimeout = window.setTimeout( + () => { + if (maaWebsocketId.value) { + const id = maaWebsocketId.value + unsubscribe(id) + maaWebsocketId.value = null + showMAAConfigMask.value = false + message.info(`用户 ${formData.Info?.Name || formData.userName} 的配置会话已超时断开`) + } + maaConfigTimeout = null + }, + 30 * 60 * 1000 + ) } else { message.error(response?.message || '启动MAA配置失败') } @@ -1017,7 +1019,6 @@ const updateStageRemain = (value: string) => { } } - // 初始化加载 onMounted(() => { if (!scriptId) { diff --git a/frontend/src/views/MAAUserEdit/NotifyConfigSection.vue b/frontend/src/views/MAAUserEdit/NotifyConfigSection.vue index 5414188..c10dcc2 100644 --- a/frontend/src/views/MAAUserEdit/NotifyConfigSection.vue +++ b/frontend/src/views/MAAUserEdit/NotifyConfigSection.vue @@ -71,36 +71,30 @@
- - - - 企业微信群机器人 - - - - - - + +
+ +