mirror of
https://gitee.com/houhuan/TrendRadar.git
synced 2025-12-21 14:27:15 +08:00
v2.4.1
This commit is contained in:
parent
4ae1fc2910
commit
0c2f6fa065
289
main.py
289
main.py
@ -20,7 +20,7 @@ import requests
|
||||
import yaml
|
||||
|
||||
|
||||
VERSION = "2.4.0"
|
||||
VERSION = "2.4.1"
|
||||
|
||||
|
||||
# === SMTP邮件配置 ===
|
||||
@ -1632,10 +1632,15 @@ def render_html_content(
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.save-btn {
|
||||
.save-buttons {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.save-btn {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
color: white;
|
||||
@ -1646,6 +1651,7 @@ def render_html_content(
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
backdrop-filter: blur(10px);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.save-btn:hover {
|
||||
@ -1658,6 +1664,11 @@ def render_html_content(
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.save-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
@ -2001,13 +2012,17 @@ def render_html_content(
|
||||
.news-item { gap: 8px; }
|
||||
.new-item { gap: 8px; }
|
||||
.news-number { width: 20px; height: 20px; font-size: 12px; }
|
||||
.save-btn {
|
||||
.save-buttons {
|
||||
position: static;
|
||||
margin-bottom: 16px;
|
||||
display: block;
|
||||
width: fit-content;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
.save-btn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -2015,7 +2030,10 @@ def render_html_content(
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<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-info">
|
||||
<div class="info-item">
|
||||
@ -2267,7 +2285,7 @@ def render_html_content(
|
||||
|
||||
<script>
|
||||
async function saveAsImage() {
|
||||
const button = document.querySelector('.save-btn');
|
||||
const button = event.target;
|
||||
const originalText = button.textContent;
|
||||
|
||||
try {
|
||||
@ -2279,17 +2297,14 @@ def render_html_content(
|
||||
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));
|
||||
|
||||
const container = document.querySelector('.container');
|
||||
|
||||
// 获取容器的精确位置和尺寸
|
||||
const rect = container.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(container);
|
||||
|
||||
const canvas = await html2canvas(container, {
|
||||
backgroundColor: '#ffffff',
|
||||
scale: 1.5,
|
||||
@ -2309,7 +2324,7 @@ def render_html_content(
|
||||
windowHeight: window.innerHeight
|
||||
});
|
||||
|
||||
button.style.visibility = 'visible';
|
||||
buttons.style.visibility = 'visible';
|
||||
|
||||
const link = document.createElement('a');
|
||||
const now = new Date();
|
||||
@ -2330,7 +2345,239 @@ def render_html_content(
|
||||
}, 2000);
|
||||
|
||||
} 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 = '保存失败';
|
||||
setTimeout(() => {
|
||||
button.textContent = originalText;
|
||||
@ -3480,10 +3727,18 @@ def send_to_ntfy(
|
||||
mode: str = "daily",
|
||||
) -> bool:
|
||||
"""发送到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 = {
|
||||
"Content-Type": "text/plain; charset=utf-8",
|
||||
"Markdown": "yes",
|
||||
"Title": f"TrendRadar 热点分析报告 - {report_type}",
|
||||
"Title": f"TrendRadar Report - {report_type_en}",
|
||||
"Priority": "default",
|
||||
"Tags": "newspaper,📰",
|
||||
}
|
||||
@ -3526,7 +3781,7 @@ def send_to_ntfy(
|
||||
batch_header = f"**[第 {i}/{len(batches)} 批次]**\n\n"
|
||||
batch_content = batch_header + batch_content
|
||||
current_headers["Title"] = (
|
||||
f"TrendRadar 热点分析报告 - {report_type} ({i}/{len(batches)})"
|
||||
f"TrendRadar Report - {report_type_en} ({i}/{len(batches)})"
|
||||
)
|
||||
|
||||
try:
|
||||
|
||||
15
readme.md
15
readme.md
@ -11,7 +11,7 @@
|
||||
[](https://github.com/sansan0/TrendRadar/stargazers)
|
||||
[](https://github.com/sansan0/TrendRadar/network/members)
|
||||
[](LICENSE)
|
||||
[](https://github.com/sansan0/TrendRadar)
|
||||
[](https://github.com/sansan0/TrendRadar)
|
||||
|
||||
[](https://work.weixin.qq.com/)
|
||||
[](https://telegram.org/)
|
||||
@ -119,6 +119,8 @@ platforms:
|
||||
name: "华尔街见闻"
|
||||
# 添加更多平台...
|
||||
```
|
||||
如果不会看的话,就直接复制他人整理好的部分[平台配置](https://github.com/sansan0/TrendRadar/issues/95)
|
||||
|
||||
</details>
|
||||
|
||||
### **智能推送策略**
|
||||
@ -462,6 +464,15 @@ GitHub 一键 Fork 即可使用,无需编程基础。
|
||||
- **小版本更新**:从 v2.x 升级到 v2.y, 用本项目的 `main.py` 代码替换你 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
|
||||
|
||||
**新增 ntfy 推送通知**
|
||||
@ -478,8 +489,6 @@ GitHub 一键 Fork 即可使用,无需编程基础。
|
||||
- **更新提示**:
|
||||
- 建议使用【大版本更新】
|
||||
|
||||
<details>
|
||||
<summary><strong>👉 历史更新</strong></summary>
|
||||
|
||||
### 2025/09/26 - v2.3.2
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user