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.
- package/README.md +28 -50
- package/app/plugins/builtin/agi-eval-evaluation.rssany.js +188 -0
- package/app/plugins/builtin/amii-research-talent.rssany.js +73 -0
- package/app/plugins/builtin/anthropic-research.rssany.js +155 -0
- package/app/plugins/builtin/appen-resources.rssany.js +155 -0
- package/app/plugins/builtin/baai-wudao-paper-article.rssany.js +185 -0
- package/app/plugins/builtin/baaidata-csdn.rssany.js +242 -0
- package/app/plugins/builtin/baidu-research.rssany.js +222 -0
- package/app/plugins/builtin/brightdata-blog.rssany.js +301 -0
- package/app/plugins/builtin/bytedance-seed-research.rssany.js +231 -0
- package/app/plugins/builtin/five-radar.rssany.js +490 -0
- package/app/plugins/builtin/flageval-news.rssany.js +118 -0
- package/app/plugins/builtin/google-deepmind-research.rssany.js +223 -0
- package/app/plugins/builtin/google-research-datasets.rssany.js +171 -0
- package/app/plugins/builtin/google-research.rssany.js +220 -0
- package/app/plugins/builtin/google.rssany.js +187 -0
- package/app/plugins/builtin/hacker-news-newest.rssany.js +130 -0
- package/app/plugins/builtin/harvard-dataverse.rssany.js +166 -0
- package/app/plugins/builtin/huaweicloud-bbs-blogs.rssany.js +185 -0
- package/app/plugins/builtin/lingowhale.rssany.js +119 -0
- package/app/plugins/builtin/meituan-tech.rssany.js +130 -0
- package/app/plugins/builtin/meta-ai-publications.rssany.js +221 -0
- package/app/plugins/builtin/mila-quebec.rssany.js +199 -0
- package/app/plugins/builtin/mit-csail-research.rssany.js +208 -0
- package/app/plugins/builtin/moonshot.rssany.js +127 -0
- package/app/plugins/builtin/opendatalab-news.rssany.js +174 -0
- package/app/plugins/builtin/opendatalab.rssany.js +109 -0
- package/app/plugins/builtin/opendrivelab-autonomous-driving.rssany.js +114 -0
- package/app/plugins/builtin/opendrivelab-embodiedai.rssany.js +114 -0
- package/app/plugins/builtin/opendrivelab-publications.rssany.js +130 -0
- package/app/plugins/builtin/opendrivelab.rssany.js +333 -0
- package/app/plugins/builtin/paperswithcode.rssany.js +227 -0
- package/app/plugins/builtin/pjlab-adg-publications.rssany.js +202 -0
- package/app/plugins/builtin/rss.rssany.js +11 -1
- package/app/plugins/builtin/selectdataset.rssany.js +206 -0
- package/app/plugins/builtin/sensetime-tech-achievements.rssany.js +154 -0
- package/app/plugins/builtin/supervisely-blog.rssany.js +159 -0
- package/app/plugins/builtin/uci-ml-repository.rssany.js +111 -0
- package/app/plugins/builtin/venturebeat.rssany.js +97 -0
- package/app/plugins/builtin/worldlabs.rssany.js +129 -0
- package/app/plugins/builtin/x.rssany.js +159 -0
- package/app/plugins/builtin/xiaohongshu.rssany.js +283 -0
- package/app/plugins/builtin/zhipu-research.rssany.js +334 -0
- package/dist/index.js +79 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/webui/build/200.html +6 -6
- package/webui/build/_app/immutable/assets/0.BB88QFoe.css +1 -0
- package/webui/build/_app/immutable/assets/{homeFeedPanelStore.BopJZtHu.css → homeFeedPanelStore.iOmfP2qL.css} +1 -1
- package/webui/build/_app/immutable/chunks/CZD-YNDw.js +31 -0
- package/webui/build/_app/immutable/chunks/{DcAshVxe.js → D6VIKef0.js} +1 -1
- package/webui/build/_app/immutable/chunks/{EIZIMsXK.js → Dbqx2mXq.js} +1 -1
- package/webui/build/_app/immutable/chunks/DeX-oq5W.js +41 -0
- package/webui/build/_app/immutable/chunks/{BXCWEhUd.js → dhB8G5Is.js} +1 -1
- package/webui/build/_app/immutable/entry/{app.DdgnooOk.js → app.XPso7q7g.js} +2 -2
- package/webui/build/_app/immutable/entry/start.Db4snNCd.js +1 -0
- package/webui/build/_app/immutable/nodes/0.BKTQePmA.js +11 -0
- package/webui/build/_app/immutable/nodes/{1.5DFDaT4c.js → 1.BS3_Rfxm.js} +1 -1
- package/webui/build/_app/immutable/nodes/{10.OVK4i9XE.js → 10.CyyxDCIS.js} +1 -1
- package/webui/build/_app/immutable/nodes/{11.Dhn_rO4A.js → 11.CtYgIaGj.js} +1 -1
- package/webui/build/_app/immutable/nodes/{14.B_KpJLxn.js → 14.D5OEGPR2.js} +1 -1
- package/webui/build/_app/immutable/nodes/{15.RaWaA-0I.js → 15.B4dFN1Gk.js} +1 -1
- package/webui/build/_app/immutable/nodes/{16.DSUgqolV.js → 16.M7ZII7tl.js} +1 -1
- package/webui/build/_app/immutable/nodes/{3.wQvGs9w-.js → 3.7r8v7qkm.js} +1 -1
- package/webui/build/_app/immutable/nodes/{5.CCtn90c0.js → 5.CHIzoGrb.js} +1 -1
- package/webui/build/_app/immutable/nodes/{6.C2_mjW1u.js → 6.BDBqx-GY.js} +1 -1
- package/webui/build/_app/immutable/nodes/{7.Dwz6W7A1.js → 7.D5czsDmz.js} +1 -1
- package/webui/build/_app/immutable/nodes/{8.DzkEw6rx.js → 8.pjVNsCdV.js} +1 -1
- package/webui/build/_app/immutable/nodes/{9.DtlXEwe1.js → 9.CsARv1BH.js} +1 -1
- package/webui/build/_app/version.json +1 -1
- package/webui/build/_app/immutable/assets/0.C6Q_nuW9.css +0 -1
- package/webui/build/_app/immutable/chunks/CkUAV0m0.js +0 -41
- package/webui/build/_app/immutable/chunks/CtijX1u3.js +0 -31
- package/webui/build/_app/immutable/entry/start.DhJaJZhR.js +0 -1
- 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,
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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();
|