From a07b9302ab8d299dd99c4d5856bec3c1f5e32ae8 Mon Sep 17 00:00:00 2001 From: admin Date: Tue, 1 Sep 2026 19:11:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=AE=A2=E9=98=85=E5=88=86?= =?UTF-8?q?=E7=B1=BB=EF=BC=8C=E6=94=AF=E6=8C=81=E5=88=86=E7=B1=BB=E5=AF=BC?= =?UTF-8?q?=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- blogwatcher-daily/SKILL.md | 115 ++++++++++-- .../scripts/blogwatcher-daily.py | 170 ++++++++++++++---- blogwatcher-daily/subscriptions.txt | 76 ++++---- 3 files changed, 270 insertions(+), 91 deletions(-) diff --git a/blogwatcher-daily/SKILL.md b/blogwatcher-daily/SKILL.md index 3ca8da1..12929ab 100644 --- a/blogwatcher-daily/SKILL.md +++ b/blogwatcher-daily/SKILL.md @@ -1,12 +1,12 @@ --- name: blogwatcher-daily -description: RSS 订阅监控 + 文章管理 + 导出。使用 RSSHub + feedparser 抓取订阅,自动去重入库 SQLite;支持按单个订阅更新/查看/标记已读/删除文章,可将文章按 txt/markdown/html 导出。附「自然语言操作指南」,AI 智能体可直接把用户口语映射到 CLI 命令。 -version: 1.5 +description: RSS 订阅监控 + 文章管理 + 分类导出。使用 RSSHub + feedparser 抓取订阅,自动去重入库 SQLite;支持按单个订阅或分类更新/查看/标记已读/删除文章,可将文章按 txt/markdown/html 导出。附「自然语言操作指南」,AI 智能体可直接把用户口语映射到 CLI 命令。 +version: 1.6 category: custom -tags: [rss, blog, monitoring, automation, export] +tags: [rss, blog, monitoring, automation, export, category] metadata: author: Hermes Agent - last_updated: 2026-08-30 + last_updated: 2026-09-01 platform: macos, ubuntu custom_skill_path: /Users/weishen/.hermes/skills/custom/ installation_note: "blogwatcher-daily 脚本在 Mac mini 本地运行(依赖 feedparser)。需要 RSSHub 服务(http://192.168.3.45:1200)访问 YouTube/Bilibili 等被墙源。" @@ -55,10 +55,34 @@ curl → 目标网站 ├── SKILL.md # 本文件 ├── scripts/ │ └── blogwatcher-daily.py # 扫描入库 + 订阅管理 + 文章导出(单文件) -├── subscriptions.txt # 订阅列表(name|URL) +├── subscriptions.txt # 订阅列表(分类|name|URL) └── blogwatcher.db # SQLite 数据库(自动创建) ``` +## 订阅文件格式(subscriptions.txt) + +``` +分类|频道名|URL[|enabled] +``` + +- **新格式(推荐)**:`AI技术|Tech With Tim|http://192.168.3.45:1200/youtube/channel/UC4JX40jDee_tINbkjycV4Sg` +- **旧格式(仍兼容)**:`频道名|URL`(读入时自动归为 `未分类`,无需手动迁移) +- `#` 开头的行视为注释 +- 判定规则:若 `parts[1]` 以 `http://` / `https://` 开头,视为旧格式;否则新格式 + +示例(同一文件可混用): + +``` +# 新格式 +AI技术|Tech With Tim|http://192.168.3.45:1200/youtube/channel/UC4JX40jDee_tINbkjycV4Sg +AI技术|灵姐说AI|http://192.168.3.45:1200/youtube/channel/UCMenHpvUet8myDntrTh7Ckw +电子书|SaltTiger|https://salttiger.com/feed/ +新闻热点|BBC中文网|http://192.168.3.45:1200/youtube/channel/UCb3TZ4SD_Ys3j4z0-8o6auA + +# 旧格式(自动归为「未分类」) +Jon Law|http://192.168.3.45:1200/youtube/channel/UCQM_HxoKmza1simMUchYfVA +``` + ## 使用方法 ### 扫描订阅(默认) @@ -74,17 +98,20 @@ python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py ```bash python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \ - --add "频道名" "URL" + --add "频道名" "URL" [--category "分类"] ``` +- 省略 `--category` 时,分类默认为 `未分类` +- 分类是任意字符串(中英文皆可),后续可通过手工编辑 `subscriptions.txt` 调整 + **YouTube 频道自动转换**:直接贴 YouTube 频道 URL 或 feed URL, 脚本自动识别并转为 RSSHub 格式,无需手动拼接。 ```bash # 以下三种方式效果相同: ---add "Tech With Tim" "https://www.youtube.com/channel/UC4JX40jDee_tINbkjycV4Sg" ---add "Tech With Tim" "https://www.youtube.com/feeds/videos.xml?channel_id=UC4JX40jDee_tINbkjycV4Sg" ---add "Tech With Tim" "http://192.168.3.45:1200/youtube/channel/UC4JX40jDee_tINbkjycV4Sg" +--add "Tech With Tim" "https://www.youtube.com/channel/UC4JX40jDee_tINbkjycV4Sg" --category "AI技术" +--add "Tech With Tim" "https://www.youtube.com/feeds/videos.xml?channel_id=UC4JX40jDee_tINbkjycV4Sg" --category "AI技术" +--add "Tech With Tim" "http://192.168.3.45:1200/youtube/channel/UC4JX40jDee_tINbkjycV4Sg" --category "AI技术" ``` ### 列出订阅 @@ -93,6 +120,22 @@ python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \ python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py --list ``` +输出**按分类分组**,序号 `[N]` 是**全局 1-based**(跨分类连续),可直接用于 `--update N` / `--articles N` / `--sub N` 等命令: + +``` +📡 当前订阅 (37 个 · 9 个分类): + +── 【AI技术】(7 个) ── + [1] Tech With Tim + http://192.168.3.45:1200/youtube/channel/UC4JX40jDee_tINbkjycV4Sg + [3] 小白AI笔记 + ... + +── 【电子书】(1 个) ── + [25] SaltTiger + https://salttiger.com/feed/ +``` + ### 强制回扫(`--all`) ```bash @@ -223,9 +266,21 @@ python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \ # 单订阅最新 20 篇 → Markdown(按订阅名或 --list 序号) python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \ --export markdown --sub "异次元软件世界" --limit 20 + +# 【分类导出】某分类下所有订阅的全部文章 +python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \ + --export markdown --category "AI技术" + +# 【分类导出】某分类下所有订阅今日文章 +python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \ + --export markdown --category "AI技术" --date $(date +%F) + +# 【分类导出】某分类下所有订阅最新 30 篇 +python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \ + --export html --category "新闻热点" --limit 30 > news.html ``` -**过滤规则**(`--date` / `--limit` / `--sub` 的组合语义) +**过滤规则**(`--date` / `--limit` / `--sub` / `--category` 的组合语义) | 参数组合 | 行为 | |---------|------| @@ -237,8 +292,16 @@ python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \ | `--export FMT --sub SUB --limit N` | 该订阅最新 N 篇 | | `--export FMT --sub SUB --date X` | 该订阅在日期 X 的文章 | | `--export FMT --sub SUB --date X --limit N` | 该订阅在日期 X 的最新 N 篇 | +| `--export FMT --category C` | 该分类下**所有订阅的全部**文章(分类过滤同样让默认日期失效) | +| `--export FMT --category C --date X` | 该分类下所有订阅在日期 X 的文章 | +| `--export FMT --category C --limit N` | 该分类下所有订阅的最新 N 篇 | +| `--export FMT --category C --date X --limit N` | 该分类下在日期 X 的最新 N 篇 | -> `--sub` 的 SUB 参数解析规则与 `--articles / --update / --mark-read / --delete` 完全一致(序号 → 精确 → 忽略大小写精确 → 忽略大小写子串),详见「订阅标识(SUB)」。 +> `--sub` 和 `--category` **互斥**(前者指定单个订阅,后者指定一组)。 +> +> `--sub` 的 SUB 参数解析规则:序号 → 精确 → 忽略大小写精确 → 忽略大小写子串,详见「订阅标识(SUB)」。 +> +> `--category` 的分类参数解析规则:精确 → 忽略大小写精确 → 忽略大小写子串(子串匹配跨多个分类时会打印警告,但仍会返回全部命中的订阅)。 **参数速查** @@ -248,6 +311,7 @@ python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \ | `--date YYYY-MM-DD` | 目标日期(本地时区,默认今天) | | `--limit N` | 文章数上限;单独使用时忽略日期 | | `--sub SUB` | 只导出该订阅的文章(SUB=`--list` 序号或名称) | +| `--category CATEGORY` | 只导出该分类下所有订阅的文章(与 `--sub` 互斥) | **输出去向** @@ -316,8 +380,10 @@ python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py | 用户可能说 | 命令 | |---|---| | "订阅 X"、"添加频道 X"、"加个 RSS"、"帮我关注 X"(附 URL) | `--add "NAME" "URL"` | +| "订阅 X 到 AI 分类"、"把 X 加到新闻热点"、"关注 X(分类 = Y)" | `--add "NAME" "URL" --category "Y"` | - YouTube URL、RSSHub URL、原生 RSS URL 都直接传,脚本内部自动路由。 +- 未指定分类时默认归为 `未分类`,事后可手工编辑 `subscriptions.txt` 调整。 ### 7. 导出 @@ -330,6 +396,13 @@ python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py | "导出异次元最新 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` | +| "导出所有 AI 技术频道的文章"、"AI 分类全部导出"、"给我看看 AI 分类的东西" | `--export markdown --category "AI技术"` | +| "导出所有 AI 技术频道今日的文章"、"AI 分类今天的" | `--export markdown --category "AI技术" --date $(date +%F)` | +| "AI 分类最新 30 篇"、"电子书类最新 10 篇" | `--export markdown --category "AI技术" --limit 30` | +| "把新闻热点导成 html 存桌面" | `--export html --category "新闻热点" > ~/Desktop/news-$(date +%Y-%m-%d).html` | + +> AI 智能体判断"分类"意图的关键词:**分类 / 类别 / 类型 / 归类 / 一类 / 那类 / category**。 +> 常见分类举例:`AI技术` `软件工具` `新闻热点` `科技资讯` `技术博客` `电子书` `学习教育` `生活方式` `未分类`。用户口语中说"AI 类"通常指分类名 `AI技术`,说"新闻类"通常指 `新闻热点`。若不确定,先跑一次 `--list` 让用户/自己看清可用分类,再定夺。 ### 订阅标识(SUB)如何解析 @@ -350,10 +423,11 @@ python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py 1. **看/查询类**(无副作用):`--list`、`--articles`。可直接执行。 2. **抓取类**(可能改 DB):`--update`、无参扫描、`--all`。安全,直接跑。 -3. **写入类**(会改 DB):`--mark-read`、`--add`。可直接执行,但结束后简报结果。 +3. **写入类**(会改 DB):`--mark-read`、`--add`(含 `--category`)。可直接执行,但结束后简报结果。 4. **删除类**(不可逆):`--delete`。**先展示要删的内容再执行**,除非用户在同一轮明确肯定。 -5. **导出类**(只读):`--export`。stdout 输出,AI 应帮用户决定重定向到哪个文件。 +5. **导出类**(只读):`--export`(含 `--sub` / `--category`)。stdout 输出,AI 应帮用户决定重定向到哪个文件。 6. **SUB 消歧**:能用序号就优先用序号(`--list` 是廉价的),避免多义。 +7. **分类消歧**:用户说"AI 类的"、"新闻类的"这种口语,先用 `--list` 拉一下可用分类,再选最匹配的作为 `--category` 值;不确定就回问用户。 ## 添加订阅示例 @@ -361,23 +435,28 @@ python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py ```bash python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \ - --add "Tech With Tim" "http://192.168.3.45:1200/youtube/channel/UC4JX40jDee_tINbkjycV4Sg" + --add "Tech With Tim" "http://192.168.3.45:1200/youtube/channel/UC4JX40jDee_tINbkjycV4Sg" \ + --category "AI技术" ``` ### Bilibili ```bash python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \ - --add "B站频道" "http://192.168.3.45:1200/bilibili/user/{uid}" + --add "B站频道" "http://192.168.3.45:1200/bilibili/user/{uid}" \ + --category "生活方式" ``` ### 普通 RSS ```bash python3 ~/.hermes/skills/custom/blogwatcher-daily/scripts/blogwatcher-daily.py \ - --add "博客名" "http://192.168.3.45:1200/rss/https://example.com/feed.xml" + --add "博客名" "http://192.168.3.45:1200/rss/https://example.com/feed.xml" \ + --category "技术博客" ``` +> 省略 `--category` 时新订阅会归入「未分类」,之后可手工编辑 `subscriptions.txt` 调整分类。 + ## RSSHub vs 直接 RSS 脚本自动判断路由,不需要手动选择: @@ -428,7 +507,7 @@ cronjob --create \ ### 扫描入库(默认命令) -1. **加载订阅**:从 `subscriptions.txt` 读取所有 name|URL 对 +1. **加载订阅**:从 `subscriptions.txt` 读取所有条目(`分类|name|URL` 新格式,或 `name|URL` 旧格式→归为「未分类」) 2. **URL 路由**:`build_fetch_url()` 自动判断: - YouTube → 转为 `http://192.168.3.45:1200/youtube/channel/{id}` - RSSHub URL → 直接使用 @@ -441,7 +520,7 @@ cronjob --create \ ### 导出(`--export`) -1. **查询**:`fetch_articles()` 按 `date(fetched_at, 'localtime')` 过滤 + 可选 `feed_url` 按订阅过滤(`--sub`)+ 可选 `LIMIT N` +1. **查询**:`fetch_articles()` 按 `date(fetched_at, 'localtime')` 过滤 + 可选 `feed_url` 按单订阅过滤(`--sub`)+ 可选 `feed_url IN (...)` 按分类内多订阅过滤(`--category`)+ 可选 `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 3919ec8..f2ddefc 100644 --- a/blogwatcher-daily/scripts/blogwatcher-daily.py +++ b/blogwatcher-daily/scripts/blogwatcher-daily.py @@ -10,8 +10,9 @@ Usage: python3 blogwatcher-daily.py --update 3 --all # 强制回扫指定订阅最新 10 篇 # 订阅管理 - python3 blogwatcher-daily.py --list # 列出所有订阅 - python3 blogwatcher-daily.py --add "频道名" "RSS URL" # 添加订阅 + python3 blogwatcher-daily.py --list # 列出所有订阅(按分类分组) + python3 blogwatcher-daily.py --add "频道名" "RSS URL" # 添加订阅(默认分类=未分类) + python3 blogwatcher-daily.py --add "频道名" "RSS URL" --category "AI技术" # 添加订阅并指定分类 # 查看文章 python3 blogwatcher-daily.py --articles "频道名" # 列出该订阅所有文章 @@ -26,10 +27,16 @@ Usage: python3 blogwatcher-daily.py --delete 3 --article-id 2 # 删除该订阅下第 2 篇(1-based,顺序同 --articles) # 导出 - 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 # 今天的文章 + python3 blogwatcher-daily.py --export html --date 2026-08-28 # 指定日期 + python3 blogwatcher-daily.py --export txt --limit 20 # 全库最新 20 篇 python3 blogwatcher-daily.py --export markdown --sub "异次元软件世界" --limit 20 # 单订阅最新 20 篇 + python3 blogwatcher-daily.py --export markdown --category "AI技术" # 分类全部文章 + python3 blogwatcher-daily.py --export markdown --category "AI技术" --date $(date +%F) # 分类今日文章 + +订阅文件格式(subscriptions.txt): + 新格式(推荐):`分类|频道名|URL` + 旧格式(仍兼容):`频道名|URL`(自动归为 `未分类`) 订阅标识(SUB): 可传 --list 输出中的整数序号(如 3),也可传订阅名(精确 → 忽略大小写精确 → 忽略大小写包含)。 @@ -217,47 +224,116 @@ def parse_rss(xml_content, fetch_url): # ========== 订阅管理 ========== def load_subscriptions(): - """从文件加载订阅列表""" + """从文件加载订阅列表 + + 支持两种格式(同一文件内可混用,脚本自动识别): + - 新格式:`category|name|url[|enabled]` + - 旧格式:`name|url[|enabled]`(分类默认为 `未分类`) + + 判定规则:若 `parts[1]` 以 `http://` / `https://` 开头,视为旧格式; + 否则视为新格式(`parts[2]` 必须是 URL)。 + """ subs = [] - if os.path.exists(SUBSCRIPTIONS_FILE): - with open(SUBSCRIPTIONS_FILE, 'r') as f: - for line in f: - line = line.strip() - if line and not line.startswith('#'): - parts = line.split('|') - if len(parts) >= 2: - name = parts[0].strip() - url = parts[1].strip() - enabled = parts[2].strip() != '0' if len(parts) > 2 else True - if enabled: - subs.append({'name': name, 'url': url}) + if not os.path.exists(SUBSCRIPTIONS_FILE): + return subs + + with open(SUBSCRIPTIONS_FILE, 'r', encoding='utf-8') as f: + for line in f: + line = line.strip() + if not line or line.startswith('#'): + continue + parts = [p.strip() for p in line.split('|')] + if len(parts) < 2: + continue + + if parts[1].startswith(('http://', 'https://')): + category = '未分类' + name = parts[0] + url = parts[1] + enabled_str = parts[2] if len(parts) > 2 else None + elif len(parts) >= 3 and parts[2].startswith(('http://', 'https://')): + category = parts[0] + name = parts[1] + url = parts[2] + enabled_str = parts[3] if len(parts) > 3 else None + else: + continue + + enabled = enabled_str != '0' if enabled_str is not None else True + if enabled: + subs.append({'category': category, 'name': name, 'url': url}) return subs -def save_subscription(name, url): +def save_subscription(name, url, category='未分类'): """添加订阅到文件""" - # 检查是否已存在 subs = load_subscriptions() for sub in subs: if sub['url'] == url: print(f"⚠️ 订阅已存在: {name}") return False - - with open(SUBSCRIPTIONS_FILE, 'a') as f: - f.write(f"{name}|{url}\n") - print(f"✅ 已添加订阅: {name}") + + with open(SUBSCRIPTIONS_FILE, 'a', encoding='utf-8') as f: + f.write(f"{category}|{name}|{url}\n") + print(f"✅ 已添加订阅: [{category}] {name}") return True def list_subscriptions(): - """列出所有订阅""" + """列出所有订阅(按分类分组,全局 1-based 序号保持不变)""" subs = load_subscriptions() if not subs: print("📭 暂无订阅") return - - print(f"\n📡 当前订阅 ({len(subs)} 个):\n") + + grouped = {} + order = [] for i, sub in enumerate(subs, 1): - print(f" [{i}] {sub['name']}") - print(f" {sub['url']}\n") + cat = sub.get('category', '未分类') + if cat not in grouped: + grouped[cat] = [] + order.append(cat) + grouped[cat].append((i, sub)) + + print(f"\n📡 当前订阅 ({len(subs)} 个 · {len(order)} 个分类):\n") + for cat in order: + items = grouped[cat] + print(f"── 【{cat}】({len(items)} 个) ──") + for i, sub in items: + print(f" [{i}] {sub['name']}") + print(f" {sub['url']}") + print() + + +def find_subscriptions_by_category(category): + """按分类查找所有订阅。 + 匹配顺序:精确 → 忽略大小写精确 → 忽略大小写子串。 + 匹配到多个分类时打印警告,仍返回全部结果(导出场景下允许跨相邻分类合并)。 + """ + subs = load_subscriptions() + if not subs: + print("❌ 订阅列表为空", file=sys.stderr) + return [] + + exact = [s for s in subs if s.get('category', '未分类') == category] + if exact: + return exact + + lower = category.lower() + exact_ci = [s for s in subs if s.get('category', '未分类').lower() == lower] + if exact_ci: + return exact_ci + + substr = [s for s in subs if lower in s.get('category', '未分类').lower()] + if substr: + matched_cats = sorted({s['category'] for s in substr}) + if len(matched_cats) > 1: + print(f"⚠️ '{category}' 子串匹配到多个分类: {matched_cats}", file=sys.stderr) + return substr + + print(f"❌ 未找到分类: {category}", file=sys.stderr) + all_cats = sorted({s.get('category', '未分类') for s in subs}) + print(f" 可用分类: {all_cats}", file=sys.stderr) + return [] + def find_subscription(identifier): """按序号(--list 中的 1-based 索引)或名称定位订阅。 @@ -438,9 +514,9 @@ def delete_articles_for_subscription(sub, article_index=None): return True # ========== 导出 ========== -def fetch_articles(target_date=None, limit=None, feed_url=None): +def fetch_articles(target_date=None, limit=None, feed_url=None, feed_urls=None): """从 articles 表读取;可选按本地日期过滤(date(fetched_at,'localtime')) - + 可选按订阅 feed_url 过滤 + 可选限制数量。""" + + 可选按单订阅 feed_url 或多订阅 feed_urls 过滤 + 可选限制数量。""" if not os.path.exists(DB_PATH): print(f"❌ 数据库不存在: {DB_PATH}", file=sys.stderr) return [] @@ -456,6 +532,10 @@ def fetch_articles(target_date=None, limit=None, feed_url=None): if feed_url: conditions.append("feed_url = ?") params.append(feed_url) + if feed_urls: + placeholders = ",".join("?" * len(feed_urls)) + conditions.append(f"feed_url IN ({placeholders})") + params.extend(feed_urls) where = ("WHERE " + " AND ".join(conditions)) if conditions else "" query = f""" @@ -556,13 +636,16 @@ FORMATTERS = { "html": format_html, } -def export_articles(fmt, target_date, limit, sub=None): +def export_articles(fmt, target_date, limit, sub=None, category=None, category_subs=None): feed_url = sub['url'] if sub else None - articles = fetch_articles(target_date, limit, feed_url=feed_url) + feed_urls = [s['url'] for s in category_subs] if category_subs else None + articles = fetch_articles(target_date, limit, feed_url=feed_url, feed_urls=feed_urls) label_parts = [] if sub: label_parts.append(sub['name']) + if category: + label_parts.append(f"分类: {category}") if target_date: label_parts.append(target_date) if limit: @@ -600,6 +683,9 @@ def main(): help='导出的文章数上限;单独使用时忽略日期取全库最新 N 篇(仅 --export 有效)') parser.add_argument('--sub', metavar='SUB', help='与 --export 搭配:只导出指定订阅的文章(SUB=--list 序号或名称)') + parser.add_argument('--category', metavar='CATEGORY', + help='与 --export 搭配:只导出指定分类下所有订阅的文章;' + '与 --add 搭配:为新订阅指定分类(默认 "未分类")') args = parser.parse_args() @@ -609,6 +695,10 @@ def main(): parser.error("--article-id 必须与 --delete 一起使用") if args.sub and not args.export: parser.error("--sub 必须与 --export 一起使用") + if args.category and not (args.export or args.add): + parser.error("--category 必须与 --export 或 --add 一起使用") + if args.sub and args.category and args.export: + parser.error("--sub 与 --category 互斥(前者指定单个订阅,后者指定一组)") global RSSHUB_BASE if args.rsshub: @@ -622,7 +712,7 @@ def main(): if not url.startswith('http'): url = f"{RSSHUB_BASE}/{url}" stored_url = convert_to_stored_url(url) - save_subscription(name, stored_url) + save_subscription(name, stored_url, category=args.category or '未分类') elif args.update: sub = find_subscription(args.update) @@ -660,17 +750,25 @@ def main(): elif args.export: sub = None + category_subs = None + category_name = None if args.sub: sub = find_subscription(args.sub) if not sub: sys.exit(1) + if args.category: + category_subs = find_subscriptions_by_category(args.category) + if not category_subs: + sys.exit(1) + category_name = args.category if args.date: target_date = args.date - elif args.limit or sub: + elif args.limit or sub or category_subs: target_date = None else: target_date = date.today().isoformat() - export_articles(args.export, target_date, args.limit, sub=sub) + export_articles(args.export, target_date, args.limit, + sub=sub, category=category_name, category_subs=category_subs) else: scan_all(force_all=args.all) diff --git a/blogwatcher-daily/subscriptions.txt b/blogwatcher-daily/subscriptions.txt index 2014027..3fdb976 100644 --- a/blogwatcher-daily/subscriptions.txt +++ b/blogwatcher-daily/subscriptions.txt @@ -1,37 +1,39 @@ -Tech With Tim|http://192.168.3.45:1200/youtube/channel/UC4JX40jDee_tINbkjycV4Sg -Jon Law|http://192.168.3.45:1200/youtube/channel/UCQM_HxoKmza1simMUchYfVA -小白AI笔记|http://192.168.3.45:1200/youtube/channel/UCEhgrCbJ0eD1PQIoSPWxzMA -大有牧森 Austin Chou|http://192.168.3.45:1200/youtube/channel/UC3hsgc8SHJs1RDMEZBCAccA -huangyihe|http://192.168.3.45:1200/youtube/channel/UCPpdGTNbIKdiWgxCrbka4Zw -陶淵小明|http://192.168.3.45:1200/youtube/channel/UCqccJHWokUkv2ZaQjgRkB3A -惫懒の欧阳川|http://192.168.3.45:1200/youtube/channel/UCqh6eVqfg9l4C0c-q9ZBUog -Brayden Chen|http://192.168.3.45:1200/youtube/channel/UCFX6lKn8w8bIbZCdLc2PBpQ -TEDx Talks|http://192.168.3.45:1200/youtube/channel/UCsT0YIqwnpJCM-mx7-gSA4Q -灵姐说AI|http://192.168.3.45:1200/youtube/channel/UCMenHpvUet8myDntrTh7Ckw -Greyson Zhang|http://192.168.3.45:1200/youtube/channel/UCCNfpRADcNqqPK8PJZgHRNQ -李哈利Harry|http://192.168.3.45:1200/youtube/channel/UCEA4ZfPzWDHp72mlq7IvUcw -coursera|http://192.168.3.45:1200/youtube/channel/UCZ50rYSkYQG31YDEJm9Di_g -無遠弗屆教學教室|http://192.168.3.45:1200/youtube/channel/UCXDP8XCQyoldEiaIRhHz-Vw -零度解说|http://192.168.3.45:1200/youtube/channel/UCvijahEyGtvMpmMHBu4FS2w -Bloomberg Business|http://192.168.3.45:1200/youtube/channel/UCUMZ7gohGI9HcU9VNsr2FJQ -Reuters|http://192.168.3.45:1200/youtube/channel/UChqUTb7kYRX8-EiaN3XFrSQ -BBC中文网|http://192.168.3.45:1200/youtube/channel/UCb3TZ4SD_Ys3j4z0-8o6auA -理想生活实验室|https://www.toodaylab.com/feed -阿榮福利味|https://www.azofreeware.com/feeds/posts/default -重灌狂人|https://feeds.feedburner.com/briian -Engadget|https://www.engadget.com/rss.xml -异次元软件世界|https://feed.iplaysoft.com/ -小众软件|https://feeds.appinn.com/appinns/ -SaltTiger|https://salttiger.com/feed/ -TED Talks Daily|https://feeds.acast.com/public/shows/67587e77c705e441797aff96 -Slashdot|https://rss.slashdot.org/Slashdot/slashdot -AWS DevOps Blog|https://aws.amazon.com/blogs/devops/feed/ -SRE WEEKLY|https://sreweekly.com/feed/ -電腦玩物|https://feeds.feedburner.com/playpc -The Guardian AI|https://www.theguardian.com/technology/artificialintelligenceai/rss -DowJones World News|https://feeds.content.dowjones.io/public/rss/RSSWorldNews -TuTu生活志|http://192.168.3.45:1200/youtube/channel/UCuhAUKCdKrjYoMiJQc74ZkQ -AI懒人报|http://192.168.3.45:1200/youtube/channel/UCNyBkFPz_IPWainccK0YbOA -Dan Koe|http://192.168.3.45:1200/youtube/channel/UCWXYDYv5STLk-zoxMP2I1Lw -技术爬爬虾 TechShrimp|http://192.168.3.45:1200/youtube/channel/UCa6D2k5qhpOI9I-WT8fpd6g -AI Foundations|http://192.168.3.45:1200/youtube/channel/UCWZwfV3ICOt3uEPpW6hYK4g \ No newline at end of file +# 格式:分类|频道名|URL[|enabled] +# 旧格式 `频道名|URL` 仍兼容,读入时自动归为「未分类」。 +AI技术|Tech With Tim|http://192.168.3.45:1200/youtube/channel/UC4JX40jDee_tINbkjycV4Sg +AI技术|Jon Law|http://192.168.3.45:1200/youtube/channel/UCQM_HxoKmza1simMUchYfVA +AI技术|小白AI笔记|http://192.168.3.45:1200/youtube/channel/UCEhgrCbJ0eD1PQIoSPWxzMA +AI技术|大有牧森 Austin Chou|http://192.168.3.45:1200/youtube/channel/UC3hsgc8SHJs1RDMEZBCAccA +AI技术|huangyihe|http://192.168.3.45:1200/youtube/channel/UCPpdGTNbIKdiWgxCrbka4Zw +AI技术|陶淵小明|http://192.168.3.45:1200/youtube/channel/UCqccJHWokUkv2ZaQjgRkB3A +AI技术|惫懒の欧阳川|http://192.168.3.45:1200/youtube/channel/UCqh6eVqfg9l4C0c-q9ZBUog +AI技术|Brayden Chen|http://192.168.3.45:1200/youtube/channel/UCFX6lKn8w8bIbZCdLc2PBpQ +学习教育|TEDx Talks|http://192.168.3.45:1200/youtube/channel/UCsT0YIqwnpJCM-mx7-gSA4Q +AI技术|灵姐说AI|http://192.168.3.45:1200/youtube/channel/UCMenHpvUet8myDntrTh7Ckw +AI技术|Greyson Zhang|http://192.168.3.45:1200/youtube/channel/UCCNfpRADcNqqPK8PJZgHRNQ +AI技术|李哈利Harry|http://192.168.3.45:1200/youtube/channel/UCEA4ZfPzWDHp72mlq7IvUcw +学习教育|coursera|http://192.168.3.45:1200/youtube/channel/UCZ50rYSkYQG31YDEJm9Di_g +学习教育|無遠弗屆教學教室|http://192.168.3.45:1200/youtube/channel/UCXDP8XCQyoldEiaIRhHz-Vw +科技资讯|零度解说|http://192.168.3.45:1200/youtube/channel/UCvijahEyGtvMpmMHBu4FS2w +新闻热点|Bloomberg Business|http://192.168.3.45:1200/youtube/channel/UCUMZ7gohGI9HcU9VNsr2FJQ +新闻热点|Reuters|http://192.168.3.45:1200/youtube/channel/UChqUTb7kYRX8-EiaN3XFrSQ +新闻热点|BBC中文网|http://192.168.3.45:1200/youtube/channel/UCb3TZ4SD_Ys3j4z0-8o6auA +生活方式|理想生活实验室|https://www.toodaylab.com/feed +软件工具|阿榮福利味|https://www.azofreeware.com/feeds/posts/default +软件工具|重灌狂人|https://feeds.feedburner.com/briian +科技资讯|Engadget|https://www.engadget.com/rss.xml +软件工具|异次元软件世界|https://feed.iplaysoft.com/ +软件工具|小众软件|https://feeds.appinn.com/appinns/ +电子书|SaltTiger|https://salttiger.com/feed/ +学习教育|TED Talks Daily|https://feeds.acast.com/public/shows/67587e77c705e441797aff96 +科技资讯|Slashdot|https://rss.slashdot.org/Slashdot/slashdot +技术博客|AWS DevOps Blog|https://aws.amazon.com/blogs/devops/feed/ +技术博客|SRE WEEKLY|https://sreweekly.com/feed/ +软件工具|電腦玩物|https://feeds.feedburner.com/playpc +AI技术|The Guardian AI|https://www.theguardian.com/technology/artificialintelligenceai/rss +新闻热点|DowJones World News|https://feeds.content.dowjones.io/public/rss/RSSWorldNews +生活方式|TuTu生活志|http://192.168.3.45:1200/youtube/channel/UCuhAUKCdKrjYoMiJQc74ZkQ +AI技术|AI懒人报|http://192.168.3.45:1200/youtube/channel/UCNyBkFPz_IPWainccK0YbOA +未分类|Dan Koe|http://192.168.3.45:1200/youtube/channel/UCWXYDYv5STLk-zoxMP2I1Lw +技术博客|技术爬爬虾 TechShrimp|http://192.168.3.45:1200/youtube/channel/UCa6D2k5qhpOI9I-WT8fpd6g +AI技术|AI Foundations|http://192.168.3.45:1200/youtube/channel/UCWZwfV3ICOt3uEPpW6hYK4g