rssany 0.2.0 → 0.3.1
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 +22 -22
- package/app/plugins/builtin/agi-eval-evaluation.rssany.js +6 -7
- package/app/plugins/builtin/amii-research-talent.rssany.js +6 -7
- package/app/plugins/builtin/anthropic-research.rssany.js +6 -8
- package/app/plugins/builtin/appen-resources.rssany.js +6 -7
- package/app/plugins/builtin/baai-wudao-paper-article.rssany.js +9 -10
- package/app/plugins/builtin/baaidata-csdn.rssany.js +6 -7
- package/app/plugins/builtin/baidu-research.rssany.js +5 -8
- package/app/plugins/builtin/brightdata-blog.rssany.js +6 -11
- package/app/plugins/builtin/bytedance-seed-research.rssany.js +5 -7
- package/app/plugins/builtin/email.rssany.js +9 -9
- package/app/plugins/builtin/five-radar.rssany.js +9 -11
- package/app/plugins/builtin/flageval-news.rssany.js +5 -7
- package/app/plugins/builtin/google-deepmind-research.rssany.js +6 -8
- package/app/plugins/builtin/google-research-datasets.rssany.js +6 -8
- package/app/plugins/builtin/google-research.rssany.js +6 -8
- package/app/plugins/builtin/hacker-news-newest.rssany.js +7 -9
- package/app/plugins/builtin/harvard-dataverse.rssany.js +6 -8
- package/app/plugins/builtin/huaweicloud-bbs-blogs.rssany.js +7 -9
- package/app/plugins/builtin/lingowhale.rssany.js +7 -9
- package/app/plugins/builtin/meituan-tech.rssany.js +7 -10
- package/app/plugins/builtin/meta-ai-publications.rssany.js +6 -11
- package/app/plugins/builtin/mila-quebec.rssany.js +6 -8
- package/app/plugins/builtin/mit-csail-research.rssany.js +7 -9
- package/app/plugins/builtin/moonshot.rssany.js +6 -8
- package/app/plugins/builtin/opendatalab-news.rssany.js +6 -7
- package/app/plugins/builtin/opendatalab.rssany.js +5 -6
- package/app/plugins/builtin/opendrivelab-autonomous-driving.rssany.js +6 -7
- package/app/plugins/builtin/opendrivelab-embodiedai.rssany.js +7 -8
- package/app/plugins/builtin/opendrivelab-publications.rssany.js +6 -8
- package/app/plugins/builtin/opendrivelab.rssany.js +7 -8
- package/app/plugins/builtin/paperswithcode.rssany.js +6 -8
- package/app/plugins/builtin/pjlab-adg-publications.rssany.js +7 -9
- package/app/plugins/builtin/rss.rssany.js +11 -12
- package/app/plugins/builtin/selectdataset.rssany.js +6 -8
- package/app/plugins/builtin/sensetime-tech-achievements.rssany.js +7 -8
- package/app/plugins/builtin/supervisely-blog.rssany.js +6 -8
- package/app/plugins/builtin/theinformation-briefings.rssany.js +7 -13
- package/app/plugins/builtin/uci-ml-repository.rssany.js +6 -7
- package/app/plugins/builtin/venturebeat.rssany.js +7 -9
- package/app/plugins/builtin/worldlabs.rssany.js +6 -8
- package/app/plugins/builtin/x.rssany.js +7 -9
- package/app/plugins/builtin/xiaohongshu.rssany.js +119 -56
- package/app/plugins/builtin/zhipu-research.rssany.js +5 -8
- package/app/plugins/site.rssany.js +25 -26
- package/{statics → app/statics}/README.md +7 -7
- package/app/webui/build/200.html +51 -0
- package/{webui/build/_app/immutable/assets/0.BB88QFoe.css → app/webui/build/_app/immutable/assets/0.DsKls1SN.css} +1 -1
- package/app/webui/build/_app/immutable/assets/13.Qu_tY6H9.css +1 -0
- package/app/webui/build/_app/immutable/assets/14.DfMfOrS3.css +1 -0
- package/app/webui/build/_app/immutable/assets/16.Cw9oSkcO.css +1 -0
- package/app/webui/build/_app/immutable/assets/4.Di6rvlY-.css +1 -0
- package/{webui/build/_app/immutable/assets/SourcesList.yTBBi3_m.css → app/webui/build/_app/immutable/assets/SourcesList.D5Lso0bo.css} +1 -1
- package/{webui/build/_app/immutable/assets/homeFeedPanelStore.CSvlNcpm.css → app/webui/build/_app/immutable/assets/homeFeedPanelStore.CE6xTfsa.css} +1 -1
- package/app/webui/build/_app/immutable/chunks/6prdYIKP.js +1 -0
- package/{webui/build/_app/immutable/chunks/Xy_fhzQq.js → app/webui/build/_app/immutable/chunks/B-CeeY89.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/B2cyTHdf.js +2 -0
- package/{webui/build/_app/immutable/chunks/DjNLq3TF.js → app/webui/build/_app/immutable/chunks/B6WG2Sd3.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/BA4Gucnq.js +1 -0
- package/{webui/build/_app/immutable/chunks/xtNWTdbD.js → app/webui/build/_app/immutable/chunks/BAJAS8BI.js} +1 -1
- package/{webui/build/_app/immutable/chunks/Dt2CddFe.js → app/webui/build/_app/immutable/chunks/BkD3yAYe.js} +1 -1
- package/{webui/build/_app/immutable/chunks/DFuhmi31.js → app/webui/build/_app/immutable/chunks/C4uF_YIK.js} +1 -1
- package/{webui/build/_app/immutable/chunks/Dw782Tjs.js → app/webui/build/_app/immutable/chunks/C8umpVpB.js} +1 -1
- package/{webui/build/_app/immutable/chunks/BQqoDzLx.js → app/webui/build/_app/immutable/chunks/CFwxUBGi.js} +1 -1
- package/{webui/build/_app/immutable/chunks/tB7QMF3U.js → app/webui/build/_app/immutable/chunks/CGCMIfh3.js} +1 -1
- package/{webui/build/_app/immutable/chunks/BK3WtZwv.js → app/webui/build/_app/immutable/chunks/CS53ooo0.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/CVW0ymE1.js +1 -0
- package/{webui/build/_app/immutable/chunks/B-OsL1Ct.js → app/webui/build/_app/immutable/chunks/ChUctqXA.js} +1 -1
- package/{webui/build/_app/immutable/chunks/D5GvRCv7.js → app/webui/build/_app/immutable/chunks/ClknbeNl.js} +1 -1
- package/{webui/build/_app/immutable/chunks/Bu9HsS-V.js → app/webui/build/_app/immutable/chunks/CqYSO3Dx.js} +1 -1
- package/{webui/build/_app/immutable/chunks/CWNeClHp.js → app/webui/build/_app/immutable/chunks/D6kzEN_P.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/DAdOEnFb.js +1 -0
- package/{webui/build/_app/immutable/chunks/Cihqbfi5.js → app/webui/build/_app/immutable/chunks/DCEayuDt.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/DJ2e04vK.js +36 -0
- package/{webui/build/_app/immutable/chunks/DEDI7Ecm.js → app/webui/build/_app/immutable/chunks/DL3Q5sfb.js} +1 -1
- package/{webui/build/_app/immutable/chunks/CVzlFH44.js → app/webui/build/_app/immutable/chunks/DVa8Y-mQ.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/DkamXS6W.js +36 -0
- package/app/webui/build/_app/immutable/chunks/DoRPmqLn.js +2 -0
- package/app/webui/build/_app/immutable/chunks/DsxvjlCC.js +13 -0
- package/{webui/build/_app/immutable/chunks/Bp63qm3L.js → app/webui/build/_app/immutable/chunks/Dyvi1wBH.js} +1 -1
- package/{webui/build/_app/immutable/chunks/CmjOpds-.js → app/webui/build/_app/immutable/chunks/_qj9U-za.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/vtBo8kBV.js +1 -0
- package/app/webui/build/_app/immutable/entry/app.RFfWi3_i.js +2 -0
- package/app/webui/build/_app/immutable/entry/start.DU_kyeGS.js +1 -0
- package/{webui/build/_app/immutable/nodes/0.I1lQdWMl.js → app/webui/build/_app/immutable/nodes/0.DK_mcVDm.js} +1 -1
- package/app/webui/build/_app/immutable/nodes/1.0PRrU2uQ.js +1 -0
- package/{webui/build/_app/immutable/nodes/10.CvfUsqsw.js → app/webui/build/_app/immutable/nodes/10.CsxzlUER.js} +1 -1
- package/app/webui/build/_app/immutable/nodes/11.D-PkhIRW.js +1 -0
- package/{webui/build/_app/immutable/nodes/12.DVFJuIWI.js → app/webui/build/_app/immutable/nodes/12.GGf-JLUY.js} +1 -1
- package/app/webui/build/_app/immutable/nodes/13.DWWcH27k.js +6 -0
- package/app/webui/build/_app/immutable/nodes/14.COwSLwDN.js +1 -0
- package/app/webui/build/_app/immutable/nodes/15.nDN_AHrs.js +1 -0
- package/app/webui/build/_app/immutable/nodes/16.zfSe93Ab.js +24 -0
- package/app/webui/build/_app/immutable/nodes/2.AJd2163d.js +1 -0
- package/app/webui/build/_app/immutable/nodes/3.CEVEHuaH.js +1 -0
- package/app/webui/build/_app/immutable/nodes/4.BT_N8pCh.js +2 -0
- package/{webui/build/_app/immutable/nodes/5.B6fR3n6J.js → app/webui/build/_app/immutable/nodes/5.BZScQ2CH.js} +1 -1
- package/{webui/build/_app/immutable/nodes/6.j2O5Mwjv.js → app/webui/build/_app/immutable/nodes/6.CkFk8X--.js} +1 -1
- package/app/webui/build/_app/immutable/nodes/7.CuQJk7te.js +1 -0
- package/{webui/build/_app/immutable/nodes/8.Bw_d63B_.js → app/webui/build/_app/immutable/nodes/8.DIavWJnU.js} +1 -1
- package/{webui/build/_app/immutable/nodes/9.pMMi5PP6.js → app/webui/build/_app/immutable/nodes/9.Db30M8x0.js} +1 -1
- package/app/webui/build/_app/version.json +1 -0
- package/app/webui/build/apple-touch-icon.png +0 -0
- package/app/webui/build/favicon.ico +0 -0
- package/app/webui/build/favicon.png +0 -0
- package/bin/rssany.js +226 -6
- package/dist/index.js +209 -152
- package/dist/index.js.map +1 -1
- package/package.json +22 -16
- package/scripts/dev.mjs +114 -0
- package/scripts/reset.mjs +1 -1
- package/init/config.json +0 -17
- package/init/sources.json +0 -353
- package/statics/401.html +0 -56
- package/statics/404.html +0 -12
- package/statics/image.png +0 -0
- package/webui/build/200.html +0 -49
- package/webui/build/_app/immutable/assets/13.BhO9zvFi.css +0 -1
- package/webui/build/_app/immutable/assets/14.CujIhjQK.css +0 -1
- package/webui/build/_app/immutable/assets/16.PP9XLDf7.css +0 -1
- package/webui/build/_app/immutable/assets/4.9wPHhVwv.css +0 -1
- package/webui/build/_app/immutable/chunks/5LVkDJzw.js +0 -1
- package/webui/build/_app/immutable/chunks/B2Q1a1-H.js +0 -2
- package/webui/build/_app/immutable/chunks/BbWUOQ_m.js +0 -1
- package/webui/build/_app/immutable/chunks/Bns1MuyM.js +0 -36
- package/webui/build/_app/immutable/chunks/DMWEh-Ek.js +0 -2
- package/webui/build/_app/immutable/chunks/bvuf_jZd.js +0 -36
- package/webui/build/_app/immutable/chunks/lk5LaiqA.js +0 -1
- package/webui/build/_app/immutable/chunks/mW5RwvnK.js +0 -13
- package/webui/build/_app/immutable/entry/app.BVkrDt5l.js +0 -2
- package/webui/build/_app/immutable/entry/start.D3Q-BMMd.js +0 -1
- package/webui/build/_app/immutable/nodes/1.BiQQfx2j.js +0 -1
- package/webui/build/_app/immutable/nodes/11.B4LHPNL6.js +0 -1
- package/webui/build/_app/immutable/nodes/13.nT3SOzEB.js +0 -1
- package/webui/build/_app/immutable/nodes/14.DfaAf0f8.js +0 -1
- package/webui/build/_app/immutable/nodes/15.CMzkX9OK.js +0 -1
- package/webui/build/_app/immutable/nodes/16.zPgTQNze.js +0 -24
- package/webui/build/_app/immutable/nodes/2.BYWOpaxy.js +0 -1
- package/webui/build/_app/immutable/nodes/3.B8Viux9S.js +0 -1
- package/webui/build/_app/immutable/nodes/4.DTSxpKm7.js +0 -2
- package/webui/build/_app/immutable/nodes/7.Bd2USIrl.js +0 -1
- package/webui/build/_app/version.json +0 -1
- /package/{webui → app/webui}/build/_app/env.js +0 -0
- /package/{webui → app/webui}/build/_app/immutable/assets/10.Dj8_pmut.css +0 -0
- /package/{webui → app/webui}/build/_app/immutable/assets/11.qYZMiTb0.css +0 -0
- /package/{webui → app/webui}/build/_app/immutable/assets/12.DfJcfUWl.css +0 -0
- /package/{webui → app/webui}/build/_app/immutable/assets/15.nNGjXhCQ.css +0 -0
- /package/{webui → app/webui}/build/_app/immutable/assets/5.B-dPiwB7.css +0 -0
- /package/{webui → app/webui}/build/_app/immutable/assets/6.B27N7pdA.css +0 -0
- /package/{webui → app/webui}/build/_app/immutable/assets/7.CrNxmd8B.css +0 -0
- /package/{webui → app/webui}/build/_app/immutable/assets/8.Cgji2b15.css +0 -0
- /package/{webui → app/webui}/build/_app/immutable/assets/9.BsCIAvn3.css +0 -0
- /package/{webui → app/webui}/build/_app/immutable/assets/BackToParentRoute.DGk-X5ow.css +0 -0
- /package/{webui → app/webui}/build/_app/immutable/chunks/BUApaBEI.js +0 -0
- /package/{webui → app/webui}/build/_app/immutable/chunks/Bfc47y5P.js +0 -0
- /package/{webui → app/webui}/build/_app/immutable/chunks/CBY2biv-.js +0 -0
- /package/{webui → app/webui}/build/_app/immutable/chunks/hp4PFHFv.js +0 -0
- /package/{webui → app/webui}/build/_app/immutable/nodes/17.BtYZF6FM.js +0 -0
- /package/{webui → app/webui}/build/_app/immutable/nodes/18.BIzqhTqv.js +0 -0
package/dist/index.js
CHANGED
|
@@ -242,7 +242,7 @@ async function migrateFile(from, to) {
|
|
|
242
242
|
logger.warn("config", "配置迁移失败", { from, to, err: err instanceof Error ? err.message : String(err) });
|
|
243
243
|
}
|
|
244
244
|
}
|
|
245
|
-
const INIT_DATA_DIR = join(PACKAGE_ROOT, "init");
|
|
245
|
+
const INIT_DATA_DIR = join(PACKAGE_ROOT, "app/init");
|
|
246
246
|
const EXAMPLE_SOURCES = join(INIT_DATA_DIR, "sources.json");
|
|
247
247
|
const EXAMPLE_CONFIG = join(INIT_DATA_DIR, "config.json");
|
|
248
248
|
async function seedExampleConfigsIfMissing() {
|
|
@@ -633,7 +633,8 @@ async function upsertItems(items, sourceUrlOverride) {
|
|
|
633
633
|
const nextAuthor = nextAuthorArr?.length ? JSON.stringify(nextAuthorArr) : null;
|
|
634
634
|
const nextPubDate = pubDateToIsoOrNull(item.pubDate);
|
|
635
635
|
const nextTags = item.tags?.length ? JSON.stringify(item.tags) : null;
|
|
636
|
-
const
|
|
636
|
+
const rawImageUrl = item.imageUrl ?? item.coverImg ?? item.cover_img;
|
|
637
|
+
const nextImageUrl = typeof rawImageUrl === "string" && rawImageUrl.trim() ? rawImageUrl.trim() : null;
|
|
637
638
|
const info = insertStmt.run({
|
|
638
639
|
id: item.guid,
|
|
639
640
|
url: item.link,
|
|
@@ -654,7 +655,9 @@ async function upsertItems(items, sourceUrlOverride) {
|
|
|
654
655
|
const existing = selectExistingStmt.get({ id: item.guid });
|
|
655
656
|
if (!existing) continue;
|
|
656
657
|
const shouldRepairTitle = !!nextTitle && !isDateOnlyTitle(nextTitle) && (isDateOnlyTitle(existing.title) || !normalizeText(existing.title));
|
|
657
|
-
const
|
|
658
|
+
const existingSummaryText = normalizeText(existing.summary ?? "");
|
|
659
|
+
const shouldClearDuplicatedSummary = nextSummary == null && !!nextTitle && existingSummaryText === nextTitle;
|
|
660
|
+
const shouldRepairSummary = !!nextSummary && (existingSummaryText.length < nextSummary.length || /!\[[^\]]*\]\([^)]*\)/.test(existingSummaryText)) || shouldClearDuplicatedSummary;
|
|
658
661
|
const shouldRepairImageUrl = !!nextImageUrl && !existing.image_url?.trim();
|
|
659
662
|
const existingAuthorArr = parseAuthorFromDb(existing.author);
|
|
660
663
|
const shouldRepairAuthor = !!nextAuthorArr?.length && !existingAuthorArr?.length;
|
|
@@ -670,7 +673,7 @@ async function upsertItems(items, sourceUrlOverride) {
|
|
|
670
673
|
id: item.guid,
|
|
671
674
|
title: shouldRepairTitle ? nextTitle : existing.title,
|
|
672
675
|
author: shouldRepairAuthor ? nextAuthor : existing.author ?? null,
|
|
673
|
-
summary: shouldRepairSummary ? nextSummary : existing.summary,
|
|
676
|
+
summary: shouldClearDuplicatedSummary ? null : shouldRepairSummary ? nextSummary : existing.summary,
|
|
674
677
|
imageUrl: shouldRepairImageUrl ? nextImageUrl : existing.image_url ?? null,
|
|
675
678
|
pubDate: shouldRepairPubDate ? nextPubDate : existing.pub_date,
|
|
676
679
|
fetchedAt: now2
|
|
@@ -682,6 +685,8 @@ async function upsertItems(items, sourceUrlOverride) {
|
|
|
682
685
|
async function updateItemContent(item) {
|
|
683
686
|
return withWriteLock(async () => {
|
|
684
687
|
const db = await getDb();
|
|
688
|
+
const rawImageUrl = item.imageUrl ?? item.coverImg ?? item.cover_img;
|
|
689
|
+
const nextImageUrl = typeof rawImageUrl === "string" && rawImageUrl.trim() ? rawImageUrl.trim() : null;
|
|
685
690
|
db.prepare(`
|
|
686
691
|
UPDATE items SET
|
|
687
692
|
content = COALESCE(content, @content),
|
|
@@ -694,7 +699,7 @@ async function updateItemContent(item) {
|
|
|
694
699
|
`).run({
|
|
695
700
|
id: item.guid,
|
|
696
701
|
content: item.content ?? null,
|
|
697
|
-
imageUrl:
|
|
702
|
+
imageUrl: nextImageUrl,
|
|
698
703
|
author: (() => {
|
|
699
704
|
const arr = normalizeAuthor(item.author);
|
|
700
705
|
return arr?.length ? JSON.stringify(arr) : null;
|
|
@@ -705,42 +710,6 @@ async function updateItemContent(item) {
|
|
|
705
710
|
});
|
|
706
711
|
});
|
|
707
712
|
}
|
|
708
|
-
async function queryFeedItems(sourceUrls, limit, offset, opts) {
|
|
709
|
-
if (sourceUrls.length === 0) return { items: [], hasMore: false };
|
|
710
|
-
const expanded = [...new Set(sourceUrls.map((u) => canonicalHttpSourceRef(u)).filter(Boolean))];
|
|
711
|
-
if (expanded.length === 0) return { items: [], hasMore: false };
|
|
712
|
-
const db = await getDb();
|
|
713
|
-
const placeholders = expanded.map((_, i) => `@u${i}`).join(", ");
|
|
714
|
-
const conditions = [`source_url IN (${placeholders})`];
|
|
715
|
-
const params = { lim: limit + 1, off: offset };
|
|
716
|
-
expanded.forEach((url, i) => {
|
|
717
|
-
params[`u${i}`] = url;
|
|
718
|
-
});
|
|
719
|
-
const sqlParams = params;
|
|
720
|
-
if (opts?.since) {
|
|
721
|
-
conditions.push("COALESCE(pub_date, fetched_at) >= @since");
|
|
722
|
-
params.since = opts.since.length === 10 ? `${opts.since}T00:00:00.000Z` : opts.since;
|
|
723
|
-
}
|
|
724
|
-
if (opts?.until) {
|
|
725
|
-
conditions.push("COALESCE(pub_date, fetched_at) < @until");
|
|
726
|
-
if (opts.until.length === 10) {
|
|
727
|
-
const d = /* @__PURE__ */ new Date(`${opts.until}T12:00:00Z`);
|
|
728
|
-
d.setUTCDate(d.getUTCDate() + 1);
|
|
729
|
-
params.until = d.toISOString();
|
|
730
|
-
} else {
|
|
731
|
-
params.until = opts.until;
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
735
|
-
const rows = db.prepare(`
|
|
736
|
-
SELECT * FROM items ${where}
|
|
737
|
-
ORDER BY COALESCE(pub_date, fetched_at) DESC
|
|
738
|
-
LIMIT ${limit + 1} OFFSET ${offset}
|
|
739
|
-
`).all(sqlParams);
|
|
740
|
-
const hasMore = rows.length > limit;
|
|
741
|
-
const items = mapRowsToDbItems(hasMore ? rows.slice(0, limit) : rows);
|
|
742
|
-
return { items, hasMore };
|
|
743
|
-
}
|
|
744
713
|
async function queryItems(opts) {
|
|
745
714
|
const db = await getDb();
|
|
746
715
|
const { sourceUrl, sourceUrls, author, q, tags: tagsFilter, limit = 20, offset = 0, since, until } = opts;
|
|
@@ -789,7 +758,7 @@ async function queryItems(opts) {
|
|
|
789
758
|
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
790
759
|
const sqlParams = params;
|
|
791
760
|
const rows = db.prepare(`
|
|
792
|
-
SELECT i.id, i.url, i.source_url, i.title, i.author, i.summary, i.content, i.tags, i.translations, i.pub_date, i.fetched_at, i.pushed_at
|
|
761
|
+
SELECT i.id, i.url, i.source_url, i.title, i.author, i.summary, i.content, i.image_url, i.tags, i.translations, i.pub_date, i.fetched_at, i.pushed_at
|
|
793
762
|
FROM items i ${where}
|
|
794
763
|
ORDER BY COALESCE(i.pub_date, i.fetched_at) DESC
|
|
795
764
|
LIMIT ${limit} OFFSET ${offset}
|
|
@@ -1204,6 +1173,22 @@ function isFrameDetachedError(e) {
|
|
|
1204
1173
|
const msg = e instanceof Error ? e.message : String(e);
|
|
1205
1174
|
return /detached|Navigating frame was detached|Session closed/i.test(msg);
|
|
1206
1175
|
}
|
|
1176
|
+
const sharedBrowsers = /* @__PURE__ */ new Map();
|
|
1177
|
+
function browserKey(config) {
|
|
1178
|
+
const wantHeadless = config.headless !== false;
|
|
1179
|
+
const executablePath = config.chromeExecutablePath ?? process.env.CHROME_PATH ?? findChromeExecutable() ?? "";
|
|
1180
|
+
const userDataDir = getUserDataDir(config.cacheDir);
|
|
1181
|
+
const proxy = resolveProxy(config) ?? "";
|
|
1182
|
+
return JSON.stringify({
|
|
1183
|
+
headless: wantHeadless,
|
|
1184
|
+
userDataDir: userDataDir ? resolve(userDataDir) : "",
|
|
1185
|
+
proxy,
|
|
1186
|
+
executablePath
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
function isBrowserConnected(browser) {
|
|
1190
|
+
return !!browser && browser.connected !== false;
|
|
1191
|
+
}
|
|
1207
1192
|
async function launchBrowser(config) {
|
|
1208
1193
|
const wantHeadless = config.headless !== false;
|
|
1209
1194
|
const executablePath = config.chromeExecutablePath ?? process.env.CHROME_PATH ?? findChromeExecutable();
|
|
@@ -1247,29 +1232,53 @@ async function launchBrowser(config) {
|
|
|
1247
1232
|
}
|
|
1248
1233
|
throw lastErr;
|
|
1249
1234
|
}
|
|
1235
|
+
async function getOrCreateBrowser(config) {
|
|
1236
|
+
const key = browserKey(config);
|
|
1237
|
+
const current = sharedBrowsers.get(key);
|
|
1238
|
+
if (isBrowserConnected(current?.browser)) {
|
|
1239
|
+
return current.browser;
|
|
1240
|
+
}
|
|
1241
|
+
if (current?.promise) {
|
|
1242
|
+
return current.promise;
|
|
1243
|
+
}
|
|
1244
|
+
const slot = {};
|
|
1245
|
+
const promise = launchBrowser({ ...config, proxy: resolveProxy(config) }).then((browser) => {
|
|
1246
|
+
slot.browser = browser;
|
|
1247
|
+
slot.promise = void 0;
|
|
1248
|
+
browser.once("disconnected", () => {
|
|
1249
|
+
if (sharedBrowsers.get(key)?.browser === browser) {
|
|
1250
|
+
sharedBrowsers.delete(key);
|
|
1251
|
+
}
|
|
1252
|
+
});
|
|
1253
|
+
return browser;
|
|
1254
|
+
}).catch((err) => {
|
|
1255
|
+
if (sharedBrowsers.get(key) === slot) {
|
|
1256
|
+
sharedBrowsers.delete(key);
|
|
1257
|
+
}
|
|
1258
|
+
throw err;
|
|
1259
|
+
});
|
|
1260
|
+
slot.promise = promise;
|
|
1261
|
+
sharedBrowsers.set(key, slot);
|
|
1262
|
+
return promise;
|
|
1263
|
+
}
|
|
1250
1264
|
async function preCheckAuth(authFlow, cacheDir, opts) {
|
|
1251
1265
|
const { checkAuth, loginUrl, domain } = authFlow;
|
|
1252
1266
|
if (domain == null || !cacheDir) return true;
|
|
1253
1267
|
const isHeadless = opts?.headless !== false;
|
|
1254
|
-
const browser = await
|
|
1268
|
+
const browser = await getOrCreateBrowser({
|
|
1255
1269
|
headless: isHeadless,
|
|
1256
1270
|
cacheDir,
|
|
1257
1271
|
proxy: resolveProxy(opts)
|
|
1258
1272
|
});
|
|
1273
|
+
const page = await browser.newPage();
|
|
1259
1274
|
try {
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
await new Promise((resolve2) => setTimeout(resolve2, 3e3));
|
|
1266
|
-
return await checkAuth(page, page.url());
|
|
1267
|
-
} finally {
|
|
1268
|
-
await page.close().catch(() => {
|
|
1269
|
-
});
|
|
1270
|
-
}
|
|
1275
|
+
await setupPage(page, isHeadless);
|
|
1276
|
+
await applyProxyAuthToPage(page, opts);
|
|
1277
|
+
await page.goto(loginUrl, { waitUntil: "domcontentloaded", timeout: 6e4 });
|
|
1278
|
+
await new Promise((resolve2) => setTimeout(resolve2, 3e3));
|
|
1279
|
+
return await checkAuth(page, page.url());
|
|
1271
1280
|
} finally {
|
|
1272
|
-
await
|
|
1281
|
+
await page.close().catch(() => {
|
|
1273
1282
|
});
|
|
1274
1283
|
}
|
|
1275
1284
|
}
|
|
@@ -1314,10 +1323,11 @@ async function fetchHtml(url, config = {}) {
|
|
|
1314
1323
|
waitAfterLoadMs,
|
|
1315
1324
|
waitForSelector,
|
|
1316
1325
|
waitForSelectorTimeoutMs,
|
|
1326
|
+
scrollBeforeSnapshot,
|
|
1317
1327
|
useHttpResponseBody
|
|
1318
1328
|
} = config;
|
|
1319
1329
|
const isHeadless = headless !== false;
|
|
1320
|
-
const browser = await
|
|
1330
|
+
const browser = await getOrCreateBrowser({
|
|
1321
1331
|
headless: isHeadless,
|
|
1322
1332
|
cacheDir,
|
|
1323
1333
|
proxy: resolveProxy(config),
|
|
@@ -1326,84 +1336,105 @@ async function fetchHtml(url, config = {}) {
|
|
|
1326
1336
|
const navigationTimeout = timeoutMs ?? 6e4;
|
|
1327
1337
|
const maxAttempts = 2;
|
|
1328
1338
|
let lastError;
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
await page.authenticate({ username: username ?? "", password: password ?? "" });
|
|
1350
|
-
}
|
|
1351
|
-
}
|
|
1352
|
-
if (timeoutMs != null) {
|
|
1353
|
-
await page.setDefaultNavigationTimeout(timeoutMs);
|
|
1354
|
-
}
|
|
1355
|
-
const response = await page.goto(url, { waitUntil, timeout: navigationTimeout });
|
|
1356
|
-
if (extraWaitMs > 0) {
|
|
1357
|
-
await new Promise((resolve2) => setTimeout(resolve2, extraWaitMs));
|
|
1339
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
1340
|
+
const page = await browser.newPage();
|
|
1341
|
+
const isRetry = attempt === 1;
|
|
1342
|
+
const waitUntil = isRetry ? "domcontentloaded" : "load";
|
|
1343
|
+
const extraWaitMs = isRetry ? Math.min(500, Math.max(0, waitAfterLoadMs ?? 2e3)) : Math.max(0, waitAfterLoadMs ?? 2e3);
|
|
1344
|
+
try {
|
|
1345
|
+
if (config.browserContext) {
|
|
1346
|
+
await config.browserContext(page.browserContext());
|
|
1347
|
+
}
|
|
1348
|
+
await setupPage(page, isHeadless);
|
|
1349
|
+
const extraHeaders = { "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", ...headers ?? {} };
|
|
1350
|
+
if (cookies != null && cookies !== "") {
|
|
1351
|
+
extraHeaders.cookie = cookies;
|
|
1352
|
+
}
|
|
1353
|
+
await page.setExtraHTTPHeaders(extraHeaders);
|
|
1354
|
+
const proxy = resolveProxy(config);
|
|
1355
|
+
if (proxy) {
|
|
1356
|
+
const { username, password } = parseProxy(proxy);
|
|
1357
|
+
if (username !== void 0 || password !== void 0) {
|
|
1358
|
+
await page.authenticate({ username: username ?? "", password: password ?? "" });
|
|
1358
1359
|
}
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1360
|
+
}
|
|
1361
|
+
if (timeoutMs != null) {
|
|
1362
|
+
await page.setDefaultNavigationTimeout(timeoutMs);
|
|
1363
|
+
}
|
|
1364
|
+
const response = await page.goto(url, { waitUntil, timeout: navigationTimeout });
|
|
1365
|
+
if (extraWaitMs > 0) {
|
|
1366
|
+
await new Promise((resolve2) => setTimeout(resolve2, extraWaitMs));
|
|
1367
|
+
}
|
|
1368
|
+
if (waitForSelector != null && waitForSelector !== "" && !isRetry) {
|
|
1369
|
+
const selectorTimeout = waitForSelectorTimeoutMs ?? 2e4;
|
|
1370
|
+
await page.waitForSelector(waitForSelector, { timeout: selectorTimeout });
|
|
1371
|
+
}
|
|
1372
|
+
if (scrollBeforeSnapshot && !isRetry) {
|
|
1373
|
+
const scrollSelector = scrollBeforeSnapshot.selector ?? null;
|
|
1374
|
+
const rounds = scrollBeforeSnapshot.rounds ?? 6;
|
|
1375
|
+
const pauseMs = scrollBeforeSnapshot.pauseMs ?? 800;
|
|
1376
|
+
for (let i = 0; i < rounds; i++) {
|
|
1377
|
+
const before = await page.evaluate((sel) => {
|
|
1378
|
+
const target = sel ? document.querySelector(sel) : null;
|
|
1379
|
+
const el = target ?? document.scrollingElement ?? document.documentElement;
|
|
1380
|
+
return el?.scrollHeight ?? 0;
|
|
1381
|
+
}, scrollSelector);
|
|
1382
|
+
await page.evaluate((sel) => {
|
|
1383
|
+
const target = sel ? document.querySelector(sel) : null;
|
|
1384
|
+
const el = target ?? document.scrollingElement ?? document.documentElement;
|
|
1385
|
+
if (!el) return;
|
|
1386
|
+
el.scrollTop = el.scrollHeight;
|
|
1387
|
+
window.scrollBy(0, window.innerHeight);
|
|
1388
|
+
}, scrollSelector);
|
|
1389
|
+
await new Promise((resolve2) => setTimeout(resolve2, pauseMs));
|
|
1390
|
+
const after = await page.evaluate((sel) => {
|
|
1391
|
+
const target = sel ? document.querySelector(sel) : null;
|
|
1392
|
+
const el = target ?? document.scrollingElement ?? document.documentElement;
|
|
1393
|
+
return el?.scrollHeight ?? 0;
|
|
1394
|
+
}, scrollSelector);
|
|
1395
|
+
if (after <= before && i >= 2) break;
|
|
1362
1396
|
}
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1397
|
+
}
|
|
1398
|
+
if (checkAuth != null || authFlow != null) {
|
|
1399
|
+
const authCheck = checkAuth ?? authFlow?.checkAuth;
|
|
1400
|
+
if (authCheck != null) {
|
|
1401
|
+
const ok = await authCheck(page, url);
|
|
1402
|
+
if (!ok) {
|
|
1403
|
+
throw new Error("checkAuth failed: 未通过认证检查,请先调用 ensureAuth 进行预处理登录");
|
|
1370
1404
|
}
|
|
1371
1405
|
}
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
}
|
|
1379
|
-
} else {
|
|
1406
|
+
}
|
|
1407
|
+
let rawBody;
|
|
1408
|
+
if (useHttpResponseBody === true && response != null) {
|
|
1409
|
+
try {
|
|
1410
|
+
rawBody = await response.text();
|
|
1411
|
+
} catch {
|
|
1380
1412
|
rawBody = await page.content();
|
|
1381
1413
|
}
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
const statusText = response?.statusText() ?? "";
|
|
1385
|
-
const rawHeaders = response?.headers() ?? {};
|
|
1386
|
-
const normalizedHeaders = headersToRecord(rawHeaders);
|
|
1387
|
-
const body = applyPurify(rawBody, purify);
|
|
1388
|
-
await page.close().catch(() => {
|
|
1389
|
-
});
|
|
1390
|
-
return { finalUrl, status, statusText, headers: normalizedHeaders, body };
|
|
1391
|
-
} catch (e) {
|
|
1392
|
-
lastError = e;
|
|
1393
|
-
await page.close().catch(() => {
|
|
1394
|
-
});
|
|
1395
|
-
if (isRetry || !isFrameDetachedError(e)) {
|
|
1396
|
-
throw e;
|
|
1397
|
-
}
|
|
1398
|
-
logger.warn("scraper", "fetchHtml 因 frame 分离重试", { url, attempt: attempt + 1, err: e instanceof Error ? e.message : String(e) });
|
|
1399
|
-
await new Promise((r) => setTimeout(r, 800));
|
|
1414
|
+
} else {
|
|
1415
|
+
rawBody = await page.content();
|
|
1400
1416
|
}
|
|
1417
|
+
const finalUrl = response?.url() ?? page.url() ?? String(url);
|
|
1418
|
+
const status = response?.status() ?? 0;
|
|
1419
|
+
const statusText = response?.statusText() ?? "";
|
|
1420
|
+
const rawHeaders = response?.headers() ?? {};
|
|
1421
|
+
const normalizedHeaders = headersToRecord(rawHeaders);
|
|
1422
|
+
const body = applyPurify(rawBody, purify);
|
|
1423
|
+
await page.close().catch(() => {
|
|
1424
|
+
});
|
|
1425
|
+
return { finalUrl, status, statusText, headers: normalizedHeaders, body };
|
|
1426
|
+
} catch (e) {
|
|
1427
|
+
lastError = e;
|
|
1428
|
+
await page.close().catch(() => {
|
|
1429
|
+
});
|
|
1430
|
+
if (isRetry || !isFrameDetachedError(e)) {
|
|
1431
|
+
throw e;
|
|
1432
|
+
}
|
|
1433
|
+
logger.warn("scraper", "fetchHtml 因 frame 分离重试", { url, attempt: attempt + 1, err: e instanceof Error ? e.message : String(e) });
|
|
1434
|
+
await new Promise((r) => setTimeout(r, 800));
|
|
1401
1435
|
}
|
|
1402
|
-
throw lastError;
|
|
1403
|
-
} finally {
|
|
1404
|
-
await browser.close().catch(() => {
|
|
1405
|
-
});
|
|
1406
1436
|
}
|
|
1437
|
+
throw lastError;
|
|
1407
1438
|
}
|
|
1408
1439
|
const VALID_INTERVALS = ["1min", "5min", "10min", "30min", "1h", "6h", "12h", "1day", "3day", "7day"];
|
|
1409
1440
|
function cronToRefreshInterval(cronExpr) {
|
|
@@ -1937,6 +1968,7 @@ function buildSiteContext(site, ctx) {
|
|
|
1937
1968
|
purify: opts?.purify,
|
|
1938
1969
|
waitForSelector: opts?.waitForSelector,
|
|
1939
1970
|
waitForSelectorTimeoutMs: opts?.waitForSelectorTimeoutMs,
|
|
1971
|
+
scrollBeforeSnapshot: opts?.scrollBeforeSnapshot,
|
|
1940
1972
|
useHttpResponseBody: opts?.useHttpResponseBody
|
|
1941
1973
|
});
|
|
1942
1974
|
return { html: res.body, finalUrl: res.finalUrl ?? url, status: res.status };
|
|
@@ -1975,6 +2007,7 @@ function createWebSource(site) {
|
|
|
1975
2007
|
const authFlow = toAuthFlow(site);
|
|
1976
2008
|
return {
|
|
1977
2009
|
id: site.id,
|
|
2010
|
+
name: site.name,
|
|
1978
2011
|
pattern: site.listUrlPattern,
|
|
1979
2012
|
priority: 50,
|
|
1980
2013
|
refreshInterval: site.refreshInterval ?? void 0,
|
|
@@ -2731,7 +2764,7 @@ async function generateAndCache(listUrl, key, config, proxy) {
|
|
|
2731
2764
|
}
|
|
2732
2765
|
return { items: out };
|
|
2733
2766
|
}
|
|
2734
|
-
async function
|
|
2767
|
+
async function crawlSource(listUrl, config = {}) {
|
|
2735
2768
|
const source = getSource(listUrl);
|
|
2736
2769
|
const proxy = await getEffectiveProxyForListUrl(listUrl, source);
|
|
2737
2770
|
const headless = resolveHeadlessForFeeder(config);
|
|
@@ -2756,6 +2789,10 @@ async function getItems(listUrl, config = {}) {
|
|
|
2756
2789
|
if (!config.force) generatingKeys.set(key, task);
|
|
2757
2790
|
}
|
|
2758
2791
|
const { items } = await task;
|
|
2792
|
+
return { items };
|
|
2793
|
+
}
|
|
2794
|
+
async function getItems(listUrl, config = {}) {
|
|
2795
|
+
const { items } = await crawlSource(listUrl, config);
|
|
2759
2796
|
return { items, fromCache: false };
|
|
2760
2797
|
}
|
|
2761
2798
|
function feedItemsToRssXml(items, listUrl, lng, opts) {
|
|
@@ -2951,7 +2988,7 @@ const DEFAULT_REFRESH = "1day";
|
|
|
2951
2988
|
const SOURCES_CONCURRENCY = 1;
|
|
2952
2989
|
function createPullTask(ref, cacheDir, cronExpr) {
|
|
2953
2990
|
return async () => {
|
|
2954
|
-
await
|
|
2991
|
+
await crawlSource(ref, {
|
|
2955
2992
|
cacheDir,
|
|
2956
2993
|
cron: cronExpr
|
|
2957
2994
|
});
|
|
@@ -3068,24 +3105,26 @@ function registerSchedulerRoutes(app) {
|
|
|
3068
3105
|
});
|
|
3069
3106
|
}
|
|
3070
3107
|
const SITE_TEMPLATE_FALLBACK = `/**
|
|
3071
|
-
* Site
|
|
3072
|
-
*
|
|
3108
|
+
* Site plugin template created from the /plugins page.
|
|
3109
|
+
* Plugin protocol: named exports. No export default is required.
|
|
3110
|
+
* Parse HTML with ctx.deps.parseHtml; do not import app dependencies directly.
|
|
3073
3111
|
*/
|
|
3074
|
-
export default {
|
|
3075
|
-
id: "__PLUGIN_ID__",
|
|
3076
|
-
listUrlPattern: __LIST_URL_PATTERN__,
|
|
3077
|
-
refreshInterval: "1day",
|
|
3078
3112
|
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3113
|
+
// Predefined fields stay together at the top.
|
|
3114
|
+
export const id = "__PLUGIN_ID__";
|
|
3115
|
+
export const name = "__PLUGIN_ID__";
|
|
3116
|
+
export const listUrlPattern = __LIST_URL_PATTERN__;
|
|
3117
|
+
export const refreshInterval = "1day";
|
|
3118
|
+
|
|
3119
|
+
export async function fetchItems(sourceId, ctx) {
|
|
3120
|
+
const { html, finalUrl } = await ctx.fetchHtml(sourceId, {
|
|
3121
|
+
waitMs: 2000,
|
|
3122
|
+
purify: true,
|
|
3123
|
+
});
|
|
3124
|
+
void ctx.deps.parseHtml(html);
|
|
3125
|
+
void finalUrl;
|
|
3126
|
+
return [];
|
|
3127
|
+
}
|
|
3089
3128
|
`;
|
|
3090
3129
|
function isValidNewPluginId(id) {
|
|
3091
3130
|
return /^[a-zA-Z][a-zA-Z0-9_-]{0,63}$/.test(id) && id !== "generic" && id !== "new";
|
|
@@ -3154,6 +3193,7 @@ function registerPluginsRoutes(app) {
|
|
|
3154
3193
|
const sites = getPluginSites().map((s) => ({
|
|
3155
3194
|
kind: "site",
|
|
3156
3195
|
id: s.id,
|
|
3196
|
+
name: s.name ?? s.id,
|
|
3157
3197
|
listUrlPattern: typeof s.listUrlPattern === "string" ? s.listUrlPattern : String(s.listUrlPattern),
|
|
3158
3198
|
hasAuth: !!(s.checkAuth && s.loginUrl)
|
|
3159
3199
|
}));
|
|
@@ -3161,6 +3201,7 @@ function registerPluginsRoutes(app) {
|
|
|
3161
3201
|
const sources = registeredSources.filter((src) => src.id !== "generic" && !siteIds.has(src.id)).map((src) => ({
|
|
3162
3202
|
kind: "source",
|
|
3163
3203
|
id: src.id,
|
|
3204
|
+
name: src.name ?? src.id,
|
|
3164
3205
|
listUrlPattern: typeof src.pattern === "string" ? src.pattern : String(src.pattern),
|
|
3165
3206
|
hasAuth: false
|
|
3166
3207
|
}));
|
|
@@ -3263,8 +3304,25 @@ function registerFeedRoutes(app) {
|
|
|
3263
3304
|
ref: resolveRef(s),
|
|
3264
3305
|
label: s.label ?? resolveRef(s)
|
|
3265
3306
|
}));
|
|
3266
|
-
const
|
|
3267
|
-
|
|
3307
|
+
const parseDateBound = (value, endExclusive) => {
|
|
3308
|
+
if (!value) return void 0;
|
|
3309
|
+
if (value.length === 10) {
|
|
3310
|
+
const d2 = /* @__PURE__ */ new Date(endExclusive ? `${value}T12:00:00Z` : `${value}T00:00:00.000Z`);
|
|
3311
|
+
if (endExclusive) d2.setUTCDate(d2.getUTCDate() + 1);
|
|
3312
|
+
return d2;
|
|
3313
|
+
}
|
|
3314
|
+
const d = new Date(value);
|
|
3315
|
+
return Number.isNaN(d.getTime()) ? void 0 : d;
|
|
3316
|
+
};
|
|
3317
|
+
const result = sourceRefs.length > 0 ? await queryItems({
|
|
3318
|
+
sourceUrls: sourceRefs,
|
|
3319
|
+
limit: limit + 1,
|
|
3320
|
+
offset,
|
|
3321
|
+
since: parseDateBound(since ?? void 0, false),
|
|
3322
|
+
until: parseDateBound(until ?? void 0, true)
|
|
3323
|
+
}) : { items: [] };
|
|
3324
|
+
const hasMore = result.items.length > limit;
|
|
3325
|
+
const dbItems = hasMore ? result.items.slice(0, limit) : result.items;
|
|
3268
3326
|
const items = dbItems.map((item) => {
|
|
3269
3327
|
const refKey = item.source_url ?? "";
|
|
3270
3328
|
const base2 = {
|
|
@@ -3840,7 +3898,7 @@ function registerTasksRoutes(app) {
|
|
|
3840
3898
|
schedule(SOURCES_GROUP, taskId, async () => {
|
|
3841
3899
|
setTaskRunning(taskId);
|
|
3842
3900
|
try {
|
|
3843
|
-
await
|
|
3901
|
+
await crawlSource(ref, { cacheDir: CACHE_DIR, force: true });
|
|
3844
3902
|
setTaskDone(taskId, { ok: true });
|
|
3845
3903
|
} catch (err) {
|
|
3846
3904
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -4247,7 +4305,7 @@ function registerAuthRoutes(app) {
|
|
|
4247
4305
|
return c.json({ ok: true, message: "已打开登录窗口,请在弹出的浏览器中完成登录,完成后刷新订阅页面即可。" });
|
|
4248
4306
|
});
|
|
4249
4307
|
}
|
|
4250
|
-
const STATICS_DIR = join(PACKAGE_ROOT, "statics");
|
|
4308
|
+
const STATICS_DIR = join(PACKAGE_ROOT, "app/statics");
|
|
4251
4309
|
function parseUrlFromPath(path, prefix) {
|
|
4252
4310
|
const raw = path.slice(prefix.length) || "";
|
|
4253
4311
|
const decoded = decodeURIComponent(raw.startsWith("/") ? raw.slice(1) : raw);
|
|
@@ -4475,7 +4533,7 @@ function getWebUiBuildDir() {
|
|
|
4475
4533
|
if (w.startsWith("/") || /^[A-Za-z]:[\\/]/.test(w)) return w;
|
|
4476
4534
|
return join(process.cwd(), w);
|
|
4477
4535
|
}
|
|
4478
|
-
return join(PACKAGE_ROOT, "webui/build");
|
|
4536
|
+
return join(PACKAGE_ROOT, "app/webui/build");
|
|
4479
4537
|
}
|
|
4480
4538
|
function isBackendOnlyPath(pathname) {
|
|
4481
4539
|
if (pathname.startsWith("/api")) return true;
|
|
@@ -4491,11 +4549,10 @@ function registerWebUiRoutes(app) {
|
|
|
4491
4549
|
const absRoot = getWebUiBuildDir();
|
|
4492
4550
|
if (!existsSync(absRoot)) {
|
|
4493
4551
|
console.warn(
|
|
4494
|
-
"未找到 WebUI
|
|
4552
|
+
"未找到 WebUI 构建目录,静态路由已注册,等待前端 watch 构建:",
|
|
4495
4553
|
absRoot,
|
|
4496
|
-
"
|
|
4554
|
+
"(开发模式:npm run dev;单独构建:npm run webui:build)"
|
|
4497
4555
|
);
|
|
4498
|
-
return;
|
|
4499
4556
|
}
|
|
4500
4557
|
const relRoot = relative(process.cwd(), absRoot).replace(/\\/g, "/");
|
|
4501
4558
|
const staticRoot = relRoot === "" || relRoot === "." ? "." : relRoot.startsWith(".") || relRoot.startsWith("/") || /^[A-Za-z]:/.test(relRoot) ? relRoot : `./${relRoot}`;
|
|
@@ -4580,7 +4637,7 @@ async function main() {
|
|
|
4580
4637
|
const server = serve({ fetch: app.fetch, port: PORT, hostname: "0.0.0.0" });
|
|
4581
4638
|
server.setMaxListeners(32);
|
|
4582
4639
|
console.log(
|
|
4583
|
-
`RssAny ${getAppVersion()} 服务已启动 http://127.0.0.1:${PORT}/(API +
|
|
4640
|
+
`RssAny ${getAppVersion()} 服务已启动 http://127.0.0.1:${PORT}/(API + 静态前端单地址)`
|
|
4584
4641
|
);
|
|
4585
4642
|
const lanIp = Object.values(networkInterfaces()).flat().find((iface) => iface?.family === "IPv4" && !iface.internal)?.address;
|
|
4586
4643
|
if (lanIp) console.log(`局域网访问 http://${lanIp}:${PORT}/`);
|