From 8f5043759f91308a582deda9bb1fa078404bbabf Mon Sep 17 00:00:00 2001 From: admin Date: Sun, 30 Aug 2026 15:50:07 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=8C=89=E8=AE=A2=E9=98=85?= =?UTF-8?q?=E5=AF=BC=E5=87=BA=E6=96=87=E7=AB=A0=E7=9A=84=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- blogwatcher-daily/SKILL.md | 20 ++++++-- .../scripts/blogwatcher-daily.py | 48 ++++++++++++++----- 2 files changed, 52 insertions(+), 16 deletions(-) diff --git a/blogwatcher-daily/SKILL.md b/blogwatcher-daily/SKILL.md index b4e67f1..3ca8da1 100644 --- a/blogwatcher-daily/SKILL.md +++ b/blogwatcher-daily/SKILL.md @@ -1,7 +1,7 @@ --- name: blogwatcher-daily description: RSS 订阅监控 + 文章管理 + 导出。使用 RSSHub + feedparser 抓取订阅,自动去重入库 SQLite;支持按单个订阅更新/查看/标记已读/删除文章,可将文章按 txt/markdown/html 导出。附「自然语言操作指南」,AI 智能体可直接把用户口语映射到 CLI 命令。 -version: 1.4 +version: 1.5 category: custom tags: [rss, blog, monitoring, automation, export] metadata: @@ -219,9 +219,13 @@ python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \ # 全库最新 20 篇 → 纯文本 python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \ --export txt --limit 20 + +# 单订阅最新 20 篇 → Markdown(按订阅名或 --list 序号) +python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \ + --export markdown --sub "异次元软件世界" --limit 20 ``` -**过滤规则**(`--date` 与 `--limit` 的组合语义) +**过滤规则**(`--date` / `--limit` / `--sub` 的组合语义) | 参数组合 | 行为 | |---------|------| @@ -229,6 +233,12 @@ python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \ | `--export FMT --date X` | 指定日期 X 的所有文章 | | `--export FMT --limit N` | 忽略日期,取**全库最新 N 篇** | | `--export FMT --date X --limit N` | 日期 X 的最新 N 篇 | +| `--export FMT --sub SUB` | 该订阅**全部**文章(`--sub` 也会让默认日期失效) | +| `--export FMT --sub SUB --limit N` | 该订阅最新 N 篇 | +| `--export FMT --sub SUB --date X` | 该订阅在日期 X 的文章 | +| `--export FMT --sub SUB --date X --limit N` | 该订阅在日期 X 的最新 N 篇 | + +> `--sub` 的 SUB 参数解析规则与 `--articles / --update / --mark-read / --delete` 完全一致(序号 → 精确 → 忽略大小写精确 → 忽略大小写子串),详见「订阅标识(SUB)」。 **参数速查** @@ -237,6 +247,7 @@ python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \ | `--export {txt,markdown,html}` | 触发导出模式并指定格式 | | `--date YYYY-MM-DD` | 目标日期(本地时区,默认今天) | | `--limit N` | 文章数上限;单独使用时忽略日期 | +| `--sub SUB` | 只导出该订阅的文章(SUB=`--list` 序号或名称) | **输出去向** @@ -315,6 +326,9 @@ python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py | "把今天的文章导出成 markdown"、"生成今日摘要 md" | `--export markdown` | | "导出 2026-08-28 的 HTML"、"我要那天的网页版" | `--export html --date 2026-08-28` | | "最新 20 篇导出为 txt"、"给我全库最新 20 篇" | `--export txt --limit 20` | +| "把异次元软件世界的文章导成 markdown"、"只导 Engadget 的" | `--export markdown --sub "SUB"` | +| "导出异次元最新 20 篇 md"、"给我 Slashdot 最近 20 条 html" | `--export markdown --sub "SUB" --limit 20` | +| "把 X 昨天的文章导出成 html" | `--export html --sub "SUB" --date YYYY-MM-DD` | | "今天的文章导 html 到桌面" | `--export html > ~/Desktop/$(date +%Y-%m-%d).html` | ### 订阅标识(SUB)如何解析 @@ -427,7 +441,7 @@ cronjob --create \ ### 导出(`--export`) -1. **查询**:`fetch_articles()` 按 `date(fetched_at, 'localtime')` 过滤 + 可选 `LIMIT N` +1. **查询**:`fetch_articles()` 按 `date(fetched_at, 'localtime')` 过滤 + 可选 `feed_url` 按订阅过滤(`--sub`)+ 可选 `LIMIT N` 2. **分组**:`_group_by_channel()` 按 `channel_title` 分桶 3. **格式化**:`FORMATTERS[fmt]` 分发到 `format_txt` / `format_markdown` / `format_html` 4. **输出**:正文写 stdout,`📊 N 篇文章 → FMT` 进度写 stderr diff --git a/blogwatcher-daily/scripts/blogwatcher-daily.py b/blogwatcher-daily/scripts/blogwatcher-daily.py index 463d575..3919ec8 100644 --- a/blogwatcher-daily/scripts/blogwatcher-daily.py +++ b/blogwatcher-daily/scripts/blogwatcher-daily.py @@ -29,6 +29,7 @@ Usage: 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 + python3 blogwatcher-daily.py --export markdown --sub "异次元软件世界" --limit 20 # 单订阅最新 20 篇 订阅标识(SUB): 可传 --list 输出中的整数序号(如 3),也可传订阅名(精确 → 忽略大小写精确 → 忽略大小写包含)。 @@ -437,8 +438,9 @@ def delete_articles_for_subscription(sub, article_index=None): return True # ========== 导出 ========== -def fetch_articles(target_date=None, limit=None): - """从 articles 表读取;可选按本地日期过滤(date(fetched_at,'localtime'))+ 可选限制数量""" +def fetch_articles(target_date=None, limit=None, feed_url=None): + """从 articles 表读取;可选按本地日期过滤(date(fetched_at,'localtime')) + + 可选按订阅 feed_url 过滤 + 可选限制数量。""" if not os.path.exists(DB_PATH): print(f"❌ 数据库不存在: {DB_PATH}", file=sys.stderr) return [] @@ -446,11 +448,15 @@ def fetch_articles(target_date=None, limit=None): conn = sqlite3.connect(DB_PATH) conn.row_factory = sqlite3.Row - where = "" + conditions = [] params = [] if target_date: - where = "WHERE date(fetched_at, 'localtime') = ?" + conditions.append("date(fetched_at, 'localtime') = ?") params.append(target_date) + if feed_url: + conditions.append("feed_url = ?") + params.append(feed_url) + where = ("WHERE " + " AND ".join(conditions)) if conditions else "" query = f""" SELECT channel_title, title, link, description, pub_date, fetched_at @@ -550,14 +556,21 @@ FORMATTERS = { "html": format_html, } -def export_articles(fmt, target_date, limit): - articles = fetch_articles(target_date, limit) +def export_articles(fmt, target_date, limit, sub=None): + feed_url = sub['url'] if sub else None + articles = fetch_articles(target_date, limit, feed_url=feed_url) + + label_parts = [] + if sub: + label_parts.append(sub['name']) if target_date: - label = target_date - elif limit: - label = f"最新 {limit} 篇" - else: - label = "全部" + label_parts.append(target_date) + if limit: + label_parts.append(f"最新 {limit} 篇") + if not label_parts: + label_parts.append("全部") + label = " — ".join(label_parts) + print(f"📊 {len(articles)} 篇文章 → {fmt}", file=sys.stderr) sys.stdout.write(FORMATTERS[fmt](articles, label)) @@ -585,6 +598,8 @@ def main(): parser.add_argument('--date', help='导出的目标日期 YYYY-MM-DD(默认今天,仅 --export 有效)') parser.add_argument('--limit', type=int, help='导出的文章数上限;单独使用时忽略日期取全库最新 N 篇(仅 --export 有效)') + parser.add_argument('--sub', metavar='SUB', + help='与 --export 搭配:只导出指定订阅的文章(SUB=--list 序号或名称)') args = parser.parse_args() @@ -592,6 +607,8 @@ def main(): parser.error("--unread 必须与 --articles 一起使用") if args.article_id is not None and not args.delete: parser.error("--article-id 必须与 --delete 一起使用") + if args.sub and not args.export: + parser.error("--sub 必须与 --export 一起使用") global RSSHUB_BASE if args.rsshub: @@ -642,13 +659,18 @@ def main(): sys.exit(1) elif args.export: + sub = None + if args.sub: + sub = find_subscription(args.sub) + if not sub: + sys.exit(1) if args.date: target_date = args.date - elif args.limit: + elif args.limit or sub: target_date = None else: target_date = date.today().isoformat() - export_articles(args.export, target_date, args.limit) + export_articles(args.export, target_date, args.limit, sub=sub) else: scan_all(force_all=args.all)