rssany 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/.env.example +1 -1
  2. package/README.md +5 -1
  3. package/app/plugins/builtin/rss.rssany.js +107 -24
  4. package/app/plugins/site.rssany.js +1 -1
  5. package/dist/index.js +986 -266
  6. package/dist/index.js.map +1 -1
  7. package/{config.examples.json → init/config.json} +7 -1
  8. package/init/sources.json +353 -0
  9. package/package.json +4 -6
  10. package/statics/401.html +1 -1
  11. package/statics/README.md +1 -1
  12. package/webui/build/200.html +16 -18
  13. package/webui/build/_app/immutable/assets/0.C6Q_nuW9.css +1 -0
  14. package/webui/build/_app/immutable/assets/10.Dj8_pmut.css +1 -0
  15. package/webui/build/_app/immutable/assets/11.qYZMiTb0.css +1 -0
  16. package/webui/build/_app/immutable/assets/12.Ct59LCqW.css +1 -0
  17. package/webui/build/_app/immutable/assets/13.BhO9zvFi.css +1 -0
  18. package/webui/build/_app/immutable/assets/14.CujIhjQK.css +1 -0
  19. package/webui/build/_app/immutable/assets/15.nNGjXhCQ.css +1 -0
  20. package/webui/build/_app/immutable/assets/16.PP9XLDf7.css +1 -0
  21. package/webui/build/_app/immutable/assets/4.9wPHhVwv.css +1 -0
  22. package/webui/build/_app/immutable/assets/6.DSJfjJwx.css +1 -0
  23. package/webui/build/_app/immutable/assets/7.CrNxmd8B.css +1 -0
  24. package/webui/build/_app/immutable/assets/8.Ba5_jYIY.css +1 -0
  25. package/webui/build/_app/immutable/assets/{9.BZheTlzZ.css → 9.m-LCx_kl.css} +1 -1
  26. package/webui/build/_app/immutable/assets/BackToParentRoute.DGk-X5ow.css +1 -0
  27. package/webui/build/_app/immutable/assets/SourcesList.yTBBi3_m.css +1 -0
  28. package/webui/build/_app/immutable/assets/homeFeedPanelStore.BopJZtHu.css +1 -0
  29. package/webui/build/_app/immutable/chunks/{V2-VOe88.js → B-OsL1Ct.js} +1 -1
  30. package/webui/build/_app/immutable/chunks/B2Q1a1-H.js +2 -0
  31. package/webui/build/_app/immutable/chunks/BK3WtZwv.js +1 -0
  32. package/webui/build/_app/immutable/chunks/BQqoDzLx.js +1 -0
  33. package/webui/build/_app/immutable/chunks/BXCWEhUd.js +1 -0
  34. package/webui/build/_app/immutable/chunks/BbWUOQ_m.js +1 -0
  35. package/webui/build/_app/immutable/chunks/Bp63qm3L.js +1 -0
  36. package/webui/build/_app/immutable/chunks/CVzlFH44.js +1 -0
  37. package/webui/build/_app/immutable/chunks/CWNeClHp.js +6 -0
  38. package/webui/build/_app/immutable/chunks/Cihqbfi5.js +1 -0
  39. package/webui/build/_app/immutable/chunks/CkUAV0m0.js +41 -0
  40. package/webui/build/_app/immutable/chunks/CtijX1u3.js +31 -0
  41. package/webui/build/_app/immutable/chunks/D5GvRCv7.js +1 -0
  42. package/webui/build/_app/immutable/chunks/DEDI7Ecm.js +1 -0
  43. package/webui/build/_app/immutable/chunks/DFuhmi31.js +1 -0
  44. package/webui/build/_app/immutable/chunks/DMWEh-Ek.js +2 -0
  45. package/webui/build/_app/immutable/chunks/{CZDFXKiF.js → DcAshVxe.js} +1 -1
  46. package/webui/build/_app/immutable/chunks/DjNLq3TF.js +1 -0
  47. package/webui/build/_app/immutable/chunks/Dt2CddFe.js +1 -0
  48. package/webui/build/_app/immutable/chunks/Dw782Tjs.js +1 -0
  49. package/webui/build/_app/immutable/chunks/EIZIMsXK.js +1 -0
  50. package/webui/build/_app/immutable/chunks/Xy_fhzQq.js +1 -0
  51. package/webui/build/_app/immutable/chunks/lk5LaiqA.js +1 -0
  52. package/webui/build/_app/immutable/chunks/mW5RwvnK.js +13 -0
  53. package/webui/build/_app/immutable/chunks/{CtHRh_pJ.js → tB7QMF3U.js} +1 -1
  54. package/webui/build/_app/immutable/chunks/xtNWTdbD.js +1 -0
  55. package/webui/build/_app/immutable/entry/app.DdgnooOk.js +2 -0
  56. package/webui/build/_app/immutable/entry/start.DhJaJZhR.js +1 -0
  57. package/webui/build/_app/immutable/nodes/0.BE05Cuc4.js +11 -0
  58. package/webui/build/_app/immutable/nodes/1.5DFDaT4c.js +1 -0
  59. package/webui/build/_app/immutable/nodes/10.OVK4i9XE.js +1 -0
  60. package/webui/build/_app/immutable/nodes/11.Dhn_rO4A.js +1 -0
  61. package/webui/build/_app/immutable/nodes/12.Cg8AeCSH.js +1 -0
  62. package/webui/build/_app/immutable/nodes/13.nT3SOzEB.js +1 -0
  63. package/webui/build/_app/immutable/nodes/14.B_KpJLxn.js +1 -0
  64. package/webui/build/_app/immutable/nodes/15.RaWaA-0I.js +1 -0
  65. package/webui/build/_app/immutable/nodes/{12.CMcby_lY.js → 16.DSUgqolV.js} +15 -15
  66. package/webui/build/_app/immutable/nodes/2.BYWOpaxy.js +1 -0
  67. package/webui/build/_app/immutable/nodes/3.wQvGs9w-.js +1 -0
  68. package/webui/build/_app/immutable/nodes/4.DTSxpKm7.js +2 -0
  69. package/webui/build/_app/immutable/nodes/5.CCtn90c0.js +1 -0
  70. package/webui/build/_app/immutable/nodes/6.C2_mjW1u.js +1 -0
  71. package/webui/build/_app/immutable/nodes/7.Dwz6W7A1.js +1 -0
  72. package/webui/build/_app/immutable/nodes/8.DzkEw6rx.js +1 -0
  73. package/webui/build/_app/immutable/nodes/9.DtlXEwe1.js +1 -0
  74. package/webui/build/_app/version.json +1 -1
  75. package/sources.example.json +0 -562
  76. package/webui/build/_app/immutable/assets/0.BUAXpTm6.css +0 -1
  77. package/webui/build/_app/immutable/assets/10.I1OuCLrU.css +0 -1
  78. package/webui/build/_app/immutable/assets/11.CrO9xaki.css +0 -1
  79. package/webui/build/_app/immutable/assets/12.BEi6fInA.css +0 -1
  80. package/webui/build/_app/immutable/assets/14.Ctlgn1LZ.css +0 -1
  81. package/webui/build/_app/immutable/assets/2.eJ80XOGm.css +0 -1
  82. package/webui/build/_app/immutable/assets/4.B8-jYAVj.css +0 -1
  83. package/webui/build/_app/immutable/assets/6.Drn-0DON.css +0 -1
  84. package/webui/build/_app/immutable/assets/7.ms2diq_q.css +0 -1
  85. package/webui/build/_app/immutable/assets/8.DKymkjjs.css +0 -1
  86. package/webui/build/_app/immutable/assets/SourcesList.BhtYlRsQ.css +0 -1
  87. package/webui/build/_app/immutable/chunks/BUngiKFg.js +0 -1
  88. package/webui/build/_app/immutable/chunks/Bt0fzibd.js +0 -1
  89. package/webui/build/_app/immutable/chunks/BxHqDcpw.js +0 -1
  90. package/webui/build/_app/immutable/chunks/ByQRbEUX.js +0 -1
  91. package/webui/build/_app/immutable/chunks/C12mHcUp.js +0 -6
  92. package/webui/build/_app/immutable/chunks/C1kQ4pHy.js +0 -1
  93. package/webui/build/_app/immutable/chunks/C74gbb4Q.js +0 -1
  94. package/webui/build/_app/immutable/chunks/CAtemnMo.js +0 -1
  95. package/webui/build/_app/immutable/chunks/CVjCNJia.js +0 -1
  96. package/webui/build/_app/immutable/chunks/CjQQ9_Q2.js +0 -2
  97. package/webui/build/_app/immutable/chunks/D-6mYMI1.js +0 -1
  98. package/webui/build/_app/immutable/chunks/D1Gs8-g3.js +0 -1
  99. package/webui/build/_app/immutable/chunks/D9dRVKgL.js +0 -1
  100. package/webui/build/_app/immutable/chunks/DCEY1XiC.js +0 -1
  101. package/webui/build/_app/immutable/chunks/DI-t-G_K.js +0 -2
  102. package/webui/build/_app/immutable/chunks/DTUxjyWL.js +0 -1
  103. package/webui/build/_app/immutable/chunks/DWJZOHke.js +0 -1
  104. package/webui/build/_app/immutable/chunks/Dgs6d7X5.js +0 -1
  105. package/webui/build/_app/immutable/chunks/DjpPK99f.js +0 -71
  106. package/webui/build/_app/immutable/chunks/DjzVVxpy.js +0 -1
  107. package/webui/build/_app/immutable/chunks/DvtNA-3X.js +0 -1
  108. package/webui/build/_app/immutable/chunks/LQVMBmDN.js +0 -1
  109. package/webui/build/_app/immutable/chunks/Qw0Qgx6J.js +0 -1
  110. package/webui/build/_app/immutable/chunks/bohabpgg.js +0 -1
  111. package/webui/build/_app/immutable/chunks/c-YfbAB_.js +0 -8
  112. package/webui/build/_app/immutable/chunks/tpTQfoNn.js +0 -1
  113. package/webui/build/_app/immutable/entry/app.Cra5Zsz4.js +0 -2
  114. package/webui/build/_app/immutable/entry/start.ToY0Qh0_.js +0 -1
  115. package/webui/build/_app/immutable/nodes/0.D2-xzG_8.js +0 -11
  116. package/webui/build/_app/immutable/nodes/1.CFixzRR6.js +0 -1
  117. package/webui/build/_app/immutable/nodes/10.ayxWydPr.js +0 -1
  118. package/webui/build/_app/immutable/nodes/11.B0JS3E2j.js +0 -1
  119. package/webui/build/_app/immutable/nodes/13.DRpZV72T.js +0 -1
  120. package/webui/build/_app/immutable/nodes/14.DVeJW6bd.js +0 -1
  121. package/webui/build/_app/immutable/nodes/2.DIZ4IPNm.js +0 -1
  122. package/webui/build/_app/immutable/nodes/3.BFSNf0FK.js +0 -1
  123. package/webui/build/_app/immutable/nodes/4.BSsIjejE.js +0 -2
  124. package/webui/build/_app/immutable/nodes/5.COxRT9Oe.js +0 -1
  125. package/webui/build/_app/immutable/nodes/6.CBgQ4YzB.js +0 -1
  126. package/webui/build/_app/immutable/nodes/7.BbzWOL0V.js +0 -6
  127. package/webui/build/_app/immutable/nodes/8.C8120200.js +0 -1
  128. package/webui/build/_app/immutable/nodes/9.BH_BGQQ4.js +0 -1
  129. /package/webui/build/_app/immutable/nodes/{15.BtYZF6FM.js → 17.BtYZF6FM.js} +0 -0
  130. /package/webui/build/_app/immutable/nodes/{16.Ba_qJjp6.js → 18.Ba_qJjp6.js} +0 -0
