rssany 0.1.2 → 0.1.5

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 (75) hide show
  1. package/README.md +28 -50
  2. package/app/plugins/builtin/agi-eval-evaluation.rssany.js +188 -0
  3. package/app/plugins/builtin/amii-research-talent.rssany.js +73 -0
  4. package/app/plugins/builtin/anthropic-research.rssany.js +155 -0
  5. package/app/plugins/builtin/appen-resources.rssany.js +155 -0
  6. package/app/plugins/builtin/baai-wudao-paper-article.rssany.js +185 -0
  7. package/app/plugins/builtin/baaidata-csdn.rssany.js +242 -0
  8. package/app/plugins/builtin/baidu-research.rssany.js +222 -0
  9. package/app/plugins/builtin/brightdata-blog.rssany.js +301 -0
  10. package/app/plugins/builtin/bytedance-seed-research.rssany.js +231 -0
  11. package/app/plugins/builtin/five-radar.rssany.js +490 -0
  12. package/app/plugins/builtin/flageval-news.rssany.js +118 -0
  13. package/app/plugins/builtin/google-deepmind-research.rssany.js +223 -0
  14. package/app/plugins/builtin/google-research-datasets.rssany.js +171 -0
  15. package/app/plugins/builtin/google-research.rssany.js +220 -0
  16. package/app/plugins/builtin/google.rssany.js +187 -0
  17. package/app/plugins/builtin/hacker-news-newest.rssany.js +130 -0
  18. package/app/plugins/builtin/harvard-dataverse.rssany.js +166 -0
  19. package/app/plugins/builtin/huaweicloud-bbs-blogs.rssany.js +185 -0
  20. package/app/plugins/builtin/lingowhale.rssany.js +119 -0
  21. package/app/plugins/builtin/meituan-tech.rssany.js +130 -0
  22. package/app/plugins/builtin/meta-ai-publications.rssany.js +221 -0
  23. package/app/plugins/builtin/mila-quebec.rssany.js +199 -0
  24. package/app/plugins/builtin/mit-csail-research.rssany.js +208 -0
  25. package/app/plugins/builtin/moonshot.rssany.js +127 -0
  26. package/app/plugins/builtin/opendatalab-news.rssany.js +174 -0
  27. package/app/plugins/builtin/opendatalab.rssany.js +109 -0
  28. package/app/plugins/builtin/opendrivelab-autonomous-driving.rssany.js +114 -0
  29. package/app/plugins/builtin/opendrivelab-embodiedai.rssany.js +114 -0
  30. package/app/plugins/builtin/opendrivelab-publications.rssany.js +130 -0
  31. package/app/plugins/builtin/opendrivelab.rssany.js +333 -0
  32. package/app/plugins/builtin/paperswithcode.rssany.js +227 -0
  33. package/app/plugins/builtin/pjlab-adg-publications.rssany.js +202 -0
  34. package/app/plugins/builtin/rss.rssany.js +11 -1
  35. package/app/plugins/builtin/selectdataset.rssany.js +206 -0
  36. package/app/plugins/builtin/sensetime-tech-achievements.rssany.js +154 -0
  37. package/app/plugins/builtin/supervisely-blog.rssany.js +159 -0
  38. package/app/plugins/builtin/uci-ml-repository.rssany.js +111 -0
  39. package/app/plugins/builtin/venturebeat.rssany.js +97 -0
  40. package/app/plugins/builtin/worldlabs.rssany.js +129 -0
  41. package/app/plugins/builtin/x.rssany.js +159 -0
  42. package/app/plugins/builtin/xiaohongshu.rssany.js +283 -0
  43. package/app/plugins/builtin/zhipu-research.rssany.js +334 -0
  44. package/dist/index.js +79 -9
  45. package/dist/index.js.map +1 -1
  46. package/package.json +1 -1
  47. package/webui/build/200.html +6 -6
  48. package/webui/build/_app/immutable/assets/0.BB88QFoe.css +1 -0
  49. package/webui/build/_app/immutable/assets/{homeFeedPanelStore.BopJZtHu.css → homeFeedPanelStore.iOmfP2qL.css} +1 -1
  50. package/webui/build/_app/immutable/chunks/CZD-YNDw.js +31 -0
  51. package/webui/build/_app/immutable/chunks/{DcAshVxe.js → D6VIKef0.js} +1 -1
  52. package/webui/build/_app/immutable/chunks/{EIZIMsXK.js → Dbqx2mXq.js} +1 -1
  53. package/webui/build/_app/immutable/chunks/DeX-oq5W.js +41 -0
  54. package/webui/build/_app/immutable/chunks/{BXCWEhUd.js → dhB8G5Is.js} +1 -1
  55. package/webui/build/_app/immutable/entry/{app.DdgnooOk.js → app.XPso7q7g.js} +2 -2
  56. package/webui/build/_app/immutable/entry/start.Db4snNCd.js +1 -0
  57. package/webui/build/_app/immutable/nodes/0.BKTQePmA.js +11 -0
  58. package/webui/build/_app/immutable/nodes/{1.5DFDaT4c.js → 1.BS3_Rfxm.js} +1 -1
  59. package/webui/build/_app/immutable/nodes/{10.OVK4i9XE.js → 10.CyyxDCIS.js} +1 -1
  60. package/webui/build/_app/immutable/nodes/{11.Dhn_rO4A.js → 11.CtYgIaGj.js} +1 -1
  61. package/webui/build/_app/immutable/nodes/{14.B_KpJLxn.js → 14.D5OEGPR2.js} +1 -1
  62. package/webui/build/_app/immutable/nodes/{15.RaWaA-0I.js → 15.B4dFN1Gk.js} +1 -1
  63. package/webui/build/_app/immutable/nodes/{16.DSUgqolV.js → 16.M7ZII7tl.js} +1 -1
  64. package/webui/build/_app/immutable/nodes/{3.wQvGs9w-.js → 3.7r8v7qkm.js} +1 -1
  65. package/webui/build/_app/immutable/nodes/{5.CCtn90c0.js → 5.CHIzoGrb.js} +1 -1
  66. package/webui/build/_app/immutable/nodes/{6.C2_mjW1u.js → 6.BDBqx-GY.js} +1 -1
  67. package/webui/build/_app/immutable/nodes/{7.Dwz6W7A1.js → 7.D5czsDmz.js} +1 -1
  68. package/webui/build/_app/immutable/nodes/{8.DzkEw6rx.js → 8.pjVNsCdV.js} +1 -1
  69. package/webui/build/_app/immutable/nodes/{9.DtlXEwe1.js → 9.CsARv1BH.js} +1 -1
  70. package/webui/build/_app/version.json +1 -1
  71. package/webui/build/_app/immutable/assets/0.C6Q_nuW9.css +0 -1
  72. package/webui/build/_app/immutable/chunks/CkUAV0m0.js +0 -41
  73. package/webui/build/_app/immutable/chunks/CtijX1u3.js +0 -31
  74. package/webui/build/_app/immutable/entry/start.DhJaJZhR.js +0 -1
  75. package/webui/build/_app/immutable/nodes/0.BE05Cuc4.js +0 -11
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import { promisify } from "node:util";
10
10
  import puppeteerCore from "puppeteer-core";
