Skip to content

wang2032/mu

Repository files navigation

Multica — Feed Reader

一个用于获取和解析 RSS 2.0 订阅源(支持 content:encoded 命名空间)的静态 Web 应用,以统一格式展示每日报告条目。内置跨日对比视图,可按工具名称和日期聚合条目,展示讨论热度图、版本时间线、热门话题排行和反复出现的问题追踪。

专为追踪 AI 开发者工具动态而设计,支持中文界面。


目录


快速开始

作为 Web 应用使用

  1. 克隆仓库:

    git clone git@github.com:wang2032/multica.git
    cd multica
  2. 在浏览器中打开 index.html 即可运行,无需安装任何依赖或构建步骤。

  3. 默认加载项目内的 feed.xml 示例文件。你也可以输入任何 RSS 2.0 订阅源的 URL 来加载。

作为 Node.js 库使用

npm install
npm run build

然后在你的 TypeScript/JavaScript 项目中引入:

import { ingestFeed, normalizeContent } from "multica";

项目架构

Multica 包含两个独立的层级:

multica/
├── 浏览器端(静态 Web 应用)
│   ├── index.html          # 单页面入口
│   ├── styles.css          # 样式表
│   ├── main.js             # UI 控制器
│   ├── feed-parser.js      # RSS 解析器(使用 DOMParser)
│   ├── cross-day.js        # 跨日对比引擎
│   └── feed.xml            # 示例 RSS 数据
│
├── Node.js 端(TypeScript 库)
│   └── src/
│       ├── index.ts            # 模块导出
│       ├── types.ts            # Block 类型定义
│       ├── normalize.ts        # HTML 内容规范化
│       ├── normalize.test.ts   # 规范化测试
│       └── feed/
│           ├── index.ts        # Feed 模块导出 + ingestFeed()
│           ├── types.ts        # FeedItem / FeedResult 接口
│           ├── parser.ts       # RSS 解析器(基于正则)
│           ├── parser.test.ts  # 解析器测试
│           └── fetcher.ts      # HTTP/本地文件获取器
│
├── package.json
├── tsconfig.json
└── jest.config.js
  • 浏览器端:纯 JavaScript 文件,通过 <script> 标签直接加载,无需构建步骤。使用浏览器原生 DOMParser 解析 XML。
  • Node.js 端:TypeScript 编写的可复用库,提供 RSS 获取、解析和 HTML 内容规范化功能。

浏览器端使用指南

加载订阅源

  1. 打开页面后,顶部输入框默认填入 feed.xml(本地示例文件)。
  2. 你可以替换为任何 RSS 2.0 订阅源的 URL,例如 https://example.com/feed.xml
  3. 点击 Load Feed 按钮或按回车键加载。

加载成功后,页面会显示频道信息和所有条目。

浏览条目列表

条目列表以卡片形式展示,每张卡片包含:

  • 标题:RSS 条目的 <title>
  • 发布日期:来自 <pubDate><dc:date>,以中文格式显示
  • 摘要:来自 <description> 的简短描述

搜索与筛选

加载订阅源后,页面顶部会出现筛选栏,提供以下筛选方式:

筛选项 说明
关键词搜索 在标题、摘要和 HTML 内容中进行全文搜索
起始日期 筛选发布日期 >= 该日期的条目
截止日期 筛选发布日期 <= 该日期的条目
工具筛选 从下拉菜单选择特定工具名称,仅显示该工具的条目
Clear 按钮 清除所有筛选条件

工具下拉菜单的选项会根据已加载的订阅源数据自动填充。筛选栏右侧会显示当前匹配的条目数量。

查看条目详情

  1. 在列表中点击任意条目卡片,进入详情视图。
  2. 详情页展示条目的完整 HTML 内容(来自 <content:encoded>)。
  3. 点击 Show raw XML 按钮可以查看该条目的原始 XML 代码。
  4. 点击 ← Back to list 按钮返回列表视图。

跨日对比视图

点击 7 Day Comparison 按钮,进入跨日对比视图。该视图会按工具名称和日期聚合所有条目,展示以下内容:

汇总统计卡片

页面顶部显示 5 张统计卡片:

统计项 说明
工具数量 识别到的不同工具总数
覆盖天数 数据跨越的天数
话题总数 所有工具的话题合计
版本更新 所有工具的版本更新合计
反复出现的问题 在 2 天以上出现的问题数量

活动趋势图

SVG 面积图,展示每个工具随时间的讨论热度变化。横轴为日期,纵轴为讨论量。不同工具以不同颜色的面积区域表示。

按工具展示的面板

每个工具一个独立面板,包含:

  • 版本时间线:按时间顺序列出该工具的所有版本变更
  • 热门话题排行:横条图展示该工具按出现频率排列的热门话题
  • 反复出现的问题:以警告色高亮显示在多个日期反复出现的问题列表

