生成日报、周报
This commit is contained in:
@@ -0,0 +1,236 @@
|
||||
"""
|
||||
钉钉Webhook推送工具
|
||||
支持推送Markdown格式消息到钉钉群
|
||||
"""
|
||||
import requests
|
||||
import json
|
||||
from typing import Optional, Dict, Any
|
||||
from loguru import logger
|
||||
|
||||
|
||||
class DingTalkWebhook:
|
||||
"""钉钉Webhook推送工具"""
|
||||
|
||||
def __init__(self, webhook_url: str):
|
||||
"""
|
||||
初始化钉钉Webhook
|
||||
|
||||
Args:
|
||||
webhook_url: 钉钉机器人Webhook地址
|
||||
"""
|
||||
self.webhook_url = webhook_url
|
||||
self.logger = logger.bind(module="DingTalkWebhook")
|
||||
|
||||
def send_text(self, content: str, at_mobiles: list = None, at_all: bool = False) -> bool:
|
||||
"""
|
||||
发送文本消息
|
||||
|
||||
Args:
|
||||
content: 消息内容
|
||||
at_mobiles: 要@的手机号列表
|
||||
at_all: 是否@所有人
|
||||
|
||||
Returns:
|
||||
是否发送成功
|
||||
"""
|
||||
data = {
|
||||
"msgtype": "text",
|
||||
"text": {
|
||||
"content": content
|
||||
}
|
||||
}
|
||||
|
||||
if at_mobiles or at_all:
|
||||
data["at"] = {}
|
||||
if at_mobiles:
|
||||
data["at"]["atMobiles"] = at_mobiles
|
||||
if at_all:
|
||||
data["at"]["isAtAll"] = True
|
||||
|
||||
return self._send(data)
|
||||
|
||||
def send_markdown(self, title: str, text: str, at_mobiles: list = None, at_all: bool = False) -> bool:
|
||||
"""
|
||||
发送Markdown消息
|
||||
|
||||
Args:
|
||||
title: 消息标题
|
||||
text: Markdown内容(钉钉支持的格式)
|
||||
at_mobiles: 要@的手机号列表
|
||||
at_all: 是否@所有人
|
||||
|
||||
Returns:
|
||||
是否发送成功
|
||||
"""
|
||||
# 钉钉markdown消息有长度限制,需要截断
|
||||
max_length = 5000
|
||||
if len(text) > max_length:
|
||||
text = text[:max_length - 100] + "\n\n...(内容已截断,完整内容请查看附件)"
|
||||
self.logger.warning(f"Markdown内容过长,已截断至{max_length}字符")
|
||||
|
||||
data = {
|
||||
"msgtype": "markdown",
|
||||
"markdown": {
|
||||
"title": title,
|
||||
"text": text
|
||||
}
|
||||
}
|
||||
|
||||
if at_mobiles or at_all:
|
||||
data["at"] = {}
|
||||
if at_mobiles:
|
||||
data["at"]["atMobiles"] = at_mobiles
|
||||
if at_all:
|
||||
data["at"]["isAtAll"] = True
|
||||
|
||||
return self._send(data)
|
||||
|
||||
def send_markdown_from_file(self, title: str, markdown_file: str,
|
||||
max_length: int = 5000, at_mobiles: list = None,
|
||||
at_all: bool = False) -> bool:
|
||||
"""
|
||||
从Markdown文件发送消息
|
||||
|
||||
Args:
|
||||
title: 消息标题
|
||||
markdown_file: Markdown文件路径
|
||||
max_length: 最大长度限制(默认5000字符)
|
||||
at_mobiles: 要@的手机号列表
|
||||
at_all: 是否@所有人
|
||||
|
||||
Returns:
|
||||
是否发送成功
|
||||
"""
|
||||
try:
|
||||
with open(markdown_file, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# 转换为钉钉markdown格式(简化一些不支持的语法)
|
||||
text = self._convert_to_dingtalk_markdown(content, max_length)
|
||||
|
||||
return self.send_markdown(title, text, at_mobiles, at_all)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"读取Markdown文件失败: {str(e)}", exc_info=True)
|
||||
return False
|
||||
|
||||
def _convert_to_dingtalk_markdown(self, content: str, max_length: int = 5000) -> str:
|
||||
"""
|
||||
将标准Markdown转换为钉钉支持的格式
|
||||
|
||||
钉钉Markdown支持的语法:
|
||||
- 标题:# ## ###
|
||||
- 加粗:**text**
|
||||
- 链接:[text](url)
|
||||
- 列表:- 或 1.
|
||||
- 引用:>
|
||||
- 代码:`code`
|
||||
- 换行:两个换行符
|
||||
|
||||
不支持:
|
||||
- 表格(需要转换为文本)
|
||||
- HTML标签
|
||||
- 复杂嵌套
|
||||
"""
|
||||
# 如果内容太长,截断并添加提示
|
||||
if len(content) > max_length:
|
||||
content = content[:max_length - 200] + "\n\n---\n\n**提示**: 内容已截断,完整内容请查看报告文件。"
|
||||
|
||||
# 钉钉markdown基本兼容标准markdown,但需要清理一些不支持的语法
|
||||
# 保留基本格式即可
|
||||
text = content
|
||||
|
||||
return text
|
||||
|
||||
def _send(self, data: Dict[str, Any]) -> bool:
|
||||
"""
|
||||
发送消息到钉钉
|
||||
|
||||
Args:
|
||||
data: 消息数据
|
||||
|
||||
Returns:
|
||||
是否发送成功
|
||||
"""
|
||||
try:
|
||||
headers = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
self.webhook_url,
|
||||
headers=headers,
|
||||
data=json.dumps(data),
|
||||
timeout=10
|
||||
)
|
||||
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
|
||||
if result.get('errcode') == 0:
|
||||
self.logger.info("消息发送成功")
|
||||
return True
|
||||
else:
|
||||
self.logger.error(f"消息发送失败: {result.get('errmsg', '未知错误')}")
|
||||
return False
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
self.logger.error(f"发送消息请求失败: {str(e)}", exc_info=True)
|
||||
return False
|
||||
except Exception as e:
|
||||
self.logger.error(f"发送消息失败: {str(e)}", exc_info=True)
|
||||
return False
|
||||
|
||||
def send_report(self, title: str, markdown_content: str, markdown_file: str = None) -> bool:
|
||||
"""
|
||||
发送报告消息(优化版本,自动处理长内容)
|
||||
|
||||
Args:
|
||||
title: 消息标题
|
||||
markdown_content: Markdown内容
|
||||
markdown_file: Markdown文件路径(可选,用于提示)
|
||||
|
||||
Returns:
|
||||
是否发送成功
|
||||
"""
|
||||
# 钉钉markdown有长度限制,需要截断或分段
|
||||
max_length = 4500 # 留一些余量
|
||||
|
||||
if len(markdown_content) <= max_length:
|
||||
# 内容不长,直接发送
|
||||
text = markdown_content
|
||||
if markdown_file:
|
||||
text += f"\n\n---\n\n**完整报告**: 已保存到 `{markdown_file}`"
|
||||
return self.send_markdown(title, text)
|
||||
else:
|
||||
# 内容太长,发送摘要
|
||||
# 提取关键部分(标题、统计、前几条新闻)
|
||||
lines = markdown_content.split('\n')
|
||||
summary_lines = []
|
||||
news_count = 0
|
||||
max_news_items = 5
|
||||
|
||||
for line in lines:
|
||||
summary_lines.append(line)
|
||||
# 计算已添加的新闻条目数
|
||||
if line.startswith('### ') and news_count < max_news_items:
|
||||
news_count += 1
|
||||
# 添加接下来的几行(摘要、链接等)
|
||||
continue
|
||||
elif news_count >= max_news_items and line.startswith('### '):
|
||||
# 达到最大条目数,停止添加
|
||||
break
|
||||
|
||||
summary = '\n'.join(summary_lines)
|
||||
|
||||
# 如果还有更多内容,添加提示
|
||||
if len(markdown_content) > len(summary):
|
||||
remaining_count = markdown_content.count('### ') - news_count
|
||||
summary += f"\n\n---\n\n**提示**: 报告内容较长,已显示前{news_count}条新闻。"
|
||||
if remaining_count > 0:
|
||||
summary += f" 还有{remaining_count}条新闻未显示。"
|
||||
if markdown_file:
|
||||
summary += f"\n\n**完整报告**: 已保存到 `{markdown_file}`"
|
||||
|
||||
return self.send_markdown(title, summary)
|
||||
|
||||
Reference in New Issue
Block a user