11
11
  import { parse, NodeType } from "node-html-parser";
12
12
  import Database from "better-sqlite3";
13
- import { mkdir, copyFile, access, rename, readFile, writeFile, readdir, stat, unlink } from "node:fs/promises";
13
+ import { mkdir, writeFile, copyFile, access, rename, readFile, readdir, stat, unlink } from "node:fs/promises";
14
14
  import { fileURLToPath, pathToFileURL } from "node:url";
15
15
  import { createHash } from "node:crypto";
16
16
  import { JSDOM } from "jsdom";
@@ -134,6 +134,18 @@ function getEffectiveItemFields(item, lng) {
134
134
  content: (t?.content != null && t.content !== "" ? t.content : item.content) ?? ""
135
135
  };
136
136
  }
137
+ function pubDateToIsoOrNull(pubDate) {
138
+ if (pubDate == null) return null;
139
+ if (pubDate instanceof Date) {
140
+ const ms = pubDate.getTime();
141
+ return Number.isNaN(ms) ? null : pubDate.toISOString();
142
+ }
143
+ if (typeof pubDate === "string") {
144
+ const s = pubDate.trim();
145
+ return s || null;
146
+ }
147
+ return null;
148
+ }
137
149
  function normalizeAuthor(author) {
138
150
  if (author == null) return void 0;
139
151
  if (Array.isArray(author)) return author.filter((s2) => typeof s2 === "string" && s2.trim()).map((s2) => s2.trim());
@@ -176,16 +188,18 @@ function mergeSourceStatsRows(rows) {
176
188
  for (const row of rows) {
177
189
  const k = canonicalHttpSourceRef(row.source_url);
178
190
  const prev = map.get(k);
191
+ const count7 = row.count_7d ?? 0;
179
192
  if (!prev) {
180
- map.set(k, { count: row.count, latest_at: row.latest_at });
193
+ map.set(k, { count: row.count, count_7d: count7, latest_at: row.latest_at });
181
194
  } else {
182
195
  map.set(k, {
183
196
  count: prev.count + row.count,
197
+ count_7d: prev.count_7d + count7,
184
198
  latest_at: maxIso(prev.latest_at, row.latest_at)
185
199
  });
186
200
  }
187
201
  }
188
- return [...map.entries()].map(([source_url, v]) => ({ source_url, count: v.count, latest_at: v.latest_at })).sort((a, b) => b.count - a.count);
202
+ return [...map.entries()].map(([source_url, v]) => ({ source_url, count: v.count, count_7d: v.count_7d, latest_at: v.latest_at })).sort((a, b) => b.count - a.count);
189
203
  }
190
204
  const httpSourceRef = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
191
205
  __proto__: null,
@@ -206,6 +220,9 @@ const CONFIG_PATH = join(USER_DIR, "config.json");
206
220
  const LEGACY_SUBSCRIPTIONS_PATH = join(USER_DIR, "subscriptions.json");
207
221
  const BUILTIN_PLUGINS_DIR = join(PACKAGE_ROOT, "app/plugins/builtin");
208
222
  const USER_PLUGINS_DIR = join(USER_DIR, "plugins");
223
+ const USER_DIR_PACKAGE_JSON = join(USER_DIR, "package.json");
224
+ const USER_DIR_PACKAGE_JSON_MINIMAL = `${JSON.stringify({ type: "module", private: true, description: "RssAny user data root; marks plugins as ESM for Node" })}
225
+ `;
209
226
  const PLUGIN_SITE_TEMPLATE_PATH = join(PACKAGE_ROOT, "app/plugins/site.rssany.js");
210
227
  async function pathExists(p) {
211
228
  try {
@@ -250,11 +267,24 @@ async function seedExampleConfigsIfMissing() {
250
267
  }
251
268
  }
252
269
  }
270
+ async function ensureUserDirPackageJsonForPlugins() {
271
+ if (await pathExists(USER_DIR_PACKAGE_JSON)) return;
272
+ try {
273
+ await writeFile(USER_DIR_PACKAGE_JSON, USER_DIR_PACKAGE_JSON_MINIMAL, "utf-8");
274
+ logger.info("config", "已写入 .rssany/package.json(type: module,消除插件 ESM 歧义)", { path: USER_DIR_PACKAGE_JSON });
275
+ } catch (err) {
276
+ logger.warn("config", "写入 .rssany/package.json 失败", {
277
+ path: USER_DIR_PACKAGE_JSON,
278
+ err: err instanceof Error ? err.message : String(err)
279
+ });
280
+ }
281
+ }
253
282
  async function initUserDir() {
254
283
  await mkdir(USER_DIR, { recursive: true });
255
284
  await mkdir(DATA_DIR, { recursive: true });
256
285
  await mkdir(CACHE_DIR, { recursive: true });
257
286
  await mkdir(USER_PLUGINS_DIR, { recursive: true });
287
+ await ensureUserDirPackageJsonForPlugins();
258
288
  await seedExampleConfigsIfMissing();
259
289
  if (!await pathExists(SOURCES_CONFIG_PATH) && await pathExists(LEGACY_SUBSCRIPTIONS_PATH)) {
260
290
  await migrateFile(LEGACY_SUBSCRIPTIONS_PATH, SOURCES_CONFIG_PATH);
@@ -619,7 +649,7 @@ async function upsertItems(items, sourceUrlOverride) {
619
649
  const nextSummary = normalizeText(item.summary) || null;
620
650
  const nextAuthorArr = normalizeAuthor(item.author);
621
651
  const nextAuthor = nextAuthorArr?.length ? JSON.stringify(nextAuthorArr) : null;
622
- const nextPubDate = item.pubDate instanceof Date ? item.pubDate.toISOString() : item.pubDate ?? null;
652
+ const nextPubDate = pubDateToIsoOrNull(item.pubDate);
623
653
  const nextTags = item.tags?.length ? JSON.stringify(item.tags) : null;
624
654
  const nextImageUrl = typeof item.imageUrl === "string" && item.imageUrl.trim() ? item.imageUrl.trim() : null;
625
655
  const info = stmt.run({
@@ -687,7 +717,7 @@ async function updateItemContent(item) {
687
717
  const arr = normalizeAuthor(item.author);
688
718
  return arr?.length ? JSON.stringify(arr) : null;
689
719
  })(),
690
- pubDate: item.pubDate instanceof Date ? item.pubDate.toISOString() : item.pubDate ?? null,
720
+ pubDate: pubDateToIsoOrNull(item.pubDate),
691
721
  tags: item.tags?.length ? JSON.stringify(item.tags) : null,
692
722
  translations: item.translations && Object.keys(item.translations).length > 0 ? JSON.stringify(item.translations) : null
693
723
  });
@@ -864,7 +894,11 @@ async function getSourceStats() {
864
894
  const { mergeSourceStatsRows: mergeSourceStatsRows2 } = await Promise.resolve().then(() => httpSourceRef);
865
895
  const db = await getDb();
866
896
  const rows = db.prepare(
867
- "SELECT source_url, COUNT(*) as count, MAX(COALESCE(pub_date, fetched_at)) as latest_at FROM items GROUP BY source_url ORDER BY count DESC"
897
+ `SELECT source_url,
898
+ COUNT(*) as count,
899
+ SUM(CASE WHEN julianday(fetched_at) >= julianday('now', '-7 days') THEN 1 ELSE 0 END) as count_7d,
900
+ MAX(COALESCE(pub_date, fetched_at)) as latest_at
901
+ FROM items GROUP BY source_url ORDER BY count DESC`
868
902
  ).all();
869
903
  return mergeSourceStatsRows2(rows);
870
904
  }
@@ -2514,7 +2548,7 @@ function feedItemsToPayload(items) {
2514
2548
  guid: i.guid,
2515
2549
  title: i.title,
2516
2550
  link: i.link,
2517
- pubDate: i.pubDate instanceof Date ? i.pubDate.toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
2551
+ pubDate: pubDateToIsoOrNull(i.pubDate) ?? (/* @__PURE__ */ new Date()).toISOString(),
2518
2552
  author: i.author,
2519
2553
  summary: i.summary,
2520
2554
  content: i.content,
@@ -2575,7 +2609,7 @@ function toRssEntry(item, lng) {
2575
2609
  link: item.link,
2576
2610
  description: desc,
2577
2611
  guid: item.guid,
2578
- published: item.pubDate?.toISOString?.() ?? void 0,
2612
+ published: pubDateToIsoOrNull(item.pubDate) ?? void 0,
2579
2613
  imageUrl: item.imageUrl
2580
2614
  };
2581
2615
  }
@@ -2957,7 +2991,7 @@ function registerRssApiRoutes(app) {
2957
2991
  link: item.link,
2958
2992
  summary,
2959
2993
  author: item.author,
2960
- pubDate: item.pubDate instanceof Date ? item.pubDate.toISOString() : item.pubDate
2994
+ pubDate: pubDateToIsoOrNull(item.pubDate)
2961
2995
  };
2962
2996
  })
2963
2997
  });