Node.js 库使用指南

安装依赖

npm install

安装的依赖包括:

  • 运行时依赖node-html-parser(用于 HTML 解析)
  • 开发依赖typescriptjestts-jest、类型定义

获取与解析订阅源

// 从 URL 或本地文件获取并解析 RSS 订阅源
import { ingestFeed } from "./src/feed";

const result = await ingestFeed("https://example.com/feed.xml");
// 或使用本地文件
// const result = await ingestFeed("./feed.xml");

console.log(result.feedTitle);       // 订阅源标题
console.log(result.feedDescription); // 订阅源描述
console.log(result.items.length);    // 条目数量
console.log(result.fetchedAt);       // 获取时间 (Date)

for (const item of result.items) {
  console.log(item.title);       // 条目标题
  console.log(item.link);        // 条目链接
  console.log(item.pubDate);     // 发布日期 (Date)
  console.log(item.summary);     // 摘要
  console.log(item.htmlContent); // HTML 内容
}

也可以分开调用获取和解析:

import { fetchFeed, parseRssFeed } from "./src/feed";

// 单独获取原始 XML
const xml = await fetchFeed("https://example.com/feed.xml");

// 单独解析 XML 字符串
const result = parseRssFeed(xml, "https://example.com/feed.xml");

HTML 内容规范化

将 RSS 条目中的 HTML 内容解析为结构化数据块:

import { normalizeContent, parseHtml, extractMetadata } from "./src";

const html = "<h2>2026-04-11: Claude</h2><p>New features released.</p>";

// 完整规范化:解析 + 提取元数据
const normalized = normalizeContent(html);
console.log(normalized.blocks);    // Block[] 结构化内容块
console.log(normalized.metadata);  // { date: "2026-04-11", topic: "claude" }

// 仅解析 HTML 为 Block 数组
const blocks = parseHtml(html);

// 仅从已有的 Block 数组中提取元数据
const metadata = extractMetadata(blocks);

API 参考

Feed 模块

函数 签名 说明
ingestFeed (source: string) => Promise<FeedResult> 一步完成获取和解析。source 可以是 HTTP/HTTPS URL 或本地文件路径
fetchFeed (source: string) => Promise<string> 获取原始 XML 文本。URL 使用 HTTP(S) 获取,其他路径视为本地文件
parseRssFeed (xml: string, sourceUrl?: string) => FeedResult 解析 RSS 2.0 XML 字符串,支持 CDATA、content:encoded 命名空间

Normalize 模块

函数 签名 说明
normalizeContent (htmlContent: string) => NormalizedContent 主入口:解析 HTML 并提取元数据,返回 { blocks, metadata }
parseHtml (html: string) => Block[] 将 HTML 解析为结构化 Block 数组
extractMetadata (blocks: Block[]) => ContentMetadata 从 Block 数组中提取日期和话题信息

导出的类型

// Feed 类型
interface FeedItem {
  id: string;          // 唯一标识(来自 <guid>,回退到 <link>)
  title: string;       // 条目标题
  link: string;        // 条目链接
  pubDate: Date;       // 发布日期
  summary: string;     // 纯文本摘要
  htmlContent: string; // 完整 HTML 内容
  rawXml: string;      // 原始 <item> XML
}

interface FeedResult {
  sourceUrl: string;       // 订阅源 URL
  feedTitle: string;       // 订阅源标题
  feedDescription: string; // 订阅源描述
  items: FeedItem[];       // 解析后的条目数组
  fetchedAt: Date;         // 获取时间
}

// Normalize 类型
interface InlineText {
  text: string;       // 文本内容
  bold?: boolean;     // 粗体标记
  italic?: boolean;   // 斜体标记
  code?: boolean;     // 行内代码标记
  href?: string;      // 链接 URL
}

type Block =
  | HeadingBlock    // 标题(h1-h6),含 level 和 children
  | ParagraphBlock  // 段落
  | ListBlock       // 列表(有序/无序),含 items 二维数组
  | TableBlock      // 表格,含可选 header 和 rows
  | QuoteBlock      // 引用块
  | LinkBlock;      // 独立链接

interface NormalizedContent {
  blocks: Block[];           // 结构化内容块
  metadata: ContentMetadata; // 提取的元数据
}

interface ContentMetadata {
  date?: string;   // 内容中提取的日期(YYYY-MM-DD 格式)
  topic?: string;  // 从标题或链接中提取的话题标识
}

订阅源格式要求

Multica 支持标准 RSS 2.0 格式的订阅源,并额外支持以下扩展:

  • content:encoded 命名空间(用于完整 HTML 内容)
  • dc:date 命名空间(作为 <pubDate> 的替代)

条目统一结构

每个解析后的条目包含以下字段:

