rssany 0.3.1 → 0.3.3
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/.env.example +52 -52
- package/README.md +156 -147
- package/app/plugins/builtin/email.rssany.js +84 -84
- package/app/plugins/builtin/rss.rssany.js +164 -164
- package/app/plugins/builtin/xiaohongshu.rssany.js +59 -2
- package/app/statics/README.md +7 -7
- package/app/webui/build/200.html +36 -36
- package/app/webui/build/_app/immutable/assets/0.BLOTwIuF.css +1 -0
- package/app/webui/build/_app/immutable/assets/10.CmGYYZFR.css +1 -0
- package/app/webui/build/_app/immutable/assets/11.Dkz3VS_N.css +1 -0
- package/app/webui/build/_app/immutable/assets/14.BCCBoMGj.css +1 -0
- package/app/webui/build/_app/immutable/assets/6.Cm_jpHOq.css +1 -0
- package/app/webui/build/_app/immutable/assets/7.CJ3BjogD.css +1 -0
- package/app/webui/build/_app/immutable/assets/9.CATKVZ-n.css +1 -0
- package/app/webui/build/_app/immutable/assets/{SourcesList.D5Lso0bo.css → SourcesList.ke66uOSi.css} +1 -1
- package/app/webui/build/_app/immutable/assets/chevron-down.CV-KWLNP.css +1 -0
- package/app/webui/build/_app/immutable/chunks/{CGCMIfh3.js → 4TuV_psf.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/{DAdOEnFb.js → B0czyjwj.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/{CFwxUBGi.js → B553hBXT.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/B8StT3Do.js +6 -0
- package/app/webui/build/_app/immutable/chunks/BI_ale1m.js +1 -0
- package/app/webui/build/_app/immutable/chunks/BK0ygNWX.js +2 -0
- package/app/webui/build/_app/immutable/chunks/BKm6QCwp.js +1 -0
- package/app/webui/build/_app/immutable/chunks/BT6b4LcZ.js +36 -0
- package/app/webui/build/_app/immutable/chunks/BZY5aksi.js +36 -0
- package/app/webui/build/_app/immutable/chunks/{C8umpVpB.js → BnqaikL8.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/BsQ08Wq_.js +1 -0
- package/app/webui/build/_app/immutable/chunks/C9wTDiHH.js +1 -0
- package/app/webui/build/_app/immutable/chunks/{B-CeeY89.js → CAKuIoAf.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/CEWi_rGa.js +1 -0
- package/app/webui/build/_app/immutable/chunks/{ChUctqXA.js → Cc7aBSsN.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/{BAJAS8BI.js → D8G961Hm.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/{CS53ooo0.js → DIeahUKq.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/DO5OXNYS.js +1 -0
- package/app/webui/build/_app/immutable/chunks/Dg_D3pjF.js +1 -0
- package/app/webui/build/_app/immutable/chunks/{Dyvi1wBH.js → DptdhtA1.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/{ClknbeNl.js → FDS7fbwH.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/{CqYSO3Dx.js → GeNMTUn1.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/{DCEayuDt.js → IhDlsCxD.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/Nd0ktDhd.js +1 -0
- package/app/webui/build/_app/immutable/chunks/{D6kzEN_P.js → SvdgnirT.js} +1 -1
- package/app/webui/build/_app/immutable/chunks/WW6La7Nt.js +2 -0
- package/app/webui/build/_app/immutable/chunks/{DsxvjlCC.js → pd_p3yYy.js} +5 -5
- package/app/webui/build/_app/immutable/chunks/rNwPv4DZ.js +1 -0
- package/app/webui/build/_app/immutable/entry/app.BKLBG-4w.js +2 -0
- package/app/webui/build/_app/immutable/entry/start.D-X6pVtx.js +1 -0
- package/app/webui/build/_app/immutable/nodes/{0.DK_mcVDm.js → 0.CJDC_3s9.js} +3 -3
- package/app/webui/build/_app/immutable/nodes/1.DsKocFSb.js +1 -0
- package/app/webui/build/_app/immutable/nodes/10.BeejAn8z.js +1 -0
- package/app/webui/build/_app/immutable/nodes/11.D--uwkk0.js +3 -0
- package/app/webui/build/_app/immutable/nodes/12.BLyQ6rUu.js +1 -0
- package/app/webui/build/_app/immutable/nodes/13.Cl0WQK13.js +1 -0
- package/app/webui/build/_app/immutable/nodes/14.T9l5Rh19.js +1 -0
- package/app/webui/build/_app/immutable/nodes/15.DHfwIlBx.js +1 -0
- package/app/webui/build/_app/immutable/nodes/{16.zfSe93Ab.js → 16.BKDfR-KV.js} +2 -2
- package/app/webui/build/_app/immutable/nodes/17.DofB8HQB.js +1 -0
- package/app/webui/build/_app/immutable/nodes/2.BOYqXdCa.js +1 -0
- package/app/webui/build/_app/immutable/nodes/3.B9ucbp_W.js +1 -0
- package/app/webui/build/_app/immutable/nodes/5.9zgwFV6I.js +2 -0
- package/app/webui/build/_app/immutable/nodes/6.Bs32Ieii.js +2 -0
- package/app/webui/build/_app/immutable/nodes/7.Cigxrk0v.js +1 -0
- package/app/webui/build/_app/immutable/nodes/8.pG10rCF0.js +1 -0
- package/app/webui/build/_app/immutable/nodes/9.Bzqb3xHY.js +1 -0
- package/app/webui/build/_app/version.json +1 -1
- package/bin/rssany.js +55 -3
- package/dist/index.js +361 -99
- package/dist/index.js.map +1 -1
- package/package.json +107 -103
- package/scripts/dev.mjs +5 -1
- package/scripts/postinstall.mjs +44 -0
- package/scripts/reset.mjs +137 -135
- package/scripts/user-dir.mjs +52 -0
- package/app/webui/build/_app/immutable/assets/0.DsKls1SN.css +0 -1
- package/app/webui/build/_app/immutable/assets/10.Dj8_pmut.css +0 -1
- package/app/webui/build/_app/immutable/assets/13.Qu_tY6H9.css +0 -1
- package/app/webui/build/_app/immutable/assets/5.B-dPiwB7.css +0 -1
- package/app/webui/build/_app/immutable/assets/6.B27N7pdA.css +0 -1
- package/app/webui/build/_app/immutable/assets/8.Cgji2b15.css +0 -1
- package/app/webui/build/_app/immutable/assets/9.BsCIAvn3.css +0 -1
- package/app/webui/build/_app/immutable/chunks/6prdYIKP.js +0 -1
- package/app/webui/build/_app/immutable/chunks/B2cyTHdf.js +0 -2
- package/app/webui/build/_app/immutable/chunks/B6WG2Sd3.js +0 -1
- package/app/webui/build/_app/immutable/chunks/BA4Gucnq.js +0 -1
- package/app/webui/build/_app/immutable/chunks/BkD3yAYe.js +0 -1
- package/app/webui/build/_app/immutable/chunks/C4uF_YIK.js +0 -1
- package/app/webui/build/_app/immutable/chunks/CBY2biv-.js +0 -1
- package/app/webui/build/_app/immutable/chunks/CVW0ymE1.js +0 -1
- package/app/webui/build/_app/immutable/chunks/DJ2e04vK.js +0 -36
- package/app/webui/build/_app/immutable/chunks/DL3Q5sfb.js +0 -1
- package/app/webui/build/_app/immutable/chunks/DVa8Y-mQ.js +0 -1
- package/app/webui/build/_app/immutable/chunks/DkamXS6W.js +0 -36
- package/app/webui/build/_app/immutable/chunks/DoRPmqLn.js +0 -2
- package/app/webui/build/_app/immutable/chunks/_qj9U-za.js +0 -1
- package/app/webui/build/_app/immutable/chunks/vtBo8kBV.js +0 -1
- package/app/webui/build/_app/immutable/entry/app.RFfWi3_i.js +0 -2
- package/app/webui/build/_app/immutable/entry/start.DU_kyeGS.js +0 -1
- package/app/webui/build/_app/immutable/nodes/1.0PRrU2uQ.js +0 -1
- package/app/webui/build/_app/immutable/nodes/10.CsxzlUER.js +0 -1
- package/app/webui/build/_app/immutable/nodes/11.D-PkhIRW.js +0 -1
- package/app/webui/build/_app/immutable/nodes/12.GGf-JLUY.js +0 -1
- package/app/webui/build/_app/immutable/nodes/13.DWWcH27k.js +0 -6
- package/app/webui/build/_app/immutable/nodes/14.COwSLwDN.js +0 -1
- package/app/webui/build/_app/immutable/nodes/15.nDN_AHrs.js +0 -1
- package/app/webui/build/_app/immutable/nodes/2.AJd2163d.js +0 -1
- package/app/webui/build/_app/immutable/nodes/3.CEVEHuaH.js +0 -1
- package/app/webui/build/_app/immutable/nodes/4.BT_N8pCh.js +0 -2
- package/app/webui/build/_app/immutable/nodes/5.BZScQ2CH.js +0 -2
- package/app/webui/build/_app/immutable/nodes/6.CkFk8X--.js +0 -1
- package/app/webui/build/_app/immutable/nodes/7.CuQJk7te.js +0 -1
- package/app/webui/build/_app/immutable/nodes/8.DIavWJnU.js +0 -1
- package/app/webui/build/_app/immutable/nodes/9.Db30M8x0.js +0 -1
- /package/app/webui/build/_app/immutable/assets/{11.qYZMiTb0.css → 12.qYZMiTb0.css} +0 -0
- /package/app/webui/build/_app/immutable/assets/{12.DfJcfUWl.css → 13.DfJcfUWl.css} +0 -0
- /package/app/webui/build/_app/immutable/assets/{14.DfMfOrS3.css → 15.DfMfOrS3.css} +0 -0
- /package/app/webui/build/_app/immutable/assets/{15.nNGjXhCQ.css → 17.nNGjXhCQ.css} +0 -0
- /package/app/webui/build/_app/immutable/assets/{4.Di6rvlY-.css → 5.Di6rvlY-.css} +0 -0
- /package/app/webui/build/_app/immutable/assets/{7.CrNxmd8B.css → 8.CrNxmd8B.css} +0 -0
- /package/app/webui/build/_app/immutable/nodes/{17.BtYZF6FM.js → 18.BtYZF6FM.js} +0 -0
- /package/app/webui/build/_app/immutable/nodes/{18.BIzqhTqv.js → 4.BIzqhTqv.js} +0 -0
package/dist/index.js
CHANGED
|
@@ -5,14 +5,14 @@ import { serve } from "@hono/node-server";
|
|
|
5
5
|
import { Hono } from "hono";
|
|
6
6
|
import { cors } from "hono/cors";
|
|
7
7
|
import { exec } from "node:child_process";
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
8
9
|
import { join, dirname, basename, resolve, sep, relative } from "node:path";
|
|
9
10
|
import { promisify } from "node:util";
|
|
10
11
|
import puppeteerCore from "puppeteer-core";
|
|
11
12
|
import { parse, NodeType } from "node-html-parser";
|
|
12
13
|
import { DatabaseSync } from "node:sqlite";
|
|
13
|
-
import { mkdir, writeFile, copyFile, access,
|
|
14
|
+
import { mkdir, rename, writeFile, copyFile, access, readFile, readdir, stat, unlink } from "node:fs/promises";
|
|
14
15
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
15
|
-
import { createHash } from "node:crypto";
|
|
16
16
|
import { JSDOM } from "jsdom";
|
|
17
17
|
import { Readability } from "@mozilla/readability";
|
|
18
18
|
import OpenAI from "openai";
|
|
@@ -160,7 +160,7 @@ function markPipelineDrop(item) {
|
|
|
160
160
|
function isPipelineDroppedItem(item) {
|
|
161
161
|
return item.extra?.[PIPELINE_DROP_EXTRA_KEY] === true;
|
|
162
162
|
}
|
|
163
|
-
function canonicalHttpSourceRef(ref) {
|
|
163
|
+
function canonicalHttpSourceRef(ref, opts = {}) {
|
|
164
164
|
const t = ref.trim();
|
|
165
165
|
if (!t) return t;
|
|
166
166
|
if (!/^https?:\/\//i.test(t)) return t.toLowerCase();
|
|
@@ -172,7 +172,7 @@ function canonicalHttpSourceRef(ref) {
|
|
|
172
172
|
if (path.length > 1 && path.endsWith("/")) {
|
|
173
173
|
path = path.slice(0, -1);
|
|
174
174
|
}
|
|
175
|
-
path = path.toLowerCase();
|
|
175
|
+
if (opts.lowerPathCase) path = path.toLowerCase();
|
|
176
176
|
return `${protocol}//${host}${path}${u.search}${u.hash}`;
|
|
177
177
|
} catch {
|
|
178
178
|
return t.toLowerCase();
|
|
@@ -209,8 +209,48 @@ const httpSourceRef = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defin
|
|
|
209
209
|
const __dir = dirname(fileURLToPath(import.meta.url));
|
|
210
210
|
const base = basename(__dir);
|
|
211
211
|
const PACKAGE_ROOT = base === "app" || base === "dist" ? join(__dir, "..") : __dir;
|
|
212
|
-
const
|
|
213
|
-
|
|
212
|
+
const LEGACY_HOME_DIR = ".rssany";
|
|
213
|
+
function resolveNpmPrefixFromPackageRoot(packageRoot) {
|
|
214
|
+
const normalized = packageRoot.replace(/\\/g, "/");
|
|
215
|
+
const libSuffix = "/lib/node_modules/rssany";
|
|
216
|
+
if (normalized.endsWith(libSuffix)) {
|
|
217
|
+
return packageRoot.slice(0, packageRoot.length - libSuffix.length);
|
|
218
|
+
}
|
|
219
|
+
const flatSuffix = "/node_modules/rssany";
|
|
220
|
+
if (normalized.endsWith(flatSuffix)) {
|
|
221
|
+
return packageRoot.slice(0, packageRoot.length - flatSuffix.length);
|
|
222
|
+
}
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
function isGlobalNpmInstall(packageRoot) {
|
|
226
|
+
const normalized = packageRoot.replace(/\\/g, "/");
|
|
227
|
+
if (normalized.endsWith("/lib/node_modules/rssany")) return true;
|
|
228
|
+
const globalPatterns = [
|
|
229
|
+
/\/npm\/node_modules\/rssany$/,
|
|
230
|
+
/\/\.local\/lib\/node_modules\/rssany$/,
|
|
231
|
+
/\/\.local\/node_modules\/rssany$/,
|
|
232
|
+
/\/\.npm-global\/lib\/node_modules\/rssany$/,
|
|
233
|
+
/\/\.nvm\/versions\/node\/[^/]+\/lib\/node_modules\/rssany$/,
|
|
234
|
+
/\/\.fnm\/node-versions\/[^/]+\/installation\/lib\/node_modules\/rssany$/
|
|
235
|
+
];
|
|
236
|
+
return globalPatterns.some((pattern) => pattern.test(normalized));
|
|
237
|
+
}
|
|
238
|
+
function resolveDefaultUserDir(packageRoot) {
|
|
239
|
+
const env = process.env.RSSANY_USER_DIR?.trim();
|
|
240
|
+
if (env) return env;
|
|
241
|
+
const npmPrefix = resolveNpmPrefixFromPackageRoot(packageRoot);
|
|
242
|
+
if (npmPrefix && isGlobalNpmInstall(packageRoot)) {
|
|
243
|
+
return join(npmPrefix, "var", "rssany");
|
|
244
|
+
}
|
|
245
|
+
if (!packageRoot.replace(/\\/g, "/").includes("/node_modules/")) {
|
|
246
|
+
return join(packageRoot, ".rssany");
|
|
247
|
+
}
|
|
248
|
+
return join(homedir(), LEGACY_HOME_DIR);
|
|
249
|
+
}
|
|
250
|
+
function getLegacyHomeUserDir() {
|
|
251
|
+
return join(homedir(), LEGACY_HOME_DIR);
|
|
252
|
+
}
|
|
253
|
+
const USER_DIR = resolveDefaultUserDir(PACKAGE_ROOT);
|
|
214
254
|
const DATA_DIR = join(USER_DIR, "data");
|
|
215
255
|
const CACHE_DIR = process.env.CACHE_DIR ?? join(USER_DIR, "cache");
|
|
216
256
|
join(USER_DIR, "sites.json");
|
|
@@ -279,7 +319,24 @@ async function ensureUserDirPackageJsonForPlugins() {
|
|
|
279
319
|
});
|
|
280
320
|
}
|
|
281
321
|
}
|
|
322
|
+
async function migrateLegacyHomeUserDir() {
|
|
323
|
+
const legacy = getLegacyHomeUserDir();
|
|
324
|
+
if (USER_DIR === legacy) return;
|
|
325
|
+
if (await pathExists(USER_DIR)) return;
|
|
326
|
+
if (!await pathExists(legacy)) return;
|
|
327
|
+
try {
|
|
328
|
+
await rename(legacy, USER_DIR);
|
|
329
|
+
logger.info("config", "已从 ~/.rssany 迁移用户数据", { from: legacy, to: USER_DIR });
|
|
330
|
+
} catch (err) {
|
|
331
|
+
logger.warn("config", "从 ~/.rssany 迁移失败", {
|
|
332
|
+
from: legacy,
|
|
333
|
+
to: USER_DIR,
|
|
334
|
+
err: err instanceof Error ? err.message : String(err)
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
282
338
|
async function initUserDir() {
|
|
339
|
+
await migrateLegacyHomeUserDir();
|
|
283
340
|
await mkdir(USER_DIR, { recursive: true });
|
|
284
341
|
await mkdir(DATA_DIR, { recursive: true });
|
|
285
342
|
await mkdir(CACHE_DIR, { recursive: true });
|
|
@@ -1081,9 +1138,14 @@ function launchArgs(config) {
|
|
|
1081
1138
|
}
|
|
1082
1139
|
return base2;
|
|
1083
1140
|
}
|
|
1084
|
-
function
|
|
1141
|
+
function proxyProfileName(proxy) {
|
|
1142
|
+
if (!proxy) return "main";
|
|
1143
|
+
const hash = createHash("sha1").update(proxy).digest("hex").slice(0, 12);
|
|
1144
|
+
return `main_proxy_${hash}`;
|
|
1145
|
+
}
|
|
1146
|
+
function getUserDataDir(cacheDir, proxy) {
|
|
1085
1147
|
if (!cacheDir) return void 0;
|
|
1086
|
-
return join(cacheDir, "browser_data",
|
|
1148
|
+
return join(cacheDir, "browser_data", proxyProfileName(proxy));
|
|
1087
1149
|
}
|
|
1088
1150
|
function isAlreadyRunningError(e) {
|
|
1089
1151
|
const msg = e instanceof Error ? e.message : String(e);
|
|
@@ -1174,13 +1236,141 @@ function isFrameDetachedError(e) {
|
|
|
1174
1236
|
return /detached|Navigating frame was detached|Session closed/i.test(msg);
|
|
1175
1237
|
}
|
|
1176
1238
|
const sharedBrowsers = /* @__PURE__ */ new Map();
|
|
1239
|
+
const managedBrowsers = /* @__PURE__ */ new WeakSet();
|
|
1240
|
+
const infoPagePromises = /* @__PURE__ */ new WeakMap();
|
|
1241
|
+
const RSSANY_INFO_PAGE_PREFIX = "data:text/html;charset=utf-8,";
|
|
1242
|
+
function rssAnyInfoPageUrl() {
|
|
1243
|
+
const html = `<!doctype html>
|
|
1244
|
+
<html lang="zh-CN">
|
|
1245
|
+
<head>
|
|
1246
|
+
<meta charset="utf-8">
|
|
1247
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1248
|
+
<title>RssAny 浏览器</title>
|
|
1249
|
+
<style>
|
|
1250
|
+
:root {
|
|
1251
|
+
color-scheme: light dark;
|
|
1252
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
1253
|
+
background: #f7f7f5;
|
|
1254
|
+
color: #202124;
|
|
1255
|
+
}
|
|
1256
|
+
body {
|
|
1257
|
+
margin: 0;
|
|
1258
|
+
min-height: 100vh;
|
|
1259
|
+
display: grid;
|
|
1260
|
+
place-items: center;
|
|
1261
|
+
padding: 32px;
|
|
1262
|
+
box-sizing: border-box;
|
|
1263
|
+
}
|
|
1264
|
+
main {
|
|
1265
|
+
max-width: 680px;
|
|
1266
|
+
border: 1px solid #d7d7d1;
|
|
1267
|
+
border-radius: 8px;
|
|
1268
|
+
background: #ffffff;
|
|
1269
|
+
padding: 28px 32px;
|
|
1270
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
|
|
1271
|
+
}
|
|
1272
|
+
h1 {
|
|
1273
|
+
margin: 0 0 12px;
|
|
1274
|
+
font-size: 24px;
|
|
1275
|
+
line-height: 1.25;
|
|
1276
|
+
}
|
|
1277
|
+
p {
|
|
1278
|
+
margin: 10px 0 0;
|
|
1279
|
+
font-size: 15px;
|
|
1280
|
+
line-height: 1.7;
|
|
1281
|
+
}
|
|
1282
|
+
code {
|
|
1283
|
+
font-family: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace;
|
|
1284
|
+
font-size: 13px;
|
|
1285
|
+
background: #f0f0ec;
|
|
1286
|
+
padding: 2px 6px;
|
|
1287
|
+
border-radius: 4px;
|
|
1288
|
+
}
|
|
1289
|
+
@media (prefers-color-scheme: dark) {
|
|
1290
|
+
:root {
|
|
1291
|
+
background: #181a1b;
|
|
1292
|
+
color: #f1f1ed;
|
|
1293
|
+
}
|
|
1294
|
+
main {
|
|
1295
|
+
background: #202325;
|
|
1296
|
+
border-color: #3a3d3f;
|
|
1297
|
+
box-shadow: none;
|
|
1298
|
+
}
|
|
1299
|
+
code {
|
|
1300
|
+
background: #303335;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
</style>
|
|
1304
|
+
</head>
|
|
1305
|
+
<body>
|
|
1306
|
+
<main>
|
|
1307
|
+
<h1>这是 RssAny 创建的浏览器</h1>
|
|
1308
|
+
<p>RssAny 会复用这个浏览器执行订阅抓取、站点登录、页面解析和调试打开等任务。</p>
|
|
1309
|
+
<p>抓取任务会临时打开自己的标签页,完成后自动关闭。请保留此页面,以便区分 RssAny 管理的浏览器窗口。</p>
|
|
1310
|
+
<p>用户数据与 cookies 保存在 <code>.rssany/cache/browser_data</code> 对应的浏览器 profile 中。</p>
|
|
1311
|
+
</main>
|
|
1312
|
+
</body>
|
|
1313
|
+
</html>`;
|
|
1314
|
+
return `${RSSANY_INFO_PAGE_PREFIX}${encodeURIComponent(html)}`;
|
|
1315
|
+
}
|
|
1316
|
+
function isRssAnyInfoPage(page) {
|
|
1317
|
+
return page.url().startsWith(RSSANY_INFO_PAGE_PREFIX);
|
|
1318
|
+
}
|
|
1319
|
+
function isBlankPage(page) {
|
|
1320
|
+
const url = page.url();
|
|
1321
|
+
return url === "about:blank" || url === "" || url.startsWith("chrome://newtab");
|
|
1322
|
+
}
|
|
1323
|
+
async function cleanupExtraBlankPages(browser) {
|
|
1324
|
+
if (!isBrowserConnected(browser)) return;
|
|
1325
|
+
const pages = await browser.pages().catch(() => []);
|
|
1326
|
+
for (const page of pages) {
|
|
1327
|
+
if (page.isClosed() || isRssAnyInfoPage(page) || !isBlankPage(page)) continue;
|
|
1328
|
+
if (pages.length <= 1) continue;
|
|
1329
|
+
await page.close().catch(() => {
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
async function ensureRssAnyInfoPage(browser) {
|
|
1334
|
+
if (!isBrowserConnected(browser)) return;
|
|
1335
|
+
const current = infoPagePromises.get(browser);
|
|
1336
|
+
if (current) return current;
|
|
1337
|
+
const promise = (async () => {
|
|
1338
|
+
const pages = await browser.pages().catch(() => []);
|
|
1339
|
+
if (pages.some((page2) => !page2.isClosed() && isRssAnyInfoPage(page2))) {
|
|
1340
|
+
await cleanupExtraBlankPages(browser);
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
const page = await browser.newPage();
|
|
1344
|
+
await page.goto(rssAnyInfoPageUrl(), { waitUntil: "domcontentloaded", timeout: 1e4 }).catch(() => {
|
|
1345
|
+
});
|
|
1346
|
+
await cleanupExtraBlankPages(browser);
|
|
1347
|
+
})().finally(() => {
|
|
1348
|
+
infoPagePromises.delete(browser);
|
|
1349
|
+
});
|
|
1350
|
+
infoPagePromises.set(browser, promise);
|
|
1351
|
+
return promise;
|
|
1352
|
+
}
|
|
1353
|
+
function setupRssAnyBrowserLifecycle(browser, headless) {
|
|
1354
|
+
if (headless || managedBrowsers.has(browser)) return;
|
|
1355
|
+
managedBrowsers.add(browser);
|
|
1356
|
+
browser.on("targetcreated", () => {
|
|
1357
|
+
setTimeout(() => {
|
|
1358
|
+
cleanupExtraBlankPages(browser).catch(() => {
|
|
1359
|
+
});
|
|
1360
|
+
}, 2500);
|
|
1361
|
+
});
|
|
1362
|
+
browser.on("targetdestroyed", () => {
|
|
1363
|
+
setTimeout(() => {
|
|
1364
|
+
ensureRssAnyInfoPage(browser).catch(() => {
|
|
1365
|
+
});
|
|
1366
|
+
}, 300);
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1177
1369
|
function browserKey(config) {
|
|
1178
|
-
const wantHeadless = config.headless !== false;
|
|
1179
1370
|
const executablePath = config.chromeExecutablePath ?? process.env.CHROME_PATH ?? findChromeExecutable() ?? "";
|
|
1180
|
-
const userDataDir = getUserDataDir(config.cacheDir);
|
|
1181
1371
|
const proxy = resolveProxy(config) ?? "";
|
|
1372
|
+
const userDataDir = getUserDataDir(config.cacheDir, proxy);
|
|
1182
1373
|
return JSON.stringify({
|
|
1183
|
-
headless: wantHeadless,
|
|
1184
1374
|
userDataDir: userDataDir ? resolve(userDataDir) : "",
|
|
1185
1375
|
proxy,
|
|
1186
1376
|
executablePath
|
|
@@ -1195,7 +1385,8 @@ async function launchBrowser(config) {
|
|
|
1195
1385
|
if (!executablePath) {
|
|
1196
1386
|
throw new Error("未找到 Chrome 可执行文件,请安装 Google Chrome 或设置 CHROME_PATH 环境变量");
|
|
1197
1387
|
}
|
|
1198
|
-
const
|
|
1388
|
+
const proxy = resolveProxy(config);
|
|
1389
|
+
const userDataDir = getUserDataDir(config.cacheDir, proxy);
|
|
1199
1390
|
const maxRetries = 2;
|
|
1200
1391
|
let lastErr;
|
|
1201
1392
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
@@ -1211,7 +1402,7 @@ async function launchBrowser(config) {
|
|
|
1211
1402
|
}
|
|
1212
1403
|
return await puppeteerCore.launch({
|
|
1213
1404
|
headless: wantHeadless,
|
|
1214
|
-
args: launchArgs({ proxy
|
|
1405
|
+
args: launchArgs({ proxy, headless: wantHeadless }),
|
|
1215
1406
|
userDataDir,
|
|
1216
1407
|
executablePath,
|
|
1217
1408
|
ignoreDefaultArgs: ["--enable-automation"]
|
|
@@ -1224,7 +1415,7 @@ async function launchBrowser(config) {
|
|
|
1224
1415
|
if (isAlreadyRunningError(e)) {
|
|
1225
1416
|
const dir = userDataDir ?? "browser_data/main";
|
|
1226
1417
|
throw new Error(
|
|
1227
|
-
`Chrome 的 profile 目录已被占用(${dir}
|
|
1418
|
+
`Chrome 的 profile 目录已被占用(${dir})。通常是因为上次未正常退出,或另一个服务实例正在使用同一个缓存目录。请关闭占用该目录的 Chrome 进程后重试,或设置环境变量 CACHE_DIR 使用不同缓存目录。`
|
|
1228
1419
|
);
|
|
1229
1420
|
}
|
|
1230
1421
|
throw e;
|
|
@@ -1233,24 +1424,53 @@ async function launchBrowser(config) {
|
|
|
1233
1424
|
throw lastErr;
|
|
1234
1425
|
}
|
|
1235
1426
|
async function getOrCreateBrowser(config) {
|
|
1236
|
-
const
|
|
1427
|
+
const normalizedConfig = { ...config, proxy: resolveProxy(config) };
|
|
1428
|
+
const key = browserKey(normalizedConfig);
|
|
1429
|
+
const wantHeadless = normalizedConfig.headless !== false;
|
|
1237
1430
|
const current = sharedBrowsers.get(key);
|
|
1238
|
-
if (isBrowserConnected(current?.browser)) {
|
|
1239
|
-
return current.browser;
|
|
1240
|
-
}
|
|
1241
1431
|
if (current?.promise) {
|
|
1242
|
-
|
|
1432
|
+
const browser = await current.promise;
|
|
1433
|
+
if (isBrowserConnected(browser) && (wantHeadless || current.headless === false)) {
|
|
1434
|
+
if (!wantHeadless) {
|
|
1435
|
+
await ensureRssAnyInfoPage(browser);
|
|
1436
|
+
}
|
|
1437
|
+
return browser;
|
|
1438
|
+
}
|
|
1439
|
+
if (isBrowserConnected(browser)) {
|
|
1440
|
+
await browser.close().catch(() => {
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1443
|
+
if (sharedBrowsers.get(key) === current) {
|
|
1444
|
+
sharedBrowsers.delete(key);
|
|
1445
|
+
}
|
|
1446
|
+
} else if (isBrowserConnected(current?.browser)) {
|
|
1447
|
+
if (wantHeadless || current.headless === false) {
|
|
1448
|
+
if (!wantHeadless) {
|
|
1449
|
+
await ensureRssAnyInfoPage(current.browser);
|
|
1450
|
+
}
|
|
1451
|
+
return current.browser;
|
|
1452
|
+
}
|
|
1453
|
+
await current.browser.close().catch(() => {
|
|
1454
|
+
});
|
|
1455
|
+
if (sharedBrowsers.get(key) === current) {
|
|
1456
|
+
sharedBrowsers.delete(key);
|
|
1457
|
+
}
|
|
1243
1458
|
}
|
|
1244
1459
|
const slot = {};
|
|
1245
|
-
const promise = launchBrowser(
|
|
1460
|
+
const promise = launchBrowser(normalizedConfig).then((browser) => {
|
|
1246
1461
|
slot.browser = browser;
|
|
1247
1462
|
slot.promise = void 0;
|
|
1463
|
+
slot.headless = wantHeadless;
|
|
1464
|
+
setupRssAnyBrowserLifecycle(browser, wantHeadless);
|
|
1248
1465
|
browser.once("disconnected", () => {
|
|
1249
1466
|
if (sharedBrowsers.get(key)?.browser === browser) {
|
|
1250
1467
|
sharedBrowsers.delete(key);
|
|
1251
1468
|
}
|
|
1252
1469
|
});
|
|
1253
|
-
|
|
1470
|
+
if (wantHeadless) {
|
|
1471
|
+
return browser;
|
|
1472
|
+
}
|
|
1473
|
+
return ensureRssAnyInfoPage(browser).then(() => browser);
|
|
1254
1474
|
}).catch((err) => {
|
|
1255
1475
|
if (sharedBrowsers.get(key) === slot) {
|
|
1256
1476
|
sharedBrowsers.delete(key);
|
|
@@ -1284,32 +1504,41 @@ async function preCheckAuth(authFlow, cacheDir, opts) {
|
|
|
1284
1504
|
}
|
|
1285
1505
|
async function ensureAuth(authFlow, cacheDir, opts) {
|
|
1286
1506
|
const { checkAuth, loginUrl, loginTimeoutMs = 60 * 1e3, pollIntervalMs = 2e3 } = authFlow;
|
|
1287
|
-
const browser = await
|
|
1507
|
+
const browser = await getOrCreateBrowser({ headless: false, cacheDir, proxy: resolveProxy(opts) });
|
|
1508
|
+
const page = await browser.newPage();
|
|
1288
1509
|
try {
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
const authenticated2 = await checkAuth(page, page.url());
|
|
1301
|
-
if (authenticated2) return;
|
|
1302
|
-
}
|
|
1303
|
-
throw new Error(`登录超时(${loginTimeoutMs}ms)`);
|
|
1304
|
-
} finally {
|
|
1305
|
-
await page.close().catch(() => {
|
|
1306
|
-
});
|
|
1510
|
+
await setupPage(page, false);
|
|
1511
|
+
await applyProxyAuthToPage(page, opts);
|
|
1512
|
+
await page.goto(loginUrl, { waitUntil: "domcontentloaded", timeout: 6e4 });
|
|
1513
|
+
await new Promise((resolve2) => setTimeout(resolve2, 3e3));
|
|
1514
|
+
const authenticated = await checkAuth(page, page.url());
|
|
1515
|
+
if (authenticated) return;
|
|
1516
|
+
const startTime = Date.now();
|
|
1517
|
+
while (Date.now() - startTime < loginTimeoutMs) {
|
|
1518
|
+
await new Promise((resolve2) => setTimeout(resolve2, pollIntervalMs));
|
|
1519
|
+
const authenticated2 = await checkAuth(page, page.url());
|
|
1520
|
+
if (authenticated2) return;
|
|
1307
1521
|
}
|
|
1522
|
+
throw new Error(`登录超时(${loginTimeoutMs}ms)`);
|
|
1308
1523
|
} finally {
|
|
1309
|
-
await
|
|
1524
|
+
await page.close().catch(() => {
|
|
1310
1525
|
});
|
|
1311
1526
|
}
|
|
1312
1527
|
}
|
|
1528
|
+
async function openBrowserPage(url, cacheDir, opts) {
|
|
1529
|
+
const browser = await getOrCreateBrowser({ headless: false, cacheDir, proxy: resolveProxy(opts) });
|
|
1530
|
+
const page = await browser.newPage();
|
|
1531
|
+
try {
|
|
1532
|
+
await setupPage(page, false);
|
|
1533
|
+
await applyProxyAuthToPage(page, opts);
|
|
1534
|
+
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 6e4 });
|
|
1535
|
+
return page;
|
|
1536
|
+
} catch (err) {
|
|
1537
|
+
await page.close().catch(() => {
|
|
1538
|
+
});
|
|
1539
|
+
throw err;
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1313
1542
|
async function fetchHtml(url, config = {}) {
|
|
1314
1543
|
const {
|
|
1315
1544
|
timeoutMs,
|
|
@@ -2133,30 +2362,52 @@ async function initSources() {
|
|
|
2133
2362
|
function resolveRef(src) {
|
|
2134
2363
|
return src.ref ?? src.url ?? "";
|
|
2135
2364
|
}
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2365
|
+
function normalizeProxyList(raw) {
|
|
2366
|
+
if (!Array.isArray(raw)) return [];
|
|
2367
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2368
|
+
const out = [];
|
|
2369
|
+
for (const v of raw) {
|
|
2370
|
+
if (typeof v !== "string") continue;
|
|
2371
|
+
const t = v.trim();
|
|
2372
|
+
if (!t || seen.has(t)) continue;
|
|
2373
|
+
seen.add(t);
|
|
2374
|
+
out.push(t);
|
|
2145
2375
|
}
|
|
2146
|
-
return
|
|
2376
|
+
return out;
|
|
2147
2377
|
}
|
|
2148
|
-
async function
|
|
2149
|
-
let root = {};
|
|
2378
|
+
async function readConfigRoot() {
|
|
2150
2379
|
try {
|
|
2151
2380
|
const raw = await readFile(CONFIG_PATH, "utf-8");
|
|
2152
|
-
|
|
2381
|
+
return JSON.parse(raw);
|
|
2153
2382
|
} catch {
|
|
2383
|
+
return {};
|
|
2154
2384
|
}
|
|
2155
|
-
|
|
2156
|
-
|
|
2385
|
+
}
|
|
2386
|
+
async function readProxySettingsFromConfig() {
|
|
2387
|
+
const root = await readConfigRoot();
|
|
2388
|
+
const globalProxy = typeof root.globalProxy === "string" ? root.globalProxy.trim() : "";
|
|
2389
|
+
return {
|
|
2390
|
+
globalProxy,
|
|
2391
|
+
proxyList: normalizeProxyList(root.proxyList)
|
|
2392
|
+
};
|
|
2393
|
+
}
|
|
2394
|
+
async function readGlobalProxyFromConfig() {
|
|
2395
|
+
const { globalProxy } = await readProxySettingsFromConfig();
|
|
2396
|
+
return globalProxy.length > 0 ? globalProxy : void 0;
|
|
2397
|
+
}
|
|
2398
|
+
async function saveProxySettingsToConfig(settings) {
|
|
2399
|
+
const root = await readConfigRoot();
|
|
2400
|
+
const globalProxy = settings.globalProxy.trim();
|
|
2401
|
+
const proxyList = normalizeProxyList(settings.proxyList);
|
|
2402
|
+
if (globalProxy) {
|
|
2403
|
+
root.globalProxy = globalProxy;
|
|
2404
|
+
} else {
|
|
2157
2405
|
delete root.globalProxy;
|
|
2406
|
+
}
|
|
2407
|
+
if (proxyList.length > 0) {
|
|
2408
|
+
root.proxyList = proxyList;
|
|
2158
2409
|
} else {
|
|
2159
|
-
root.
|
|
2410
|
+
delete root.proxyList;
|
|
2160
2411
|
}
|
|
2161
2412
|
await writeFile(CONFIG_PATH, JSON.stringify(root, null, 2) + "\n", "utf-8");
|
|
2162
2413
|
}
|
|
@@ -3533,23 +3784,7 @@ function registerSourcesRoutes(app) {
|
|
|
3533
3784
|
const source = getSource(url);
|
|
3534
3785
|
const merged = await getEffectiveProxyForListUrl(url, source);
|
|
3535
3786
|
const proxy = resolveProxy({ proxy: merged });
|
|
3536
|
-
void
|
|
3537
|
-
try {
|
|
3538
|
-
const page = await browser.newPage();
|
|
3539
|
-
await applyProxyAuthToPage(page, { proxy: merged });
|
|
3540
|
-
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";
|
|
3541
|
-
await page.setUserAgent(realUserAgent);
|
|
3542
|
-
await page.setViewport({ width: 1366, height: 960 });
|
|
3543
|
-
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 6e4 });
|
|
3544
|
-
page.once("close", () => {
|
|
3545
|
-
void browser.close().catch(() => {
|
|
3546
|
-
});
|
|
3547
|
-
});
|
|
3548
|
-
} catch {
|
|
3549
|
-
await browser.close().catch(() => {
|
|
3550
|
-
});
|
|
3551
|
-
}
|
|
3552
|
-
}).catch(() => {
|
|
3787
|
+
void openBrowserPage(url, CACHE_DIR, { proxy }).catch(() => {
|
|
3553
3788
|
});
|
|
3554
3789
|
return c.json({ ok: true, message: "已在爬虫浏览器中打开" });
|
|
3555
3790
|
} catch {
|
|
@@ -3824,16 +4059,15 @@ function registerLlmRoutes(app) {
|
|
|
3824
4059
|
}
|
|
3825
4060
|
function registerProxySettingsRoutes(app) {
|
|
3826
4061
|
app.get("/api/proxy", requireAdmin(), async (c) => {
|
|
3827
|
-
|
|
3828
|
-
return c.json({ globalProxy });
|
|
4062
|
+
return c.json(await readProxySettingsFromConfig());
|
|
3829
4063
|
});
|
|
3830
4064
|
app.put("/api/proxy", requireAdmin(), async (c) => {
|
|
3831
4065
|
try {
|
|
3832
4066
|
const body = await c.req.json().catch(() => ({}));
|
|
3833
4067
|
const globalProxy = typeof body.globalProxy === "string" ? body.globalProxy : "";
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
return c.json({ ok: true,
|
|
4068
|
+
const proxyList = Array.isArray(body.proxyList) ? body.proxyList.filter((v) => typeof v === "string") : [];
|
|
4069
|
+
await saveProxySettingsToConfig({ globalProxy, proxyList });
|
|
4070
|
+
return c.json({ ok: true, ...await readProxySettingsFromConfig() });
|
|
3837
4071
|
} catch (err) {
|
|
3838
4072
|
return c.json(
|
|
3839
4073
|
{ ok: false, message: err instanceof Error ? err.message : String(err) },
|
|
@@ -3920,7 +4154,7 @@ const CACHE_KEY_PREFIX = "feed-favicon:v1:";
|
|
|
3920
4154
|
const CACHE_MAX_AGE_SEC = 3 * 24 * 60 * 60;
|
|
3921
4155
|
const CACHE_MAX_AGE_MS = CACHE_MAX_AGE_SEC * 1e3;
|
|
3922
4156
|
const CACHE_CONTROL = `public, max-age=${CACHE_MAX_AGE_SEC}`;
|
|
3923
|
-
const FETCH_TIMEOUT_MS = 6e3;
|
|
4157
|
+
const FETCH_TIMEOUT_MS$1 = 6e3;
|
|
3924
4158
|
const MAX_ICON_BYTES = 2 * 1024 * 1024;
|
|
3925
4159
|
const MAX_HTML_BYTES = 512 * 1024;
|
|
3926
4160
|
const inflightByDomain = /* @__PURE__ */ new Map();
|
|
@@ -4008,7 +4242,7 @@ async function fetchHtmlPage(url) {
|
|
|
4008
4242
|
Accept: "text/html,application/xhtml+xml;q=0.9,*/*;q=0.1",
|
|
4009
4243
|
"User-Agent": "Mozilla/5.0 (compatible; RssAny/1.0; +https://github.com/rssany/rssany) favicon"
|
|
4010
4244
|
},
|
|
4011
|
-
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
4245
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS$1)
|
|
4012
4246
|
});
|
|
4013
4247
|
if (!upstream.ok) return null;
|
|
4014
4248
|
const ab = await upstream.arrayBuffer();
|
|
@@ -4105,7 +4339,7 @@ async function fetchIconCandidate(url) {
|
|
|
4105
4339
|
Accept: "image/avif,image/webp,image/apng,image/*,*/*;q=0.8",
|
|
4106
4340
|
"User-Agent": "Mozilla/5.0 (compatible; RssAny/1.0; +https://github.com/rssany/rssany) favicon"
|
|
4107
4341
|
},
|
|
4108
|
-
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
4342
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS$1)
|
|
4109
4343
|
});
|
|
4110
4344
|
} catch {
|
|
4111
4345
|
return null;
|
|
@@ -4216,9 +4450,53 @@ function registerFeedFaviconRoutes(app) {
|
|
|
4216
4450
|
});
|
|
4217
4451
|
});
|
|
4218
4452
|
}
|
|
4453
|
+
const MAX_BYTES = 5 * 1024 * 1024;
|
|
4454
|
+
const FETCH_TIMEOUT_MS = 12e3;
|
|
4455
|
+
function isAllowedImageUrl(raw) {
|
|
4456
|
+
try {
|
|
4457
|
+
const url = new URL(raw);
|
|
4458
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
|
4459
|
+
if (!url.hostname) return null;
|
|
4460
|
+
return url;
|
|
4461
|
+
} catch {
|
|
4462
|
+
return null;
|
|
4463
|
+
}
|
|
4464
|
+
}
|
|
4465
|
+
function registerCoverImgRoutes(app) {
|
|
4466
|
+
app.get("/api/cover-img", async (c) => {
|
|
4467
|
+
const raw = (c.req.query("url") ?? "").trim();
|
|
4468
|
+
if (!raw) return c.text("missing url", 400);
|
|
4469
|
+
const url = isAllowedImageUrl(raw);
|
|
4470
|
+
if (!url) return c.text("invalid url", 400);
|
|
4471
|
+
try {
|
|
4472
|
+
const res = await fetch(url.toString(), {
|
|
4473
|
+
redirect: "follow",
|
|
4474
|
+
headers: {
|
|
4475
|
+
Accept: "image/avif,image/webp,image/apng,image/*,*/*;q=0.8",
|
|
4476
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
|
4477
|
+
Referer: `${url.origin}/`
|
|
4478
|
+
},
|
|
4479
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
4480
|
+
});
|
|
4481
|
+
if (!res.ok) return c.text("upstream error", 502);
|
|
4482
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
4483
|
+
if (buf.length > MAX_BYTES) return c.text("too large", 413);
|
|
4484
|
+
const ct = (res.headers.get("content-type") ?? "application/octet-stream").split(";")[0].trim();
|
|
4485
|
+
return new Response(buf, {
|
|
4486
|
+
headers: {
|
|
4487
|
+
"Content-Type": ct,
|
|
4488
|
+
"Cache-Control": "public, max-age=86400"
|
|
4489
|
+
}
|
|
4490
|
+
});
|
|
4491
|
+
} catch {
|
|
4492
|
+
return c.text("fetch failed", 502);
|
|
4493
|
+
}
|
|
4494
|
+
});
|
|
4495
|
+
}
|
|
4219
4496
|
function registerApiRoutes(app) {
|
|
4220
4497
|
registerServerRoutes(app);
|
|
4221
4498
|
registerFeedFaviconRoutes(app);
|
|
4499
|
+
registerCoverImgRoutes(app);
|
|
4222
4500
|
registerRssApiRoutes(app);
|
|
4223
4501
|
registerSchedulerRoutes(app);
|
|
4224
4502
|
registerPluginsRoutes(app);
|
|
@@ -4263,23 +4541,7 @@ function registerAuthRoutes(app) {
|
|
|
4263
4541
|
if (!authFlow) return c.json({ ok: false, message: "该站点无需登录" }, 400);
|
|
4264
4542
|
const { loginUrl } = authFlow;
|
|
4265
4543
|
const proxy = await resolveProxyForSite(site);
|
|
4266
|
-
void
|
|
4267
|
-
try {
|
|
4268
|
-
const page = await browser.newPage();
|
|
4269
|
-
await applyProxyAuthToPage(page, { proxy });
|
|
4270
|
-
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";
|
|
4271
|
-
await page.setUserAgent(realUserAgent);
|
|
4272
|
-
await page.setViewport({ width: 1366, height: 960 });
|
|
4273
|
-
await page.goto(loginUrl, { waitUntil: "domcontentloaded", timeout: 6e4 });
|
|
4274
|
-
page.once("close", () => {
|
|
4275
|
-
void browser.close().catch(() => {
|
|
4276
|
-
});
|
|
4277
|
-
});
|
|
4278
|
-
} catch {
|
|
4279
|
-
await browser.close().catch(() => {
|
|
4280
|
-
});
|
|
4281
|
-
}
|
|
4282
|
-
}).catch(() => {
|
|
4544
|
+
void openBrowserPage(loginUrl, CACHE_DIR, { proxy: resolveProxy({ proxy }) }).catch(() => {
|
|
4283
4545
|
});
|
|
4284
4546
|
return c.json({ ok: true, message: "已打开登录页面" });
|
|
4285
4547
|
});
|