rssany 0.1.6 → 0.3.0

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 (129) hide show
  1. package/README.md +23 -27
  2. package/app/plugins/builtin/agi-eval-evaluation.rssany.js +7 -8
  3. package/app/plugins/builtin/amii-research-talent.rssany.js +6 -7
  4. package/app/plugins/builtin/anthropic-research.rssany.js +6 -8
  5. package/app/plugins/builtin/appen-resources.rssany.js +6 -7
  6. package/app/plugins/builtin/baai-wudao-paper-article.rssany.js +9 -10
  7. package/app/plugins/builtin/baaidata-csdn.rssany.js +6 -7
  8. package/app/plugins/builtin/baidu-research.rssany.js +5 -8
  9. package/app/plugins/builtin/brightdata-blog.rssany.js +7 -12
  10. package/app/plugins/builtin/bytedance-seed-research.rssany.js +5 -7
  11. package/app/plugins/builtin/email.rssany.js +9 -9
  12. package/app/plugins/builtin/five-radar.rssany.js +10 -12
  13. package/app/plugins/builtin/flageval-news.rssany.js +5 -7
  14. package/app/plugins/builtin/google-deepmind-research.rssany.js +7 -9
  15. package/app/plugins/builtin/google-research-datasets.rssany.js +6 -8
  16. package/app/plugins/builtin/google-research.rssany.js +6 -8
  17. package/app/plugins/builtin/hacker-news-newest.rssany.js +7 -9
  18. package/app/plugins/builtin/harvard-dataverse.rssany.js +6 -8
  19. package/app/plugins/builtin/huaweicloud-bbs-blogs.rssany.js +7 -9
  20. package/app/plugins/builtin/lingowhale.rssany.js +7 -9
  21. package/app/plugins/builtin/meituan-tech.rssany.js +7 -10
  22. package/app/plugins/builtin/meta-ai-publications.rssany.js +6 -11
  23. package/app/plugins/builtin/mila-quebec.rssany.js +6 -8
  24. package/app/plugins/builtin/mit-csail-research.rssany.js +7 -9
  25. package/app/plugins/builtin/moonshot.rssany.js +6 -8
  26. package/app/plugins/builtin/opendatalab-news.rssany.js +6 -7
  27. package/app/plugins/builtin/opendatalab.rssany.js +5 -6
  28. package/app/plugins/builtin/opendrivelab-autonomous-driving.rssany.js +6 -7
  29. package/app/plugins/builtin/opendrivelab-embodiedai.rssany.js +7 -8
  30. package/app/plugins/builtin/opendrivelab-publications.rssany.js +7 -9
  31. package/app/plugins/builtin/opendrivelab.rssany.js +7 -8
  32. package/app/plugins/builtin/paperswithcode.rssany.js +6 -8
  33. package/app/plugins/builtin/pjlab-adg-publications.rssany.js +8 -10
  34. package/app/plugins/builtin/rss.rssany.js +11 -12
  35. package/app/plugins/builtin/selectdataset.rssany.js +6 -8
  36. package/app/plugins/builtin/sensetime-tech-achievements.rssany.js +7 -8
  37. package/app/plugins/builtin/supervisely-blog.rssany.js +6 -8
  38. package/app/plugins/builtin/theinformation-briefings.rssany.js +144 -136
  39. package/app/plugins/builtin/uci-ml-repository.rssany.js +6 -7
  40. package/app/plugins/builtin/venturebeat.rssany.js +7 -9
  41. package/app/plugins/builtin/worldlabs.rssany.js +6 -8
  42. package/app/plugins/builtin/x.rssany.js +7 -9
  43. package/app/plugins/builtin/xiaohongshu.rssany.js +119 -56
  44. package/app/plugins/builtin/zhipu-research.rssany.js +7 -10
  45. package/app/plugins/site.rssany.js +25 -25
  46. package/{statics → app/statics}/README.md +7 -7
  47. package/bin/rssany.js +226 -6
  48. package/dist/index.js +545 -396
  49. package/dist/index.js.map +1 -1
  50. package/package.json +20 -13
  51. package/scripts/dev.mjs +114 -0
  52. package/scripts/reset.mjs +1 -1
  53. package/app/plugins/builtin/google.rssany.js +0 -187
  54. package/init/config.json +0 -17
  55. package/init/sources.json +0 -353
  56. package/statics/401.html +0 -56
  57. package/statics/404.html +0 -12
  58. package/statics/image.png +0 -0
  59. package/webui/build/200.html +0 -49
  60. package/webui/build/_app/env.js +0 -1
  61. package/webui/build/_app/immutable/assets/0.BB88QFoe.css +0 -1
  62. package/webui/build/_app/immutable/assets/10.Dj8_pmut.css +0 -1
  63. package/webui/build/_app/immutable/assets/11.qYZMiTb0.css +0 -1
  64. package/webui/build/_app/immutable/assets/12.Ct59LCqW.css +0 -1
  65. package/webui/build/_app/immutable/assets/13.BhO9zvFi.css +0 -1
  66. package/webui/build/_app/immutable/assets/14.CujIhjQK.css +0 -1
  67. package/webui/build/_app/immutable/assets/15.nNGjXhCQ.css +0 -1
  68. package/webui/build/_app/immutable/assets/16.PP9XLDf7.css +0 -1
  69. package/webui/build/_app/immutable/assets/4.9wPHhVwv.css +0 -1
  70. package/webui/build/_app/immutable/assets/5.ClehBQ0g.css +0 -1
  71. package/webui/build/_app/immutable/assets/6.DSJfjJwx.css +0 -1
  72. package/webui/build/_app/immutable/assets/7.CrNxmd8B.css +0 -1
  73. package/webui/build/_app/immutable/assets/8.Ba5_jYIY.css +0 -1
  74. package/webui/build/_app/immutable/assets/9.m-LCx_kl.css +0 -1
  75. package/webui/build/_app/immutable/assets/BackToParentRoute.DGk-X5ow.css +0 -1
  76. package/webui/build/_app/immutable/assets/SourcesList.yTBBi3_m.css +0 -1
  77. package/webui/build/_app/immutable/assets/homeFeedPanelStore.CSvlNcpm.css +0 -1
  78. package/webui/build/_app/immutable/chunks/B-OsL1Ct.js +0 -1
  79. package/webui/build/_app/immutable/chunks/B2Q1a1-H.js +0 -2
  80. package/webui/build/_app/immutable/chunks/BK3WtZwv.js +0 -1
  81. package/webui/build/_app/immutable/chunks/BQqoDzLx.js +0 -1
  82. package/webui/build/_app/immutable/chunks/BUApaBEI.js +0 -1
  83. package/webui/build/_app/immutable/chunks/BbWUOQ_m.js +0 -1
  84. package/webui/build/_app/immutable/chunks/Bfc47y5P.js +0 -1
  85. package/webui/build/_app/immutable/chunks/Bp63qm3L.js +0 -1
  86. package/webui/build/_app/immutable/chunks/BwlaCkNX.js +0 -36
  87. package/webui/build/_app/immutable/chunks/C0J2-L94.js +0 -1
  88. package/webui/build/_app/immutable/chunks/CBY2biv-.js +0 -1
  89. package/webui/build/_app/immutable/chunks/CLOXMsDk.js +0 -36
  90. package/webui/build/_app/immutable/chunks/CVzlFH44.js +0 -1
  91. package/webui/build/_app/immutable/chunks/CWNeClHp.js +0 -6
  92. package/webui/build/_app/immutable/chunks/Cihqbfi5.js +0 -1
  93. package/webui/build/_app/immutable/chunks/D5GvRCv7.js +0 -1
  94. package/webui/build/_app/immutable/chunks/DEDI7Ecm.js +0 -1
  95. package/webui/build/_app/immutable/chunks/DFuhmi31.js +0 -1
  96. package/webui/build/_app/immutable/chunks/DMWEh-Ek.js +0 -2
  97. package/webui/build/_app/immutable/chunks/DgceFEv5.js +0 -1
  98. package/webui/build/_app/immutable/chunks/DjNLq3TF.js +0 -1
  99. package/webui/build/_app/immutable/chunks/Dt2CddFe.js +0 -1
  100. package/webui/build/_app/immutable/chunks/Dw782Tjs.js +0 -1
  101. package/webui/build/_app/immutable/chunks/SqCUd34O.js +0 -1
  102. package/webui/build/_app/immutable/chunks/Xy_fhzQq.js +0 -1
  103. package/webui/build/_app/immutable/chunks/hp4PFHFv.js +0 -1
  104. package/webui/build/_app/immutable/chunks/lk5LaiqA.js +0 -1
  105. package/webui/build/_app/immutable/chunks/mW5RwvnK.js +0 -13
  106. package/webui/build/_app/immutable/chunks/tB7QMF3U.js +0 -1
  107. package/webui/build/_app/immutable/chunks/xtNWTdbD.js +0 -1
  108. package/webui/build/_app/immutable/entry/app.B8zBPipq.js +0 -2
  109. package/webui/build/_app/immutable/entry/start.CxRCKeCl.js +0 -1
  110. package/webui/build/_app/immutable/nodes/0.ChLNE3xy.js +0 -11
  111. package/webui/build/_app/immutable/nodes/1.1N74-4Io.js +0 -1
  112. package/webui/build/_app/immutable/nodes/10.DY30t9Ib.js +0 -1
  113. package/webui/build/_app/immutable/nodes/11.ITuxnukH.js +0 -1
  114. package/webui/build/_app/immutable/nodes/12.qLzWqB1c.js +0 -1
  115. package/webui/build/_app/immutable/nodes/13.nT3SOzEB.js +0 -1
  116. package/webui/build/_app/immutable/nodes/14.BHnIxbVM.js +0 -1
  117. package/webui/build/_app/immutable/nodes/15.CLjT9il3.js +0 -1
  118. package/webui/build/_app/immutable/nodes/16.BD-mKCLN.js +0 -24
  119. package/webui/build/_app/immutable/nodes/17.BtYZF6FM.js +0 -1
  120. package/webui/build/_app/immutable/nodes/18.Ba_qJjp6.js +0 -1
  121. package/webui/build/_app/immutable/nodes/2.BYWOpaxy.js +0 -1
  122. package/webui/build/_app/immutable/nodes/3.Dt5o2Fmz.js +0 -1
  123. package/webui/build/_app/immutable/nodes/4.DTSxpKm7.js +0 -2
  124. package/webui/build/_app/immutable/nodes/5.Dy3vSsIP.js +0 -1
  125. package/webui/build/_app/immutable/nodes/6.DvclsL6H.js +0 -1
  126. package/webui/build/_app/immutable/nodes/7.D2nJy-Uz.js +0 -1
  127. package/webui/build/_app/immutable/nodes/8.C75mhrqs.js +0 -1
  128. package/webui/build/_app/immutable/nodes/9.Bp_QXw3w.js +0 -1
  129. package/webui/build/_app/version.json +0 -1
