@skrillex1224/playwright-toolkit 2.1.286 → 2.1.287
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/dist/browser.js +161 -409
- package/dist/browser.js.map +4 -4
- package/dist/index.cjs +1494 -2032
- package/dist/index.cjs.map +4 -4
- package/dist/index.js +1495 -2033
- package/dist/index.js.map +4 -4
- package/dist/internals/proxy-meter.js +51 -5
- package/index.d.ts +6 -59
- package/package.json +1 -7
package/dist/index.js
CHANGED
|
@@ -13,11 +13,9 @@ __export(constants_exports, {
|
|
|
13
13
|
ActorInfo: () => ActorInfo,
|
|
14
14
|
Code: () => Code,
|
|
15
15
|
Device: () => Device,
|
|
16
|
-
Mode: () => Mode,
|
|
17
16
|
PresetOfLiveViewKey: () => PresetOfLiveViewKey,
|
|
18
17
|
Status: () => Status,
|
|
19
|
-
normalizeDevice: () => normalizeDevice
|
|
20
|
-
normalizeMode: () => normalizeMode
|
|
18
|
+
normalizeDevice: () => normalizeDevice
|
|
21
19
|
});
|
|
22
20
|
var Code = {
|
|
23
21
|
Success: 0,
|
|
@@ -37,10 +35,6 @@ var Device = Object.freeze({
|
|
|
37
35
|
Desktop: "desktop",
|
|
38
36
|
Mobile: "mobile"
|
|
39
37
|
});
|
|
40
|
-
var Mode = Object.freeze({
|
|
41
|
-
Default: "default",
|
|
42
|
-
CloakBrowser: "cloakbrowser"
|
|
43
|
-
});
|
|
44
38
|
var normalizeDevice = (value, fallback = Device.Desktop) => {
|
|
45
39
|
const normalizedFallback = String(fallback || "").trim().toLowerCase() === Device.Mobile ? Device.Mobile : Device.Desktop;
|
|
46
40
|
const raw = String(value || "").trim().toLowerCase();
|
|
@@ -48,13 +42,6 @@ var normalizeDevice = (value, fallback = Device.Desktop) => {
|
|
|
48
42
|
if (raw === Device.Desktop) return Device.Desktop;
|
|
49
43
|
return normalizedFallback;
|
|
50
44
|
};
|
|
51
|
-
var normalizeMode = (value, fallback = Mode.Default) => {
|
|
52
|
-
const normalizedFallback = String(fallback || "").trim().toLowerCase() === Mode.CloakBrowser ? Mode.CloakBrowser : Mode.Default;
|
|
53
|
-
const raw = String(value || "").trim().toLowerCase();
|
|
54
|
-
if (raw === Mode.CloakBrowser) return Mode.CloakBrowser;
|
|
55
|
-
if (raw === Mode.Default) return Mode.Default;
|
|
56
|
-
return normalizedFallback;
|
|
57
|
-
};
|
|
58
45
|
var createActorInfo = (info) => {
|
|
59
46
|
const normalizeDomain = (value) => {
|
|
60
47
|
if (!value) return "";
|
|
@@ -122,6 +109,7 @@ var createActorInfo = (info) => {
|
|
|
122
109
|
const buildLandingUrl = ({ protocol: protocol2, domain: domain2, path: path4 }) => {
|
|
123
110
|
const safeProtocol = String(protocol2).trim();
|
|
124
111
|
const safeDomain = normalizeDomain(domain2);
|
|
112
|
+
if (!safeDomain) return "";
|
|
125
113
|
const safePath = normalizePath(path4);
|
|
126
114
|
return `${safeProtocol}://${safeDomain}${safePath}`;
|
|
127
115
|
};
|
|
@@ -329,6 +317,18 @@ var ActorInfo = {
|
|
|
329
317
|
prefix: "",
|
|
330
318
|
xurl: []
|
|
331
319
|
}
|
|
320
|
+
}),
|
|
321
|
+
// 通用网页抓取 Actor:入口 URL 来自 query,因此这里只声明统一的 Actor 元信息。
|
|
322
|
+
webpage: createActorInfo({
|
|
323
|
+
key: "webpage",
|
|
324
|
+
name: "\u901A\u7528\u7F51\u9875",
|
|
325
|
+
domain: "",
|
|
326
|
+
path: "/",
|
|
327
|
+
share: {
|
|
328
|
+
mode: "dom",
|
|
329
|
+
prefix: "",
|
|
330
|
+
xurl: []
|
|
331
|
+
}
|
|
332
332
|
})
|
|
333
333
|
};
|
|
334
334
|
|
|
@@ -753,7 +753,7 @@ var adjustAffixedElementsForExpandedScreenshot = async (page, options = {}) => {
|
|
|
753
753
|
if (safeTargetHeight <= viewportHeight + 1) {
|
|
754
754
|
return 0;
|
|
755
755
|
}
|
|
756
|
-
const
|
|
756
|
+
const hasOwn = (source, key) => Object.prototype.hasOwnProperty.call(source, key);
|
|
757
757
|
const isVisible = (el, style, rect) => {
|
|
758
758
|
if (!el || !style || !rect) return false;
|
|
759
759
|
if (style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse") {
|
|
@@ -793,7 +793,7 @@ var adjustAffixedElementsForExpandedScreenshot = async (page, options = {}) => {
|
|
|
793
793
|
return true;
|
|
794
794
|
});
|
|
795
795
|
topLevelCandidates.forEach(({ el, position, edge }) => {
|
|
796
|
-
if (!
|
|
796
|
+
if (!hasOwn(el.dataset, "pkAffixedAdjusted")) {
|
|
797
797
|
el.dataset.pkAffixedAdjusted = "1";
|
|
798
798
|
el.dataset.pkOrigPosition = el.style.getPropertyValue("position") || "";
|
|
799
799
|
el.dataset.pkOrigPositionPriority = el.style.getPropertyPriority("position") || "";
|
|
@@ -824,7 +824,7 @@ var adjustAffixedElementsForExpandedScreenshot = async (page, options = {}) => {
|
|
|
824
824
|
};
|
|
825
825
|
var restoreAffixedElementsForExpandedScreenshot = async (page) => {
|
|
826
826
|
await page.evaluate((className) => {
|
|
827
|
-
const
|
|
827
|
+
const hasOwn = (source, key) => Object.prototype.hasOwnProperty.call(source, key);
|
|
828
828
|
const expansionKeys = [
|
|
829
829
|
"pkOrigOverflow",
|
|
830
830
|
"pkOrigHeight",
|
|
@@ -832,28 +832,28 @@ var restoreAffixedElementsForExpandedScreenshot = async (page) => {
|
|
|
832
832
|
"pkOrigMaxHeight"
|
|
833
833
|
];
|
|
834
834
|
document.querySelectorAll('[data-pk-affixed-adjusted="1"]').forEach((el) => {
|
|
835
|
-
if (
|
|
835
|
+
if (hasOwn(el.dataset, "pkOrigPosition")) {
|
|
836
836
|
el.style.setProperty("position", el.dataset.pkOrigPosition || "", el.dataset.pkOrigPositionPriority || "");
|
|
837
837
|
delete el.dataset.pkOrigPosition;
|
|
838
838
|
delete el.dataset.pkOrigPositionPriority;
|
|
839
839
|
}
|
|
840
|
-
if (
|
|
840
|
+
if (hasOwn(el.dataset, "pkOrigTop")) {
|
|
841
841
|
el.style.setProperty("top", el.dataset.pkOrigTop || "", el.dataset.pkOrigTopPriority || "");
|
|
842
842
|
delete el.dataset.pkOrigTop;
|
|
843
843
|
delete el.dataset.pkOrigTopPriority;
|
|
844
844
|
}
|
|
845
|
-
if (
|
|
845
|
+
if (hasOwn(el.dataset, "pkOrigBottom")) {
|
|
846
846
|
el.style.setProperty("bottom", el.dataset.pkOrigBottom || "", el.dataset.pkOrigBottomPriority || "");
|
|
847
847
|
delete el.dataset.pkOrigBottom;
|
|
848
848
|
delete el.dataset.pkOrigBottomPriority;
|
|
849
849
|
}
|
|
850
|
-
if (
|
|
850
|
+
if (hasOwn(el.dataset, "pkOrigTranslate")) {
|
|
851
851
|
el.style.setProperty("translate", el.dataset.pkOrigTranslate || "", el.dataset.pkOrigTranslatePriority || "");
|
|
852
852
|
delete el.dataset.pkOrigTranslate;
|
|
853
853
|
delete el.dataset.pkOrigTranslatePriority;
|
|
854
854
|
}
|
|
855
855
|
delete el.dataset.pkAffixedAdjusted;
|
|
856
|
-
const stillExpanded = expansionKeys.some((key) =>
|
|
856
|
+
const stillExpanded = expansionKeys.some((key) => hasOwn(el.dataset, key));
|
|
857
857
|
if (!stillExpanded) {
|
|
858
858
|
el.classList.remove(className);
|
|
859
859
|
}
|
|
@@ -896,7 +896,7 @@ var prepareExpandedFullPageScreenshot = async (page, options = {}) => {
|
|
|
896
896
|
viewportResized
|
|
897
897
|
};
|
|
898
898
|
};
|
|
899
|
-
var restoreExpandedFullPageScreenshot = async (page,
|
|
899
|
+
var restoreExpandedFullPageScreenshot = async (page, state = {}) => {
|
|
900
900
|
await page.evaluate((className) => {
|
|
901
901
|
const targets = new Set([
|
|
902
902
|
...document.querySelectorAll(`.${className}`),
|
|
@@ -920,8 +920,8 @@ var restoreExpandedFullPageScreenshot = async (page, state2 = {}) => {
|
|
|
920
920
|
el.classList.remove(className);
|
|
921
921
|
});
|
|
922
922
|
}, EXPANDED_SCROLLABLE_CLASS);
|
|
923
|
-
if (
|
|
924
|
-
await page.setViewportSize(
|
|
923
|
+
if (state?.originalViewport && state?.viewportResized) {
|
|
924
|
+
await page.setViewportSize(state.originalViewport);
|
|
925
925
|
}
|
|
926
926
|
};
|
|
927
927
|
var capturePageScreenshot = async (page, options = {}) => {
|
|
@@ -978,21 +978,21 @@ var capturePageScreenshot = async (page, options = {}) => {
|
|
|
978
978
|
}
|
|
979
979
|
};
|
|
980
980
|
var captureExpandedFullPageScreenshot = async (page, options = {}) => {
|
|
981
|
-
const
|
|
981
|
+
const state = await prepareExpandedFullPageScreenshot(page, options);
|
|
982
982
|
try {
|
|
983
983
|
return await capturePageScreenshot(page, {
|
|
984
984
|
fullPage: true,
|
|
985
985
|
type: options.type || "png",
|
|
986
986
|
quality: options.quality,
|
|
987
987
|
timeout: options.timeout,
|
|
988
|
-
maxClipHeight:
|
|
988
|
+
maxClipHeight: state.targetHeight
|
|
989
989
|
});
|
|
990
990
|
} finally {
|
|
991
991
|
await restoreAffixedElementsForExpandedScreenshot(page).catch((error) => {
|
|
992
992
|
logger.warning(`\u79FB\u52A8\u7AEF\u5438\u9644\u5143\u7D20\u6062\u590D\u5931\u8D25: ${error?.message || error}`);
|
|
993
993
|
});
|
|
994
994
|
if (options.restore) {
|
|
995
|
-
await restoreExpandedFullPageScreenshot(page,
|
|
995
|
+
await restoreExpandedFullPageScreenshot(page, state);
|
|
996
996
|
}
|
|
997
997
|
}
|
|
998
998
|
};
|
|
@@ -1059,7 +1059,7 @@ import { serializeError as serializeError2 } from "serialize-error";
|
|
|
1059
1059
|
|
|
1060
1060
|
// src/internals/proxy-meter-runtime.js
|
|
1061
1061
|
import { spawn, spawnSync } from "child_process";
|
|
1062
|
-
import { existsSync, mkdirSync, readFileSync, rmSync } from "fs";
|
|
1062
|
+
import { createWriteStream, existsSync, mkdirSync, readFileSync, rmSync } from "fs";
|
|
1063
1063
|
import { tmpdir } from "os";
|
|
1064
1064
|
import path from "path";
|
|
1065
1065
|
import { fileURLToPath } from "url";
|
|
@@ -1168,6 +1168,19 @@ var ensureLogPath = () => {
|
|
|
1168
1168
|
const label = runId ? `proxy-meter-${runId}-${suffix}.json` : `proxy-meter-${process.pid}-${suffix}.json`;
|
|
1169
1169
|
return path.join(baseDir, label);
|
|
1170
1170
|
};
|
|
1171
|
+
var ensureStdioLogPath = (logPath) => `${logPath}.stdio.log`;
|
|
1172
|
+
var writeChildOutput = (stream, output, prefix) => {
|
|
1173
|
+
if (!stream || !output) return;
|
|
1174
|
+
output.on("data", (chunk) => {
|
|
1175
|
+
const text = chunk?.toString?.() || String(chunk || "");
|
|
1176
|
+
if (!text) return;
|
|
1177
|
+
for (const line of text.split(/\r?\n/)) {
|
|
1178
|
+
if (!line) continue;
|
|
1179
|
+
stream.write(`[${(/* @__PURE__ */ new Date()).toISOString()}] ${prefix} ${line}
|
|
1180
|
+
`);
|
|
1181
|
+
}
|
|
1182
|
+
});
|
|
1183
|
+
};
|
|
1171
1184
|
var readSnapshot = (logPath) => {
|
|
1172
1185
|
if (!logPath || !existsSync(logPath)) return null;
|
|
1173
1186
|
try {
|
|
@@ -1335,6 +1348,7 @@ var startProxyMeter = (options = {}) => {
|
|
|
1335
1348
|
observedDomainResourceTypes = /* @__PURE__ */ new Map();
|
|
1336
1349
|
const port = pickFreePort();
|
|
1337
1350
|
const logPath = ensureLogPath();
|
|
1351
|
+
const stdioLogPath = ensureStdioLogPath(logPath);
|
|
1338
1352
|
const scriptPath = resolveScriptPath();
|
|
1339
1353
|
const debugMode = Boolean(options.debugMode);
|
|
1340
1354
|
const debugMaxEvents = Math.max(10, toSafeInt(options.debugMaxEvents) || DEFAULT_DEBUG_MAX_EVENTS);
|
|
@@ -1347,19 +1361,28 @@ var startProxyMeter = (options = {}) => {
|
|
|
1347
1361
|
PROXY_METER_DEBUG: debugMode ? "1" : "0",
|
|
1348
1362
|
PROXY_METER_DEBUG_MAX_EVENTS: String(debugMaxEvents)
|
|
1349
1363
|
};
|
|
1364
|
+
const stdioLog = createWriteStream(stdioLogPath, { flags: "a" });
|
|
1365
|
+
stdioLog.write(`[${(/* @__PURE__ */ new Date()).toISOString()}] [proxy-meter-runtime] start script=${scriptPath} port=${port} snapshot=${logPath}
|
|
1366
|
+
`);
|
|
1350
1367
|
const child = spawn(process.execPath, [scriptPath], {
|
|
1351
1368
|
env,
|
|
1352
|
-
stdio: ["ignore", "
|
|
1369
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1353
1370
|
});
|
|
1354
|
-
child.
|
|
1371
|
+
writeChildOutput(stdioLog, child.stdout, "stdout");
|
|
1372
|
+
writeChildOutput(stdioLog, child.stderr, "stderr");
|
|
1373
|
+
child.once("exit", (code, signal) => {
|
|
1374
|
+
stdioLog.write(`[${(/* @__PURE__ */ new Date()).toISOString()}] [proxy-meter-runtime] exit code=${code ?? ""} signal=${signal ?? ""}
|
|
1375
|
+
`);
|
|
1376
|
+
stdioLog.end();
|
|
1355
1377
|
if (code && code !== 0) {
|
|
1356
|
-
logger2.warn(`[proxy-meter] exited with code ${code}`);
|
|
1378
|
+
logger2.warn(`[proxy-meter] exited with code ${code}; stdio=${stdioLogPath}`);
|
|
1357
1379
|
}
|
|
1358
1380
|
});
|
|
1359
1381
|
runtime = {
|
|
1360
1382
|
proc: child,
|
|
1361
1383
|
port,
|
|
1362
1384
|
logPath,
|
|
1385
|
+
stdioLogPath,
|
|
1363
1386
|
startedAt: Date.now()
|
|
1364
1387
|
};
|
|
1365
1388
|
registerCleanup();
|
|
@@ -1887,8 +1910,8 @@ var normalizeBrowserProfile = (value) => {
|
|
|
1887
1910
|
payload: buildBrowserProfilePayload(core, observed)
|
|
1888
1911
|
};
|
|
1889
1912
|
};
|
|
1890
|
-
var rememberRuntimeState = (
|
|
1891
|
-
rememberedRuntimeState = deepClone(
|
|
1913
|
+
var rememberRuntimeState = (state) => {
|
|
1914
|
+
rememberedRuntimeState = deepClone(state);
|
|
1892
1915
|
return rememberedRuntimeState;
|
|
1893
1916
|
};
|
|
1894
1917
|
var normalizeRuntimeState = (source = {}, actor = "") => {
|
|
@@ -1947,7 +1970,7 @@ var RuntimeEnv = {
|
|
|
1947
1970
|
} else {
|
|
1948
1971
|
delete normalizedRuntime.browser_profile;
|
|
1949
1972
|
}
|
|
1950
|
-
const
|
|
1973
|
+
const state = {
|
|
1951
1974
|
actor: resolvedActor,
|
|
1952
1975
|
device,
|
|
1953
1976
|
runtime: normalizedRuntime,
|
|
@@ -1961,73 +1984,73 @@ var RuntimeEnv = {
|
|
|
1961
1984
|
browserProfileCore: browserProfile.core,
|
|
1962
1985
|
browserProfileObserved: browserProfile.observed
|
|
1963
1986
|
};
|
|
1964
|
-
rememberRuntimeState(
|
|
1965
|
-
return
|
|
1987
|
+
rememberRuntimeState(state);
|
|
1988
|
+
return state;
|
|
1966
1989
|
},
|
|
1967
1990
|
// buildEnvPatch 只构造允许回写到后端 env 的字段集合。
|
|
1968
1991
|
buildEnvPatch(source = {}, actor = "") {
|
|
1969
|
-
const
|
|
1970
|
-
const browserProfile = buildBrowserProfilePayload(
|
|
1992
|
+
const state = normalizeRuntimeState(source, actor);
|
|
1993
|
+
const browserProfile = buildBrowserProfilePayload(state.browserProfileCore, state.browserProfileObserved);
|
|
1971
1994
|
const envPatch = {
|
|
1972
|
-
...Array.isArray(
|
|
1973
|
-
...Object.keys(
|
|
1974
|
-
...Object.keys(
|
|
1995
|
+
...Array.isArray(state.cookies) && state.cookies.length > 0 ? { cookies: state.cookies } : {},
|
|
1996
|
+
...Object.keys(state.localStorage || {}).length > 0 ? { local_storage: state.localStorage } : {},
|
|
1997
|
+
...Object.keys(state.sessionStorage || {}).length > 0 ? { session_storage: state.sessionStorage } : {},
|
|
1975
1998
|
...Object.keys(browserProfile).length > 0 ? { browser_profile: browserProfile } : {}
|
|
1976
1999
|
};
|
|
1977
2000
|
return Object.keys(envPatch).length > 0 ? envPatch : null;
|
|
1978
2001
|
},
|
|
1979
2002
|
// hasLoginState 只判断 runtime 是否存在有效载荷,不再区分具体字段来源。
|
|
1980
2003
|
hasLoginState(source = {}, actor = "") {
|
|
1981
|
-
const
|
|
1982
|
-
return isPlainObject(
|
|
2004
|
+
const state = normalizeRuntimeState(source, actor);
|
|
2005
|
+
return isPlainObject(state.runtime) && Object.keys(state.runtime || {}).length > 0;
|
|
1983
2006
|
},
|
|
1984
2007
|
rememberState(source = {}) {
|
|
1985
|
-
const
|
|
1986
|
-
rememberRuntimeState(
|
|
2008
|
+
const state = normalizeRuntimeState(source);
|
|
2009
|
+
rememberRuntimeState(state);
|
|
1987
2010
|
return RuntimeEnv.peekRememberedState();
|
|
1988
2011
|
},
|
|
1989
2012
|
peekRememberedState() {
|
|
1990
2013
|
return rememberedRuntimeState ? deepClone(rememberedRuntimeState) : null;
|
|
1991
2014
|
},
|
|
1992
2015
|
getBrowserProfileCore(source = {}, actor = "") {
|
|
1993
|
-
const
|
|
1994
|
-
return deepClone(
|
|
2016
|
+
const state = normalizeRuntimeState(source, actor);
|
|
2017
|
+
return deepClone(state.browserProfileCore || {});
|
|
1995
2018
|
},
|
|
1996
2019
|
setBrowserProfileCore(source = {}, core = {}, actor = "") {
|
|
1997
|
-
const
|
|
2020
|
+
const state = normalizeRuntimeState(source, actor);
|
|
1998
2021
|
const normalizedCore = normalizeBrowserProfileCore({
|
|
1999
2022
|
...core,
|
|
2000
|
-
device: normalizeKnownDevice(core?.device) ||
|
|
2023
|
+
device: normalizeKnownDevice(core?.device) || state.device
|
|
2001
2024
|
});
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
if (Object.keys(
|
|
2005
|
-
|
|
2025
|
+
state.browserProfileCore = normalizedCore;
|
|
2026
|
+
state.browserProfile = buildBrowserProfilePayload(normalizedCore, state.browserProfileObserved);
|
|
2027
|
+
if (Object.keys(state.browserProfile).length > 0) {
|
|
2028
|
+
state.runtime.browser_profile = state.browserProfile;
|
|
2006
2029
|
} else {
|
|
2007
|
-
delete
|
|
2030
|
+
delete state.runtime.browser_profile;
|
|
2008
2031
|
}
|
|
2009
|
-
rememberRuntimeState(
|
|
2010
|
-
return
|
|
2032
|
+
rememberRuntimeState(state);
|
|
2033
|
+
return state;
|
|
2011
2034
|
},
|
|
2012
2035
|
// applyToPage 只负责把登录态相关字段注入页面:
|
|
2013
2036
|
// cookies / localStorage / sessionStorage。
|
|
2014
2037
|
// 指纹、时区、UA、viewport 的回放发生在 launch.js 启动阶段,不在这里做。
|
|
2015
2038
|
async applyToPage(page, source = {}, options = {}) {
|
|
2016
2039
|
if (!page) return;
|
|
2017
|
-
let
|
|
2040
|
+
let state = normalizeRuntimeState(source, options?.actor || "");
|
|
2018
2041
|
if (typeof options?.preapply === "function") {
|
|
2019
|
-
|
|
2020
|
-
rememberRuntimeState(
|
|
2042
|
+
state = await options.preapply(state) || state;
|
|
2043
|
+
rememberRuntimeState(state);
|
|
2021
2044
|
}
|
|
2022
2045
|
Object.defineProperty(page, PageRuntimeStateKey, {
|
|
2023
2046
|
configurable: true,
|
|
2024
2047
|
enumerable: false,
|
|
2025
2048
|
writable: true,
|
|
2026
|
-
value:
|
|
2049
|
+
value: state
|
|
2027
2050
|
});
|
|
2028
|
-
const localStorage =
|
|
2029
|
-
const sessionStorage =
|
|
2030
|
-
const cookies = (
|
|
2051
|
+
const localStorage = state.localStorage || {};
|
|
2052
|
+
const sessionStorage = state.sessionStorage || {};
|
|
2053
|
+
const cookies = (state.cookies || []).map((cookie) => {
|
|
2031
2054
|
const normalized = { ...cookie };
|
|
2032
2055
|
if (!normalized.path) {
|
|
2033
2056
|
normalized.path = "/";
|
|
@@ -2065,8 +2088,8 @@ var RuntimeEnv = {
|
|
|
2065
2088
|
},
|
|
2066
2089
|
// captureEnvPatch 在任务结束时采集最新环境快照,用于 pushSuccess / pushFailed 自动回写。
|
|
2067
2090
|
async captureEnvPatch(page, source = {}, options = {}) {
|
|
2068
|
-
const
|
|
2069
|
-
const baseline = RuntimeEnv.buildEnvPatch(
|
|
2091
|
+
const state = normalizeRuntimeState(source, options?.actor || "");
|
|
2092
|
+
const baseline = RuntimeEnv.buildEnvPatch(state) || {};
|
|
2070
2093
|
if (!page || typeof page.evaluate !== "function" || typeof page.context !== "function") {
|
|
2071
2094
|
return Object.keys(baseline).length > 0 ? baseline : null;
|
|
2072
2095
|
}
|
|
@@ -2085,7 +2108,7 @@ var RuntimeEnv = {
|
|
|
2085
2108
|
cookies
|
|
2086
2109
|
},
|
|
2087
2110
|
{
|
|
2088
|
-
browserProfileCore:
|
|
2111
|
+
browserProfileCore: state.browserProfileCore
|
|
2089
2112
|
}
|
|
2090
2113
|
);
|
|
2091
2114
|
return RuntimeEnv.mergeEnvPatches(baseline, capturedPatch);
|
|
@@ -2099,11 +2122,11 @@ var RuntimeEnv = {
|
|
|
2099
2122
|
var logger3 = createInternalLogger("ApifyKit");
|
|
2100
2123
|
var resolveRuntimeContext = (input) => {
|
|
2101
2124
|
const rememberedState = RuntimeEnv.peekRememberedState();
|
|
2102
|
-
const
|
|
2103
|
-
const envPatch = RuntimeEnv.buildEnvPatch(
|
|
2125
|
+
const state = rememberedState || RuntimeEnv.parseInput(input || {});
|
|
2126
|
+
const envPatch = RuntimeEnv.buildEnvPatch(state) || null;
|
|
2104
2127
|
return {
|
|
2105
|
-
actor:
|
|
2106
|
-
runtime:
|
|
2128
|
+
actor: state.actor,
|
|
2129
|
+
runtime: state.runtime,
|
|
2107
2130
|
envPatch
|
|
2108
2131
|
};
|
|
2109
2132
|
};
|
|
@@ -2462,99 +2485,8 @@ var Utils = {
|
|
|
2462
2485
|
}
|
|
2463
2486
|
};
|
|
2464
2487
|
|
|
2465
|
-
// src/
|
|
2466
|
-
var
|
|
2467
|
-
mode: Mode.Default
|
|
2468
|
-
};
|
|
2469
|
-
var normalizeStrategies = (strategies) => strategies && typeof strategies === "object" ? strategies : {};
|
|
2470
|
-
var ToolkitContext = {
|
|
2471
|
-
get mode() {
|
|
2472
|
-
return state.mode;
|
|
2473
|
-
},
|
|
2474
|
-
setMode(mode = Mode.Default) {
|
|
2475
|
-
state.mode = normalizeMode(mode, Mode.Default);
|
|
2476
|
-
return state.mode;
|
|
2477
|
-
}
|
|
2478
|
-
};
|
|
2479
|
-
var getToolkitMode = () => state.mode;
|
|
2480
|
-
var setToolkitMode = (mode = Mode.Default) => ToolkitContext.setMode(mode);
|
|
2481
|
-
var resolveModeStrategy = (strategies = {}, mode = getToolkitMode(), fallbackMode = Mode.Default) => {
|
|
2482
|
-
const normalizedStrategies = normalizeStrategies(strategies);
|
|
2483
|
-
const normalizedMode = normalizeMode(mode, fallbackMode);
|
|
2484
|
-
const delegate = normalizedStrategies[normalizedMode] ?? normalizedStrategies[fallbackMode] ?? Object.values(normalizedStrategies).find(Boolean) ?? null;
|
|
2485
|
-
return {
|
|
2486
|
-
mode: normalizedMode,
|
|
2487
|
-
delegate
|
|
2488
|
-
};
|
|
2489
|
-
};
|
|
2490
|
-
|
|
2491
|
-
// src/internals/delegate.js
|
|
2492
|
-
var normalizeArrayMethodDefinition = (definition) => typeof definition === "string" ? { name: definition, enumerable: true } : {
|
|
2493
|
-
name: String(definition?.name || "").trim(),
|
|
2494
|
-
enumerable: definition?.enumerable !== false
|
|
2495
|
-
};
|
|
2496
|
-
var normalizeMethodDefinitions = (methods = {}) => {
|
|
2497
|
-
if (Array.isArray(methods)) {
|
|
2498
|
-
return methods.map(normalizeArrayMethodDefinition).filter((method) => method.name);
|
|
2499
|
-
}
|
|
2500
|
-
if (!methods || typeof methods !== "object") {
|
|
2501
|
-
return [];
|
|
2502
|
-
}
|
|
2503
|
-
return Object.entries(methods).map(([name, definition]) => ({
|
|
2504
|
-
name: String(name || "").trim(),
|
|
2505
|
-
enumerable: definition?.enumerable !== false
|
|
2506
|
-
})).filter((method) => method.name);
|
|
2507
|
-
};
|
|
2508
|
-
var normalizeDelegateResolution = (resolution) => {
|
|
2509
|
-
if (resolution && typeof resolution === "object" && ("delegate" in resolution || "label" in resolution)) {
|
|
2510
|
-
return {
|
|
2511
|
-
delegate: resolution.delegate ?? null,
|
|
2512
|
-
label: String(resolution.label || "current delegate").trim() || "current delegate"
|
|
2513
|
-
};
|
|
2514
|
-
}
|
|
2515
|
-
return {
|
|
2516
|
-
delegate: resolution,
|
|
2517
|
-
label: "current delegate"
|
|
2518
|
-
};
|
|
2519
|
-
};
|
|
2520
|
-
var createMethodDescriptor = (namespace, methodName, enumerable, resolveDelegate) => ({
|
|
2521
|
-
enumerable,
|
|
2522
|
-
value: (...args) => {
|
|
2523
|
-
const { delegate, label } = normalizeDelegateResolution(resolveDelegate(methodName, args));
|
|
2524
|
-
if (typeof delegate?.[methodName] !== "function") {
|
|
2525
|
-
throw new Error(`${namespace}.${methodName} is not available in ${label}`);
|
|
2526
|
-
}
|
|
2527
|
-
return delegate[methodName](...args);
|
|
2528
|
-
}
|
|
2529
|
-
});
|
|
2530
|
-
var withDelegatedProperties = (target = {}, {
|
|
2531
|
-
namespace = "Delegate",
|
|
2532
|
-
methods = {},
|
|
2533
|
-
resolveDelegate = () => null
|
|
2534
|
-
} = {}) => {
|
|
2535
|
-
const descriptors = Object.fromEntries(
|
|
2536
|
-
normalizeMethodDefinitions(methods).map((method) => [
|
|
2537
|
-
method.name,
|
|
2538
|
-
createMethodDescriptor(namespace, method.name, method.enumerable, resolveDelegate)
|
|
2539
|
-
])
|
|
2540
|
-
);
|
|
2541
|
-
return Object.defineProperties(target, descriptors);
|
|
2542
|
-
};
|
|
2543
|
-
var createDelegatedFacade = (namespace, strategies = {}, methods = {}) => {
|
|
2544
|
-
return withDelegatedProperties({}, {
|
|
2545
|
-
namespace,
|
|
2546
|
-
methods,
|
|
2547
|
-
resolveDelegate: () => {
|
|
2548
|
-
const { mode, delegate } = resolveModeStrategy(strategies);
|
|
2549
|
-
return {
|
|
2550
|
-
delegate,
|
|
2551
|
-
label: `${mode} mode`
|
|
2552
|
-
};
|
|
2553
|
-
}
|
|
2554
|
-
});
|
|
2555
|
-
};
|
|
2556
|
-
|
|
2557
|
-
// src/internals/anti-cheat/default.js
|
|
2488
|
+
// src/anti-cheat.js
|
|
2489
|
+
var logger5 = createInternalLogger("AntiCheat");
|
|
2558
2490
|
var BASE_CONFIG = Object.freeze({
|
|
2559
2491
|
locale: "zh-CN",
|
|
2560
2492
|
acceptLanguage: "zh-CN,zh;q=0.9",
|
|
@@ -2589,7 +2521,7 @@ function buildFingerprintOptions({ locale = BASE_CONFIG.locale, browserMajorVers
|
|
|
2589
2521
|
}
|
|
2590
2522
|
return options;
|
|
2591
2523
|
}
|
|
2592
|
-
var
|
|
2524
|
+
var AntiCheat = {
|
|
2593
2525
|
/**
|
|
2594
2526
|
* 获取统一的基础配置
|
|
2595
2527
|
*/
|
|
@@ -2626,54 +2558,6 @@ var DefaultAntiCheat = {
|
|
|
2626
2558
|
}
|
|
2627
2559
|
};
|
|
2628
2560
|
|
|
2629
|
-
// src/internals/anti-cheat/cloakbrowser.js
|
|
2630
|
-
var CLOAK_BROWSER_BASE_CONFIG = Object.freeze({
|
|
2631
|
-
locale: "",
|
|
2632
|
-
acceptLanguage: "",
|
|
2633
|
-
timezoneId: "",
|
|
2634
|
-
timezoneOffset: null,
|
|
2635
|
-
geolocation: null
|
|
2636
|
-
});
|
|
2637
|
-
var normalizeHeaders = (headers) => headers && typeof headers === "object" ? headers : {};
|
|
2638
|
-
var CloakBrowserAntiCheat = {
|
|
2639
|
-
/**
|
|
2640
|
-
* CloakBrowser 自身会负责浏览器指纹,toolkit 在该模式下尽量不再注入额外反检测配置。
|
|
2641
|
-
*/
|
|
2642
|
-
getBaseConfig() {
|
|
2643
|
-
return { ...CLOAK_BROWSER_BASE_CONFIG };
|
|
2644
|
-
},
|
|
2645
|
-
getFingerprintGeneratorOptions() {
|
|
2646
|
-
return {};
|
|
2647
|
-
},
|
|
2648
|
-
getLaunchArgs() {
|
|
2649
|
-
return [];
|
|
2650
|
-
},
|
|
2651
|
-
getTlsFingerprintOptions() {
|
|
2652
|
-
return {};
|
|
2653
|
-
},
|
|
2654
|
-
applyLocaleHeaders(headers, acceptLanguage = "") {
|
|
2655
|
-
const normalizedHeaders = normalizeHeaders(headers);
|
|
2656
|
-
if (acceptLanguage && !normalizedHeaders["accept-language"]) {
|
|
2657
|
-
normalizedHeaders["accept-language"] = acceptLanguage;
|
|
2658
|
-
}
|
|
2659
|
-
return normalizedHeaders;
|
|
2660
|
-
}
|
|
2661
|
-
};
|
|
2662
|
-
|
|
2663
|
-
// src/anti-cheat.js
|
|
2664
|
-
var antiCheatStrategies = {
|
|
2665
|
-
[Mode.Default]: DefaultAntiCheat,
|
|
2666
|
-
[Mode.CloakBrowser]: CloakBrowserAntiCheat
|
|
2667
|
-
};
|
|
2668
|
-
var antiCheatFacadeMethods = Object.freeze({
|
|
2669
|
-
getBaseConfig: { enumerable: true },
|
|
2670
|
-
getFingerprintGeneratorOptions: { enumerable: true },
|
|
2671
|
-
getLaunchArgs: { enumerable: true },
|
|
2672
|
-
getTlsFingerprintOptions: { enumerable: true },
|
|
2673
|
-
applyLocaleHeaders: { enumerable: true }
|
|
2674
|
-
});
|
|
2675
|
-
var AntiCheat = createDelegatedFacade("AntiCheat", antiCheatStrategies, antiCheatFacadeMethods);
|
|
2676
|
-
|
|
2677
2561
|
// src/device-input.js
|
|
2678
2562
|
var resolveDeviceFromPage = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
|
|
2679
2563
|
var assertPage = (page, method) => {
|
|
@@ -3073,12 +2957,12 @@ var resolveDescriptor = (descriptor, device) => {
|
|
|
3073
2957
|
}
|
|
3074
2958
|
return resolved;
|
|
3075
2959
|
};
|
|
3076
|
-
var attachRuntimeState = (page,
|
|
2960
|
+
var attachRuntimeState = (page, state) => {
|
|
3077
2961
|
Object.defineProperty(page, PageRuntimeStateKey, {
|
|
3078
2962
|
configurable: true,
|
|
3079
2963
|
enumerable: false,
|
|
3080
2964
|
writable: true,
|
|
3081
|
-
value:
|
|
2965
|
+
value: state
|
|
3082
2966
|
});
|
|
3083
2967
|
};
|
|
3084
2968
|
var restoreRuntimeState = (page, snapshot) => {
|
|
@@ -3224,1240 +3108,1136 @@ var DeviceView = {
|
|
|
3224
3108
|
}
|
|
3225
3109
|
};
|
|
3226
3110
|
|
|
3227
|
-
// src/internals/humanize/
|
|
3228
|
-
import delay3 from "delay";
|
|
3229
|
-
|
|
3230
|
-
// src/internals/humanize/shared.js
|
|
3111
|
+
// src/internals/humanize/desktop.js
|
|
3231
3112
|
import delay2 from "delay";
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
if (
|
|
3238
|
-
|
|
3239
|
-
if (typeof target === "string") {
|
|
3240
|
-
element = await page.$(target);
|
|
3241
|
-
}
|
|
3242
|
-
if (!element) {
|
|
3243
|
-
if (throwOnMissing) {
|
|
3244
|
-
throw new Error(`\u627E\u4E0D\u5230\u5143\u7D20 ${String(target)}`);
|
|
3245
|
-
}
|
|
3246
|
-
return null;
|
|
3247
|
-
}
|
|
3248
|
-
return element;
|
|
3249
|
-
};
|
|
3250
|
-
var waitJitter = (base, jitterPercent = 0.3) => delay2(jitterMs(base, jitterPercent));
|
|
3251
|
-
|
|
3252
|
-
// src/internals/humanize/mobile.js
|
|
3253
|
-
var logger5 = createInternalLogger("Humanize.Mobile");
|
|
3254
|
-
var initializedPages = /* @__PURE__ */ new WeakSet();
|
|
3255
|
-
var DEFAULT_TAP_TIMEOUT_MS = 2500;
|
|
3256
|
-
var DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS = 1200;
|
|
3257
|
-
var DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS = 900;
|
|
3258
|
-
var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
3259
|
-
var resolveViewport = (page) => page?.viewportSize?.() || { width: 390, height: 844 };
|
|
3260
|
-
var describeTarget = (target) => {
|
|
3261
|
-
if (target == null) return "Current Position";
|
|
3262
|
-
return typeof target === "string" ? target : "ElementHandle";
|
|
3263
|
-
};
|
|
3264
|
-
var clipBoxToViewport = (box, viewport) => {
|
|
3265
|
-
if (!box || !viewport) return box;
|
|
3266
|
-
const left = clamp(box.x, 0, viewport.width);
|
|
3267
|
-
const top = clamp(box.y, 0, viewport.height);
|
|
3268
|
-
const right = clamp(box.x + box.width, 0, viewport.width);
|
|
3269
|
-
const bottom = clamp(box.y + box.height, 0, viewport.height);
|
|
3270
|
-
if (right - left > 1 && bottom - top > 1) {
|
|
3271
|
-
return {
|
|
3272
|
-
x: left,
|
|
3273
|
-
y: top,
|
|
3274
|
-
width: right - left,
|
|
3275
|
-
height: bottom - top
|
|
3276
|
-
};
|
|
3277
|
-
}
|
|
3278
|
-
return box;
|
|
3279
|
-
};
|
|
3280
|
-
var centerPointInBox = (box) => ({
|
|
3281
|
-
x: box.x + box.width / 2,
|
|
3282
|
-
y: box.y + box.height / 2
|
|
3283
|
-
});
|
|
3284
|
-
var withTimeout = async (operation, timeoutMs, label) => {
|
|
3285
|
-
const safeTimeoutMs = Math.max(50, Number(timeoutMs || 0));
|
|
3286
|
-
let timeoutId = null;
|
|
3287
|
-
try {
|
|
3288
|
-
return await Promise.race([
|
|
3289
|
-
Promise.resolve().then(operation),
|
|
3290
|
-
new Promise((_, reject) => {
|
|
3291
|
-
timeoutId = setTimeout(() => {
|
|
3292
|
-
reject(new Error(`${label} timeout after ${safeTimeoutMs}ms`));
|
|
3293
|
-
}, safeTimeoutMs);
|
|
3294
|
-
})
|
|
3295
|
-
]);
|
|
3296
|
-
} finally {
|
|
3297
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
3113
|
+
import { createCursor } from "ghost-cursor-playwright";
|
|
3114
|
+
var logger6 = createInternalLogger("Humanize");
|
|
3115
|
+
var $CursorWeakMap = /* @__PURE__ */ new WeakMap();
|
|
3116
|
+
function $GetCursor(page) {
|
|
3117
|
+
const cursor = $CursorWeakMap.get(page);
|
|
3118
|
+
if (!cursor) {
|
|
3119
|
+
throw new Error("Cursor \u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 Humanize.initializeCursor(page)");
|
|
3298
3120
|
}
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
isFixed = true;
|
|
3323
|
-
positioning = style.position;
|
|
3324
|
-
break;
|
|
3325
|
-
}
|
|
3121
|
+
return cursor;
|
|
3122
|
+
}
|
|
3123
|
+
var Humanize = {
|
|
3124
|
+
/**
|
|
3125
|
+
* 生成带抖动的毫秒数 - 基于基础值添加随机浮动 (±30% 默认)
|
|
3126
|
+
* @param {number} base - 基础延迟 (ms)
|
|
3127
|
+
* @param {number} [jitterPercent=0.3] - 抖动百分比 (0.3 = ±30%)
|
|
3128
|
+
* @returns {number} 抖动后的毫秒数
|
|
3129
|
+
*/
|
|
3130
|
+
jitterMs(base, jitterPercent = 0.3) {
|
|
3131
|
+
const jitter = base * jitterPercent * (Math.random() * 2 - 1);
|
|
3132
|
+
return Math.max(10, Math.round(base + jitter));
|
|
3133
|
+
},
|
|
3134
|
+
/**
|
|
3135
|
+
* 初始化页面的 Ghost Cursor(必须在使用其他 cursor 相关方法前调用)
|
|
3136
|
+
*
|
|
3137
|
+
* @param {import('playwright').Page} page
|
|
3138
|
+
* @returns {Promise<void>}
|
|
3139
|
+
*/
|
|
3140
|
+
async initializeCursor(page) {
|
|
3141
|
+
if ($CursorWeakMap.has(page)) {
|
|
3142
|
+
logger6.debug("initializeCursor: cursor already exists, skipping");
|
|
3143
|
+
return;
|
|
3326
3144
|
}
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3145
|
+
logger6.start("initializeCursor", "creating cursor");
|
|
3146
|
+
const cursor = await createCursor(page);
|
|
3147
|
+
$CursorWeakMap.set(page, cursor);
|
|
3148
|
+
logger6.success("initializeCursor", "cursor initialized");
|
|
3149
|
+
},
|
|
3150
|
+
/**
|
|
3151
|
+
* 人类化鼠标移动 - 使用 ghost-cursor 移动到指定位置或元素
|
|
3152
|
+
*
|
|
3153
|
+
* @param {import('playwright').Page} page
|
|
3154
|
+
* @param {string|{x: number, y: number}|import('playwright').ElementHandle} target - CSS选择器、坐标对象或元素句柄
|
|
3155
|
+
*/
|
|
3156
|
+
async humanMove(page, target) {
|
|
3157
|
+
const cursor = $GetCursor(page);
|
|
3158
|
+
logger6.start("humanMove", `target=${typeof target === "string" ? target : "element/coords"}`);
|
|
3159
|
+
try {
|
|
3160
|
+
if (typeof target === "string") {
|
|
3161
|
+
const element = await page.$(target);
|
|
3162
|
+
if (!element) {
|
|
3163
|
+
logger6.warn(`humanMove: \u5143\u7D20\u4E0D\u5B58\u5728 ${target}`);
|
|
3164
|
+
return false;
|
|
3165
|
+
}
|
|
3166
|
+
const box = await element.boundingBox();
|
|
3167
|
+
if (!box) {
|
|
3168
|
+
logger6.warn(`humanMove: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E ${target}`);
|
|
3169
|
+
return false;
|
|
3170
|
+
}
|
|
3171
|
+
const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
|
|
3172
|
+
const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
|
|
3173
|
+
await cursor.actions.move({ x, y });
|
|
3174
|
+
} else if (target && typeof target.x === "number" && typeof target.y === "number") {
|
|
3175
|
+
await cursor.actions.move(target);
|
|
3176
|
+
} else if (target && typeof target.boundingBox === "function") {
|
|
3177
|
+
const box = await target.boundingBox();
|
|
3178
|
+
if (box) {
|
|
3179
|
+
const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
|
|
3180
|
+
const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
|
|
3181
|
+
await cursor.actions.move({ x, y });
|
|
3182
|
+
}
|
|
3342
3183
|
}
|
|
3184
|
+
logger6.success("humanMove");
|
|
3185
|
+
return true;
|
|
3186
|
+
} catch (error) {
|
|
3187
|
+
logger6.fail("humanMove", error);
|
|
3188
|
+
throw error;
|
|
3343
3189
|
}
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
const
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
if (pointElement === el || el.contains(pointElement) || pointElement.contains(el)) {
|
|
3373
|
-
return true;
|
|
3374
|
-
}
|
|
3375
|
-
const common = commonAncestor(el, pointElement);
|
|
3376
|
-
if (!common) return false;
|
|
3377
|
-
const commonRect = common.getBoundingClientRect?.();
|
|
3378
|
-
if (!commonRect || commonRect.width <= 0 || commonRect.height <= 0) return false;
|
|
3379
|
-
const commonArea = commonRect.width * commonRect.height;
|
|
3380
|
-
const targetArea = Math.max(1, rect.width * rect.height);
|
|
3381
|
-
const maxSharedRegionArea = Math.max(targetArea * 12, 4096);
|
|
3382
|
-
if (commonArea > maxSharedRegionArea) return false;
|
|
3383
|
-
if (commonRect.width > Math.max(rect.width * 8, 120) || commonRect.height > Math.max(rect.height * 8, 120)) {
|
|
3384
|
-
return false;
|
|
3385
|
-
}
|
|
3386
|
-
return common.contains(el) && common.contains(pointElement);
|
|
3387
|
-
};
|
|
3388
|
-
const describeElement = (node) => {
|
|
3389
|
-
if (!node) return null;
|
|
3390
|
-
const className = typeof node.className === "string" ? node.className : node.className && typeof node.className.baseVal === "string" ? node.className.baseVal : "";
|
|
3391
|
-
const rect2 = node.getBoundingClientRect?.();
|
|
3392
|
-
const style = window.getComputedStyle(node);
|
|
3393
|
-
return {
|
|
3394
|
-
tag: node.tagName,
|
|
3395
|
-
id: node.id || "",
|
|
3396
|
-
className,
|
|
3397
|
-
isFixed: Boolean(style && (style.position === "fixed" || style.position === "sticky")),
|
|
3398
|
-
positioning: style?.position || "",
|
|
3399
|
-
top: rect2 ? rect2.top : null,
|
|
3400
|
-
bottom: rect2 ? rect2.bottom : null,
|
|
3401
|
-
left: rect2 ? rect2.left : null,
|
|
3402
|
-
right: rect2 ? rect2.right : null
|
|
3403
|
-
};
|
|
3404
|
-
};
|
|
3405
|
-
const samplePoints = [
|
|
3406
|
-
{ x: cx, y: cy },
|
|
3407
|
-
{ x: visibleLeft + Math.min(8, Math.max(1, visibleWidth * 0.25)), y: cy },
|
|
3408
|
-
{ x: visibleRight - Math.min(8, Math.max(1, visibleWidth * 0.25)), y: cy },
|
|
3409
|
-
{ x: cx, y: visibleTop + Math.min(8, Math.max(1, visibleHeight * 0.25)) },
|
|
3410
|
-
{ x: cx, y: visibleBottom - Math.min(8, Math.max(1, visibleHeight * 0.25)) }
|
|
3411
|
-
];
|
|
3412
|
-
let obstruction = null;
|
|
3413
|
-
for (const point of samplePoints) {
|
|
3414
|
-
const pointElement = document.elementFromPoint(point.x, point.y);
|
|
3415
|
-
if (sameTapTarget(pointElement)) {
|
|
3416
|
-
return { code: "VISIBLE", isFixed, positioning };
|
|
3417
|
-
}
|
|
3418
|
-
obstruction = obstruction || describeElement(pointElement);
|
|
3419
|
-
}
|
|
3420
|
-
if (obstruction) {
|
|
3421
|
-
return {
|
|
3422
|
-
code: "OBSTRUCTED",
|
|
3423
|
-
reason: "\u88AB\u906E\u6321",
|
|
3424
|
-
direction: cy > viewH / 2 ? "down" : "up",
|
|
3425
|
-
obstruction,
|
|
3426
|
-
cy,
|
|
3427
|
-
viewH,
|
|
3428
|
-
isFixed,
|
|
3429
|
-
positioning
|
|
3430
|
-
};
|
|
3431
|
-
}
|
|
3432
|
-
return { code: "VISIBLE", isFixed, positioning };
|
|
3433
|
-
});
|
|
3434
|
-
};
|
|
3435
|
-
var activateElementFallback = async (element, point = null, options = {}) => {
|
|
3436
|
-
return element.evaluate((el, { innerOptions }) => {
|
|
3437
|
-
const isEditable = (node) => {
|
|
3438
|
-
if (!node || node.nodeType !== Node.ELEMENT_NODE) return false;
|
|
3439
|
-
if (node.isContentEditable) return true;
|
|
3440
|
-
if (node instanceof HTMLTextAreaElement) return !node.disabled && !node.readOnly;
|
|
3441
|
-
if (node instanceof HTMLInputElement) {
|
|
3442
|
-
return !node.disabled && !node.readOnly && typeof node.select === "function";
|
|
3443
|
-
}
|
|
3444
|
-
return false;
|
|
3445
|
-
};
|
|
3446
|
-
const findEditable = (node) => {
|
|
3447
|
-
for (let current = node; current && current !== document.body; current = current.parentElement) {
|
|
3448
|
-
if (isEditable(current)) return current;
|
|
3449
|
-
}
|
|
3450
|
-
return null;
|
|
3451
|
-
};
|
|
3452
|
-
const editable = findEditable(el);
|
|
3453
|
-
if (editable && typeof editable.focus === "function") {
|
|
3454
|
-
editable.focus({ preventScroll: true });
|
|
3455
|
-
if (innerOptions.editableOnly) {
|
|
3456
|
-
return { activated: true, method: "focus", tag: editable.tagName || "" };
|
|
3190
|
+
},
|
|
3191
|
+
/**
|
|
3192
|
+
* 渐进式滚动到元素可见(仅处理 Y 轴滚动)
|
|
3193
|
+
* 返回 restore 方法,用于将滚动容器恢复到原位置
|
|
3194
|
+
*
|
|
3195
|
+
* @param {import('playwright').Page} page
|
|
3196
|
+
* @param {string|import('playwright').ElementHandle} target - CSS 选择器或元素句柄
|
|
3197
|
+
* @param {Object} [options]
|
|
3198
|
+
* @param {number} [options.maxSteps=20] - 最大滚动步数
|
|
3199
|
+
* @param {number} [options.minStep=260] - 单次滚动最小步长
|
|
3200
|
+
* @param {number} [options.maxStep=800] - 单次滚动最大步长
|
|
3201
|
+
* @param {number} [options.maxDurationMs] - 最长耗时上限 (默认随 maxSteps 估算)
|
|
3202
|
+
*/
|
|
3203
|
+
async humanScroll(page, target, options = {}) {
|
|
3204
|
+
const {
|
|
3205
|
+
maxSteps = 20,
|
|
3206
|
+
minStep = 260,
|
|
3207
|
+
maxStep = 800,
|
|
3208
|
+
maxDurationMs = maxSteps * 220 + 800
|
|
3209
|
+
} = options;
|
|
3210
|
+
const targetDesc = typeof target === "string" ? target : "ElementHandle";
|
|
3211
|
+
logger6.start("humanScroll", `target=${targetDesc}`);
|
|
3212
|
+
let element;
|
|
3213
|
+
if (typeof target === "string") {
|
|
3214
|
+
element = await page.$(target);
|
|
3215
|
+
if (!element) {
|
|
3216
|
+
logger6.warn(`humanScroll | \u5143\u7D20\u672A\u627E\u5230: ${target}`);
|
|
3217
|
+
return { element: null, didScroll: false };
|
|
3457
3218
|
}
|
|
3219
|
+
} else {
|
|
3220
|
+
element = target;
|
|
3458
3221
|
}
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
};
|
|
3486
|
-
var getScrollableRect = async (element) => {
|
|
3487
|
-
return element.evaluate((el) => {
|
|
3488
|
-
const isScrollable = (node) => {
|
|
3489
|
-
const style = window.getComputedStyle(node);
|
|
3490
|
-
if (!style) return false;
|
|
3491
|
-
const overflowY = style.overflowY;
|
|
3492
|
-
if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
|
|
3493
|
-
return node.scrollHeight > node.clientHeight + 1;
|
|
3494
|
-
};
|
|
3495
|
-
let current = el;
|
|
3496
|
-
while (current && current !== document.body) {
|
|
3497
|
-
if (isScrollable(current)) {
|
|
3498
|
-
const rect = current.getBoundingClientRect();
|
|
3499
|
-
if (rect && rect.width > 0 && rect.height > 0) {
|
|
3222
|
+
const cursor = $GetCursor(page);
|
|
3223
|
+
let didScroll = false;
|
|
3224
|
+
const checkVisibility = async () => {
|
|
3225
|
+
return await element.evaluate((el) => {
|
|
3226
|
+
const rect = el.getBoundingClientRect();
|
|
3227
|
+
if (!rect || rect.width === 0 || rect.height === 0) {
|
|
3228
|
+
return { code: "ZERO_DIMENSIONS", reason: "\u5C3A\u5BF8\u4E3A\u96F6" };
|
|
3229
|
+
}
|
|
3230
|
+
const cx = rect.left + rect.width / 2;
|
|
3231
|
+
const cy = rect.top + rect.height / 2;
|
|
3232
|
+
const viewH = window.innerHeight;
|
|
3233
|
+
const viewW = window.innerWidth;
|
|
3234
|
+
let isFixed = false;
|
|
3235
|
+
for (let node = el; node && node !== document.body; node = node.parentElement) {
|
|
3236
|
+
const style = window.getComputedStyle(node);
|
|
3237
|
+
if (style && style.position === "fixed") {
|
|
3238
|
+
isFixed = true;
|
|
3239
|
+
break;
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
3242
|
+
if (cy < 0 || cy > viewH || cx < 0 || cx > viewW) {
|
|
3243
|
+
const direction = cy < 0 ? "up" : cy > viewH ? "down" : "unknown";
|
|
3244
|
+
return { code: "OUT_OF_VIEWPORT", reason: "\u4E0D\u5728\u89C6\u53E3\u5185", direction, cy, viewH, isFixed };
|
|
3245
|
+
}
|
|
3246
|
+
const pointElement = document.elementFromPoint(cx, cy);
|
|
3247
|
+
if (pointElement && !el.contains(pointElement) && !pointElement.contains(el)) {
|
|
3500
3248
|
return {
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3249
|
+
code: "OBSTRUCTED",
|
|
3250
|
+
reason: "\u88AB\u906E\u6321",
|
|
3251
|
+
obstruction: {
|
|
3252
|
+
tag: pointElement.tagName,
|
|
3253
|
+
id: pointElement.id,
|
|
3254
|
+
className: pointElement.className
|
|
3255
|
+
},
|
|
3256
|
+
cy,
|
|
3257
|
+
// Return Center Y for smart direction calculation
|
|
3258
|
+
viewH,
|
|
3259
|
+
isFixed
|
|
3505
3260
|
};
|
|
3506
3261
|
}
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
}
|
|
3510
|
-
return null;
|
|
3511
|
-
});
|
|
3512
|
-
};
|
|
3513
|
-
var scrollScrollableAncestor = async (element, deltaY) => {
|
|
3514
|
-
return element.evaluate((el, amount) => {
|
|
3515
|
-
const isScrollable = (node) => {
|
|
3516
|
-
const style = window.getComputedStyle(node);
|
|
3517
|
-
if (!style) return false;
|
|
3518
|
-
const overflowY = style.overflowY;
|
|
3519
|
-
if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
|
|
3520
|
-
return node.scrollHeight > node.clientHeight + 1;
|
|
3262
|
+
return { code: "VISIBLE", isFixed };
|
|
3263
|
+
});
|
|
3521
3264
|
};
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
scrollTop: current.scrollTop
|
|
3265
|
+
const getScrollableRect2 = async () => {
|
|
3266
|
+
return await element.evaluate((el) => {
|
|
3267
|
+
const isScrollable = (node) => {
|
|
3268
|
+
const style = window.getComputedStyle(node);
|
|
3269
|
+
if (!style) return false;
|
|
3270
|
+
const overflowY = style.overflowY;
|
|
3271
|
+
if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
|
|
3272
|
+
return node.scrollHeight > node.clientHeight + 1;
|
|
3531
3273
|
};
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
};
|
|
3544
|
-
var scrollAwayFromObstruction = async (element, status) => {
|
|
3545
|
-
return element.evaluate((el, innerStatus) => {
|
|
3546
|
-
const isScrollable = (node) => {
|
|
3547
|
-
const style = window.getComputedStyle(node);
|
|
3548
|
-
if (!style) return false;
|
|
3549
|
-
const overflowY = style.overflowY;
|
|
3550
|
-
if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
|
|
3551
|
-
return node.scrollHeight > node.clientHeight + 1;
|
|
3552
|
-
};
|
|
3553
|
-
let scroller = el;
|
|
3554
|
-
while (scroller && scroller !== document.body) {
|
|
3555
|
-
if (isScrollable(scroller)) break;
|
|
3556
|
-
scroller = scroller.parentElement;
|
|
3557
|
-
}
|
|
3558
|
-
if (!scroller || scroller === document.body) {
|
|
3559
|
-
return { moved: false, scrollTop: 0, deltaY: 0 };
|
|
3560
|
-
}
|
|
3561
|
-
const rect = el.getBoundingClientRect();
|
|
3562
|
-
const scrollerRect = scroller.getBoundingClientRect();
|
|
3563
|
-
const obstruction = innerStatus?.obstruction || {};
|
|
3564
|
-
const obstructionTop = Number(obstruction.top);
|
|
3565
|
-
const obstructionBottom = Number(obstruction.bottom);
|
|
3566
|
-
const obstructionMiddle = Number.isFinite(obstructionTop) && Number.isFinite(obstructionBottom) ? (obstructionTop + obstructionBottom) / 2 : scrollerRect.top + scrollerRect.height / 2;
|
|
3567
|
-
const scrollerMiddle = scrollerRect.top + scrollerRect.height / 2;
|
|
3568
|
-
const padding = 18;
|
|
3569
|
-
let deltaY = 0;
|
|
3570
|
-
if (Number.isFinite(obstructionTop) && rect.bottom > obstructionTop && obstructionTop >= scrollerRect.top && obstructionTop <= scrollerRect.bottom && obstructionMiddle >= scrollerMiddle) {
|
|
3571
|
-
deltaY = rect.bottom - obstructionTop + padding;
|
|
3572
|
-
} else if (Number.isFinite(obstructionBottom) && rect.top < obstructionBottom && obstructionBottom >= scrollerRect.top && obstructionBottom <= scrollerRect.bottom && obstructionMiddle < scrollerMiddle) {
|
|
3573
|
-
deltaY = rect.top - obstructionBottom - padding;
|
|
3574
|
-
} else {
|
|
3575
|
-
const fallbackDistance = Math.max(48, Math.min(180, scrollerRect.height * 0.45));
|
|
3576
|
-
deltaY = obstructionMiddle >= scrollerMiddle ? fallbackDistance : -fallbackDistance;
|
|
3577
|
-
}
|
|
3578
|
-
const beforeTop = scroller.scrollTop;
|
|
3579
|
-
scroller.scrollTop = beforeTop + deltaY;
|
|
3580
|
-
return {
|
|
3581
|
-
moved: Math.abs(scroller.scrollTop - beforeTop) > 2,
|
|
3582
|
-
scrollTop: scroller.scrollTop,
|
|
3583
|
-
deltaY
|
|
3584
|
-
};
|
|
3585
|
-
}, status);
|
|
3586
|
-
};
|
|
3587
|
-
var getElementViewportSnapshot = async (element) => {
|
|
3588
|
-
return element.evaluate((el) => {
|
|
3589
|
-
const rect = el.getBoundingClientRect();
|
|
3590
|
-
return {
|
|
3591
|
-
top: rect.top,
|
|
3592
|
-
bottom: rect.bottom,
|
|
3593
|
-
left: rect.left,
|
|
3594
|
-
right: rect.right,
|
|
3595
|
-
width: rect.width,
|
|
3596
|
-
height: rect.height,
|
|
3597
|
-
scrollX: window.scrollX,
|
|
3598
|
-
scrollY: window.scrollY
|
|
3274
|
+
let current = el;
|
|
3275
|
+
while (current && current !== document.body) {
|
|
3276
|
+
if (isScrollable(current)) {
|
|
3277
|
+
const rect = current.getBoundingClientRect();
|
|
3278
|
+
if (rect && rect.width > 0 && rect.height > 0) {
|
|
3279
|
+
return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
current = current.parentElement;
|
|
3283
|
+
}
|
|
3284
|
+
return null;
|
|
3285
|
+
});
|
|
3599
3286
|
};
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
}
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
try {
|
|
3665
|
-
if (page.context && typeof page.context().newCDPSession === "function") {
|
|
3666
|
-
client = await page.context().newCDPSession(page);
|
|
3287
|
+
const startTime = Date.now();
|
|
3288
|
+
try {
|
|
3289
|
+
for (let i = 0; i < maxSteps; i++) {
|
|
3290
|
+
if (Date.now() - startTime > maxDurationMs) {
|
|
3291
|
+
logger6.warn(`humanScroll | \u8D85\u65F6\u4FDD\u62A4\u89E6\u53D1 (${maxDurationMs}ms)`);
|
|
3292
|
+
return { element, didScroll };
|
|
3293
|
+
}
|
|
3294
|
+
const status = await checkVisibility();
|
|
3295
|
+
if (status.code === "VISIBLE") {
|
|
3296
|
+
if (status.isFixed) {
|
|
3297
|
+
logger6.info("humanScroll | fixed \u5BB9\u5668\u5185\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
|
|
3298
|
+
} else {
|
|
3299
|
+
logger6.debug("humanScroll | \u5143\u7D20\u53EF\u89C1\u4E14\u65E0\u906E\u6321");
|
|
3300
|
+
}
|
|
3301
|
+
logger6.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
|
|
3302
|
+
return { element, didScroll };
|
|
3303
|
+
}
|
|
3304
|
+
logger6.debug(`humanScroll | \u6B65\u9AA4 ${i + 1}/${maxSteps}: ${status.reason} ${status.direction ? `(${status.direction})` : ""}`);
|
|
3305
|
+
if (status.code === "OBSTRUCTED" && status.obstruction) {
|
|
3306
|
+
logger6.debug(`humanScroll | \u88AB\u4EE5\u4E0B\u5143\u7D20\u906E\u6321 <${status.obstruction.tag} id="${status.obstruction.id}">`);
|
|
3307
|
+
}
|
|
3308
|
+
const scrollRect = await getScrollableRect2();
|
|
3309
|
+
if (!scrollRect && status.isFixed) {
|
|
3310
|
+
logger6.warn("humanScroll | fixed \u5BB9\u5668\u5185\u4E14\u65E0\u53EF\u6EDA\u52A8\u7956\u5148\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
|
|
3311
|
+
return { element, didScroll };
|
|
3312
|
+
}
|
|
3313
|
+
const stepMin = scrollRect ? Math.min(minStep, Math.max(60, scrollRect.height * 0.4)) : minStep;
|
|
3314
|
+
const stepMax = scrollRect ? Math.min(maxStep, Math.max(stepMin + 40, scrollRect.height * 0.8)) : maxStep;
|
|
3315
|
+
let deltaY = 0;
|
|
3316
|
+
if (status.code === "OUT_OF_VIEWPORT") {
|
|
3317
|
+
if (status.direction === "down") {
|
|
3318
|
+
deltaY = stepMin + Math.random() * (stepMax - stepMin);
|
|
3319
|
+
} else if (status.direction === "up") {
|
|
3320
|
+
deltaY = -(stepMin + Math.random() * (stepMax - stepMin));
|
|
3321
|
+
} else {
|
|
3322
|
+
deltaY = 100;
|
|
3323
|
+
}
|
|
3324
|
+
} else if (status.code === "OBSTRUCTED") {
|
|
3325
|
+
const halfY = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
|
|
3326
|
+
const isBottomHalf = status.cy > halfY;
|
|
3327
|
+
const direction = isBottomHalf ? 1 : -1;
|
|
3328
|
+
deltaY = direction * (stepMin + Math.random() * 50);
|
|
3329
|
+
}
|
|
3330
|
+
if (i === 0) {
|
|
3331
|
+
const viewSize = page.viewportSize();
|
|
3332
|
+
if (scrollRect) {
|
|
3333
|
+
const safeX = scrollRect.x + scrollRect.width * 0.5 + (Math.random() - 0.5) * Math.min(80, scrollRect.width * 0.4);
|
|
3334
|
+
const safeY = scrollRect.y + scrollRect.height * 0.5 + (Math.random() - 0.5) * Math.min(80, scrollRect.height * 0.4);
|
|
3335
|
+
await cursor.actions.move({ x: safeX, y: safeY });
|
|
3336
|
+
} else if (viewSize) {
|
|
3337
|
+
const safeX = viewSize.width * 0.5 + (Math.random() - 0.5) * 80;
|
|
3338
|
+
const safeY = viewSize.height * 0.5 + (Math.random() - 0.5) * 80;
|
|
3339
|
+
await cursor.actions.move({ x: safeX, y: safeY });
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
await page.mouse.wheel(0, deltaY);
|
|
3343
|
+
didScroll = true;
|
|
3344
|
+
await delay2(this.jitterMs(20 + Math.random() * 40, 0.2));
|
|
3345
|
+
}
|
|
3346
|
+
logger6.warn(`humanScroll | \u5728 ${maxSteps} \u6B65\u540E\u65E0\u6CD5\u786E\u4FDD\u53EF\u89C1\u6027`);
|
|
3347
|
+
return { element, didScroll };
|
|
3348
|
+
} catch (error) {
|
|
3349
|
+
logger6.fail("humanScroll", error);
|
|
3350
|
+
throw error;
|
|
3667
3351
|
}
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3352
|
+
},
|
|
3353
|
+
/**
|
|
3354
|
+
* 人类化点击 - 使用 ghost-cursor 模拟人类鼠标移动轨迹并点击
|
|
3355
|
+
*
|
|
3356
|
+
* @param {import('playwright').Page} page
|
|
3357
|
+
* @param {string|import('playwright').ElementHandle} [target] - CSS 选择器或元素句柄。如果为空,则点击当前鼠标位置
|
|
3358
|
+
* @param {Object} [options]
|
|
3359
|
+
* @param {number} [options.reactionDelay=250] - 反应延迟基础值 (ms),实际 ±30% 抖动
|
|
3360
|
+
* @param {boolean} [options.throwOnMissing=true] - 元素不存在时是否抛出错误
|
|
3361
|
+
* @param {boolean} [options.scrollIfNeeded=true] - 元素不在视口时是否自动滚动
|
|
3362
|
+
*/
|
|
3363
|
+
async humanClick(page, target, options = {}) {
|
|
3364
|
+
const cursor = $GetCursor(page);
|
|
3365
|
+
const { reactionDelay = 250, throwOnMissing = true, scrollIfNeeded = true, restore = false } = options;
|
|
3366
|
+
const targetDesc = target == null ? "Current Position" : typeof target === "string" ? target : "ElementHandle";
|
|
3367
|
+
logger6.start("humanClick", `target=${targetDesc}`);
|
|
3368
|
+
const restoreOnce = async () => {
|
|
3369
|
+
if (restoreOnce.restored) return;
|
|
3370
|
+
restoreOnce.restored = true;
|
|
3371
|
+
if (typeof restoreOnce.do !== "function") return;
|
|
3372
|
+
try {
|
|
3373
|
+
await delay2(this.jitterMs(1e3));
|
|
3374
|
+
await restoreOnce.do();
|
|
3375
|
+
} catch (restoreError) {
|
|
3376
|
+
logger6.warn(`humanClick: \u6062\u590D\u6EDA\u52A8\u4F4D\u7F6E\u5931\u8D25: ${restoreError.message}`);
|
|
3377
|
+
}
|
|
3378
|
+
};
|
|
3379
|
+
try {
|
|
3380
|
+
if (target == null) {
|
|
3381
|
+
await delay2(this.jitterMs(reactionDelay, 0.4));
|
|
3382
|
+
await cursor.actions.click();
|
|
3383
|
+
logger6.success("humanClick", "Clicked current position");
|
|
3384
|
+
return true;
|
|
3385
|
+
}
|
|
3386
|
+
let element;
|
|
3387
|
+
if (typeof target === "string") {
|
|
3388
|
+
element = await page.$(target);
|
|
3389
|
+
if (!element) {
|
|
3390
|
+
if (throwOnMissing) {
|
|
3391
|
+
throw new Error(`\u627E\u4E0D\u5230\u5143\u7D20 ${target}`);
|
|
3392
|
+
}
|
|
3393
|
+
logger6.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${target}`);
|
|
3394
|
+
return false;
|
|
3395
|
+
}
|
|
3396
|
+
} else {
|
|
3397
|
+
element = target;
|
|
3398
|
+
}
|
|
3399
|
+
if (scrollIfNeeded) {
|
|
3400
|
+
const { restore: restoreFn, didScroll } = await this.humanScroll(page, element);
|
|
3401
|
+
restoreOnce.do = didScroll && restore ? restoreFn : null;
|
|
3402
|
+
}
|
|
3403
|
+
const box = await element.boundingBox();
|
|
3404
|
+
if (!box) {
|
|
3405
|
+
await restoreOnce();
|
|
3406
|
+
if (throwOnMissing) {
|
|
3407
|
+
throw new Error("\u65E0\u6CD5\u83B7\u53D6\u5143\u7D20\u4F4D\u7F6E");
|
|
3408
|
+
}
|
|
3409
|
+
logger6.warn("humanClick: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E\uFF0C\u8DF3\u8FC7\u70B9\u51FB");
|
|
3410
|
+
return false;
|
|
3411
|
+
}
|
|
3412
|
+
const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.3;
|
|
3413
|
+
const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.3;
|
|
3414
|
+
await cursor.actions.move({ x, y });
|
|
3415
|
+
await delay2(this.jitterMs(reactionDelay, 0.4));
|
|
3416
|
+
await cursor.actions.click();
|
|
3417
|
+
await restoreOnce();
|
|
3418
|
+
logger6.success("humanClick");
|
|
3681
3419
|
return true;
|
|
3420
|
+
} catch (error) {
|
|
3421
|
+
await restoreOnce();
|
|
3422
|
+
logger6.fail("humanClick", error);
|
|
3423
|
+
throw error;
|
|
3682
3424
|
}
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3425
|
+
},
|
|
3426
|
+
/**
|
|
3427
|
+
* 随机延迟一段毫秒数(带 ±30% 抖动)
|
|
3428
|
+
* @param {number} baseMs - 基础延迟毫秒数
|
|
3429
|
+
* @param {number} [jitterPercent=0.3] - 抖动百分比
|
|
3430
|
+
*/
|
|
3431
|
+
async randomSleep(baseMs, jitterPercent = 0.3) {
|
|
3432
|
+
const ms = this.jitterMs(baseMs, jitterPercent);
|
|
3433
|
+
logger6.start("randomSleep", `base=${baseMs}, actual=${ms}ms`);
|
|
3434
|
+
await delay2(ms);
|
|
3435
|
+
logger6.success("randomSleep");
|
|
3436
|
+
},
|
|
3437
|
+
/**
|
|
3438
|
+
* 模拟人类"注视"或"阅读"行为:鼠标在页面上随机微动
|
|
3439
|
+
* @param {import('playwright').Page} page
|
|
3440
|
+
* @param {number} [baseDurationMs=2500] - 基础持续时间 (±40% 抖动)
|
|
3441
|
+
*/
|
|
3442
|
+
async simulateGaze(page, baseDurationMs = 2500) {
|
|
3443
|
+
const cursor = $GetCursor(page);
|
|
3444
|
+
const durationMs = this.jitterMs(baseDurationMs, 0.4);
|
|
3445
|
+
logger6.start("simulateGaze", `duration=${durationMs}ms`);
|
|
3446
|
+
const startTime = Date.now();
|
|
3447
|
+
const viewportSize = page.viewportSize() || { width: 1920, height: 1080 };
|
|
3448
|
+
while (Date.now() - startTime < durationMs) {
|
|
3449
|
+
const x = 100 + Math.random() * (viewportSize.width - 200);
|
|
3450
|
+
const y = 100 + Math.random() * (viewportSize.height - 200);
|
|
3451
|
+
await cursor.actions.move({ x, y });
|
|
3452
|
+
await delay2(this.jitterMs(600, 0.5));
|
|
3707
3453
|
}
|
|
3708
|
-
|
|
3709
|
-
}
|
|
3710
|
-
|
|
3454
|
+
logger6.success("simulateGaze");
|
|
3455
|
+
},
|
|
3456
|
+
/**
|
|
3457
|
+
* 人类化输入 - 带节奏变化(快-慢-停顿-偶尔加速)
|
|
3458
|
+
* @param {import('playwright').Page} page
|
|
3459
|
+
* @param {string} selector - 输入框选择器
|
|
3460
|
+
* @param {string} text - 要输入的文本
|
|
3461
|
+
* @param {Object} [options]
|
|
3462
|
+
* @param {number} [options.baseDelay=180] - 基础按键延迟 (ms),实际 ±40% 抖动
|
|
3463
|
+
* @param {number} [options.pauseProbability=0.08] - 停顿概率 (0-1)
|
|
3464
|
+
* @param {number} [options.pauseBase=800] - 停顿时长基础值 (ms),实际 ±50% 抖动
|
|
3465
|
+
*/
|
|
3466
|
+
async humanType(page, selector, text, options = {}) {
|
|
3467
|
+
logger6.start("humanType", `selector=${selector}, textLen=${text.length}`);
|
|
3468
|
+
const {
|
|
3469
|
+
baseDelay = 180,
|
|
3470
|
+
pauseProbability = 0.08,
|
|
3471
|
+
pauseBase = 800
|
|
3472
|
+
} = options;
|
|
3711
3473
|
try {
|
|
3712
|
-
|
|
3713
|
-
await
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3474
|
+
const locator = page.locator(selector);
|
|
3475
|
+
await Humanize.humanClick(page, locator);
|
|
3476
|
+
await delay2(this.jitterMs(200, 0.4));
|
|
3477
|
+
for (let i = 0; i < text.length; i++) {
|
|
3478
|
+
const char = text[i];
|
|
3479
|
+
let charDelay;
|
|
3480
|
+
if (char === " ") {
|
|
3481
|
+
charDelay = this.jitterMs(baseDelay * 0.6, 0.3);
|
|
3482
|
+
} else if (/[,.!?;:,。!?;:]/.test(char)) {
|
|
3483
|
+
charDelay = this.jitterMs(baseDelay * 1.5, 0.4);
|
|
3484
|
+
} else {
|
|
3485
|
+
charDelay = this.jitterMs(baseDelay, 0.4);
|
|
3486
|
+
}
|
|
3487
|
+
await page.keyboard.type(char);
|
|
3488
|
+
await delay2(charDelay);
|
|
3489
|
+
if (Math.random() < pauseProbability && i < text.length - 1) {
|
|
3490
|
+
const pauseTime = this.jitterMs(pauseBase, 0.5);
|
|
3491
|
+
logger6.debug(`\u505C\u987F ${pauseTime}ms...`);
|
|
3492
|
+
await delay2(pauseTime);
|
|
3493
|
+
}
|
|
3725
3494
|
}
|
|
3495
|
+
logger6.success("humanType");
|
|
3496
|
+
} catch (error) {
|
|
3497
|
+
logger6.fail("humanType", error);
|
|
3498
|
+
throw error;
|
|
3726
3499
|
}
|
|
3727
|
-
}
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3500
|
+
},
|
|
3501
|
+
/**
|
|
3502
|
+
* 人类化按键 - 模拟用户在当前焦点或指定目标上按下一次键。
|
|
3503
|
+
* @param {import('playwright').Page} page
|
|
3504
|
+
* @param {string|import('playwright').ElementHandle|import('playwright').Locator} targetOrKey - 目标或按键
|
|
3505
|
+
* @param {string|Object} [maybeKey] - 按键或选项
|
|
3506
|
+
* @param {Object} [options]
|
|
3507
|
+
*/
|
|
3508
|
+
async humanPress(page, targetOrKey, maybeKey, options = {}) {
|
|
3509
|
+
const hasTarget = typeof maybeKey === "string";
|
|
3510
|
+
const key = hasTarget ? maybeKey : targetOrKey;
|
|
3511
|
+
const pressOptions = hasTarget ? options : maybeKey || options;
|
|
3512
|
+
const {
|
|
3513
|
+
reactionDelay = 180,
|
|
3514
|
+
holdDelay = 45,
|
|
3515
|
+
focusDelay = 180,
|
|
3516
|
+
scrollIfNeeded = true,
|
|
3517
|
+
throwOnMissing = true,
|
|
3518
|
+
keyboardOptions = {}
|
|
3519
|
+
} = pressOptions || {};
|
|
3520
|
+
const targetDesc = hasTarget ? typeof targetOrKey === "string" ? targetOrKey : "ElementHandle" : "current focus";
|
|
3521
|
+
logger6.start("humanPress", `key=${key}, target=${targetDesc}`);
|
|
3522
|
+
try {
|
|
3523
|
+
if (hasTarget) {
|
|
3524
|
+
await this.humanClick(page, targetOrKey, { reactionDelay: focusDelay, scrollIfNeeded, throwOnMissing });
|
|
3525
|
+
}
|
|
3526
|
+
await delay2(this.jitterMs(reactionDelay, 0.45));
|
|
3527
|
+
await page.keyboard.press(key, {
|
|
3528
|
+
...keyboardOptions,
|
|
3529
|
+
delay: this.jitterMs(holdDelay, 0.5)
|
|
3530
|
+
});
|
|
3531
|
+
logger6.success("humanPress");
|
|
3532
|
+
return true;
|
|
3743
3533
|
} catch (error) {
|
|
3744
|
-
|
|
3745
|
-
|
|
3534
|
+
logger6.fail("humanPress", error);
|
|
3535
|
+
throw error;
|
|
3746
3536
|
}
|
|
3747
|
-
}
|
|
3748
|
-
await withTimeout(
|
|
3749
|
-
() => page.mouse.click(point.x, point.y),
|
|
3750
|
-
mouseFallbackTimeoutMs,
|
|
3751
|
-
"mouse.click fallback"
|
|
3752
|
-
);
|
|
3753
|
-
return { method: "mouse" };
|
|
3754
|
-
};
|
|
3755
|
-
var MobileHumanize = {
|
|
3756
|
-
jitterMs,
|
|
3757
|
-
async initializeCursor(page) {
|
|
3758
|
-
if (initializedPages.has(page)) return;
|
|
3759
|
-
initializedPages.add(page);
|
|
3760
|
-
logger5.debug("initializeCursor: mobile mode uses touch gestures, cursor init skipped");
|
|
3761
3537
|
},
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3538
|
+
/**
|
|
3539
|
+
* 人类化清空输入框 - 模拟人类删除文本的行为
|
|
3540
|
+
* @param {import('playwright').Page} page
|
|
3541
|
+
* @param {string} selector - 输入框选择器
|
|
3542
|
+
*/
|
|
3543
|
+
async humanClear(page, selector) {
|
|
3544
|
+
logger6.start("humanClear", `selector=${selector}`);
|
|
3545
|
+
try {
|
|
3546
|
+
const locator = page.locator(selector);
|
|
3547
|
+
await locator.click();
|
|
3548
|
+
await delay2(this.jitterMs(200, 0.4));
|
|
3549
|
+
const currentValue = await locator.inputValue();
|
|
3550
|
+
if (!currentValue || currentValue.length === 0) {
|
|
3551
|
+
logger6.success("humanClear", "already empty");
|
|
3552
|
+
return;
|
|
3768
3553
|
}
|
|
3554
|
+
await page.keyboard.press("Meta+A");
|
|
3555
|
+
await delay2(this.jitterMs(100, 0.4));
|
|
3556
|
+
await page.keyboard.press("Backspace");
|
|
3557
|
+
logger6.success("humanClear");
|
|
3558
|
+
} catch (error) {
|
|
3559
|
+
logger6.fail("humanClear", error);
|
|
3560
|
+
throw error;
|
|
3769
3561
|
}
|
|
3770
|
-
await waitJitter(80, 0.4);
|
|
3771
|
-
return true;
|
|
3772
3562
|
},
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
logger5.start("humanScroll", `target=${targetDesc}`);
|
|
3783
|
-
const element = await resolveElement(page, target, { throwOnMissing });
|
|
3784
|
-
if (!element) {
|
|
3785
|
-
logger5.warn(`humanScroll | \u5143\u7D20\u672A\u627E\u5230: ${targetDesc}`);
|
|
3786
|
-
return { element: null, didScroll: false, restore: null };
|
|
3787
|
-
}
|
|
3563
|
+
/**
|
|
3564
|
+
* 页面预热浏览 - 模拟人类进入页面后的探索行为
|
|
3565
|
+
* @param {import('playwright').Page} page
|
|
3566
|
+
* @param {number} [baseDuration=3500] - 预热时长基础值 (±40% 抖动)
|
|
3567
|
+
*/
|
|
3568
|
+
async warmUpBrowsing(page, baseDuration = 3500) {
|
|
3569
|
+
const cursor = $GetCursor(page);
|
|
3570
|
+
const durationMs = this.jitterMs(baseDuration, 0.4);
|
|
3571
|
+
logger6.start("warmUpBrowsing", `duration=${durationMs}ms`);
|
|
3788
3572
|
const startTime = Date.now();
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
if (
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
logger5.warn(`humanScroll | \u5143\u7D20\u4E0D\u53EF\u6EDA\u52A8\u81F3\u53EF\u70B9\u51FB\u72B6\u6001: ${status.reason || status.code}`);
|
|
3803
|
-
return { element, didScroll, restore: null };
|
|
3804
|
-
}
|
|
3805
|
-
const scrollRect = await getScrollableRect(element);
|
|
3806
|
-
if (!scrollRect && status.isFixed && status.code === "OUT_OF_VIEWPORT") {
|
|
3807
|
-
logger5.warn(`humanScroll | fixed/sticky \u76EE\u6807\u4E0D\u5728\u89C6\u53E3\u5185\uFF0C\u9875\u9762\u6EDA\u52A8\u65E0\u6CD5\u6539\u53D8\u5176\u4F4D\u7F6E (direction=${status.direction || "unknown"})`);
|
|
3808
|
-
return { element, didScroll, restore: null, unscrollable: true };
|
|
3809
|
-
}
|
|
3810
|
-
if (!scrollRect && status.isFixed && status.code === "OBSTRUCTED") {
|
|
3811
|
-
logger5.warn(`humanScroll | fixed/sticky \u76EE\u6807\u88AB\u906E\u6321\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u89E3\u9664 (${status.obstruction?.tag || "unknown"})`);
|
|
3812
|
-
return { element, didScroll, restore: null, unscrollable: true };
|
|
3813
|
-
}
|
|
3814
|
-
if (scrollRect && status.code === "OBSTRUCTED" && status.obstruction?.isFixed) {
|
|
3815
|
-
const moved = await scrollAwayFromObstruction(element, status);
|
|
3816
|
-
if (moved.moved) {
|
|
3817
|
-
logger5.debug(`humanScroll | sticky/fixed \u906E\u6321\u8865\u507F\u6EDA\u52A8 top=${Math.round(moved.scrollTop || 0)}`);
|
|
3818
|
-
await waitJitter(90, 0.3);
|
|
3819
|
-
didScroll = true;
|
|
3820
|
-
continue;
|
|
3821
|
-
}
|
|
3822
|
-
}
|
|
3823
|
-
if (Date.now() - startTime > maxDurationMs) {
|
|
3824
|
-
logger5.warn(`humanScroll | mobile timeout (${maxDurationMs}ms, status=${status.code}, direction=${status.direction || "unknown"}, fixed=${Boolean(status.isFixed)})`);
|
|
3825
|
-
return { element, didScroll, restore: null };
|
|
3826
|
-
}
|
|
3827
|
-
const stepMin = scrollRect ? Math.min(minStep, Math.max(60, scrollRect.height * 0.4)) : minStep;
|
|
3828
|
-
const stepMax = scrollRect ? Math.min(maxStep, Math.max(stepMin + 40, scrollRect.height * 0.8)) : maxStep;
|
|
3829
|
-
logger5.debug(`humanScroll | \u6B65\u9AA4 ${i + 1}/${maxSteps}: ${status.reason || status.code} ${status.direction ? `(${status.direction})` : ""}`);
|
|
3830
|
-
const distance = stepMin + Math.random() * Math.max(1, stepMax - stepMin);
|
|
3831
|
-
let deltaY = status.direction === "up" ? -distance : distance;
|
|
3832
|
-
if (status.code === "OBSTRUCTED") {
|
|
3833
|
-
if (status.obstruction?.isFixed && status.obstruction.top != null) {
|
|
3834
|
-
const obstructionMiddle = (Number(status.obstruction.top || 0) + Number(status.obstruction.bottom || status.obstruction.top || 0)) / 2;
|
|
3835
|
-
const visibleMiddle = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
|
|
3836
|
-
deltaY = obstructionMiddle < visibleMiddle ? -distance : distance;
|
|
3573
|
+
const viewportSize = page.viewportSize() || { width: 1920, height: 1080 };
|
|
3574
|
+
try {
|
|
3575
|
+
while (Date.now() - startTime < durationMs) {
|
|
3576
|
+
const action = Math.random();
|
|
3577
|
+
if (action < 0.4) {
|
|
3578
|
+
const x = 100 + Math.random() * (viewportSize.width - 200);
|
|
3579
|
+
const y = 100 + Math.random() * (viewportSize.height - 200);
|
|
3580
|
+
await cursor.actions.move({ x, y });
|
|
3581
|
+
await delay2(this.jitterMs(350, 0.4));
|
|
3582
|
+
} else if (action < 0.7) {
|
|
3583
|
+
const scrollY = (Math.random() - 0.5) * 200;
|
|
3584
|
+
await page.mouse.wheel(0, scrollY);
|
|
3585
|
+
await delay2(this.jitterMs(500, 0.4));
|
|
3837
3586
|
} else {
|
|
3838
|
-
|
|
3839
|
-
deltaY = status.cy > halfY ? distance : -distance;
|
|
3587
|
+
await delay2(this.jitterMs(800, 0.5));
|
|
3840
3588
|
}
|
|
3841
3589
|
}
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
const
|
|
3868
|
-
|
|
3869
|
-
await page.evaluate((state2) => window.scrollTo(state2.x, state2.y), beforeWindowState);
|
|
3870
|
-
logger5.debug(`humanScroll | \u7A97\u53E3\u6EDA\u52A8\u56DE\u6536 from=${Math.round(afterWindowState.y)} to=${Math.round(beforeWindowState.y)}`);
|
|
3871
|
-
}
|
|
3590
|
+
logger6.success("warmUpBrowsing");
|
|
3591
|
+
} catch (error) {
|
|
3592
|
+
logger6.fail("warmUpBrowsing", error);
|
|
3593
|
+
throw error;
|
|
3594
|
+
}
|
|
3595
|
+
},
|
|
3596
|
+
/**
|
|
3597
|
+
* 自然滚动 - 带惯性、减速效果和随机抖动
|
|
3598
|
+
* @param {import('playwright').Page} page
|
|
3599
|
+
* @param {'up' | 'down'} [direction='down'] - 滚动方向
|
|
3600
|
+
* @param {number} [distance=300] - 总滚动距离基础值 (px),±15% 抖动
|
|
3601
|
+
* @param {number} [baseSteps=5] - 分几步完成基础值,±1 随机
|
|
3602
|
+
*/
|
|
3603
|
+
async naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
|
|
3604
|
+
const steps = Math.max(3, baseSteps + Math.floor(Math.random() * 3) - 1);
|
|
3605
|
+
const actualDistance = this.jitterMs(distance, 0.15);
|
|
3606
|
+
logger6.start("naturalScroll", `dir=${direction}, dist=${actualDistance}, steps=${steps}`);
|
|
3607
|
+
const sign = direction === "down" ? 1 : -1;
|
|
3608
|
+
const stepDistance = actualDistance / steps;
|
|
3609
|
+
try {
|
|
3610
|
+
for (let i = 0; i < steps; i++) {
|
|
3611
|
+
const factor = 1 - i / steps * 0.5;
|
|
3612
|
+
const jitter = 0.9 + Math.random() * 0.2;
|
|
3613
|
+
const scrollAmount = stepDistance * factor * sign * jitter;
|
|
3614
|
+
await page.mouse.wheel(0, scrollAmount);
|
|
3615
|
+
const baseDelay = 60 + i * 25;
|
|
3616
|
+
await delay2(this.jitterMs(baseDelay, 0.3));
|
|
3872
3617
|
}
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3618
|
+
logger6.success("naturalScroll");
|
|
3619
|
+
} catch (error) {
|
|
3620
|
+
logger6.fail("naturalScroll", error);
|
|
3621
|
+
throw error;
|
|
3622
|
+
}
|
|
3623
|
+
}
|
|
3624
|
+
};
|
|
3625
|
+
|
|
3626
|
+
// src/internals/humanize/shared.js
|
|
3627
|
+
import delay3 from "delay";
|
|
3628
|
+
var jitterMs = (base, jitterPercent = 0.3) => {
|
|
3629
|
+
const jitter = Number(base || 0) * Number(jitterPercent || 0) * (Math.random() * 2 - 1);
|
|
3630
|
+
return Math.max(10, Math.round(Number(base || 0) + jitter));
|
|
3631
|
+
};
|
|
3632
|
+
var resolveElement = async (page, target, { throwOnMissing = true } = {}) => {
|
|
3633
|
+
if (target == null) return null;
|
|
3634
|
+
let element = target;
|
|
3635
|
+
if (typeof target === "string") {
|
|
3636
|
+
element = await page.$(target);
|
|
3637
|
+
}
|
|
3638
|
+
if (!element) {
|
|
3639
|
+
if (throwOnMissing) {
|
|
3640
|
+
throw new Error(`\u627E\u4E0D\u5230\u5143\u7D20 ${String(target)}`);
|
|
3641
|
+
}
|
|
3642
|
+
return null;
|
|
3643
|
+
}
|
|
3644
|
+
return element;
|
|
3645
|
+
};
|
|
3646
|
+
var waitJitter = (base, jitterPercent = 0.3) => delay3(jitterMs(base, jitterPercent));
|
|
3647
|
+
|
|
3648
|
+
// src/internals/humanize/mobile.js
|
|
3649
|
+
var logger7 = createInternalLogger("Humanize.Mobile");
|
|
3650
|
+
var initializedPages = /* @__PURE__ */ new WeakSet();
|
|
3651
|
+
var DEFAULT_TAP_TIMEOUT_MS = 2500;
|
|
3652
|
+
var DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS = 1200;
|
|
3653
|
+
var DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS = 900;
|
|
3654
|
+
var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
3655
|
+
var resolveViewport = (page) => page?.viewportSize?.() || { width: 390, height: 844 };
|
|
3656
|
+
var describeTarget = (target) => {
|
|
3657
|
+
if (target == null) return "Current Position";
|
|
3658
|
+
return typeof target === "string" ? target : "ElementHandle";
|
|
3659
|
+
};
|
|
3660
|
+
var clipBoxToViewport = (box, viewport) => {
|
|
3661
|
+
if (!box || !viewport) return box;
|
|
3662
|
+
const left = clamp(box.x, 0, viewport.width);
|
|
3663
|
+
const top = clamp(box.y, 0, viewport.height);
|
|
3664
|
+
const right = clamp(box.x + box.width, 0, viewport.width);
|
|
3665
|
+
const bottom = clamp(box.y + box.height, 0, viewport.height);
|
|
3666
|
+
if (right - left > 1 && bottom - top > 1) {
|
|
3667
|
+
return {
|
|
3668
|
+
x: left,
|
|
3669
|
+
y: top,
|
|
3670
|
+
width: right - left,
|
|
3671
|
+
height: bottom - top
|
|
3672
|
+
};
|
|
3673
|
+
}
|
|
3674
|
+
return box;
|
|
3675
|
+
};
|
|
3676
|
+
var centerPointInBox = (box) => ({
|
|
3677
|
+
x: box.x + box.width / 2,
|
|
3678
|
+
y: box.y + box.height / 2
|
|
3679
|
+
});
|
|
3680
|
+
var withTimeout = async (operation, timeoutMs, label) => {
|
|
3681
|
+
const safeTimeoutMs = Math.max(50, Number(timeoutMs || 0));
|
|
3682
|
+
let timeoutId = null;
|
|
3683
|
+
try {
|
|
3684
|
+
return await Promise.race([
|
|
3685
|
+
Promise.resolve().then(operation),
|
|
3686
|
+
new Promise((_, reject) => {
|
|
3687
|
+
timeoutId = setTimeout(() => {
|
|
3688
|
+
reject(new Error(`${label} timeout after ${safeTimeoutMs}ms`));
|
|
3689
|
+
}, safeTimeoutMs);
|
|
3690
|
+
})
|
|
3691
|
+
]);
|
|
3692
|
+
} finally {
|
|
3693
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
3694
|
+
}
|
|
3695
|
+
};
|
|
3696
|
+
var checkElementVisibility = async (element) => {
|
|
3697
|
+
return element.evaluate((el) => {
|
|
3698
|
+
const targetStyle = window.getComputedStyle(el);
|
|
3699
|
+
if (!targetStyle || targetStyle.display === "none" || targetStyle.visibility === "hidden" || targetStyle.visibility === "collapse") {
|
|
3700
|
+
return { code: "NOT_INTERACTABLE", reason: "\u5143\u7D20\u4E0D\u53EF\u89C1", direction: "down" };
|
|
3701
|
+
}
|
|
3702
|
+
const rect = el.getBoundingClientRect();
|
|
3703
|
+
if (!rect || rect.width <= 0 || rect.height <= 0) {
|
|
3704
|
+
return { code: "ZERO_DIMENSIONS", reason: "\u5C3A\u5BF8\u4E3A\u96F6", direction: "down" };
|
|
3705
|
+
}
|
|
3706
|
+
const viewW = window.innerWidth;
|
|
3707
|
+
const viewH = window.innerHeight;
|
|
3708
|
+
const centerY = rect.top + rect.height / 2;
|
|
3709
|
+
let clipLeft = 0;
|
|
3710
|
+
let clipRight = viewW;
|
|
3711
|
+
let clipTop = 0;
|
|
3712
|
+
let clipBottom = viewH;
|
|
3713
|
+
let isFixed = false;
|
|
3714
|
+
let positioning = null;
|
|
3715
|
+
for (let node = el; node && node !== document.body; node = node.parentElement) {
|
|
3716
|
+
const style = window.getComputedStyle(node);
|
|
3717
|
+
if (style && (style.position === "fixed" || style.position === "sticky")) {
|
|
3718
|
+
isFixed = true;
|
|
3719
|
+
positioning = style.position;
|
|
3720
|
+
break;
|
|
3887
3721
|
}
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
return {
|
|
3901
|
-
kind: "element",
|
|
3902
|
-
top: current.scrollTop,
|
|
3903
|
-
left: current.scrollLeft
|
|
3904
|
-
};
|
|
3905
|
-
}
|
|
3906
|
-
current = current.parentElement;
|
|
3907
|
-
}
|
|
3908
|
-
return { kind: "window", top: window.scrollY, left: window.scrollX };
|
|
3909
|
-
});
|
|
3910
|
-
const topDelta = Number(afterState.top || 0) - Number(beforeState.top || 0);
|
|
3911
|
-
const leftDelta = Number(afterState.left || 0) - Number(beforeState.left || 0);
|
|
3912
|
-
const expectedDelta = Number(deltaY || 0);
|
|
3913
|
-
const moved = beforeState.kind !== afterState.kind || Math.abs(topDelta) > 2 || Math.abs(leftDelta) > 2;
|
|
3914
|
-
if (!moved) {
|
|
3915
|
-
const fallback = await scrollScrollableAncestor(element, deltaY);
|
|
3916
|
-
logger5.debug(`humanScroll | \u5BB9\u5668\u89E6\u6478\u65E0\u6548\uFF0C\u76F4\u63A5\u6EDA\u52A8 fallback=${fallback.scroller ? "ancestor" : "window"} top=${Math.round(fallback.scrollTop || 0)}`);
|
|
3917
|
-
} else if (beforeState.kind === afterState.kind && Math.abs(expectedDelta) > 24 && Math.sign(topDelta || expectedDelta) === Math.sign(expectedDelta) && Math.abs(topDelta) < Math.min(Math.abs(expectedDelta) * 0.45, 96)) {
|
|
3918
|
-
const residualDelta = expectedDelta - topDelta;
|
|
3919
|
-
if (Math.sign(residualDelta) === Math.sign(expectedDelta) && Math.abs(residualDelta) > 24) {
|
|
3920
|
-
const fallback = await scrollScrollableAncestor(element, residualDelta);
|
|
3921
|
-
logger5.debug(`humanScroll | \u5BB9\u5668\u89E6\u6478\u8DDD\u79BB\u4E0D\u8DB3\uFF0C\u8865\u507F\u6EDA\u52A8 fallback=${fallback.scroller ? "ancestor" : "window"} top=${Math.round(fallback.scrollTop || 0)}`);
|
|
3922
|
-
}
|
|
3923
|
-
}
|
|
3722
|
+
}
|
|
3723
|
+
for (let node = el.parentElement; node && node !== document.body; node = node.parentElement) {
|
|
3724
|
+
const style = window.getComputedStyle(node);
|
|
3725
|
+
if (!style) continue;
|
|
3726
|
+
const clipsY = ["auto", "scroll", "overlay", "hidden", "clip"].includes(style.overflowY);
|
|
3727
|
+
const clipsX = ["auto", "scroll", "overlay", "hidden", "clip"].includes(style.overflowX);
|
|
3728
|
+
if (!clipsX && !clipsY) continue;
|
|
3729
|
+
const nodeRect = node.getBoundingClientRect();
|
|
3730
|
+
if (!nodeRect || nodeRect.width <= 0 || nodeRect.height <= 0) continue;
|
|
3731
|
+
if (clipsX) {
|
|
3732
|
+
clipLeft = Math.max(clipLeft, nodeRect.left);
|
|
3733
|
+
clipRight = Math.min(clipRight, nodeRect.right);
|
|
3924
3734
|
}
|
|
3925
|
-
if (
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
|
|
3929
|
-
logger5.warn(`humanScroll | \u76EE\u6807\u4E0D\u968F\u6EDA\u52A8\u5BB9\u5668\u79FB\u52A8\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u6539\u53D8\u5176\u4F4D\u7F6E (status=${status.code}, direction=${status.direction || "unknown"})`);
|
|
3930
|
-
return { element, didScroll, restore: null, unscrollable: true };
|
|
3931
|
-
}
|
|
3735
|
+
if (clipsY) {
|
|
3736
|
+
clipTop = Math.max(clipTop, nodeRect.top);
|
|
3737
|
+
clipBottom = Math.min(clipBottom, nodeRect.bottom);
|
|
3932
3738
|
}
|
|
3933
|
-
didScroll = true;
|
|
3934
3739
|
}
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3740
|
+
const visibleLeft = Math.max(clipLeft, Math.min(clipRight, rect.left));
|
|
3741
|
+
const visibleRight = Math.max(clipLeft, Math.min(clipRight, rect.right));
|
|
3742
|
+
const visibleTop = Math.max(clipTop, Math.min(clipBottom, rect.top));
|
|
3743
|
+
const visibleBottom = Math.max(clipTop, Math.min(clipBottom, rect.bottom));
|
|
3744
|
+
const visibleWidth = visibleRight - visibleLeft;
|
|
3745
|
+
const visibleHeight = visibleBottom - visibleTop;
|
|
3746
|
+
const cx = visibleLeft + visibleWidth / 2;
|
|
3747
|
+
const cy = visibleTop + visibleHeight / 2;
|
|
3748
|
+
if (visibleWidth <= 1 || visibleHeight <= 1) {
|
|
3749
|
+
return {
|
|
3750
|
+
code: "OUT_OF_VIEWPORT",
|
|
3751
|
+
reason: "\u4E0D\u5728\u89C6\u53E3\u5185",
|
|
3752
|
+
direction: centerY < clipTop ? "up" : "down",
|
|
3753
|
+
cy: centerY,
|
|
3754
|
+
viewH,
|
|
3755
|
+
isFixed,
|
|
3756
|
+
positioning
|
|
3757
|
+
};
|
|
3946
3758
|
}
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
activateFallbackTimeoutMs = DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS,
|
|
3958
|
-
fallbackDomClick = true,
|
|
3959
|
-
fallbackDomClickOnTapError = true
|
|
3960
|
-
} = options;
|
|
3961
|
-
const targetDesc = describeTarget(target);
|
|
3962
|
-
logger5.start("humanClick", `target=${targetDesc}`);
|
|
3963
|
-
try {
|
|
3964
|
-
if (target == null) {
|
|
3965
|
-
const viewport = resolveViewport(page);
|
|
3966
|
-
await waitJitter(reactionDelay, 0.45);
|
|
3967
|
-
await tapPoint(page, {
|
|
3968
|
-
x: viewport.width * (0.45 + Math.random() * 0.1),
|
|
3969
|
-
y: viewport.height * (0.48 + Math.random() * 0.12)
|
|
3970
|
-
}, {
|
|
3971
|
-
timeoutMs: tapTimeoutMs,
|
|
3972
|
-
mouseFallbackTimeoutMs
|
|
3973
|
-
});
|
|
3974
|
-
logger5.success("humanClick", "Tapped current position");
|
|
3759
|
+
const isRootNode = (node) => !node || node === document || node === document.body || node === document.documentElement;
|
|
3760
|
+
const commonAncestor = (a, b) => {
|
|
3761
|
+
for (let current = a; current && !isRootNode(current); current = current.parentElement) {
|
|
3762
|
+
if (current.contains(b)) return current;
|
|
3763
|
+
}
|
|
3764
|
+
return null;
|
|
3765
|
+
};
|
|
3766
|
+
const sameTapTarget = (pointElement) => {
|
|
3767
|
+
if (!pointElement) return false;
|
|
3768
|
+
if (pointElement === el || el.contains(pointElement) || pointElement.contains(el)) {
|
|
3975
3769
|
return true;
|
|
3976
3770
|
}
|
|
3977
|
-
const
|
|
3978
|
-
if (!
|
|
3979
|
-
|
|
3771
|
+
const common = commonAncestor(el, pointElement);
|
|
3772
|
+
if (!common) return false;
|
|
3773
|
+
const commonRect = common.getBoundingClientRect?.();
|
|
3774
|
+
if (!commonRect || commonRect.width <= 0 || commonRect.height <= 0) return false;
|
|
3775
|
+
const commonArea = commonRect.width * commonRect.height;
|
|
3776
|
+
const targetArea = Math.max(1, rect.width * rect.height);
|
|
3777
|
+
const maxSharedRegionArea = Math.max(targetArea * 12, 4096);
|
|
3778
|
+
if (commonArea > maxSharedRegionArea) return false;
|
|
3779
|
+
if (commonRect.width > Math.max(rect.width * 8, 120) || commonRect.height > Math.max(rect.height * 8, 120)) {
|
|
3980
3780
|
return false;
|
|
3981
3781
|
}
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
3782
|
+
return common.contains(el) && common.contains(pointElement);
|
|
3783
|
+
};
|
|
3784
|
+
const describeElement = (node) => {
|
|
3785
|
+
if (!node) return null;
|
|
3786
|
+
const className = typeof node.className === "string" ? node.className : node.className && typeof node.className.baseVal === "string" ? node.className.baseVal : "";
|
|
3787
|
+
const rect2 = node.getBoundingClientRect?.();
|
|
3788
|
+
const style = window.getComputedStyle(node);
|
|
3789
|
+
return {
|
|
3790
|
+
tag: node.tagName,
|
|
3791
|
+
id: node.id || "",
|
|
3792
|
+
className,
|
|
3793
|
+
isFixed: Boolean(style && (style.position === "fixed" || style.position === "sticky")),
|
|
3794
|
+
positioning: style?.position || "",
|
|
3795
|
+
top: rect2 ? rect2.top : null,
|
|
3796
|
+
bottom: rect2 ? rect2.bottom : null,
|
|
3797
|
+
left: rect2 ? rect2.left : null,
|
|
3798
|
+
right: rect2 ? rect2.right : null
|
|
3799
|
+
};
|
|
3800
|
+
};
|
|
3801
|
+
const samplePoints = [
|
|
3802
|
+
{ x: cx, y: cy },
|
|
3803
|
+
{ x: visibleLeft + Math.min(8, Math.max(1, visibleWidth * 0.25)), y: cy },
|
|
3804
|
+
{ x: visibleRight - Math.min(8, Math.max(1, visibleWidth * 0.25)), y: cy },
|
|
3805
|
+
{ x: cx, y: visibleTop + Math.min(8, Math.max(1, visibleHeight * 0.25)) },
|
|
3806
|
+
{ x: cx, y: visibleBottom - Math.min(8, Math.max(1, visibleHeight * 0.25)) }
|
|
3807
|
+
];
|
|
3808
|
+
let obstruction = null;
|
|
3809
|
+
for (const point of samplePoints) {
|
|
3810
|
+
const pointElement = document.elementFromPoint(point.x, point.y);
|
|
3811
|
+
if (sameTapTarget(pointElement)) {
|
|
3812
|
+
return { code: "VISIBLE", isFixed, positioning };
|
|
4011
3813
|
}
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
3814
|
+
obstruction = obstruction || describeElement(pointElement);
|
|
3815
|
+
}
|
|
3816
|
+
if (obstruction) {
|
|
3817
|
+
return {
|
|
3818
|
+
code: "OBSTRUCTED",
|
|
3819
|
+
reason: "\u88AB\u906E\u6321",
|
|
3820
|
+
direction: cy > viewH / 2 ? "down" : "up",
|
|
3821
|
+
obstruction,
|
|
3822
|
+
cy,
|
|
3823
|
+
viewH,
|
|
3824
|
+
isFixed,
|
|
3825
|
+
positioning
|
|
3826
|
+
};
|
|
3827
|
+
}
|
|
3828
|
+
return { code: "VISIBLE", isFixed, positioning };
|
|
3829
|
+
});
|
|
3830
|
+
};
|
|
3831
|
+
var activateElementFallback = async (element, point = null, options = {}) => {
|
|
3832
|
+
return element.evaluate((el, { innerOptions }) => {
|
|
3833
|
+
const isEditable = (node) => {
|
|
3834
|
+
if (!node || node.nodeType !== Node.ELEMENT_NODE) return false;
|
|
3835
|
+
if (node.isContentEditable) return true;
|
|
3836
|
+
if (node instanceof HTMLTextAreaElement) return !node.disabled && !node.readOnly;
|
|
3837
|
+
if (node instanceof HTMLInputElement) {
|
|
3838
|
+
return !node.disabled && !node.readOnly && typeof node.select === "function";
|
|
3839
|
+
}
|
|
3840
|
+
return false;
|
|
3841
|
+
};
|
|
3842
|
+
const findEditable = (node) => {
|
|
3843
|
+
for (let current = node; current && current !== document.body; current = current.parentElement) {
|
|
3844
|
+
if (isEditable(current)) return current;
|
|
4017
3845
|
}
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
});
|
|
4026
|
-
} catch (tapError) {
|
|
4027
|
-
if (!fallbackDomClickOnTapError) throw tapError;
|
|
4028
|
-
const fallback = await withTimeout(
|
|
4029
|
-
() => activateElementFallback(element, tapTarget, {
|
|
4030
|
-
editableOnly: false
|
|
4031
|
-
}),
|
|
4032
|
-
activateFallbackTimeoutMs,
|
|
4033
|
-
"activation fallback"
|
|
4034
|
-
).catch(() => null);
|
|
4035
|
-
if (!fallback?.activated) throw tapError;
|
|
4036
|
-
logger5.warn(`humanClick: tap \u5931\u8D25\u540E\u5DF2\u7528 ${fallback.method} \u515C\u5E95: ${tapError?.message || tapError}`);
|
|
3846
|
+
return null;
|
|
3847
|
+
};
|
|
3848
|
+
const editable = findEditable(el);
|
|
3849
|
+
if (editable && typeof editable.focus === "function") {
|
|
3850
|
+
editable.focus({ preventScroll: true });
|
|
3851
|
+
if (innerOptions.editableOnly) {
|
|
3852
|
+
return { activated: true, method: "focus", tag: editable.tagName || "" };
|
|
4037
3853
|
}
|
|
4038
|
-
await waitJitter(120, 0.35);
|
|
4039
|
-
logger5.success("humanClick");
|
|
4040
|
-
return true;
|
|
4041
|
-
} catch (error) {
|
|
4042
|
-
logger5.fail("humanClick", error);
|
|
4043
|
-
throw error;
|
|
4044
3854
|
}
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
await waitJitter(baseMs, jitterPercent);
|
|
4048
|
-
},
|
|
4049
|
-
async simulateGaze(page, baseDurationMs = 2500) {
|
|
4050
|
-
const durationMs = jitterMs(baseDurationMs, 0.4);
|
|
4051
|
-
const startTime = Date.now();
|
|
4052
|
-
while (Date.now() - startTime < durationMs) {
|
|
4053
|
-
if (Math.random() < 0.28) {
|
|
4054
|
-
const distance = 70 + Math.random() * 120;
|
|
4055
|
-
await dispatchTouchSwipe(page, Math.random() < 0.7 ? distance : -distance, {
|
|
4056
|
-
durationMs: 180,
|
|
4057
|
-
steps: 4
|
|
4058
|
-
});
|
|
4059
|
-
} else {
|
|
4060
|
-
await waitJitter(420, 0.55);
|
|
4061
|
-
}
|
|
3855
|
+
if (innerOptions.editableOnly) {
|
|
3856
|
+
return { activated: false, method: "none", tag: el?.tagName || "" };
|
|
4062
3857
|
}
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
const {
|
|
4066
|
-
baseDelay = 160,
|
|
4067
|
-
pauseProbability = 0.08,
|
|
4068
|
-
pauseBase = 700
|
|
4069
|
-
} = options;
|
|
4070
|
-
await MobileHumanize.humanClick(page, selector, { scrollIfNeeded: true });
|
|
4071
|
-
await waitJitter(220, 0.45);
|
|
4072
|
-
for (let i = 0; i < String(text).length; i += 1) {
|
|
4073
|
-
const char = String(text)[i];
|
|
4074
|
-
const charDelay = /[,.!?;:,。!?;:]/.test(char) ? jitterMs(baseDelay * 1.45, 0.4) : jitterMs(char === " " ? baseDelay * 0.65 : baseDelay, 0.4);
|
|
4075
|
-
await page.keyboard.type(char);
|
|
4076
|
-
await waitJitter(charDelay, 0.15);
|
|
4077
|
-
if (Math.random() < pauseProbability && i < String(text).length - 1) {
|
|
4078
|
-
await waitJitter(pauseBase, 0.5);
|
|
4079
|
-
}
|
|
3858
|
+
if (typeof el.focus === "function") {
|
|
3859
|
+
el.focus({ preventScroll: true });
|
|
4080
3860
|
}
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
const key = hasTarget ? maybeKey : targetOrKey;
|
|
4085
|
-
const pressOptions = hasTarget ? options : maybeKey || options;
|
|
4086
|
-
const {
|
|
4087
|
-
reactionDelay = 170,
|
|
4088
|
-
holdDelay = 42,
|
|
4089
|
-
focusDelay = 180,
|
|
4090
|
-
scrollIfNeeded = true,
|
|
4091
|
-
throwOnMissing = true,
|
|
4092
|
-
keyboardOptions = {}
|
|
4093
|
-
} = pressOptions || {};
|
|
4094
|
-
const targetDesc = hasTarget ? describeTarget(targetOrKey) : "current focus";
|
|
4095
|
-
logger5.start("humanPress", `key=${key}, target=${targetDesc}`);
|
|
4096
|
-
try {
|
|
4097
|
-
if (hasTarget) {
|
|
4098
|
-
await MobileHumanize.humanClick(page, targetOrKey, {
|
|
4099
|
-
reactionDelay: focusDelay,
|
|
4100
|
-
scrollIfNeeded,
|
|
4101
|
-
throwOnMissing
|
|
4102
|
-
});
|
|
4103
|
-
}
|
|
4104
|
-
await waitJitter(reactionDelay, 0.45);
|
|
4105
|
-
await page.keyboard.press(key, {
|
|
4106
|
-
...keyboardOptions,
|
|
4107
|
-
delay: jitterMs(holdDelay, 0.5)
|
|
4108
|
-
});
|
|
4109
|
-
logger5.success("humanPress");
|
|
4110
|
-
return true;
|
|
4111
|
-
} catch (error) {
|
|
4112
|
-
logger5.fail("humanPress", error);
|
|
4113
|
-
throw error;
|
|
3861
|
+
if (typeof el.click === "function") {
|
|
3862
|
+
el.click();
|
|
3863
|
+
return { activated: true, method: "dom-click", tag: el.tagName || "" };
|
|
4114
3864
|
}
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
3865
|
+
if (typeof el.dispatchEvent === "function") {
|
|
3866
|
+
el.dispatchEvent(new MouseEvent("click", {
|
|
3867
|
+
bubbles: true,
|
|
3868
|
+
cancelable: true,
|
|
3869
|
+
view: window
|
|
3870
|
+
}));
|
|
3871
|
+
return { activated: true, method: "dispatch-click", tag: el.tagName || "" };
|
|
3872
|
+
}
|
|
3873
|
+
return {
|
|
3874
|
+
activated: Boolean(editable),
|
|
3875
|
+
method: editable ? "focus" : "none",
|
|
3876
|
+
tag: el?.tagName || ""
|
|
4126
3877
|
};
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
if (
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
3878
|
+
}, {
|
|
3879
|
+
innerOptions: options || {}
|
|
3880
|
+
});
|
|
3881
|
+
};
|
|
3882
|
+
var getScrollableRect = async (element) => {
|
|
3883
|
+
return element.evaluate((el) => {
|
|
3884
|
+
const isScrollable = (node) => {
|
|
3885
|
+
const style = window.getComputedStyle(node);
|
|
3886
|
+
if (!style) return false;
|
|
3887
|
+
const overflowY = style.overflowY;
|
|
3888
|
+
if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
|
|
3889
|
+
return node.scrollHeight > node.clientHeight + 1;
|
|
3890
|
+
};
|
|
3891
|
+
let current = el;
|
|
3892
|
+
while (current && current !== document.body) {
|
|
3893
|
+
if (isScrollable(current)) {
|
|
3894
|
+
const rect = current.getBoundingClientRect();
|
|
3895
|
+
if (rect && rect.width > 0 && rect.height > 0) {
|
|
3896
|
+
return {
|
|
3897
|
+
x: rect.x,
|
|
3898
|
+
y: rect.y,
|
|
3899
|
+
width: rect.width,
|
|
3900
|
+
height: rect.height
|
|
3901
|
+
};
|
|
3902
|
+
}
|
|
4140
3903
|
}
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
}
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
const
|
|
4150
|
-
if (
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
3904
|
+
current = current.parentElement;
|
|
3905
|
+
}
|
|
3906
|
+
return null;
|
|
3907
|
+
});
|
|
3908
|
+
};
|
|
3909
|
+
var scrollScrollableAncestor = async (element, deltaY) => {
|
|
3910
|
+
return element.evaluate((el, amount) => {
|
|
3911
|
+
const isScrollable = (node) => {
|
|
3912
|
+
const style = window.getComputedStyle(node);
|
|
3913
|
+
if (!style) return false;
|
|
3914
|
+
const overflowY = style.overflowY;
|
|
3915
|
+
if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
|
|
3916
|
+
return node.scrollHeight > node.clientHeight + 1;
|
|
3917
|
+
};
|
|
3918
|
+
let current = el;
|
|
3919
|
+
while (current && current !== document.body) {
|
|
3920
|
+
if (isScrollable(current)) {
|
|
3921
|
+
const beforeTop2 = current.scrollTop;
|
|
3922
|
+
current.scrollTop = beforeTop2 + amount;
|
|
3923
|
+
return {
|
|
3924
|
+
scroller: true,
|
|
3925
|
+
moved: current.scrollTop !== beforeTop2,
|
|
3926
|
+
scrollTop: current.scrollTop
|
|
3927
|
+
};
|
|
4162
3928
|
}
|
|
3929
|
+
current = current.parentElement;
|
|
4163
3930
|
}
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
3931
|
+
const beforeTop = window.scrollY;
|
|
3932
|
+
window.scrollBy(0, amount);
|
|
3933
|
+
return {
|
|
3934
|
+
scroller: null,
|
|
3935
|
+
moved: window.scrollY !== beforeTop,
|
|
3936
|
+
scrollTop: window.scrollY
|
|
3937
|
+
};
|
|
3938
|
+
}, deltaY);
|
|
3939
|
+
};
|
|
3940
|
+
var scrollAwayFromObstruction = async (element, status) => {
|
|
3941
|
+
return element.evaluate((el, innerStatus) => {
|
|
3942
|
+
const isScrollable = (node) => {
|
|
3943
|
+
const style = window.getComputedStyle(node);
|
|
3944
|
+
if (!style) return false;
|
|
3945
|
+
const overflowY = style.overflowY;
|
|
3946
|
+
if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
|
|
3947
|
+
return node.scrollHeight > node.clientHeight + 1;
|
|
3948
|
+
};
|
|
3949
|
+
let scroller = el;
|
|
3950
|
+
while (scroller && scroller !== document.body) {
|
|
3951
|
+
if (isScrollable(scroller)) break;
|
|
3952
|
+
scroller = scroller.parentElement;
|
|
3953
|
+
}
|
|
3954
|
+
if (!scroller || scroller === document.body) {
|
|
3955
|
+
return { moved: false, scrollTop: 0, deltaY: 0 };
|
|
3956
|
+
}
|
|
3957
|
+
const rect = el.getBoundingClientRect();
|
|
3958
|
+
const scrollerRect = scroller.getBoundingClientRect();
|
|
3959
|
+
const obstruction = innerStatus?.obstruction || {};
|
|
3960
|
+
const obstructionTop = Number(obstruction.top);
|
|
3961
|
+
const obstructionBottom = Number(obstruction.bottom);
|
|
3962
|
+
const obstructionMiddle = Number.isFinite(obstructionTop) && Number.isFinite(obstructionBottom) ? (obstructionTop + obstructionBottom) / 2 : scrollerRect.top + scrollerRect.height / 2;
|
|
3963
|
+
const scrollerMiddle = scrollerRect.top + scrollerRect.height / 2;
|
|
3964
|
+
const padding = 18;
|
|
3965
|
+
let deltaY = 0;
|
|
3966
|
+
if (Number.isFinite(obstructionTop) && rect.bottom > obstructionTop && obstructionTop >= scrollerRect.top && obstructionTop <= scrollerRect.bottom && obstructionMiddle >= scrollerMiddle) {
|
|
3967
|
+
deltaY = rect.bottom - obstructionTop + padding;
|
|
3968
|
+
} else if (Number.isFinite(obstructionBottom) && rect.top < obstructionBottom && obstructionBottom >= scrollerRect.top && obstructionBottom <= scrollerRect.bottom && obstructionMiddle < scrollerMiddle) {
|
|
3969
|
+
deltaY = rect.top - obstructionBottom - padding;
|
|
3970
|
+
} else {
|
|
3971
|
+
const fallbackDistance = Math.max(48, Math.min(180, scrollerRect.height * 0.45));
|
|
3972
|
+
deltaY = obstructionMiddle >= scrollerMiddle ? fallbackDistance : -fallbackDistance;
|
|
4175
3973
|
}
|
|
4176
|
-
|
|
3974
|
+
const beforeTop = scroller.scrollTop;
|
|
3975
|
+
scroller.scrollTop = beforeTop + deltaY;
|
|
3976
|
+
return {
|
|
3977
|
+
moved: Math.abs(scroller.scrollTop - beforeTop) > 2,
|
|
3978
|
+
scrollTop: scroller.scrollTop,
|
|
3979
|
+
deltaY
|
|
3980
|
+
};
|
|
3981
|
+
}, status);
|
|
4177
3982
|
};
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
3983
|
+
var getElementViewportSnapshot = async (element) => {
|
|
3984
|
+
return element.evaluate((el) => {
|
|
3985
|
+
const rect = el.getBoundingClientRect();
|
|
3986
|
+
return {
|
|
3987
|
+
top: rect.top,
|
|
3988
|
+
bottom: rect.bottom,
|
|
3989
|
+
left: rect.left,
|
|
3990
|
+
right: rect.right,
|
|
3991
|
+
width: rect.width,
|
|
3992
|
+
height: rect.height,
|
|
3993
|
+
scrollX: window.scrollX,
|
|
3994
|
+
scrollY: window.scrollY
|
|
3995
|
+
};
|
|
3996
|
+
});
|
|
3997
|
+
};
|
|
3998
|
+
var isTargetImmobileAfterScroll = (before, after) => {
|
|
3999
|
+
if (!before || !after) return false;
|
|
4000
|
+
const rectDeltaY = Number(after.top || 0) - Number(before.top || 0);
|
|
4001
|
+
const rectDeltaX = Number(after.left || 0) - Number(before.left || 0);
|
|
4002
|
+
const scrollDeltaY = Number(after.scrollY || 0) - Number(before.scrollY || 0);
|
|
4003
|
+
const scrollDeltaX = Number(after.scrollX || 0) - Number(before.scrollX || 0);
|
|
4004
|
+
const rectMoved = Math.abs(rectDeltaY) > 3 || Math.abs(rectDeltaX) > 3;
|
|
4005
|
+
const pageMoved = Math.abs(scrollDeltaY) > 3 || Math.abs(scrollDeltaX) > 3;
|
|
4006
|
+
if (!rectMoved && !pageMoved) return true;
|
|
4007
|
+
if (pageMoved && !rectMoved) return true;
|
|
4008
|
+
if (Math.abs(scrollDeltaY) > 12 && Math.abs(rectDeltaY) < Math.min(12, Math.abs(scrollDeltaY) * 0.2)) {
|
|
4009
|
+
return true;
|
|
4187
4010
|
}
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4011
|
+
return false;
|
|
4012
|
+
};
|
|
4013
|
+
var restoreWindowFromSnapshot = async (page, before, after) => {
|
|
4014
|
+
if (!before || !after) return;
|
|
4015
|
+
if (Math.abs(Number(after.scrollX || 0) - Number(before.scrollX || 0)) <= 2 && Math.abs(Number(after.scrollY || 0) - Number(before.scrollY || 0)) <= 2) {
|
|
4016
|
+
return;
|
|
4191
4017
|
}
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4018
|
+
await page.evaluate(
|
|
4019
|
+
(state) => window.scrollTo(state.x, state.y),
|
|
4020
|
+
{ x: Number(before.scrollX || 0), y: Number(before.scrollY || 0) }
|
|
4021
|
+
).catch(() => {
|
|
4022
|
+
});
|
|
4196
4023
|
};
|
|
4197
|
-
var
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
}
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4024
|
+
var dispatchTouchSwipe = async (page, deltaY, options = {}) => {
|
|
4025
|
+
const viewport = resolveViewport(page);
|
|
4026
|
+
const rawRect = options.rect || null;
|
|
4027
|
+
const rect = rawRect ? {
|
|
4028
|
+
x: clamp(rawRect.x, 0, viewport.width),
|
|
4029
|
+
y: clamp(rawRect.y, 0, viewport.height),
|
|
4030
|
+
width: clamp(rawRect.width, 0, viewport.width),
|
|
4031
|
+
height: clamp(rawRect.height, 0, viewport.height)
|
|
4032
|
+
} : null;
|
|
4033
|
+
const area = rect && rect.width > 24 && rect.height > 48 ? {
|
|
4034
|
+
left: rect.x,
|
|
4035
|
+
right: Math.min(viewport.width, rect.x + rect.width),
|
|
4036
|
+
top: rect.y,
|
|
4037
|
+
bottom: Math.min(viewport.height, rect.y + rect.height)
|
|
4038
|
+
} : {
|
|
4039
|
+
left: 0,
|
|
4040
|
+
right: viewport.width,
|
|
4041
|
+
top: 0,
|
|
4042
|
+
bottom: viewport.height
|
|
4043
|
+
};
|
|
4044
|
+
const areaWidth = Math.max(1, area.right - area.left);
|
|
4045
|
+
const areaHeight = Math.max(1, area.bottom - area.top);
|
|
4046
|
+
const maxDistance = rect ? Math.max(60, areaHeight * 0.72) : Math.max(120, viewport.height * 0.72);
|
|
4047
|
+
const distance = clamp(Math.abs(deltaY), Math.min(80, maxDistance), maxDistance);
|
|
4048
|
+
const direction = deltaY >= 0 ? 1 : -1;
|
|
4049
|
+
const startX = clamp(
|
|
4050
|
+
area.left + areaWidth * (0.45 + Math.random() * 0.1),
|
|
4051
|
+
24,
|
|
4052
|
+
viewport.width - 24
|
|
4053
|
+
);
|
|
4054
|
+
const startY = direction > 0 ? clamp(area.top + areaHeight * (0.72 + Math.random() * 0.1), area.top + 24, area.bottom - 16) : clamp(area.top + areaHeight * (0.28 + Math.random() * 0.1), area.top + 16, area.bottom - 24);
|
|
4055
|
+
const endY = clamp(startY - direction * distance, Math.max(16, area.top + 12), Math.min(viewport.height - 16, area.bottom - 12));
|
|
4056
|
+
const steps = Math.max(4, Math.round(Number(options.steps || 6)));
|
|
4057
|
+
const durationMs = jitterMs(options.durationMs || 320, 0.35);
|
|
4058
|
+
let client = null;
|
|
4059
|
+
const scrollBefore = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })).catch(() => null);
|
|
4060
|
+
try {
|
|
4061
|
+
if (page.context && typeof page.context().newCDPSession === "function") {
|
|
4062
|
+
client = await page.context().newCDPSession(page);
|
|
4228
4063
|
}
|
|
4229
|
-
|
|
4064
|
+
if (!client) throw new Error("CDP session unavailable");
|
|
4065
|
+
await client.send("Input.synthesizeScrollGesture", {
|
|
4066
|
+
x: startX,
|
|
4067
|
+
y: startY,
|
|
4068
|
+
yDistance: -direction * distance,
|
|
4069
|
+
xOverscroll: (Math.random() - 0.5) * 4,
|
|
4070
|
+
yOverscroll: (Math.random() - 0.5) * 8,
|
|
4071
|
+
speed: 700 + Math.round(Math.random() * 350),
|
|
4072
|
+
gestureSourceType: "touch"
|
|
4230
4073
|
});
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
if (target == null) {
|
|
4235
|
-
await DeviceInput.clickPoint(page, { x: 0, y: 0 }, void 0, { forceMouse: true });
|
|
4236
|
-
return true;
|
|
4237
|
-
}
|
|
4238
|
-
if (isPoint2(target)) {
|
|
4239
|
-
await DeviceInput.clickPoint(page, target, void 0, { forceMouse: true });
|
|
4074
|
+
await waitJitter(160, 0.35);
|
|
4075
|
+
const scrollAfterSynth = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })).catch(() => null);
|
|
4076
|
+
if (scrollBefore && scrollAfterSynth && (Math.abs(scrollAfterSynth.y - scrollBefore.y) > 2 || Math.abs(scrollAfterSynth.x - scrollBefore.x) > 2)) {
|
|
4240
4077
|
return true;
|
|
4241
4078
|
}
|
|
4242
|
-
await
|
|
4243
|
-
|
|
4079
|
+
await client.send("Input.dispatchTouchEvent", {
|
|
4080
|
+
type: "touchStart",
|
|
4081
|
+
touchPoints: [{ x: startX, y: startY, id: 1 }]
|
|
4244
4082
|
});
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
await DeviceInput.move(page, {
|
|
4256
|
-
x: 100 + Math.random() * Math.max(120, viewport.width - 200),
|
|
4257
|
-
y: 100 + Math.random() * Math.max(120, viewport.height - 200)
|
|
4258
|
-
}, void 0, { forceMouse: true });
|
|
4259
|
-
await sleep(300, 0.5);
|
|
4083
|
+
for (let i = 1; i <= steps; i += 1) {
|
|
4084
|
+
const progress = i / steps;
|
|
4085
|
+
const eased = 1 - Math.pow(1 - progress, 2);
|
|
4086
|
+
const x = startX + (Math.random() - 0.5) * 3;
|
|
4087
|
+
const y = startY + (endY - startY) * eased + (Math.random() - 0.5) * 5;
|
|
4088
|
+
await waitJitter(durationMs / steps, 0.25);
|
|
4089
|
+
await client.send("Input.dispatchTouchEvent", {
|
|
4090
|
+
type: "touchMove",
|
|
4091
|
+
touchPoints: [{ x, y, id: 1 }]
|
|
4092
|
+
});
|
|
4260
4093
|
}
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
clickOptions: FORCE_CLICK_OPTIONS
|
|
4265
|
-
});
|
|
4266
|
-
await DeviceInput.keyboardType(page, text, {
|
|
4267
|
-
...options.baseDelay != null ? { delay: Math.max(0, Number(options.baseDelay) || 0) } : {}
|
|
4094
|
+
await client.send("Input.dispatchTouchEvent", {
|
|
4095
|
+
type: "touchEnd",
|
|
4096
|
+
touchPoints: []
|
|
4268
4097
|
});
|
|
4098
|
+
await waitJitter(120, 0.35);
|
|
4099
|
+
const scrollAfterTouch = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })).catch(() => null);
|
|
4100
|
+
if (scrollBefore && scrollAfterTouch && Math.abs(scrollAfterTouch.y - scrollBefore.y) <= 2 && Math.abs(scrollAfterTouch.x - scrollBefore.x) <= 2) {
|
|
4101
|
+
await page.evaluate((amount) => window.scrollBy(0, amount), deltaY);
|
|
4102
|
+
await waitJitter(120, 0.35);
|
|
4103
|
+
}
|
|
4269
4104
|
return true;
|
|
4270
|
-
}
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
await
|
|
4274
|
-
|
|
4275
|
-
|
|
4105
|
+
} catch (error) {
|
|
4106
|
+
logger7.debug(`touch swipe fallback: ${error?.message || error}`);
|
|
4107
|
+
try {
|
|
4108
|
+
await page.evaluate((amount) => window.scrollBy(0, amount), deltaY);
|
|
4109
|
+
await waitJitter(120, 0.35);
|
|
4110
|
+
return true;
|
|
4111
|
+
} catch {
|
|
4112
|
+
await page.mouse.wheel(0, deltaY);
|
|
4113
|
+
await waitJitter(120, 0.35);
|
|
4276
4114
|
return true;
|
|
4277
4115
|
}
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
async humanClear(page, target) {
|
|
4285
|
-
await DeviceInput.fill(page, target, "", { force: true });
|
|
4286
|
-
},
|
|
4287
|
-
async warmUpBrowsing(page, baseDuration = 3500) {
|
|
4288
|
-
await this.simulateGaze(page, Math.min(baseDuration, 900));
|
|
4289
|
-
},
|
|
4290
|
-
async naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
|
|
4291
|
-
const steps = Math.max(1, Number(baseSteps) || 1);
|
|
4292
|
-
const delta = (Number(distance) || 0) / steps * (direction === "down" ? 1 : -1);
|
|
4293
|
-
for (let index = 0; index < steps; index += 1) {
|
|
4294
|
-
await page.mouse.wheel(0, delta);
|
|
4295
|
-
await sleep(60 + index * 20, 0.3);
|
|
4116
|
+
} finally {
|
|
4117
|
+
if (client && typeof client.detach === "function") {
|
|
4118
|
+
try {
|
|
4119
|
+
await client.detach();
|
|
4120
|
+
} catch {
|
|
4121
|
+
}
|
|
4296
4122
|
}
|
|
4297
4123
|
}
|
|
4298
4124
|
};
|
|
4299
|
-
var
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4125
|
+
var tapPoint = async (page, point, options = {}) => {
|
|
4126
|
+
const {
|
|
4127
|
+
timeoutMs = DEFAULT_TAP_TIMEOUT_MS,
|
|
4128
|
+
mouseFallbackTimeoutMs = DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS,
|
|
4129
|
+
allowMouseFallback = true
|
|
4130
|
+
} = options;
|
|
4131
|
+
if (page.touchscreen && typeof page.touchscreen.tap === "function") {
|
|
4132
|
+
try {
|
|
4133
|
+
await withTimeout(
|
|
4134
|
+
() => page.touchscreen.tap(point.x, point.y),
|
|
4135
|
+
timeoutMs,
|
|
4136
|
+
"touchscreen.tap"
|
|
4137
|
+
);
|
|
4138
|
+
return { method: "touchscreen" };
|
|
4139
|
+
} catch (error) {
|
|
4140
|
+
logger7.warn(`tapPoint | touchscreen.tap \u5931\u8D25\u6216\u8D85\u65F6\uFF0C\u5C1D\u8BD5\u9F20\u6807\u515C\u5E95: ${error?.message || error}`);
|
|
4141
|
+
if (!allowMouseFallback) throw error;
|
|
4142
|
+
}
|
|
4314
4143
|
}
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
jitterMs(base, jitterPercent = 0.3) {
|
|
4325
|
-
const jitter = base * jitterPercent * (Math.random() * 2 - 1);
|
|
4326
|
-
return Math.max(10, Math.round(base + jitter));
|
|
4327
|
-
},
|
|
4328
|
-
/**
|
|
4329
|
-
* 初始化页面的 Ghost Cursor(必须在使用其他 cursor 相关方法前调用)
|
|
4330
|
-
*
|
|
4331
|
-
* @param {import('playwright').Page} page
|
|
4332
|
-
* @returns {Promise<void>}
|
|
4333
|
-
*/
|
|
4144
|
+
await withTimeout(
|
|
4145
|
+
() => page.mouse.click(point.x, point.y),
|
|
4146
|
+
mouseFallbackTimeoutMs,
|
|
4147
|
+
"mouse.click fallback"
|
|
4148
|
+
);
|
|
4149
|
+
return { method: "mouse" };
|
|
4150
|
+
};
|
|
4151
|
+
var MobileHumanize = {
|
|
4152
|
+
jitterMs,
|
|
4334
4153
|
async initializeCursor(page) {
|
|
4335
|
-
if (
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
}
|
|
4339
|
-
logger6.start("initializeCursor", "creating cursor");
|
|
4340
|
-
const cursor = await createCursor(page);
|
|
4341
|
-
$CursorWeakMap.set(page, cursor);
|
|
4342
|
-
logger6.success("initializeCursor", "cursor initialized");
|
|
4154
|
+
if (initializedPages.has(page)) return;
|
|
4155
|
+
initializedPages.add(page);
|
|
4156
|
+
logger7.debug("initializeCursor: mobile mode uses touch gestures, cursor init skipped");
|
|
4343
4157
|
},
|
|
4344
|
-
/**
|
|
4345
|
-
* 人类化鼠标移动 - 使用 ghost-cursor 移动到指定位置或元素
|
|
4346
|
-
*
|
|
4347
|
-
* @param {import('playwright').Page} page
|
|
4348
|
-
* @param {string|{x: number, y: number}|import('playwright').ElementHandle} target - CSS选择器、坐标对象或元素句柄
|
|
4349
|
-
*/
|
|
4350
4158
|
async humanMove(page, target) {
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
if (
|
|
4355
|
-
|
|
4356
|
-
if (!element) {
|
|
4357
|
-
logger6.warn(`humanMove: \u5143\u7D20\u4E0D\u5B58\u5728 ${target}`);
|
|
4358
|
-
return false;
|
|
4359
|
-
}
|
|
4360
|
-
const box = await element.boundingBox();
|
|
4361
|
-
if (!box) {
|
|
4362
|
-
logger6.warn(`humanMove: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E ${target}`);
|
|
4363
|
-
return false;
|
|
4364
|
-
}
|
|
4365
|
-
const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
|
|
4366
|
-
const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
|
|
4367
|
-
await cursor.actions.move({ x, y });
|
|
4368
|
-
} else if (target && typeof target.x === "number" && typeof target.y === "number") {
|
|
4369
|
-
await cursor.actions.move(target);
|
|
4370
|
-
} else if (target && typeof target.boundingBox === "function") {
|
|
4371
|
-
const box = await target.boundingBox();
|
|
4372
|
-
if (box) {
|
|
4373
|
-
const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
|
|
4374
|
-
const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
|
|
4375
|
-
await cursor.actions.move({ x, y });
|
|
4376
|
-
}
|
|
4159
|
+
logger7.debug(`humanMove: mobile no-op target=${typeof target === "string" ? target : "element/coords"}`);
|
|
4160
|
+
if (typeof target === "string" || target && typeof target.boundingBox === "function") {
|
|
4161
|
+
const element = await resolveElement(page, target, { throwOnMissing: false });
|
|
4162
|
+
if (!element) {
|
|
4163
|
+
return false;
|
|
4377
4164
|
}
|
|
4378
|
-
logger6.success("humanMove");
|
|
4379
|
-
return true;
|
|
4380
|
-
} catch (error) {
|
|
4381
|
-
logger6.fail("humanMove", error);
|
|
4382
|
-
throw error;
|
|
4383
4165
|
}
|
|
4166
|
+
await waitJitter(80, 0.4);
|
|
4167
|
+
return true;
|
|
4384
4168
|
},
|
|
4385
|
-
/**
|
|
4386
|
-
* 渐进式滚动到元素可见(仅处理 Y 轴滚动)
|
|
4387
|
-
* 返回 restore 方法,用于将滚动容器恢复到原位置
|
|
4388
|
-
*
|
|
4389
|
-
* @param {import('playwright').Page} page
|
|
4390
|
-
* @param {string|import('playwright').ElementHandle} target - CSS 选择器或元素句柄
|
|
4391
|
-
* @param {Object} [options]
|
|
4392
|
-
* @param {number} [options.maxSteps=20] - 最大滚动步数
|
|
4393
|
-
* @param {number} [options.minStep=260] - 单次滚动最小步长
|
|
4394
|
-
* @param {number} [options.maxStep=800] - 单次滚动最大步长
|
|
4395
|
-
* @param {number} [options.maxDurationMs] - 最长耗时上限 (默认随 maxSteps 估算)
|
|
4396
|
-
*/
|
|
4397
4169
|
async humanScroll(page, target, options = {}) {
|
|
4398
4170
|
const {
|
|
4399
4171
|
maxSteps = 20,
|
|
4400
|
-
minStep =
|
|
4401
|
-
maxStep =
|
|
4402
|
-
maxDurationMs = maxSteps *
|
|
4172
|
+
minStep = 180,
|
|
4173
|
+
maxStep = 520,
|
|
4174
|
+
maxDurationMs = maxSteps * 280 + 1200,
|
|
4175
|
+
throwOnMissing = false
|
|
4403
4176
|
} = options;
|
|
4404
|
-
const targetDesc =
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
if (
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
logger6.warn(`humanScroll | \u5143\u7D20\u672A\u627E\u5230: ${target}`);
|
|
4411
|
-
return { element: null, didScroll: false };
|
|
4412
|
-
}
|
|
4413
|
-
} else {
|
|
4414
|
-
element = target;
|
|
4177
|
+
const targetDesc = describeTarget(target);
|
|
4178
|
+
logger7.start("humanScroll", `target=${targetDesc}`);
|
|
4179
|
+
const element = await resolveElement(page, target, { throwOnMissing });
|
|
4180
|
+
if (!element) {
|
|
4181
|
+
logger7.warn(`humanScroll | \u5143\u7D20\u672A\u627E\u5230: ${targetDesc}`);
|
|
4182
|
+
return { element: null, didScroll: false, restore: null };
|
|
4415
4183
|
}
|
|
4416
|
-
const
|
|
4184
|
+
const startTime = Date.now();
|
|
4417
4185
|
let didScroll = false;
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
if (
|
|
4422
|
-
|
|
4423
|
-
}
|
|
4424
|
-
|
|
4425
|
-
const cy = rect.top + rect.height / 2;
|
|
4426
|
-
const viewH = window.innerHeight;
|
|
4427
|
-
const viewW = window.innerWidth;
|
|
4428
|
-
let isFixed = false;
|
|
4429
|
-
for (let node = el; node && node !== document.body; node = node.parentElement) {
|
|
4430
|
-
const style = window.getComputedStyle(node);
|
|
4431
|
-
if (style && style.position === "fixed") {
|
|
4432
|
-
isFixed = true;
|
|
4433
|
-
break;
|
|
4434
|
-
}
|
|
4186
|
+
for (let i = 0; i < maxSteps; i += 1) {
|
|
4187
|
+
const status = await checkElementVisibility(element);
|
|
4188
|
+
if (status.code === "VISIBLE") {
|
|
4189
|
+
if (status.isFixed) {
|
|
4190
|
+
logger7.info("humanScroll | fixed/sticky \u5BB9\u5668\u5185\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
|
|
4191
|
+
} else {
|
|
4192
|
+
logger7.debug("humanScroll | \u5143\u7D20\u53EF\u89C1\u4E14\u65E0\u906E\u6321");
|
|
4435
4193
|
}
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4194
|
+
logger7.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
|
|
4195
|
+
return { element, didScroll, restore: null };
|
|
4196
|
+
}
|
|
4197
|
+
if (status.code === "ZERO_DIMENSIONS" || status.code === "NOT_INTERACTABLE") {
|
|
4198
|
+
logger7.warn(`humanScroll | \u5143\u7D20\u4E0D\u53EF\u6EDA\u52A8\u81F3\u53EF\u70B9\u51FB\u72B6\u6001: ${status.reason || status.code}`);
|
|
4199
|
+
return { element, didScroll, restore: null };
|
|
4200
|
+
}
|
|
4201
|
+
const scrollRect = await getScrollableRect(element);
|
|
4202
|
+
if (!scrollRect && status.isFixed && status.code === "OUT_OF_VIEWPORT") {
|
|
4203
|
+
logger7.warn(`humanScroll | fixed/sticky \u76EE\u6807\u4E0D\u5728\u89C6\u53E3\u5185\uFF0C\u9875\u9762\u6EDA\u52A8\u65E0\u6CD5\u6539\u53D8\u5176\u4F4D\u7F6E (direction=${status.direction || "unknown"})`);
|
|
4204
|
+
return { element, didScroll, restore: null, unscrollable: true };
|
|
4205
|
+
}
|
|
4206
|
+
if (!scrollRect && status.isFixed && status.code === "OBSTRUCTED") {
|
|
4207
|
+
logger7.warn(`humanScroll | fixed/sticky \u76EE\u6807\u88AB\u906E\u6321\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u89E3\u9664 (${status.obstruction?.tag || "unknown"})`);
|
|
4208
|
+
return { element, didScroll, restore: null, unscrollable: true };
|
|
4209
|
+
}
|
|
4210
|
+
if (scrollRect && status.code === "OBSTRUCTED" && status.obstruction?.isFixed) {
|
|
4211
|
+
const moved = await scrollAwayFromObstruction(element, status);
|
|
4212
|
+
if (moved.moved) {
|
|
4213
|
+
logger7.debug(`humanScroll | sticky/fixed \u906E\u6321\u8865\u507F\u6EDA\u52A8 top=${Math.round(moved.scrollTop || 0)}`);
|
|
4214
|
+
await waitJitter(90, 0.3);
|
|
4215
|
+
didScroll = true;
|
|
4216
|
+
continue;
|
|
4439
4217
|
}
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4218
|
+
}
|
|
4219
|
+
if (Date.now() - startTime > maxDurationMs) {
|
|
4220
|
+
logger7.warn(`humanScroll | mobile timeout (${maxDurationMs}ms, status=${status.code}, direction=${status.direction || "unknown"}, fixed=${Boolean(status.isFixed)})`);
|
|
4221
|
+
return { element, didScroll, restore: null };
|
|
4222
|
+
}
|
|
4223
|
+
const stepMin = scrollRect ? Math.min(minStep, Math.max(60, scrollRect.height * 0.4)) : minStep;
|
|
4224
|
+
const stepMax = scrollRect ? Math.min(maxStep, Math.max(stepMin + 40, scrollRect.height * 0.8)) : maxStep;
|
|
4225
|
+
logger7.debug(`humanScroll | \u6B65\u9AA4 ${i + 1}/${maxSteps}: ${status.reason || status.code} ${status.direction ? `(${status.direction})` : ""}`);
|
|
4226
|
+
const distance = stepMin + Math.random() * Math.max(1, stepMax - stepMin);
|
|
4227
|
+
let deltaY = status.direction === "up" ? -distance : distance;
|
|
4228
|
+
if (status.code === "OBSTRUCTED") {
|
|
4229
|
+
if (status.obstruction?.isFixed && status.obstruction.top != null) {
|
|
4230
|
+
const obstructionMiddle = (Number(status.obstruction.top || 0) + Number(status.obstruction.bottom || status.obstruction.top || 0)) / 2;
|
|
4231
|
+
const visibleMiddle = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
|
|
4232
|
+
deltaY = obstructionMiddle < visibleMiddle ? -distance : distance;
|
|
4233
|
+
} else {
|
|
4234
|
+
const halfY = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
|
|
4235
|
+
deltaY = status.cy > halfY ? distance : -distance;
|
|
4455
4236
|
}
|
|
4456
|
-
|
|
4457
|
-
});
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
return await element.evaluate((el) => {
|
|
4237
|
+
}
|
|
4238
|
+
const beforeWindowState = scrollRect ? await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })) : null;
|
|
4239
|
+
const beforeElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
|
|
4240
|
+
const beforeState = scrollRect ? await element.evaluate((el) => {
|
|
4461
4241
|
const isScrollable = (node) => {
|
|
4462
4242
|
const style = window.getComputedStyle(node);
|
|
4463
4243
|
if (!style) return false;
|
|
@@ -4468,427 +4248,386 @@ var Humanize = {
|
|
|
4468
4248
|
let current = el;
|
|
4469
4249
|
while (current && current !== document.body) {
|
|
4470
4250
|
if (isScrollable(current)) {
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4251
|
+
return {
|
|
4252
|
+
kind: "element",
|
|
4253
|
+
top: current.scrollTop,
|
|
4254
|
+
left: current.scrollLeft
|
|
4255
|
+
};
|
|
4475
4256
|
}
|
|
4476
4257
|
current = current.parentElement;
|
|
4477
4258
|
}
|
|
4478
|
-
return
|
|
4479
|
-
});
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
return { element, didScroll };
|
|
4487
|
-
}
|
|
4488
|
-
const status = await checkVisibility();
|
|
4489
|
-
if (status.code === "VISIBLE") {
|
|
4490
|
-
if (status.isFixed) {
|
|
4491
|
-
logger6.info("humanScroll | fixed \u5BB9\u5668\u5185\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
|
|
4492
|
-
} else {
|
|
4493
|
-
logger6.debug("humanScroll | \u5143\u7D20\u53EF\u89C1\u4E14\u65E0\u906E\u6321");
|
|
4494
|
-
}
|
|
4495
|
-
logger6.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
|
|
4496
|
-
return { element, didScroll };
|
|
4497
|
-
}
|
|
4498
|
-
logger6.debug(`humanScroll | \u6B65\u9AA4 ${i + 1}/${maxSteps}: ${status.reason} ${status.direction ? `(${status.direction})` : ""}`);
|
|
4499
|
-
if (status.code === "OBSTRUCTED" && status.obstruction) {
|
|
4500
|
-
logger6.debug(`humanScroll | \u88AB\u4EE5\u4E0B\u5143\u7D20\u906E\u6321 <${status.obstruction.tag} id="${status.obstruction.id}">`);
|
|
4259
|
+
return { kind: "window", top: window.scrollY, left: window.scrollX };
|
|
4260
|
+
}) : null;
|
|
4261
|
+
await dispatchTouchSwipe(page, deltaY, { rect: scrollRect });
|
|
4262
|
+
if (scrollRect && beforeWindowState) {
|
|
4263
|
+
const afterWindowState = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY }));
|
|
4264
|
+
if (Math.abs(afterWindowState.x - beforeWindowState.x) > 2 || Math.abs(afterWindowState.y - beforeWindowState.y) > 2) {
|
|
4265
|
+
await page.evaluate((state) => window.scrollTo(state.x, state.y), beforeWindowState);
|
|
4266
|
+
logger7.debug(`humanScroll | \u7A97\u53E3\u6EDA\u52A8\u56DE\u6536 from=${Math.round(afterWindowState.y)} to=${Math.round(beforeWindowState.y)}`);
|
|
4501
4267
|
}
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4268
|
+
}
|
|
4269
|
+
let afterElementSnapshot = null;
|
|
4270
|
+
const readAfterElementSnapshot = async () => {
|
|
4271
|
+
if (!afterElementSnapshot) {
|
|
4272
|
+
afterElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
|
|
4506
4273
|
}
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
} else {
|
|
4516
|
-
deltaY = 100;
|
|
4517
|
-
}
|
|
4518
|
-
} else if (status.code === "OBSTRUCTED") {
|
|
4519
|
-
const halfY = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
|
|
4520
|
-
const isBottomHalf = status.cy > halfY;
|
|
4521
|
-
const direction = isBottomHalf ? 1 : -1;
|
|
4522
|
-
deltaY = direction * (stepMin + Math.random() * 50);
|
|
4274
|
+
return afterElementSnapshot;
|
|
4275
|
+
};
|
|
4276
|
+
if (!scrollRect && beforeElementSnapshot) {
|
|
4277
|
+
const afterSnapshot = await readAfterElementSnapshot();
|
|
4278
|
+
if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
|
|
4279
|
+
await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
|
|
4280
|
+
logger7.warn(`humanScroll | \u76EE\u6807\u4E0D\u968F\u9875\u9762\u6EDA\u52A8\u79FB\u52A8\uFF0C\u9875\u9762\u6EDA\u52A8\u65E0\u6CD5\u6539\u53D8\u5176\u4F4D\u7F6E (status=${status.code}, direction=${status.direction || "unknown"})`);
|
|
4281
|
+
return { element, didScroll, restore: null, unscrollable: true };
|
|
4523
4282
|
}
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
const
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4283
|
+
}
|
|
4284
|
+
if (scrollRect && beforeState) {
|
|
4285
|
+
const afterState = await element.evaluate((el) => {
|
|
4286
|
+
const isScrollable = (node) => {
|
|
4287
|
+
const style = window.getComputedStyle(node);
|
|
4288
|
+
if (!style) return false;
|
|
4289
|
+
const overflowY = style.overflowY;
|
|
4290
|
+
if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
|
|
4291
|
+
return node.scrollHeight > node.clientHeight + 1;
|
|
4292
|
+
};
|
|
4293
|
+
let current = el;
|
|
4294
|
+
while (current && current !== document.body) {
|
|
4295
|
+
if (isScrollable(current)) {
|
|
4296
|
+
return {
|
|
4297
|
+
kind: "element",
|
|
4298
|
+
top: current.scrollTop,
|
|
4299
|
+
left: current.scrollLeft
|
|
4300
|
+
};
|
|
4301
|
+
}
|
|
4302
|
+
current = current.parentElement;
|
|
4303
|
+
}
|
|
4304
|
+
return { kind: "window", top: window.scrollY, left: window.scrollX };
|
|
4305
|
+
});
|
|
4306
|
+
const topDelta = Number(afterState.top || 0) - Number(beforeState.top || 0);
|
|
4307
|
+
const leftDelta = Number(afterState.left || 0) - Number(beforeState.left || 0);
|
|
4308
|
+
const expectedDelta = Number(deltaY || 0);
|
|
4309
|
+
const moved = beforeState.kind !== afterState.kind || Math.abs(topDelta) > 2 || Math.abs(leftDelta) > 2;
|
|
4310
|
+
if (!moved) {
|
|
4311
|
+
const fallback = await scrollScrollableAncestor(element, deltaY);
|
|
4312
|
+
logger7.debug(`humanScroll | \u5BB9\u5668\u89E6\u6478\u65E0\u6548\uFF0C\u76F4\u63A5\u6EDA\u52A8 fallback=${fallback.scroller ? "ancestor" : "window"} top=${Math.round(fallback.scrollTop || 0)}`);
|
|
4313
|
+
} else if (beforeState.kind === afterState.kind && Math.abs(expectedDelta) > 24 && Math.sign(topDelta || expectedDelta) === Math.sign(expectedDelta) && Math.abs(topDelta) < Math.min(Math.abs(expectedDelta) * 0.45, 96)) {
|
|
4314
|
+
const residualDelta = expectedDelta - topDelta;
|
|
4315
|
+
if (Math.sign(residualDelta) === Math.sign(expectedDelta) && Math.abs(residualDelta) > 24) {
|
|
4316
|
+
const fallback = await scrollScrollableAncestor(element, residualDelta);
|
|
4317
|
+
logger7.debug(`humanScroll | \u5BB9\u5668\u89E6\u6478\u8DDD\u79BB\u4E0D\u8DB3\uFF0C\u8865\u507F\u6EDA\u52A8 fallback=${fallback.scroller ? "ancestor" : "window"} top=${Math.round(fallback.scrollTop || 0)}`);
|
|
4534
4318
|
}
|
|
4535
4319
|
}
|
|
4536
|
-
await page.mouse.wheel(0, deltaY);
|
|
4537
|
-
didScroll = true;
|
|
4538
|
-
await delay4(this.jitterMs(20 + Math.random() * 40, 0.2));
|
|
4539
4320
|
}
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4321
|
+
if (scrollRect && beforeElementSnapshot) {
|
|
4322
|
+
const afterSnapshot = await getElementViewportSnapshot(element).catch(() => null);
|
|
4323
|
+
if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
|
|
4324
|
+
await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
|
|
4325
|
+
logger7.warn(`humanScroll | \u76EE\u6807\u4E0D\u968F\u6EDA\u52A8\u5BB9\u5668\u79FB\u52A8\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u6539\u53D8\u5176\u4F4D\u7F6E (status=${status.code}, direction=${status.direction || "unknown"})`);
|
|
4326
|
+
return { element, didScroll, restore: null, unscrollable: true };
|
|
4327
|
+
}
|
|
4328
|
+
}
|
|
4329
|
+
didScroll = true;
|
|
4330
|
+
}
|
|
4331
|
+
try {
|
|
4332
|
+
await element.scrollIntoViewIfNeeded?.();
|
|
4333
|
+
await waitJitter(80, 0.3);
|
|
4334
|
+
const finalStatus = await checkElementVisibility(element);
|
|
4335
|
+
if (finalStatus.code === "VISIBLE") {
|
|
4336
|
+
logger7.info("humanScroll | \u539F\u751F scrollIntoViewIfNeeded \u515C\u5E95\u6210\u529F");
|
|
4337
|
+
logger7.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
|
|
4338
|
+
return { element, didScroll: true, restore: null };
|
|
4339
|
+
}
|
|
4340
|
+
} catch (fallbackError) {
|
|
4341
|
+
logger7.debug(`humanScroll | native fallback failed: ${fallbackError?.message || fallbackError}`);
|
|
4545
4342
|
}
|
|
4343
|
+
logger7.warn(`humanScroll | \u5728 ${maxSteps} \u6B65\u540E\u65E0\u6CD5\u786E\u4FDD\u53EF\u89C1\u6027`);
|
|
4344
|
+
return { element, didScroll, restore: null };
|
|
4546
4345
|
},
|
|
4547
|
-
/**
|
|
4548
|
-
* 人类化点击 - 使用 ghost-cursor 模拟人类鼠标移动轨迹并点击
|
|
4549
|
-
*
|
|
4550
|
-
* @param {import('playwright').Page} page
|
|
4551
|
-
* @param {string|import('playwright').ElementHandle} [target] - CSS 选择器或元素句柄。如果为空,则点击当前鼠标位置
|
|
4552
|
-
* @param {Object} [options]
|
|
4553
|
-
* @param {number} [options.reactionDelay=250] - 反应延迟基础值 (ms),实际 ±30% 抖动
|
|
4554
|
-
* @param {boolean} [options.throwOnMissing=true] - 元素不存在时是否抛出错误
|
|
4555
|
-
* @param {boolean} [options.scrollIfNeeded=true] - 元素不在视口时是否自动滚动
|
|
4556
|
-
*/
|
|
4557
4346
|
async humanClick(page, target, options = {}) {
|
|
4558
|
-
const
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
logger6.warn(`humanClick: \u6062\u590D\u6EDA\u52A8\u4F4D\u7F6E\u5931\u8D25: ${restoreError.message}`);
|
|
4571
|
-
}
|
|
4572
|
-
};
|
|
4347
|
+
const {
|
|
4348
|
+
reactionDelay = 220,
|
|
4349
|
+
throwOnMissing = true,
|
|
4350
|
+
scrollIfNeeded = true,
|
|
4351
|
+
tapTimeoutMs = DEFAULT_TAP_TIMEOUT_MS,
|
|
4352
|
+
mouseFallbackTimeoutMs = DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS,
|
|
4353
|
+
activateFallbackTimeoutMs = DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS,
|
|
4354
|
+
fallbackDomClick = true,
|
|
4355
|
+
fallbackDomClickOnTapError = true
|
|
4356
|
+
} = options;
|
|
4357
|
+
const targetDesc = describeTarget(target);
|
|
4358
|
+
logger7.start("humanClick", `target=${targetDesc}`);
|
|
4573
4359
|
try {
|
|
4574
4360
|
if (target == null) {
|
|
4575
|
-
|
|
4576
|
-
await
|
|
4577
|
-
|
|
4361
|
+
const viewport = resolveViewport(page);
|
|
4362
|
+
await waitJitter(reactionDelay, 0.45);
|
|
4363
|
+
await tapPoint(page, {
|
|
4364
|
+
x: viewport.width * (0.45 + Math.random() * 0.1),
|
|
4365
|
+
y: viewport.height * (0.48 + Math.random() * 0.12)
|
|
4366
|
+
}, {
|
|
4367
|
+
timeoutMs: tapTimeoutMs,
|
|
4368
|
+
mouseFallbackTimeoutMs
|
|
4369
|
+
});
|
|
4370
|
+
logger7.success("humanClick", "Tapped current position");
|
|
4578
4371
|
return true;
|
|
4579
4372
|
}
|
|
4580
|
-
|
|
4581
|
-
if (
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4373
|
+
const element = await resolveElement(page, target, { throwOnMissing });
|
|
4374
|
+
if (!element) {
|
|
4375
|
+
logger7.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${targetDesc}`);
|
|
4376
|
+
return false;
|
|
4377
|
+
}
|
|
4378
|
+
const scrollResult = scrollIfNeeded ? await MobileHumanize.humanScroll(page, element, { throwOnMissing }) : null;
|
|
4379
|
+
const status = await checkElementVisibility(element).catch(() => null);
|
|
4380
|
+
if (status && status.code !== "VISIBLE") {
|
|
4381
|
+
if (fallbackDomClick && (status.code === "OUT_OF_VIEWPORT" || status.code === "OBSTRUCTED") && (status.isFixed || scrollResult?.unscrollable)) {
|
|
4382
|
+
let fallback = await withTimeout(
|
|
4383
|
+
() => activateElementFallback(element, null, {
|
|
4384
|
+
editableOnly: true
|
|
4385
|
+
}),
|
|
4386
|
+
activateFallbackTimeoutMs,
|
|
4387
|
+
"focus fallback"
|
|
4388
|
+
).catch(() => null);
|
|
4389
|
+
if (!fallback?.activated) {
|
|
4390
|
+
fallback = await withTimeout(
|
|
4391
|
+
() => activateElementFallback(element, null, {
|
|
4392
|
+
editableOnly: false
|
|
4393
|
+
}),
|
|
4394
|
+
activateFallbackTimeoutMs,
|
|
4395
|
+
"activation fallback"
|
|
4396
|
+
).catch(() => null);
|
|
4397
|
+
}
|
|
4398
|
+
if (fallback?.activated) {
|
|
4399
|
+
logger7.warn(`humanClick: \u4E0D\u53EF\u6EDA\u52A8\u76EE\u6807\u4E0D\u53EF\u7269\u7406\u70B9\u51FB\uFF0C\u5DF2\u7528 ${fallback.method} \u6FC0\u6D3B`);
|
|
4400
|
+
return true;
|
|
4586
4401
|
}
|
|
4587
|
-
logger6.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${target}`);
|
|
4588
|
-
return false;
|
|
4589
4402
|
}
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
const { restore: restoreFn, didScroll } = await this.humanScroll(page, element);
|
|
4595
|
-
restoreOnce.do = didScroll && restore ? restoreFn : null;
|
|
4403
|
+
const message = `\u5143\u7D20\u4E0D\u53EF\u70B9\u51FB: ${status.reason || status.code}`;
|
|
4404
|
+
if (throwOnMissing) throw new Error(message);
|
|
4405
|
+
logger7.warn(`humanClick: ${message}\uFF0C\u8DF3\u8FC7\u70B9\u51FB`);
|
|
4406
|
+
return false;
|
|
4596
4407
|
}
|
|
4597
4408
|
const box = await element.boundingBox();
|
|
4598
4409
|
if (!box) {
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
throw new Error("\u65E0\u6CD5\u83B7\u53D6\u5143\u7D20\u4F4D\u7F6E");
|
|
4602
|
-
}
|
|
4603
|
-
logger6.warn("humanClick: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E\uFF0C\u8DF3\u8FC7\u70B9\u51FB");
|
|
4410
|
+
if (throwOnMissing) throw new Error("\u65E0\u6CD5\u83B7\u53D6\u5143\u7D20\u4F4D\u7F6E");
|
|
4411
|
+
logger7.warn("humanClick: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E\uFF0C\u8DF3\u8FC7\u70B9\u51FB");
|
|
4604
4412
|
return false;
|
|
4605
4413
|
}
|
|
4606
|
-
|
|
4607
|
-
const
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4414
|
+
await waitJitter(reactionDelay, 0.45);
|
|
4415
|
+
const visibleBox = clipBoxToViewport(box, resolveViewport(page));
|
|
4416
|
+
const tapTarget = centerPointInBox(visibleBox);
|
|
4417
|
+
try {
|
|
4418
|
+
await tapPoint(page, tapTarget, {
|
|
4419
|
+
timeoutMs: tapTimeoutMs,
|
|
4420
|
+
mouseFallbackTimeoutMs
|
|
4421
|
+
});
|
|
4422
|
+
} catch (tapError) {
|
|
4423
|
+
if (!fallbackDomClickOnTapError) throw tapError;
|
|
4424
|
+
const fallback = await withTimeout(
|
|
4425
|
+
() => activateElementFallback(element, tapTarget, {
|
|
4426
|
+
editableOnly: false
|
|
4427
|
+
}),
|
|
4428
|
+
activateFallbackTimeoutMs,
|
|
4429
|
+
"activation fallback"
|
|
4430
|
+
).catch(() => null);
|
|
4431
|
+
if (!fallback?.activated) throw tapError;
|
|
4432
|
+
logger7.warn(`humanClick: tap \u5931\u8D25\u540E\u5DF2\u7528 ${fallback.method} \u515C\u5E95: ${tapError?.message || tapError}`);
|
|
4433
|
+
}
|
|
4434
|
+
await waitJitter(120, 0.35);
|
|
4435
|
+
logger7.success("humanClick");
|
|
4613
4436
|
return true;
|
|
4614
4437
|
} catch (error) {
|
|
4615
|
-
|
|
4616
|
-
logger6.fail("humanClick", error);
|
|
4438
|
+
logger7.fail("humanClick", error);
|
|
4617
4439
|
throw error;
|
|
4618
4440
|
}
|
|
4619
4441
|
},
|
|
4620
|
-
/**
|
|
4621
|
-
* 随机延迟一段毫秒数(带 ±30% 抖动)
|
|
4622
|
-
* @param {number} baseMs - 基础延迟毫秒数
|
|
4623
|
-
* @param {number} [jitterPercent=0.3] - 抖动百分比
|
|
4624
|
-
*/
|
|
4625
4442
|
async randomSleep(baseMs, jitterPercent = 0.3) {
|
|
4626
|
-
|
|
4627
|
-
logger6.start("randomSleep", `base=${baseMs}, actual=${ms}ms`);
|
|
4628
|
-
await delay4(ms);
|
|
4629
|
-
logger6.success("randomSleep");
|
|
4443
|
+
await waitJitter(baseMs, jitterPercent);
|
|
4630
4444
|
},
|
|
4631
|
-
/**
|
|
4632
|
-
* 模拟人类"注视"或"阅读"行为:鼠标在页面上随机微动
|
|
4633
|
-
* @param {import('playwright').Page} page
|
|
4634
|
-
* @param {number} [baseDurationMs=2500] - 基础持续时间 (±40% 抖动)
|
|
4635
|
-
*/
|
|
4636
4445
|
async simulateGaze(page, baseDurationMs = 2500) {
|
|
4637
|
-
const
|
|
4638
|
-
const durationMs = this.jitterMs(baseDurationMs, 0.4);
|
|
4639
|
-
logger6.start("simulateGaze", `duration=${durationMs}ms`);
|
|
4446
|
+
const durationMs = jitterMs(baseDurationMs, 0.4);
|
|
4640
4447
|
const startTime = Date.now();
|
|
4641
|
-
const viewportSize = page.viewportSize() || { width: 1920, height: 1080 };
|
|
4642
4448
|
while (Date.now() - startTime < durationMs) {
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4449
|
+
if (Math.random() < 0.28) {
|
|
4450
|
+
const distance = 70 + Math.random() * 120;
|
|
4451
|
+
await dispatchTouchSwipe(page, Math.random() < 0.7 ? distance : -distance, {
|
|
4452
|
+
durationMs: 180,
|
|
4453
|
+
steps: 4
|
|
4454
|
+
});
|
|
4455
|
+
} else {
|
|
4456
|
+
await waitJitter(420, 0.55);
|
|
4457
|
+
}
|
|
4647
4458
|
}
|
|
4648
|
-
logger6.success("simulateGaze");
|
|
4649
4459
|
},
|
|
4650
|
-
/**
|
|
4651
|
-
* 人类化输入 - 带节奏变化(快-慢-停顿-偶尔加速)
|
|
4652
|
-
* @param {import('playwright').Page} page
|
|
4653
|
-
* @param {string} selector - 输入框选择器
|
|
4654
|
-
* @param {string} text - 要输入的文本
|
|
4655
|
-
* @param {Object} [options]
|
|
4656
|
-
* @param {number} [options.baseDelay=180] - 基础按键延迟 (ms),实际 ±40% 抖动
|
|
4657
|
-
* @param {number} [options.pauseProbability=0.08] - 停顿概率 (0-1)
|
|
4658
|
-
* @param {number} [options.pauseBase=800] - 停顿时长基础值 (ms),实际 ±50% 抖动
|
|
4659
|
-
*/
|
|
4660
4460
|
async humanType(page, selector, text, options = {}) {
|
|
4661
|
-
logger6.start("humanType", `selector=${selector}, textLen=${text.length}`);
|
|
4662
4461
|
const {
|
|
4663
|
-
baseDelay =
|
|
4462
|
+
baseDelay = 160,
|
|
4664
4463
|
pauseProbability = 0.08,
|
|
4665
|
-
pauseBase =
|
|
4464
|
+
pauseBase = 700
|
|
4666
4465
|
} = options;
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
} else if (/[,.!?;:,。!?;:]/.test(char)) {
|
|
4677
|
-
charDelay = this.jitterMs(baseDelay * 1.5, 0.4);
|
|
4678
|
-
} else {
|
|
4679
|
-
charDelay = this.jitterMs(baseDelay, 0.4);
|
|
4680
|
-
}
|
|
4681
|
-
await page.keyboard.type(char);
|
|
4682
|
-
await delay4(charDelay);
|
|
4683
|
-
if (Math.random() < pauseProbability && i < text.length - 1) {
|
|
4684
|
-
const pauseTime = this.jitterMs(pauseBase, 0.5);
|
|
4685
|
-
logger6.debug(`\u505C\u987F ${pauseTime}ms...`);
|
|
4686
|
-
await delay4(pauseTime);
|
|
4687
|
-
}
|
|
4466
|
+
await MobileHumanize.humanClick(page, selector, { scrollIfNeeded: true });
|
|
4467
|
+
await waitJitter(220, 0.45);
|
|
4468
|
+
for (let i = 0; i < String(text).length; i += 1) {
|
|
4469
|
+
const char = String(text)[i];
|
|
4470
|
+
const charDelay = /[,.!?;:,。!?;:]/.test(char) ? jitterMs(baseDelay * 1.45, 0.4) : jitterMs(char === " " ? baseDelay * 0.65 : baseDelay, 0.4);
|
|
4471
|
+
await page.keyboard.type(char);
|
|
4472
|
+
await waitJitter(charDelay, 0.15);
|
|
4473
|
+
if (Math.random() < pauseProbability && i < String(text).length - 1) {
|
|
4474
|
+
await waitJitter(pauseBase, 0.5);
|
|
4688
4475
|
}
|
|
4689
|
-
logger6.success("humanType");
|
|
4690
|
-
} catch (error) {
|
|
4691
|
-
logger6.fail("humanType", error);
|
|
4692
|
-
throw error;
|
|
4693
4476
|
}
|
|
4694
4477
|
},
|
|
4695
|
-
/**
|
|
4696
|
-
* 人类化按键 - 模拟用户在当前焦点或指定目标上按下一次键。
|
|
4697
|
-
* @param {import('playwright').Page} page
|
|
4698
|
-
* @param {string|import('playwright').ElementHandle|import('playwright').Locator} targetOrKey - 目标或按键
|
|
4699
|
-
* @param {string|Object} [maybeKey] - 按键或选项
|
|
4700
|
-
* @param {Object} [options]
|
|
4701
|
-
*/
|
|
4702
4478
|
async humanPress(page, targetOrKey, maybeKey, options = {}) {
|
|
4703
4479
|
const hasTarget = typeof maybeKey === "string";
|
|
4704
4480
|
const key = hasTarget ? maybeKey : targetOrKey;
|
|
4705
4481
|
const pressOptions = hasTarget ? options : maybeKey || options;
|
|
4706
4482
|
const {
|
|
4707
|
-
reactionDelay =
|
|
4708
|
-
holdDelay =
|
|
4483
|
+
reactionDelay = 170,
|
|
4484
|
+
holdDelay = 42,
|
|
4709
4485
|
focusDelay = 180,
|
|
4710
4486
|
scrollIfNeeded = true,
|
|
4711
4487
|
throwOnMissing = true,
|
|
4712
4488
|
keyboardOptions = {}
|
|
4713
4489
|
} = pressOptions || {};
|
|
4714
|
-
const targetDesc = hasTarget ?
|
|
4715
|
-
|
|
4490
|
+
const targetDesc = hasTarget ? describeTarget(targetOrKey) : "current focus";
|
|
4491
|
+
logger7.start("humanPress", `key=${key}, target=${targetDesc}`);
|
|
4716
4492
|
try {
|
|
4717
4493
|
if (hasTarget) {
|
|
4718
|
-
await
|
|
4494
|
+
await MobileHumanize.humanClick(page, targetOrKey, {
|
|
4495
|
+
reactionDelay: focusDelay,
|
|
4496
|
+
scrollIfNeeded,
|
|
4497
|
+
throwOnMissing
|
|
4498
|
+
});
|
|
4719
4499
|
}
|
|
4720
|
-
await
|
|
4500
|
+
await waitJitter(reactionDelay, 0.45);
|
|
4721
4501
|
await page.keyboard.press(key, {
|
|
4722
4502
|
...keyboardOptions,
|
|
4723
|
-
delay:
|
|
4503
|
+
delay: jitterMs(holdDelay, 0.5)
|
|
4724
4504
|
});
|
|
4725
|
-
|
|
4505
|
+
logger7.success("humanPress");
|
|
4726
4506
|
return true;
|
|
4727
4507
|
} catch (error) {
|
|
4728
|
-
|
|
4508
|
+
logger7.fail("humanPress", error);
|
|
4729
4509
|
throw error;
|
|
4730
4510
|
}
|
|
4731
4511
|
},
|
|
4732
|
-
/**
|
|
4733
|
-
* 人类化清空输入框 - 模拟人类删除文本的行为
|
|
4734
|
-
* @param {import('playwright').Page} page
|
|
4735
|
-
* @param {string} selector - 输入框选择器
|
|
4736
|
-
*/
|
|
4737
4512
|
async humanClear(page, selector) {
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4513
|
+
const locator = page.locator(selector);
|
|
4514
|
+
await MobileHumanize.humanClick(page, locator, { scrollIfNeeded: true });
|
|
4515
|
+
await waitJitter(160, 0.4);
|
|
4516
|
+
const readValue = async () => {
|
|
4517
|
+
try {
|
|
4518
|
+
return await locator.inputValue({ timeout: 600 });
|
|
4519
|
+
} catch {
|
|
4520
|
+
return await locator.evaluate((el) => "value" in el ? String(el.value || "") : String(el.textContent || "")).catch(() => "");
|
|
4521
|
+
}
|
|
4522
|
+
};
|
|
4523
|
+
const currentValue = await readValue();
|
|
4524
|
+
if (!currentValue) return;
|
|
4525
|
+
await page.keyboard.press("ControlOrMeta+A");
|
|
4526
|
+
await waitJitter(90, 0.35);
|
|
4527
|
+
await page.keyboard.press("Backspace");
|
|
4528
|
+
await waitJitter(120, 0.35);
|
|
4529
|
+
if (!await readValue()) return;
|
|
4530
|
+
await locator.evaluate((el) => {
|
|
4531
|
+
if ("value" in el) {
|
|
4532
|
+
el.value = "";
|
|
4533
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
4534
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
4746
4535
|
return;
|
|
4747
4536
|
}
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
logger6.success("humanClear");
|
|
4752
|
-
} catch (error) {
|
|
4753
|
-
logger6.fail("humanClear", error);
|
|
4754
|
-
throw error;
|
|
4755
|
-
}
|
|
4537
|
+
el.textContent = "";
|
|
4538
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
4539
|
+
});
|
|
4756
4540
|
},
|
|
4757
|
-
/**
|
|
4758
|
-
* 页面预热浏览 - 模拟人类进入页面后的探索行为
|
|
4759
|
-
* @param {import('playwright').Page} page
|
|
4760
|
-
* @param {number} [baseDuration=3500] - 预热时长基础值 (±40% 抖动)
|
|
4761
|
-
*/
|
|
4762
4541
|
async warmUpBrowsing(page, baseDuration = 3500) {
|
|
4763
|
-
const
|
|
4764
|
-
const durationMs = this.jitterMs(baseDuration, 0.4);
|
|
4765
|
-
logger6.start("warmUpBrowsing", `duration=${durationMs}ms`);
|
|
4542
|
+
const durationMs = jitterMs(baseDuration, 0.4);
|
|
4766
4543
|
const startTime = Date.now();
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
await delay4(this.jitterMs(800, 0.5));
|
|
4782
|
-
}
|
|
4544
|
+
while (Date.now() - startTime < durationMs) {
|
|
4545
|
+
const action = Math.random();
|
|
4546
|
+
if (action < 0.5) {
|
|
4547
|
+
await dispatchTouchSwipe(page, 120 + Math.random() * 220, {
|
|
4548
|
+
durationMs: 240,
|
|
4549
|
+
steps: 5
|
|
4550
|
+
});
|
|
4551
|
+
} else if (action < 0.7) {
|
|
4552
|
+
await dispatchTouchSwipe(page, -(80 + Math.random() * 160), {
|
|
4553
|
+
durationMs: 220,
|
|
4554
|
+
steps: 4
|
|
4555
|
+
});
|
|
4556
|
+
} else {
|
|
4557
|
+
await waitJitter(560, 0.55);
|
|
4783
4558
|
}
|
|
4784
|
-
logger6.success("warmUpBrowsing");
|
|
4785
|
-
} catch (error) {
|
|
4786
|
-
logger6.fail("warmUpBrowsing", error);
|
|
4787
|
-
throw error;
|
|
4788
4559
|
}
|
|
4789
4560
|
},
|
|
4790
|
-
/**
|
|
4791
|
-
* 自然滚动 - 带惯性、减速效果和随机抖动
|
|
4792
|
-
* @param {import('playwright').Page} page
|
|
4793
|
-
* @param {'up' | 'down'} [direction='down'] - 滚动方向
|
|
4794
|
-
* @param {number} [distance=300] - 总滚动距离基础值 (px),±15% 抖动
|
|
4795
|
-
* @param {number} [baseSteps=5] - 分几步完成基础值,±1 随机
|
|
4796
|
-
*/
|
|
4797
4561
|
async naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
|
|
4798
4562
|
const steps = Math.max(3, baseSteps + Math.floor(Math.random() * 3) - 1);
|
|
4799
|
-
const actualDistance =
|
|
4800
|
-
logger6.start("naturalScroll", `dir=${direction}, dist=${actualDistance}, steps=${steps}`);
|
|
4563
|
+
const actualDistance = jitterMs(distance, 0.15);
|
|
4801
4564
|
const sign = direction === "down" ? 1 : -1;
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
await page.mouse.wheel(0, scrollAmount);
|
|
4809
|
-
const baseDelay = 60 + i * 25;
|
|
4810
|
-
await delay4(this.jitterMs(baseDelay, 0.3));
|
|
4811
|
-
}
|
|
4812
|
-
logger6.success("naturalScroll");
|
|
4813
|
-
} catch (error) {
|
|
4814
|
-
logger6.fail("naturalScroll", error);
|
|
4815
|
-
throw error;
|
|
4565
|
+
for (let i = 0; i < steps; i += 1) {
|
|
4566
|
+
const factor = 1 - i / steps * 0.45;
|
|
4567
|
+
await dispatchTouchSwipe(page, actualDistance / steps * factor * sign, {
|
|
4568
|
+
durationMs: 150 + i * 20,
|
|
4569
|
+
steps: 4
|
|
4570
|
+
});
|
|
4816
4571
|
}
|
|
4817
4572
|
}
|
|
4818
4573
|
};
|
|
4819
4574
|
|
|
4820
|
-
// src/
|
|
4821
|
-
var
|
|
4822
|
-
var
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
var
|
|
4830
|
-
initializeCursor: { enumerable: true },
|
|
4831
|
-
humanMove: { enumerable: true },
|
|
4832
|
-
humanScroll: { enumerable: true },
|
|
4833
|
-
humanClick: { enumerable: true },
|
|
4834
|
-
simulateGaze: { enumerable: true },
|
|
4835
|
-
humanType: { enumerable: true },
|
|
4836
|
-
humanPress: { enumerable: true },
|
|
4837
|
-
humanClear: { enumerable: true },
|
|
4838
|
-
warmUpBrowsing: { enumerable: true },
|
|
4839
|
-
naturalScroll: { enumerable: true }
|
|
4840
|
-
});
|
|
4841
|
-
var isPageLike2 = (value) => value && typeof value === "object" && typeof value.evaluate === "function";
|
|
4842
|
-
var createSharedHumanizeMethods = ({ desktopDelegate, resolveDelegate }) => ({
|
|
4575
|
+
// src/humanize.js
|
|
4576
|
+
var resolveDeviceFromPage2 = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
|
|
4577
|
+
var resolveDelegate = (page) => {
|
|
4578
|
+
return resolveDeviceFromPage2(page) === Device.Mobile ? MobileHumanize : Humanize;
|
|
4579
|
+
};
|
|
4580
|
+
var callDelegate = (method, page, args) => {
|
|
4581
|
+
const delegate = resolveDelegate(page);
|
|
4582
|
+
return delegate[method](page, ...args);
|
|
4583
|
+
};
|
|
4584
|
+
var Humanize2 = {
|
|
4843
4585
|
jitterMs(base, jitterPercent = 0.3) {
|
|
4844
|
-
return
|
|
4586
|
+
return Humanize.jitterMs(base, jitterPercent);
|
|
4587
|
+
},
|
|
4588
|
+
initializeCursor(page) {
|
|
4589
|
+
return callDelegate("initializeCursor", page, []);
|
|
4590
|
+
},
|
|
4591
|
+
humanMove(page, target) {
|
|
4592
|
+
return callDelegate("humanMove", page, [target]);
|
|
4593
|
+
},
|
|
4594
|
+
humanScroll(page, target, options = {}) {
|
|
4595
|
+
return callDelegate("humanScroll", page, [target, options]);
|
|
4596
|
+
},
|
|
4597
|
+
humanClick(page, target, options = {}) {
|
|
4598
|
+
return callDelegate("humanClick", page, [target, options]);
|
|
4845
4599
|
},
|
|
4846
4600
|
randomSleep(pageOrBaseMs, maybeBaseMs, maybeJitterPercent) {
|
|
4847
|
-
if (
|
|
4848
|
-
|
|
4601
|
+
if (pageOrBaseMs && typeof pageOrBaseMs === "object" && typeof pageOrBaseMs.evaluate === "function") {
|
|
4602
|
+
const delegate = resolveDelegate(pageOrBaseMs);
|
|
4603
|
+
return delegate.randomSleep(maybeBaseMs, maybeJitterPercent);
|
|
4604
|
+
}
|
|
4605
|
+
return Humanize.randomSleep(pageOrBaseMs, maybeBaseMs);
|
|
4606
|
+
},
|
|
4607
|
+
simulateGaze(page, baseDurationMs = 2500) {
|
|
4608
|
+
return callDelegate("simulateGaze", page, [baseDurationMs]);
|
|
4609
|
+
},
|
|
4610
|
+
humanType(page, selector, text, options = {}) {
|
|
4611
|
+
return callDelegate("humanType", page, [selector, text, options]);
|
|
4612
|
+
},
|
|
4613
|
+
humanPress(page, targetOrKey, maybeKey, options = {}) {
|
|
4614
|
+
if (typeof maybeKey === "string") {
|
|
4615
|
+
return callDelegate("humanPress", page, [targetOrKey, maybeKey, options]);
|
|
4849
4616
|
}
|
|
4850
|
-
return
|
|
4617
|
+
return callDelegate("humanPress", page, [targetOrKey, maybeKey || options]);
|
|
4618
|
+
},
|
|
4619
|
+
humanClear(page, selector) {
|
|
4620
|
+
return callDelegate("humanClear", page, [selector]);
|
|
4621
|
+
},
|
|
4622
|
+
warmUpBrowsing(page, baseDuration = 3500) {
|
|
4623
|
+
return callDelegate("warmUpBrowsing", page, [baseDuration]);
|
|
4624
|
+
},
|
|
4625
|
+
naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
|
|
4626
|
+
return callDelegate("naturalScroll", page, [direction, distance, baseSteps]);
|
|
4851
4627
|
}
|
|
4852
|
-
});
|
|
4853
|
-
var withHumanizeDelegates = (target, resolveDelegate) => withDelegatedProperties(target, {
|
|
4854
|
-
namespace: "Humanize",
|
|
4855
|
-
methods: HUMANIZE_DELEGATED_METHODS,
|
|
4856
|
-
resolveDelegate: (_method, args) => ({
|
|
4857
|
-
delegate: resolveDelegate(args[0]),
|
|
4858
|
-
label: "resolved humanize delegate"
|
|
4859
|
-
})
|
|
4860
|
-
});
|
|
4861
|
-
var createHumanizeExport = (context = {}) => {
|
|
4862
|
-
return Object.assign(
|
|
4863
|
-
createSharedHumanizeMethods(context),
|
|
4864
|
-
withHumanizeDelegates({}, context.resolveDelegate)
|
|
4865
|
-
);
|
|
4866
4628
|
};
|
|
4867
|
-
var DefaultHumanize = createHumanizeExport(DefaultHumanizeContext);
|
|
4868
|
-
var CloakBrowserHumanize = createHumanizeExport(CloakBrowserHumanizeContext);
|
|
4869
|
-
|
|
4870
|
-
// src/humanize.js
|
|
4871
|
-
var humanizeStrategies = {
|
|
4872
|
-
[Mode.Default]: DefaultHumanize,
|
|
4873
|
-
[Mode.CloakBrowser]: CloakBrowserHumanize
|
|
4874
|
-
};
|
|
4875
|
-
var humanizeFacadeMethods = Object.freeze({
|
|
4876
|
-
jitterMs: { enumerable: true },
|
|
4877
|
-
initializeCursor: { enumerable: true },
|
|
4878
|
-
humanMove: { enumerable: true },
|
|
4879
|
-
humanScroll: { enumerable: true },
|
|
4880
|
-
humanClick: { enumerable: true },
|
|
4881
|
-
randomSleep: { enumerable: true },
|
|
4882
|
-
simulateGaze: { enumerable: true },
|
|
4883
|
-
humanType: { enumerable: true },
|
|
4884
|
-
humanPress: { enumerable: true },
|
|
4885
|
-
humanClear: { enumerable: true },
|
|
4886
|
-
warmUpBrowsing: { enumerable: true },
|
|
4887
|
-
naturalScroll: { enumerable: true }
|
|
4888
|
-
});
|
|
4889
|
-
var Humanize2 = createDelegatedFacade("Humanize", humanizeStrategies, humanizeFacadeMethods);
|
|
4890
4629
|
|
|
4891
|
-
// src/
|
|
4630
|
+
// src/launch.js
|
|
4892
4631
|
import { execFileSync } from "node:child_process";
|
|
4893
4632
|
import { FingerprintGenerator } from "fingerprint-generator";
|
|
4894
4633
|
import { FingerprintInjector } from "fingerprint-injector";
|
|
@@ -4973,8 +4712,8 @@ var ByPass = {
|
|
|
4973
4712
|
resolveRouteByProxy
|
|
4974
4713
|
};
|
|
4975
4714
|
|
|
4976
|
-
// src/
|
|
4977
|
-
var
|
|
4715
|
+
// src/launch.js
|
|
4716
|
+
var logger8 = createInternalLogger("Launch");
|
|
4978
4717
|
var REQUEST_HOOK_FLAG = Symbol("playwright-toolkit-request-hook");
|
|
4979
4718
|
var injectedContexts = /* @__PURE__ */ new WeakSet();
|
|
4980
4719
|
var browserMajorVersionCache = /* @__PURE__ */ new Map();
|
|
@@ -5026,7 +4765,7 @@ var detectBrowserMajorVersion = (launcher) => {
|
|
|
5026
4765
|
});
|
|
5027
4766
|
detectedVersion = parseChromeMajorVersion(rawVersion);
|
|
5028
4767
|
} catch (error) {
|
|
5029
|
-
|
|
4768
|
+
logger8.warn(`\u8BFB\u53D6\u6D4F\u89C8\u5668\u7248\u672C\u5931\u8D25: ${error?.message || error}`);
|
|
5030
4769
|
}
|
|
5031
4770
|
browserMajorVersionCache.set(executablePath, detectedVersion);
|
|
5032
4771
|
return detectedVersion;
|
|
@@ -5040,7 +4779,7 @@ var resolveCoreDevice = (core = {}) => {
|
|
|
5040
4779
|
};
|
|
5041
4780
|
var buildFingerprintGenerator = ({ locale, browserMajorVersion, device }) => {
|
|
5042
4781
|
return new FingerprintGenerator(
|
|
5043
|
-
|
|
4782
|
+
AntiCheat.getFingerprintGeneratorOptions({
|
|
5044
4783
|
locale,
|
|
5045
4784
|
browserMajorVersion,
|
|
5046
4785
|
device
|
|
@@ -5063,7 +4802,7 @@ var generateFingerprintForCore = ({ locale, browserMajorVersion, device }) => {
|
|
|
5063
4802
|
if (requestedBrowserMajorVersion <= 0) {
|
|
5064
4803
|
throw error;
|
|
5065
4804
|
}
|
|
5066
|
-
|
|
4805
|
+
logger8.warn(
|
|
5067
4806
|
`\u5F53\u524D\u6D4F\u89C8\u5668\u5927\u7248\u672C ${requestedBrowserMajorVersion} \u65E0\u53EF\u7528 strict \u6307\u7EB9\u6837\u672C\uFF0C\u9000\u56DE\u4E0D\u7ED1\u5B9A\u5927\u7248\u672C\u751F\u6210: ${error?.message || error}`
|
|
5068
4807
|
);
|
|
5069
4808
|
}
|
|
@@ -5084,7 +4823,7 @@ var buildReplayableBrowserProfile = (runtimeState, launcher) => {
|
|
|
5084
4823
|
}
|
|
5085
4824
|
let nextState = RuntimeEnv.rememberState(runtimeState);
|
|
5086
4825
|
let browserProfileCore = RuntimeEnv.getBrowserProfileCore(nextState);
|
|
5087
|
-
const timezoneId = String(browserProfileCore?.timezone_id || "").trim() ||
|
|
4826
|
+
const timezoneId = String(browserProfileCore?.timezone_id || "").trim() || AntiCheat.getBaseConfig().timezoneId;
|
|
5088
4827
|
const locale = DEFAULT_LOCALE;
|
|
5089
4828
|
const currentBrowserMajorVersion = detectBrowserMajorVersion(launcher);
|
|
5090
4829
|
const storedBrowserMajorVersion = Number(browserProfileCore?.browser_major_version || 0);
|
|
@@ -5108,7 +4847,7 @@ var buildReplayableBrowserProfile = (runtimeState, launcher) => {
|
|
|
5108
4847
|
schema_version: DEFAULT_BROWSER_PROFILE_SCHEMA_VERSION
|
|
5109
4848
|
};
|
|
5110
4849
|
nextState = RuntimeEnv.setBrowserProfileCore(nextState, browserProfileCore);
|
|
5111
|
-
|
|
4850
|
+
logger8.info(
|
|
5112
4851
|
`\u5DF2\u751F\u6210\u6D4F\u89C8\u5668\u6307\u7EB9\u771F\u6E90 | env=${String(nextState.envId || "-")} | device=${device} | version=${browserProfileCore.browser_major_version || "-"} | fingerprintVersion=${fingerprintBrowserMajorVersion || "-"} | exactVersion=${generated.exactBrowserMajorVersion ? "true" : "false"} | timezone=${timezoneId}`
|
|
5113
4852
|
);
|
|
5114
4853
|
return { runtimeState: nextState, browserProfileCore };
|
|
@@ -5163,7 +4902,7 @@ var applyFingerprintPageOptions = (pageOptions = {}, { fingerprintWithHeaders =
|
|
|
5163
4902
|
pageOptions.timezoneId = timezoneId;
|
|
5164
4903
|
}
|
|
5165
4904
|
};
|
|
5166
|
-
var buildReplayBrowserPoolOptions = (browserProfileCore) => {
|
|
4905
|
+
var buildReplayBrowserPoolOptions = (browserProfileCore, modifyPageOptions = null) => {
|
|
5167
4906
|
const fingerprintWithHeaders = browserProfileCore?.fingerprint;
|
|
5168
4907
|
const fingerprint = fingerprintWithHeaders?.fingerprint;
|
|
5169
4908
|
if (!fingerprintWithHeaders || !fingerprint) {
|
|
@@ -5172,13 +4911,16 @@ var buildReplayBrowserPoolOptions = (browserProfileCore) => {
|
|
|
5172
4911
|
return {
|
|
5173
4912
|
useFingerprints: false,
|
|
5174
4913
|
prePageCreateHooks: [
|
|
5175
|
-
(
|
|
4914
|
+
async (pageId, browserController, pageOptions = {}) => {
|
|
5176
4915
|
if (!pageOptions || typeof pageOptions !== "object") return;
|
|
5177
4916
|
applyFingerprintPageOptions(pageOptions, {
|
|
5178
4917
|
fingerprintWithHeaders,
|
|
5179
4918
|
locale: browserProfileCore.locale,
|
|
5180
4919
|
timezoneId: browserProfileCore.timezone_id
|
|
5181
4920
|
});
|
|
4921
|
+
if (modifyPageOptions) {
|
|
4922
|
+
await modifyPageOptions(pageOptions, { pageId, browserController });
|
|
4923
|
+
}
|
|
5182
4924
|
}
|
|
5183
4925
|
],
|
|
5184
4926
|
postPageCreateHooks: [
|
|
@@ -5193,7 +4935,7 @@ var buildReplayBrowserPoolOptions = (browserProfileCore) => {
|
|
|
5193
4935
|
]
|
|
5194
4936
|
};
|
|
5195
4937
|
};
|
|
5196
|
-
var
|
|
4938
|
+
var Launch = {
|
|
5197
4939
|
getPlaywrightCrawlerOptions(options = {}) {
|
|
5198
4940
|
const normalizedOptions = Array.isArray(options) ? { customArgs: options } : options || {};
|
|
5199
4941
|
const {
|
|
@@ -5204,11 +4946,13 @@ var DefaultLaunch = {
|
|
|
5204
4946
|
debugMode = false,
|
|
5205
4947
|
isRunningOnApify = false,
|
|
5206
4948
|
launcher = null,
|
|
4949
|
+
hooks = {},
|
|
5207
4950
|
preNavigationHooks = [],
|
|
5208
4951
|
postNavigationHooks = [],
|
|
5209
4952
|
runtimeState = null
|
|
5210
4953
|
} = normalizedOptions;
|
|
5211
4954
|
const device = resolveRuntimeDevice(runtimeState);
|
|
4955
|
+
const modifyPageOptions = typeof hooks?.modifyPageOptions === "function" ? hooks.modifyPageOptions : null;
|
|
5212
4956
|
const { byPassDomains, enableProxy, proxyUrl } = resolveProxyLaunchOptions(proxyConfiguration);
|
|
5213
4957
|
const byPassRules = ByPass.buildByPassDomainRules(byPassDomains);
|
|
5214
4958
|
const proxyMeter = enableProxy && proxyUrl ? ProxyMeterRuntime.startProxyMeter({ proxyUrl, debugMode }) : null;
|
|
@@ -5217,11 +4961,11 @@ var DefaultLaunch = {
|
|
|
5217
4961
|
launchProxy.bypass = byPassDomains.join(",");
|
|
5218
4962
|
}
|
|
5219
4963
|
const replayContext = buildReplayableBrowserProfile(runtimeState, launcher);
|
|
5220
|
-
const replayBrowserPoolOptions = buildReplayBrowserPoolOptions(replayContext.browserProfileCore);
|
|
4964
|
+
const replayBrowserPoolOptions = buildReplayBrowserPoolOptions(replayContext.browserProfileCore, modifyPageOptions);
|
|
5221
4965
|
const launchLocale = String(replayContext.browserProfileCore?.locale || DEFAULT_LOCALE).trim() || DEFAULT_LOCALE;
|
|
5222
4966
|
const launchOptions = {
|
|
5223
4967
|
args: [
|
|
5224
|
-
...
|
|
4968
|
+
...AntiCheat.getLaunchArgs({ locale: launchLocale }),
|
|
5225
4969
|
...customArgs
|
|
5226
4970
|
],
|
|
5227
4971
|
ignoreDefaultArgs: ["--enable-automation"]
|
|
@@ -5237,18 +4981,18 @@ var DefaultLaunch = {
|
|
|
5237
4981
|
upstreamLabel = `${parsedProxyUrl.protocol}//${parsedProxyUrl.host}`;
|
|
5238
4982
|
} catch {
|
|
5239
4983
|
}
|
|
5240
|
-
|
|
4984
|
+
logger8.info(
|
|
5241
4985
|
`[\u4EE3\u7406\u5DF2\u542F\u7528] \u672C\u5730=${launchProxy.server} \u4E0A\u6E38=${upstreamLabel || "-"} \u76F4\u8FDE\u57DF\u540D=${(byPassDomains || []).join(",")}`
|
|
5242
4986
|
);
|
|
5243
|
-
|
|
4987
|
+
logger8.info(`[\u6D41\u91CF\u89C2\u6D4B] \u9010\u8BF7\u6C42\u8C03\u8BD5=${Boolean(debugMode) ? "\u5F00\u542F" : "\u5173\u95ED"}\uFF08\u6C47\u603B\u59CB\u7EC8\u5F00\u542F\uFF09`);
|
|
5244
4988
|
} else if (enableByPassLogger && enableProxy && !launchProxy) {
|
|
5245
|
-
|
|
5246
|
-
|
|
4989
|
+
logger8.info("[\u4EE3\u7406\u672A\u542F\u7528] enable_proxy=true \u4F46 proxy_url \u4E3A\u7A7A");
|
|
4990
|
+
logger8.info(`[\u6D41\u91CF\u89C2\u6D4B] \u9010\u8BF7\u6C42\u8C03\u8BD5=${Boolean(debugMode) ? "\u5F00\u542F" : "\u5173\u95ED"}\uFF08\u6C47\u603B\u59CB\u7EC8\u5F00\u542F\uFF09`);
|
|
5247
4991
|
} else if (enableByPassLogger && !enableProxy && proxyUrl) {
|
|
5248
|
-
|
|
5249
|
-
|
|
4992
|
+
logger8.info("[\u4EE3\u7406\u672A\u542F\u7528] enable_proxy=false \u4E14 proxy_url \u5DF2\u914D\u7F6E");
|
|
4993
|
+
logger8.info(`[\u6D41\u91CF\u89C2\u6D4B] \u9010\u8BF7\u6C42\u8C03\u8BD5=${Boolean(debugMode) ? "\u5F00\u542F" : "\u5173\u95ED"}\uFF08\u6C47\u603B\u59CB\u7EC8\u5F00\u542F\uFF09`);
|
|
5250
4994
|
} else if (enableByPassLogger) {
|
|
5251
|
-
|
|
4995
|
+
logger8.info(`[\u6D41\u91CF\u89C2\u6D4B] \u9010\u8BF7\u6C42\u8C03\u8BD5=${Boolean(debugMode) ? "\u5F00\u542F" : "\u5173\u95ED"}\uFF08\u6C47\u603B\u59CB\u7EC8\u5F00\u542F\uFF09`);
|
|
5252
4996
|
}
|
|
5253
4997
|
const onPageCreated = (page) => {
|
|
5254
4998
|
const recommendedGotoOptions = {
|
|
@@ -5270,7 +5014,7 @@ var DefaultLaunch = {
|
|
|
5270
5014
|
}
|
|
5271
5015
|
if (!enableByPassLogger || byPassDomains.length === 0) return;
|
|
5272
5016
|
if (!matched || !matched.rule) return;
|
|
5273
|
-
|
|
5017
|
+
logger8.info(`[\u76F4\u8FDE\u547D\u4E2D] \u89C4\u5219=${matched.rule.pattern} \u57DF\u540D=${matched.hostname} \u8D44\u6E90\u7C7B\u578B=${resourceType} \u65B9\u6CD5=${req.method()} \u5730\u5740=${requestUrl}`);
|
|
5274
5018
|
};
|
|
5275
5019
|
page.on("request", requestHandler);
|
|
5276
5020
|
return recommendedGotoOptions;
|
|
@@ -5289,20 +5033,23 @@ var DefaultLaunch = {
|
|
|
5289
5033
|
browserPoolOptions: replayBrowserPoolOptions || {
|
|
5290
5034
|
useFingerprints: true,
|
|
5291
5035
|
fingerprintOptions: {
|
|
5292
|
-
fingerprintGeneratorOptions:
|
|
5036
|
+
fingerprintGeneratorOptions: AntiCheat.getFingerprintGeneratorOptions({
|
|
5293
5037
|
locale: launchLocale,
|
|
5294
5038
|
device
|
|
5295
5039
|
})
|
|
5296
5040
|
},
|
|
5297
5041
|
prePageCreateHooks: [
|
|
5298
|
-
(
|
|
5042
|
+
async (pageId, browserController, pageOptions = {}) => {
|
|
5299
5043
|
const fingerprintWithHeaders = browserController?.launchContext?.fingerprint;
|
|
5300
|
-
const timezoneId =
|
|
5044
|
+
const timezoneId = AntiCheat.getBaseConfig().timezoneId;
|
|
5301
5045
|
applyFingerprintPageOptions(pageOptions, {
|
|
5302
5046
|
fingerprintWithHeaders,
|
|
5303
5047
|
locale: launchLocale,
|
|
5304
5048
|
timezoneId
|
|
5305
5049
|
});
|
|
5050
|
+
if (modifyPageOptions) {
|
|
5051
|
+
await modifyPageOptions(pageOptions, { pageId, browserController });
|
|
5052
|
+
}
|
|
5306
5053
|
}
|
|
5307
5054
|
]
|
|
5308
5055
|
},
|
|
@@ -5324,263 +5071,6 @@ var DefaultLaunch = {
|
|
|
5324
5071
|
}
|
|
5325
5072
|
};
|
|
5326
5073
|
|
|
5327
|
-
// src/internals/launch/cloakbrowser.js
|
|
5328
|
-
import { execFile } from "node:child_process";
|
|
5329
|
-
import { promisify } from "node:util";
|
|
5330
|
-
var logger8 = createInternalLogger("CloakBrowser");
|
|
5331
|
-
var execFileAsync = promisify(execFile);
|
|
5332
|
-
var DEFAULT_CLOAK_CRAWLER_BASE_OPTIONS = Object.freeze({
|
|
5333
|
-
maxConcurrency: 1,
|
|
5334
|
-
maxRequestRetries: 0,
|
|
5335
|
-
requestHandlerTimeoutSecs: 240,
|
|
5336
|
-
navigationTimeoutSecs: 120
|
|
5337
|
-
});
|
|
5338
|
-
var DEFAULT_CLOAK_HUMANIZE_OPTIONS = Object.freeze({
|
|
5339
|
-
humanize: true
|
|
5340
|
-
});
|
|
5341
|
-
var DEFAULT_CLOAK_GOTO_OPTIONS = Object.freeze({
|
|
5342
|
-
waitUntil: "commit"
|
|
5343
|
-
});
|
|
5344
|
-
var cachedCloakBrowserModulePromise = null;
|
|
5345
|
-
var hasOwn = (target, key) => Object.prototype.hasOwnProperty.call(target, key);
|
|
5346
|
-
var loadCloakBrowserModule = async () => {
|
|
5347
|
-
if (!cachedCloakBrowserModulePromise) {
|
|
5348
|
-
cachedCloakBrowserModulePromise = import("cloakbrowser").catch((error) => {
|
|
5349
|
-
cachedCloakBrowserModulePromise = null;
|
|
5350
|
-
throw new Error("cloakbrowser \u6A21\u5757\u52A0\u8F7D\u5931\u8D25\uFF0C\u8BF7\u786E\u8BA4\u5F53\u524D\u8FD0\u884C\u73AF\u5883\u5DF2\u5B89\u88C5 cloakbrowser\u3002", {
|
|
5351
|
-
cause: error
|
|
5352
|
-
});
|
|
5353
|
-
});
|
|
5354
|
-
}
|
|
5355
|
-
return cachedCloakBrowserModulePromise;
|
|
5356
|
-
};
|
|
5357
|
-
var buildCloakLaunchOptions = async (options = {}) => {
|
|
5358
|
-
const { buildLaunchOptions } = await loadCloakBrowserModule();
|
|
5359
|
-
return await buildLaunchOptions(normalizeObject(options));
|
|
5360
|
-
};
|
|
5361
|
-
var normalizeObject = (value) => {
|
|
5362
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
5363
|
-
return {};
|
|
5364
|
-
}
|
|
5365
|
-
return value;
|
|
5366
|
-
};
|
|
5367
|
-
var normalizeStringArray = (value) => {
|
|
5368
|
-
if (!Array.isArray(value)) {
|
|
5369
|
-
return [];
|
|
5370
|
-
}
|
|
5371
|
-
return value.map((item) => String(item || "").trim()).filter(Boolean);
|
|
5372
|
-
};
|
|
5373
|
-
var resolveCloakBrowserProxy = (proxyConfiguration = {}) => {
|
|
5374
|
-
const config = normalizeObject(proxyConfiguration);
|
|
5375
|
-
const proxyUrl = String(config.proxy_url || "").trim();
|
|
5376
|
-
const enableProxy = typeof config.enable_proxy === "boolean" ? config.enable_proxy : proxyUrl !== "";
|
|
5377
|
-
if (!enableProxy || !proxyUrl) {
|
|
5378
|
-
return null;
|
|
5379
|
-
}
|
|
5380
|
-
const byPassDomains = ByPass.normalizeByPassDomains(config.by_pass_domains);
|
|
5381
|
-
if (byPassDomains.length === 0) {
|
|
5382
|
-
return proxyUrl;
|
|
5383
|
-
}
|
|
5384
|
-
const parsedProxyUrl = new URL(proxyUrl.includes("://") ? proxyUrl : `http://${proxyUrl}`);
|
|
5385
|
-
return {
|
|
5386
|
-
server: `${parsedProxyUrl.protocol}//${parsedProxyUrl.host}`,
|
|
5387
|
-
username: decodeURIComponent(parsedProxyUrl.username || ""),
|
|
5388
|
-
password: decodeURIComponent(parsedProxyUrl.password || ""),
|
|
5389
|
-
bypass: byPassDomains.join(",")
|
|
5390
|
-
};
|
|
5391
|
-
};
|
|
5392
|
-
var extractFingerprintArg = (launchOptions = {}) => {
|
|
5393
|
-
const args = Array.isArray(launchOptions?.args) ? launchOptions.args : [];
|
|
5394
|
-
return args.find((value) => String(value || "").startsWith("--fingerprint=")) || "";
|
|
5395
|
-
};
|
|
5396
|
-
var createStableGotoHook = (recommendedGotoOptions = DEFAULT_CLOAK_GOTO_OPTIONS) => {
|
|
5397
|
-
const normalizedRecommendedGotoOptions = normalizeObject(recommendedGotoOptions);
|
|
5398
|
-
const fallbackGotoOptions = Object.keys(normalizedRecommendedGotoOptions).length > 0 ? normalizedRecommendedGotoOptions : DEFAULT_CLOAK_GOTO_OPTIONS;
|
|
5399
|
-
return async (_crawlingContext, gotoOptions = {}) => {
|
|
5400
|
-
for (const [key, value] of Object.entries(fallbackGotoOptions)) {
|
|
5401
|
-
if (gotoOptions[key] == null) {
|
|
5402
|
-
gotoOptions[key] = value;
|
|
5403
|
-
}
|
|
5404
|
-
}
|
|
5405
|
-
};
|
|
5406
|
-
};
|
|
5407
|
-
var attachCloakBrowserHumanizeHook = ({
|
|
5408
|
-
browserPoolOptions = {},
|
|
5409
|
-
activeBrowsers,
|
|
5410
|
-
patchedBrowsers,
|
|
5411
|
-
humanizeOptions = DEFAULT_CLOAK_HUMANIZE_OPTIONS
|
|
5412
|
-
} = {}) => {
|
|
5413
|
-
const normalizedBrowserPoolOptions = normalizeObject(browserPoolOptions);
|
|
5414
|
-
const shouldHumanize = humanizeOptions !== false;
|
|
5415
|
-
const normalizedHumanizeOptions = shouldHumanize ? {
|
|
5416
|
-
...DEFAULT_CLOAK_HUMANIZE_OPTIONS,
|
|
5417
|
-
...normalizeObject(humanizeOptions)
|
|
5418
|
-
} : null;
|
|
5419
|
-
return {
|
|
5420
|
-
...normalizedBrowserPoolOptions,
|
|
5421
|
-
useFingerprints: false,
|
|
5422
|
-
postLaunchHooks: [
|
|
5423
|
-
...Array.isArray(normalizedBrowserPoolOptions.postLaunchHooks) ? normalizedBrowserPoolOptions.postLaunchHooks : [],
|
|
5424
|
-
async (_pageId, browserController) => {
|
|
5425
|
-
const browser = browserController?.browser;
|
|
5426
|
-
if (!browser || typeof browser.contexts !== "function") {
|
|
5427
|
-
return;
|
|
5428
|
-
}
|
|
5429
|
-
activeBrowsers.add(browser);
|
|
5430
|
-
if (typeof browser.once === "function") {
|
|
5431
|
-
browser.once("disconnected", () => {
|
|
5432
|
-
activeBrowsers.delete(browser);
|
|
5433
|
-
});
|
|
5434
|
-
}
|
|
5435
|
-
if (!shouldHumanize || patchedBrowsers.has(browser)) {
|
|
5436
|
-
return;
|
|
5437
|
-
}
|
|
5438
|
-
const { humanizeBrowser } = await loadCloakBrowserModule();
|
|
5439
|
-
await humanizeBrowser(browser, normalizedHumanizeOptions);
|
|
5440
|
-
patchedBrowsers.add(browser);
|
|
5441
|
-
}
|
|
5442
|
-
]
|
|
5443
|
-
};
|
|
5444
|
-
};
|
|
5445
|
-
var closeTrackedBrowsers = async (activeBrowsers) => {
|
|
5446
|
-
const browsers = Array.from(activeBrowsers || []);
|
|
5447
|
-
activeBrowsers?.clear?.();
|
|
5448
|
-
await Promise.allSettled(
|
|
5449
|
-
browsers.map(async (browser) => {
|
|
5450
|
-
if (!browser || typeof browser.isConnected !== "function" || !browser.isConnected()) {
|
|
5451
|
-
return;
|
|
5452
|
-
}
|
|
5453
|
-
await browser.close().catch(() => {
|
|
5454
|
-
});
|
|
5455
|
-
})
|
|
5456
|
-
);
|
|
5457
|
-
};
|
|
5458
|
-
var forceTerminateBrowsersByFingerprintArg = async (fingerprintArg) => {
|
|
5459
|
-
if (!fingerprintArg) {
|
|
5460
|
-
return;
|
|
5461
|
-
}
|
|
5462
|
-
await execFileAsync("pkill", ["-f", "--", fingerprintArg]).catch((error) => {
|
|
5463
|
-
if (error?.code === 1 || error?.code === "ENOENT") {
|
|
5464
|
-
return;
|
|
5465
|
-
}
|
|
5466
|
-
logger8.info(`\u5F3A\u5236\u5173\u95ED CloakBrowser \u8FDB\u7A0B\u5931\u8D25\uFF08\u5FFD\u7565\uFF09: ${error?.message || String(error)}`);
|
|
5467
|
-
});
|
|
5468
|
-
};
|
|
5469
|
-
var CloakBrowserLaunch = {
|
|
5470
|
-
resolveProxyConfiguration(proxyConfiguration = {}) {
|
|
5471
|
-
return resolveCloakBrowserProxy(proxyConfiguration);
|
|
5472
|
-
},
|
|
5473
|
-
extractFingerprintArg(launchOptions = {}) {
|
|
5474
|
-
return extractFingerprintArg(launchOptions);
|
|
5475
|
-
},
|
|
5476
|
-
createStableGotoHook(recommendedGotoOptions = DEFAULT_CLOAK_GOTO_OPTIONS) {
|
|
5477
|
-
return createStableGotoHook(recommendedGotoOptions);
|
|
5478
|
-
},
|
|
5479
|
-
async getPlaywrightCrawlerOptions(options = {}) {
|
|
5480
|
-
const runtime2 = await CloakBrowserLaunch.createPlaywrightCrawlerRuntime(options);
|
|
5481
|
-
return Object.defineProperties(runtime2.crawlerOptions, {
|
|
5482
|
-
cleanup: {
|
|
5483
|
-
enumerable: false,
|
|
5484
|
-
value: runtime2.cleanup
|
|
5485
|
-
},
|
|
5486
|
-
closeActiveBrowsers: {
|
|
5487
|
-
enumerable: false,
|
|
5488
|
-
value: runtime2.closeActiveBrowsers
|
|
5489
|
-
},
|
|
5490
|
-
forceTerminateActiveProcesses: {
|
|
5491
|
-
enumerable: false,
|
|
5492
|
-
value: runtime2.forceTerminateActiveProcesses
|
|
5493
|
-
}
|
|
5494
|
-
});
|
|
5495
|
-
},
|
|
5496
|
-
async buildLaunchOptions(options = {}) {
|
|
5497
|
-
return await buildCloakLaunchOptions(options);
|
|
5498
|
-
},
|
|
5499
|
-
async createPlaywrightCrawlerRuntime(options = {}) {
|
|
5500
|
-
const normalizedOptions = normalizeObject(options);
|
|
5501
|
-
const {
|
|
5502
|
-
proxyConfiguration = {},
|
|
5503
|
-
runInHeadfulMode = false,
|
|
5504
|
-
isRunningOnApify = false,
|
|
5505
|
-
launcher = null,
|
|
5506
|
-
cloakOptions = {},
|
|
5507
|
-
humanizeOptions = DEFAULT_CLOAK_HUMANIZE_OPTIONS,
|
|
5508
|
-
crawlerBaseOptions = {},
|
|
5509
|
-
browserPoolOptions = {},
|
|
5510
|
-
launchContext = {},
|
|
5511
|
-
preNavigationHooks = [],
|
|
5512
|
-
postNavigationHooks = [],
|
|
5513
|
-
recommendedGotoOptions = DEFAULT_CLOAK_GOTO_OPTIONS
|
|
5514
|
-
} = normalizedOptions;
|
|
5515
|
-
const normalizedCloakOptions = normalizeObject(cloakOptions);
|
|
5516
|
-
const activeBrowsers = /* @__PURE__ */ new Set();
|
|
5517
|
-
const patchedBrowsers = /* @__PURE__ */ new WeakSet();
|
|
5518
|
-
const defaultArgs = isRunningOnApify ? ["--no-sandbox", "--disable-setuid-sandbox"] : [];
|
|
5519
|
-
const extraArgs = normalizeStringArray(normalizedCloakOptions.args);
|
|
5520
|
-
const proxy = hasOwn(normalizedCloakOptions, "proxy") ? normalizedCloakOptions.proxy : resolveCloakBrowserProxy(proxyConfiguration);
|
|
5521
|
-
const headless = hasOwn(normalizedCloakOptions, "headless") ? normalizedCloakOptions.headless : !runInHeadfulMode || isRunningOnApify;
|
|
5522
|
-
const mergedCloakOptions = {
|
|
5523
|
-
...normalizedCloakOptions,
|
|
5524
|
-
headless,
|
|
5525
|
-
proxy,
|
|
5526
|
-
args: [...defaultArgs, ...extraArgs]
|
|
5527
|
-
};
|
|
5528
|
-
const launchOptions = await buildCloakLaunchOptions(mergedCloakOptions);
|
|
5529
|
-
const fingerprintArg = extractFingerprintArg(launchOptions);
|
|
5530
|
-
const internalPreNavigationHook = createStableGotoHook(recommendedGotoOptions);
|
|
5531
|
-
const normalizedPreNavigationHooks = Array.isArray(preNavigationHooks) ? preNavigationHooks : [];
|
|
5532
|
-
const normalizedPostNavigationHooks = Array.isArray(postNavigationHooks) ? postNavigationHooks : [];
|
|
5533
|
-
const crawlerOptions = {
|
|
5534
|
-
...DEFAULT_CLOAK_CRAWLER_BASE_OPTIONS,
|
|
5535
|
-
...normalizeObject(crawlerBaseOptions),
|
|
5536
|
-
headless,
|
|
5537
|
-
launchContext: {
|
|
5538
|
-
useIncognitoPages: true,
|
|
5539
|
-
...normalizeObject(launchContext),
|
|
5540
|
-
...launcher ? { launcher } : {},
|
|
5541
|
-
launchOptions
|
|
5542
|
-
},
|
|
5543
|
-
browserPoolOptions: attachCloakBrowserHumanizeHook({
|
|
5544
|
-
browserPoolOptions,
|
|
5545
|
-
activeBrowsers,
|
|
5546
|
-
patchedBrowsers,
|
|
5547
|
-
humanizeOptions
|
|
5548
|
-
}),
|
|
5549
|
-
preNavigationHooks: [internalPreNavigationHook, ...normalizedPreNavigationHooks],
|
|
5550
|
-
...normalizedPostNavigationHooks.length > 0 ? { postNavigationHooks: normalizedPostNavigationHooks } : {}
|
|
5551
|
-
};
|
|
5552
|
-
const closeActiveBrowsers = async () => {
|
|
5553
|
-
await closeTrackedBrowsers(activeBrowsers);
|
|
5554
|
-
};
|
|
5555
|
-
const forceTerminateActiveProcesses = async () => {
|
|
5556
|
-
await forceTerminateBrowsersByFingerprintArg(fingerprintArg);
|
|
5557
|
-
};
|
|
5558
|
-
const cleanup = async () => {
|
|
5559
|
-
await closeActiveBrowsers();
|
|
5560
|
-
await forceTerminateActiveProcesses();
|
|
5561
|
-
};
|
|
5562
|
-
return {
|
|
5563
|
-
headless,
|
|
5564
|
-
launchOptions,
|
|
5565
|
-
fingerprintArg,
|
|
5566
|
-
crawlerOptions,
|
|
5567
|
-
closeActiveBrowsers,
|
|
5568
|
-
forceTerminateActiveProcesses,
|
|
5569
|
-
cleanup
|
|
5570
|
-
};
|
|
5571
|
-
}
|
|
5572
|
-
};
|
|
5573
|
-
|
|
5574
|
-
// src/launch.js
|
|
5575
|
-
var launchStrategies = {
|
|
5576
|
-
[Mode.Default]: DefaultLaunch,
|
|
5577
|
-
[Mode.CloakBrowser]: CloakBrowserLaunch
|
|
5578
|
-
};
|
|
5579
|
-
var launchFacadeMethods = Object.freeze({
|
|
5580
|
-
getPlaywrightCrawlerOptions: { enumerable: true }
|
|
5581
|
-
});
|
|
5582
|
-
var Launch = createDelegatedFacade("Launch", launchStrategies, launchFacadeMethods);
|
|
5583
|
-
|
|
5584
5074
|
// src/live-view.js
|
|
5585
5075
|
import express from "express";
|
|
5586
5076
|
import { Actor } from "apify";
|
|
@@ -6668,7 +6158,7 @@ var Mutation = {
|
|
|
6668
6158
|
const overallTimeout = options.timeout ?? 180 * 1e3;
|
|
6669
6159
|
const onMutation = options.onMutation;
|
|
6670
6160
|
const pollInterval = 500;
|
|
6671
|
-
const
|
|
6161
|
+
const sleep = (ms) => new Promise((resolve) => {
|
|
6672
6162
|
setTimeout(resolve, ms);
|
|
6673
6163
|
});
|
|
6674
6164
|
const truncate = (value, max = 800) => {
|
|
@@ -6829,8 +6319,8 @@ var Mutation = {
|
|
|
6829
6319
|
throw e;
|
|
6830
6320
|
}
|
|
6831
6321
|
}
|
|
6832
|
-
let
|
|
6833
|
-
if (!
|
|
6322
|
+
let state = await buildState();
|
|
6323
|
+
if (!state?.hasMatched) {
|
|
6834
6324
|
logger12.warning("waitForStableAcrossRoots \u672A\u627E\u5230\u53EF\u76D1\u63A7\u7684\u5143\u7D20");
|
|
6835
6325
|
return { mutationCount: 0, stableTime: 0, wasPaused: false };
|
|
6836
6326
|
}
|
|
@@ -6838,7 +6328,7 @@ var Mutation = {
|
|
|
6838
6328
|
let stableSince = 0;
|
|
6839
6329
|
let isPaused = false;
|
|
6840
6330
|
let wasPaused = false;
|
|
6841
|
-
let lastSnapshotKey =
|
|
6331
|
+
let lastSnapshotKey = state.snapshotKey;
|
|
6842
6332
|
const applyPauseSignal = (signal) => {
|
|
6843
6333
|
const nextPaused = signal === "__PAUSE__";
|
|
6844
6334
|
if (nextPaused) {
|
|
@@ -6852,15 +6342,15 @@ var Mutation = {
|
|
|
6852
6342
|
};
|
|
6853
6343
|
const initialSignal = await invokeMutationCallback({
|
|
6854
6344
|
mutationCount: 0,
|
|
6855
|
-
html:
|
|
6856
|
-
text:
|
|
6857
|
-
mutationNodes:
|
|
6345
|
+
html: state.html || "",
|
|
6346
|
+
text: state.text || "",
|
|
6347
|
+
mutationNodes: state.mutationNodes || []
|
|
6858
6348
|
});
|
|
6859
6349
|
applyPauseSignal(initialSignal);
|
|
6860
6350
|
const deadline = Date.now() + overallTimeout;
|
|
6861
|
-
let lastState =
|
|
6351
|
+
let lastState = state;
|
|
6862
6352
|
while (Date.now() < deadline) {
|
|
6863
|
-
await
|
|
6353
|
+
await sleep(pollInterval);
|
|
6864
6354
|
lastState = await buildState();
|
|
6865
6355
|
if (!lastState?.hasMatched) {
|
|
6866
6356
|
continue;
|
|
@@ -7948,7 +7438,7 @@ var Logger = {
|
|
|
7948
7438
|
};
|
|
7949
7439
|
|
|
7950
7440
|
// src/share.js
|
|
7951
|
-
import
|
|
7441
|
+
import delay4 from "delay";
|
|
7952
7442
|
|
|
7953
7443
|
// src/internals/watermarkify.js
|
|
7954
7444
|
var DEFAULT_TIMEZONE_OFFSET = 8;
|
|
@@ -8444,10 +7934,7 @@ var buildWatermarkifyRenderHtml = ({ imageSrc, overlaySvg, width, height, imageH
|
|
|
8444
7934
|
</html>
|
|
8445
7935
|
`;
|
|
8446
7936
|
};
|
|
8447
|
-
var
|
|
8448
|
-
return String(value || "default").trim().toLowerCase() === "cloakbrowser" ? "cloakbrowser" : "default";
|
|
8449
|
-
};
|
|
8450
|
-
var composeScreenshotBufferWithBrowser = async (page, buffer, overlaySvg, imageInfo = {}, options = {}) => {
|
|
7937
|
+
var composeScreenshotBufferWithBrowser = async (page, buffer, overlaySvg, imageInfo = {}) => {
|
|
8451
7938
|
if (!page || typeof page.context !== "function") {
|
|
8452
7939
|
logger13.warning("watermarkify \u6D4F\u89C8\u5668\u5408\u6210\u8DF3\u8FC7: \u7F3A\u5C11\u53EF\u7528 page");
|
|
8453
7940
|
return buffer;
|
|
@@ -8471,35 +7958,15 @@ var composeScreenshotBufferWithBrowser = async (page, buffer, overlaySvg, imageI
|
|
|
8471
7958
|
height: viewportHeight
|
|
8472
7959
|
}).catch(() => {
|
|
8473
7960
|
});
|
|
8474
|
-
|
|
8475
|
-
|
|
8476
|
-
|
|
8477
|
-
|
|
8478
|
-
|
|
8479
|
-
|
|
8480
|
-
|
|
8481
|
-
|
|
8482
|
-
|
|
8483
|
-
await renderPage.goto("about:blank", {
|
|
8484
|
-
waitUntil: "commit"
|
|
8485
|
-
}).catch(() => {
|
|
8486
|
-
});
|
|
8487
|
-
await renderPage.evaluate((html) => {
|
|
8488
|
-
document.open();
|
|
8489
|
-
document.write(html);
|
|
8490
|
-
document.close();
|
|
8491
|
-
}, renderHtml);
|
|
8492
|
-
} else {
|
|
8493
|
-
await renderPage.setContent(buildWatermarkifyRenderHtml({
|
|
8494
|
-
imageSrc: `data:${imageInfo.mimeType || "image/png"};base64,${buffer.toString("base64")}`,
|
|
8495
|
-
overlaySvg,
|
|
8496
|
-
width: safeWidth,
|
|
8497
|
-
height: safeHeight,
|
|
8498
|
-
imageHeight: safeImageHeight
|
|
8499
|
-
}), {
|
|
8500
|
-
waitUntil: "load"
|
|
8501
|
-
});
|
|
8502
|
-
}
|
|
7961
|
+
await renderPage.setContent(buildWatermarkifyRenderHtml({
|
|
7962
|
+
imageSrc: `data:${imageInfo.mimeType || "image/png"};base64,${buffer.toString("base64")}`,
|
|
7963
|
+
overlaySvg,
|
|
7964
|
+
width: safeWidth,
|
|
7965
|
+
height: safeHeight,
|
|
7966
|
+
imageHeight: safeImageHeight
|
|
7967
|
+
}), {
|
|
7968
|
+
waitUntil: "load"
|
|
7969
|
+
});
|
|
8503
7970
|
await renderPage.waitForFunction(() => {
|
|
8504
7971
|
const image = document.getElementById("pk-base-image");
|
|
8505
7972
|
return image instanceof HTMLImageElement && image.complete && image.naturalWidth > 0 && image.naturalHeight > 0;
|
|
@@ -9434,7 +8901,7 @@ var buildWatermarkifySvg = (meta, imageWidth, imageHeight) => {
|
|
|
9434
8901
|
</svg>
|
|
9435
8902
|
`;
|
|
9436
8903
|
};
|
|
9437
|
-
var watermarkifyScreenshotBuffer = async (buffer, meta, page = null
|
|
8904
|
+
var watermarkifyScreenshotBuffer = async (buffer, meta, page = null) => {
|
|
9438
8905
|
const hasWatermark = meta?.watermark?.enabled !== false && normalizeText(meta?.watermarkText);
|
|
9439
8906
|
const hasStrip = meta?.strip?.enabled !== false && Array.isArray(meta?.stripSegments) && meta.stripSegments.length > 0;
|
|
9440
8907
|
if (!Buffer.isBuffer(buffer) || !meta || !hasWatermark && !hasStrip) {
|
|
@@ -9455,7 +8922,7 @@ var watermarkifyScreenshotBuffer = async (buffer, meta, page = null, options = {
|
|
|
9455
8922
|
if (!overlaySvg) {
|
|
9456
8923
|
return buffer;
|
|
9457
8924
|
}
|
|
9458
|
-
return await composeScreenshotBufferWithBrowser(page, buffer, overlaySvg, outputImageInfo
|
|
8925
|
+
return await composeScreenshotBufferWithBrowser(page, buffer, overlaySvg, outputImageInfo);
|
|
9459
8926
|
};
|
|
9460
8927
|
|
|
9461
8928
|
// src/internals/compression.js
|
|
@@ -10019,7 +9486,7 @@ var Share = {
|
|
|
10019
9486
|
);
|
|
10020
9487
|
nextProgressLogTs = now + 5e3;
|
|
10021
9488
|
}
|
|
10022
|
-
await
|
|
9489
|
+
await delay4(Math.max(0, Math.min(DEFAULT_POLL_INTERVAL_MS, remaining)));
|
|
10023
9490
|
}
|
|
10024
9491
|
if (!timeoutDisabled && share.mode === "response" && stats.responseMatched === 0) {
|
|
10025
9492
|
logger15.warning(
|
|
@@ -10053,7 +9520,6 @@ var Share = {
|
|
|
10053
9520
|
* @param {number} [options.maxBytes] 默认 5MiB,返回 base64 超过后会压缩
|
|
10054
9521
|
* @param {'jpeg'|'jpg'} [options.type] 压缩输出格式,默认 jpeg
|
|
10055
9522
|
* @param {boolean|Object} [options.compression] 传 false 可关闭压缩
|
|
10056
|
-
* @param {'default'|'cloakbrowser'} [options.mode] 截图水印合成模式,默认 default
|
|
10057
9523
|
* @returns {Promise<string>} base64 image
|
|
10058
9524
|
*/
|
|
10059
9525
|
async captureScreen(page, options = {}) {
|
|
@@ -10074,9 +9540,7 @@ var Share = {
|
|
|
10074
9540
|
...screenshotWatermarkify,
|
|
10075
9541
|
capturedAt
|
|
10076
9542
|
});
|
|
10077
|
-
outputBuffer = await watermarkifyScreenshotBuffer(rawBuffer, watermarkifyMeta, page
|
|
10078
|
-
mode: options.mode === "cloakbrowser" ? "cloakbrowser" : "default"
|
|
10079
|
-
});
|
|
9543
|
+
outputBuffer = await watermarkifyScreenshotBuffer(rawBuffer, watermarkifyMeta, page);
|
|
10080
9544
|
}
|
|
10081
9545
|
return await compressImageBufferToBase64(outputBuffer, compression);
|
|
10082
9546
|
}
|
|
@@ -10084,9 +9548,8 @@ var Share = {
|
|
|
10084
9548
|
|
|
10085
9549
|
// entrys/node.js
|
|
10086
9550
|
Logger.setLogger(crawleeLog);
|
|
10087
|
-
var usePlaywrightToolKit = (
|
|
10088
|
-
|
|
10089
|
-
const toolkit = {
|
|
9551
|
+
var usePlaywrightToolKit = () => {
|
|
9552
|
+
return {
|
|
10090
9553
|
ApifyKit,
|
|
10091
9554
|
AntiCheat,
|
|
10092
9555
|
DeviceInput,
|
|
@@ -10106,7 +9569,6 @@ var usePlaywrightToolKit = (mode = "default") => {
|
|
|
10106
9569
|
ByPass,
|
|
10107
9570
|
$Internals: { LOG_TEMPLATES, stripAnsi }
|
|
10108
9571
|
};
|
|
10109
|
-
return toolkit;
|
|
10110
9572
|
};
|
|
10111
9573
|
export {
|
|
10112
9574
|
usePlaywrightToolKit
|