380 lines
9.5 KiB
Python
380 lines
9.5 KiB
Python
|
|
"""
|
|||
|
|
通知工具
|
|||
|
|
提供发送通知的功能(邮件、Webhook等)
|
|||
|
|
"""
|
|||
|
|
import httpx
|
|||
|
|
from typing import Dict, Any, Optional, List
|
|||
|
|
from dataclasses import dataclass
|
|||
|
|
from enum import Enum
|
|||
|
|
|
|||
|
|
|
|||
|
|
class NotificationType(Enum):
|
|||
|
|
"""通知类型"""
|
|||
|
|
EMAIL = "email"
|
|||
|
|
WEBHOOK = "webhook"
|
|||
|
|
SMS = "sms"
|
|||
|
|
DINGTALK = "dingtalk"
|
|||
|
|
WECHAT = "wechat"
|
|||
|
|
SLACK = "slack"
|
|||
|
|
|
|||
|
|
|
|||
|
|
@dataclass
|
|||
|
|
class NotificationConfig:
|
|||
|
|
"""通知配置"""
|
|||
|
|
# Email配置
|
|||
|
|
smtp_host: str = ""
|
|||
|
|
smtp_port: int = 587
|
|||
|
|
smtp_user: str = ""
|
|||
|
|
smtp_password: str = ""
|
|||
|
|
from_email: str = ""
|
|||
|
|
|
|||
|
|
# Webhook配置
|
|||
|
|
webhook_url: str = ""
|
|||
|
|
webhook_secret: str = ""
|
|||
|
|
|
|||
|
|
# 钉钉配置
|
|||
|
|
dingtalk_webhook: str = ""
|
|||
|
|
|
|||
|
|
# Slack配置
|
|||
|
|
slack_webhook: str = ""
|
|||
|
|
|
|||
|
|
|
|||
|
|
class NotificationTool:
|
|||
|
|
"""
|
|||
|
|
通知工具
|
|||
|
|
|
|||
|
|
支持多种通知渠道:
|
|||
|
|
- Email (SMTP)
|
|||
|
|
- Webhook
|
|||
|
|
- 钉钉
|
|||
|
|
- Slack
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
def __init__(self, config: Optional[NotificationConfig] = None):
|
|||
|
|
self.config = config or NotificationConfig()
|
|||
|
|
|
|||
|
|
async def send_email(
|
|||
|
|
self,
|
|||
|
|
to: str,
|
|||
|
|
subject: str,
|
|||
|
|
body: str,
|
|||
|
|
cc: Optional[List[str]] = None,
|
|||
|
|
is_html: bool = False
|
|||
|
|
) -> Dict[str, Any]:
|
|||
|
|
"""
|
|||
|
|
发送邮件
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
to: 收件人
|
|||
|
|
subject: 主题
|
|||
|
|
body: 内容
|
|||
|
|
cc: 抄送列表
|
|||
|
|
is_html: 是否HTML格式
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
发送结果
|
|||
|
|
"""
|
|||
|
|
if not self.config.smtp_host:
|
|||
|
|
return {
|
|||
|
|
"success": False,
|
|||
|
|
"error": "Email not configured"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
import smtplib
|
|||
|
|
from email.mime.text import MIMEText
|
|||
|
|
from email.mime.multipart import MIMEMultipart
|
|||
|
|
|
|||
|
|
# 构建邮件
|
|||
|
|
msg = MIMEMultipart('alternative')
|
|||
|
|
msg['Subject'] = subject
|
|||
|
|
msg['From'] = self.config.from_email or self.config.smtp_user
|
|||
|
|
msg['To'] = to
|
|||
|
|
|
|||
|
|
if cc:
|
|||
|
|
msg['Cc'] = ",".join(cc)
|
|||
|
|
|
|||
|
|
# 添加内容
|
|||
|
|
content_type = "html" if is_html else "plain"
|
|||
|
|
msg.attach(MIMEText(body, content_type))
|
|||
|
|
|
|||
|
|
# 发送
|
|||
|
|
with smtplib.SMTP(self.config.smtp_host, self.config.smtp_port) as server:
|
|||
|
|
server.starttls()
|
|||
|
|
server.login(self.config.smtp_user, self.config.smtp_password)
|
|||
|
|
server.send_message(msg)
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"success": True,
|
|||
|
|
"type": "email",
|
|||
|
|
"to": to,
|
|||
|
|
"subject": subject
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
return {
|
|||
|
|
"success": False,
|
|||
|
|
"error": str(e),
|
|||
|
|
"type": "email"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async def send_webhook(
|
|||
|
|
self,
|
|||
|
|
url: str,
|
|||
|
|
data: Dict[str, Any],
|
|||
|
|
method: str = "POST",
|
|||
|
|
headers: Optional[Dict[str, str]] = None
|
|||
|
|
) -> Dict[str, Any]:
|
|||
|
|
"""
|
|||
|
|
发送Webhook
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
url: Webhook URL
|
|||
|
|
data: 请求数据
|
|||
|
|
method: HTTP方法
|
|||
|
|
headers: 请求头
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
发送结果
|
|||
|
|
"""
|
|||
|
|
try:
|
|||
|
|
async with httpx.AsyncClient(timeout=10) as client:
|
|||
|
|
response = await client.request(
|
|||
|
|
method=method,
|
|||
|
|
url=url,
|
|||
|
|
json=data,
|
|||
|
|
headers=headers
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"success": response.status_code < 400,
|
|||
|
|
"status_code": response.status_code,
|
|||
|
|
"type": "webhook",
|
|||
|
|
"url": url
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
return {
|
|||
|
|
"success": False,
|
|||
|
|
"error": str(e),
|
|||
|
|
"type": "webhook"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async def send_dingtalk(
|
|||
|
|
self,
|
|||
|
|
message: str,
|
|||
|
|
webhook: Optional[str] = None
|
|||
|
|
) -> Dict[str, Any]:
|
|||
|
|
"""
|
|||
|
|
发送钉钉消息
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
message: 消息内容
|
|||
|
|
webhook: 自定义webhook URL
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
发送结果
|
|||
|
|
"""
|
|||
|
|
url = webhook or self.config.dingtalk_webhook
|
|||
|
|
if not url:
|
|||
|
|
return {
|
|||
|
|
"success": False,
|
|||
|
|
"error": "Dingtalk webhook not configured"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
async with httpx.AsyncClient(timeout=10) as client:
|
|||
|
|
response = await client.post(
|
|||
|
|
url,
|
|||
|
|
json={
|
|||
|
|
"msgtype": "text",
|
|||
|
|
"text": {
|
|||
|
|
"content": message
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
result = response.json()
|
|||
|
|
return {
|
|||
|
|
"success": result.get("errcode") == 0,
|
|||
|
|
"type": "dingtalk",
|
|||
|
|
"response": result
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
return {
|
|||
|
|
"success": False,
|
|||
|
|
"error": str(e),
|
|||
|
|
"type": "dingtalk"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async def send_slack(
|
|||
|
|
self,
|
|||
|
|
message: str,
|
|||
|
|
channel: Optional[str] = None,
|
|||
|
|
webhook: Optional[str] = None
|
|||
|
|
) -> Dict[str, Any]:
|
|||
|
|
"""
|
|||
|
|
发送Slack消息
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
message: 消息内容
|
|||
|
|
channel: 频道
|
|||
|
|
webhook: 自定义webhook URL
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
发送结果
|
|||
|
|
"""
|
|||
|
|
url = webhook or self.config.slack_webhook
|
|||
|
|
if not url:
|
|||
|
|
return {
|
|||
|
|
"success": False,
|
|||
|
|
"error": "Slack webhook not configured"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
payload = {"text": message}
|
|||
|
|
if channel:
|
|||
|
|
payload["channel"] = channel
|
|||
|
|
|
|||
|
|
async with httpx.AsyncClient(timeout=10) as client:
|
|||
|
|
response = await client.post(url, json=payload)
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"success": response.status_code == 200,
|
|||
|
|
"type": "slack",
|
|||
|
|
"status_code": response.status_code
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
return {
|
|||
|
|
"success": False,
|
|||
|
|
"error": str(e),
|
|||
|
|
"type": "slack"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async def send(
|
|||
|
|
self,
|
|||
|
|
type: str,
|
|||
|
|
message: str,
|
|||
|
|
**kwargs
|
|||
|
|
) -> Dict[str, Any]:
|
|||
|
|
"""
|
|||
|
|
统一发送接口
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
type: 通知类型 (email, webhook, dingtalk, slack)
|
|||
|
|
message: 消息内容
|
|||
|
|
**kwargs: 其他参数
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
发送结果
|
|||
|
|
"""
|
|||
|
|
type = type.lower()
|
|||
|
|
|
|||
|
|
if type == "email":
|
|||
|
|
return await self.send_email(
|
|||
|
|
to=kwargs.get("to", ""),
|
|||
|
|
subject=kwargs.get("subject", "Notification"),
|
|||
|
|
body=message,
|
|||
|
|
cc=kwargs.get("cc")
|
|||
|
|
)
|
|||
|
|
elif type == "webhook":
|
|||
|
|
return await self.send_webhook(
|
|||
|
|
url=kwargs.get("url", ""),
|
|||
|
|
data=kwargs.get("data", {"message": message})
|
|||
|
|
)
|
|||
|
|
elif type == "dingtalk":
|
|||
|
|
return await self.send_dingtalk(
|
|||
|
|
message=message,
|
|||
|
|
webhook=kwargs.get("webhook")
|
|||
|
|
)
|
|||
|
|
elif type == "slack":
|
|||
|
|
return await self.send_slack(
|
|||
|
|
message=message,
|
|||
|
|
channel=kwargs.get("channel"),
|
|||
|
|
webhook=kwargs.get("webhook")
|
|||
|
|
)
|
|||
|
|
else:
|
|||
|
|
return {
|
|||
|
|
"success": False,
|
|||
|
|
"error": f"Unknown notification type: {type}"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
# 全局通知工具
|
|||
|
|
notification_tool = NotificationTool()
|
|||
|
|
|
|||
|
|
|
|||
|
|
# 便捷函数
|
|||
|
|
async def send_notification(
|
|||
|
|
type: str,
|
|||
|
|
message: str,
|
|||
|
|
**kwargs
|
|||
|
|
) -> Dict[str, Any]:
|
|||
|
|
"""发送通知"""
|
|||
|
|
return await notification_tool.send(type, message, **kwargs)
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def send_email(
|
|||
|
|
to: str,
|
|||
|
|
subject: str,
|
|||
|
|
body: str
|
|||
|
|
) -> Dict[str, Any]:
|
|||
|
|
"""发送邮件"""
|
|||
|
|
return await notification_tool.send_email(to, subject, body)
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def send_webhook(
|
|||
|
|
url: str,
|
|||
|
|
data: Dict[str, Any]
|
|||
|
|
) -> Dict[str, Any]:
|
|||
|
|
"""发送Webhook"""
|
|||
|
|
return await notification_tool.send_webhook(url, data)
|
|||
|
|
|
|||
|
|
|
|||
|
|
# 工具定义
|
|||
|
|
SEND_NOTIFICATION_TOOL = {
|
|||
|
|
"name": "send_notification",
|
|||
|
|
"description": "Send notifications via email, webhook, dingtalk, or slack.",
|
|||
|
|
"parameters": {
|
|||
|
|
"type": "object",
|
|||
|
|
"properties": {
|
|||
|
|
"type": {
|
|||
|
|
"type": "string",
|
|||
|
|
"description": "Notification type: email, webhook, dingtalk, slack",
|
|||
|
|
"enum": ["email", "webhook", "dingtalk", "slack"]
|
|||
|
|
},
|
|||
|
|
"message": {
|
|||
|
|
"type": "string",
|
|||
|
|
"description": "The notification message"
|
|||
|
|
},
|
|||
|
|
"to": {
|
|||
|
|
"type": "string",
|
|||
|
|
"description": "For email: recipient email address"
|
|||
|
|
},
|
|||
|
|
"subject": {
|
|||
|
|
"type": "string",
|
|||
|
|
"description": "For email: email subject"
|
|||
|
|
},
|
|||
|
|
"url": {
|
|||
|
|
"type": "string",
|
|||
|
|
"description": "For webhook: webhook URL"
|
|||
|
|
},
|
|||
|
|
"data": {
|
|||
|
|
"type": "object",
|
|||
|
|
"description": "For webhook: JSON data to send"
|
|||
|
|
},
|
|||
|
|
"webhook": {
|
|||
|
|
"type": "string",
|
|||
|
|
"description": "Custom webhook URL for dingtalk/slack"
|
|||
|
|
},
|
|||
|
|
"channel": {
|
|||
|
|
"type": "string",
|
|||
|
|
"description": "For slack: channel name"
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
"required": ["type", "message"]
|
|||
|
|
}
|
|||
|
|
}
|