@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.cjs
CHANGED
|
@@ -40,11 +40,9 @@ __export(constants_exports, {
|
|
|
40
40
|
ActorInfo: () => ActorInfo,
|
|
41
41
|
Code: () => Code,
|
|
42
42
|
Device: () => Device,
|
|
43
|
-
Mode: () => Mode,
|
|
44
43
|
PresetOfLiveViewKey: () => PresetOfLiveViewKey,
|
|
45
44
|
Status: () => Status,
|
|
46
|
-
normalizeDevice: () => normalizeDevice
|
|
47
|
-
normalizeMode: () => normalizeMode
|
|
45
|
+
normalizeDevice: () => normalizeDevice
|
|
48
46
|
});
|
|
49
47
|
var Code = {
|
|
50
48
|
Success: 0,
|
|
@@ -64,10 +62,6 @@ var Device = Object.freeze({
|
|
|
64
62
|
Desktop: "desktop",
|
|
65
63
|
Mobile: "mobile"
|
|
66
64
|
});
|
|
67
|
-
var Mode = Object.freeze({
|
|
68
|
-
Default: "default",
|
|
69
|
-
CloakBrowser: "cloakbrowser"
|
|
70
|
-
});
|
|
71
65
|
var normalizeDevice = (value, fallback = Device.Desktop) => {
|
|
72
66
|
const normalizedFallback = String(fallback || "").trim().toLowerCase() === Device.Mobile ? Device.Mobile : Device.Desktop;
|
|
73
67
|
const raw = String(value || "").trim().toLowerCase();
|
|
@@ -75,13 +69,6 @@ var normalizeDevice = (value, fallback = Device.Desktop) => {
|
|
|
75
69
|
if (raw === Device.Desktop) return Device.Desktop;
|
|
76
70
|
return normalizedFallback;
|
|
77
71
|
};
|
|
78
|
-
var normalizeMode = (value, fallback = Mode.Default) => {
|
|
79
|
-
const normalizedFallback = String(fallback || "").trim().toLowerCase() === Mode.CloakBrowser ? Mode.CloakBrowser : Mode.Default;
|
|
80
|
-
const raw = String(value || "").trim().toLowerCase();
|
|
81
|
-
if (raw === Mode.CloakBrowser) return Mode.CloakBrowser;
|
|
82
|
-
if (raw === Mode.Default) return Mode.Default;
|
|
83
|
-
return normalizedFallback;
|
|
84
|
-
};
|
|
85
72
|
var createActorInfo = (info) => {
|
|
86
73
|
const normalizeDomain = (value) => {
|
|
87
74
|
if (!value) return "";
|
|
@@ -149,6 +136,7 @@ var createActorInfo = (info) => {
|
|
|
149
136
|
const buildLandingUrl = ({ protocol: protocol2, domain: domain2, path: path4 }) => {
|
|
150
137
|
const safeProtocol = String(protocol2).trim();
|
|
151
138
|
const safeDomain = normalizeDomain(domain2);
|
|
139
|
+
if (!safeDomain) return "";
|
|
152
140
|
const safePath = normalizePath(path4);
|
|
153
141
|
return `${safeProtocol}://${safeDomain}${safePath}`;
|
|
154
142
|
};
|
|
@@ -356,6 +344,18 @@ var ActorInfo = {
|
|
|
356
344
|
prefix: "",
|
|
357
345
|
xurl: []
|
|
358
346
|
}
|
|
347
|
+
}),
|
|
348
|
+
// 通用网页抓取 Actor:入口 URL 来自 query,因此这里只声明统一的 Actor 元信息。
|
|
349
|
+
webpage: createActorInfo({
|
|
350
|
+
key: "webpage",
|
|
351
|
+
name: "\u901A\u7528\u7F51\u9875",
|
|
352
|
+
domain: "",
|
|
353
|
+
path: "/",
|
|
354
|
+
share: {
|
|
355
|
+
mode: "dom",
|
|
356
|
+
prefix: "",
|
|
357
|
+
xurl: []
|
|
358
|
+
}
|
|
359
359
|
})
|
|
360
360
|
};
|
|
361
361
|
|
|
@@ -780,7 +780,7 @@ var adjustAffixedElementsForExpandedScreenshot = async (page, options = {}) => {
|
|
|
780
780
|
if (safeTargetHeight <= viewportHeight + 1) {
|
|
781
781
|
return 0;
|
|
782
782
|
}
|
|
783
|
-
const
|
|
783
|
+
const hasOwn = (source, key) => Object.prototype.hasOwnProperty.call(source, key);
|
|
784
784
|
const isVisible = (el, style, rect) => {
|
|
785
785
|
if (!el || !style || !rect) return false;
|
|
786
786
|
if (style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse") {
|
|
@@ -820,7 +820,7 @@ var adjustAffixedElementsForExpandedScreenshot = async (page, options = {}) => {
|
|
|
820
820
|
return true;
|
|
821
821
|
});
|
|
822
822
|
topLevelCandidates.forEach(({ el, position, edge }) => {
|
|
823
|
-
if (!
|
|
823
|
+
if (!hasOwn(el.dataset, "pkAffixedAdjusted")) {
|
|
824
824
|
el.dataset.pkAffixedAdjusted = "1";
|
|
825
825
|
el.dataset.pkOrigPosition = el.style.getPropertyValue("position") || "";
|
|
826
826
|
el.dataset.pkOrigPositionPriority = el.style.getPropertyPriority("position") || "";
|
|
@@ -851,7 +851,7 @@ var adjustAffixedElementsForExpandedScreenshot = async (page, options = {}) => {
|
|
|
851
851
|
};
|
|
852
852
|
var restoreAffixedElementsForExpandedScreenshot = async (page) => {
|
|
853
853
|
await page.evaluate((className) => {
|
|
854
|
-
const
|
|
854
|
+
const hasOwn = (source, key) => Object.prototype.hasOwnProperty.call(source, key);
|
|
855
855
|
const expansionKeys = [
|
|
856
856
|
"pkOrigOverflow",
|
|
857
857
|
"pkOrigHeight",
|
|
@@ -859,28 +859,28 @@ var restoreAffixedElementsForExpandedScreenshot = async (page) => {
|
|
|
859
859
|
"pkOrigMaxHeight"
|
|
860
860
|
];
|
|
861
861
|
document.querySelectorAll('[data-pk-affixed-adjusted="1"]').forEach((el) => {
|
|
862
|
-
if (
|
|
862
|
+
if (hasOwn(el.dataset, "pkOrigPosition")) {
|
|
863
863
|
el.style.setProperty("position", el.dataset.pkOrigPosition || "", el.dataset.pkOrigPositionPriority || "");
|
|
864
864
|
delete el.dataset.pkOrigPosition;
|
|
865
865
|
delete el.dataset.pkOrigPositionPriority;
|
|
866
866
|
}
|
|
867
|
-
if (
|
|
867
|
+
if (hasOwn(el.dataset, "pkOrigTop")) {
|
|
868
868
|
el.style.setProperty("top", el.dataset.pkOrigTop || "", el.dataset.pkOrigTopPriority || "");
|
|
869
869
|
delete el.dataset.pkOrigTop;
|
|
870
870
|
delete el.dataset.pkOrigTopPriority;
|
|
871
871
|
}
|
|
872
|
-
if (
|
|
872
|
+
if (hasOwn(el.dataset, "pkOrigBottom")) {
|
|
873
873
|
el.style.setProperty("bottom", el.dataset.pkOrigBottom || "", el.dataset.pkOrigBottomPriority || "");
|
|
874
874
|
delete el.dataset.pkOrigBottom;
|
|
875
875
|
delete el.dataset.pkOrigBottomPriority;
|
|
876
876
|
}
|
|
877
|
-
if (
|
|
877
|
+
if (hasOwn(el.dataset, "pkOrigTranslate")) {
|
|
878
878
|
el.style.setProperty("translate", el.dataset.pkOrigTranslate || "", el.dataset.pkOrigTranslatePriority || "");
|
|
879
879
|
delete el.dataset.pkOrigTranslate;
|
|
880
880
|
delete el.dataset.pkOrigTranslatePriority;
|
|
881
881
|
}
|
|
882
882
|
delete el.dataset.pkAffixedAdjusted;
|
|
883
|
-
const stillExpanded = expansionKeys.some((key) =>
|
|
883
|
+
const stillExpanded = expansionKeys.some((key) => hasOwn(el.dataset, key));
|
|
884
884
|
if (!stillExpanded) {
|
|
885
885
|
el.classList.remove(className);
|
|
886
886
|
}
|
|
@@ -923,7 +923,7 @@ var prepareExpandedFullPageScreenshot = async (page, options = {}) => {
|
|
|
923
923
|
viewportResized
|
|
924
924
|
};
|
|
925
925
|
};
|
|
926
|
-
var restoreExpandedFullPageScreenshot = async (page,
|
|
926
|
+
var restoreExpandedFullPageScreenshot = async (page, state = {}) => {
|
|
927
927
|
await page.evaluate((className) => {
|
|
928
928
|
const targets = new Set([
|
|
929
929
|
...document.querySelectorAll(`.${className}`),
|
|
@@ -947,8 +947,8 @@ var restoreExpandedFullPageScreenshot = async (page, state2 = {}) => {
|
|
|
947
947
|
el.classList.remove(className);
|
|
948
948
|
});
|
|
949
949
|
}, EXPANDED_SCROLLABLE_CLASS);
|
|
950
|
-
if (
|
|
951
|
-
await page.setViewportSize(
|
|
950
|
+
if (state?.originalViewport && state?.viewportResized) {
|
|
951
|
+
await page.setViewportSize(state.originalViewport);
|
|
952
952
|
}
|
|
953
953
|
};
|
|
954
954
|
var capturePageScreenshot = async (page, options = {}) => {
|
|
@@ -1005,21 +1005,21 @@ var capturePageScreenshot = async (page, options = {}) => {
|
|
|
1005
1005
|
}
|
|
1006
1006
|
};
|
|
1007
1007
|
var captureExpandedFullPageScreenshot = async (page, options = {}) => {
|
|
1008
|
-
const
|
|
1008
|
+
const state = await prepareExpandedFullPageScreenshot(page, options);
|
|
1009
1009
|
try {
|
|
1010
1010
|
return await capturePageScreenshot(page, {
|
|
1011
1011
|
fullPage: true,
|
|
1012
1012
|
type: options.type || "png",
|
|
1013
1013
|
quality: options.quality,
|
|
1014
1014
|
timeout: options.timeout,
|
|
1015
|
-
maxClipHeight:
|
|
1015
|
+
maxClipHeight: state.targetHeight
|
|
1016
1016
|
});
|
|
1017
1017
|
} finally {
|
|
1018
1018
|
await restoreAffixedElementsForExpandedScreenshot(page).catch((error) => {
|
|
1019
1019
|
logger.warning(`\u79FB\u52A8\u7AEF\u5438\u9644\u5143\u7D20\u6062\u590D\u5931\u8D25: ${error?.message || error}`);
|
|
1020
1020
|
});
|
|
1021
1021
|
if (options.restore) {
|
|
1022
|
-
await restoreExpandedFullPageScreenshot(page,
|
|
1022
|
+
await restoreExpandedFullPageScreenshot(page, state);
|
|
1023
1023
|
}
|
|
1024
1024
|
}
|
|
1025
1025
|
};
|
|
@@ -1196,6 +1196,19 @@ var ensureLogPath = () => {
|
|
|
1196
1196
|
const label = runId ? `proxy-meter-${runId}-${suffix}.json` : `proxy-meter-${process.pid}-${suffix}.json`;
|
|
1197
1197
|
return import_path.default.join(baseDir, label);
|
|
1198
1198
|
};
|
|
1199
|
+
var ensureStdioLogPath = (logPath) => `${logPath}.stdio.log`;
|
|
1200
|
+
var writeChildOutput = (stream, output, prefix) => {
|
|
1201
|
+
if (!stream || !output) return;
|
|
1202
|
+
output.on("data", (chunk) => {
|
|
1203
|
+
const text = chunk?.toString?.() || String(chunk || "");
|
|
1204
|
+
if (!text) return;
|
|
1205
|
+
for (const line of text.split(/\r?\n/)) {
|
|
1206
|
+
if (!line) continue;
|
|
1207
|
+
stream.write(`[${(/* @__PURE__ */ new Date()).toISOString()}] ${prefix} ${line}
|
|
1208
|
+
`);
|
|
1209
|
+
}
|
|
1210
|
+
});
|
|
1211
|
+
};
|
|
1199
1212
|
var readSnapshot = (logPath) => {
|
|
1200
1213
|
if (!logPath || !(0, import_fs.existsSync)(logPath)) return null;
|
|
1201
1214
|
try {
|
|
@@ -1363,6 +1376,7 @@ var startProxyMeter = (options = {}) => {
|
|
|
1363
1376
|
observedDomainResourceTypes = /* @__PURE__ */ new Map();
|
|
1364
1377
|
const port = pickFreePort();
|
|
1365
1378
|
const logPath = ensureLogPath();
|
|
1379
|
+
const stdioLogPath = ensureStdioLogPath(logPath);
|
|
1366
1380
|
const scriptPath = resolveScriptPath();
|
|
1367
1381
|
const debugMode = Boolean(options.debugMode);
|
|
1368
1382
|
const debugMaxEvents = Math.max(10, toSafeInt(options.debugMaxEvents) || DEFAULT_DEBUG_MAX_EVENTS);
|
|
@@ -1375,19 +1389,28 @@ var startProxyMeter = (options = {}) => {
|
|
|
1375
1389
|
PROXY_METER_DEBUG: debugMode ? "1" : "0",
|
|
1376
1390
|
PROXY_METER_DEBUG_MAX_EVENTS: String(debugMaxEvents)
|
|
1377
1391
|
};
|
|
1392
|
+
const stdioLog = (0, import_fs.createWriteStream)(stdioLogPath, { flags: "a" });
|
|
1393
|
+
stdioLog.write(`[${(/* @__PURE__ */ new Date()).toISOString()}] [proxy-meter-runtime] start script=${scriptPath} port=${port} snapshot=${logPath}
|
|
1394
|
+
`);
|
|
1378
1395
|
const child = (0, import_child_process.spawn)(process.execPath, [scriptPath], {
|
|
1379
1396
|
env,
|
|
1380
|
-
stdio: ["ignore", "
|
|
1397
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1381
1398
|
});
|
|
1382
|
-
child.
|
|
1399
|
+
writeChildOutput(stdioLog, child.stdout, "stdout");
|
|
1400
|
+
writeChildOutput(stdioLog, child.stderr, "stderr");
|
|
1401
|
+
child.once("exit", (code, signal) => {
|
|
1402
|
+
stdioLog.write(`[${(/* @__PURE__ */ new Date()).toISOString()}] [proxy-meter-runtime] exit code=${code ?? ""} signal=${signal ?? ""}
|
|
1403
|
+
`);
|
|
1404
|
+
stdioLog.end();
|
|
1383
1405
|
if (code && code !== 0) {
|
|
1384
|
-
logger2.warn(`[proxy-meter] exited with code ${code}`);
|
|
1406
|
+
logger2.warn(`[proxy-meter] exited with code ${code}; stdio=${stdioLogPath}`);
|
|
1385
1407
|
}
|
|
1386
1408
|
});
|
|
1387
1409
|
runtime = {
|
|
1388
1410
|
proc: child,
|
|
1389
1411
|
port,
|
|
1390
1412
|
logPath,
|
|
1413
|
+
stdioLogPath,
|
|
1391
1414
|
startedAt: Date.now()
|
|
1392
1415
|
};
|
|
1393
1416
|
registerCleanup();
|
|
@@ -1915,8 +1938,8 @@ var normalizeBrowserProfile = (value) => {
|
|
|
1915
1938
|
payload: buildBrowserProfilePayload(core, observed)
|
|
1916
1939
|
};
|
|
1917
1940
|
};
|
|
1918
|
-
var rememberRuntimeState = (
|
|
1919
|
-
rememberedRuntimeState = deepClone(
|
|
1941
|
+
var rememberRuntimeState = (state) => {
|
|
1942
|
+
rememberedRuntimeState = deepClone(state);
|
|
1920
1943
|
return rememberedRuntimeState;
|
|
1921
1944
|
};
|
|
1922
1945
|
var normalizeRuntimeState = (source = {}, actor = "") => {
|
|
@@ -1975,7 +1998,7 @@ var RuntimeEnv = {
|
|
|
1975
1998
|
} else {
|
|
1976
1999
|
delete normalizedRuntime.browser_profile;
|
|
1977
2000
|
}
|
|
1978
|
-
const
|
|
2001
|
+
const state = {
|
|
1979
2002
|
actor: resolvedActor,
|
|
1980
2003
|
device,
|
|
1981
2004
|
runtime: normalizedRuntime,
|
|
@@ -1989,73 +2012,73 @@ var RuntimeEnv = {
|
|
|
1989
2012
|
browserProfileCore: browserProfile.core,
|
|
1990
2013
|
browserProfileObserved: browserProfile.observed
|
|
1991
2014
|
};
|
|
1992
|
-
rememberRuntimeState(
|
|
1993
|
-
return
|
|
2015
|
+
rememberRuntimeState(state);
|
|
2016
|
+
return state;
|
|
1994
2017
|
},
|
|
1995
2018
|
// buildEnvPatch 只构造允许回写到后端 env 的字段集合。
|
|
1996
2019
|
buildEnvPatch(source = {}, actor = "") {
|
|
1997
|
-
const
|
|
1998
|
-
const browserProfile = buildBrowserProfilePayload(
|
|
2020
|
+
const state = normalizeRuntimeState(source, actor);
|
|
2021
|
+
const browserProfile = buildBrowserProfilePayload(state.browserProfileCore, state.browserProfileObserved);
|
|
1999
2022
|
const envPatch = {
|
|
2000
|
-
...Array.isArray(
|
|
2001
|
-
...Object.keys(
|
|
2002
|
-
...Object.keys(
|
|
2023
|
+
...Array.isArray(state.cookies) && state.cookies.length > 0 ? { cookies: state.cookies } : {},
|
|
2024
|
+
...Object.keys(state.localStorage || {}).length > 0 ? { local_storage: state.localStorage } : {},
|
|
2025
|
+
...Object.keys(state.sessionStorage || {}).length > 0 ? { session_storage: state.sessionStorage } : {},
|
|
2003
2026
|
...Object.keys(browserProfile).length > 0 ? { browser_profile: browserProfile } : {}
|
|
2004
2027
|
};
|
|
2005
2028
|
return Object.keys(envPatch).length > 0 ? envPatch : null;
|
|
2006
2029
|
},
|
|
2007
2030
|
// hasLoginState 只判断 runtime 是否存在有效载荷,不再区分具体字段来源。
|
|
2008
2031
|
hasLoginState(source = {}, actor = "") {
|
|
2009
|
-
const
|
|
2010
|
-
return isPlainObject(
|
|
2032
|
+
const state = normalizeRuntimeState(source, actor);
|
|
2033
|
+
return isPlainObject(state.runtime) && Object.keys(state.runtime || {}).length > 0;
|
|
2011
2034
|
},
|
|
2012
2035
|
rememberState(source = {}) {
|
|
2013
|
-
const
|
|
2014
|
-
rememberRuntimeState(
|
|
2036
|
+
const state = normalizeRuntimeState(source);
|
|
2037
|
+
rememberRuntimeState(state);
|
|
2015
2038
|
return RuntimeEnv.peekRememberedState();
|
|
2016
2039
|
},
|
|
2017
2040
|
peekRememberedState() {
|
|
2018
2041
|
return rememberedRuntimeState ? deepClone(rememberedRuntimeState) : null;
|
|
2019
2042
|
},
|
|
2020
2043
|
getBrowserProfileCore(source = {}, actor = "") {
|
|
2021
|
-
const
|
|
2022
|
-
return deepClone(
|
|
2044
|
+
const state = normalizeRuntimeState(source, actor);
|
|
2045
|
+
return deepClone(state.browserProfileCore || {});
|
|
2023
2046
|
},
|
|
2024
2047
|
setBrowserProfileCore(source = {}, core = {}, actor = "") {
|
|
2025
|
-
const
|
|
2048
|
+
const state = normalizeRuntimeState(source, actor);
|
|
2026
2049
|
const normalizedCore = normalizeBrowserProfileCore({
|
|
2027
2050
|
...core,
|
|
2028
|
-
device: normalizeKnownDevice(core?.device) ||
|
|
2051
|
+
device: normalizeKnownDevice(core?.device) || state.device
|
|
2029
2052
|
});
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
if (Object.keys(
|
|
2033
|
-
|
|
2053
|
+
state.browserProfileCore = normalizedCore;
|
|
2054
|
+
state.browserProfile = buildBrowserProfilePayload(normalizedCore, state.browserProfileObserved);
|
|
2055
|
+
if (Object.keys(state.browserProfile).length > 0) {
|
|
2056
|
+
state.runtime.browser_profile = state.browserProfile;
|
|
2034
2057
|
} else {
|
|
2035
|
-
delete
|
|
2058
|
+
delete state.runtime.browser_profile;
|
|
2036
2059
|
}
|
|
2037
|
-
rememberRuntimeState(
|
|
2038
|
-
return
|
|
2060
|
+
rememberRuntimeState(state);
|
|
2061
|
+
return state;
|
|
2039
2062
|
},
|
|
2040
2063
|
// applyToPage 只负责把登录态相关字段注入页面:
|
|
2041
2064
|
// cookies / localStorage / sessionStorage。
|
|
2042
2065
|
// 指纹、时区、UA、viewport 的回放发生在 launch.js 启动阶段,不在这里做。
|
|
2043
2066
|
async applyToPage(page, source = {}, options = {}) {
|
|
2044
2067
|
if (!page) return;
|
|
2045
|
-
let
|
|
2068
|
+
let state = normalizeRuntimeState(source, options?.actor || "");
|
|
2046
2069
|
if (typeof options?.preapply === "function") {
|
|
2047
|
-
|
|
2048
|
-
rememberRuntimeState(
|
|
2070
|
+
state = await options.preapply(state) || state;
|
|
2071
|
+
rememberRuntimeState(state);
|
|
2049
2072
|
}
|
|
2050
2073
|
Object.defineProperty(page, PageRuntimeStateKey, {
|
|
2051
2074
|
configurable: true,
|
|
2052
2075
|
enumerable: false,
|
|
2053
2076
|
writable: true,
|
|
2054
|
-
value:
|
|
2077
|
+
value: state
|
|
2055
2078
|
});
|
|
2056
|
-
const localStorage =
|
|
2057
|
-
const sessionStorage =
|
|
2058
|
-
const cookies = (
|
|
2079
|
+
const localStorage = state.localStorage || {};
|
|
2080
|
+
const sessionStorage = state.sessionStorage || {};
|
|
2081
|
+
const cookies = (state.cookies || []).map((cookie) => {
|
|
2059
2082
|
const normalized = { ...cookie };
|
|
2060
2083
|
if (!normalized.path) {
|
|
2061
2084
|
normalized.path = "/";
|
|
@@ -2093,8 +2116,8 @@ var RuntimeEnv = {
|
|
|
2093
2116
|
},
|
|
2094
2117
|
// captureEnvPatch 在任务结束时采集最新环境快照,用于 pushSuccess / pushFailed 自动回写。
|
|
2095
2118
|
async captureEnvPatch(page, source = {}, options = {}) {
|
|
2096
|
-
const
|
|
2097
|
-
const baseline = RuntimeEnv.buildEnvPatch(
|
|
2119
|
+
const state = normalizeRuntimeState(source, options?.actor || "");
|
|
2120
|
+
const baseline = RuntimeEnv.buildEnvPatch(state) || {};
|
|
2098
2121
|
if (!page || typeof page.evaluate !== "function" || typeof page.context !== "function") {
|
|
2099
2122
|
return Object.keys(baseline).length > 0 ? baseline : null;
|
|
2100
2123
|
}
|
|
@@ -2113,7 +2136,7 @@ var RuntimeEnv = {
|
|
|
2113
2136
|
cookies
|
|
2114
2137
|
},
|
|
2115
2138
|
{
|
|
2116
|
-
browserProfileCore:
|
|
2139
|
+
browserProfileCore: state.browserProfileCore
|
|
2117
2140
|
}
|
|
2118
2141
|
);
|
|
2119
2142
|
return RuntimeEnv.mergeEnvPatches(baseline, capturedPatch);
|
|
@@ -2127,11 +2150,11 @@ var RuntimeEnv = {
|
|
|
2127
2150
|
var logger3 = createInternalLogger("ApifyKit");
|
|
2128
2151
|
var resolveRuntimeContext = (input) => {
|
|
2129
2152
|
const rememberedState = RuntimeEnv.peekRememberedState();
|
|
2130
|
-
const
|
|
2131
|
-
const envPatch = RuntimeEnv.buildEnvPatch(
|
|
2153
|
+
const state = rememberedState || RuntimeEnv.parseInput(input || {});
|
|
2154
|
+
const envPatch = RuntimeEnv.buildEnvPatch(state) || null;
|
|
2132
2155
|
return {
|
|
2133
|
-
actor:
|
|
2134
|
-
runtime:
|
|
2156
|
+
actor: state.actor,
|
|
2157
|
+
runtime: state.runtime,
|
|
2135
2158
|
envPatch
|
|
2136
2159
|
};
|
|
2137
2160
|
};
|
|
@@ -2490,99 +2513,8 @@ var Utils = {
|
|
|
2490
2513
|
}
|
|
2491
2514
|
};
|
|
2492
2515
|
|
|
2493
|
-
// src/
|
|
2494
|
-
var
|
|
2495
|
-
mode: Mode.Default
|
|
2496
|
-
};
|
|
2497
|
-
var normalizeStrategies = (strategies) => strategies && typeof strategies === "object" ? strategies : {};
|
|
2498
|
-
var ToolkitContext = {
|
|
2499
|
-
get mode() {
|
|
2500
|
-
return state.mode;
|
|
2501
|
-
},
|
|
2502
|
-
setMode(mode = Mode.Default) {
|
|
2503
|
-
state.mode = normalizeMode(mode, Mode.Default);
|
|
2504
|
-
return state.mode;
|
|
2505
|
-
}
|
|
2506
|
-
};
|
|
2507
|
-
var getToolkitMode = () => state.mode;
|
|
2508
|
-
var setToolkitMode = (mode = Mode.Default) => ToolkitContext.setMode(mode);
|
|
2509
|
-
var resolveModeStrategy = (strategies = {}, mode = getToolkitMode(), fallbackMode = Mode.Default) => {
|
|
2510
|
-
const normalizedStrategies = normalizeStrategies(strategies);
|
|
2511
|
-
const normalizedMode = normalizeMode(mode, fallbackMode);
|
|
2512
|
-
const delegate = normalizedStrategies[normalizedMode] ?? normalizedStrategies[fallbackMode] ?? Object.values(normalizedStrategies).find(Boolean) ?? null;
|
|
2513
|
-
return {
|
|
2514
|
-
mode: normalizedMode,
|
|
2515
|
-
delegate
|
|
2516
|
-
};
|
|
2517
|
-
};
|
|
2518
|
-
|
|
2519
|
-
// src/internals/delegate.js
|
|
2520
|
-
var normalizeArrayMethodDefinition = (definition) => typeof definition === "string" ? { name: definition, enumerable: true } : {
|
|
2521
|
-
name: String(definition?.name || "").trim(),
|
|
2522
|
-
enumerable: definition?.enumerable !== false
|
|
2523
|
-
};
|
|
2524
|
-
var normalizeMethodDefinitions = (methods = {}) => {
|
|
2525
|
-
if (Array.isArray(methods)) {
|
|
2526
|
-
return methods.map(normalizeArrayMethodDefinition).filter((method) => method.name);
|
|
2527
|
-
}
|
|
2528
|
-
if (!methods || typeof methods !== "object") {
|
|
2529
|
-
return [];
|
|
2530
|
-
}
|
|
2531
|
-
return Object.entries(methods).map(([name, definition]) => ({
|
|
2532
|
-
name: String(name || "").trim(),
|
|
2533
|
-
enumerable: definition?.enumerable !== false
|
|
2534
|
-
})).filter((method) => method.name);
|
|
2535
|
-
};
|
|
2536
|
-
var normalizeDelegateResolution = (resolution) => {
|
|
2537
|
-
if (resolution && typeof resolution === "object" && ("delegate" in resolution || "label" in resolution)) {
|
|
2538
|
-
return {
|
|
2539
|
-
delegate: resolution.delegate ?? null,
|
|
2540
|
-
label: String(resolution.label || "current delegate").trim() || "current delegate"
|
|
2541
|
-
};
|
|
2542
|
-
}
|
|
2543
|
-
return {
|
|
2544
|
-
delegate: resolution,
|
|
2545
|
-
label: "current delegate"
|
|
2546
|
-
};
|
|
2547
|
-
};
|
|
2548
|
-
var createMethodDescriptor = (namespace, methodName, enumerable, resolveDelegate) => ({
|
|
2549
|
-
enumerable,
|
|
2550
|
-
value: (...args) => {
|
|
2551
|
-
const { delegate, label } = normalizeDelegateResolution(resolveDelegate(methodName, args));
|
|
2552
|
-
if (typeof delegate?.[methodName] !== "function") {
|
|
2553
|
-
throw new Error(`${namespace}.${methodName} is not available in ${label}`);
|
|
2554
|
-
}
|
|
2555
|
-
return delegate[methodName](...args);
|
|
2556
|
-
}
|
|
2557
|
-
});
|
|
2558
|
-
var withDelegatedProperties = (target = {}, {
|
|
2559
|
-
namespace = "Delegate",
|
|
2560
|
-
methods = {},
|
|
2561
|
-
resolveDelegate = () => null
|
|
2562
|
-
} = {}) => {
|
|
2563
|
-
const descriptors = Object.fromEntries(
|
|
2564
|
-
normalizeMethodDefinitions(methods).map((method) => [
|
|
2565
|
-
method.name,
|
|
2566
|
-
createMethodDescriptor(namespace, method.name, method.enumerable, resolveDelegate)
|
|
2567
|
-
])
|
|
2568
|
-
);
|
|
2569
|
-
return Object.defineProperties(target, descriptors);
|
|
2570
|
-
};
|
|
2571
|
-
var createDelegatedFacade = (namespace, strategies = {}, methods = {}) => {
|
|
2572
|
-
return withDelegatedProperties({}, {
|
|
2573
|
-
namespace,
|
|
2574
|
-
methods,
|
|
2575
|
-
resolveDelegate: () => {
|
|
2576
|
-
const { mode, delegate } = resolveModeStrategy(strategies);
|
|
2577
|
-
return {
|
|
2578
|
-
delegate,
|
|
2579
|
-
label: `${mode} mode`
|
|
2580
|
-
};
|
|
2581
|
-
}
|
|
2582
|
-
});
|
|
2583
|
-
};
|
|
2584
|
-
|
|
2585
|
-
// src/internals/anti-cheat/default.js
|
|
2516
|
+
// src/anti-cheat.js
|
|
2517
|
+
var logger5 = createInternalLogger("AntiCheat");
|
|
2586
2518
|
var BASE_CONFIG = Object.freeze({
|
|
2587
2519
|
locale: "zh-CN",
|
|
2588
2520
|
acceptLanguage: "zh-CN,zh;q=0.9",
|
|
@@ -2617,7 +2549,7 @@ function buildFingerprintOptions({ locale = BASE_CONFIG.locale, browserMajorVers
|
|
|
2617
2549
|
}
|
|
2618
2550
|
return options;
|
|
2619
2551
|
}
|
|
2620
|
-
var
|
|
2552
|
+
var AntiCheat = {
|
|
2621
2553
|
/**
|
|
2622
2554
|
* 获取统一的基础配置
|
|
2623
2555
|
*/
|
|
@@ -2654,54 +2586,6 @@ var DefaultAntiCheat = {
|
|
|
2654
2586
|
}
|
|
2655
2587
|
};
|
|
2656
2588
|
|
|
2657
|
-
// src/internals/anti-cheat/cloakbrowser.js
|
|
2658
|
-
var CLOAK_BROWSER_BASE_CONFIG = Object.freeze({
|
|
2659
|
-
locale: "",
|
|
2660
|
-
acceptLanguage: "",
|
|
2661
|
-
timezoneId: "",
|
|
2662
|
-
timezoneOffset: null,
|
|
2663
|
-
geolocation: null
|
|
2664
|
-
});
|
|
2665
|
-
var normalizeHeaders = (headers) => headers && typeof headers === "object" ? headers : {};
|
|
2666
|
-
var CloakBrowserAntiCheat = {
|
|
2667
|
-
/**
|
|
2668
|
-
* CloakBrowser 自身会负责浏览器指纹,toolkit 在该模式下尽量不再注入额外反检测配置。
|
|
2669
|
-
*/
|
|
2670
|
-
getBaseConfig() {
|
|
2671
|
-
return { ...CLOAK_BROWSER_BASE_CONFIG };
|
|
2672
|
-
},
|
|
2673
|
-
getFingerprintGeneratorOptions() {
|
|
2674
|
-
return {};
|
|
2675
|
-
},
|
|
2676
|
-
getLaunchArgs() {
|
|
2677
|
-
return [];
|
|
2678
|
-
},
|
|
2679
|
-
getTlsFingerprintOptions() {
|
|
2680
|
-
return {};
|
|
2681
|
-
},
|
|
2682
|
-
applyLocaleHeaders(headers, acceptLanguage = "") {
|
|
2683
|
-
const normalizedHeaders = normalizeHeaders(headers);
|
|
2684
|
-
if (acceptLanguage && !normalizedHeaders["accept-language"]) {
|
|
2685
|
-
normalizedHeaders["accept-language"] = acceptLanguage;
|
|
2686
|
-
}
|
|
2687
|
-
return normalizedHeaders;
|
|
2688
|
-
}
|
|
2689
|
-
};
|
|
2690
|
-
|
|
2691
|
-
// src/anti-cheat.js
|
|
2692
|
-
var antiCheatStrategies = {
|
|
2693
|
-
[Mode.Default]: DefaultAntiCheat,
|
|
2694
|
-
[Mode.CloakBrowser]: CloakBrowserAntiCheat
|
|
2695
|
-
};
|
|
2696
|
-
var antiCheatFacadeMethods = Object.freeze({
|
|
2697
|
-
getBaseConfig: { enumerable: true },
|
|
2698
|
-
getFingerprintGeneratorOptions: { enumerable: true },
|
|
2699
|
-
getLaunchArgs: { enumerable: true },
|
|
2700
|
-
getTlsFingerprintOptions: { enumerable: true },
|
|
2701
|
-
applyLocaleHeaders: { enumerable: true }
|
|
2702
|
-
});
|
|
2703
|
-
var AntiCheat = createDelegatedFacade("AntiCheat", antiCheatStrategies, antiCheatFacadeMethods);
|
|
2704
|
-
|
|
2705
2589
|
// src/device-input.js
|
|
2706
2590
|
var resolveDeviceFromPage = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
|
|
2707
2591
|
var assertPage = (page, method) => {
|
|
@@ -3101,12 +2985,12 @@ var resolveDescriptor = (descriptor, device) => {
|
|
|
3101
2985
|
}
|
|
3102
2986
|
return resolved;
|
|
3103
2987
|
};
|
|
3104
|
-
var attachRuntimeState = (page,
|
|
2988
|
+
var attachRuntimeState = (page, state) => {
|
|
3105
2989
|
Object.defineProperty(page, PageRuntimeStateKey, {
|
|
3106
2990
|
configurable: true,
|
|
3107
2991
|
enumerable: false,
|
|
3108
2992
|
writable: true,
|
|
3109
|
-
value:
|
|
2993
|
+
value: state
|
|
3110
2994
|
});
|
|
3111
2995
|
};
|
|
3112
2996
|
var restoreRuntimeState = (page, snapshot) => {
|
|
@@ -3252,1240 +3136,1136 @@ var DeviceView = {
|
|
|
3252
3136
|
}
|
|
3253
3137
|
};
|
|
3254
3138
|
|
|
3255
|
-
// src/internals/humanize/
|
|
3256
|
-
var import_delay3 = __toESM(require("delay"), 1);
|
|
3257
|
-
|
|
3258
|
-
// src/internals/humanize/shared.js
|
|
3139
|
+
// src/internals/humanize/desktop.js
|
|
3259
3140
|
var import_delay2 = __toESM(require("delay"), 1);
|
|
3260
|
-
var
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
if (
|
|
3266
|
-
|
|
3267
|
-
if (typeof target === "string") {
|
|
3268
|
-
element = await page.$(target);
|
|
3269
|
-
}
|
|
3270
|
-
if (!element) {
|
|
3271
|
-
if (throwOnMissing) {
|
|
3272
|
-
throw new Error(`\u627E\u4E0D\u5230\u5143\u7D20 ${String(target)}`);
|
|
3273
|
-
}
|
|
3274
|
-
return null;
|
|
3275
|
-
}
|
|
3276
|
-
return element;
|
|
3277
|
-
};
|
|
3278
|
-
var waitJitter = (base, jitterPercent = 0.3) => (0, import_delay2.default)(jitterMs(base, jitterPercent));
|
|
3279
|
-
|
|
3280
|
-
// src/internals/humanize/mobile.js
|
|
3281
|
-
var logger5 = createInternalLogger("Humanize.Mobile");
|
|
3282
|
-
var initializedPages = /* @__PURE__ */ new WeakSet();
|
|
3283
|
-
var DEFAULT_TAP_TIMEOUT_MS = 2500;
|
|
3284
|
-
var DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS = 1200;
|
|
3285
|
-
var DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS = 900;
|
|
3286
|
-
var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
3287
|
-
var resolveViewport = (page) => page?.viewportSize?.() || { width: 390, height: 844 };
|
|
3288
|
-
var describeTarget = (target) => {
|
|
3289
|
-
if (target == null) return "Current Position";
|
|
3290
|
-
return typeof target === "string" ? target : "ElementHandle";
|
|
3291
|
-
};
|
|
3292
|
-
var clipBoxToViewport = (box, viewport) => {
|
|
3293
|
-
if (!box || !viewport) return box;
|
|
3294
|
-
const left = clamp(box.x, 0, viewport.width);
|
|
3295
|
-
const top = clamp(box.y, 0, viewport.height);
|
|
3296
|
-
const right = clamp(box.x + box.width, 0, viewport.width);
|
|
3297
|
-
const bottom = clamp(box.y + box.height, 0, viewport.height);
|
|
3298
|
-
if (right - left > 1 && bottom - top > 1) {
|
|
3299
|
-
return {
|
|
3300
|
-
x: left,
|
|
3301
|
-
y: top,
|
|
3302
|
-
width: right - left,
|
|
3303
|
-
height: bottom - top
|
|
3304
|
-
};
|
|
3305
|
-
}
|
|
3306
|
-
return box;
|
|
3307
|
-
};
|
|
3308
|
-
var centerPointInBox = (box) => ({
|
|
3309
|
-
x: box.x + box.width / 2,
|
|
3310
|
-
y: box.y + box.height / 2
|
|
3311
|
-
});
|
|
3312
|
-
var withTimeout = async (operation, timeoutMs, label) => {
|
|
3313
|
-
const safeTimeoutMs = Math.max(50, Number(timeoutMs || 0));
|
|
3314
|
-
let timeoutId = null;
|
|
3315
|
-
try {
|
|
3316
|
-
return await Promise.race([
|
|
3317
|
-
Promise.resolve().then(operation),
|
|
3318
|
-
new Promise((_, reject) => {
|
|
3319
|
-
timeoutId = setTimeout(() => {
|
|
3320
|
-
reject(new Error(`${label} timeout after ${safeTimeoutMs}ms`));
|
|
3321
|
-
}, safeTimeoutMs);
|
|
3322
|
-
})
|
|
3323
|
-
]);
|
|
3324
|
-
} finally {
|
|
3325
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
3141
|
+
var import_ghost_cursor_playwright = require("ghost-cursor-playwright");
|
|
3142
|
+
var logger6 = createInternalLogger("Humanize");
|
|
3143
|
+
var $CursorWeakMap = /* @__PURE__ */ new WeakMap();
|
|
3144
|
+
function $GetCursor(page) {
|
|
3145
|
+
const cursor = $CursorWeakMap.get(page);
|
|
3146
|
+
if (!cursor) {
|
|
3147
|
+
throw new Error("Cursor \u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 Humanize.initializeCursor(page)");
|
|
3326
3148
|
}
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
isFixed = true;
|
|
3351
|
-
positioning = style.position;
|
|
3352
|
-
break;
|
|
3353
|
-
}
|
|
3149
|
+
return cursor;
|
|
3150
|
+
}
|
|
3151
|
+
var Humanize = {
|
|
3152
|
+
/**
|
|
3153
|
+
* 生成带抖动的毫秒数 - 基于基础值添加随机浮动 (±30% 默认)
|
|
3154
|
+
* @param {number} base - 基础延迟 (ms)
|
|
3155
|
+
* @param {number} [jitterPercent=0.3] - 抖动百分比 (0.3 = ±30%)
|
|
3156
|
+
* @returns {number} 抖动后的毫秒数
|
|
3157
|
+
*/
|
|
3158
|
+
jitterMs(base, jitterPercent = 0.3) {
|
|
3159
|
+
const jitter = base * jitterPercent * (Math.random() * 2 - 1);
|
|
3160
|
+
return Math.max(10, Math.round(base + jitter));
|
|
3161
|
+
},
|
|
3162
|
+
/**
|
|
3163
|
+
* 初始化页面的 Ghost Cursor(必须在使用其他 cursor 相关方法前调用)
|
|
3164
|
+
*
|
|
3165
|
+
* @param {import('playwright').Page} page
|
|
3166
|
+
* @returns {Promise<void>}
|
|
3167
|
+
*/
|
|
3168
|
+
async initializeCursor(page) {
|
|
3169
|
+
if ($CursorWeakMap.has(page)) {
|
|
3170
|
+
logger6.debug("initializeCursor: cursor already exists, skipping");
|
|
3171
|
+
return;
|
|
3354
3172
|
}
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3173
|
+
logger6.start("initializeCursor", "creating cursor");
|
|
3174
|
+
const cursor = await (0, import_ghost_cursor_playwright.createCursor)(page);
|
|
3175
|
+
$CursorWeakMap.set(page, cursor);
|
|
3176
|
+
logger6.success("initializeCursor", "cursor initialized");
|
|
3177
|
+
},
|
|
3178
|
+
/**
|
|
3179
|
+
* 人类化鼠标移动 - 使用 ghost-cursor 移动到指定位置或元素
|
|
3180
|
+
*
|
|
3181
|
+
* @param {import('playwright').Page} page
|
|
3182
|
+
* @param {string|{x: number, y: number}|import('playwright').ElementHandle} target - CSS选择器、坐标对象或元素句柄
|
|
3183
|
+
*/
|
|
3184
|
+
async humanMove(page, target) {
|
|
3185
|
+
const cursor = $GetCursor(page);
|
|
3186
|
+
logger6.start("humanMove", `target=${typeof target === "string" ? target : "element/coords"}`);
|
|
3187
|
+
try {
|
|
3188
|
+
if (typeof target === "string") {
|
|
3189
|
+
const element = await page.$(target);
|
|
3190
|
+
if (!element) {
|
|
3191
|
+
logger6.warn(`humanMove: \u5143\u7D20\u4E0D\u5B58\u5728 ${target}`);
|
|
3192
|
+
return false;
|
|
3193
|
+
}
|
|
3194
|
+
const box = await element.boundingBox();
|
|
3195
|
+
if (!box) {
|
|
3196
|
+
logger6.warn(`humanMove: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E ${target}`);
|
|
3197
|
+
return false;
|
|
3198
|
+
}
|
|
3199
|
+
const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
|
|
3200
|
+
const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
|
|
3201
|
+
await cursor.actions.move({ x, y });
|
|
3202
|
+
} else if (target && typeof target.x === "number" && typeof target.y === "number") {
|
|
3203
|
+
await cursor.actions.move(target);
|
|
3204
|
+
} else if (target && typeof target.boundingBox === "function") {
|
|
3205
|
+
const box = await target.boundingBox();
|
|
3206
|
+
if (box) {
|
|
3207
|
+
const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
|
|
3208
|
+
const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
|
|
3209
|
+
await cursor.actions.move({ x, y });
|
|
3210
|
+
}
|
|
3370
3211
|
}
|
|
3212
|
+
logger6.success("humanMove");
|
|
3213
|
+
return true;
|
|
3214
|
+
} catch (error) {
|
|
3215
|
+
logger6.fail("humanMove", error);
|
|
3216
|
+
throw error;
|
|
3371
3217
|
}
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
const
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
if (pointElement === el || el.contains(pointElement) || pointElement.contains(el)) {
|
|
3401
|
-
return true;
|
|
3402
|
-
}
|
|
3403
|
-
const common = commonAncestor(el, pointElement);
|
|
3404
|
-
if (!common) return false;
|
|
3405
|
-
const commonRect = common.getBoundingClientRect?.();
|
|
3406
|
-
if (!commonRect || commonRect.width <= 0 || commonRect.height <= 0) return false;
|
|
3407
|
-
const commonArea = commonRect.width * commonRect.height;
|
|
3408
|
-
const targetArea = Math.max(1, rect.width * rect.height);
|
|
3409
|
-
const maxSharedRegionArea = Math.max(targetArea * 12, 4096);
|
|
3410
|
-
if (commonArea > maxSharedRegionArea) return false;
|
|
3411
|
-
if (commonRect.width > Math.max(rect.width * 8, 120) || commonRect.height > Math.max(rect.height * 8, 120)) {
|
|
3412
|
-
return false;
|
|
3413
|
-
}
|
|
3414
|
-
return common.contains(el) && common.contains(pointElement);
|
|
3415
|
-
};
|
|
3416
|
-
const describeElement = (node) => {
|
|
3417
|
-
if (!node) return null;
|
|
3418
|
-
const className = typeof node.className === "string" ? node.className : node.className && typeof node.className.baseVal === "string" ? node.className.baseVal : "";
|
|
3419
|
-
const rect2 = node.getBoundingClientRect?.();
|
|
3420
|
-
const style = window.getComputedStyle(node);
|
|
3421
|
-
return {
|
|
3422
|
-
tag: node.tagName,
|
|
3423
|
-
id: node.id || "",
|
|
3424
|
-
className,
|
|
3425
|
-
isFixed: Boolean(style && (style.position === "fixed" || style.position === "sticky")),
|
|
3426
|
-
positioning: style?.position || "",
|
|
3427
|
-
top: rect2 ? rect2.top : null,
|
|
3428
|
-
bottom: rect2 ? rect2.bottom : null,
|
|
3429
|
-
left: rect2 ? rect2.left : null,
|
|
3430
|
-
right: rect2 ? rect2.right : null
|
|
3431
|
-
};
|
|
3432
|
-
};
|
|
3433
|
-
const samplePoints = [
|
|
3434
|
-
{ x: cx, y: cy },
|
|
3435
|
-
{ x: visibleLeft + Math.min(8, Math.max(1, visibleWidth * 0.25)), y: cy },
|
|
3436
|
-
{ x: visibleRight - Math.min(8, Math.max(1, visibleWidth * 0.25)), y: cy },
|
|
3437
|
-
{ x: cx, y: visibleTop + Math.min(8, Math.max(1, visibleHeight * 0.25)) },
|
|
3438
|
-
{ x: cx, y: visibleBottom - Math.min(8, Math.max(1, visibleHeight * 0.25)) }
|
|
3439
|
-
];
|
|
3440
|
-
let obstruction = null;
|
|
3441
|
-
for (const point of samplePoints) {
|
|
3442
|
-
const pointElement = document.elementFromPoint(point.x, point.y);
|
|
3443
|
-
if (sameTapTarget(pointElement)) {
|
|
3444
|
-
return { code: "VISIBLE", isFixed, positioning };
|
|
3445
|
-
}
|
|
3446
|
-
obstruction = obstruction || describeElement(pointElement);
|
|
3447
|
-
}
|
|
3448
|
-
if (obstruction) {
|
|
3449
|
-
return {
|
|
3450
|
-
code: "OBSTRUCTED",
|
|
3451
|
-
reason: "\u88AB\u906E\u6321",
|
|
3452
|
-
direction: cy > viewH / 2 ? "down" : "up",
|
|
3453
|
-
obstruction,
|
|
3454
|
-
cy,
|
|
3455
|
-
viewH,
|
|
3456
|
-
isFixed,
|
|
3457
|
-
positioning
|
|
3458
|
-
};
|
|
3459
|
-
}
|
|
3460
|
-
return { code: "VISIBLE", isFixed, positioning };
|
|
3461
|
-
});
|
|
3462
|
-
};
|
|
3463
|
-
var activateElementFallback = async (element, point = null, options = {}) => {
|
|
3464
|
-
return element.evaluate((el, { innerOptions }) => {
|
|
3465
|
-
const isEditable = (node) => {
|
|
3466
|
-
if (!node || node.nodeType !== Node.ELEMENT_NODE) return false;
|
|
3467
|
-
if (node.isContentEditable) return true;
|
|
3468
|
-
if (node instanceof HTMLTextAreaElement) return !node.disabled && !node.readOnly;
|
|
3469
|
-
if (node instanceof HTMLInputElement) {
|
|
3470
|
-
return !node.disabled && !node.readOnly && typeof node.select === "function";
|
|
3471
|
-
}
|
|
3472
|
-
return false;
|
|
3473
|
-
};
|
|
3474
|
-
const findEditable = (node) => {
|
|
3475
|
-
for (let current = node; current && current !== document.body; current = current.parentElement) {
|
|
3476
|
-
if (isEditable(current)) return current;
|
|
3477
|
-
}
|
|
3478
|
-
return null;
|
|
3479
|
-
};
|
|
3480
|
-
const editable = findEditable(el);
|
|
3481
|
-
if (editable && typeof editable.focus === "function") {
|
|
3482
|
-
editable.focus({ preventScroll: true });
|
|
3483
|
-
if (innerOptions.editableOnly) {
|
|
3484
|
-
return { activated: true, method: "focus", tag: editable.tagName || "" };
|
|
3218
|
+
},
|
|
3219
|
+
/**
|
|
3220
|
+
* 渐进式滚动到元素可见(仅处理 Y 轴滚动)
|
|
3221
|
+
* 返回 restore 方法,用于将滚动容器恢复到原位置
|
|
3222
|
+
*
|
|
3223
|
+
* @param {import('playwright').Page} page
|
|
3224
|
+
* @param {string|import('playwright').ElementHandle} target - CSS 选择器或元素句柄
|
|
3225
|
+
* @param {Object} [options]
|
|
3226
|
+
* @param {number} [options.maxSteps=20] - 最大滚动步数
|
|
3227
|
+
* @param {number} [options.minStep=260] - 单次滚动最小步长
|
|
3228
|
+
* @param {number} [options.maxStep=800] - 单次滚动最大步长
|
|
3229
|
+
* @param {number} [options.maxDurationMs] - 最长耗时上限 (默认随 maxSteps 估算)
|
|
3230
|
+
*/
|
|
3231
|
+
async humanScroll(page, target, options = {}) {
|
|
3232
|
+
const {
|
|
3233
|
+
maxSteps = 20,
|
|
3234
|
+
minStep = 260,
|
|
3235
|
+
maxStep = 800,
|
|
3236
|
+
maxDurationMs = maxSteps * 220 + 800
|
|
3237
|
+
} = options;
|
|
3238
|
+
const targetDesc = typeof target === "string" ? target : "ElementHandle";
|
|
3239
|
+
logger6.start("humanScroll", `target=${targetDesc}`);
|
|
3240
|
+
let element;
|
|
3241
|
+
if (typeof target === "string") {
|
|
3242
|
+
element = await page.$(target);
|
|
3243
|
+
if (!element) {
|
|
3244
|
+
logger6.warn(`humanScroll | \u5143\u7D20\u672A\u627E\u5230: ${target}`);
|
|
3245
|
+
return { element: null, didScroll: false };
|
|
3485
3246
|
}
|
|
3247
|
+
} else {
|
|
3248
|
+
element = target;
|
|
3486
3249
|
}
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
};
|
|
3514
|
-
var getScrollableRect = async (element) => {
|
|
3515
|
-
return element.evaluate((el) => {
|
|
3516
|
-
const isScrollable = (node) => {
|
|
3517
|
-
const style = window.getComputedStyle(node);
|
|
3518
|
-
if (!style) return false;
|
|
3519
|
-
const overflowY = style.overflowY;
|
|
3520
|
-
if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
|
|
3521
|
-
return node.scrollHeight > node.clientHeight + 1;
|
|
3522
|
-
};
|
|
3523
|
-
let current = el;
|
|
3524
|
-
while (current && current !== document.body) {
|
|
3525
|
-
if (isScrollable(current)) {
|
|
3526
|
-
const rect = current.getBoundingClientRect();
|
|
3527
|
-
if (rect && rect.width > 0 && rect.height > 0) {
|
|
3250
|
+
const cursor = $GetCursor(page);
|
|
3251
|
+
let didScroll = false;
|
|
3252
|
+
const checkVisibility = async () => {
|
|
3253
|
+
return await element.evaluate((el) => {
|
|
3254
|
+
const rect = el.getBoundingClientRect();
|
|
3255
|
+
if (!rect || rect.width === 0 || rect.height === 0) {
|
|
3256
|
+
return { code: "ZERO_DIMENSIONS", reason: "\u5C3A\u5BF8\u4E3A\u96F6" };
|
|
3257
|
+
}
|
|
3258
|
+
const cx = rect.left + rect.width / 2;
|
|
3259
|
+
const cy = rect.top + rect.height / 2;
|
|
3260
|
+
const viewH = window.innerHeight;
|
|
3261
|
+
const viewW = window.innerWidth;
|
|
3262
|
+
let isFixed = false;
|
|
3263
|
+
for (let node = el; node && node !== document.body; node = node.parentElement) {
|
|
3264
|
+
const style = window.getComputedStyle(node);
|
|
3265
|
+
if (style && style.position === "fixed") {
|
|
3266
|
+
isFixed = true;
|
|
3267
|
+
break;
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
3270
|
+
if (cy < 0 || cy > viewH || cx < 0 || cx > viewW) {
|
|
3271
|
+
const direction = cy < 0 ? "up" : cy > viewH ? "down" : "unknown";
|
|
3272
|
+
return { code: "OUT_OF_VIEWPORT", reason: "\u4E0D\u5728\u89C6\u53E3\u5185", direction, cy, viewH, isFixed };
|
|
3273
|
+
}
|
|
3274
|
+
const pointElement = document.elementFromPoint(cx, cy);
|
|
3275
|
+
if (pointElement && !el.contains(pointElement) && !pointElement.contains(el)) {
|
|
3528
3276
|
return {
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3277
|
+
code: "OBSTRUCTED",
|
|
3278
|
+
reason: "\u88AB\u906E\u6321",
|
|
3279
|
+
obstruction: {
|
|
3280
|
+
tag: pointElement.tagName,
|
|
3281
|
+
id: pointElement.id,
|
|
3282
|
+
className: pointElement.className
|
|
3283
|
+
},
|
|
3284
|
+
cy,
|
|
3285
|
+
// Return Center Y for smart direction calculation
|
|
3286
|
+
viewH,
|
|
3287
|
+
isFixed
|
|
3533
3288
|
};
|
|
3534
3289
|
}
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
}
|
|
3538
|
-
return null;
|
|
3539
|
-
});
|
|
3540
|
-
};
|
|
3541
|
-
var scrollScrollableAncestor = async (element, deltaY) => {
|
|
3542
|
-
return element.evaluate((el, amount) => {
|
|
3543
|
-
const isScrollable = (node) => {
|
|
3544
|
-
const style = window.getComputedStyle(node);
|
|
3545
|
-
if (!style) return false;
|
|
3546
|
-
const overflowY = style.overflowY;
|
|
3547
|
-
if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
|
|
3548
|
-
return node.scrollHeight > node.clientHeight + 1;
|
|
3290
|
+
return { code: "VISIBLE", isFixed };
|
|
3291
|
+
});
|
|
3549
3292
|
};
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
scrollTop: current.scrollTop
|
|
3293
|
+
const getScrollableRect2 = async () => {
|
|
3294
|
+
return await element.evaluate((el) => {
|
|
3295
|
+
const isScrollable = (node) => {
|
|
3296
|
+
const style = window.getComputedStyle(node);
|
|
3297
|
+
if (!style) return false;
|
|
3298
|
+
const overflowY = style.overflowY;
|
|
3299
|
+
if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
|
|
3300
|
+
return node.scrollHeight > node.clientHeight + 1;
|
|
3559
3301
|
};
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
};
|
|
3572
|
-
var scrollAwayFromObstruction = async (element, status) => {
|
|
3573
|
-
return element.evaluate((el, innerStatus) => {
|
|
3574
|
-
const isScrollable = (node) => {
|
|
3575
|
-
const style = window.getComputedStyle(node);
|
|
3576
|
-
if (!style) return false;
|
|
3577
|
-
const overflowY = style.overflowY;
|
|
3578
|
-
if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
|
|
3579
|
-
return node.scrollHeight > node.clientHeight + 1;
|
|
3580
|
-
};
|
|
3581
|
-
let scroller = el;
|
|
3582
|
-
while (scroller && scroller !== document.body) {
|
|
3583
|
-
if (isScrollable(scroller)) break;
|
|
3584
|
-
scroller = scroller.parentElement;
|
|
3585
|
-
}
|
|
3586
|
-
if (!scroller || scroller === document.body) {
|
|
3587
|
-
return { moved: false, scrollTop: 0, deltaY: 0 };
|
|
3588
|
-
}
|
|
3589
|
-
const rect = el.getBoundingClientRect();
|
|
3590
|
-
const scrollerRect = scroller.getBoundingClientRect();
|
|
3591
|
-
const obstruction = innerStatus?.obstruction || {};
|
|
3592
|
-
const obstructionTop = Number(obstruction.top);
|
|
3593
|
-
const obstructionBottom = Number(obstruction.bottom);
|
|
3594
|
-
const obstructionMiddle = Number.isFinite(obstructionTop) && Number.isFinite(obstructionBottom) ? (obstructionTop + obstructionBottom) / 2 : scrollerRect.top + scrollerRect.height / 2;
|
|
3595
|
-
const scrollerMiddle = scrollerRect.top + scrollerRect.height / 2;
|
|
3596
|
-
const padding = 18;
|
|
3597
|
-
let deltaY = 0;
|
|
3598
|
-
if (Number.isFinite(obstructionTop) && rect.bottom > obstructionTop && obstructionTop >= scrollerRect.top && obstructionTop <= scrollerRect.bottom && obstructionMiddle >= scrollerMiddle) {
|
|
3599
|
-
deltaY = rect.bottom - obstructionTop + padding;
|
|
3600
|
-
} else if (Number.isFinite(obstructionBottom) && rect.top < obstructionBottom && obstructionBottom >= scrollerRect.top && obstructionBottom <= scrollerRect.bottom && obstructionMiddle < scrollerMiddle) {
|
|
3601
|
-
deltaY = rect.top - obstructionBottom - padding;
|
|
3602
|
-
} else {
|
|
3603
|
-
const fallbackDistance = Math.max(48, Math.min(180, scrollerRect.height * 0.45));
|
|
3604
|
-
deltaY = obstructionMiddle >= scrollerMiddle ? fallbackDistance : -fallbackDistance;
|
|
3605
|
-
}
|
|
3606
|
-
const beforeTop = scroller.scrollTop;
|
|
3607
|
-
scroller.scrollTop = beforeTop + deltaY;
|
|
3608
|
-
return {
|
|
3609
|
-
moved: Math.abs(scroller.scrollTop - beforeTop) > 2,
|
|
3610
|
-
scrollTop: scroller.scrollTop,
|
|
3611
|
-
deltaY
|
|
3612
|
-
};
|
|
3613
|
-
}, status);
|
|
3614
|
-
};
|
|
3615
|
-
var getElementViewportSnapshot = async (element) => {
|
|
3616
|
-
return element.evaluate((el) => {
|
|
3617
|
-
const rect = el.getBoundingClientRect();
|
|
3618
|
-
return {
|
|
3619
|
-
top: rect.top,
|
|
3620
|
-
bottom: rect.bottom,
|
|
3621
|
-
left: rect.left,
|
|
3622
|
-
right: rect.right,
|
|
3623
|
-
width: rect.width,
|
|
3624
|
-
height: rect.height,
|
|
3625
|
-
scrollX: window.scrollX,
|
|
3626
|
-
scrollY: window.scrollY
|
|
3302
|
+
let current = el;
|
|
3303
|
+
while (current && current !== document.body) {
|
|
3304
|
+
if (isScrollable(current)) {
|
|
3305
|
+
const rect = current.getBoundingClientRect();
|
|
3306
|
+
if (rect && rect.width > 0 && rect.height > 0) {
|
|
3307
|
+
return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
current = current.parentElement;
|
|
3311
|
+
}
|
|
3312
|
+
return null;
|
|
3313
|
+
});
|
|
3627
3314
|
};
|
|
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
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
try {
|
|
3693
|
-
if (page.context && typeof page.context().newCDPSession === "function") {
|
|
3694
|
-
client = await page.context().newCDPSession(page);
|
|
3315
|
+
const startTime = Date.now();
|
|
3316
|
+
try {
|
|
3317
|
+
for (let i = 0; i < maxSteps; i++) {
|
|
3318
|
+
if (Date.now() - startTime > maxDurationMs) {
|
|
3319
|
+
logger6.warn(`humanScroll | \u8D85\u65F6\u4FDD\u62A4\u89E6\u53D1 (${maxDurationMs}ms)`);
|
|
3320
|
+
return { element, didScroll };
|
|
3321
|
+
}
|
|
3322
|
+
const status = await checkVisibility();
|
|
3323
|
+
if (status.code === "VISIBLE") {
|
|
3324
|
+
if (status.isFixed) {
|
|
3325
|
+
logger6.info("humanScroll | fixed \u5BB9\u5668\u5185\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
|
|
3326
|
+
} else {
|
|
3327
|
+
logger6.debug("humanScroll | \u5143\u7D20\u53EF\u89C1\u4E14\u65E0\u906E\u6321");
|
|
3328
|
+
}
|
|
3329
|
+
logger6.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
|
|
3330
|
+
return { element, didScroll };
|
|
3331
|
+
}
|
|
3332
|
+
logger6.debug(`humanScroll | \u6B65\u9AA4 ${i + 1}/${maxSteps}: ${status.reason} ${status.direction ? `(${status.direction})` : ""}`);
|
|
3333
|
+
if (status.code === "OBSTRUCTED" && status.obstruction) {
|
|
3334
|
+
logger6.debug(`humanScroll | \u88AB\u4EE5\u4E0B\u5143\u7D20\u906E\u6321 <${status.obstruction.tag} id="${status.obstruction.id}">`);
|
|
3335
|
+
}
|
|
3336
|
+
const scrollRect = await getScrollableRect2();
|
|
3337
|
+
if (!scrollRect && status.isFixed) {
|
|
3338
|
+
logger6.warn("humanScroll | fixed \u5BB9\u5668\u5185\u4E14\u65E0\u53EF\u6EDA\u52A8\u7956\u5148\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
|
|
3339
|
+
return { element, didScroll };
|
|
3340
|
+
}
|
|
3341
|
+
const stepMin = scrollRect ? Math.min(minStep, Math.max(60, scrollRect.height * 0.4)) : minStep;
|
|
3342
|
+
const stepMax = scrollRect ? Math.min(maxStep, Math.max(stepMin + 40, scrollRect.height * 0.8)) : maxStep;
|
|
3343
|
+
let deltaY = 0;
|
|
3344
|
+
if (status.code === "OUT_OF_VIEWPORT") {
|
|
3345
|
+
if (status.direction === "down") {
|
|
3346
|
+
deltaY = stepMin + Math.random() * (stepMax - stepMin);
|
|
3347
|
+
} else if (status.direction === "up") {
|
|
3348
|
+
deltaY = -(stepMin + Math.random() * (stepMax - stepMin));
|
|
3349
|
+
} else {
|
|
3350
|
+
deltaY = 100;
|
|
3351
|
+
}
|
|
3352
|
+
} else if (status.code === "OBSTRUCTED") {
|
|
3353
|
+
const halfY = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
|
|
3354
|
+
const isBottomHalf = status.cy > halfY;
|
|
3355
|
+
const direction = isBottomHalf ? 1 : -1;
|
|
3356
|
+
deltaY = direction * (stepMin + Math.random() * 50);
|
|
3357
|
+
}
|
|
3358
|
+
if (i === 0) {
|
|
3359
|
+
const viewSize = page.viewportSize();
|
|
3360
|
+
if (scrollRect) {
|
|
3361
|
+
const safeX = scrollRect.x + scrollRect.width * 0.5 + (Math.random() - 0.5) * Math.min(80, scrollRect.width * 0.4);
|
|
3362
|
+
const safeY = scrollRect.y + scrollRect.height * 0.5 + (Math.random() - 0.5) * Math.min(80, scrollRect.height * 0.4);
|
|
3363
|
+
await cursor.actions.move({ x: safeX, y: safeY });
|
|
3364
|
+
} else if (viewSize) {
|
|
3365
|
+
const safeX = viewSize.width * 0.5 + (Math.random() - 0.5) * 80;
|
|
3366
|
+
const safeY = viewSize.height * 0.5 + (Math.random() - 0.5) * 80;
|
|
3367
|
+
await cursor.actions.move({ x: safeX, y: safeY });
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
await page.mouse.wheel(0, deltaY);
|
|
3371
|
+
didScroll = true;
|
|
3372
|
+
await (0, import_delay2.default)(this.jitterMs(20 + Math.random() * 40, 0.2));
|
|
3373
|
+
}
|
|
3374
|
+
logger6.warn(`humanScroll | \u5728 ${maxSteps} \u6B65\u540E\u65E0\u6CD5\u786E\u4FDD\u53EF\u89C1\u6027`);
|
|
3375
|
+
return { element, didScroll };
|
|
3376
|
+
} catch (error) {
|
|
3377
|
+
logger6.fail("humanScroll", error);
|
|
3378
|
+
throw error;
|
|
3695
3379
|
}
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3380
|
+
},
|
|
3381
|
+
/**
|
|
3382
|
+
* 人类化点击 - 使用 ghost-cursor 模拟人类鼠标移动轨迹并点击
|
|
3383
|
+
*
|
|
3384
|
+
* @param {import('playwright').Page} page
|
|
3385
|
+
* @param {string|import('playwright').ElementHandle} [target] - CSS 选择器或元素句柄。如果为空,则点击当前鼠标位置
|
|
3386
|
+
* @param {Object} [options]
|
|
3387
|
+
* @param {number} [options.reactionDelay=250] - 反应延迟基础值 (ms),实际 ±30% 抖动
|
|
3388
|
+
* @param {boolean} [options.throwOnMissing=true] - 元素不存在时是否抛出错误
|
|
3389
|
+
* @param {boolean} [options.scrollIfNeeded=true] - 元素不在视口时是否自动滚动
|
|
3390
|
+
*/
|
|
3391
|
+
async humanClick(page, target, options = {}) {
|
|
3392
|
+
const cursor = $GetCursor(page);
|
|
3393
|
+
const { reactionDelay = 250, throwOnMissing = true, scrollIfNeeded = true, restore = false } = options;
|
|
3394
|
+
const targetDesc = target == null ? "Current Position" : typeof target === "string" ? target : "ElementHandle";
|
|
3395
|
+
logger6.start("humanClick", `target=${targetDesc}`);
|
|
3396
|
+
const restoreOnce = async () => {
|
|
3397
|
+
if (restoreOnce.restored) return;
|
|
3398
|
+
restoreOnce.restored = true;
|
|
3399
|
+
if (typeof restoreOnce.do !== "function") return;
|
|
3400
|
+
try {
|
|
3401
|
+
await (0, import_delay2.default)(this.jitterMs(1e3));
|
|
3402
|
+
await restoreOnce.do();
|
|
3403
|
+
} catch (restoreError) {
|
|
3404
|
+
logger6.warn(`humanClick: \u6062\u590D\u6EDA\u52A8\u4F4D\u7F6E\u5931\u8D25: ${restoreError.message}`);
|
|
3405
|
+
}
|
|
3406
|
+
};
|
|
3407
|
+
try {
|
|
3408
|
+
if (target == null) {
|
|
3409
|
+
await (0, import_delay2.default)(this.jitterMs(reactionDelay, 0.4));
|
|
3410
|
+
await cursor.actions.click();
|
|
3411
|
+
logger6.success("humanClick", "Clicked current position");
|
|
3412
|
+
return true;
|
|
3413
|
+
}
|
|
3414
|
+
let element;
|
|
3415
|
+
if (typeof target === "string") {
|
|
3416
|
+
element = await page.$(target);
|
|
3417
|
+
if (!element) {
|
|
3418
|
+
if (throwOnMissing) {
|
|
3419
|
+
throw new Error(`\u627E\u4E0D\u5230\u5143\u7D20 ${target}`);
|
|
3420
|
+
}
|
|
3421
|
+
logger6.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${target}`);
|
|
3422
|
+
return false;
|
|
3423
|
+
}
|
|
3424
|
+
} else {
|
|
3425
|
+
element = target;
|
|
3426
|
+
}
|
|
3427
|
+
if (scrollIfNeeded) {
|
|
3428
|
+
const { restore: restoreFn, didScroll } = await this.humanScroll(page, element);
|
|
3429
|
+
restoreOnce.do = didScroll && restore ? restoreFn : null;
|
|
3430
|
+
}
|
|
3431
|
+
const box = await element.boundingBox();
|
|
3432
|
+
if (!box) {
|
|
3433
|
+
await restoreOnce();
|
|
3434
|
+
if (throwOnMissing) {
|
|
3435
|
+
throw new Error("\u65E0\u6CD5\u83B7\u53D6\u5143\u7D20\u4F4D\u7F6E");
|
|
3436
|
+
}
|
|
3437
|
+
logger6.warn("humanClick: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E\uFF0C\u8DF3\u8FC7\u70B9\u51FB");
|
|
3438
|
+
return false;
|
|
3439
|
+
}
|
|
3440
|
+
const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.3;
|
|
3441
|
+
const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.3;
|
|
3442
|
+
await cursor.actions.move({ x, y });
|
|
3443
|
+
await (0, import_delay2.default)(this.jitterMs(reactionDelay, 0.4));
|
|
3444
|
+
await cursor.actions.click();
|
|
3445
|
+
await restoreOnce();
|
|
3446
|
+
logger6.success("humanClick");
|
|
3709
3447
|
return true;
|
|
3448
|
+
} catch (error) {
|
|
3449
|
+
await restoreOnce();
|
|
3450
|
+
logger6.fail("humanClick", error);
|
|
3451
|
+
throw error;
|
|
3710
3452
|
}
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3453
|
+
},
|
|
3454
|
+
/**
|
|
3455
|
+
* 随机延迟一段毫秒数(带 ±30% 抖动)
|
|
3456
|
+
* @param {number} baseMs - 基础延迟毫秒数
|
|
3457
|
+
* @param {number} [jitterPercent=0.3] - 抖动百分比
|
|
3458
|
+
*/
|
|
3459
|
+
async randomSleep(baseMs, jitterPercent = 0.3) {
|
|
3460
|
+
const ms = this.jitterMs(baseMs, jitterPercent);
|
|
3461
|
+
logger6.start("randomSleep", `base=${baseMs}, actual=${ms}ms`);
|
|
3462
|
+
await (0, import_delay2.default)(ms);
|
|
3463
|
+
logger6.success("randomSleep");
|
|
3464
|
+
},
|
|
3465
|
+
/**
|
|
3466
|
+
* 模拟人类"注视"或"阅读"行为:鼠标在页面上随机微动
|
|
3467
|
+
* @param {import('playwright').Page} page
|
|
3468
|
+
* @param {number} [baseDurationMs=2500] - 基础持续时间 (±40% 抖动)
|
|
3469
|
+
*/
|
|
3470
|
+
async simulateGaze(page, baseDurationMs = 2500) {
|
|
3471
|
+
const cursor = $GetCursor(page);
|
|
3472
|
+
const durationMs = this.jitterMs(baseDurationMs, 0.4);
|
|
3473
|
+
logger6.start("simulateGaze", `duration=${durationMs}ms`);
|
|
3474
|
+
const startTime = Date.now();
|
|
3475
|
+
const viewportSize = page.viewportSize() || { width: 1920, height: 1080 };
|
|
3476
|
+
while (Date.now() - startTime < durationMs) {
|
|
3477
|
+
const x = 100 + Math.random() * (viewportSize.width - 200);
|
|
3478
|
+
const y = 100 + Math.random() * (viewportSize.height - 200);
|
|
3479
|
+
await cursor.actions.move({ x, y });
|
|
3480
|
+
await (0, import_delay2.default)(this.jitterMs(600, 0.5));
|
|
3735
3481
|
}
|
|
3736
|
-
|
|
3737
|
-
}
|
|
3738
|
-
|
|
3482
|
+
logger6.success("simulateGaze");
|
|
3483
|
+
},
|
|
3484
|
+
/**
|
|
3485
|
+
* 人类化输入 - 带节奏变化(快-慢-停顿-偶尔加速)
|
|
3486
|
+
* @param {import('playwright').Page} page
|
|
3487
|
+
* @param {string} selector - 输入框选择器
|
|
3488
|
+
* @param {string} text - 要输入的文本
|
|
3489
|
+
* @param {Object} [options]
|
|
3490
|
+
* @param {number} [options.baseDelay=180] - 基础按键延迟 (ms),实际 ±40% 抖动
|
|
3491
|
+
* @param {number} [options.pauseProbability=0.08] - 停顿概率 (0-1)
|
|
3492
|
+
* @param {number} [options.pauseBase=800] - 停顿时长基础值 (ms),实际 ±50% 抖动
|
|
3493
|
+
*/
|
|
3494
|
+
async humanType(page, selector, text, options = {}) {
|
|
3495
|
+
logger6.start("humanType", `selector=${selector}, textLen=${text.length}`);
|
|
3496
|
+
const {
|
|
3497
|
+
baseDelay = 180,
|
|
3498
|
+
pauseProbability = 0.08,
|
|
3499
|
+
pauseBase = 800
|
|
3500
|
+
} = options;
|
|
3739
3501
|
try {
|
|
3740
|
-
|
|
3741
|
-
await
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3502
|
+
const locator = page.locator(selector);
|
|
3503
|
+
await Humanize.humanClick(page, locator);
|
|
3504
|
+
await (0, import_delay2.default)(this.jitterMs(200, 0.4));
|
|
3505
|
+
for (let i = 0; i < text.length; i++) {
|
|
3506
|
+
const char = text[i];
|
|
3507
|
+
let charDelay;
|
|
3508
|
+
if (char === " ") {
|
|
3509
|
+
charDelay = this.jitterMs(baseDelay * 0.6, 0.3);
|
|
3510
|
+
} else if (/[,.!?;:,。!?;:]/.test(char)) {
|
|
3511
|
+
charDelay = this.jitterMs(baseDelay * 1.5, 0.4);
|
|
3512
|
+
} else {
|
|
3513
|
+
charDelay = this.jitterMs(baseDelay, 0.4);
|
|
3514
|
+
}
|
|
3515
|
+
await page.keyboard.type(char);
|
|
3516
|
+
await (0, import_delay2.default)(charDelay);
|
|
3517
|
+
if (Math.random() < pauseProbability && i < text.length - 1) {
|
|
3518
|
+
const pauseTime = this.jitterMs(pauseBase, 0.5);
|
|
3519
|
+
logger6.debug(`\u505C\u987F ${pauseTime}ms...`);
|
|
3520
|
+
await (0, import_delay2.default)(pauseTime);
|
|
3521
|
+
}
|
|
3753
3522
|
}
|
|
3523
|
+
logger6.success("humanType");
|
|
3524
|
+
} catch (error) {
|
|
3525
|
+
logger6.fail("humanType", error);
|
|
3526
|
+
throw error;
|
|
3754
3527
|
}
|
|
3755
|
-
}
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3528
|
+
},
|
|
3529
|
+
/**
|
|
3530
|
+
* 人类化按键 - 模拟用户在当前焦点或指定目标上按下一次键。
|
|
3531
|
+
* @param {import('playwright').Page} page
|
|
3532
|
+
* @param {string|import('playwright').ElementHandle|import('playwright').Locator} targetOrKey - 目标或按键
|
|
3533
|
+
* @param {string|Object} [maybeKey] - 按键或选项
|
|
3534
|
+
* @param {Object} [options]
|
|
3535
|
+
*/
|
|
3536
|
+
async humanPress(page, targetOrKey, maybeKey, options = {}) {
|
|
3537
|
+
const hasTarget = typeof maybeKey === "string";
|
|
3538
|
+
const key = hasTarget ? maybeKey : targetOrKey;
|
|
3539
|
+
const pressOptions = hasTarget ? options : maybeKey || options;
|
|
3540
|
+
const {
|
|
3541
|
+
reactionDelay = 180,
|
|
3542
|
+
holdDelay = 45,
|
|
3543
|
+
focusDelay = 180,
|
|
3544
|
+
scrollIfNeeded = true,
|
|
3545
|
+
throwOnMissing = true,
|
|
3546
|
+
keyboardOptions = {}
|
|
3547
|
+
} = pressOptions || {};
|
|
3548
|
+
const targetDesc = hasTarget ? typeof targetOrKey === "string" ? targetOrKey : "ElementHandle" : "current focus";
|
|
3549
|
+
logger6.start("humanPress", `key=${key}, target=${targetDesc}`);
|
|
3550
|
+
try {
|
|
3551
|
+
if (hasTarget) {
|
|
3552
|
+
await this.humanClick(page, targetOrKey, { reactionDelay: focusDelay, scrollIfNeeded, throwOnMissing });
|
|
3553
|
+
}
|
|
3554
|
+
await (0, import_delay2.default)(this.jitterMs(reactionDelay, 0.45));
|
|
3555
|
+
await page.keyboard.press(key, {
|
|
3556
|
+
...keyboardOptions,
|
|
3557
|
+
delay: this.jitterMs(holdDelay, 0.5)
|
|
3558
|
+
});
|
|
3559
|
+
logger6.success("humanPress");
|
|
3560
|
+
return true;
|
|
3771
3561
|
} catch (error) {
|
|
3772
|
-
|
|
3773
|
-
|
|
3562
|
+
logger6.fail("humanPress", error);
|
|
3563
|
+
throw error;
|
|
3774
3564
|
}
|
|
3775
|
-
}
|
|
3776
|
-
await withTimeout(
|
|
3777
|
-
() => page.mouse.click(point.x, point.y),
|
|
3778
|
-
mouseFallbackTimeoutMs,
|
|
3779
|
-
"mouse.click fallback"
|
|
3780
|
-
);
|
|
3781
|
-
return { method: "mouse" };
|
|
3782
|
-
};
|
|
3783
|
-
var MobileHumanize = {
|
|
3784
|
-
jitterMs,
|
|
3785
|
-
async initializeCursor(page) {
|
|
3786
|
-
if (initializedPages.has(page)) return;
|
|
3787
|
-
initializedPages.add(page);
|
|
3788
|
-
logger5.debug("initializeCursor: mobile mode uses touch gestures, cursor init skipped");
|
|
3789
3565
|
},
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3566
|
+
/**
|
|
3567
|
+
* 人类化清空输入框 - 模拟人类删除文本的行为
|
|
3568
|
+
* @param {import('playwright').Page} page
|
|
3569
|
+
* @param {string} selector - 输入框选择器
|
|
3570
|
+
*/
|
|
3571
|
+
async humanClear(page, selector) {
|
|
3572
|
+
logger6.start("humanClear", `selector=${selector}`);
|
|
3573
|
+
try {
|
|
3574
|
+
const locator = page.locator(selector);
|
|
3575
|
+
await locator.click();
|
|
3576
|
+
await (0, import_delay2.default)(this.jitterMs(200, 0.4));
|
|
3577
|
+
const currentValue = await locator.inputValue();
|
|
3578
|
+
if (!currentValue || currentValue.length === 0) {
|
|
3579
|
+
logger6.success("humanClear", "already empty");
|
|
3580
|
+
return;
|
|
3796
3581
|
}
|
|
3582
|
+
await page.keyboard.press("Meta+A");
|
|
3583
|
+
await (0, import_delay2.default)(this.jitterMs(100, 0.4));
|
|
3584
|
+
await page.keyboard.press("Backspace");
|
|
3585
|
+
logger6.success("humanClear");
|
|
3586
|
+
} catch (error) {
|
|
3587
|
+
logger6.fail("humanClear", error);
|
|
3588
|
+
throw error;
|
|
3797
3589
|
}
|
|
3798
|
-
await waitJitter(80, 0.4);
|
|
3799
|
-
return true;
|
|
3800
3590
|
},
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
logger5.start("humanScroll", `target=${targetDesc}`);
|
|
3811
|
-
const element = await resolveElement(page, target, { throwOnMissing });
|
|
3812
|
-
if (!element) {
|
|
3813
|
-
logger5.warn(`humanScroll | \u5143\u7D20\u672A\u627E\u5230: ${targetDesc}`);
|
|
3814
|
-
return { element: null, didScroll: false, restore: null };
|
|
3815
|
-
}
|
|
3591
|
+
/**
|
|
3592
|
+
* 页面预热浏览 - 模拟人类进入页面后的探索行为
|
|
3593
|
+
* @param {import('playwright').Page} page
|
|
3594
|
+
* @param {number} [baseDuration=3500] - 预热时长基础值 (±40% 抖动)
|
|
3595
|
+
*/
|
|
3596
|
+
async warmUpBrowsing(page, baseDuration = 3500) {
|
|
3597
|
+
const cursor = $GetCursor(page);
|
|
3598
|
+
const durationMs = this.jitterMs(baseDuration, 0.4);
|
|
3599
|
+
logger6.start("warmUpBrowsing", `duration=${durationMs}ms`);
|
|
3816
3600
|
const startTime = Date.now();
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
if (
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
logger5.warn(`humanScroll | \u5143\u7D20\u4E0D\u53EF\u6EDA\u52A8\u81F3\u53EF\u70B9\u51FB\u72B6\u6001: ${status.reason || status.code}`);
|
|
3831
|
-
return { element, didScroll, restore: null };
|
|
3832
|
-
}
|
|
3833
|
-
const scrollRect = await getScrollableRect(element);
|
|
3834
|
-
if (!scrollRect && status.isFixed && status.code === "OUT_OF_VIEWPORT") {
|
|
3835
|
-
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"})`);
|
|
3836
|
-
return { element, didScroll, restore: null, unscrollable: true };
|
|
3837
|
-
}
|
|
3838
|
-
if (!scrollRect && status.isFixed && status.code === "OBSTRUCTED") {
|
|
3839
|
-
logger5.warn(`humanScroll | fixed/sticky \u76EE\u6807\u88AB\u906E\u6321\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u89E3\u9664 (${status.obstruction?.tag || "unknown"})`);
|
|
3840
|
-
return { element, didScroll, restore: null, unscrollable: true };
|
|
3841
|
-
}
|
|
3842
|
-
if (scrollRect && status.code === "OBSTRUCTED" && status.obstruction?.isFixed) {
|
|
3843
|
-
const moved = await scrollAwayFromObstruction(element, status);
|
|
3844
|
-
if (moved.moved) {
|
|
3845
|
-
logger5.debug(`humanScroll | sticky/fixed \u906E\u6321\u8865\u507F\u6EDA\u52A8 top=${Math.round(moved.scrollTop || 0)}`);
|
|
3846
|
-
await waitJitter(90, 0.3);
|
|
3847
|
-
didScroll = true;
|
|
3848
|
-
continue;
|
|
3849
|
-
}
|
|
3850
|
-
}
|
|
3851
|
-
if (Date.now() - startTime > maxDurationMs) {
|
|
3852
|
-
logger5.warn(`humanScroll | mobile timeout (${maxDurationMs}ms, status=${status.code}, direction=${status.direction || "unknown"}, fixed=${Boolean(status.isFixed)})`);
|
|
3853
|
-
return { element, didScroll, restore: null };
|
|
3854
|
-
}
|
|
3855
|
-
const stepMin = scrollRect ? Math.min(minStep, Math.max(60, scrollRect.height * 0.4)) : minStep;
|
|
3856
|
-
const stepMax = scrollRect ? Math.min(maxStep, Math.max(stepMin + 40, scrollRect.height * 0.8)) : maxStep;
|
|
3857
|
-
logger5.debug(`humanScroll | \u6B65\u9AA4 ${i + 1}/${maxSteps}: ${status.reason || status.code} ${status.direction ? `(${status.direction})` : ""}`);
|
|
3858
|
-
const distance = stepMin + Math.random() * Math.max(1, stepMax - stepMin);
|
|
3859
|
-
let deltaY = status.direction === "up" ? -distance : distance;
|
|
3860
|
-
if (status.code === "OBSTRUCTED") {
|
|
3861
|
-
if (status.obstruction?.isFixed && status.obstruction.top != null) {
|
|
3862
|
-
const obstructionMiddle = (Number(status.obstruction.top || 0) + Number(status.obstruction.bottom || status.obstruction.top || 0)) / 2;
|
|
3863
|
-
const visibleMiddle = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
|
|
3864
|
-
deltaY = obstructionMiddle < visibleMiddle ? -distance : distance;
|
|
3601
|
+
const viewportSize = page.viewportSize() || { width: 1920, height: 1080 };
|
|
3602
|
+
try {
|
|
3603
|
+
while (Date.now() - startTime < durationMs) {
|
|
3604
|
+
const action = Math.random();
|
|
3605
|
+
if (action < 0.4) {
|
|
3606
|
+
const x = 100 + Math.random() * (viewportSize.width - 200);
|
|
3607
|
+
const y = 100 + Math.random() * (viewportSize.height - 200);
|
|
3608
|
+
await cursor.actions.move({ x, y });
|
|
3609
|
+
await (0, import_delay2.default)(this.jitterMs(350, 0.4));
|
|
3610
|
+
} else if (action < 0.7) {
|
|
3611
|
+
const scrollY = (Math.random() - 0.5) * 200;
|
|
3612
|
+
await page.mouse.wheel(0, scrollY);
|
|
3613
|
+
await (0, import_delay2.default)(this.jitterMs(500, 0.4));
|
|
3865
3614
|
} else {
|
|
3866
|
-
|
|
3867
|
-
deltaY = status.cy > halfY ? distance : -distance;
|
|
3615
|
+
await (0, import_delay2.default)(this.jitterMs(800, 0.5));
|
|
3868
3616
|
}
|
|
3869
3617
|
}
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
const
|
|
3896
|
-
|
|
3897
|
-
await page.evaluate((state2) => window.scrollTo(state2.x, state2.y), beforeWindowState);
|
|
3898
|
-
logger5.debug(`humanScroll | \u7A97\u53E3\u6EDA\u52A8\u56DE\u6536 from=${Math.round(afterWindowState.y)} to=${Math.round(beforeWindowState.y)}`);
|
|
3899
|
-
}
|
|
3618
|
+
logger6.success("warmUpBrowsing");
|
|
3619
|
+
} catch (error) {
|
|
3620
|
+
logger6.fail("warmUpBrowsing", error);
|
|
3621
|
+
throw error;
|
|
3622
|
+
}
|
|
3623
|
+
},
|
|
3624
|
+
/**
|
|
3625
|
+
* 自然滚动 - 带惯性、减速效果和随机抖动
|
|
3626
|
+
* @param {import('playwright').Page} page
|
|
3627
|
+
* @param {'up' | 'down'} [direction='down'] - 滚动方向
|
|
3628
|
+
* @param {number} [distance=300] - 总滚动距离基础值 (px),±15% 抖动
|
|
3629
|
+
* @param {number} [baseSteps=5] - 分几步完成基础值,±1 随机
|
|
3630
|
+
*/
|
|
3631
|
+
async naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
|
|
3632
|
+
const steps = Math.max(3, baseSteps + Math.floor(Math.random() * 3) - 1);
|
|
3633
|
+
const actualDistance = this.jitterMs(distance, 0.15);
|
|
3634
|
+
logger6.start("naturalScroll", `dir=${direction}, dist=${actualDistance}, steps=${steps}`);
|
|
3635
|
+
const sign = direction === "down" ? 1 : -1;
|
|
3636
|
+
const stepDistance = actualDistance / steps;
|
|
3637
|
+
try {
|
|
3638
|
+
for (let i = 0; i < steps; i++) {
|
|
3639
|
+
const factor = 1 - i / steps * 0.5;
|
|
3640
|
+
const jitter = 0.9 + Math.random() * 0.2;
|
|
3641
|
+
const scrollAmount = stepDistance * factor * sign * jitter;
|
|
3642
|
+
await page.mouse.wheel(0, scrollAmount);
|
|
3643
|
+
const baseDelay = 60 + i * 25;
|
|
3644
|
+
await (0, import_delay2.default)(this.jitterMs(baseDelay, 0.3));
|
|
3900
3645
|
}
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3646
|
+
logger6.success("naturalScroll");
|
|
3647
|
+
} catch (error) {
|
|
3648
|
+
logger6.fail("naturalScroll", error);
|
|
3649
|
+
throw error;
|
|
3650
|
+
}
|
|
3651
|
+
}
|
|
3652
|
+
};
|
|
3653
|
+
|
|
3654
|
+
// src/internals/humanize/shared.js
|
|
3655
|
+
var import_delay3 = __toESM(require("delay"), 1);
|
|
3656
|
+
var jitterMs = (base, jitterPercent = 0.3) => {
|
|
3657
|
+
const jitter = Number(base || 0) * Number(jitterPercent || 0) * (Math.random() * 2 - 1);
|
|
3658
|
+
return Math.max(10, Math.round(Number(base || 0) + jitter));
|
|
3659
|
+
};
|
|
3660
|
+
var resolveElement = async (page, target, { throwOnMissing = true } = {}) => {
|
|
3661
|
+
if (target == null) return null;
|
|
3662
|
+
let element = target;
|
|
3663
|
+
if (typeof target === "string") {
|
|
3664
|
+
element = await page.$(target);
|
|
3665
|
+
}
|
|
3666
|
+
if (!element) {
|
|
3667
|
+
if (throwOnMissing) {
|
|
3668
|
+
throw new Error(`\u627E\u4E0D\u5230\u5143\u7D20 ${String(target)}`);
|
|
3669
|
+
}
|
|
3670
|
+
return null;
|
|
3671
|
+
}
|
|
3672
|
+
return element;
|
|
3673
|
+
};
|
|
3674
|
+
var waitJitter = (base, jitterPercent = 0.3) => (0, import_delay3.default)(jitterMs(base, jitterPercent));
|
|
3675
|
+
|
|
3676
|
+
// src/internals/humanize/mobile.js
|
|
3677
|
+
var logger7 = createInternalLogger("Humanize.Mobile");
|
|
3678
|
+
var initializedPages = /* @__PURE__ */ new WeakSet();
|
|
3679
|
+
var DEFAULT_TAP_TIMEOUT_MS = 2500;
|
|
3680
|
+
var DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS = 1200;
|
|
3681
|
+
var DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS = 900;
|
|
3682
|
+
var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
3683
|
+
var resolveViewport = (page) => page?.viewportSize?.() || { width: 390, height: 844 };
|
|
3684
|
+
var describeTarget = (target) => {
|
|
3685
|
+
if (target == null) return "Current Position";
|
|
3686
|
+
return typeof target === "string" ? target : "ElementHandle";
|
|
3687
|
+
};
|
|
3688
|
+
var clipBoxToViewport = (box, viewport) => {
|
|
3689
|
+
if (!box || !viewport) return box;
|
|
3690
|
+
const left = clamp(box.x, 0, viewport.width);
|
|
3691
|
+
const top = clamp(box.y, 0, viewport.height);
|
|
3692
|
+
const right = clamp(box.x + box.width, 0, viewport.width);
|
|
3693
|
+
const bottom = clamp(box.y + box.height, 0, viewport.height);
|
|
3694
|
+
if (right - left > 1 && bottom - top > 1) {
|
|
3695
|
+
return {
|
|
3696
|
+
x: left,
|
|
3697
|
+
y: top,
|
|
3698
|
+
width: right - left,
|
|
3699
|
+
height: bottom - top
|
|
3700
|
+
};
|
|
3701
|
+
}
|
|
3702
|
+
return box;
|
|
3703
|
+
};
|
|
3704
|
+
var centerPointInBox = (box) => ({
|
|
3705
|
+
x: box.x + box.width / 2,
|
|
3706
|
+
y: box.y + box.height / 2
|
|
3707
|
+
});
|
|
3708
|
+
var withTimeout = async (operation, timeoutMs, label) => {
|
|
3709
|
+
const safeTimeoutMs = Math.max(50, Number(timeoutMs || 0));
|
|
3710
|
+
let timeoutId = null;
|
|
3711
|
+
try {
|
|
3712
|
+
return await Promise.race([
|
|
3713
|
+
Promise.resolve().then(operation),
|
|
3714
|
+
new Promise((_, reject) => {
|
|
3715
|
+
timeoutId = setTimeout(() => {
|
|
3716
|
+
reject(new Error(`${label} timeout after ${safeTimeoutMs}ms`));
|
|
3717
|
+
}, safeTimeoutMs);
|
|
3718
|
+
})
|
|
3719
|
+
]);
|
|
3720
|
+
} finally {
|
|
3721
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
3722
|
+
}
|
|
3723
|
+
};
|
|
3724
|
+
var checkElementVisibility = async (element) => {
|
|
3725
|
+
return element.evaluate((el) => {
|
|
3726
|
+
const targetStyle = window.getComputedStyle(el);
|
|
3727
|
+
if (!targetStyle || targetStyle.display === "none" || targetStyle.visibility === "hidden" || targetStyle.visibility === "collapse") {
|
|
3728
|
+
return { code: "NOT_INTERACTABLE", reason: "\u5143\u7D20\u4E0D\u53EF\u89C1", direction: "down" };
|
|
3729
|
+
}
|
|
3730
|
+
const rect = el.getBoundingClientRect();
|
|
3731
|
+
if (!rect || rect.width <= 0 || rect.height <= 0) {
|
|
3732
|
+
return { code: "ZERO_DIMENSIONS", reason: "\u5C3A\u5BF8\u4E3A\u96F6", direction: "down" };
|
|
3733
|
+
}
|
|
3734
|
+
const viewW = window.innerWidth;
|
|
3735
|
+
const viewH = window.innerHeight;
|
|
3736
|
+
const centerY = rect.top + rect.height / 2;
|
|
3737
|
+
let clipLeft = 0;
|
|
3738
|
+
let clipRight = viewW;
|
|
3739
|
+
let clipTop = 0;
|
|
3740
|
+
let clipBottom = viewH;
|
|
3741
|
+
let isFixed = false;
|
|
3742
|
+
let positioning = null;
|
|
3743
|
+
for (let node = el; node && node !== document.body; node = node.parentElement) {
|
|
3744
|
+
const style = window.getComputedStyle(node);
|
|
3745
|
+
if (style && (style.position === "fixed" || style.position === "sticky")) {
|
|
3746
|
+
isFixed = true;
|
|
3747
|
+
positioning = style.position;
|
|
3748
|
+
break;
|
|
3915
3749
|
}
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
return {
|
|
3929
|
-
kind: "element",
|
|
3930
|
-
top: current.scrollTop,
|
|
3931
|
-
left: current.scrollLeft
|
|
3932
|
-
};
|
|
3933
|
-
}
|
|
3934
|
-
current = current.parentElement;
|
|
3935
|
-
}
|
|
3936
|
-
return { kind: "window", top: window.scrollY, left: window.scrollX };
|
|
3937
|
-
});
|
|
3938
|
-
const topDelta = Number(afterState.top || 0) - Number(beforeState.top || 0);
|
|
3939
|
-
const leftDelta = Number(afterState.left || 0) - Number(beforeState.left || 0);
|
|
3940
|
-
const expectedDelta = Number(deltaY || 0);
|
|
3941
|
-
const moved = beforeState.kind !== afterState.kind || Math.abs(topDelta) > 2 || Math.abs(leftDelta) > 2;
|
|
3942
|
-
if (!moved) {
|
|
3943
|
-
const fallback = await scrollScrollableAncestor(element, deltaY);
|
|
3944
|
-
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)}`);
|
|
3945
|
-
} 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)) {
|
|
3946
|
-
const residualDelta = expectedDelta - topDelta;
|
|
3947
|
-
if (Math.sign(residualDelta) === Math.sign(expectedDelta) && Math.abs(residualDelta) > 24) {
|
|
3948
|
-
const fallback = await scrollScrollableAncestor(element, residualDelta);
|
|
3949
|
-
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)}`);
|
|
3950
|
-
}
|
|
3951
|
-
}
|
|
3750
|
+
}
|
|
3751
|
+
for (let node = el.parentElement; node && node !== document.body; node = node.parentElement) {
|
|
3752
|
+
const style = window.getComputedStyle(node);
|
|
3753
|
+
if (!style) continue;
|
|
3754
|
+
const clipsY = ["auto", "scroll", "overlay", "hidden", "clip"].includes(style.overflowY);
|
|
3755
|
+
const clipsX = ["auto", "scroll", "overlay", "hidden", "clip"].includes(style.overflowX);
|
|
3756
|
+
if (!clipsX && !clipsY) continue;
|
|
3757
|
+
const nodeRect = node.getBoundingClientRect();
|
|
3758
|
+
if (!nodeRect || nodeRect.width <= 0 || nodeRect.height <= 0) continue;
|
|
3759
|
+
if (clipsX) {
|
|
3760
|
+
clipLeft = Math.max(clipLeft, nodeRect.left);
|
|
3761
|
+
clipRight = Math.min(clipRight, nodeRect.right);
|
|
3952
3762
|
}
|
|
3953
|
-
if (
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
|
|
3957
|
-
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"})`);
|
|
3958
|
-
return { element, didScroll, restore: null, unscrollable: true };
|
|
3959
|
-
}
|
|
3763
|
+
if (clipsY) {
|
|
3764
|
+
clipTop = Math.max(clipTop, nodeRect.top);
|
|
3765
|
+
clipBottom = Math.min(clipBottom, nodeRect.bottom);
|
|
3960
3766
|
}
|
|
3961
|
-
didScroll = true;
|
|
3962
3767
|
}
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3768
|
+
const visibleLeft = Math.max(clipLeft, Math.min(clipRight, rect.left));
|
|
3769
|
+
const visibleRight = Math.max(clipLeft, Math.min(clipRight, rect.right));
|
|
3770
|
+
const visibleTop = Math.max(clipTop, Math.min(clipBottom, rect.top));
|
|
3771
|
+
const visibleBottom = Math.max(clipTop, Math.min(clipBottom, rect.bottom));
|
|
3772
|
+
const visibleWidth = visibleRight - visibleLeft;
|
|
3773
|
+
const visibleHeight = visibleBottom - visibleTop;
|
|
3774
|
+
const cx = visibleLeft + visibleWidth / 2;
|
|
3775
|
+
const cy = visibleTop + visibleHeight / 2;
|
|
3776
|
+
if (visibleWidth <= 1 || visibleHeight <= 1) {
|
|
3777
|
+
return {
|
|
3778
|
+
code: "OUT_OF_VIEWPORT",
|
|
3779
|
+
reason: "\u4E0D\u5728\u89C6\u53E3\u5185",
|
|
3780
|
+
direction: centerY < clipTop ? "up" : "down",
|
|
3781
|
+
cy: centerY,
|
|
3782
|
+
viewH,
|
|
3783
|
+
isFixed,
|
|
3784
|
+
positioning
|
|
3785
|
+
};
|
|
3974
3786
|
}
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
activateFallbackTimeoutMs = DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS,
|
|
3986
|
-
fallbackDomClick = true,
|
|
3987
|
-
fallbackDomClickOnTapError = true
|
|
3988
|
-
} = options;
|
|
3989
|
-
const targetDesc = describeTarget(target);
|
|
3990
|
-
logger5.start("humanClick", `target=${targetDesc}`);
|
|
3991
|
-
try {
|
|
3992
|
-
if (target == null) {
|
|
3993
|
-
const viewport = resolveViewport(page);
|
|
3994
|
-
await waitJitter(reactionDelay, 0.45);
|
|
3995
|
-
await tapPoint(page, {
|
|
3996
|
-
x: viewport.width * (0.45 + Math.random() * 0.1),
|
|
3997
|
-
y: viewport.height * (0.48 + Math.random() * 0.12)
|
|
3998
|
-
}, {
|
|
3999
|
-
timeoutMs: tapTimeoutMs,
|
|
4000
|
-
mouseFallbackTimeoutMs
|
|
4001
|
-
});
|
|
4002
|
-
logger5.success("humanClick", "Tapped current position");
|
|
3787
|
+
const isRootNode = (node) => !node || node === document || node === document.body || node === document.documentElement;
|
|
3788
|
+
const commonAncestor = (a, b) => {
|
|
3789
|
+
for (let current = a; current && !isRootNode(current); current = current.parentElement) {
|
|
3790
|
+
if (current.contains(b)) return current;
|
|
3791
|
+
}
|
|
3792
|
+
return null;
|
|
3793
|
+
};
|
|
3794
|
+
const sameTapTarget = (pointElement) => {
|
|
3795
|
+
if (!pointElement) return false;
|
|
3796
|
+
if (pointElement === el || el.contains(pointElement) || pointElement.contains(el)) {
|
|
4003
3797
|
return true;
|
|
4004
3798
|
}
|
|
4005
|
-
const
|
|
4006
|
-
if (!
|
|
4007
|
-
|
|
3799
|
+
const common = commonAncestor(el, pointElement);
|
|
3800
|
+
if (!common) return false;
|
|
3801
|
+
const commonRect = common.getBoundingClientRect?.();
|
|
3802
|
+
if (!commonRect || commonRect.width <= 0 || commonRect.height <= 0) return false;
|
|
3803
|
+
const commonArea = commonRect.width * commonRect.height;
|
|
3804
|
+
const targetArea = Math.max(1, rect.width * rect.height);
|
|
3805
|
+
const maxSharedRegionArea = Math.max(targetArea * 12, 4096);
|
|
3806
|
+
if (commonArea > maxSharedRegionArea) return false;
|
|
3807
|
+
if (commonRect.width > Math.max(rect.width * 8, 120) || commonRect.height > Math.max(rect.height * 8, 120)) {
|
|
4008
3808
|
return false;
|
|
4009
3809
|
}
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
3810
|
+
return common.contains(el) && common.contains(pointElement);
|
|
3811
|
+
};
|
|
3812
|
+
const describeElement = (node) => {
|
|
3813
|
+
if (!node) return null;
|
|
3814
|
+
const className = typeof node.className === "string" ? node.className : node.className && typeof node.className.baseVal === "string" ? node.className.baseVal : "";
|
|
3815
|
+
const rect2 = node.getBoundingClientRect?.();
|
|
3816
|
+
const style = window.getComputedStyle(node);
|
|
3817
|
+
return {
|
|
3818
|
+
tag: node.tagName,
|
|
3819
|
+
id: node.id || "",
|
|
3820
|
+
className,
|
|
3821
|
+
isFixed: Boolean(style && (style.position === "fixed" || style.position === "sticky")),
|
|
3822
|
+
positioning: style?.position || "",
|
|
3823
|
+
top: rect2 ? rect2.top : null,
|
|
3824
|
+
bottom: rect2 ? rect2.bottom : null,
|
|
3825
|
+
left: rect2 ? rect2.left : null,
|
|
3826
|
+
right: rect2 ? rect2.right : null
|
|
3827
|
+
};
|
|
3828
|
+
};
|
|
3829
|
+
const samplePoints = [
|
|
3830
|
+
{ x: cx, y: cy },
|
|
3831
|
+
{ x: visibleLeft + Math.min(8, Math.max(1, visibleWidth * 0.25)), y: cy },
|
|
3832
|
+
{ x: visibleRight - Math.min(8, Math.max(1, visibleWidth * 0.25)), y: cy },
|
|
3833
|
+
{ x: cx, y: visibleTop + Math.min(8, Math.max(1, visibleHeight * 0.25)) },
|
|
3834
|
+
{ x: cx, y: visibleBottom - Math.min(8, Math.max(1, visibleHeight * 0.25)) }
|
|
3835
|
+
];
|
|
3836
|
+
let obstruction = null;
|
|
3837
|
+
for (const point of samplePoints) {
|
|
3838
|
+
const pointElement = document.elementFromPoint(point.x, point.y);
|
|
3839
|
+
if (sameTapTarget(pointElement)) {
|
|
3840
|
+
return { code: "VISIBLE", isFixed, positioning };
|
|
4039
3841
|
}
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
3842
|
+
obstruction = obstruction || describeElement(pointElement);
|
|
3843
|
+
}
|
|
3844
|
+
if (obstruction) {
|
|
3845
|
+
return {
|
|
3846
|
+
code: "OBSTRUCTED",
|
|
3847
|
+
reason: "\u88AB\u906E\u6321",
|
|
3848
|
+
direction: cy > viewH / 2 ? "down" : "up",
|
|
3849
|
+
obstruction,
|
|
3850
|
+
cy,
|
|
3851
|
+
viewH,
|
|
3852
|
+
isFixed,
|
|
3853
|
+
positioning
|
|
3854
|
+
};
|
|
3855
|
+
}
|
|
3856
|
+
return { code: "VISIBLE", isFixed, positioning };
|
|
3857
|
+
});
|
|
3858
|
+
};
|
|
3859
|
+
var activateElementFallback = async (element, point = null, options = {}) => {
|
|
3860
|
+
return element.evaluate((el, { innerOptions }) => {
|
|
3861
|
+
const isEditable = (node) => {
|
|
3862
|
+
if (!node || node.nodeType !== Node.ELEMENT_NODE) return false;
|
|
3863
|
+
if (node.isContentEditable) return true;
|
|
3864
|
+
if (node instanceof HTMLTextAreaElement) return !node.disabled && !node.readOnly;
|
|
3865
|
+
if (node instanceof HTMLInputElement) {
|
|
3866
|
+
return !node.disabled && !node.readOnly && typeof node.select === "function";
|
|
3867
|
+
}
|
|
3868
|
+
return false;
|
|
3869
|
+
};
|
|
3870
|
+
const findEditable = (node) => {
|
|
3871
|
+
for (let current = node; current && current !== document.body; current = current.parentElement) {
|
|
3872
|
+
if (isEditable(current)) return current;
|
|
4045
3873
|
}
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
});
|
|
4054
|
-
} catch (tapError) {
|
|
4055
|
-
if (!fallbackDomClickOnTapError) throw tapError;
|
|
4056
|
-
const fallback = await withTimeout(
|
|
4057
|
-
() => activateElementFallback(element, tapTarget, {
|
|
4058
|
-
editableOnly: false
|
|
4059
|
-
}),
|
|
4060
|
-
activateFallbackTimeoutMs,
|
|
4061
|
-
"activation fallback"
|
|
4062
|
-
).catch(() => null);
|
|
4063
|
-
if (!fallback?.activated) throw tapError;
|
|
4064
|
-
logger5.warn(`humanClick: tap \u5931\u8D25\u540E\u5DF2\u7528 ${fallback.method} \u515C\u5E95: ${tapError?.message || tapError}`);
|
|
3874
|
+
return null;
|
|
3875
|
+
};
|
|
3876
|
+
const editable = findEditable(el);
|
|
3877
|
+
if (editable && typeof editable.focus === "function") {
|
|
3878
|
+
editable.focus({ preventScroll: true });
|
|
3879
|
+
if (innerOptions.editableOnly) {
|
|
3880
|
+
return { activated: true, method: "focus", tag: editable.tagName || "" };
|
|
4065
3881
|
}
|
|
4066
|
-
await waitJitter(120, 0.35);
|
|
4067
|
-
logger5.success("humanClick");
|
|
4068
|
-
return true;
|
|
4069
|
-
} catch (error) {
|
|
4070
|
-
logger5.fail("humanClick", error);
|
|
4071
|
-
throw error;
|
|
4072
3882
|
}
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
await waitJitter(baseMs, jitterPercent);
|
|
4076
|
-
},
|
|
4077
|
-
async simulateGaze(page, baseDurationMs = 2500) {
|
|
4078
|
-
const durationMs = jitterMs(baseDurationMs, 0.4);
|
|
4079
|
-
const startTime = Date.now();
|
|
4080
|
-
while (Date.now() - startTime < durationMs) {
|
|
4081
|
-
if (Math.random() < 0.28) {
|
|
4082
|
-
const distance = 70 + Math.random() * 120;
|
|
4083
|
-
await dispatchTouchSwipe(page, Math.random() < 0.7 ? distance : -distance, {
|
|
4084
|
-
durationMs: 180,
|
|
4085
|
-
steps: 4
|
|
4086
|
-
});
|
|
4087
|
-
} else {
|
|
4088
|
-
await waitJitter(420, 0.55);
|
|
4089
|
-
}
|
|
3883
|
+
if (innerOptions.editableOnly) {
|
|
3884
|
+
return { activated: false, method: "none", tag: el?.tagName || "" };
|
|
4090
3885
|
}
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
const {
|
|
4094
|
-
baseDelay = 160,
|
|
4095
|
-
pauseProbability = 0.08,
|
|
4096
|
-
pauseBase = 700
|
|
4097
|
-
} = options;
|
|
4098
|
-
await MobileHumanize.humanClick(page, selector, { scrollIfNeeded: true });
|
|
4099
|
-
await waitJitter(220, 0.45);
|
|
4100
|
-
for (let i = 0; i < String(text).length; i += 1) {
|
|
4101
|
-
const char = String(text)[i];
|
|
4102
|
-
const charDelay = /[,.!?;:,。!?;:]/.test(char) ? jitterMs(baseDelay * 1.45, 0.4) : jitterMs(char === " " ? baseDelay * 0.65 : baseDelay, 0.4);
|
|
4103
|
-
await page.keyboard.type(char);
|
|
4104
|
-
await waitJitter(charDelay, 0.15);
|
|
4105
|
-
if (Math.random() < pauseProbability && i < String(text).length - 1) {
|
|
4106
|
-
await waitJitter(pauseBase, 0.5);
|
|
4107
|
-
}
|
|
3886
|
+
if (typeof el.focus === "function") {
|
|
3887
|
+
el.focus({ preventScroll: true });
|
|
4108
3888
|
}
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
const key = hasTarget ? maybeKey : targetOrKey;
|
|
4113
|
-
const pressOptions = hasTarget ? options : maybeKey || options;
|
|
4114
|
-
const {
|
|
4115
|
-
reactionDelay = 170,
|
|
4116
|
-
holdDelay = 42,
|
|
4117
|
-
focusDelay = 180,
|
|
4118
|
-
scrollIfNeeded = true,
|
|
4119
|
-
throwOnMissing = true,
|
|
4120
|
-
keyboardOptions = {}
|
|
4121
|
-
} = pressOptions || {};
|
|
4122
|
-
const targetDesc = hasTarget ? describeTarget(targetOrKey) : "current focus";
|
|
4123
|
-
logger5.start("humanPress", `key=${key}, target=${targetDesc}`);
|
|
4124
|
-
try {
|
|
4125
|
-
if (hasTarget) {
|
|
4126
|
-
await MobileHumanize.humanClick(page, targetOrKey, {
|
|
4127
|
-
reactionDelay: focusDelay,
|
|
4128
|
-
scrollIfNeeded,
|
|
4129
|
-
throwOnMissing
|
|
4130
|
-
});
|
|
4131
|
-
}
|
|
4132
|
-
await waitJitter(reactionDelay, 0.45);
|
|
4133
|
-
await page.keyboard.press(key, {
|
|
4134
|
-
...keyboardOptions,
|
|
4135
|
-
delay: jitterMs(holdDelay, 0.5)
|
|
4136
|
-
});
|
|
4137
|
-
logger5.success("humanPress");
|
|
4138
|
-
return true;
|
|
4139
|
-
} catch (error) {
|
|
4140
|
-
logger5.fail("humanPress", error);
|
|
4141
|
-
throw error;
|
|
3889
|
+
if (typeof el.click === "function") {
|
|
3890
|
+
el.click();
|
|
3891
|
+
return { activated: true, method: "dom-click", tag: el.tagName || "" };
|
|
4142
3892
|
}
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
3893
|
+
if (typeof el.dispatchEvent === "function") {
|
|
3894
|
+
el.dispatchEvent(new MouseEvent("click", {
|
|
3895
|
+
bubbles: true,
|
|
3896
|
+
cancelable: true,
|
|
3897
|
+
view: window
|
|
3898
|
+
}));
|
|
3899
|
+
return { activated: true, method: "dispatch-click", tag: el.tagName || "" };
|
|
3900
|
+
}
|
|
3901
|
+
return {
|
|
3902
|
+
activated: Boolean(editable),
|
|
3903
|
+
method: editable ? "focus" : "none",
|
|
3904
|
+
tag: el?.tagName || ""
|
|
4154
3905
|
};
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
if (
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
3906
|
+
}, {
|
|
3907
|
+
innerOptions: options || {}
|
|
3908
|
+
});
|
|
3909
|
+
};
|
|
3910
|
+
var getScrollableRect = async (element) => {
|
|
3911
|
+
return element.evaluate((el) => {
|
|
3912
|
+
const isScrollable = (node) => {
|
|
3913
|
+
const style = window.getComputedStyle(node);
|
|
3914
|
+
if (!style) return false;
|
|
3915
|
+
const overflowY = style.overflowY;
|
|
3916
|
+
if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
|
|
3917
|
+
return node.scrollHeight > node.clientHeight + 1;
|
|
3918
|
+
};
|
|
3919
|
+
let current = el;
|
|
3920
|
+
while (current && current !== document.body) {
|
|
3921
|
+
if (isScrollable(current)) {
|
|
3922
|
+
const rect = current.getBoundingClientRect();
|
|
3923
|
+
if (rect && rect.width > 0 && rect.height > 0) {
|
|
3924
|
+
return {
|
|
3925
|
+
x: rect.x,
|
|
3926
|
+
y: rect.y,
|
|
3927
|
+
width: rect.width,
|
|
3928
|
+
height: rect.height
|
|
3929
|
+
};
|
|
3930
|
+
}
|
|
4168
3931
|
}
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
}
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
const
|
|
4178
|
-
if (
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
3932
|
+
current = current.parentElement;
|
|
3933
|
+
}
|
|
3934
|
+
return null;
|
|
3935
|
+
});
|
|
3936
|
+
};
|
|
3937
|
+
var scrollScrollableAncestor = async (element, deltaY) => {
|
|
3938
|
+
return element.evaluate((el, amount) => {
|
|
3939
|
+
const isScrollable = (node) => {
|
|
3940
|
+
const style = window.getComputedStyle(node);
|
|
3941
|
+
if (!style) return false;
|
|
3942
|
+
const overflowY = style.overflowY;
|
|
3943
|
+
if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
|
|
3944
|
+
return node.scrollHeight > node.clientHeight + 1;
|
|
3945
|
+
};
|
|
3946
|
+
let current = el;
|
|
3947
|
+
while (current && current !== document.body) {
|
|
3948
|
+
if (isScrollable(current)) {
|
|
3949
|
+
const beforeTop2 = current.scrollTop;
|
|
3950
|
+
current.scrollTop = beforeTop2 + amount;
|
|
3951
|
+
return {
|
|
3952
|
+
scroller: true,
|
|
3953
|
+
moved: current.scrollTop !== beforeTop2,
|
|
3954
|
+
scrollTop: current.scrollTop
|
|
3955
|
+
};
|
|
4190
3956
|
}
|
|
3957
|
+
current = current.parentElement;
|
|
4191
3958
|
}
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
3959
|
+
const beforeTop = window.scrollY;
|
|
3960
|
+
window.scrollBy(0, amount);
|
|
3961
|
+
return {
|
|
3962
|
+
scroller: null,
|
|
3963
|
+
moved: window.scrollY !== beforeTop,
|
|
3964
|
+
scrollTop: window.scrollY
|
|
3965
|
+
};
|
|
3966
|
+
}, deltaY);
|
|
3967
|
+
};
|
|
3968
|
+
var scrollAwayFromObstruction = async (element, status) => {
|
|
3969
|
+
return element.evaluate((el, innerStatus) => {
|
|
3970
|
+
const isScrollable = (node) => {
|
|
3971
|
+
const style = window.getComputedStyle(node);
|
|
3972
|
+
if (!style) return false;
|
|
3973
|
+
const overflowY = style.overflowY;
|
|
3974
|
+
if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
|
|
3975
|
+
return node.scrollHeight > node.clientHeight + 1;
|
|
3976
|
+
};
|
|
3977
|
+
let scroller = el;
|
|
3978
|
+
while (scroller && scroller !== document.body) {
|
|
3979
|
+
if (isScrollable(scroller)) break;
|
|
3980
|
+
scroller = scroller.parentElement;
|
|
3981
|
+
}
|
|
3982
|
+
if (!scroller || scroller === document.body) {
|
|
3983
|
+
return { moved: false, scrollTop: 0, deltaY: 0 };
|
|
3984
|
+
}
|
|
3985
|
+
const rect = el.getBoundingClientRect();
|
|
3986
|
+
const scrollerRect = scroller.getBoundingClientRect();
|
|
3987
|
+
const obstruction = innerStatus?.obstruction || {};
|
|
3988
|
+
const obstructionTop = Number(obstruction.top);
|
|
3989
|
+
const obstructionBottom = Number(obstruction.bottom);
|
|
3990
|
+
const obstructionMiddle = Number.isFinite(obstructionTop) && Number.isFinite(obstructionBottom) ? (obstructionTop + obstructionBottom) / 2 : scrollerRect.top + scrollerRect.height / 2;
|
|
3991
|
+
const scrollerMiddle = scrollerRect.top + scrollerRect.height / 2;
|
|
3992
|
+
const padding = 18;
|
|
3993
|
+
let deltaY = 0;
|
|
3994
|
+
if (Number.isFinite(obstructionTop) && rect.bottom > obstructionTop && obstructionTop >= scrollerRect.top && obstructionTop <= scrollerRect.bottom && obstructionMiddle >= scrollerMiddle) {
|
|
3995
|
+
deltaY = rect.bottom - obstructionTop + padding;
|
|
3996
|
+
} else if (Number.isFinite(obstructionBottom) && rect.top < obstructionBottom && obstructionBottom >= scrollerRect.top && obstructionBottom <= scrollerRect.bottom && obstructionMiddle < scrollerMiddle) {
|
|
3997
|
+
deltaY = rect.top - obstructionBottom - padding;
|
|
3998
|
+
} else {
|
|
3999
|
+
const fallbackDistance = Math.max(48, Math.min(180, scrollerRect.height * 0.45));
|
|
4000
|
+
deltaY = obstructionMiddle >= scrollerMiddle ? fallbackDistance : -fallbackDistance;
|
|
4203
4001
|
}
|
|
4204
|
-
|
|
4002
|
+
const beforeTop = scroller.scrollTop;
|
|
4003
|
+
scroller.scrollTop = beforeTop + deltaY;
|
|
4004
|
+
return {
|
|
4005
|
+
moved: Math.abs(scroller.scrollTop - beforeTop) > 2,
|
|
4006
|
+
scrollTop: scroller.scrollTop,
|
|
4007
|
+
deltaY
|
|
4008
|
+
};
|
|
4009
|
+
}, status);
|
|
4205
4010
|
};
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4011
|
+
var getElementViewportSnapshot = async (element) => {
|
|
4012
|
+
return element.evaluate((el) => {
|
|
4013
|
+
const rect = el.getBoundingClientRect();
|
|
4014
|
+
return {
|
|
4015
|
+
top: rect.top,
|
|
4016
|
+
bottom: rect.bottom,
|
|
4017
|
+
left: rect.left,
|
|
4018
|
+
right: rect.right,
|
|
4019
|
+
width: rect.width,
|
|
4020
|
+
height: rect.height,
|
|
4021
|
+
scrollX: window.scrollX,
|
|
4022
|
+
scrollY: window.scrollY
|
|
4023
|
+
};
|
|
4024
|
+
});
|
|
4025
|
+
};
|
|
4026
|
+
var isTargetImmobileAfterScroll = (before, after) => {
|
|
4027
|
+
if (!before || !after) return false;
|
|
4028
|
+
const rectDeltaY = Number(after.top || 0) - Number(before.top || 0);
|
|
4029
|
+
const rectDeltaX = Number(after.left || 0) - Number(before.left || 0);
|
|
4030
|
+
const scrollDeltaY = Number(after.scrollY || 0) - Number(before.scrollY || 0);
|
|
4031
|
+
const scrollDeltaX = Number(after.scrollX || 0) - Number(before.scrollX || 0);
|
|
4032
|
+
const rectMoved = Math.abs(rectDeltaY) > 3 || Math.abs(rectDeltaX) > 3;
|
|
4033
|
+
const pageMoved = Math.abs(scrollDeltaY) > 3 || Math.abs(scrollDeltaX) > 3;
|
|
4034
|
+
if (!rectMoved && !pageMoved) return true;
|
|
4035
|
+
if (pageMoved && !rectMoved) return true;
|
|
4036
|
+
if (Math.abs(scrollDeltaY) > 12 && Math.abs(rectDeltaY) < Math.min(12, Math.abs(scrollDeltaY) * 0.2)) {
|
|
4037
|
+
return true;
|
|
4215
4038
|
}
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4039
|
+
return false;
|
|
4040
|
+
};
|
|
4041
|
+
var restoreWindowFromSnapshot = async (page, before, after) => {
|
|
4042
|
+
if (!before || !after) return;
|
|
4043
|
+
if (Math.abs(Number(after.scrollX || 0) - Number(before.scrollX || 0)) <= 2 && Math.abs(Number(after.scrollY || 0) - Number(before.scrollY || 0)) <= 2) {
|
|
4044
|
+
return;
|
|
4219
4045
|
}
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4046
|
+
await page.evaluate(
|
|
4047
|
+
(state) => window.scrollTo(state.x, state.y),
|
|
4048
|
+
{ x: Number(before.scrollX || 0), y: Number(before.scrollY || 0) }
|
|
4049
|
+
).catch(() => {
|
|
4050
|
+
});
|
|
4224
4051
|
};
|
|
4225
|
-
var
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
}
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4052
|
+
var dispatchTouchSwipe = async (page, deltaY, options = {}) => {
|
|
4053
|
+
const viewport = resolveViewport(page);
|
|
4054
|
+
const rawRect = options.rect || null;
|
|
4055
|
+
const rect = rawRect ? {
|
|
4056
|
+
x: clamp(rawRect.x, 0, viewport.width),
|
|
4057
|
+
y: clamp(rawRect.y, 0, viewport.height),
|
|
4058
|
+
width: clamp(rawRect.width, 0, viewport.width),
|
|
4059
|
+
height: clamp(rawRect.height, 0, viewport.height)
|
|
4060
|
+
} : null;
|
|
4061
|
+
const area = rect && rect.width > 24 && rect.height > 48 ? {
|
|
4062
|
+
left: rect.x,
|
|
4063
|
+
right: Math.min(viewport.width, rect.x + rect.width),
|
|
4064
|
+
top: rect.y,
|
|
4065
|
+
bottom: Math.min(viewport.height, rect.y + rect.height)
|
|
4066
|
+
} : {
|
|
4067
|
+
left: 0,
|
|
4068
|
+
right: viewport.width,
|
|
4069
|
+
top: 0,
|
|
4070
|
+
bottom: viewport.height
|
|
4071
|
+
};
|
|
4072
|
+
const areaWidth = Math.max(1, area.right - area.left);
|
|
4073
|
+
const areaHeight = Math.max(1, area.bottom - area.top);
|
|
4074
|
+
const maxDistance = rect ? Math.max(60, areaHeight * 0.72) : Math.max(120, viewport.height * 0.72);
|
|
4075
|
+
const distance = clamp(Math.abs(deltaY), Math.min(80, maxDistance), maxDistance);
|
|
4076
|
+
const direction = deltaY >= 0 ? 1 : -1;
|
|
4077
|
+
const startX = clamp(
|
|
4078
|
+
area.left + areaWidth * (0.45 + Math.random() * 0.1),
|
|
4079
|
+
24,
|
|
4080
|
+
viewport.width - 24
|
|
4081
|
+
);
|
|
4082
|
+
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);
|
|
4083
|
+
const endY = clamp(startY - direction * distance, Math.max(16, area.top + 12), Math.min(viewport.height - 16, area.bottom - 12));
|
|
4084
|
+
const steps = Math.max(4, Math.round(Number(options.steps || 6)));
|
|
4085
|
+
const durationMs = jitterMs(options.durationMs || 320, 0.35);
|
|
4086
|
+
let client = null;
|
|
4087
|
+
const scrollBefore = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })).catch(() => null);
|
|
4088
|
+
try {
|
|
4089
|
+
if (page.context && typeof page.context().newCDPSession === "function") {
|
|
4090
|
+
client = await page.context().newCDPSession(page);
|
|
4256
4091
|
}
|
|
4257
|
-
|
|
4092
|
+
if (!client) throw new Error("CDP session unavailable");
|
|
4093
|
+
await client.send("Input.synthesizeScrollGesture", {
|
|
4094
|
+
x: startX,
|
|
4095
|
+
y: startY,
|
|
4096
|
+
yDistance: -direction * distance,
|
|
4097
|
+
xOverscroll: (Math.random() - 0.5) * 4,
|
|
4098
|
+
yOverscroll: (Math.random() - 0.5) * 8,
|
|
4099
|
+
speed: 700 + Math.round(Math.random() * 350),
|
|
4100
|
+
gestureSourceType: "touch"
|
|
4258
4101
|
});
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
if (target == null) {
|
|
4263
|
-
await DeviceInput.clickPoint(page, { x: 0, y: 0 }, void 0, { forceMouse: true });
|
|
4264
|
-
return true;
|
|
4265
|
-
}
|
|
4266
|
-
if (isPoint2(target)) {
|
|
4267
|
-
await DeviceInput.clickPoint(page, target, void 0, { forceMouse: true });
|
|
4102
|
+
await waitJitter(160, 0.35);
|
|
4103
|
+
const scrollAfterSynth = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })).catch(() => null);
|
|
4104
|
+
if (scrollBefore && scrollAfterSynth && (Math.abs(scrollAfterSynth.y - scrollBefore.y) > 2 || Math.abs(scrollAfterSynth.x - scrollBefore.x) > 2)) {
|
|
4268
4105
|
return true;
|
|
4269
4106
|
}
|
|
4270
|
-
await
|
|
4271
|
-
|
|
4107
|
+
await client.send("Input.dispatchTouchEvent", {
|
|
4108
|
+
type: "touchStart",
|
|
4109
|
+
touchPoints: [{ x: startX, y: startY, id: 1 }]
|
|
4272
4110
|
});
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
await DeviceInput.move(page, {
|
|
4284
|
-
x: 100 + Math.random() * Math.max(120, viewport.width - 200),
|
|
4285
|
-
y: 100 + Math.random() * Math.max(120, viewport.height - 200)
|
|
4286
|
-
}, void 0, { forceMouse: true });
|
|
4287
|
-
await sleep(300, 0.5);
|
|
4111
|
+
for (let i = 1; i <= steps; i += 1) {
|
|
4112
|
+
const progress = i / steps;
|
|
4113
|
+
const eased = 1 - Math.pow(1 - progress, 2);
|
|
4114
|
+
const x = startX + (Math.random() - 0.5) * 3;
|
|
4115
|
+
const y = startY + (endY - startY) * eased + (Math.random() - 0.5) * 5;
|
|
4116
|
+
await waitJitter(durationMs / steps, 0.25);
|
|
4117
|
+
await client.send("Input.dispatchTouchEvent", {
|
|
4118
|
+
type: "touchMove",
|
|
4119
|
+
touchPoints: [{ x, y, id: 1 }]
|
|
4120
|
+
});
|
|
4288
4121
|
}
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
clickOptions: FORCE_CLICK_OPTIONS
|
|
4293
|
-
});
|
|
4294
|
-
await DeviceInput.keyboardType(page, text, {
|
|
4295
|
-
...options.baseDelay != null ? { delay: Math.max(0, Number(options.baseDelay) || 0) } : {}
|
|
4122
|
+
await client.send("Input.dispatchTouchEvent", {
|
|
4123
|
+
type: "touchEnd",
|
|
4124
|
+
touchPoints: []
|
|
4296
4125
|
});
|
|
4126
|
+
await waitJitter(120, 0.35);
|
|
4127
|
+
const scrollAfterTouch = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })).catch(() => null);
|
|
4128
|
+
if (scrollBefore && scrollAfterTouch && Math.abs(scrollAfterTouch.y - scrollBefore.y) <= 2 && Math.abs(scrollAfterTouch.x - scrollBefore.x) <= 2) {
|
|
4129
|
+
await page.evaluate((amount) => window.scrollBy(0, amount), deltaY);
|
|
4130
|
+
await waitJitter(120, 0.35);
|
|
4131
|
+
}
|
|
4297
4132
|
return true;
|
|
4298
|
-
}
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
await
|
|
4302
|
-
|
|
4303
|
-
|
|
4133
|
+
} catch (error) {
|
|
4134
|
+
logger7.debug(`touch swipe fallback: ${error?.message || error}`);
|
|
4135
|
+
try {
|
|
4136
|
+
await page.evaluate((amount) => window.scrollBy(0, amount), deltaY);
|
|
4137
|
+
await waitJitter(120, 0.35);
|
|
4138
|
+
return true;
|
|
4139
|
+
} catch {
|
|
4140
|
+
await page.mouse.wheel(0, deltaY);
|
|
4141
|
+
await waitJitter(120, 0.35);
|
|
4304
4142
|
return true;
|
|
4305
4143
|
}
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
async humanClear(page, target) {
|
|
4313
|
-
await DeviceInput.fill(page, target, "", { force: true });
|
|
4314
|
-
},
|
|
4315
|
-
async warmUpBrowsing(page, baseDuration = 3500) {
|
|
4316
|
-
await this.simulateGaze(page, Math.min(baseDuration, 900));
|
|
4317
|
-
},
|
|
4318
|
-
async naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
|
|
4319
|
-
const steps = Math.max(1, Number(baseSteps) || 1);
|
|
4320
|
-
const delta = (Number(distance) || 0) / steps * (direction === "down" ? 1 : -1);
|
|
4321
|
-
for (let index = 0; index < steps; index += 1) {
|
|
4322
|
-
await page.mouse.wheel(0, delta);
|
|
4323
|
-
await sleep(60 + index * 20, 0.3);
|
|
4144
|
+
} finally {
|
|
4145
|
+
if (client && typeof client.detach === "function") {
|
|
4146
|
+
try {
|
|
4147
|
+
await client.detach();
|
|
4148
|
+
} catch {
|
|
4149
|
+
}
|
|
4324
4150
|
}
|
|
4325
4151
|
}
|
|
4326
4152
|
};
|
|
4327
|
-
var
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4153
|
+
var tapPoint = async (page, point, options = {}) => {
|
|
4154
|
+
const {
|
|
4155
|
+
timeoutMs = DEFAULT_TAP_TIMEOUT_MS,
|
|
4156
|
+
mouseFallbackTimeoutMs = DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS,
|
|
4157
|
+
allowMouseFallback = true
|
|
4158
|
+
} = options;
|
|
4159
|
+
if (page.touchscreen && typeof page.touchscreen.tap === "function") {
|
|
4160
|
+
try {
|
|
4161
|
+
await withTimeout(
|
|
4162
|
+
() => page.touchscreen.tap(point.x, point.y),
|
|
4163
|
+
timeoutMs,
|
|
4164
|
+
"touchscreen.tap"
|
|
4165
|
+
);
|
|
4166
|
+
return { method: "touchscreen" };
|
|
4167
|
+
} catch (error) {
|
|
4168
|
+
logger7.warn(`tapPoint | touchscreen.tap \u5931\u8D25\u6216\u8D85\u65F6\uFF0C\u5C1D\u8BD5\u9F20\u6807\u515C\u5E95: ${error?.message || error}`);
|
|
4169
|
+
if (!allowMouseFallback) throw error;
|
|
4170
|
+
}
|
|
4342
4171
|
}
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
jitterMs(base, jitterPercent = 0.3) {
|
|
4353
|
-
const jitter = base * jitterPercent * (Math.random() * 2 - 1);
|
|
4354
|
-
return Math.max(10, Math.round(base + jitter));
|
|
4355
|
-
},
|
|
4356
|
-
/**
|
|
4357
|
-
* 初始化页面的 Ghost Cursor(必须在使用其他 cursor 相关方法前调用)
|
|
4358
|
-
*
|
|
4359
|
-
* @param {import('playwright').Page} page
|
|
4360
|
-
* @returns {Promise<void>}
|
|
4361
|
-
*/
|
|
4172
|
+
await withTimeout(
|
|
4173
|
+
() => page.mouse.click(point.x, point.y),
|
|
4174
|
+
mouseFallbackTimeoutMs,
|
|
4175
|
+
"mouse.click fallback"
|
|
4176
|
+
);
|
|
4177
|
+
return { method: "mouse" };
|
|
4178
|
+
};
|
|
4179
|
+
var MobileHumanize = {
|
|
4180
|
+
jitterMs,
|
|
4362
4181
|
async initializeCursor(page) {
|
|
4363
|
-
if (
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
}
|
|
4367
|
-
logger6.start("initializeCursor", "creating cursor");
|
|
4368
|
-
const cursor = await (0, import_ghost_cursor_playwright.createCursor)(page);
|
|
4369
|
-
$CursorWeakMap.set(page, cursor);
|
|
4370
|
-
logger6.success("initializeCursor", "cursor initialized");
|
|
4182
|
+
if (initializedPages.has(page)) return;
|
|
4183
|
+
initializedPages.add(page);
|
|
4184
|
+
logger7.debug("initializeCursor: mobile mode uses touch gestures, cursor init skipped");
|
|
4371
4185
|
},
|
|
4372
|
-
/**
|
|
4373
|
-
* 人类化鼠标移动 - 使用 ghost-cursor 移动到指定位置或元素
|
|
4374
|
-
*
|
|
4375
|
-
* @param {import('playwright').Page} page
|
|
4376
|
-
* @param {string|{x: number, y: number}|import('playwright').ElementHandle} target - CSS选择器、坐标对象或元素句柄
|
|
4377
|
-
*/
|
|
4378
4186
|
async humanMove(page, target) {
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
if (
|
|
4383
|
-
|
|
4384
|
-
if (!element) {
|
|
4385
|
-
logger6.warn(`humanMove: \u5143\u7D20\u4E0D\u5B58\u5728 ${target}`);
|
|
4386
|
-
return false;
|
|
4387
|
-
}
|
|
4388
|
-
const box = await element.boundingBox();
|
|
4389
|
-
if (!box) {
|
|
4390
|
-
logger6.warn(`humanMove: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E ${target}`);
|
|
4391
|
-
return false;
|
|
4392
|
-
}
|
|
4393
|
-
const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
|
|
4394
|
-
const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
|
|
4395
|
-
await cursor.actions.move({ x, y });
|
|
4396
|
-
} else if (target && typeof target.x === "number" && typeof target.y === "number") {
|
|
4397
|
-
await cursor.actions.move(target);
|
|
4398
|
-
} else if (target && typeof target.boundingBox === "function") {
|
|
4399
|
-
const box = await target.boundingBox();
|
|
4400
|
-
if (box) {
|
|
4401
|
-
const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
|
|
4402
|
-
const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
|
|
4403
|
-
await cursor.actions.move({ x, y });
|
|
4404
|
-
}
|
|
4187
|
+
logger7.debug(`humanMove: mobile no-op target=${typeof target === "string" ? target : "element/coords"}`);
|
|
4188
|
+
if (typeof target === "string" || target && typeof target.boundingBox === "function") {
|
|
4189
|
+
const element = await resolveElement(page, target, { throwOnMissing: false });
|
|
4190
|
+
if (!element) {
|
|
4191
|
+
return false;
|
|
4405
4192
|
}
|
|
4406
|
-
logger6.success("humanMove");
|
|
4407
|
-
return true;
|
|
4408
|
-
} catch (error) {
|
|
4409
|
-
logger6.fail("humanMove", error);
|
|
4410
|
-
throw error;
|
|
4411
4193
|
}
|
|
4194
|
+
await waitJitter(80, 0.4);
|
|
4195
|
+
return true;
|
|
4412
4196
|
},
|
|
4413
|
-
/**
|
|
4414
|
-
* 渐进式滚动到元素可见(仅处理 Y 轴滚动)
|
|
4415
|
-
* 返回 restore 方法,用于将滚动容器恢复到原位置
|
|
4416
|
-
*
|
|
4417
|
-
* @param {import('playwright').Page} page
|
|
4418
|
-
* @param {string|import('playwright').ElementHandle} target - CSS 选择器或元素句柄
|
|
4419
|
-
* @param {Object} [options]
|
|
4420
|
-
* @param {number} [options.maxSteps=20] - 最大滚动步数
|
|
4421
|
-
* @param {number} [options.minStep=260] - 单次滚动最小步长
|
|
4422
|
-
* @param {number} [options.maxStep=800] - 单次滚动最大步长
|
|
4423
|
-
* @param {number} [options.maxDurationMs] - 最长耗时上限 (默认随 maxSteps 估算)
|
|
4424
|
-
*/
|
|
4425
4197
|
async humanScroll(page, target, options = {}) {
|
|
4426
4198
|
const {
|
|
4427
4199
|
maxSteps = 20,
|
|
4428
|
-
minStep =
|
|
4429
|
-
maxStep =
|
|
4430
|
-
maxDurationMs = maxSteps *
|
|
4200
|
+
minStep = 180,
|
|
4201
|
+
maxStep = 520,
|
|
4202
|
+
maxDurationMs = maxSteps * 280 + 1200,
|
|
4203
|
+
throwOnMissing = false
|
|
4431
4204
|
} = options;
|
|
4432
|
-
const targetDesc =
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
if (
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
logger6.warn(`humanScroll | \u5143\u7D20\u672A\u627E\u5230: ${target}`);
|
|
4439
|
-
return { element: null, didScroll: false };
|
|
4440
|
-
}
|
|
4441
|
-
} else {
|
|
4442
|
-
element = target;
|
|
4205
|
+
const targetDesc = describeTarget(target);
|
|
4206
|
+
logger7.start("humanScroll", `target=${targetDesc}`);
|
|
4207
|
+
const element = await resolveElement(page, target, { throwOnMissing });
|
|
4208
|
+
if (!element) {
|
|
4209
|
+
logger7.warn(`humanScroll | \u5143\u7D20\u672A\u627E\u5230: ${targetDesc}`);
|
|
4210
|
+
return { element: null, didScroll: false, restore: null };
|
|
4443
4211
|
}
|
|
4444
|
-
const
|
|
4212
|
+
const startTime = Date.now();
|
|
4445
4213
|
let didScroll = false;
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
if (
|
|
4450
|
-
|
|
4451
|
-
}
|
|
4452
|
-
|
|
4453
|
-
const cy = rect.top + rect.height / 2;
|
|
4454
|
-
const viewH = window.innerHeight;
|
|
4455
|
-
const viewW = window.innerWidth;
|
|
4456
|
-
let isFixed = false;
|
|
4457
|
-
for (let node = el; node && node !== document.body; node = node.parentElement) {
|
|
4458
|
-
const style = window.getComputedStyle(node);
|
|
4459
|
-
if (style && style.position === "fixed") {
|
|
4460
|
-
isFixed = true;
|
|
4461
|
-
break;
|
|
4462
|
-
}
|
|
4214
|
+
for (let i = 0; i < maxSteps; i += 1) {
|
|
4215
|
+
const status = await checkElementVisibility(element);
|
|
4216
|
+
if (status.code === "VISIBLE") {
|
|
4217
|
+
if (status.isFixed) {
|
|
4218
|
+
logger7.info("humanScroll | fixed/sticky \u5BB9\u5668\u5185\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
|
|
4219
|
+
} else {
|
|
4220
|
+
logger7.debug("humanScroll | \u5143\u7D20\u53EF\u89C1\u4E14\u65E0\u906E\u6321");
|
|
4463
4221
|
}
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4222
|
+
logger7.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
|
|
4223
|
+
return { element, didScroll, restore: null };
|
|
4224
|
+
}
|
|
4225
|
+
if (status.code === "ZERO_DIMENSIONS" || status.code === "NOT_INTERACTABLE") {
|
|
4226
|
+
logger7.warn(`humanScroll | \u5143\u7D20\u4E0D\u53EF\u6EDA\u52A8\u81F3\u53EF\u70B9\u51FB\u72B6\u6001: ${status.reason || status.code}`);
|
|
4227
|
+
return { element, didScroll, restore: null };
|
|
4228
|
+
}
|
|
4229
|
+
const scrollRect = await getScrollableRect(element);
|
|
4230
|
+
if (!scrollRect && status.isFixed && status.code === "OUT_OF_VIEWPORT") {
|
|
4231
|
+
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"})`);
|
|
4232
|
+
return { element, didScroll, restore: null, unscrollable: true };
|
|
4233
|
+
}
|
|
4234
|
+
if (!scrollRect && status.isFixed && status.code === "OBSTRUCTED") {
|
|
4235
|
+
logger7.warn(`humanScroll | fixed/sticky \u76EE\u6807\u88AB\u906E\u6321\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u89E3\u9664 (${status.obstruction?.tag || "unknown"})`);
|
|
4236
|
+
return { element, didScroll, restore: null, unscrollable: true };
|
|
4237
|
+
}
|
|
4238
|
+
if (scrollRect && status.code === "OBSTRUCTED" && status.obstruction?.isFixed) {
|
|
4239
|
+
const moved = await scrollAwayFromObstruction(element, status);
|
|
4240
|
+
if (moved.moved) {
|
|
4241
|
+
logger7.debug(`humanScroll | sticky/fixed \u906E\u6321\u8865\u507F\u6EDA\u52A8 top=${Math.round(moved.scrollTop || 0)}`);
|
|
4242
|
+
await waitJitter(90, 0.3);
|
|
4243
|
+
didScroll = true;
|
|
4244
|
+
continue;
|
|
4467
4245
|
}
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4246
|
+
}
|
|
4247
|
+
if (Date.now() - startTime > maxDurationMs) {
|
|
4248
|
+
logger7.warn(`humanScroll | mobile timeout (${maxDurationMs}ms, status=${status.code}, direction=${status.direction || "unknown"}, fixed=${Boolean(status.isFixed)})`);
|
|
4249
|
+
return { element, didScroll, restore: null };
|
|
4250
|
+
}
|
|
4251
|
+
const stepMin = scrollRect ? Math.min(minStep, Math.max(60, scrollRect.height * 0.4)) : minStep;
|
|
4252
|
+
const stepMax = scrollRect ? Math.min(maxStep, Math.max(stepMin + 40, scrollRect.height * 0.8)) : maxStep;
|
|
4253
|
+
logger7.debug(`humanScroll | \u6B65\u9AA4 ${i + 1}/${maxSteps}: ${status.reason || status.code} ${status.direction ? `(${status.direction})` : ""}`);
|
|
4254
|
+
const distance = stepMin + Math.random() * Math.max(1, stepMax - stepMin);
|
|
4255
|
+
let deltaY = status.direction === "up" ? -distance : distance;
|
|
4256
|
+
if (status.code === "OBSTRUCTED") {
|
|
4257
|
+
if (status.obstruction?.isFixed && status.obstruction.top != null) {
|
|
4258
|
+
const obstructionMiddle = (Number(status.obstruction.top || 0) + Number(status.obstruction.bottom || status.obstruction.top || 0)) / 2;
|
|
4259
|
+
const visibleMiddle = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
|
|
4260
|
+
deltaY = obstructionMiddle < visibleMiddle ? -distance : distance;
|
|
4261
|
+
} else {
|
|
4262
|
+
const halfY = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
|
|
4263
|
+
deltaY = status.cy > halfY ? distance : -distance;
|
|
4483
4264
|
}
|
|
4484
|
-
|
|
4485
|
-
});
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
return await element.evaluate((el) => {
|
|
4265
|
+
}
|
|
4266
|
+
const beforeWindowState = scrollRect ? await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })) : null;
|
|
4267
|
+
const beforeElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
|
|
4268
|
+
const beforeState = scrollRect ? await element.evaluate((el) => {
|
|
4489
4269
|
const isScrollable = (node) => {
|
|
4490
4270
|
const style = window.getComputedStyle(node);
|
|
4491
4271
|
if (!style) return false;
|
|
@@ -4496,427 +4276,386 @@ var Humanize = {
|
|
|
4496
4276
|
let current = el;
|
|
4497
4277
|
while (current && current !== document.body) {
|
|
4498
4278
|
if (isScrollable(current)) {
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4279
|
+
return {
|
|
4280
|
+
kind: "element",
|
|
4281
|
+
top: current.scrollTop,
|
|
4282
|
+
left: current.scrollLeft
|
|
4283
|
+
};
|
|
4503
4284
|
}
|
|
4504
4285
|
current = current.parentElement;
|
|
4505
4286
|
}
|
|
4506
|
-
return
|
|
4507
|
-
});
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
return { element, didScroll };
|
|
4515
|
-
}
|
|
4516
|
-
const status = await checkVisibility();
|
|
4517
|
-
if (status.code === "VISIBLE") {
|
|
4518
|
-
if (status.isFixed) {
|
|
4519
|
-
logger6.info("humanScroll | fixed \u5BB9\u5668\u5185\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
|
|
4520
|
-
} else {
|
|
4521
|
-
logger6.debug("humanScroll | \u5143\u7D20\u53EF\u89C1\u4E14\u65E0\u906E\u6321");
|
|
4522
|
-
}
|
|
4523
|
-
logger6.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
|
|
4524
|
-
return { element, didScroll };
|
|
4525
|
-
}
|
|
4526
|
-
logger6.debug(`humanScroll | \u6B65\u9AA4 ${i + 1}/${maxSteps}: ${status.reason} ${status.direction ? `(${status.direction})` : ""}`);
|
|
4527
|
-
if (status.code === "OBSTRUCTED" && status.obstruction) {
|
|
4528
|
-
logger6.debug(`humanScroll | \u88AB\u4EE5\u4E0B\u5143\u7D20\u906E\u6321 <${status.obstruction.tag} id="${status.obstruction.id}">`);
|
|
4287
|
+
return { kind: "window", top: window.scrollY, left: window.scrollX };
|
|
4288
|
+
}) : null;
|
|
4289
|
+
await dispatchTouchSwipe(page, deltaY, { rect: scrollRect });
|
|
4290
|
+
if (scrollRect && beforeWindowState) {
|
|
4291
|
+
const afterWindowState = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY }));
|
|
4292
|
+
if (Math.abs(afterWindowState.x - beforeWindowState.x) > 2 || Math.abs(afterWindowState.y - beforeWindowState.y) > 2) {
|
|
4293
|
+
await page.evaluate((state) => window.scrollTo(state.x, state.y), beforeWindowState);
|
|
4294
|
+
logger7.debug(`humanScroll | \u7A97\u53E3\u6EDA\u52A8\u56DE\u6536 from=${Math.round(afterWindowState.y)} to=${Math.round(beforeWindowState.y)}`);
|
|
4529
4295
|
}
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4296
|
+
}
|
|
4297
|
+
let afterElementSnapshot = null;
|
|
4298
|
+
const readAfterElementSnapshot = async () => {
|
|
4299
|
+
if (!afterElementSnapshot) {
|
|
4300
|
+
afterElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
|
|
4534
4301
|
}
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
} else {
|
|
4544
|
-
deltaY = 100;
|
|
4545
|
-
}
|
|
4546
|
-
} else if (status.code === "OBSTRUCTED") {
|
|
4547
|
-
const halfY = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
|
|
4548
|
-
const isBottomHalf = status.cy > halfY;
|
|
4549
|
-
const direction = isBottomHalf ? 1 : -1;
|
|
4550
|
-
deltaY = direction * (stepMin + Math.random() * 50);
|
|
4302
|
+
return afterElementSnapshot;
|
|
4303
|
+
};
|
|
4304
|
+
if (!scrollRect && beforeElementSnapshot) {
|
|
4305
|
+
const afterSnapshot = await readAfterElementSnapshot();
|
|
4306
|
+
if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
|
|
4307
|
+
await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
|
|
4308
|
+
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"})`);
|
|
4309
|
+
return { element, didScroll, restore: null, unscrollable: true };
|
|
4551
4310
|
}
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
const
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4311
|
+
}
|
|
4312
|
+
if (scrollRect && beforeState) {
|
|
4313
|
+
const afterState = await element.evaluate((el) => {
|
|
4314
|
+
const isScrollable = (node) => {
|
|
4315
|
+
const style = window.getComputedStyle(node);
|
|
4316
|
+
if (!style) return false;
|
|
4317
|
+
const overflowY = style.overflowY;
|
|
4318
|
+
if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
|
|
4319
|
+
return node.scrollHeight > node.clientHeight + 1;
|
|
4320
|
+
};
|
|
4321
|
+
let current = el;
|
|
4322
|
+
while (current && current !== document.body) {
|
|
4323
|
+
if (isScrollable(current)) {
|
|
4324
|
+
return {
|
|
4325
|
+
kind: "element",
|
|
4326
|
+
top: current.scrollTop,
|
|
4327
|
+
left: current.scrollLeft
|
|
4328
|
+
};
|
|
4329
|
+
}
|
|
4330
|
+
current = current.parentElement;
|
|
4331
|
+
}
|
|
4332
|
+
return { kind: "window", top: window.scrollY, left: window.scrollX };
|
|
4333
|
+
});
|
|
4334
|
+
const topDelta = Number(afterState.top || 0) - Number(beforeState.top || 0);
|
|
4335
|
+
const leftDelta = Number(afterState.left || 0) - Number(beforeState.left || 0);
|
|
4336
|
+
const expectedDelta = Number(deltaY || 0);
|
|
4337
|
+
const moved = beforeState.kind !== afterState.kind || Math.abs(topDelta) > 2 || Math.abs(leftDelta) > 2;
|
|
4338
|
+
if (!moved) {
|
|
4339
|
+
const fallback = await scrollScrollableAncestor(element, deltaY);
|
|
4340
|
+
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)}`);
|
|
4341
|
+
} 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)) {
|
|
4342
|
+
const residualDelta = expectedDelta - topDelta;
|
|
4343
|
+
if (Math.sign(residualDelta) === Math.sign(expectedDelta) && Math.abs(residualDelta) > 24) {
|
|
4344
|
+
const fallback = await scrollScrollableAncestor(element, residualDelta);
|
|
4345
|
+
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)}`);
|
|
4562
4346
|
}
|
|
4563
4347
|
}
|
|
4564
|
-
await page.mouse.wheel(0, deltaY);
|
|
4565
|
-
didScroll = true;
|
|
4566
|
-
await (0, import_delay4.default)(this.jitterMs(20 + Math.random() * 40, 0.2));
|
|
4567
4348
|
}
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4349
|
+
if (scrollRect && beforeElementSnapshot) {
|
|
4350
|
+
const afterSnapshot = await getElementViewportSnapshot(element).catch(() => null);
|
|
4351
|
+
if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
|
|
4352
|
+
await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
|
|
4353
|
+
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"})`);
|
|
4354
|
+
return { element, didScroll, restore: null, unscrollable: true };
|
|
4355
|
+
}
|
|
4356
|
+
}
|
|
4357
|
+
didScroll = true;
|
|
4358
|
+
}
|
|
4359
|
+
try {
|
|
4360
|
+
await element.scrollIntoViewIfNeeded?.();
|
|
4361
|
+
await waitJitter(80, 0.3);
|
|
4362
|
+
const finalStatus = await checkElementVisibility(element);
|
|
4363
|
+
if (finalStatus.code === "VISIBLE") {
|
|
4364
|
+
logger7.info("humanScroll | \u539F\u751F scrollIntoViewIfNeeded \u515C\u5E95\u6210\u529F");
|
|
4365
|
+
logger7.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
|
|
4366
|
+
return { element, didScroll: true, restore: null };
|
|
4367
|
+
}
|
|
4368
|
+
} catch (fallbackError) {
|
|
4369
|
+
logger7.debug(`humanScroll | native fallback failed: ${fallbackError?.message || fallbackError}`);
|
|
4573
4370
|
}
|
|
4371
|
+
logger7.warn(`humanScroll | \u5728 ${maxSteps} \u6B65\u540E\u65E0\u6CD5\u786E\u4FDD\u53EF\u89C1\u6027`);
|
|
4372
|
+
return { element, didScroll, restore: null };
|
|
4574
4373
|
},
|
|
4575
|
-
/**
|
|
4576
|
-
* 人类化点击 - 使用 ghost-cursor 模拟人类鼠标移动轨迹并点击
|
|
4577
|
-
*
|
|
4578
|
-
* @param {import('playwright').Page} page
|
|
4579
|
-
* @param {string|import('playwright').ElementHandle} [target] - CSS 选择器或元素句柄。如果为空,则点击当前鼠标位置
|
|
4580
|
-
* @param {Object} [options]
|
|
4581
|
-
* @param {number} [options.reactionDelay=250] - 反应延迟基础值 (ms),实际 ±30% 抖动
|
|
4582
|
-
* @param {boolean} [options.throwOnMissing=true] - 元素不存在时是否抛出错误
|
|
4583
|
-
* @param {boolean} [options.scrollIfNeeded=true] - 元素不在视口时是否自动滚动
|
|
4584
|
-
*/
|
|
4585
4374
|
async humanClick(page, target, options = {}) {
|
|
4586
|
-
const
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
logger6.warn(`humanClick: \u6062\u590D\u6EDA\u52A8\u4F4D\u7F6E\u5931\u8D25: ${restoreError.message}`);
|
|
4599
|
-
}
|
|
4600
|
-
};
|
|
4375
|
+
const {
|
|
4376
|
+
reactionDelay = 220,
|
|
4377
|
+
throwOnMissing = true,
|
|
4378
|
+
scrollIfNeeded = true,
|
|
4379
|
+
tapTimeoutMs = DEFAULT_TAP_TIMEOUT_MS,
|
|
4380
|
+
mouseFallbackTimeoutMs = DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS,
|
|
4381
|
+
activateFallbackTimeoutMs = DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS,
|
|
4382
|
+
fallbackDomClick = true,
|
|
4383
|
+
fallbackDomClickOnTapError = true
|
|
4384
|
+
} = options;
|
|
4385
|
+
const targetDesc = describeTarget(target);
|
|
4386
|
+
logger7.start("humanClick", `target=${targetDesc}`);
|
|
4601
4387
|
try {
|
|
4602
4388
|
if (target == null) {
|
|
4603
|
-
|
|
4604
|
-
await
|
|
4605
|
-
|
|
4389
|
+
const viewport = resolveViewport(page);
|
|
4390
|
+
await waitJitter(reactionDelay, 0.45);
|
|
4391
|
+
await tapPoint(page, {
|
|
4392
|
+
x: viewport.width * (0.45 + Math.random() * 0.1),
|
|
4393
|
+
y: viewport.height * (0.48 + Math.random() * 0.12)
|
|
4394
|
+
}, {
|
|
4395
|
+
timeoutMs: tapTimeoutMs,
|
|
4396
|
+
mouseFallbackTimeoutMs
|
|
4397
|
+
});
|
|
4398
|
+
logger7.success("humanClick", "Tapped current position");
|
|
4606
4399
|
return true;
|
|
4607
4400
|
}
|
|
4608
|
-
|
|
4609
|
-
if (
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4401
|
+
const element = await resolveElement(page, target, { throwOnMissing });
|
|
4402
|
+
if (!element) {
|
|
4403
|
+
logger7.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${targetDesc}`);
|
|
4404
|
+
return false;
|
|
4405
|
+
}
|
|
4406
|
+
const scrollResult = scrollIfNeeded ? await MobileHumanize.humanScroll(page, element, { throwOnMissing }) : null;
|
|
4407
|
+
const status = await checkElementVisibility(element).catch(() => null);
|
|
4408
|
+
if (status && status.code !== "VISIBLE") {
|
|
4409
|
+
if (fallbackDomClick && (status.code === "OUT_OF_VIEWPORT" || status.code === "OBSTRUCTED") && (status.isFixed || scrollResult?.unscrollable)) {
|
|
4410
|
+
let fallback = await withTimeout(
|
|
4411
|
+
() => activateElementFallback(element, null, {
|
|
4412
|
+
editableOnly: true
|
|
4413
|
+
}),
|
|
4414
|
+
activateFallbackTimeoutMs,
|
|
4415
|
+
"focus fallback"
|
|
4416
|
+
).catch(() => null);
|
|
4417
|
+
if (!fallback?.activated) {
|
|
4418
|
+
fallback = await withTimeout(
|
|
4419
|
+
() => activateElementFallback(element, null, {
|
|
4420
|
+
editableOnly: false
|
|
4421
|
+
}),
|
|
4422
|
+
activateFallbackTimeoutMs,
|
|
4423
|
+
"activation fallback"
|
|
4424
|
+
).catch(() => null);
|
|
4425
|
+
}
|
|
4426
|
+
if (fallback?.activated) {
|
|
4427
|
+
logger7.warn(`humanClick: \u4E0D\u53EF\u6EDA\u52A8\u76EE\u6807\u4E0D\u53EF\u7269\u7406\u70B9\u51FB\uFF0C\u5DF2\u7528 ${fallback.method} \u6FC0\u6D3B`);
|
|
4428
|
+
return true;
|
|
4614
4429
|
}
|
|
4615
|
-
logger6.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${target}`);
|
|
4616
|
-
return false;
|
|
4617
4430
|
}
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
const { restore: restoreFn, didScroll } = await this.humanScroll(page, element);
|
|
4623
|
-
restoreOnce.do = didScroll && restore ? restoreFn : null;
|
|
4431
|
+
const message = `\u5143\u7D20\u4E0D\u53EF\u70B9\u51FB: ${status.reason || status.code}`;
|
|
4432
|
+
if (throwOnMissing) throw new Error(message);
|
|
4433
|
+
logger7.warn(`humanClick: ${message}\uFF0C\u8DF3\u8FC7\u70B9\u51FB`);
|
|
4434
|
+
return false;
|
|
4624
4435
|
}
|
|
4625
4436
|
const box = await element.boundingBox();
|
|
4626
4437
|
if (!box) {
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
throw new Error("\u65E0\u6CD5\u83B7\u53D6\u5143\u7D20\u4F4D\u7F6E");
|
|
4630
|
-
}
|
|
4631
|
-
logger6.warn("humanClick: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E\uFF0C\u8DF3\u8FC7\u70B9\u51FB");
|
|
4438
|
+
if (throwOnMissing) throw new Error("\u65E0\u6CD5\u83B7\u53D6\u5143\u7D20\u4F4D\u7F6E");
|
|
4439
|
+
logger7.warn("humanClick: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E\uFF0C\u8DF3\u8FC7\u70B9\u51FB");
|
|
4632
4440
|
return false;
|
|
4633
4441
|
}
|
|
4634
|
-
|
|
4635
|
-
const
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4442
|
+
await waitJitter(reactionDelay, 0.45);
|
|
4443
|
+
const visibleBox = clipBoxToViewport(box, resolveViewport(page));
|
|
4444
|
+
const tapTarget = centerPointInBox(visibleBox);
|
|
4445
|
+
try {
|
|
4446
|
+
await tapPoint(page, tapTarget, {
|
|
4447
|
+
timeoutMs: tapTimeoutMs,
|
|
4448
|
+
mouseFallbackTimeoutMs
|
|
4449
|
+
});
|
|
4450
|
+
} catch (tapError) {
|
|
4451
|
+
if (!fallbackDomClickOnTapError) throw tapError;
|
|
4452
|
+
const fallback = await withTimeout(
|
|
4453
|
+
() => activateElementFallback(element, tapTarget, {
|
|
4454
|
+
editableOnly: false
|
|
4455
|
+
}),
|
|
4456
|
+
activateFallbackTimeoutMs,
|
|
4457
|
+
"activation fallback"
|
|
4458
|
+
).catch(() => null);
|
|
4459
|
+
if (!fallback?.activated) throw tapError;
|
|
4460
|
+
logger7.warn(`humanClick: tap \u5931\u8D25\u540E\u5DF2\u7528 ${fallback.method} \u515C\u5E95: ${tapError?.message || tapError}`);
|
|
4461
|
+
}
|
|
4462
|
+
await waitJitter(120, 0.35);
|
|
4463
|
+
logger7.success("humanClick");
|
|
4641
4464
|
return true;
|
|
4642
4465
|
} catch (error) {
|
|
4643
|
-
|
|
4644
|
-
logger6.fail("humanClick", error);
|
|
4466
|
+
logger7.fail("humanClick", error);
|
|
4645
4467
|
throw error;
|
|
4646
4468
|
}
|
|
4647
4469
|
},
|
|
4648
|
-
/**
|
|
4649
|
-
* 随机延迟一段毫秒数(带 ±30% 抖动)
|
|
4650
|
-
* @param {number} baseMs - 基础延迟毫秒数
|
|
4651
|
-
* @param {number} [jitterPercent=0.3] - 抖动百分比
|
|
4652
|
-
*/
|
|
4653
4470
|
async randomSleep(baseMs, jitterPercent = 0.3) {
|
|
4654
|
-
|
|
4655
|
-
logger6.start("randomSleep", `base=${baseMs}, actual=${ms}ms`);
|
|
4656
|
-
await (0, import_delay4.default)(ms);
|
|
4657
|
-
logger6.success("randomSleep");
|
|
4471
|
+
await waitJitter(baseMs, jitterPercent);
|
|
4658
4472
|
},
|
|
4659
|
-
/**
|
|
4660
|
-
* 模拟人类"注视"或"阅读"行为:鼠标在页面上随机微动
|
|
4661
|
-
* @param {import('playwright').Page} page
|
|
4662
|
-
* @param {number} [baseDurationMs=2500] - 基础持续时间 (±40% 抖动)
|
|
4663
|
-
*/
|
|
4664
4473
|
async simulateGaze(page, baseDurationMs = 2500) {
|
|
4665
|
-
const
|
|
4666
|
-
const durationMs = this.jitterMs(baseDurationMs, 0.4);
|
|
4667
|
-
logger6.start("simulateGaze", `duration=${durationMs}ms`);
|
|
4474
|
+
const durationMs = jitterMs(baseDurationMs, 0.4);
|
|
4668
4475
|
const startTime = Date.now();
|
|
4669
|
-
const viewportSize = page.viewportSize() || { width: 1920, height: 1080 };
|
|
4670
4476
|
while (Date.now() - startTime < durationMs) {
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4477
|
+
if (Math.random() < 0.28) {
|
|
4478
|
+
const distance = 70 + Math.random() * 120;
|
|
4479
|
+
await dispatchTouchSwipe(page, Math.random() < 0.7 ? distance : -distance, {
|
|
4480
|
+
durationMs: 180,
|
|
4481
|
+
steps: 4
|
|
4482
|
+
});
|
|
4483
|
+
} else {
|
|
4484
|
+
await waitJitter(420, 0.55);
|
|
4485
|
+
}
|
|
4675
4486
|
}
|
|
4676
|
-
logger6.success("simulateGaze");
|
|
4677
4487
|
},
|
|
4678
|
-
/**
|
|
4679
|
-
* 人类化输入 - 带节奏变化(快-慢-停顿-偶尔加速)
|
|
4680
|
-
* @param {import('playwright').Page} page
|
|
4681
|
-
* @param {string} selector - 输入框选择器
|
|
4682
|
-
* @param {string} text - 要输入的文本
|
|
4683
|
-
* @param {Object} [options]
|
|
4684
|
-
* @param {number} [options.baseDelay=180] - 基础按键延迟 (ms),实际 ±40% 抖动
|
|
4685
|
-
* @param {number} [options.pauseProbability=0.08] - 停顿概率 (0-1)
|
|
4686
|
-
* @param {number} [options.pauseBase=800] - 停顿时长基础值 (ms),实际 ±50% 抖动
|
|
4687
|
-
*/
|
|
4688
4488
|
async humanType(page, selector, text, options = {}) {
|
|
4689
|
-
logger6.start("humanType", `selector=${selector}, textLen=${text.length}`);
|
|
4690
4489
|
const {
|
|
4691
|
-
baseDelay =
|
|
4490
|
+
baseDelay = 160,
|
|
4692
4491
|
pauseProbability = 0.08,
|
|
4693
|
-
pauseBase =
|
|
4492
|
+
pauseBase = 700
|
|
4694
4493
|
} = options;
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
} else if (/[,.!?;:,。!?;:]/.test(char)) {
|
|
4705
|
-
charDelay = this.jitterMs(baseDelay * 1.5, 0.4);
|
|
4706
|
-
} else {
|
|
4707
|
-
charDelay = this.jitterMs(baseDelay, 0.4);
|
|
4708
|
-
}
|
|
4709
|
-
await page.keyboard.type(char);
|
|
4710
|
-
await (0, import_delay4.default)(charDelay);
|
|
4711
|
-
if (Math.random() < pauseProbability && i < text.length - 1) {
|
|
4712
|
-
const pauseTime = this.jitterMs(pauseBase, 0.5);
|
|
4713
|
-
logger6.debug(`\u505C\u987F ${pauseTime}ms...`);
|
|
4714
|
-
await (0, import_delay4.default)(pauseTime);
|
|
4715
|
-
}
|
|
4494
|
+
await MobileHumanize.humanClick(page, selector, { scrollIfNeeded: true });
|
|
4495
|
+
await waitJitter(220, 0.45);
|
|
4496
|
+
for (let i = 0; i < String(text).length; i += 1) {
|
|
4497
|
+
const char = String(text)[i];
|
|
4498
|
+
const charDelay = /[,.!?;:,。!?;:]/.test(char) ? jitterMs(baseDelay * 1.45, 0.4) : jitterMs(char === " " ? baseDelay * 0.65 : baseDelay, 0.4);
|
|
4499
|
+
await page.keyboard.type(char);
|
|
4500
|
+
await waitJitter(charDelay, 0.15);
|
|
4501
|
+
if (Math.random() < pauseProbability && i < String(text).length - 1) {
|
|
4502
|
+
await waitJitter(pauseBase, 0.5);
|
|
4716
4503
|
}
|
|
4717
|
-
logger6.success("humanType");
|
|
4718
|
-
} catch (error) {
|
|
4719
|
-
logger6.fail("humanType", error);
|
|
4720
|
-
throw error;
|
|
4721
4504
|
}
|
|
4722
4505
|
},
|
|
4723
|
-
/**
|
|
4724
|
-
* 人类化按键 - 模拟用户在当前焦点或指定目标上按下一次键。
|
|
4725
|
-
* @param {import('playwright').Page} page
|
|
4726
|
-
* @param {string|import('playwright').ElementHandle|import('playwright').Locator} targetOrKey - 目标或按键
|
|
4727
|
-
* @param {string|Object} [maybeKey] - 按键或选项
|
|
4728
|
-
* @param {Object} [options]
|
|
4729
|
-
*/
|
|
4730
4506
|
async humanPress(page, targetOrKey, maybeKey, options = {}) {
|
|
4731
4507
|
const hasTarget = typeof maybeKey === "string";
|
|
4732
4508
|
const key = hasTarget ? maybeKey : targetOrKey;
|
|
4733
4509
|
const pressOptions = hasTarget ? options : maybeKey || options;
|
|
4734
4510
|
const {
|
|
4735
|
-
reactionDelay =
|
|
4736
|
-
holdDelay =
|
|
4511
|
+
reactionDelay = 170,
|
|
4512
|
+
holdDelay = 42,
|
|
4737
4513
|
focusDelay = 180,
|
|
4738
4514
|
scrollIfNeeded = true,
|
|
4739
4515
|
throwOnMissing = true,
|
|
4740
4516
|
keyboardOptions = {}
|
|
4741
4517
|
} = pressOptions || {};
|
|
4742
|
-
const targetDesc = hasTarget ?
|
|
4743
|
-
|
|
4518
|
+
const targetDesc = hasTarget ? describeTarget(targetOrKey) : "current focus";
|
|
4519
|
+
logger7.start("humanPress", `key=${key}, target=${targetDesc}`);
|
|
4744
4520
|
try {
|
|
4745
4521
|
if (hasTarget) {
|
|
4746
|
-
await
|
|
4522
|
+
await MobileHumanize.humanClick(page, targetOrKey, {
|
|
4523
|
+
reactionDelay: focusDelay,
|
|
4524
|
+
scrollIfNeeded,
|
|
4525
|
+
throwOnMissing
|
|
4526
|
+
});
|
|
4747
4527
|
}
|
|
4748
|
-
await (
|
|
4528
|
+
await waitJitter(reactionDelay, 0.45);
|
|
4749
4529
|
await page.keyboard.press(key, {
|
|
4750
4530
|
...keyboardOptions,
|
|
4751
|
-
delay:
|
|
4531
|
+
delay: jitterMs(holdDelay, 0.5)
|
|
4752
4532
|
});
|
|
4753
|
-
|
|
4533
|
+
logger7.success("humanPress");
|
|
4754
4534
|
return true;
|
|
4755
4535
|
} catch (error) {
|
|
4756
|
-
|
|
4536
|
+
logger7.fail("humanPress", error);
|
|
4757
4537
|
throw error;
|
|
4758
4538
|
}
|
|
4759
4539
|
},
|
|
4760
|
-
/**
|
|
4761
|
-
* 人类化清空输入框 - 模拟人类删除文本的行为
|
|
4762
|
-
* @param {import('playwright').Page} page
|
|
4763
|
-
* @param {string} selector - 输入框选择器
|
|
4764
|
-
*/
|
|
4765
4540
|
async humanClear(page, selector) {
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4541
|
+
const locator = page.locator(selector);
|
|
4542
|
+
await MobileHumanize.humanClick(page, locator, { scrollIfNeeded: true });
|
|
4543
|
+
await waitJitter(160, 0.4);
|
|
4544
|
+
const readValue = async () => {
|
|
4545
|
+
try {
|
|
4546
|
+
return await locator.inputValue({ timeout: 600 });
|
|
4547
|
+
} catch {
|
|
4548
|
+
return await locator.evaluate((el) => "value" in el ? String(el.value || "") : String(el.textContent || "")).catch(() => "");
|
|
4549
|
+
}
|
|
4550
|
+
};
|
|
4551
|
+
const currentValue = await readValue();
|
|
4552
|
+
if (!currentValue) return;
|
|
4553
|
+
await page.keyboard.press("ControlOrMeta+A");
|
|
4554
|
+
await waitJitter(90, 0.35);
|
|
4555
|
+
await page.keyboard.press("Backspace");
|
|
4556
|
+
await waitJitter(120, 0.35);
|
|
4557
|
+
if (!await readValue()) return;
|
|
4558
|
+
await locator.evaluate((el) => {
|
|
4559
|
+
if ("value" in el) {
|
|
4560
|
+
el.value = "";
|
|
4561
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
4562
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
4774
4563
|
return;
|
|
4775
4564
|
}
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
logger6.success("humanClear");
|
|
4780
|
-
} catch (error) {
|
|
4781
|
-
logger6.fail("humanClear", error);
|
|
4782
|
-
throw error;
|
|
4783
|
-
}
|
|
4565
|
+
el.textContent = "";
|
|
4566
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
4567
|
+
});
|
|
4784
4568
|
},
|
|
4785
|
-
/**
|
|
4786
|
-
* 页面预热浏览 - 模拟人类进入页面后的探索行为
|
|
4787
|
-
* @param {import('playwright').Page} page
|
|
4788
|
-
* @param {number} [baseDuration=3500] - 预热时长基础值 (±40% 抖动)
|
|
4789
|
-
*/
|
|
4790
4569
|
async warmUpBrowsing(page, baseDuration = 3500) {
|
|
4791
|
-
const
|
|
4792
|
-
const durationMs = this.jitterMs(baseDuration, 0.4);
|
|
4793
|
-
logger6.start("warmUpBrowsing", `duration=${durationMs}ms`);
|
|
4570
|
+
const durationMs = jitterMs(baseDuration, 0.4);
|
|
4794
4571
|
const startTime = Date.now();
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
await (0, import_delay4.default)(this.jitterMs(800, 0.5));
|
|
4810
|
-
}
|
|
4572
|
+
while (Date.now() - startTime < durationMs) {
|
|
4573
|
+
const action = Math.random();
|
|
4574
|
+
if (action < 0.5) {
|
|
4575
|
+
await dispatchTouchSwipe(page, 120 + Math.random() * 220, {
|
|
4576
|
+
durationMs: 240,
|
|
4577
|
+
steps: 5
|
|
4578
|
+
});
|
|
4579
|
+
} else if (action < 0.7) {
|
|
4580
|
+
await dispatchTouchSwipe(page, -(80 + Math.random() * 160), {
|
|
4581
|
+
durationMs: 220,
|
|
4582
|
+
steps: 4
|
|
4583
|
+
});
|
|
4584
|
+
} else {
|
|
4585
|
+
await waitJitter(560, 0.55);
|
|
4811
4586
|
}
|
|
4812
|
-
logger6.success("warmUpBrowsing");
|
|
4813
|
-
} catch (error) {
|
|
4814
|
-
logger6.fail("warmUpBrowsing", error);
|
|
4815
|
-
throw error;
|
|
4816
4587
|
}
|
|
4817
4588
|
},
|
|
4818
|
-
/**
|
|
4819
|
-
* 自然滚动 - 带惯性、减速效果和随机抖动
|
|
4820
|
-
* @param {import('playwright').Page} page
|
|
4821
|
-
* @param {'up' | 'down'} [direction='down'] - 滚动方向
|
|
4822
|
-
* @param {number} [distance=300] - 总滚动距离基础值 (px),±15% 抖动
|
|
4823
|
-
* @param {number} [baseSteps=5] - 分几步完成基础值,±1 随机
|
|
4824
|
-
*/
|
|
4825
4589
|
async naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
|
|
4826
4590
|
const steps = Math.max(3, baseSteps + Math.floor(Math.random() * 3) - 1);
|
|
4827
|
-
const actualDistance =
|
|
4828
|
-
logger6.start("naturalScroll", `dir=${direction}, dist=${actualDistance}, steps=${steps}`);
|
|
4591
|
+
const actualDistance = jitterMs(distance, 0.15);
|
|
4829
4592
|
const sign = direction === "down" ? 1 : -1;
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
await page.mouse.wheel(0, scrollAmount);
|
|
4837
|
-
const baseDelay = 60 + i * 25;
|
|
4838
|
-
await (0, import_delay4.default)(this.jitterMs(baseDelay, 0.3));
|
|
4839
|
-
}
|
|
4840
|
-
logger6.success("naturalScroll");
|
|
4841
|
-
} catch (error) {
|
|
4842
|
-
logger6.fail("naturalScroll", error);
|
|
4843
|
-
throw error;
|
|
4593
|
+
for (let i = 0; i < steps; i += 1) {
|
|
4594
|
+
const factor = 1 - i / steps * 0.45;
|
|
4595
|
+
await dispatchTouchSwipe(page, actualDistance / steps * factor * sign, {
|
|
4596
|
+
durationMs: 150 + i * 20,
|
|
4597
|
+
steps: 4
|
|
4598
|
+
});
|
|
4844
4599
|
}
|
|
4845
4600
|
}
|
|
4846
4601
|
};
|
|
4847
4602
|
|
|
4848
|
-
// src/
|
|
4849
|
-
var
|
|
4850
|
-
var
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
var
|
|
4858
|
-
initializeCursor: { enumerable: true },
|
|
4859
|
-
humanMove: { enumerable: true },
|
|
4860
|
-
humanScroll: { enumerable: true },
|
|
4861
|
-
humanClick: { enumerable: true },
|
|
4862
|
-
simulateGaze: { enumerable: true },
|
|
4863
|
-
humanType: { enumerable: true },
|
|
4864
|
-
humanPress: { enumerable: true },
|
|
4865
|
-
humanClear: { enumerable: true },
|
|
4866
|
-
warmUpBrowsing: { enumerable: true },
|
|
4867
|
-
naturalScroll: { enumerable: true }
|
|
4868
|
-
});
|
|
4869
|
-
var isPageLike2 = (value) => value && typeof value === "object" && typeof value.evaluate === "function";
|
|
4870
|
-
var createSharedHumanizeMethods = ({ desktopDelegate, resolveDelegate }) => ({
|
|
4603
|
+
// src/humanize.js
|
|
4604
|
+
var resolveDeviceFromPage2 = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
|
|
4605
|
+
var resolveDelegate = (page) => {
|
|
4606
|
+
return resolveDeviceFromPage2(page) === Device.Mobile ? MobileHumanize : Humanize;
|
|
4607
|
+
};
|
|
4608
|
+
var callDelegate = (method, page, args) => {
|
|
4609
|
+
const delegate = resolveDelegate(page);
|
|
4610
|
+
return delegate[method](page, ...args);
|
|
4611
|
+
};
|
|
4612
|
+
var Humanize2 = {
|
|
4871
4613
|
jitterMs(base, jitterPercent = 0.3) {
|
|
4872
|
-
return
|
|
4614
|
+
return Humanize.jitterMs(base, jitterPercent);
|
|
4615
|
+
},
|
|
4616
|
+
initializeCursor(page) {
|
|
4617
|
+
return callDelegate("initializeCursor", page, []);
|
|
4618
|
+
},
|
|
4619
|
+
humanMove(page, target) {
|
|
4620
|
+
return callDelegate("humanMove", page, [target]);
|
|
4621
|
+
},
|
|
4622
|
+
humanScroll(page, target, options = {}) {
|
|
4623
|
+
return callDelegate("humanScroll", page, [target, options]);
|
|
4624
|
+
},
|
|
4625
|
+
humanClick(page, target, options = {}) {
|
|
4626
|
+
return callDelegate("humanClick", page, [target, options]);
|
|
4873
4627
|
},
|
|
4874
4628
|
randomSleep(pageOrBaseMs, maybeBaseMs, maybeJitterPercent) {
|
|
4875
|
-
if (
|
|
4876
|
-
|
|
4629
|
+
if (pageOrBaseMs && typeof pageOrBaseMs === "object" && typeof pageOrBaseMs.evaluate === "function") {
|
|
4630
|
+
const delegate = resolveDelegate(pageOrBaseMs);
|
|
4631
|
+
return delegate.randomSleep(maybeBaseMs, maybeJitterPercent);
|
|
4632
|
+
}
|
|
4633
|
+
return Humanize.randomSleep(pageOrBaseMs, maybeBaseMs);
|
|
4634
|
+
},
|
|
4635
|
+
simulateGaze(page, baseDurationMs = 2500) {
|
|
4636
|
+
return callDelegate("simulateGaze", page, [baseDurationMs]);
|
|
4637
|
+
},
|
|
4638
|
+
humanType(page, selector, text, options = {}) {
|
|
4639
|
+
return callDelegate("humanType", page, [selector, text, options]);
|
|
4640
|
+
},
|
|
4641
|
+
humanPress(page, targetOrKey, maybeKey, options = {}) {
|
|
4642
|
+
if (typeof maybeKey === "string") {
|
|
4643
|
+
return callDelegate("humanPress", page, [targetOrKey, maybeKey, options]);
|
|
4877
4644
|
}
|
|
4878
|
-
return
|
|
4645
|
+
return callDelegate("humanPress", page, [targetOrKey, maybeKey || options]);
|
|
4646
|
+
},
|
|
4647
|
+
humanClear(page, selector) {
|
|
4648
|
+
return callDelegate("humanClear", page, [selector]);
|
|
4649
|
+
},
|
|
4650
|
+
warmUpBrowsing(page, baseDuration = 3500) {
|
|
4651
|
+
return callDelegate("warmUpBrowsing", page, [baseDuration]);
|
|
4652
|
+
},
|
|
4653
|
+
naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
|
|
4654
|
+
return callDelegate("naturalScroll", page, [direction, distance, baseSteps]);
|
|
4879
4655
|
}
|
|
4880
|
-
});
|
|
4881
|
-
var withHumanizeDelegates = (target, resolveDelegate) => withDelegatedProperties(target, {
|
|
4882
|
-
namespace: "Humanize",
|
|
4883
|
-
methods: HUMANIZE_DELEGATED_METHODS,
|
|
4884
|
-
resolveDelegate: (_method, args) => ({
|
|
4885
|
-
delegate: resolveDelegate(args[0]),
|
|
4886
|
-
label: "resolved humanize delegate"
|
|
4887
|
-
})
|
|
4888
|
-
});
|
|
4889
|
-
var createHumanizeExport = (context = {}) => {
|
|
4890
|
-
return Object.assign(
|
|
4891
|
-
createSharedHumanizeMethods(context),
|
|
4892
|
-
withHumanizeDelegates({}, context.resolveDelegate)
|
|
4893
|
-
);
|
|
4894
4656
|
};
|
|
4895
|
-
var DefaultHumanize = createHumanizeExport(DefaultHumanizeContext);
|
|
4896
|
-
var CloakBrowserHumanize = createHumanizeExport(CloakBrowserHumanizeContext);
|
|
4897
|
-
|
|
4898
|
-
// src/humanize.js
|
|
4899
|
-
var humanizeStrategies = {
|
|
4900
|
-
[Mode.Default]: DefaultHumanize,
|
|
4901
|
-
[Mode.CloakBrowser]: CloakBrowserHumanize
|
|
4902
|
-
};
|
|
4903
|
-
var humanizeFacadeMethods = Object.freeze({
|
|
4904
|
-
jitterMs: { enumerable: true },
|
|
4905
|
-
initializeCursor: { enumerable: true },
|
|
4906
|
-
humanMove: { enumerable: true },
|
|
4907
|
-
humanScroll: { enumerable: true },
|
|
4908
|
-
humanClick: { enumerable: true },
|
|
4909
|
-
randomSleep: { enumerable: true },
|
|
4910
|
-
simulateGaze: { enumerable: true },
|
|
4911
|
-
humanType: { enumerable: true },
|
|
4912
|
-
humanPress: { enumerable: true },
|
|
4913
|
-
humanClear: { enumerable: true },
|
|
4914
|
-
warmUpBrowsing: { enumerable: true },
|
|
4915
|
-
naturalScroll: { enumerable: true }
|
|
4916
|
-
});
|
|
4917
|
-
var Humanize2 = createDelegatedFacade("Humanize", humanizeStrategies, humanizeFacadeMethods);
|
|
4918
4657
|
|
|
4919
|
-
// src/
|
|
4658
|
+
// src/launch.js
|
|
4920
4659
|
var import_node_child_process = require("node:child_process");
|
|
4921
4660
|
var import_fingerprint_generator = require("fingerprint-generator");
|
|
4922
4661
|
var import_fingerprint_injector = require("fingerprint-injector");
|
|
@@ -5001,8 +4740,8 @@ var ByPass = {
|
|
|
5001
4740
|
resolveRouteByProxy
|
|
5002
4741
|
};
|
|
5003
4742
|
|
|
5004
|
-
// src/
|
|
5005
|
-
var
|
|
4743
|
+
// src/launch.js
|
|
4744
|
+
var logger8 = createInternalLogger("Launch");
|
|
5006
4745
|
var REQUEST_HOOK_FLAG = Symbol("playwright-toolkit-request-hook");
|
|
5007
4746
|
var injectedContexts = /* @__PURE__ */ new WeakSet();
|
|
5008
4747
|
var browserMajorVersionCache = /* @__PURE__ */ new Map();
|
|
@@ -5054,7 +4793,7 @@ var detectBrowserMajorVersion = (launcher) => {
|
|
|
5054
4793
|
});
|
|
5055
4794
|
detectedVersion = parseChromeMajorVersion(rawVersion);
|
|
5056
4795
|
} catch (error) {
|
|
5057
|
-
|
|
4796
|
+
logger8.warn(`\u8BFB\u53D6\u6D4F\u89C8\u5668\u7248\u672C\u5931\u8D25: ${error?.message || error}`);
|
|
5058
4797
|
}
|
|
5059
4798
|
browserMajorVersionCache.set(executablePath, detectedVersion);
|
|
5060
4799
|
return detectedVersion;
|
|
@@ -5068,7 +4807,7 @@ var resolveCoreDevice = (core = {}) => {
|
|
|
5068
4807
|
};
|
|
5069
4808
|
var buildFingerprintGenerator = ({ locale, browserMajorVersion, device }) => {
|
|
5070
4809
|
return new import_fingerprint_generator.FingerprintGenerator(
|
|
5071
|
-
|
|
4810
|
+
AntiCheat.getFingerprintGeneratorOptions({
|
|
5072
4811
|
locale,
|
|
5073
4812
|
browserMajorVersion,
|
|
5074
4813
|
device
|
|
@@ -5091,7 +4830,7 @@ var generateFingerprintForCore = ({ locale, browserMajorVersion, device }) => {
|
|
|
5091
4830
|
if (requestedBrowserMajorVersion <= 0) {
|
|
5092
4831
|
throw error;
|
|
5093
4832
|
}
|
|
5094
|
-
|
|
4833
|
+
logger8.warn(
|
|
5095
4834
|
`\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}`
|
|
5096
4835
|
);
|
|
5097
4836
|
}
|
|
@@ -5112,7 +4851,7 @@ var buildReplayableBrowserProfile = (runtimeState, launcher) => {
|
|
|
5112
4851
|
}
|
|
5113
4852
|
let nextState = RuntimeEnv.rememberState(runtimeState);
|
|
5114
4853
|
let browserProfileCore = RuntimeEnv.getBrowserProfileCore(nextState);
|
|
5115
|
-
const timezoneId = String(browserProfileCore?.timezone_id || "").trim() ||
|
|
4854
|
+
const timezoneId = String(browserProfileCore?.timezone_id || "").trim() || AntiCheat.getBaseConfig().timezoneId;
|
|
5116
4855
|
const locale = DEFAULT_LOCALE;
|
|
5117
4856
|
const currentBrowserMajorVersion = detectBrowserMajorVersion(launcher);
|
|
5118
4857
|
const storedBrowserMajorVersion = Number(browserProfileCore?.browser_major_version || 0);
|
|
@@ -5136,7 +4875,7 @@ var buildReplayableBrowserProfile = (runtimeState, launcher) => {
|
|
|
5136
4875
|
schema_version: DEFAULT_BROWSER_PROFILE_SCHEMA_VERSION
|
|
5137
4876
|
};
|
|
5138
4877
|
nextState = RuntimeEnv.setBrowserProfileCore(nextState, browserProfileCore);
|
|
5139
|
-
|
|
4878
|
+
logger8.info(
|
|
5140
4879
|
`\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}`
|
|
5141
4880
|
);
|
|
5142
4881
|
return { runtimeState: nextState, browserProfileCore };
|
|
@@ -5191,7 +4930,7 @@ var applyFingerprintPageOptions = (pageOptions = {}, { fingerprintWithHeaders =
|
|
|
5191
4930
|
pageOptions.timezoneId = timezoneId;
|
|
5192
4931
|
}
|
|
5193
4932
|
};
|
|
5194
|
-
var buildReplayBrowserPoolOptions = (browserProfileCore) => {
|
|
4933
|
+
var buildReplayBrowserPoolOptions = (browserProfileCore, modifyPageOptions = null) => {
|
|
5195
4934
|
const fingerprintWithHeaders = browserProfileCore?.fingerprint;
|
|
5196
4935
|
const fingerprint = fingerprintWithHeaders?.fingerprint;
|
|
5197
4936
|
if (!fingerprintWithHeaders || !fingerprint) {
|
|
@@ -5200,13 +4939,16 @@ var buildReplayBrowserPoolOptions = (browserProfileCore) => {
|
|
|
5200
4939
|
return {
|
|
5201
4940
|
useFingerprints: false,
|
|
5202
4941
|
prePageCreateHooks: [
|
|
5203
|
-
(
|
|
4942
|
+
async (pageId, browserController, pageOptions = {}) => {
|
|
5204
4943
|
if (!pageOptions || typeof pageOptions !== "object") return;
|
|
5205
4944
|
applyFingerprintPageOptions(pageOptions, {
|
|
5206
4945
|
fingerprintWithHeaders,
|
|
5207
4946
|
locale: browserProfileCore.locale,
|
|
5208
4947
|
timezoneId: browserProfileCore.timezone_id
|
|
5209
4948
|
});
|
|
4949
|
+
if (modifyPageOptions) {
|
|
4950
|
+
await modifyPageOptions(pageOptions, { pageId, browserController });
|
|
4951
|
+
}
|
|
5210
4952
|
}
|
|
5211
4953
|
],
|
|
5212
4954
|
postPageCreateHooks: [
|
|
@@ -5221,7 +4963,7 @@ var buildReplayBrowserPoolOptions = (browserProfileCore) => {
|
|
|
5221
4963
|
]
|
|
5222
4964
|
};
|
|
5223
4965
|
};
|
|
5224
|
-
var
|
|
4966
|
+
var Launch = {
|
|
5225
4967
|
getPlaywrightCrawlerOptions(options = {}) {
|
|
5226
4968
|
const normalizedOptions = Array.isArray(options) ? { customArgs: options } : options || {};
|
|
5227
4969
|
const {
|
|
@@ -5232,11 +4974,13 @@ var DefaultLaunch = {
|
|
|
5232
4974
|
debugMode = false,
|
|
5233
4975
|
isRunningOnApify = false,
|
|
5234
4976
|
launcher = null,
|
|
4977
|
+
hooks = {},
|
|
5235
4978
|
preNavigationHooks = [],
|
|
5236
4979
|
postNavigationHooks = [],
|
|
5237
4980
|
runtimeState = null
|
|
5238
4981
|
} = normalizedOptions;
|
|
5239
4982
|
const device = resolveRuntimeDevice(runtimeState);
|
|
4983
|
+
const modifyPageOptions = typeof hooks?.modifyPageOptions === "function" ? hooks.modifyPageOptions : null;
|
|
5240
4984
|
const { byPassDomains, enableProxy, proxyUrl } = resolveProxyLaunchOptions(proxyConfiguration);
|
|
5241
4985
|
const byPassRules = ByPass.buildByPassDomainRules(byPassDomains);
|
|
5242
4986
|
const proxyMeter = enableProxy && proxyUrl ? ProxyMeterRuntime.startProxyMeter({ proxyUrl, debugMode }) : null;
|
|
@@ -5245,11 +4989,11 @@ var DefaultLaunch = {
|
|
|
5245
4989
|
launchProxy.bypass = byPassDomains.join(",");
|
|
5246
4990
|
}
|
|
5247
4991
|
const replayContext = buildReplayableBrowserProfile(runtimeState, launcher);
|
|
5248
|
-
const replayBrowserPoolOptions = buildReplayBrowserPoolOptions(replayContext.browserProfileCore);
|
|
4992
|
+
const replayBrowserPoolOptions = buildReplayBrowserPoolOptions(replayContext.browserProfileCore, modifyPageOptions);
|
|
5249
4993
|
const launchLocale = String(replayContext.browserProfileCore?.locale || DEFAULT_LOCALE).trim() || DEFAULT_LOCALE;
|
|
5250
4994
|
const launchOptions = {
|
|
5251
4995
|
args: [
|
|
5252
|
-
...
|
|
4996
|
+
...AntiCheat.getLaunchArgs({ locale: launchLocale }),
|
|
5253
4997
|
...customArgs
|
|
5254
4998
|
],
|
|
5255
4999
|
ignoreDefaultArgs: ["--enable-automation"]
|
|
@@ -5265,18 +5009,18 @@ var DefaultLaunch = {
|
|
|
5265
5009
|
upstreamLabel = `${parsedProxyUrl.protocol}//${parsedProxyUrl.host}`;
|
|
5266
5010
|
} catch {
|
|
5267
5011
|
}
|
|
5268
|
-
|
|
5012
|
+
logger8.info(
|
|
5269
5013
|
`[\u4EE3\u7406\u5DF2\u542F\u7528] \u672C\u5730=${launchProxy.server} \u4E0A\u6E38=${upstreamLabel || "-"} \u76F4\u8FDE\u57DF\u540D=${(byPassDomains || []).join(",")}`
|
|
5270
5014
|
);
|
|
5271
|
-
|
|
5015
|
+
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`);
|
|
5272
5016
|
} else if (enableByPassLogger && enableProxy && !launchProxy) {
|
|
5273
|
-
|
|
5274
|
-
|
|
5017
|
+
logger8.info("[\u4EE3\u7406\u672A\u542F\u7528] enable_proxy=true \u4F46 proxy_url \u4E3A\u7A7A");
|
|
5018
|
+
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`);
|
|
5275
5019
|
} else if (enableByPassLogger && !enableProxy && proxyUrl) {
|
|
5276
|
-
|
|
5277
|
-
|
|
5020
|
+
logger8.info("[\u4EE3\u7406\u672A\u542F\u7528] enable_proxy=false \u4E14 proxy_url \u5DF2\u914D\u7F6E");
|
|
5021
|
+
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`);
|
|
5278
5022
|
} else if (enableByPassLogger) {
|
|
5279
|
-
|
|
5023
|
+
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`);
|
|
5280
5024
|
}
|
|
5281
5025
|
const onPageCreated = (page) => {
|
|
5282
5026
|
const recommendedGotoOptions = {
|
|
@@ -5298,7 +5042,7 @@ var DefaultLaunch = {
|
|
|
5298
5042
|
}
|
|
5299
5043
|
if (!enableByPassLogger || byPassDomains.length === 0) return;
|
|
5300
5044
|
if (!matched || !matched.rule) return;
|
|
5301
|
-
|
|
5045
|
+
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}`);
|
|
5302
5046
|
};
|
|
5303
5047
|
page.on("request", requestHandler);
|
|
5304
5048
|
return recommendedGotoOptions;
|
|
@@ -5317,20 +5061,23 @@ var DefaultLaunch = {
|
|
|
5317
5061
|
browserPoolOptions: replayBrowserPoolOptions || {
|
|
5318
5062
|
useFingerprints: true,
|
|
5319
5063
|
fingerprintOptions: {
|
|
5320
|
-
fingerprintGeneratorOptions:
|
|
5064
|
+
fingerprintGeneratorOptions: AntiCheat.getFingerprintGeneratorOptions({
|
|
5321
5065
|
locale: launchLocale,
|
|
5322
5066
|
device
|
|
5323
5067
|
})
|
|
5324
5068
|
},
|
|
5325
5069
|
prePageCreateHooks: [
|
|
5326
|
-
(
|
|
5070
|
+
async (pageId, browserController, pageOptions = {}) => {
|
|
5327
5071
|
const fingerprintWithHeaders = browserController?.launchContext?.fingerprint;
|
|
5328
|
-
const timezoneId =
|
|
5072
|
+
const timezoneId = AntiCheat.getBaseConfig().timezoneId;
|
|
5329
5073
|
applyFingerprintPageOptions(pageOptions, {
|
|
5330
5074
|
fingerprintWithHeaders,
|
|
5331
5075
|
locale: launchLocale,
|
|
5332
5076
|
timezoneId
|
|
5333
5077
|
});
|
|
5078
|
+
if (modifyPageOptions) {
|
|
5079
|
+
await modifyPageOptions(pageOptions, { pageId, browserController });
|
|
5080
|
+
}
|
|
5334
5081
|
}
|
|
5335
5082
|
]
|
|
5336
5083
|
},
|
|
@@ -5352,263 +5099,6 @@ var DefaultLaunch = {
|
|
|
5352
5099
|
}
|
|
5353
5100
|
};
|
|
5354
5101
|
|
|
5355
|
-
// src/internals/launch/cloakbrowser.js
|
|
5356
|
-
var import_node_child_process2 = require("node:child_process");
|
|
5357
|
-
var import_node_util = require("node:util");
|
|
5358
|
-
var logger8 = createInternalLogger("CloakBrowser");
|
|
5359
|
-
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process2.execFile);
|
|
5360
|
-
var DEFAULT_CLOAK_CRAWLER_BASE_OPTIONS = Object.freeze({
|
|
5361
|
-
maxConcurrency: 1,
|
|
5362
|
-
maxRequestRetries: 0,
|
|
5363
|
-
requestHandlerTimeoutSecs: 240,
|
|
5364
|
-
navigationTimeoutSecs: 120
|
|
5365
|
-
});
|
|
5366
|
-
var DEFAULT_CLOAK_HUMANIZE_OPTIONS = Object.freeze({
|
|
5367
|
-
humanize: true
|
|
5368
|
-
});
|
|
5369
|
-
var DEFAULT_CLOAK_GOTO_OPTIONS = Object.freeze({
|
|
5370
|
-
waitUntil: "commit"
|
|
5371
|
-
});
|
|
5372
|
-
var cachedCloakBrowserModulePromise = null;
|
|
5373
|
-
var hasOwn = (target, key) => Object.prototype.hasOwnProperty.call(target, key);
|
|
5374
|
-
var loadCloakBrowserModule = async () => {
|
|
5375
|
-
if (!cachedCloakBrowserModulePromise) {
|
|
5376
|
-
cachedCloakBrowserModulePromise = import("cloakbrowser").catch((error) => {
|
|
5377
|
-
cachedCloakBrowserModulePromise = null;
|
|
5378
|
-
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", {
|
|
5379
|
-
cause: error
|
|
5380
|
-
});
|
|
5381
|
-
});
|
|
5382
|
-
}
|
|
5383
|
-
return cachedCloakBrowserModulePromise;
|
|
5384
|
-
};
|
|
5385
|
-
var buildCloakLaunchOptions = async (options = {}) => {
|
|
5386
|
-
const { buildLaunchOptions } = await loadCloakBrowserModule();
|
|
5387
|
-
return await buildLaunchOptions(normalizeObject(options));
|
|
5388
|
-
};
|
|
5389
|
-
var normalizeObject = (value) => {
|
|
5390
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
5391
|
-
return {};
|
|
5392
|
-
}
|
|
5393
|
-
return value;
|
|
5394
|
-
};
|
|
5395
|
-
var normalizeStringArray = (value) => {
|
|
5396
|
-
if (!Array.isArray(value)) {
|
|
5397
|
-
return [];
|
|
5398
|
-
}
|
|
5399
|
-
return value.map((item) => String(item || "").trim()).filter(Boolean);
|
|
5400
|
-
};
|
|
5401
|
-
var resolveCloakBrowserProxy = (proxyConfiguration = {}) => {
|
|
5402
|
-
const config = normalizeObject(proxyConfiguration);
|
|
5403
|
-
const proxyUrl = String(config.proxy_url || "").trim();
|
|
5404
|
-
const enableProxy = typeof config.enable_proxy === "boolean" ? config.enable_proxy : proxyUrl !== "";
|
|
5405
|
-
if (!enableProxy || !proxyUrl) {
|
|
5406
|
-
return null;
|
|
5407
|
-
}
|
|
5408
|
-
const byPassDomains = ByPass.normalizeByPassDomains(config.by_pass_domains);
|
|
5409
|
-
if (byPassDomains.length === 0) {
|
|
5410
|
-
return proxyUrl;
|
|
5411
|
-
}
|
|
5412
|
-
const parsedProxyUrl = new URL(proxyUrl.includes("://") ? proxyUrl : `http://${proxyUrl}`);
|
|
5413
|
-
return {
|
|
5414
|
-
server: `${parsedProxyUrl.protocol}//${parsedProxyUrl.host}`,
|
|
5415
|
-
username: decodeURIComponent(parsedProxyUrl.username || ""),
|
|
5416
|
-
password: decodeURIComponent(parsedProxyUrl.password || ""),
|
|
5417
|
-
bypass: byPassDomains.join(",")
|
|
5418
|
-
};
|
|
5419
|
-
};
|
|
5420
|
-
var extractFingerprintArg = (launchOptions = {}) => {
|
|
5421
|
-
const args = Array.isArray(launchOptions?.args) ? launchOptions.args : [];
|
|
5422
|
-
return args.find((value) => String(value || "").startsWith("--fingerprint=")) || "";
|
|
5423
|
-
};
|
|
5424
|
-
var createStableGotoHook = (recommendedGotoOptions = DEFAULT_CLOAK_GOTO_OPTIONS) => {
|
|
5425
|
-
const normalizedRecommendedGotoOptions = normalizeObject(recommendedGotoOptions);
|
|
5426
|
-
const fallbackGotoOptions = Object.keys(normalizedRecommendedGotoOptions).length > 0 ? normalizedRecommendedGotoOptions : DEFAULT_CLOAK_GOTO_OPTIONS;
|
|
5427
|
-
return async (_crawlingContext, gotoOptions = {}) => {
|
|
5428
|
-
for (const [key, value] of Object.entries(fallbackGotoOptions)) {
|
|
5429
|
-
if (gotoOptions[key] == null) {
|
|
5430
|
-
gotoOptions[key] = value;
|
|
5431
|
-
}
|
|
5432
|
-
}
|
|
5433
|
-
};
|
|
5434
|
-
};
|
|
5435
|
-
var attachCloakBrowserHumanizeHook = ({
|
|
5436
|
-
browserPoolOptions = {},
|
|
5437
|
-
activeBrowsers,
|
|
5438
|
-
patchedBrowsers,
|
|
5439
|
-
humanizeOptions = DEFAULT_CLOAK_HUMANIZE_OPTIONS
|
|
5440
|
-
} = {}) => {
|
|
5441
|
-
const normalizedBrowserPoolOptions = normalizeObject(browserPoolOptions);
|
|
5442
|
-
const shouldHumanize = humanizeOptions !== false;
|
|
5443
|
-
const normalizedHumanizeOptions = shouldHumanize ? {
|
|
5444
|
-
...DEFAULT_CLOAK_HUMANIZE_OPTIONS,
|
|
5445
|
-
...normalizeObject(humanizeOptions)
|
|
5446
|
-
} : null;
|
|
5447
|
-
return {
|
|
5448
|
-
...normalizedBrowserPoolOptions,
|
|
5449
|
-
useFingerprints: false,
|
|
5450
|
-
postLaunchHooks: [
|
|
5451
|
-
...Array.isArray(normalizedBrowserPoolOptions.postLaunchHooks) ? normalizedBrowserPoolOptions.postLaunchHooks : [],
|
|
5452
|
-
async (_pageId, browserController) => {
|
|
5453
|
-
const browser = browserController?.browser;
|
|
5454
|
-
if (!browser || typeof browser.contexts !== "function") {
|
|
5455
|
-
return;
|
|
5456
|
-
}
|
|
5457
|
-
activeBrowsers.add(browser);
|
|
5458
|
-
if (typeof browser.once === "function") {
|
|
5459
|
-
browser.once("disconnected", () => {
|
|
5460
|
-
activeBrowsers.delete(browser);
|
|
5461
|
-
});
|
|
5462
|
-
}
|
|
5463
|
-
if (!shouldHumanize || patchedBrowsers.has(browser)) {
|
|
5464
|
-
return;
|
|
5465
|
-
}
|
|
5466
|
-
const { humanizeBrowser } = await loadCloakBrowserModule();
|
|
5467
|
-
await humanizeBrowser(browser, normalizedHumanizeOptions);
|
|
5468
|
-
patchedBrowsers.add(browser);
|
|
5469
|
-
}
|
|
5470
|
-
]
|
|
5471
|
-
};
|
|
5472
|
-
};
|
|
5473
|
-
var closeTrackedBrowsers = async (activeBrowsers) => {
|
|
5474
|
-
const browsers = Array.from(activeBrowsers || []);
|
|
5475
|
-
activeBrowsers?.clear?.();
|
|
5476
|
-
await Promise.allSettled(
|
|
5477
|
-
browsers.map(async (browser) => {
|
|
5478
|
-
if (!browser || typeof browser.isConnected !== "function" || !browser.isConnected()) {
|
|
5479
|
-
return;
|
|
5480
|
-
}
|
|
5481
|
-
await browser.close().catch(() => {
|
|
5482
|
-
});
|
|
5483
|
-
})
|
|
5484
|
-
);
|
|
5485
|
-
};
|
|
5486
|
-
var forceTerminateBrowsersByFingerprintArg = async (fingerprintArg) => {
|
|
5487
|
-
if (!fingerprintArg) {
|
|
5488
|
-
return;
|
|
5489
|
-
}
|
|
5490
|
-
await execFileAsync("pkill", ["-f", "--", fingerprintArg]).catch((error) => {
|
|
5491
|
-
if (error?.code === 1 || error?.code === "ENOENT") {
|
|
5492
|
-
return;
|
|
5493
|
-
}
|
|
5494
|
-
logger8.info(`\u5F3A\u5236\u5173\u95ED CloakBrowser \u8FDB\u7A0B\u5931\u8D25\uFF08\u5FFD\u7565\uFF09: ${error?.message || String(error)}`);
|
|
5495
|
-
});
|
|
5496
|
-
};
|
|
5497
|
-
var CloakBrowserLaunch = {
|
|
5498
|
-
resolveProxyConfiguration(proxyConfiguration = {}) {
|
|
5499
|
-
return resolveCloakBrowserProxy(proxyConfiguration);
|
|
5500
|
-
},
|
|
5501
|
-
extractFingerprintArg(launchOptions = {}) {
|
|
5502
|
-
return extractFingerprintArg(launchOptions);
|
|
5503
|
-
},
|
|
5504
|
-
createStableGotoHook(recommendedGotoOptions = DEFAULT_CLOAK_GOTO_OPTIONS) {
|
|
5505
|
-
return createStableGotoHook(recommendedGotoOptions);
|
|
5506
|
-
},
|
|
5507
|
-
async getPlaywrightCrawlerOptions(options = {}) {
|
|
5508
|
-
const runtime2 = await CloakBrowserLaunch.createPlaywrightCrawlerRuntime(options);
|
|
5509
|
-
return Object.defineProperties(runtime2.crawlerOptions, {
|
|
5510
|
-
cleanup: {
|
|
5511
|
-
enumerable: false,
|
|
5512
|
-
value: runtime2.cleanup
|
|
5513
|
-
},
|
|
5514
|
-
closeActiveBrowsers: {
|
|
5515
|
-
enumerable: false,
|
|
5516
|
-
value: runtime2.closeActiveBrowsers
|
|
5517
|
-
},
|
|
5518
|
-
forceTerminateActiveProcesses: {
|
|
5519
|
-
enumerable: false,
|
|
5520
|
-
value: runtime2.forceTerminateActiveProcesses
|
|
5521
|
-
}
|
|
5522
|
-
});
|
|
5523
|
-
},
|
|
5524
|
-
async buildLaunchOptions(options = {}) {
|
|
5525
|
-
return await buildCloakLaunchOptions(options);
|
|
5526
|
-
},
|
|
5527
|
-
async createPlaywrightCrawlerRuntime(options = {}) {
|
|
5528
|
-
const normalizedOptions = normalizeObject(options);
|
|
5529
|
-
const {
|
|
5530
|
-
proxyConfiguration = {},
|
|
5531
|
-
runInHeadfulMode = false,
|
|
5532
|
-
isRunningOnApify = false,
|
|
5533
|
-
launcher = null,
|
|
5534
|
-
cloakOptions = {},
|
|
5535
|
-
humanizeOptions = DEFAULT_CLOAK_HUMANIZE_OPTIONS,
|
|
5536
|
-
crawlerBaseOptions = {},
|
|
5537
|
-
browserPoolOptions = {},
|
|
5538
|
-
launchContext = {},
|
|
5539
|
-
preNavigationHooks = [],
|
|
5540
|
-
postNavigationHooks = [],
|
|
5541
|
-
recommendedGotoOptions = DEFAULT_CLOAK_GOTO_OPTIONS
|
|
5542
|
-
} = normalizedOptions;
|
|
5543
|
-
const normalizedCloakOptions = normalizeObject(cloakOptions);
|
|
5544
|
-
const activeBrowsers = /* @__PURE__ */ new Set();
|
|
5545
|
-
const patchedBrowsers = /* @__PURE__ */ new WeakSet();
|
|
5546
|
-
const defaultArgs = isRunningOnApify ? ["--no-sandbox", "--disable-setuid-sandbox"] : [];
|
|
5547
|
-
const extraArgs = normalizeStringArray(normalizedCloakOptions.args);
|
|
5548
|
-
const proxy = hasOwn(normalizedCloakOptions, "proxy") ? normalizedCloakOptions.proxy : resolveCloakBrowserProxy(proxyConfiguration);
|
|
5549
|
-
const headless = hasOwn(normalizedCloakOptions, "headless") ? normalizedCloakOptions.headless : !runInHeadfulMode || isRunningOnApify;
|
|
5550
|
-
const mergedCloakOptions = {
|
|
5551
|
-
...normalizedCloakOptions,
|
|
5552
|
-
headless,
|
|
5553
|
-
proxy,
|
|
5554
|
-
args: [...defaultArgs, ...extraArgs]
|
|
5555
|
-
};
|
|
5556
|
-
const launchOptions = await buildCloakLaunchOptions(mergedCloakOptions);
|
|
5557
|
-
const fingerprintArg = extractFingerprintArg(launchOptions);
|
|
5558
|
-
const internalPreNavigationHook = createStableGotoHook(recommendedGotoOptions);
|
|
5559
|
-
const normalizedPreNavigationHooks = Array.isArray(preNavigationHooks) ? preNavigationHooks : [];
|
|
5560
|
-
const normalizedPostNavigationHooks = Array.isArray(postNavigationHooks) ? postNavigationHooks : [];
|
|
5561
|
-
const crawlerOptions = {
|
|
5562
|
-
...DEFAULT_CLOAK_CRAWLER_BASE_OPTIONS,
|
|
5563
|
-
...normalizeObject(crawlerBaseOptions),
|
|
5564
|
-
headless,
|
|
5565
|
-
launchContext: {
|
|
5566
|
-
useIncognitoPages: true,
|
|
5567
|
-
...normalizeObject(launchContext),
|
|
5568
|
-
...launcher ? { launcher } : {},
|
|
5569
|
-
launchOptions
|
|
5570
|
-
},
|
|
5571
|
-
browserPoolOptions: attachCloakBrowserHumanizeHook({
|
|
5572
|
-
browserPoolOptions,
|
|
5573
|
-
activeBrowsers,
|
|
5574
|
-
patchedBrowsers,
|
|
5575
|
-
humanizeOptions
|
|
5576
|
-
}),
|
|
5577
|
-
preNavigationHooks: [internalPreNavigationHook, ...normalizedPreNavigationHooks],
|
|
5578
|
-
...normalizedPostNavigationHooks.length > 0 ? { postNavigationHooks: normalizedPostNavigationHooks } : {}
|
|
5579
|
-
};
|
|
5580
|
-
const closeActiveBrowsers = async () => {
|
|
5581
|
-
await closeTrackedBrowsers(activeBrowsers);
|
|
5582
|
-
};
|
|
5583
|
-
const forceTerminateActiveProcesses = async () => {
|
|
5584
|
-
await forceTerminateBrowsersByFingerprintArg(fingerprintArg);
|
|
5585
|
-
};
|
|
5586
|
-
const cleanup = async () => {
|
|
5587
|
-
await closeActiveBrowsers();
|
|
5588
|
-
await forceTerminateActiveProcesses();
|
|
5589
|
-
};
|
|
5590
|
-
return {
|
|
5591
|
-
headless,
|
|
5592
|
-
launchOptions,
|
|
5593
|
-
fingerprintArg,
|
|
5594
|
-
crawlerOptions,
|
|
5595
|
-
closeActiveBrowsers,
|
|
5596
|
-
forceTerminateActiveProcesses,
|
|
5597
|
-
cleanup
|
|
5598
|
-
};
|
|
5599
|
-
}
|
|
5600
|
-
};
|
|
5601
|
-
|
|
5602
|
-
// src/launch.js
|
|
5603
|
-
var launchStrategies = {
|
|
5604
|
-
[Mode.Default]: DefaultLaunch,
|
|
5605
|
-
[Mode.CloakBrowser]: CloakBrowserLaunch
|
|
5606
|
-
};
|
|
5607
|
-
var launchFacadeMethods = Object.freeze({
|
|
5608
|
-
getPlaywrightCrawlerOptions: { enumerable: true }
|
|
5609
|
-
});
|
|
5610
|
-
var Launch = createDelegatedFacade("Launch", launchStrategies, launchFacadeMethods);
|
|
5611
|
-
|
|
5612
5102
|
// src/live-view.js
|
|
5613
5103
|
var import_express = __toESM(require("express"), 1);
|
|
5614
5104
|
var import_apify = require("apify");
|
|
@@ -6696,7 +6186,7 @@ var Mutation = {
|
|
|
6696
6186
|
const overallTimeout = options.timeout ?? 180 * 1e3;
|
|
6697
6187
|
const onMutation = options.onMutation;
|
|
6698
6188
|
const pollInterval = 500;
|
|
6699
|
-
const
|
|
6189
|
+
const sleep = (ms) => new Promise((resolve) => {
|
|
6700
6190
|
setTimeout(resolve, ms);
|
|
6701
6191
|
});
|
|
6702
6192
|
const truncate = (value, max = 800) => {
|
|
@@ -6857,8 +6347,8 @@ var Mutation = {
|
|
|
6857
6347
|
throw e;
|
|
6858
6348
|
}
|
|
6859
6349
|
}
|
|
6860
|
-
let
|
|
6861
|
-
if (!
|
|
6350
|
+
let state = await buildState();
|
|
6351
|
+
if (!state?.hasMatched) {
|
|
6862
6352
|
logger12.warning("waitForStableAcrossRoots \u672A\u627E\u5230\u53EF\u76D1\u63A7\u7684\u5143\u7D20");
|
|
6863
6353
|
return { mutationCount: 0, stableTime: 0, wasPaused: false };
|
|
6864
6354
|
}
|
|
@@ -6866,7 +6356,7 @@ var Mutation = {
|
|
|
6866
6356
|
let stableSince = 0;
|
|
6867
6357
|
let isPaused = false;
|
|
6868
6358
|
let wasPaused = false;
|
|
6869
|
-
let lastSnapshotKey =
|
|
6359
|
+
let lastSnapshotKey = state.snapshotKey;
|
|
6870
6360
|
const applyPauseSignal = (signal) => {
|
|
6871
6361
|
const nextPaused = signal === "__PAUSE__";
|
|
6872
6362
|
if (nextPaused) {
|
|
@@ -6880,15 +6370,15 @@ var Mutation = {
|
|
|
6880
6370
|
};
|
|
6881
6371
|
const initialSignal = await invokeMutationCallback({
|
|
6882
6372
|
mutationCount: 0,
|
|
6883
|
-
html:
|
|
6884
|
-
text:
|
|
6885
|
-
mutationNodes:
|
|
6373
|
+
html: state.html || "",
|
|
6374
|
+
text: state.text || "",
|
|
6375
|
+
mutationNodes: state.mutationNodes || []
|
|
6886
6376
|
});
|
|
6887
6377
|
applyPauseSignal(initialSignal);
|
|
6888
6378
|
const deadline = Date.now() + overallTimeout;
|
|
6889
|
-
let lastState =
|
|
6379
|
+
let lastState = state;
|
|
6890
6380
|
while (Date.now() < deadline) {
|
|
6891
|
-
await
|
|
6381
|
+
await sleep(pollInterval);
|
|
6892
6382
|
lastState = await buildState();
|
|
6893
6383
|
if (!lastState?.hasMatched) {
|
|
6894
6384
|
continue;
|
|
@@ -7976,7 +7466,7 @@ var Logger = {
|
|
|
7976
7466
|
};
|
|
7977
7467
|
|
|
7978
7468
|
// src/share.js
|
|
7979
|
-
var
|
|
7469
|
+
var import_delay4 = __toESM(require("delay"), 1);
|
|
7980
7470
|
|
|
7981
7471
|
// src/internals/watermarkify.js
|
|
7982
7472
|
var DEFAULT_TIMEZONE_OFFSET = 8;
|
|
@@ -8472,10 +7962,7 @@ var buildWatermarkifyRenderHtml = ({ imageSrc, overlaySvg, width, height, imageH
|
|
|
8472
7962
|
</html>
|
|
8473
7963
|
`;
|
|
8474
7964
|
};
|
|
8475
|
-
var
|
|
8476
|
-
return String(value || "default").trim().toLowerCase() === "cloakbrowser" ? "cloakbrowser" : "default";
|
|
8477
|
-
};
|
|
8478
|
-
var composeScreenshotBufferWithBrowser = async (page, buffer, overlaySvg, imageInfo = {}, options = {}) => {
|
|
7965
|
+
var composeScreenshotBufferWithBrowser = async (page, buffer, overlaySvg, imageInfo = {}) => {
|
|
8479
7966
|
if (!page || typeof page.context !== "function") {
|
|
8480
7967
|
logger13.warning("watermarkify \u6D4F\u89C8\u5668\u5408\u6210\u8DF3\u8FC7: \u7F3A\u5C11\u53EF\u7528 page");
|
|
8481
7968
|
return buffer;
|
|
@@ -8499,35 +7986,15 @@ var composeScreenshotBufferWithBrowser = async (page, buffer, overlaySvg, imageI
|
|
|
8499
7986
|
height: viewportHeight
|
|
8500
7987
|
}).catch(() => {
|
|
8501
7988
|
});
|
|
8502
|
-
|
|
8503
|
-
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
|
|
8508
|
-
|
|
8509
|
-
|
|
8510
|
-
|
|
8511
|
-
await renderPage.goto("about:blank", {
|
|
8512
|
-
waitUntil: "commit"
|
|
8513
|
-
}).catch(() => {
|
|
8514
|
-
});
|
|
8515
|
-
await renderPage.evaluate((html) => {
|
|
8516
|
-
document.open();
|
|
8517
|
-
document.write(html);
|
|
8518
|
-
document.close();
|
|
8519
|
-
}, renderHtml);
|
|
8520
|
-
} else {
|
|
8521
|
-
await renderPage.setContent(buildWatermarkifyRenderHtml({
|
|
8522
|
-
imageSrc: `data:${imageInfo.mimeType || "image/png"};base64,${buffer.toString("base64")}`,
|
|
8523
|
-
overlaySvg,
|
|
8524
|
-
width: safeWidth,
|
|
8525
|
-
height: safeHeight,
|
|
8526
|
-
imageHeight: safeImageHeight
|
|
8527
|
-
}), {
|
|
8528
|
-
waitUntil: "load"
|
|
8529
|
-
});
|
|
8530
|
-
}
|
|
7989
|
+
await renderPage.setContent(buildWatermarkifyRenderHtml({
|
|
7990
|
+
imageSrc: `data:${imageInfo.mimeType || "image/png"};base64,${buffer.toString("base64")}`,
|
|
7991
|
+
overlaySvg,
|
|
7992
|
+
width: safeWidth,
|
|
7993
|
+
height: safeHeight,
|
|
7994
|
+
imageHeight: safeImageHeight
|
|
7995
|
+
}), {
|
|
7996
|
+
waitUntil: "load"
|
|
7997
|
+
});
|
|
8531
7998
|
await renderPage.waitForFunction(() => {
|
|
8532
7999
|
const image = document.getElementById("pk-base-image");
|
|
8533
8000
|
return image instanceof HTMLImageElement && image.complete && image.naturalWidth > 0 && image.naturalHeight > 0;
|
|
@@ -9462,7 +8929,7 @@ var buildWatermarkifySvg = (meta, imageWidth, imageHeight) => {
|
|
|
9462
8929
|
</svg>
|
|
9463
8930
|
`;
|
|
9464
8931
|
};
|
|
9465
|
-
var watermarkifyScreenshotBuffer = async (buffer, meta, page = null
|
|
8932
|
+
var watermarkifyScreenshotBuffer = async (buffer, meta, page = null) => {
|
|
9466
8933
|
const hasWatermark = meta?.watermark?.enabled !== false && normalizeText(meta?.watermarkText);
|
|
9467
8934
|
const hasStrip = meta?.strip?.enabled !== false && Array.isArray(meta?.stripSegments) && meta.stripSegments.length > 0;
|
|
9468
8935
|
if (!Buffer.isBuffer(buffer) || !meta || !hasWatermark && !hasStrip) {
|
|
@@ -9483,7 +8950,7 @@ var watermarkifyScreenshotBuffer = async (buffer, meta, page = null, options = {
|
|
|
9483
8950
|
if (!overlaySvg) {
|
|
9484
8951
|
return buffer;
|
|
9485
8952
|
}
|
|
9486
|
-
return await composeScreenshotBufferWithBrowser(page, buffer, overlaySvg, outputImageInfo
|
|
8953
|
+
return await composeScreenshotBufferWithBrowser(page, buffer, overlaySvg, outputImageInfo);
|
|
9487
8954
|
};
|
|
9488
8955
|
|
|
9489
8956
|
// src/internals/compression.js
|
|
@@ -10047,7 +9514,7 @@ var Share = {
|
|
|
10047
9514
|
);
|
|
10048
9515
|
nextProgressLogTs = now + 5e3;
|
|
10049
9516
|
}
|
|
10050
|
-
await (0,
|
|
9517
|
+
await (0, import_delay4.default)(Math.max(0, Math.min(DEFAULT_POLL_INTERVAL_MS, remaining)));
|
|
10051
9518
|
}
|
|
10052
9519
|
if (!timeoutDisabled && share.mode === "response" && stats.responseMatched === 0) {
|
|
10053
9520
|
logger15.warning(
|
|
@@ -10081,7 +9548,6 @@ var Share = {
|
|
|
10081
9548
|
* @param {number} [options.maxBytes] 默认 5MiB,返回 base64 超过后会压缩
|
|
10082
9549
|
* @param {'jpeg'|'jpg'} [options.type] 压缩输出格式,默认 jpeg
|
|
10083
9550
|
* @param {boolean|Object} [options.compression] 传 false 可关闭压缩
|
|
10084
|
-
* @param {'default'|'cloakbrowser'} [options.mode] 截图水印合成模式,默认 default
|
|
10085
9551
|
* @returns {Promise<string>} base64 image
|
|
10086
9552
|
*/
|
|
10087
9553
|
async captureScreen(page, options = {}) {
|
|
@@ -10102,9 +9568,7 @@ var Share = {
|
|
|
10102
9568
|
...screenshotWatermarkify,
|
|
10103
9569
|
capturedAt
|
|
10104
9570
|
});
|
|
10105
|
-
outputBuffer = await watermarkifyScreenshotBuffer(rawBuffer, watermarkifyMeta, page
|
|
10106
|
-
mode: options.mode === "cloakbrowser" ? "cloakbrowser" : "default"
|
|
10107
|
-
});
|
|
9571
|
+
outputBuffer = await watermarkifyScreenshotBuffer(rawBuffer, watermarkifyMeta, page);
|
|
10108
9572
|
}
|
|
10109
9573
|
return await compressImageBufferToBase64(outputBuffer, compression);
|
|
10110
9574
|
}
|
|
@@ -10112,9 +9576,8 @@ var Share = {
|
|
|
10112
9576
|
|
|
10113
9577
|
// entrys/node.js
|
|
10114
9578
|
Logger.setLogger(import_crawlee.log);
|
|
10115
|
-
var usePlaywrightToolKit = (
|
|
10116
|
-
|
|
10117
|
-
const toolkit = {
|
|
9579
|
+
var usePlaywrightToolKit = () => {
|
|
9580
|
+
return {
|
|
10118
9581
|
ApifyKit,
|
|
10119
9582
|
AntiCheat,
|
|
10120
9583
|
DeviceInput,
|
|
@@ -10134,7 +9597,6 @@ var usePlaywrightToolKit = (mode = "default") => {
|
|
|
10134
9597
|
ByPass,
|
|
10135
9598
|
$Internals: { LOG_TEMPLATES, stripAnsi }
|
|
10136
9599
|
};
|
|
10137
|
-
return toolkit;
|
|
10138
9600
|
};
|
|
10139
9601
|
// Annotate the CommonJS export names for ESM import in node:
|
|
10140
9602
|
0 && (module.exports = {
|