@@ -3369,6 +3403,42 @@ function registerSourcesRoutes(app) {
3369
3403
  return c.json({});
3370
3404
  }
3371
3405
  });
3406
+ app.post("/api/sources/open-browser", requireAdmin(), async (c) => {
3407
+ try {
3408
+ const body = await c.req.json();
3409
+ const raw = typeof body?.url === "string" ? body.url.trim() : "";
3410
+ if (!raw) return c.json({ ok: false, message: "缺少 url" }, 400);
3411
+ const lower = raw.toLowerCase();
3412
+ if (!lower.startsWith("http://") && !lower.startsWith("https://")) {
3413
+ return c.json({ ok: false, message: "仅支持 http(s) URL" }, 400);
3414
+ }
3415
+ const url = raw;
3416
+ const source = getSource(url);
3417
+ const merged = await getEffectiveProxyForListUrl(url, source);
3418
+ const proxy = resolveProxy({ proxy: merged });
3419
+ void launchBrowser({ headless: false, cacheDir: CACHE_DIR, proxy }).then(async (browser) => {
3420
+ try {
3421
+ const page = await browser.newPage();
3422
+ await applyProxyAuthToPage(page, { proxy: merged });
3423
+ const realUserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
3424
+ await page.setUserAgent(realUserAgent);
3425
+ await page.setViewport({ width: 1366, height: 960 });
3426
+ await page.goto(url, { waitUntil: "domcontentloaded", timeout: 6e4 });
3427
+ page.once("close", () => {
3428
+ void browser.close().catch(() => {
3429
+ });
3430
+ });
3431
+ } catch {
3432
+ await browser.close().catch(() => {
3433
+ });
3434
+ }
3435
+ }).catch(() => {
3436
+ });
3437
+ return c.json({ ok: true, message: "已在爬虫浏览器中打开" });
3438
+ } catch {
3439
+ return c.json({ ok: false, message: "请求体无效" }, 400);
3440
+ }
3441
+ });
3372
3442
  app.get("/api/sources/raw", requireAdmin(), async (c) => {
3373
3443
  try {
3374
3444
  const raw = await getSourcesRaw();