字段 数据来源 说明
id <guid><link> 唯一标识
title <title> 条目标题
link <link> 条目链接
pubDate <pubDate><dc:date> 发布日期
summary <description> 纯文本摘要
htmlContent <content:encoded> 完整 HTML 内容(无则回退到 description)
rawXml 序列化的 <item> 节点 原始 XML

跨日对比的格式要求

若要使用跨日对比功能,条目标题应遵循以下命名规范:

<工具名称> YYYY-MM-DD

例如:Claude 2026-04-11Cursor 2026-04-10

条目的 HTML 内容(<content:encoded>)应使用以下标题划分段落:

中文标题 英文标题 用途
版本动态 Version 版本更新信息
热门话题 Topics 热门讨论话题
反复出现的问题 Issues 反复出现的问题

示例内容结构:

<h2>版本动态</h2>
<ul>
  <li>v3.5 新增代码补全功能</li>
  <li>修复了内存泄漏问题</li>
</ul>

<h2>热门话题</h2>
<ul>
  <li>多语言支持需求增加</li>
  <li>性能优化成为关注焦点</li>
</ul>

<h2>反复出现的问题</h2>
<ul>
  <li>大文件处理时偶发崩溃</li>
</ul>

项目文件说明

文件 说明
index.html 单页面 HTML 入口,包含页面结构和 UI 元素
styles.css 全部 CSS 样式(737 行),深色主题,卡片布局,响应式设计
main.js UI 控制器(854 行):管理列表、详情、跨日对比三个视图,处理搜索筛选,生成 SVG 图表
feed-parser.js RSS 2.0 解析器(128 行):使用浏览器 DOMParser 解析 XML,支持 content:encoded 命名空间
cross-day.js 跨日对比引擎(287 行):按工具聚合条目,构建版本时间线、热门排行和问题追踪
feed.xml 示例 RSS 订阅源数据(509 行),用于测试
src/index.ts 模块导出:重新导出 normalize 模块的函数和类型
src/types.ts Block 类型系统定义:InlineText、HeadingBlock、ParagraphBlock 等
src/normalize.ts HTML 内容规范化器(311 行):解析 HTML 为结构化 Block 数组并提取元数据
src/feed/index.ts Feed 模块入口:导出获取、解析函数和 ingestFeed() 便捷方法
src/feed/types.ts FeedItem 和 FeedResult 接口定义
src/feed/parser.ts RSS 2.0 解析器(基于正则,无 DOM 依赖)
src/feed/fetcher.ts HTTP(S)/本地文件获取器(55 行),支持重定向跟随(最多 5 次)

技术栈

类别 技术
前端 原生 JavaScript(无框架),HTML5,CSS3
后端/库 TypeScript,Node.js
HTML 解析 node-html-parser(Node.js 端)/ DOMParser(浏览器端)
构建工具 TypeScript 编译器 (tsc)
测试框架 Jest + ts-jest
图表 自生成 SVG(无第三方图表库)
国际化 中文界面,日期使用 zh-CN locale

开发指南

构建

编译 TypeScript 源码到 dist/ 目录:

npm run build

编译配置(tsconfig.json):

  • 输入目录:src/
  • 输出目录:dist/
  • 模块系统:CommonJS
  • 目标版本:ES2022
  • 启用严格模式
  • 生成 source map 和声明文件(.d.ts

测试

运行全部测试:

npm test

测试覆盖范围:

  • src/feed/parser.test.ts:RSS 解析测试,包括频道元数据、条目提取、CDATA 处理、XML 实体、边界情况
  • src/normalize.test.ts:HTML 规范化测试,包括标题、段落、列表、表格、引用、行内格式、元数据提取、集成测试

测试配置(jest.config.js):

  • 预设:ts-jest
  • 环境:node
  • 测试文件匹配:src/**/*.test.ts

数据结构参考

统一条目字段映射

RSS 2.0 原始字段          →  统一字段
─────────────────────────────────────
<guid>                     →  id
<title>                    →  title
<link>                     →  link
<pubDate> / <dc:date>      →  pubDate
<description>              →  summary
<content:encoded>          →  htmlContent
原始 <item> 节点           →  rawXml

HTML 内容块类型

HTML 元素            →  Block 类型
────────────────────────────────
<h1> ~ <h6>          →  HeadingBlock(含 level: 1-6)
<p>                  →  ParagraphBlock
<ul> / <ol>          →  ListBlock(含 ordered: boolean)
<table>              →  TableBlock(含可选 header)
<blockquote>         →  QuoteBlock
独立 <a>             →  LinkBlock
<div>/<section>/...  →  递归解析子元素
其他元素             →  作为 ParagraphBlock 处理

行内格式支持

HTML 标签           →  InlineText 属性
─────────────────────────────────────
<strong> / <b>       →  bold: true
<em> / <i>           →  italic: true
<code>               →  code: true
<a href="...">       →  href: "..."
<br>                 →  text: "\n"

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors