生成日报、周报

This commit is contained in:
z66
2025-10-30 09:54:47 +08:00
parent c894e344aa
commit c5a5a0a99c
17 changed files with 3129 additions and 1 deletions
+236
View File
@@ -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)