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"]
|
||
}
|
||
}
|