@@ -1,3 +1,8 @@
1
+ export const id = "hacker-news-newest";
2
+ export const name = "Hacker News Newest";
3
+ export const listUrlPattern = /^https:\/\/news\.ycombinator\.com\/newest\/?(\?.*)?$/i;
4
+ export const refreshInterval = "10min";
5
+
1
6
  let _deps;
2
7
 
3
8
  // Hacker News newest 插件:解析 newest 列表页为 FeedItem(仅列表,不做正文 enrich)
@@ -17,7 +22,7 @@ function toAbsoluteUrl(rawHref, baseUrl) {
17
22
  if (!href || href.startsWith("#") || href.startsWith("javascript:")) return null;
18
23
  try {
19
24
  const url = new URL(href, baseUrl);
20
- if (!/^https?:$/i.test(url.protocol)) return null;
25
+ if (!/^https:$/i.test(url.protocol)) return null;
21
26
  return url.href;
22
27
  } catch {
23
28
  return null;
@@ -88,7 +93,7 @@ function parseMeta(root, row, itemId) {
88
93
  }
89
94
 
90
95
 
91
- async function fetchItems(sourceId, ctx) {
96
+ export async function fetchItems(sourceId, ctx) {
92
97
  _deps = ctx.deps;
93
98
  const { html, finalUrl } = await ctx.fetchHtml(sourceId, { waitMs: 3000 });
94
99
  const root = _deps.parseHtml(html);
@@ -121,10 +126,3 @@ async function fetchItems(sourceId, ctx) {
121
126
  return items;
122
127
  }
123
128
 
124
-
125
- export default {
126
- id: "hacker-news-newest",
127
- listUrlPattern: /^https?:\/\/news\.ycombinator\.com\/newest\/?(\?.*)?$/i,
128
- refreshInterval: "10min",
129
- fetchItems,
130
- };
@@ -1,3 +1,7 @@
1
+ export const id = "harvard-dataverse";
2
+ export const name = "Harvard Dataverse";
3
+ export const listUrlPattern = /^https:\/\/dataverse\.harvard\.edu(?:\/?$|\/\?.*|\/dataverse\/[^/?#]+\/?(?:\?.*)?|\/dataverse\.xhtml(?:\?.*)?)$/i;
4
+
1
5
  let _deps;
2
6
 
3
7
 
@@ -34,7 +38,7 @@ function toHttpUrl(rawUrl, baseUrl = DATAVERSE_ORIGIN) {
34
38
  if (!text) return null;
35
39
  try {
36
40
  const url = new URL(text, baseUrl);
37
- if (!/^https?:$/i.test(url.protocol)) return null;
41
+ if (!/^https:$/i.test(url.protocol)) return null;
38
42
  return url.href;
39
43
  } catch {
40
44
  return null;
@@ -131,7 +135,7 @@ function toFeedItem(record, index) {
131
135
  };
132
136
  }
133
137
 
134
- async function fetchItems(sourceId, ctx) {
138
+ export async function fetchItems(sourceId, ctx) {
135
139
  _deps = ctx.deps;
136
140
  const query = buildQuery(sourceId);
137
141
  const apiUrl = `${DATAVERSE_SEARCH_API}?${query.toString()}`;
@@ -158,9 +162,3 @@ async function fetchItems(sourceId, ctx) {
158
162
  return items;
159
163
  }
160
164
 
161
- export default {
162
- id: "harvard-dataverse",
163
- listUrlPattern:
164
- /^https?:\/\/dataverse\.harvard\.edu(?:\/?$|\/\?.*|\/dataverse\/[^/?#]+\/?(?:\?.*)?|\/dataverse\.xhtml(?:\?.*)?)$/i,
165
- fetchItems,
166
- };
@@ -1,3 +1,8 @@
1
+ export const id = "huaweicloud-bbs-blogs";
2
+ export const name = "Huaweicloud Bbs Blogs";
3
+ export const listUrlPattern = /^https:\/\/bbs\.huaweicloud\.com\/blogs\/?(\?.*)?$/i;
4
+ export const refreshInterval = "1h";
5
+
1
6
  let _deps;
2
7
 
3
8
  // 华为云社区博客插件:抓取 https://bbs.huaweicloud.com/blogs 列表条目(默认仅列表,不做 enrich)
@@ -28,7 +33,7 @@ function toAbsoluteUrl(href, baseUrl) {
28
33
  if (!href) return null;
29
34
  try {
30
35
  const url = new URL(href, baseUrl);
31
- if (!/^https?:$/i.test(url.protocol)) return null;
36
+ if (!/^https:$/i.test(url.protocol)) return null;
32
37
  return url.href;
33
38
  } catch {
34
39
  return null;
@@ -160,7 +165,7 @@ function parseFromTitleAnchors(root, pageUrl) {
160
165
  }
161
166
 
162
167
 
163
- async function fetchItems(sourceId, ctx) {
168
+ export async function fetchItems(sourceId, ctx) {
164
169
  _deps = ctx.deps;
165
170
  const { html, finalUrl } = await ctx.fetchHtml(sourceId, { waitMs: 4500 });
166
171
  const root = _deps.parseHtml(html);
@@ -176,10 +181,3 @@ async function fetchItems(sourceId, ctx) {
176
181
  return items;
177
182
  }
178
183
 
179
-
180
- export default {
181
- id: "huaweicloud-bbs-blogs",
182
- listUrlPattern: /^https?:\/\/bbs\.huaweicloud\.com\/blogs\/?(\?.*)?$/i,
183
- refreshInterval: "1h",
184
- fetchItems,
185
- };
@@ -8,7 +8,10 @@
8
8
  // lingowhale://articles?app_id=xxx&app_secret=yyy
9
9
  // 也可通过环境变量提供(优先级低于 URL 参数):
10
10
  // LINGOWHALE_APP_ID / LINGOWHALE_APP_SECRET
11
-
11
+ export const id = "lingowhale";
12
+ export const name = "Lingowhale";
13
+ export const listUrlPattern = /^lingowhale:\/\//;
14
+ export const refreshInterval = "1h";
12
15
 
13
16
  const BASE_URL = "https://open.lingowhale.com/open-api/v1";
14
17
 
@@ -104,16 +107,11 @@ async function fetchTodayArticles(appId, appSecret) {
104
107
  }
105
108
 
106
109
 
107
- export default {
108
- id: "lingowhale",
109
- listUrlPattern: /^lingowhale:\/\//,
110
- refreshInterval: "1h",
111
-
112
- async fetchItems(sourceId, _ctx) {
110
+ export async function fetchItems(sourceId, _ctx) {
113
111
  const { appId, appSecret } = resolveCredentials(sourceId);
114
112
  const endpoint = resolveEndpoint(sourceId);
115
113
  const maxPages = resolveMaxPages(sourceId);
116
114
  if (endpoint === "today") return fetchTodayArticles(appId, appSecret);
117
115
  return fetchArticles(appId, appSecret, maxPages);
118
- },
119
- };
116
+ }
117
+
@@ -1,3 +1,8 @@
1
+ export const id = "meituan-tech";
2
+ export const name = "Meituan Tech";
3
+ export const listUrlPattern = /^https:\/\/(www\.)?tech\.meituan\.com(\/.*)?$/i;
4
+ export const refreshInterval = "1day";
5
+
1
6
  let _deps;
2
7
 
3
8
  // 美团技术团队博客:https://tech.meituan.com/
@@ -12,7 +17,7 @@ function toAbsoluteUrl(href, baseUrl) {
12
17
  if (!href) return null;
13
18
  try {
14
19
  const url = new URL(href, baseUrl);
15
- if (!/^https?:$/i.test(url.protocol)) return null;
20
+ if (!/^https:$/i.test(url.protocol)) return null;
16
21
  return url.href;
17
22
  } catch {
18
23
  return null;
@@ -34,7 +39,7 @@ function hashGuid(link) {
34
39
  return _deps.createHash("sha256").update(link).digest("hex");
35
40
  }
36
41
 
37
- async function fetchItems(sourceId, ctx) {
42
+ export async function fetchItems(sourceId, ctx) {
38
43
  _deps = ctx.deps;
39
44
  const { html, finalUrl } = await ctx.fetchHtml(sourceId, {
40
45
  waitMs: 5000,
@@ -120,11 +125,3 @@ async function enrichItem(item, ctx) {
120
125
  return ctx.extractItem(item);
121
126
  }
122
127
 
123
- export default {
124
- id: "meituan-tech",
125
- listUrlPattern: /^https?:\/\/(www\.)?tech\.meituan\.com(\/.*)?$/i,
126
- detailUrlPattern: /^https?:\/\/(www\.)?tech\.meituan\.com\/\d{4}\/\d{2}\/\d{2}\/[^/]+\.html(?:\?.*)?$/i,
127
- refreshInterval: "1day",
128
- fetchItems,
129
- enrichItem,
130
- };
@@ -1,3 +1,7 @@
1
+ export const id = "meta-ai-publications";
2
+ export const name = "Meta AI Publications";
3
+ export const listUrlPattern = /^https:\/\/ai\.meta\.com\/results\/?\?.*content_types(?:%5B0%5D|\[0\])=publication(?:&.*)?$/i;
4
+
1
5
  let _deps;
2
6
 
3
7
  // Meta AI Publications 插件:抓取结果页中的 publication 条目(不做正文 enrich)
@@ -5,8 +9,6 @@ let _deps;
5
9
 
6
10
 
7
11
  const PUBLICATION_PATH_RE = /^\/research\/publications\/[^?#]+\/?$/i;
8
- const PUBLICATION_RESULTS_URL_RE =
9
- /^https?:\/\/ai\.meta\.com\/results\/?\?.*content_types(?:%5B0%5D|\[0\])=publication(?:&.*)?$/i;
10
12
  const MONTH_TO_INDEX = {
11
13
  january: 0,
12
14
  february: 1,
@@ -42,7 +44,7 @@ function toAbsolutePublicationUrl(rawHref, pageUrl) {
42
44
  if (!href || href.startsWith("#") || href.startsWith("javascript:")) return null;
43
45
  try {
44
46
  const url = new URL(href, pageUrl);
45
- if (!/^https?:$/i.test(url.protocol)) return null;
47
+ if (!/^https:$/i.test(url.protocol)) return null;
46
48
  if (url.hostname !== "ai.meta.com") return null;
47
49
  if (!PUBLICATION_PATH_RE.test(url.pathname)) return null;
48
50
  return url.href;
@@ -169,7 +171,7 @@ function extractSummary(card, title) {
169
171
  }
170
172
 
171
173
 
172
- async function fetchItems(sourceId, ctx) {
174
+ export async function fetchItems(sourceId, ctx) {
173
175
  _deps = ctx.deps;
174
176
  const { html, finalUrl, status } = await ctx.fetchHtml(sourceId, { waitMs: 3500, purify: false });
175
177
  if (status >= 400) {
@@ -212,10 +214,3 @@ async function fetchItems(sourceId, ctx) {
212
214
  }
213
215
  return items;
214
216
  }
215
-
216
-
217
- export default {
218
- id: "meta-ai-publications",
219
- listUrlPattern: PUBLICATION_RESULTS_URL_RE,
220
- fetchItems,
221
- };
@@ -1,3 +1,7 @@
1
+ export const id = "mila-quebec";
2
+ export const name = "Mila Quebec";
3
+ export const listUrlPattern = /^https:\/\/(www\.)?mila\.quebec\/en(?:\/news)?(?:\/)?(?:\?.*)?$/i;
4
+
1
5
  let _deps;
2
6
 
3
7
  // Mila (Quebec AI Institute) 新闻列表插件:支持首页 /en 与新闻页 /en/news
@@ -39,7 +43,7 @@ function toAbsoluteHttpUrl(rawHref, baseUrl) {
39
43
  if (!href || href.startsWith("#") || href.startsWith("javascript:")) return null;
40
44
  try {
41
45
  const url = new URL(href, baseUrl);
42
- if (!/^https?:$/i.test(url.protocol)) return null;
46
+ if (!/^https:$/i.test(url.protocol)) return null;
43
47
  return url.href;
44
48
  } catch {
45
49
  return null;
@@ -131,7 +135,7 @@ function chooseSummary(node, title) {
131
135
  }
132
136
 
133
137
 
134
- async function fetchItems(sourceId, ctx) {
138
+ export async function fetchItems(sourceId, ctx) {
135
139
  _deps = ctx.deps;
136
140
  const { html, finalUrl } = await ctx.fetchHtml(sourceId, { waitMs: 4000 });
137
141
  const root = _deps.parseHtml(html);
@@ -191,9 +195,3 @@ async function fetchItems(sourceId, ctx) {
191
195
  return items;
192
196
  }
193
197
 
194
-
195
- export default {
196
- id: "mila-quebec",
197
- listUrlPattern: /^https?:\/\/(www\.)?mila\.quebec\/en(?:\/news)?(?:\/)?(?:\?.*)?$/i,
198
- fetchItems,
199
- };
@@ -1,10 +1,14 @@
1
+ export const id = "mit-csail-research";
2
+ export const name = "MIT CSAIL Research";
3
+ export const listUrlPattern = /^https:\/\/(www\.)?csail\.mit\.edu\/research(?:\?.*)?$/i;
4
+
1
5
  let _deps;
2
6
 
3
7
  // MIT CSAIL Research plugin: warm up via homepage, then parse /research list items.
4
8
 
5
9
 
6
10
 
7
- const SITE_ID = "mit-csail-research";
11
+ const SITE_ID = id;
8
12
  const CSAIL_HOME_URL = "https://www.csail.mit.edu/";
9
13
  const CSAIL_RESEARCH_PATH = "/research";
10
14
  const SUMMARY_SELECTOR = "div, p, span, h2, h3, h4, a";
@@ -35,7 +39,7 @@ function toAbsoluteHttpUrl(rawHref, baseUrl) {
35
39
  if (!href || href.startsWith("#") || href.startsWith("javascript:") || href.startsWith("mailto:")) return null;
36
40
  try {
37
41
  const url = new URL(href, baseUrl);
38
- if (!/^https?:$/i.test(url.protocol)) return null;
42
+ if (!/^https:$/i.test(url.protocol)) return null;
39
43
  return url.href;
40
44
  } catch {
41
45
  return null;
@@ -169,7 +173,7 @@ function parseItems(html, finalUrl, requestedCategory) {
169
173
  }
170
174
 
171
175
 
172
- async function fetchItems(sourceId, ctx) {
176
+ export async function fetchItems(sourceId, ctx) {
173
177
  _deps = ctx.deps;
174
178
  const sourceUrl = new URL(sourceId);
175
179
  const requestedCategory = sourceUrl.searchParams.get("category") ?? "";
@@ -200,9 +204,3 @@ async function fetchItems(sourceId, ctx) {
200
204
  return items;
201
205
  }
202
206
 
203
-
204
- export default {
205
- id: SITE_ID,
206
- listUrlPattern: /^https?:\/\/(www\.)?csail\.mit\.edu\/research(?:\?.*)?$/i,
207
- fetchItems,
208
- };
@@ -1,3 +1,7 @@
1
+ export const id = "moonshot";
2
+ export const name = "Moonshot";
3
+ export const listUrlPattern = /^https:\/\/(www\.)?moonshot\.ai(?:\/[a-z]{2}(?:-[a-z]{2})?)?\/?(\?.*)?$/i;
4
+
1
5
  let _deps;
2
6
 
3
7
  // Moonshot 官方站插件:抓取首页“最新研究”列表,输出 FeedItem(不含 enrich)
@@ -24,7 +28,7 @@ function toAbsoluteHttpUrl(rawHref, baseUrl) {
24
28
  if (!href || href.startsWith("#") || href.startsWith("javascript:")) return null;
25
29
  try {
26
30
  const url = new URL(href, baseUrl);
27
- if (!/^https?:$/i.test(url.protocol)) return null;
31
+ if (!/^https:$/i.test(url.protocol)) return null;
28
32
  return url.href;
29
33
  } catch {
30
34
  return null;
@@ -95,7 +99,7 @@ function collectCandidateAnchors(root) {
95
99
  }
96
100
 
97
101
 
98
- async function fetchItems(sourceId, ctx) {
102
+ export async function fetchItems(sourceId, ctx) {
99
103
  _deps = ctx.deps;
100
104
  const { html, finalUrl } = await ctx.fetchHtml(sourceId, { waitMs: 4500 });
101
105
  const root = _deps.parseHtml(html);
@@ -119,9 +123,3 @@ async function fetchItems(sourceId, ctx) {
119
123
  return items;
120
124
  }
121
125
 
122
-
123
- export default {
124
- id: "moonshot",
125
- listUrlPattern: /^https?:\/\/(www\.)?moonshot\.ai(?:\/[a-z]{2}(?:-[a-z]{2})?)?\/?(\?.*)?$/i,
126
- fetchItems,
127
- };
@@ -1,3 +1,8 @@
1
+ export const id = "opendatalab-news";
2
+ export const name = "Opendatalab News";
3
+ export const listUrlPattern = /^https:\/\/(www\.)?opendatalab\.(org\.cn|com)\/news\/?(\?.*)?$/i;
4
+ export const refreshInterval = "1h";
5
+
1
6
  let _deps;
2
7
 
3
8
 
@@ -139,7 +144,7 @@ function mapItems(records, source) {
139
144
  return items;
140
145
  }
141
146
 
142
- async function fetchItems(_sourceId, _ctx) {
147
+ export async function fetchItems(_sourceId, _ctx) {
143
148
  _deps = _ctx.deps;
144
149
  const collected = [];
145
150
 
@@ -166,9 +171,3 @@ async function fetchItems(_sourceId, _ctx) {
166
171
  return deduped;
167
172
  }
168
173
 
169
- export default {
170
- id: "opendatalab-news",
171
- listUrlPattern: /^https?:\/\/(www\.)?opendatalab\.(org\.cn|com)\/news\/?(\?.*)?$/i,
172
- refreshInterval: "1h",
173
- fetchItems,
174
- };
@@ -1,3 +1,7 @@
1
+ export const id = "opendatalab";
2
+ export const name = "Opendatalab";
3
+ export const listUrlPattern = /^https:\/\/(www\.)?opendatalab\.(org\.cn|com)\/?(?:datasets\/?)?(?:\?.*)?$/i;
4
+
1
5
  let _deps;
2
6
 
3
7
 
@@ -72,7 +76,7 @@ function parsePaginationFromSourceId(sourceId) {
72
76
  }
73
77
  }
74
78
 
75
- async function fetchItems(sourceId, ctx) {
79
+ export async function fetchItems(sourceId, ctx) {
76
80
  _deps = ctx.deps;
77
81
  const { pageNo, pageSize } = parsePaginationFromSourceId(sourceId);
78
82
  const response = await fetch(OPENDATALAB_LIST_API, {
@@ -102,8 +106,3 @@ async function fetchItems(sourceId, ctx) {
102
106
  return items;
103
107
  }
104
108
 
105
- export default {
106
- id: "opendatalab",
107
- listUrlPattern: /^https?:\/\/(www\.)?opendatalab\.(org\.cn|com)\/?(?:datasets\/?)?(?:\?.*)?$/i,
108
- fetchItems,
109
- };
@@ -1,3 +1,7 @@
1
+ export const id = "opendrivelab-autonomous-driving";
2
+ export const name = "Opendrivelab Autonomous Driving";
3
+ export const listUrlPattern = /^https:\/\/(www\.)?opendrivelab\.com\/AutonomousDriving\/?(\?.*)?$/i;
4
+
1
5
  let _deps;
2
6
 
3
7
  // OpenDriveLab Autonomous Driving 插件:抓取时间线条目并输出 FeedItem(不含 enrich)
@@ -17,7 +21,7 @@ function toAbsoluteHttpUrl(rawHref, baseUrl) {
17
21
  if (!href || href.startsWith("#") || href.startsWith("javascript:")) return null;
18
22
  try {
19
23
  const url = new URL(href, baseUrl);
20
- if (!/^https?:$/i.test(url.protocol)) return null;
24
+ if (!/^https:$/i.test(url.protocol)) return null;
21
25
  return url.href;
22
26
  } catch {
23
27
  return null;
@@ -72,7 +76,7 @@ function findTitleAnchor(li, finalUrl) {
72
76
  return fallback;
73
77
  }
74
78
 
75
- async function fetchItems(sourceId, ctx) {
79
+ export async function fetchItems(sourceId, ctx) {
76
80
  _deps = ctx.deps;
77
81
  const { html, finalUrl } = await ctx.fetchHtml(sourceId, { waitMs: 3500 });
78
82
  const root = _deps.parseHtml(html);
@@ -107,8 +111,3 @@ async function fetchItems(sourceId, ctx) {
107
111
  return items;
108
112
  }
109
113
 
110
- export default {
111
- id: "opendrivelab-autonomous-driving",
112
- listUrlPattern: /^https?:\/\/(www\.)?opendrivelab\.com\/AutonomousDriving\/?(\?.*)?$/i,
113
- fetchItems,
114
- };
@@ -1,7 +1,11 @@
1
+ export const id = "opendrivelab-embodiedai";
2
+ export const name = "Opendrivelab Embodiedai";
3
+ export const listUrlPattern = /^https:\/\/(www\.)?opendrivelab\.com\/EmbodiedAI\/?(\?.*)?$/i;
4
+
1
5
  let _deps;
2
6
 
3
7
 
4
- const SITE_ID = "opendrivelab-embodiedai";
8
+ const SITE_ID = id;
5
9
  const DATE_RE = /\b(20\d{2})[./-](\d{1,2})[./-](\d{1,2})\b/;
6
10
  const ACTION_LINK_LABELS = new Set([
7
11
  "paper",
@@ -29,7 +33,7 @@ function toAbsoluteHttpUrl(rawHref, baseUrl) {
29
33
  if (!href || href.startsWith("#") || href.startsWith("javascript:")) return null;
30
34
  try {
31
35
  const url = new URL(href, baseUrl);
32
- if (!/^https?:$/i.test(url.protocol)) return null;
36
+ if (!/^https:$/i.test(url.protocol)) return null;
33
37
  return url.href;
34
38
  } catch {
35
39
  return null;
@@ -94,7 +98,7 @@ function buildItemsFromHtml(html, finalUrl) {
94
98
  return items;
95
99
  }
96
100
 
97
- async function fetchItems(sourceId, ctx) {
101
+ export async function fetchItems(sourceId, ctx) {
98
102
  _deps = ctx.deps;
99
103
  const { html, finalUrl } = await ctx.fetchHtml(sourceId, { waitMs: 3500 });
100
104
  const items = buildItemsFromHtml(html, finalUrl);
@@ -107,8 +111,3 @@ async function fetchItems(sourceId, ctx) {
107
111
  throw new Error(`[${SITE_ID}] 未解析到 Embodied AI 条目,页面结构可能已变化`);
108
112
  }
109
113
 
110
- export default {
111
- id: SITE_ID,
112
- listUrlPattern: /^https?:\/\/(www\.)?opendrivelab\.com\/EmbodiedAI\/?(\?.*)?$/i,
113
- fetchItems,
114
- };
@@ -1,3 +1,7 @@
1
+ export const id = "opendrivelab-publications";
2
+ export const name = "Opendrivelab Publications";
3
+ export const listUrlPattern = /^https:\/\/(www\.)?opendrivelab\.com\/publications\/?(\?.*)?$/i;
4
+
1
5
  let _deps;
2
6
 
3
7
 
@@ -16,7 +20,7 @@ function toHttpUrl(rawHref, baseUrl) {
16
20
  if (!rawHref) return null;
17
21
  try {
18
22
  const url = new URL(rawHref, baseUrl);
19
- if (!/^https?:$/i.test(url.protocol)) return null;
23
+ if (!/^https:$/i.test(url.protocol)) return null;
20
24
  return url;
21
25
  } catch {
22
26
  return null;
@@ -83,7 +87,7 @@ function extractContext(anchor) {
83
87
  }
84
88
 
85
89
 
86
- async function fetchItems(sourceId, ctx) {
90
+ export async function fetchItems(sourceId, ctx) {
87
91
  _deps = ctx.deps;
88
92
  const { html, finalUrl } = await ctx.fetchHtml(sourceId, { waitMs: 3500 });
89
93
  const root = _deps.parseHtml(html);
@@ -104,7 +108,7 @@ async function fetchItems(sourceId, ctx) {
104
108
  if (seen.has(link)) continue;
105
109
  seen.add(link);
106
110
 
107
- const { summary, category, year } = extractContext(anchor);
111
+ const { summary, category: _category, year } = extractContext(anchor);
108
112
  const pubDate = year != null ? new Date(Date.UTC(year, 0, 1)) : new Date();
109
113
 
110
114
  items.push({
@@ -122,9 +126,3 @@ async function fetchItems(sourceId, ctx) {
122
126
  return items;
123
127
  }
124
128
 
125
-
126
- export default {
127
- id: "opendrivelab-publications",
128
- listUrlPattern: /^https?:\/\/(www\.)?opendrivelab\.com\/publications\/?(\?.*)?$/i,
129
- fetchItems,
130
- };
@@ -1,9 +1,13 @@
1
+ export const id = "opendrivelab";
2
+ export const name = "Opendrivelab";
3
+ export const listUrlPattern = /^https:\/\/(www\.)?opendrivelab\.com\/?(\?.*)?$/i;
4
+
1
5
  let _deps;
2
6
 
3
7
  // OpenDriveLab 首页插件:解析首页展示内容并输出 FeedItem(不含 enrich)
4
8
 
5
9
 
6
- const SITE_ID = "opendrivelab";
10
+ const SITE_ID = id;
7
11
  const NAVIGATION_TITLES = new Set([
8
12
  "news",
9
13
  "recruit",
@@ -58,7 +62,7 @@ function toAbsoluteHttpUrl(rawHref, baseUrl) {
58
62
  if (!href || href.startsWith("#") || href.startsWith("javascript:") || href.startsWith("mailto:")) return null;
59
63
  try {
60
64
  const url = new URL(href, baseUrl);
61
- if (!/^https?:$/i.test(url.protocol)) return null;
65
+ if (!/^https:$/i.test(url.protocol)) return null;
62
66
  return url.href;
63
67
  } catch {
64
68
  return null;
@@ -307,7 +311,7 @@ function parseFromTitleAnchors(root, finalUrl, seen) {
307
311
  return items;
308
312
  }
309
313
 
310
- async function fetchItems(sourceId, ctx) {
314
+ export async function fetchItems(sourceId, ctx) {
311
315
  _deps = ctx.deps;
312
316
  const { html, finalUrl } = await ctx.fetchHtml(sourceId, { waitMs: 4500 });
313
317
  const root = _deps.parseHtml(html);
@@ -326,8 +330,3 @@ async function fetchItems(sourceId, ctx) {
326
330
  throw new Error(`[${SITE_ID}] 未解析到首页条目,页面结构可能已变化`);
327
331
  }
328
332
 
329
- export default {
330
- id: SITE_ID,
331
- listUrlPattern: /^https?:\/\/(www\.)?opendrivelab\.com\/?(\?.*)?$/i,
332
- fetchItems,
333
- };
@@ -1,8 +1,12 @@
1
+ export const id = "paperswithcode";
2
+ export const name = "Paperswithcode";
3
+ export const listUrlPattern = /^https:\/\/(www\.)?paperswithcode\.(co|com)(?:\/(?:papers)?\/?)?(?:\?.*)?$/i;
4
+
1
5
  let _deps;
2
6
 
3
7
 
4
8
 
5
- const SITE_ID = "paperswithcode";
9
+ const SITE_ID = id;
6
10
  const API_ORIGIN = "https://paperswithcode.co";
7
11
  const DEFAULT_TRENDING_LIMIT = 30;
8
12
  const DEFAULT_MAX_AGE_DAYS = 180;
@@ -187,7 +191,7 @@ async function fetchLatestItems(sourceUrl) {
187
191
  }
188
192
 
189
193
 
190
- async function fetchItems(sourceId, _ctx) {
194
+ export async function fetchItems(sourceId, _ctx) {
191
195
  _deps = _ctx.deps;
192
196
  let sourceUrl;
193
197
  try {
@@ -219,9 +223,3 @@ async function fetchItems(sourceId, _ctx) {
219
223
  throw new Error(`[${SITE_ID}] 未解析到条目(${errors.join(" | ")})`);
220
224
  }
221
225
 
222
-
223
- export default {
224
- id: SITE_ID,
225
- listUrlPattern: /^https?:\/\/(www\.)?paperswithcode\.(co|com)(?:\/(?:papers)?\/?)?(?:\?.*)?$/i,
226
- fetchItems,
227
- };
@@ -1,8 +1,12 @@
1
+ export const id = "pjlab-adg-publications";
2
+ export const name = "PJLAB Adg Publications";
3
+ export const listUrlPattern = /^https:\/\/pjlab-adg\.github\.io\/publications\/?(\?.*)?$/i;
4
+
1
5
  let _deps;
2
6
 
3
7
 
4
8
 
5
- const SITE_ID = "pjlab-adg-publications";
9
+ const SITE_ID = id;
6
10
 
7
11
 
8
12
  function normalizeText(text) {
@@ -33,7 +37,7 @@ function toAbsoluteLink(rawHref, baseUrl) {
33
37
  if (!href || href.startsWith("#") || href.startsWith("javascript:")) return null;
34
38
  try {
35
39
  const url = new URL(href, baseUrl);
36
- if (!/^https?:$/i.test(url.protocol)) return null;
40
+ if (!/^https:$/i.test(url.protocol)) return null;
37
41
  return url.href;
38
42
  } catch {
39
43
  return null;
@@ -140,7 +144,7 @@ function parseOneEntry(liNode, currentYear, pageUrl) {
140
144
  const fallbackYear = parseYear(`${periodical} ${detailNode.textContent}`);
141
145
  const finalYear = currentYear ?? fallbackYear;
142
146
  const pubDate = finalYear != null ? new Date(Date.UTC(finalYear, 0, 1, 0, 0, 0)) : new Date();
143
- const badge = normalizeText((liNode.querySelector(".abbr .badge") ?? liNode.querySelector("abbr"))?.textContent) || undefined;
147
+ const _badge = normalizeText((liNode.querySelector(".abbr .badge") ?? liNode.querySelector("abbr"))?.textContent) || undefined;
144
148
  const link = pickBestLink(detailNode, pageUrl, entryId);
145
149
  const guidSeed = entryId || link || `${title}|${author ?? ""}|${finalYear ?? ""}`;
146
150
 
@@ -183,7 +187,7 @@ function parseItems(html, finalUrl) {
183
187
  }
184
188
 
185
189
 
186
- async function fetchItems(sourceId, ctx) {
190
+ export async function fetchItems(sourceId, ctx) {
187
191
  _deps = ctx.deps;
188
192
  const { html, finalUrl } = await ctx.fetchHtml(sourceId, { waitMs: 3500 });
189
193
  const pageUrl = finalUrl || sourceId;
@@ -194,9 +198,3 @@ async function fetchItems(sourceId, ctx) {
194
198
  return items;
195
199
  }
196
200
 
197
-
198
- export default {
199
- id: SITE_ID,
200
- listUrlPattern: /^https?:\/\/pjlab-adg\.github\.io\/publications\/?(\?.*)?$/i,
201
- fetchItems,
202
- };