package/.env.example CHANGED
@@ -33,7 +33,7 @@ SMTP_PASS=your-smtp-password
33
33
  # Resend API Key(driver=resend 时使用;https://resend.com/api-keys)
34
34
  RESEND_API_KEY=re_xxxxxxxxxxxxxxxxxxxx
35
35
 
36
- # ─── LLM(研究报告模式用)──────────────────────────────────────────────────
36
+ # ─── LLM(解析 / Pipeline 等;也可在管理后台「设置 → LLM」写入 .rssany/config.json)──
37
37
  OPENAI_API_KEY=sk-...
38
38
  OPENAI_BASE_URL=https://api.openai.com/v1
39
39
  OPENAI_MODEL=gpt-4o-mini
package/README.md CHANGED
@@ -5,6 +5,10 @@
5
5
 
6
6
  **RSSAny** 是一套自托管的订阅管线:列表 URL → **抓取与解析**(规则 / LLM)→ **正文提取**(自定义 / Readability / LLM)→ **upsert 去重** → 固定 **pipeline**(打标签、翻译等)→ 对外提供 `**/rss`** 等输出。
7
7
 
8
+ ## 界面预览
9
+
10
+ ![RSSAny Web 界面截图](images/ScreenShot_2026-04-10_153307_558.png)
11
+
8
12
  ---
