修改blogwatcher-daily脚本

This commit is contained in:
2026-08-29 21:18:59 +08:00
parent c0679bb3e8
commit 04001f273a
4 changed files with 230 additions and 166 deletions

2
.gitignore vendored
View File

@@ -2,3 +2,5 @@
.DS_Store .DS_Store
**/.DS_Store **/.DS_Store
__pycache__/

View File

@@ -1,12 +1,12 @@
--- ---
name: blogwatcher-daily name: blogwatcher-daily
description: RSS 订阅监控 + 每日笔记生成。使用 RSSHub + feedparser 抓取 31 个订阅,自动去重并存入 SQLite,新文章追加写入 Markdown 笔记。 description: RSS 订阅监控 + 文章导出。使用 RSSHub + feedparser 抓取 31 个订阅,自动去重并存入 SQLite;可将文章按 txt/markdown/html 三种格式导出。
version: 1.0 version: 1.3
category: custom category: custom
tags: [rss, blog, monitoring, automation] tags: [rss, blog, monitoring, automation, export]
metadata: metadata:
author: Hermes Agent author: Hermes Agent
last_updated: 2025-04-19 last_updated: 2026-08-29
platform: macos, ubuntu platform: macos, ubuntu
custom_skill_path: /Users/weishen/.hermes/skills/custom/ custom_skill_path: /Users/weishen/.hermes/skills/custom/
installation_note: "blogwatcher-daily 脚本在 Mac mini 本地运行(依赖 feedparser)。需要 RSSHub 服务(http://192.168.3.45:1200)访问 YouTube/Bilibili 等被墙源。" installation_note: "blogwatcher-daily 脚本在 Mac mini 本地运行(依赖 feedparser)。需要 RSSHub 服务(http://192.168.3.45:1200)访问 YouTube/Bilibili 等被墙源。"
@@ -14,7 +14,7 @@ metadata:
# Blogwatcher Daily # Blogwatcher Daily
RSS 订阅监控 + 每日笔记生成自动化。 RSS 订阅监控自动化,抓取后去重入库,可按 txt / markdown / html 三种格式导出。
## 依赖 ## 依赖
@@ -22,7 +22,7 @@ RSS 订阅监控 + 每日笔记生成自动化。
pip3 install feedparser pip3 install feedparser
``` ```
> feedparser 是 Python 最成熟的 RSS 解析库,支持 RSS 1.0/2.0/Atom、任意编码、畸形 XML。 > feedparser 是 Python 最成熟的 RSS 解析库,支持 RSS 1.0/2.0/Atom、任意编码、畸形 XML。HTML 导出使用 Python 标准库 `html.escape`,无第三方依赖。
## 实际抓取架构(重要) ## 实际抓取架构(重要)
@@ -48,35 +48,13 @@ curl → 目标网站
- 已有的 RSSHub URL → 直接使用 - 已有的 RSSHub URL → 直接使用
- 其他 → 直接访问原始 URL - 其他 → 直接访问原始 URL
```
YouTube 频道 URL
↓ 脚本自动识别并转为 RSSHub 格式
http://192.168.3.45:1200/youtube/channel/{id}
↓
curl → RSSHub → YouTube
普通 RSS(Engadget, Slashdot 等)
↓ 脚本直接访问(绕过 RSSHub /rss/ 路由)
原始 RSS URL
↓
curl → 目标网站
```
**关键发现**:RSSHub 的 `/rss/{url}` 路由不稳定(返回 RSSHub 欢迎页),
因此普通 RSS 源直接访问,不走 RSSHub 代理。
脚本内部 `build_fetch_url()` 根据 URL 类型自动选择路由:
- YouTube → RSSHub
- 已有的 RSSHub URL → 直接使用
- 其他 → 直接访问原始 URL
## 目录结构 ## 目录结构
``` ```
~/.hermes/skills/custom/blogwatcher-daily/ ~/.hermes/skills/custom/blogwatcher-daily/
├── SKILL.md # 本文件 ├── SKILL.md # 本文件
├── scripts/ ├── scripts/
│ └── blogwatcher-daily.py # 主脚本 │ └── blogwatcher-daily.py # 扫描入库 + 订阅管理 + 文章导出(单文件)
├── subscriptions.txt # 订阅列表(name|URL) ├── subscriptions.txt # 订阅列表(name|URL)
└── blogwatcher.db # SQLite 数据库(自动创建) └── blogwatcher.db # SQLite 数据库(自动创建)
``` ```
@@ -90,7 +68,7 @@ python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py
``` ```
- 扫描所有订阅,新增文章存入数据库 - 扫描所有订阅,新增文章存入数据库
- 生成今日笔记:`~/Workspace/nexus/ishenwei/blogwatcher/YYYY-MM-DD.md` - 控制台打印每个订阅新增文章数量及总计
### 添加订阅 ### 添加订阅
@@ -115,24 +93,13 @@ python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py --list python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py --list
``` ```
### 仅扫描(不写文件)
```bash
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py --scan-only
```
### 强制回扫(`--all`) ### 强制回扫(`--all`)
```bash ```bash
# 强制抓取每频道10篇(忽略已读状态),写入独立文件 # 强制抓取每频道10篇(忽略已读状态)
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py --all python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py --all
# 测试模式:只打印,不写文件
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py --scan-only --all
``` ```
- 输出文件:`~/Workspace/nexus/ishenwei/blogwatcher/all-YYYY-MM-DD.md`(**覆盖模式**,每次运行覆盖)
- **不混入**每日报告 `YYYY-MM-DD.md`
- 每频道最多10篇 - 每频道最多10篇
- 用途:历史内容回扫、测试订阅状态 - 用途:历史内容回扫、测试订阅状态
@@ -142,6 +109,51 @@ python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py -
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py --mark-read python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py --mark-read
``` ```
### 导出文章(`--export`)
从 SQLite 里读取文章并输出到 stdout,格式通过 `--export {txt,markdown,html}` 选择:
```bash
# 今天所有文章 → Markdown
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py --export markdown
# 指定日期 → HTML
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \
--export html --date 2026-08-28 > digest.html
# 全库最新 20 篇 → 纯文本
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \
--export txt --limit 20
```
**过滤规则**(`--date` 与 `--limit` 的组合语义)
| 参数组合 | 行为 |
|---------|------|
| `--export FMT`(默认) | 今天(本地时区)的所有文章 |
| `--export FMT --date X` | 指定日期 X 的所有文章 |
| `--export FMT --limit N` | 忽略日期,取**全库最新 N 篇** |
| `--export FMT --date X --limit N` | 日期 X 的最新 N 篇 |
**参数速查**
| 参数 | 说明 |
|------|------|
| `--export {txt,markdown,html}` | 触发导出模式并指定格式 |
| `--date YYYY-MM-DD` | 目标日期(本地时区,默认今天) |
| `--limit N` | 文章数上限;单独使用时忽略日期 |
**输出去向**
- 正文 → **stdout**(可直接 `> file.md` 重定向)
- 进度 → **stderr**(`📊 N 篇文章 → FMT`)
**格式细节**
- `txt`:分频道分组,纯文本无标记,适合终端查看或管道
- `markdown`:`## 【频道】` + `- [title](link)` + 描述引用块
- `html`:完整 `<!DOCTYPE html>` 单文件,内嵌简洁 CSS,标题/URL/描述全部经 `html.escape` 转义(防 XSS)
## 添加订阅示例 ## 添加订阅示例
### YouTube 频道 ### YouTube 频道
@@ -182,13 +194,12 @@ python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \
| 配置项 | 默认值 | | 配置项 | 默认值 |
|--------|--------| |--------|--------|
| RSSHub 地址 | `http://192.168.3.45:1200`(可设置 `RSSHUB_URL` 环境变量覆盖) | | RSSHub 地址 | `http://192.168.3.45:1200`(可设置 `RSSHUB_URL` 环境变量覆盖) |
| 笔记输出目录 | `~/Workspace/nexus/ishenwei/blogwatcher/` |
| 数据库 | `~/.hermes/skills/custom/blogwatcher-daily/blogwatcher.db` | | 数据库 | `~/.hermes/skills/custom/blogwatcher-daily/blogwatcher.db` |
| 订阅列表 | `~/.hermes/skills/custom/blogwatcher-daily/subscriptions.txt` | | 订阅列表 | `~/.hermes/skills/custom/blogwatcher-daily/subscriptions.txt` |
## Cron Job 设置 ## Cron Job 设置
推荐每天早上 6:00 自动执行: 推荐每天早上 6:00 自动扫描:
```bash ```bash
cronjob --create \ cronjob --create \
@@ -200,13 +211,22 @@ cronjob --create \
执行步骤: 执行步骤:
1. 加载 blogwatcher-daily 技能 1. 加载 blogwatcher-daily 技能
2. 运行:python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py 2. 运行扫描:python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py
3. 检查输出,确认写入的笔记文件路径 3. 汇报本次新增文章数量"
4. 如果有新文章,简短汇总发给我" ```
如需**扫描 + 落盘 HTML 摘要**(例如放进 Obsidian vault 或自建 dashboard),可用原生 crontab 串起来:
```cron
0 6 * * * python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py && \
python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \
--export html > ~/blogwatcher/$(date +\%Y-\%m-\%d).html
``` ```
## 数据流程 ## 数据流程
### 扫描入库(默认命令)
1. **加载订阅**:从 `subscriptions.txt` 读取所有 name|URL 对 1. **加载订阅**:从 `subscriptions.txt` 读取所有 name|URL 对
2. **URL 路由**:`build_fetch_url()` 自动判断: 2. **URL 路由**:`build_fetch_url()` 自动判断:
- YouTube → 转为 `http://192.168.3.45:1200/youtube/channel/{id}` - YouTube → 转为 `http://192.168.3.45:1200/youtube/channel/{id}`
@@ -216,9 +236,14 @@ cronjob --create \
4. **解析**:feedparser 提取 title、link、description、pub_date 4. **解析**:feedparser 提取 title、link、description、pub_date
5. **去重**:按 link 去重,已存在则跳过 5. **去重**:按 link 去重,已存在则跳过
6. **存储**:新文章存入 SQLite 6. **存储**:新文章存入 SQLite
7. **输出**:**追加写入** Markdown 笔记(同一文件多次扫描不会覆盖,仅追加新链接) 7. **输出**:控制台打印每个订阅新增数量与总计(不生成文件)
- 读取已有文件,用正则提取现有链接 → 去重
- 新文章追加 `## 📦 新增 N 篇` 分隔块 ### 导出(`--export`)
1. **查询**:`fetch_articles()` 按 `date(fetched_at, 'localtime')` 过滤 + 可选 `LIMIT N`
2. **分组**:`_group_by_channel()` 按 `channel_title` 分桶
3. **格式化**:`FORMATTERS[fmt]` 分发到 `format_txt` / `format_markdown` / `format_html`
4. **输出**:正文写 stdout,`📊 N 篇文章 → FMT` 进度写 stderr
## 已知问题 ## 已知问题
@@ -236,4 +261,5 @@ cronjob --create \
- 每次最多取每源 10 篇(TED Talks 等除外,取 20 篇) - 每次最多取每源 10 篇(TED Talks 等除外,取 20 篇)
- 数据库自动创建,无需手动初始化 - 数据库自动创建,无需手动初始化
- OPML 文件可用 Python 解析后批量导入(参考 /wiki-ingest 流程中的 OPML 解析代码) - OPML 文件可用 Python 解析后批量导入(参考 /wiki-ingest 流程中的 OPML 解析代码)
- **Markdown 追加模式**:同一天多次扫描不会覆盖旧内容,仅追加新链接(去重基于 URL) - 导出的"今天"按**本地时区**(SQLite `date(fetched_at, 'localtime')`),不是 UTC
- HTML 导出使用 `html.escape` 转义标题/链接/描述,可安全嵌入网页;CSS 内嵌,单文件独立可用

View File

@@ -4,20 +4,23 @@ Blogwatcher Daily - RSS Feed 监控脚本
放在 ~/.hermes/skills/research/blogwatcher-daily/scripts/blogwatcher-daily.py 放在 ~/.hermes/skills/research/blogwatcher-daily/scripts/blogwatcher-daily.py
Usage: Usage:
python3 blogwatcher-daily.py # 扫描所有订阅,写入今日笔记 python3 blogwatcher-daily.py # 扫描所有订阅,新增文章存入数据库
python3 blogwatcher-daily.py --list # 列出所有订阅 python3 blogwatcher-daily.py --list # 列出所有订阅
python3 blogwatcher-daily.py --add "频道名" "RSS URL" # 添加订阅 python3 blogwatcher-daily.py --add "频道名" "RSS URL" # 添加订阅
python3 blogwatcher-daily.py --scan-only # 只扫描不写入文件 python3 blogwatcher-daily.py --mark-read # 标记所有文章为已读
python3 blogwatcher-daily.py --mark-read # 标记所有文章为已读 python3 blogwatcher-daily.py --export markdown # 把今天的文章导出为 Markdown
python3 blogwatcher-daily.py --export html --date 2026-08-28
python3 blogwatcher-daily.py --export txt --limit 20
""" """
import os import os
import re import re
import sys
import sqlite3 import sqlite3
import argparse import argparse
import feedparser import feedparser
from datetime import datetime from datetime import date
from html import unescape from html import escape, unescape
from urllib.request import Request, urlopen from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError from urllib.error import URLError, HTTPError
import ssl import ssl
@@ -27,7 +30,6 @@ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
SKILL_DIR = os.path.dirname(SCRIPT_DIR) # .../research/blogwatcher-daily SKILL_DIR = os.path.dirname(SCRIPT_DIR) # .../research/blogwatcher-daily
DB_PATH = os.path.join(SKILL_DIR, "blogwatcher.db") DB_PATH = os.path.join(SKILL_DIR, "blogwatcher.db")
SUBSCRIPTIONS_FILE = os.path.join(SKILL_DIR, "subscriptions.txt") SUBSCRIPTIONS_FILE = os.path.join(SKILL_DIR, "subscriptions.txt")
OUTPUT_DIR = os.path.expanduser("~/Workspace/nexus/ishenwei/blogwatcher")
# RSSHub 基础地址(可配置) # RSSHub 基础地址(可配置)
RSSHUB_BASE = os.environ.get("RSSHUB_URL", "http://192.168.3.45:1200") RSSHUB_BASE = os.environ.get("RSSHUB_URL", "http://192.168.3.45:1200")
@@ -235,99 +237,10 @@ def list_subscriptions():
print(f" [{i}] {sub['name']}") print(f" [{i}] {sub['name']}")
print(f" {sub['url']}\n") print(f" {sub['url']}\n")
# ========== Markdown 输出 ==========
def generate_markdown(results, date_str=None):
"""生成 Markdown 格式"""
if date_str is None:
date_str = datetime.now().strftime("%Y-%m-%d")
md = f"# Blogwatcher Daily - {date_str}\n\n"
md += f"_生成时间: {datetime.now().strftime('%H:%M:%S')}_\n\n"
md += "---\n\n"
if not results:
md += "_今日无新文章_\n"
return md
# 按频道分组
channels = {}
for item in results:
ch = item['channel']
if ch not in channels:
channels[ch] = []
channels[ch].append(item)
for channel, items in channels.items():
md += f"## 【{channel}】\n\n"
for item in items:
md += f"- [{item['title']}]({item['link']})\n"
if item['description']:
md += f" {item['description'][:150]}...\n"
md += "\n"
md += "---\n\n"
return md
def save_daily_report(new_results, date_str=None):
"""保存每日报告(追加模式,避免覆盖)"""
if date_str is None:
date_str = datetime.now().strftime("%Y-%m-%d")
os.makedirs(OUTPUT_DIR, exist_ok=True)
filepath = os.path.join(OUTPUT_DIR, f"{date_str}.md")
# 读取已有内容,提取已有链接避免重复
existing_links = set()
if os.path.exists(filepath):
with open(filepath, 'r') as f:
existing_content = f.read()
# 简单提取已有链接(用于去重),去掉末尾的 ) 等标点
import re as _re
raw_links = _re.findall(r'https?://\S+', existing_content)
existing_links = set()
for l in raw_links:
# 去掉末尾的 ) 等非 URL 字符
while l and l[-1] in '),;:':
l = l[:-1]
existing_links.add(l)
else:
existing_content = ""
# 如果没有新结果,且文件已存在,直接返回
if not new_results and existing_content:
return filepath
# 生成新内容的 header
new_md = ""
new_articles = [a for a in new_results if a['link'] not in existing_links]
if new_articles:
new_md += f"\n## 📦 新增 {len(new_articles)} 篇 ({datetime.now().strftime('%H:%M:%S')})\n\n"
channels = {}
for item in new_articles:
ch = item['channel']
if ch not in channels:
channels[ch] = []
channels[ch].append(item)
for channel, items in channels.items():
new_md += f"### 【{channel}】\n\n"
for item in items:
new_md += f"- [{item['title']}]({item['link']})\n"
if item['description']:
new_md += f" {item['description'][:150]}...\n"
new_md += "\n"
# 追加写入
with open(filepath, 'a') as f:
f.write(new_md)
return filepath
# ========== 主流程 ========== # ========== 主流程 ==========
def scan_all(force_all=False, write_file=True): def scan_all(force_all=False):
"""扫描所有订阅 """扫描所有订阅
force_all: True 则忽略已读状态,每个频道强制抓10篇 force_all: True 则忽略已读状态,每个频道强制抓10篇
write_file: True 则写入文件
""" """
print("=" * 50) print("=" * 50)
print("Blogwatcher Daily Scan") print("Blogwatcher Daily Scan")
@@ -375,32 +288,149 @@ def scan_all(force_all=False, write_file=True):
print("-" * 50) print("-" * 50)
print(f"📊 扫描完成: 共发现 {new_count} 篇新文章\n") print(f"📊 扫描完成: 共发现 {new_count} 篇新文章\n")
if all_new_articles and write_file: if not all_new_articles:
if force_all:
# --all 模式:写入独立文件(不追加日常报告)
md = generate_markdown(all_new_articles)
filepath = os.path.join(OUTPUT_DIR, f"all-{datetime.now().strftime('%Y-%m-%d')}.md")
with open(filepath, 'w') as f:
f.write(md)
print(f"📝 已写入(force-all 模式): {filepath}")
else:
filepath = save_daily_report(all_new_articles)
print(f"📝 已写入: {filepath}")
elif not all_new_articles:
print("📭 今日无新文章") print("📭 今日无新文章")
return all_new_articles return all_new_articles
# ========== 导出 ==========
def fetch_articles(target_date=None, limit=None):
"""从 articles 表读取;可选按本地日期过滤(date(fetched_at,'localtime'))+ 可选限制数量"""
if not os.path.exists(DB_PATH):
print(f"❌ 数据库不存在: {DB_PATH}", file=sys.stderr)
return []
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
where = ""
params = []
if target_date:
where = "WHERE date(fetched_at, 'localtime') = ?"
params.append(target_date)
query = f"""
SELECT channel_title, title, link, description, pub_date, fetched_at
FROM articles
{where}
ORDER BY fetched_at DESC, channel_title
"""
if limit:
query += " LIMIT ?"
params.append(limit)
cursor = conn.execute(query, params)
rows = [dict(r) for r in cursor.fetchall()]
conn.close()
return rows
def _group_by_channel(articles):
grouped = {}
for a in articles:
ch = a["channel_title"] or "Unknown"
grouped.setdefault(ch, []).append(a)
return grouped
def format_txt(articles, label):
lines = [f"Blogwatcher Daily — {label}", "=" * 50, ""]
if not articles:
lines.append("(无文章)")
return "\n".join(lines) + "\n"
lines.append(f"共 {len(articles)} 篇文章")
lines.append("")
for channel, items in _group_by_channel(articles).items():
lines.append(f"[{channel}]")
for it in items:
title = (it["title"] or "Untitled").strip()
link = (it["link"] or "").strip()
lines.append(f" - {title}")
if link:
lines.append(f" {link}")
desc = (it["description"] or "").strip()
if desc:
lines.append(f" {desc[:200]}")
lines.append("")
return "\n".join(lines) + "\n"
def format_markdown(articles, label):
md = f"# Blogwatcher Daily — {label}\n\n"
if not articles:
return md + "_无文章_\n"
md += f"共 **{len(articles)}** 篇文章。\n\n---\n\n"
for channel, items in _group_by_channel(articles).items():
md += f"## 【{channel}】\n\n"
for it in items:
title = (it["title"] or "Untitled").strip()
link = (it["link"] or "").strip()
md += f"- [{title}]({link})\n"
desc = (it["description"] or "").strip()
if desc:
md += f" > {desc[:200]}\n"
md += "\n"
return md
def format_html(articles, label):
parts = [
"<!DOCTYPE html>",
'<html lang="zh-CN"><head><meta charset="utf-8">',
f"<title>Blogwatcher Daily — {escape(label)}</title>",
"<style>body{font-family:-apple-system,sans-serif;max-width:800px;"
"margin:2em auto;padding:0 1em;line-height:1.5}"
"h2{border-bottom:1px solid #ddd;padding-bottom:0.3em;margin-top:2em}"
"blockquote{color:#555;border-left:3px solid #ccc;margin:0.3em 0 0.6em;"
"padding:0.2em 1em}"
"li{margin-bottom:0.5em}</style>",
"</head><body>",
f"<h1>Blogwatcher Daily — {escape(label)}</h1>",
]
if not articles:
parts.append("<p><em>无文章</em></p></body></html>")
return "\n".join(parts) + "\n"
parts.append(f"<p>共 <strong>{len(articles)}</strong> 篇文章。</p><hr>")
for channel, items in _group_by_channel(articles).items():
parts.append(f"<h2>【{escape(channel)}】</h2>\n<ul>")
for it in items:
title = (it["title"] or "Untitled").strip()
link = (it["link"] or "").strip()
parts.append(f' <li><a href="{escape(link)}">{escape(title)}</a>')
desc = (it["description"] or "").strip()
if desc:
parts.append(f" <blockquote>{escape(desc[:200])}</blockquote>")
parts.append(" </li>")
parts.append("</ul>")
parts.append("</body></html>")
return "\n".join(parts) + "\n"
FORMATTERS = {
"txt": format_txt,
"markdown": format_markdown,
"html": format_html,
}
def export_articles(fmt, target_date, limit):
articles = fetch_articles(target_date, limit)
if target_date:
label = target_date
elif limit:
label = f"最新 {limit} 篇"
else:
label = "全部"
print(f"📊 {len(articles)} 篇文章 → {fmt}", file=sys.stderr)
sys.stdout.write(FORMATTERS[fmt](articles, label))
# ========== CLI ========== # ========== CLI ==========
def main(): def main():
parser = argparse.ArgumentParser(description='Blogwatcher Daily RSS 监控脚本') parser = argparse.ArgumentParser(description='Blogwatcher Daily RSS 监控脚本')
parser.add_argument('--list', '-l', action='store_true', help='列出所有订阅') parser.add_argument('--list', '-l', action='store_true', help='列出所有订阅')
parser.add_argument('--add', nargs=2, metavar=('NAME', 'URL'), help='添加订阅') parser.add_argument('--add', nargs=2, metavar=('NAME', 'URL'), help='添加订阅')
parser.add_argument('--scan-only', action='store_true', help='仅扫描不写入文件')
parser.add_argument('--mark-read', action='store_true', help='标记所有为已读') parser.add_argument('--mark-read', action='store_true', help='标记所有为已读')
parser.add_argument('--date', '-d', help='指定日期 (YYYY-MM-DD)')
parser.add_argument('--rsshub', help='设置 RSSHub 地址') parser.add_argument('--rsshub', help='设置 RSSHub 地址')
parser.add_argument('--all', action='store_true', help='忽略已读状态,强制抓取每频道最新10篇(写入 all-YYYY-MM-DD.md,不追加日常报告)') parser.add_argument('--all', action='store_true', help='忽略已读状态,强制抓取每频道最新10篇')
parser.add_argument('--export', choices=['txt', 'markdown', 'html'],
help='导出文章为指定格式,写到 stdout(可搭配 --date/--limit)')
parser.add_argument('--date', help='导出的目标日期 YYYY-MM-DD(默认今天,仅 --export 有效)')
parser.add_argument('--limit', type=int,
help='导出的文章数上限;单独使用时忽略日期取全库最新 N 篇(仅 --export 有效)')
args = parser.parse_args() args = parser.parse_args()
@@ -426,11 +456,17 @@ def main():
conn.close() conn.close()
print("✅ 已标记所有文章为已读") print("✅ 已标记所有文章为已读")
elif args.scan_only: elif args.export:
scan_all(force_all=args.all, write_file=False) if args.date:
target_date = args.date
elif args.limit:
target_date = None
else:
target_date = date.today().isoformat()
export_articles(args.export, target_date, args.limit)
else: else:
scan_all(force_all=args.all, write_file=True) scan_all(force_all=args.all)
if __name__ == "__main__": if __name__ == "__main__":
main() main()