This commit is contained in:
sansan 2025-10-08 19:38:23 +08:00
parent 4ae1fc2910
commit 0c2f6fa065
3 changed files with 286 additions and 22 deletions

291
main.py
View File

@ -20,7 +20,7 @@ import requests
import yaml import yaml
VERSION = "2.4.0" VERSION = "2.4.1"
# === SMTP邮件配置 === # === SMTP邮件配置 ===
@ -1632,10 +1632,15 @@ def render_html_content(
position: relative; position: relative;
} }
.save-btn { .save-buttons {
position: absolute; position: absolute;
top: 16px; top: 16px;
right: 16px; right: 16px;
display: flex;
gap: 8px;
}
.save-btn {
background: rgba(255, 255, 255, 0.2); background: rgba(255, 255, 255, 0.2);
border: 1px solid rgba(255, 255, 255, 0.3); border: 1px solid rgba(255, 255, 255, 0.3);
color: white; color: white;
@ -1646,6 +1651,7 @@ def render_html_content(
font-weight: 500; font-weight: 500;
transition: all 0.2s ease; transition: all 0.2s ease;
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
white-space: nowrap;
} }
.save-btn:hover { .save-btn:hover {
@ -1658,6 +1664,11 @@ def render_html_content(
transform: translateY(0); transform: translateY(0);
} }
.save-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.header-title { .header-title {
font-size: 22px; font-size: 22px;
font-weight: 700; font-weight: 700;
@ -2001,13 +2012,17 @@ def render_html_content(
.news-item { gap: 8px; } .news-item { gap: 8px; }
.new-item { gap: 8px; } .new-item { gap: 8px; }
.news-number { width: 20px; height: 20px; font-size: 12px; } .news-number { width: 20px; height: 20px; font-size: 12px; }
.save-btn { .save-buttons {
position: static; position: static;
margin-bottom: 16px; margin-bottom: 16px;
display: block; display: flex;
width: fit-content; gap: 8px;
margin-left: auto; justify-content: center;
margin-right: auto; flex-direction: column;
width: 100%;
}
.save-btn {
width: 100%;
} }
} }
</style> </style>
@ -2015,7 +2030,10 @@ def render_html_content(
<body> <body>
<div class="container"> <div class="container">
<div class="header"> <div class="header">
<button class="save-btn" onclick="saveAsImage()">保存为图片</button> <div class="save-buttons">
<button class="save-btn" onclick="saveAsImage()">保存为图片</button>
<button class="save-btn" onclick="saveAsMultipleImages()">分段保存</button>
</div>
<div class="header-title">热点新闻分析</div> <div class="header-title">热点新闻分析</div>
<div class="header-info"> <div class="header-info">
<div class="info-item"> <div class="info-item">
@ -2267,7 +2285,7 @@ def render_html_content(
<script> <script>
async function saveAsImage() { async function saveAsImage() {
const button = document.querySelector('.save-btn'); const button = event.target;
const originalText = button.textContent; const originalText = button.textContent;
try { try {
@ -2279,17 +2297,14 @@ def render_html_content(
await new Promise(resolve => setTimeout(resolve, 200)); await new Promise(resolve => setTimeout(resolve, 200));
// 截图前隐藏按钮 // 截图前隐藏按钮
button.style.visibility = 'hidden'; const buttons = document.querySelector('.save-buttons');
buttons.style.visibility = 'hidden';
// 再次等待确保按钮完全隐藏 // 再次等待确保按钮完全隐藏
await new Promise(resolve => setTimeout(resolve, 100)); await new Promise(resolve => setTimeout(resolve, 100));
const container = document.querySelector('.container'); const container = document.querySelector('.container');
// 获取容器的精确位置和尺寸
const rect = container.getBoundingClientRect();
const computedStyle = window.getComputedStyle(container);
const canvas = await html2canvas(container, { const canvas = await html2canvas(container, {
backgroundColor: '#ffffff', backgroundColor: '#ffffff',
scale: 1.5, scale: 1.5,
@ -2309,7 +2324,7 @@ def render_html_content(
windowHeight: window.innerHeight windowHeight: window.innerHeight
}); });
button.style.visibility = 'visible'; buttons.style.visibility = 'visible';
const link = document.createElement('a'); const link = document.createElement('a');
const now = new Date(); const now = new Date();
@ -2330,7 +2345,239 @@ def render_html_content(
}, 2000); }, 2000);
} catch (error) { } catch (error) {
button.style.visibility = 'visible'; const buttons = document.querySelector('.save-buttons');
buttons.style.visibility = 'visible';
button.textContent = '保存失败';
setTimeout(() => {
button.textContent = originalText;
button.disabled = false;
}, 2000);
}
}
async function saveAsMultipleImages() {
const button = event.target;
const originalText = button.textContent;
const container = document.querySelector('.container');
const scale = 1.5;
const maxHeight = 5000 / scale;
try {
button.textContent = '分析中...';
button.disabled = true;
// 获取所有可能的分割元素
const newsItems = Array.from(container.querySelectorAll('.news-item'));
const wordGroups = Array.from(container.querySelectorAll('.word-group'));
const newSection = container.querySelector('.new-section');
const errorSection = container.querySelector('.error-section');
const header = container.querySelector('.header');
const footer = container.querySelector('.footer');
// 计算元素位置和高度
const containerRect = container.getBoundingClientRect();
const elements = [];
// 添加header作为必须包含的元素
elements.push({
type: 'header',
element: header,
top: 0,
bottom: header.offsetHeight,
height: header.offsetHeight
});
// 添加错误信息如果存在
if (errorSection) {
const rect = errorSection.getBoundingClientRect();
elements.push({
type: 'error',
element: errorSection,
top: rect.top - containerRect.top,
bottom: rect.bottom - containerRect.top,
height: rect.height
});
}
// 按word-group分组处理news-item
wordGroups.forEach(group => {
const groupRect = group.getBoundingClientRect();
const groupNewsItems = group.querySelectorAll('.news-item');
// 添加word-group的header部分
const wordHeader = group.querySelector('.word-header');
if (wordHeader) {
const headerRect = wordHeader.getBoundingClientRect();
elements.push({
type: 'word-header',
element: wordHeader,
parent: group,
top: groupRect.top - containerRect.top,
bottom: headerRect.bottom - containerRect.top,
height: headerRect.height
});
}
// 添加每个news-item
groupNewsItems.forEach(item => {
const rect = item.getBoundingClientRect();
elements.push({
type: 'news-item',
element: item,
parent: group,
top: rect.top - containerRect.top,
bottom: rect.bottom - containerRect.top,
height: rect.height
});
});
});
// 添加新增新闻部分
if (newSection) {
const rect = newSection.getBoundingClientRect();
elements.push({
type: 'new-section',
element: newSection,
top: rect.top - containerRect.top,
bottom: rect.bottom - containerRect.top,
height: rect.height
});
}
// 添加footer
const footerRect = footer.getBoundingClientRect();
elements.push({
type: 'footer',
element: footer,
top: footerRect.top - containerRect.top,
bottom: footerRect.bottom - containerRect.top,
height: footer.offsetHeight
});
// 计算分割点
const segments = [];
let currentSegment = { start: 0, end: 0, height: 0, includeHeader: true };
let headerHeight = header.offsetHeight;
currentSegment.height = headerHeight;
for (let i = 1; i < elements.length; i++) {
const element = elements[i];
const potentialHeight = element.bottom - currentSegment.start;
// 检查是否需要创建新分段
if (potentialHeight > maxHeight && currentSegment.height > headerHeight) {
// 在前一个元素结束处分割
currentSegment.end = elements[i - 1].bottom;
segments.push(currentSegment);
// 开始新分段
currentSegment = {
start: currentSegment.end,
end: 0,
height: element.bottom - currentSegment.end,
includeHeader: false
};
} else {
currentSegment.height = potentialHeight;
currentSegment.end = element.bottom;
}
}
// 添加最后一个分段
if (currentSegment.height > 0) {
currentSegment.end = container.offsetHeight;
segments.push(currentSegment);
}
button.textContent = `生成中 (0/${segments.length})...`;
// 隐藏保存按钮
const buttons = document.querySelector('.save-buttons');
buttons.style.visibility = 'hidden';
// 为每个分段生成图片
const images = [];
for (let i = 0; i < segments.length; i++) {
const segment = segments[i];
button.textContent = `生成中 (${i + 1}/${segments.length})...`;
// 创建临时容器用于截图
const tempContainer = document.createElement('div');
tempContainer.style.cssText = `
position: absolute;
left: -9999px;
top: 0;
width: ${container.offsetWidth}px;
background: white;
`;
tempContainer.className = 'container';
// 克隆容器内容
const clonedContainer = container.cloneNode(true);
// 移除克隆内容中的保存按钮
const clonedButtons = clonedContainer.querySelector('.save-buttons');
if (clonedButtons) {
clonedButtons.style.display = 'none';
}
tempContainer.appendChild(clonedContainer);
document.body.appendChild(tempContainer);
// 等待DOM更新
await new Promise(resolve => setTimeout(resolve, 100));
// 使用html2canvas截取特定区域
const canvas = await html2canvas(clonedContainer, {
backgroundColor: '#ffffff',
scale: scale,
useCORS: true,
allowTaint: false,
imageTimeout: 10000,
logging: false,
width: container.offsetWidth,
height: segment.end - segment.start,
x: 0,
y: segment.start,
windowWidth: window.innerWidth,
windowHeight: window.innerHeight
});
images.push(canvas.toDataURL('image/png', 1.0));
// 清理临时容器
document.body.removeChild(tempContainer);
}
// 恢复按钮显示
buttons.style.visibility = 'visible';
// 下载所有图片
const now = new Date();
const baseFilename = `TrendRadar_热点新闻分析_${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}_${String(now.getHours()).padStart(2, '0')}${String(now.getMinutes()).padStart(2, '0')}`;
for (let i = 0; i < images.length; i++) {
const link = document.createElement('a');
link.download = `${baseFilename}_part${i + 1}.png`;
link.href = images[i];
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// 延迟一下避免浏览器阻止多个下载
await new Promise(resolve => setTimeout(resolve, 100));
}
button.textContent = `已保存 ${segments.length} 张图片!`;
setTimeout(() => {
button.textContent = originalText;
button.disabled = false;
}, 2000);
} catch (error) {
console.error('分段保存失败:', error);
const buttons = document.querySelector('.save-buttons');
buttons.style.visibility = 'visible';
button.textContent = '保存失败'; button.textContent = '保存失败';
setTimeout(() => { setTimeout(() => {
button.textContent = originalText; button.textContent = originalText;
@ -3480,10 +3727,18 @@ def send_to_ntfy(
mode: str = "daily", mode: str = "daily",
) -> bool: ) -> bool:
"""发送到ntfy支持分批发送严格遵守4KB限制""" """发送到ntfy支持分批发送严格遵守4KB限制"""
# 避免 HTTP header 编码问题
report_type_en_map = {
"当日汇总": "Daily Summary",
"当前榜单汇总": "Current Ranking",
"增量更新": "Incremental Update",
}
report_type_en = report_type_en_map.get(report_type, report_type)
headers = { headers = {
"Content-Type": "text/plain; charset=utf-8", "Content-Type": "text/plain; charset=utf-8",
"Markdown": "yes", "Markdown": "yes",
"Title": f"TrendRadar 热点分析报告 - {report_type}", "Title": f"TrendRadar Report - {report_type_en}",
"Priority": "default", "Priority": "default",
"Tags": "newspaper,📰", "Tags": "newspaper,📰",
} }
@ -3526,7 +3781,7 @@ def send_to_ntfy(
batch_header = f"**[第 {i}/{len(batches)} 批次]**\n\n" batch_header = f"**[第 {i}/{len(batches)} 批次]**\n\n"
batch_content = batch_header + batch_content batch_content = batch_header + batch_content
current_headers["Title"] = ( current_headers["Title"] = (
f"TrendRadar 热点分析报告 - {report_type} ({i}/{len(batches)})" f"TrendRadar Report - {report_type_en} ({i}/{len(batches)})"
) )
try: try:

View File

@ -11,7 +11,7 @@
[![GitHub Stars](https://img.shields.io/github/stars/sansan0/TrendRadar?style=flat-square&logo=github&color=yellow)](https://github.com/sansan0/TrendRadar/stargazers) [![GitHub Stars](https://img.shields.io/github/stars/sansan0/TrendRadar?style=flat-square&logo=github&color=yellow)](https://github.com/sansan0/TrendRadar/stargazers)
[![GitHub Forks](https://img.shields.io/github/forks/sansan0/TrendRadar?style=flat-square&logo=github&color=blue)](https://github.com/sansan0/TrendRadar/network/members) [![GitHub Forks](https://img.shields.io/github/forks/sansan0/TrendRadar?style=flat-square&logo=github&color=blue)](https://github.com/sansan0/TrendRadar/network/members)
[![License](https://img.shields.io/badge/license-GPL--3.0-blue.svg?style=flat-square)](LICENSE) [![License](https://img.shields.io/badge/license-GPL--3.0-blue.svg?style=flat-square)](LICENSE)
[![Version](https://img.shields.io/badge/version-v2.4.0-green.svg?style=flat-square)](https://github.com/sansan0/TrendRadar) [![Version](https://img.shields.io/badge/version-v2.4.1-green.svg?style=flat-square)](https://github.com/sansan0/TrendRadar)
[![企业微信通知](https://img.shields.io/badge/企业微信-通知-00D4AA?style=flat-square)](https://work.weixin.qq.com/) [![企业微信通知](https://img.shields.io/badge/企业微信-通知-00D4AA?style=flat-square)](https://work.weixin.qq.com/)
[![Telegram通知](https://img.shields.io/badge/Telegram-通知-00D4AA?style=flat-square)](https://telegram.org/) [![Telegram通知](https://img.shields.io/badge/Telegram-通知-00D4AA?style=flat-square)](https://telegram.org/)
@ -119,6 +119,8 @@ platforms:
name: "华尔街见闻" name: "华尔街见闻"
# 添加更多平台... # 添加更多平台...
``` ```
如果不会看的话,就直接复制他人整理好的部分[平台配置](https://github.com/sansan0/TrendRadar/issues/95)
</details> </details>
### **智能推送策略** ### **智能推送策略**
@ -462,6 +464,15 @@ GitHub 一键 Fork 即可使用,无需编程基础。
- **小版本更新**:从 v2.x 升级到 v2.y, 用本项目的 `main.py` 代码替换你 fork 仓库中的对应文件 - **小版本更新**:从 v2.x 升级到 v2.y, 用本项目的 `main.py` 代码替换你 fork 仓库中的对应文件
- **大版本升级**:从 v1.x 升级到 v2.y, 建议删除现有 fork 后重新 fork这样更省力且避免配置冲突 - **大版本升级**:从 v1.x 升级到 v2.y, 建议删除现有 fork 后重新 fork这样更省力且避免配置冲突
### 2025/10/8 - v2.4.1
- 修复 ntfy 推送编码问题
- 增加 github page 图片分段导出功能
<details>
<summary><strong>👉 历史更新</strong></summary>
### 2025/10/2 - v2.4.0 ### 2025/10/2 - v2.4.0
**新增 ntfy 推送通知** **新增 ntfy 推送通知**
@ -478,8 +489,6 @@ GitHub 一键 Fork 即可使用,无需编程基础。
- **更新提示** - **更新提示**
- 建议使用【大版本更新】 - 建议使用【大版本更新】
<details>
<summary><strong>👉 历史更新</strong></summary>
### 2025/09/26 - v2.3.2 ### 2025/09/26 - v2.3.2

View File

@ -1 +1 @@
2.4.0 2.4.1