9
13
 
10
14
  ## 功能概览
@@ -56,7 +60,7 @@ pnpm run webui:install
56
60
  ```bash
57
61
  cp .env.example .env
58
62
  ```
59
- 2. 信源与全局配置:首次启动会在 **`~/.rssany/`**(Windows:`%USERPROFILE%\.rssany\`)下自动从包内示例生成 `sources.json`、`config.json`(若已存在则不会覆盖)。也可手动复制仓库里的 `sources.example.json`、`config.examples.json`。
63
+ 2. 信源与全局配置:首次启动会在 **`~/.rssany/`**(Windows:`%USERPROFILE%\.rssany\`)下自动从包内 **`init/`** 目录中的默认数据复制生成 `sources.json`、`config.json`(若已存在则不会覆盖)。也可手动复制仓库里的 `init/sources.json`、`init/config.json`。
60
64
  3. (可选)LLM:在 `.env` 中设置 `OPENAI_API_KEY`、`OPENAI_BASE_URL`、`OPENAI_MODEL` 等。
61
65
 
62
66
  ### 运行
@@ -1,30 +1,95 @@
1
- // 内置 RSS/Atom/JSON Feed 插件:匹配 *rss*、*atom*、*.xml 等标准 Feed URL
1
+ // 内置 RSS/Atom/JSON Feed:通过浏览器(Puppeteer)拉取 Feed URL,再用 rss-parser 解析;
2
+ // 与站点插件一致走 Chrome,便于应对需浏览器环境或代理的场景;XML 使用 HTTP 响应原文(useHttpResponseBody)。
2
3
 
3
4
  const UA = "RssAny/1.0 (+https://github.com/joohw/rssany)";
4
5
 
5
- async function fetchFeed(url, ctx) {
6
- const { deps } = ctx;
7
- const proxyToUse = ctx.proxy ?? process.env.HTTP_PROXY ?? process.env.HTTPS_PROXY;
8
- if (proxyToUse) {
9
- const agent = new deps.HttpsProxyAgent(proxyToUse);
10
- const parserWithProxy = new deps.RssParser({
11
- timeout: 15_000,
12
- headers: {
13
- "User-Agent": UA,
14
- Accept: "application/rss+xml,application/atom+xml,application/json,application/xml,text/xml,*/*",
15
- },
16
- requestOptions: { agent },
17
- });
18
- return parserWithProxy.parseURL(url);
6
+ const IMAGE_TYPE_RE = /^image\//i;
7
+ const IMAGE_EXT_IN_PATH_RE = /\.(jpg|jpeg|png|gif|webp|avif|svg)(\?|#|$)/i;
8
+
9
+ function trimUrl(s) {
10
+ if (typeof s !== "string") return undefined;
11
+ const t = s.trim();
12
+ return t || undefined;
13
+ }
14
+
15
+ /** rss-parser 条目上尽量取出配图 URL(入库用 imageUrl,与 Gateway 的 cover_img 对齐)。 */
16
+ function extractItemImageUrl(item) {
17
+ const enc = item.enclosure;
18
+ if (enc && typeof enc.url === "string") {
19
+ const u = trimUrl(enc.url);
20
+ const t = typeof enc.type === "string" ? enc.type : "";
21
+ if (u && (IMAGE_TYPE_RE.test(t) || (!t && IMAGE_EXT_IN_PATH_RE.test(u)))) {
22
+ return u;
23
+ }
24
+ }
25
+
26
+ const itunesImg = item.itunes && typeof item.itunes.image === "string" ? item.itunes.image : undefined;
27
+ const fromItunes = trimUrl(itunesImg);
28
+ if (fromItunes) return fromItunes;
29
+
30
+ const thumbs = item.mediaThumbnail;
31
+ if (Array.isArray(thumbs) && thumbs[0]?.$) {
32
+ const u = trimUrl(thumbs[0].$.url ?? thumbs[0].$.href);
33
+ if (u) return u;
34
+ }
35
+
36
+ const mediaBlocks = item.mediaContent;
37
+ if (Array.isArray(mediaBlocks)) {
38
+ for (const block of mediaBlocks) {
39
+ const $ = block && block.$;
40
+ if (!$ || typeof $.url !== "string") continue;
41
+ const medium = $.medium;
42
+ const ctype = typeof $.type === "string" ? $.type : "";
43
+ if (medium === "image" || IMAGE_TYPE_RE.test(ctype)) {
44
+ const u = trimUrl($.url);
45
+ if (u) return u;
46
+ }
47
+ }
48
+ }
49
+
50
+ const atomLinks = item.atomLinks;
51
+ if (Array.isArray(atomLinks)) {
52
+ for (const l of atomLinks) {
53
+ const $ = l && l.$;
54
+ if (!$ || typeof $.href !== "string") continue;
55
+ const rel = String($.rel || "").toLowerCase();
56
+ const ctype = String($.type || "").toLowerCase();
57
+ if (rel === "enclosure" && ctype.startsWith("image/")) {
58
+ const u = trimUrl($.href);
59
+ if (u) return u;
60
+ }
61
+ }
19
62
  }
20
- const parser = new deps.RssParser({
21
- timeout: 15_000,
22
- headers: {
23
- "User-Agent": UA,
24
- Accept: "application/rss+xml,application/atom+xml,application/json,application/xml,text/xml,*/*",
25
- },
63
+
64
+ const fromHtml =
65
+ firstImgSrcFromHtml(item.content) ||
66
+ firstImgSrcFromHtml(item.summary) ||
67
+ firstImgSrcFromHtml(item["content:encoded"]) ||
68
+ firstImgSrcFromHtml(item.contentSnippet);
69
+ if (fromHtml && /^https?:\/\//i.test(fromHtml)) {
70
+ return fromHtml;
71
+ }
72
+
73
+ return undefined;
74
+ }
75
+
76
+ function firstImgSrcFromHtml(html) {
77
+ if (typeof html !== "string" || !html) return undefined;
78
+ const m = html.match(/<img[^>]+src\s*=\s*["']([^"']+)["']/i);
79
+ return m ? trimUrl(m[1]) : undefined;
80
+ }
81
+
82
+ async function fetchFeedXml(url, ctx) {
83
+ const fetchHtml = ctx.fetchHtml;
84
+ if (typeof fetchHtml !== "function") {
85
+ throw new Error("RSS 插件需要 ctx.fetchHtml(请通过 feeder / buildSourceContext 调用)");
86
+ }
87
+ const { html } = await fetchHtml(url, {
88
+ waitMs: 800,
89
+ purify: false,
90
+ useHttpResponseBody: true,
26
91
  });
27
- return parser.parseURL(url);
92
+ return html;
28
93
  }
29
94
 
30
95
  export default {
@@ -35,7 +100,22 @@ export default {
35
100
  refreshInterval: "1h",
36
101
  async fetchItems(sourceId, ctx) {
37
102
  const { deps } = ctx;
38
- const feed = await fetchFeed(sourceId, ctx);
103
+ const xml = await fetchFeedXml(sourceId, ctx);
104
+ const parser = new deps.RssParser({
105
+ timeout: 30_000,
106
+ headers: {
107
+ "User-Agent": UA,
108
+ Accept: "application/rss+xml,application/atom+xml,application/json,application/xml,text/xml,*/*",
109
+ },
110
+ customFields: {
111
+ item: [
112
+ ["media:thumbnail", "mediaThumbnail", { keepArray: true }],
113
+ ["media:content", "mediaContent", { keepArray: true }],
114
+ ["link", "atomLinks", { keepArray: true }],
115
+ ],
116
+ },
117
+ });
118
+ const feed = await parser.parseString(xml);
39
119
  return (feed.items ?? []).map((item) => {
40
120
  const link = item.link ?? item.guid ?? sourceId;
41
121
  const guid = item.guid ?? deps.createHash("sha256").update(link).digest("hex");
@@ -52,7 +132,8 @@ export default {
52
132
  typeof item.summary === "string" ? item.summary : typeof item.contentSnippet === "string" ? item.contentSnippet : undefined;
53
133
  const content =
54
134
  typeof item.content === "string" ? item.content : typeof item["content:encoded"] === "string" ? item["content:encoded"] : undefined;
55
- return {
135
+ const imageUrl = extractItemImageUrl(item);
136
+ const base = {
56
137
  guid,
57
138
  title: item.title ?? "",
58
139
  link,
@@ -61,6 +142,8 @@ export default {
61
142
  summary,
62
143
  content,
63
144
  };
145
+ if (!imageUrl) return base;
146
+ return { ...base, imageUrl, cover_img: imageUrl };
64
147
  });
65
148
  },
66
149
  };
@@ -7,7 +7,7 @@
7
7
 
8
8
  export default {
9
9
  id: "__PLUGIN_ID__",
10
- listUrlPattern: "https://example.com/{segment}",
10
+ listUrlPattern: __LIST_URL_PATTERN__,
11
11
  refreshInterval: "1day",
12
12
 
13
13
  /** sourceId 与订阅里 ref 一致;ctx 含 fetchHtml、extractItem、deps(parseHtml 等) */