specific-ai-cli 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.en.md +177 -0
- package/README.ja.md +177 -0
- package/README.ko.md +177 -0
- package/README.md +174 -2
- package/README.zh-TW.md +177 -0
- package/assets/banner.txt +3 -0
- package/dist/cli/argv.js +98 -0
- package/dist/cli/commands/auth.js +11 -0
- package/dist/cli/commands/config.js +23 -0
- package/dist/cli/commands/doctor.js +134 -0
- package/dist/cli/commands/platforms.js +24 -0
- package/dist/cli/commands/version.js +4 -0
- package/dist/cli/confirm.js +22 -0
- package/dist/cli/dispatch.js +440 -0
- package/dist/cli/help.js +223 -0
- package/dist/cli/hints.js +60 -0
- package/dist/cli/main.js +18 -0
- package/dist/cli/output.js +66 -0
- package/dist/core/auth-handlers.js +41 -0
- package/dist/core/auth-store.js +119 -0
- package/dist/core/capabilities.js +163 -0
- package/dist/core/config.js +121 -0
- package/dist/core/cookies.js +164 -0
- package/dist/core/errors.js +37 -0
- package/dist/core/files.js +89 -0
- package/dist/core/fsutil.js +52 -0
- package/dist/core/hash.js +4 -0
- package/dist/core/http.js +331 -0
- package/dist/core/image.js +280 -0
- package/dist/core/log.js +40 -0
- package/dist/core/login.js +191 -0
- package/dist/core/mp4.js +130 -0
- package/dist/core/normalize.js +207 -0
- package/dist/core/options.js +110 -0
- package/dist/core/paths.js +38 -0
- package/dist/core/pb.js +59 -0
- package/dist/core/py.js +181 -0
- package/dist/core/rand.js +131 -0
- package/dist/core/registry.js +129 -0
- package/dist/core/schemas.js +41 -0
- package/dist/core/stream.js +104 -0
- package/dist/core/toolkit.js +35 -0
- package/dist/core/vm.js +73 -0
- package/dist/core/vocab.js +157 -0
- package/dist/platforms/_shared/impaas.js +410 -0
- package/dist/platforms/bilibili/index.js +216 -0
- package/dist/platforms/bilibili/web/api.js +806 -0
- package/dist/platforms/bilibili/web/client.js +311 -0
- package/dist/platforms/bilibili/web/commands.js +783 -0
- package/dist/platforms/bilibili/web/ddddocr.js +342 -0
- package/dist/platforms/bilibili/web/gaia.js +130 -0
- package/dist/platforms/bilibili/web/geetest-manual.js +154 -0
- package/dist/platforms/bilibili/web/geetest-vision.js +232 -0
- package/dist/platforms/bilibili/web/geetest.js +450 -0
- package/dist/platforms/bilibili/web/normalize.js +234 -0
- package/dist/platforms/bilibili/web/profile.js +135 -0
- package/dist/platforms/bilibili/web/resolve.js +112 -0
- package/dist/platforms/bilibili/web/sign.js +223 -0
- package/dist/platforms/bilibili/web/upos.js +102 -0
- package/dist/platforms/douyin/index.js +142 -0
- package/dist/platforms/douyin/web/api.js +863 -0
- package/dist/platforms/douyin/web/client.js +413 -0
- package/dist/platforms/douyin/web/commands.js +712 -0
- package/dist/platforms/douyin/web/creator.js +601 -0
- package/dist/platforms/douyin/web/crypto.js +247 -0
- package/dist/platforms/douyin/web/dtrait.js +190 -0
- package/dist/platforms/douyin/web/im.js +502 -0
- package/dist/platforms/douyin/web/image.js +5 -0
- package/dist/platforms/douyin/web/live.js +225 -0
- package/dist/platforms/douyin/web/mssdk.js +148 -0
- package/dist/platforms/douyin/web/normalize.js +326 -0
- package/dist/platforms/douyin/web/passport.js +1247 -0
- package/dist/platforms/douyin/web/profile.js +221 -0
- package/dist/platforms/douyin/web/proto.js +26 -0
- package/dist/platforms/douyin/web/resolve.js +88 -0
- package/dist/platforms/douyin/web/sign.js +436 -0
- package/dist/platforms/douyin/web/tos.js +78 -0
- package/dist/platforms/index.js +17 -0
- package/dist/platforms/jd/index.js +67 -0
- package/dist/platforms/jd/web/aks.js +115 -0
- package/dist/platforms/jd/web/api.js +549 -0
- package/dist/platforms/jd/web/chat.js +132 -0
- package/dist/platforms/jd/web/client.js +470 -0
- package/dist/platforms/jd/web/commands.js +557 -0
- package/dist/platforms/jd/web/jcap/helper.js +90 -0
- package/dist/platforms/jd/web/jcap/host.js +107 -0
- package/dist/platforms/jd/web/jcap/lsd.js +300 -0
- package/dist/platforms/jd/web/jcap/solver.js +995 -0
- package/dist/platforms/jd/web/jcap.js +150 -0
- package/dist/platforms/jd/web/js.js +420 -0
- package/dist/platforms/jd/web/login.js +775 -0
- package/dist/platforms/jd/web/normalize.js +297 -0
- package/dist/platforms/jd/web/profile.js +256 -0
- package/dist/platforms/jd/web/util.js +175 -0
- package/dist/platforms/jd/web/webm.js +133 -0
- package/dist/platforms/kuaishou/index.js +98 -0
- package/dist/platforms/kuaishou/web/api.js +694 -0
- package/dist/platforms/kuaishou/web/captcha-crypto.js +134 -0
- package/dist/platforms/kuaishou/web/captcha-fp.js +180 -0
- package/dist/platforms/kuaishou/web/captcha.js +273 -0
- package/dist/platforms/kuaishou/web/client.js +487 -0
- package/dist/platforms/kuaishou/web/commands.js +863 -0
- package/dist/platforms/kuaishou/web/gap.js +144 -0
- package/dist/platforms/kuaishou/web/gdfp.js +376 -0
- package/dist/platforms/kuaishou/web/gql.js +11 -0
- package/dist/platforms/kuaishou/web/live.js +125 -0
- package/dist/platforms/kuaishou/web/normalize.js +241 -0
- package/dist/platforms/kuaishou/web/oracle.js +329 -0
- package/dist/platforms/kuaishou/web/profile.js +259 -0
- package/dist/platforms/kuaishou/web/resolve.js +84 -0
- package/dist/platforms/kuaishou/web/session.js +592 -0
- package/dist/platforms/kuaishou/web/sign.js +568 -0
- package/dist/platforms/taobao/index.js +56 -0
- package/dist/platforms/taobao/web/api.js +96 -0
- package/dist/platforms/taobao/web/client.js +102 -0
- package/dist/platforms/taobao/web/commands.js +172 -0
- package/dist/platforms/taobao/web/im.js +68 -0
- package/dist/platforms/taobao/web/normalize.js +103 -0
- package/dist/platforms/taobao/web/profile.js +85 -0
- package/dist/platforms/taobao/web/resolve.js +51 -0
- package/dist/platforms/taobao/web/sign.js +35 -0
- package/dist/platforms/tiktok/index.js +120 -0
- package/dist/platforms/tiktok/web/api.js +1234 -0
- package/dist/platforms/tiktok/web/client.js +362 -0
- package/dist/platforms/tiktok/web/commands.js +821 -0
- package/dist/platforms/tiktok/web/im.js +133 -0
- package/dist/platforms/tiktok/web/jsrun.js +126 -0
- package/dist/platforms/tiktok/web/normalize.js +286 -0
- package/dist/platforms/tiktok/web/profile.js +169 -0
- package/dist/platforms/tiktok/web/resolve.js +156 -0
- package/dist/platforms/tiktok/web/sign.js +585 -0
- package/dist/platforms/tiktok/web/upload.js +805 -0
- package/dist/platforms/tiktok/web/wire.js +490 -0
- package/dist/platforms/weibo/index.js +91 -0
- package/dist/platforms/weibo/web/api.js +206 -0
- package/dist/platforms/weibo/web/client.js +155 -0
- package/dist/platforms/weibo/web/commands.js +202 -0
- package/dist/platforms/weibo/web/normalize.js +137 -0
- package/dist/platforms/weibo/web/profile.js +246 -0
- package/dist/platforms/weibo/web/resolve.js +60 -0
- package/dist/platforms/weibo/web/sign.js +85 -0
- package/dist/platforms/x/index.js +107 -0
- package/dist/platforms/x/web/api.js +443 -0
- package/dist/platforms/x/web/article.js +212 -0
- package/dist/platforms/x/web/client.js +202 -0
- package/dist/platforms/x/web/commands.js +363 -0
- package/dist/platforms/x/web/graphql.js +108 -0
- package/dist/platforms/x/web/normalize.js +235 -0
- package/dist/platforms/x/web/profile.js +99 -0
- package/dist/platforms/x/web/resolve.js +44 -0
- package/dist/platforms/x/web/transaction.js +171 -0
- package/dist/platforms/xhs/index.js +101 -0
- package/dist/platforms/xhs/web/api.js +278 -0
- package/dist/platforms/xhs/web/client.js +540 -0
- package/dist/platforms/xhs/web/commands.js +1046 -0
- package/dist/platforms/xhs/web/creator-api.js +609 -0
- package/dist/platforms/xhs/web/creator.js +203 -0
- package/dist/platforms/xhs/web/js.js +194 -0
- package/dist/platforms/xhs/web/login.js +230 -0
- package/dist/platforms/xhs/web/normalize.js +166 -0
- package/dist/platforms/xhs/web/profile.js +127 -0
- package/dist/platforms/xhs/web/push.js +361 -0
- package/dist/platforms/xhs/web/resolve.js +69 -0
- package/dist/platforms/xhs/web/state.js +368 -0
- package/dist/platforms/xianyu/index.js +59 -0
- package/dist/platforms/xianyu/web/api.js +312 -0
- package/dist/platforms/xianyu/web/client.js +134 -0
- package/dist/platforms/xianyu/web/commands.js +305 -0
- package/dist/platforms/xianyu/web/im.js +73 -0
- package/dist/platforms/xianyu/web/normalize.js +123 -0
- package/dist/platforms/xianyu/web/profile.js +146 -0
- package/dist/platforms/xianyu/web/resolve.js +48 -0
- package/dist/platforms/xianyu/web/sign.js +79 -0
- package/package.json +77 -4
- package/static/douyin/Live.proto +194 -0
- package/static/douyin/Request.proto +89 -0
- package/static/douyin/Response.proto +46 -0
- package/static/douyin/acrawler_runtime/ac_vm.js +2 -0
- package/static/douyin/acrawler_runtime/browser_window_shape.json +1 -0
- package/static/douyin/acrawler_runtime/canvas_actual_exact.json +1 -0
- package/static/douyin/acrawler_runtime/run_ac_node.js +1005 -0
- package/static/douyin/acrawler_runtime/runner_window_keys_baseline.txt +240 -0
- package/static/douyin/challenge_profile.json +801 -0
- package/static/douyin/challenge_template_runner.js +217 -0
- package/static/douyin/dtrait_profile.json +66 -0
- package/static/douyin/mstoken_common_profile.json +1 -0
- package/static/douyin/mstoken_profile.json +214 -0
- package/static/jd/h5st5_env.js +750 -0
- package/static/jd/h5st5_lib.js +5 -0
- package/static/jd/h5st5_server.js +49 -0
- package/static/jd/jcap/env/env_core.js +512 -0
- package/static/jd/jcap/env/run.js +1307 -0
- package/static/jd/jcap/run/jcap_ujb96b.js +39 -0
- package/static/jd/package.json +4 -0
- package/static/jd/pc_tk_lib.js +1 -0
- package/static/jd/pc_tk_server.js +63 -0
- package/static/jd/summer_cryptico_runner.js +50 -0
- package/static/jd/webm/env/run.js +453 -0
- package/static/jd/webm/run/jdwebm-riskhandle.js +49 -0
- package/static/kuaishou/bundles/weapon/kwf-0.0.2.2cee19b4b7dec496.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-0-0.0.1-obfuscated.4b7f3de1ab0ddcc2.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-1-0.0.1-obfuscated.346ebdb1c78df316.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-10-0.0.1-obfuscated.a33db41897b983af.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-11-0.0.1-obfuscated.5e0a90af726d8a7e.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-12-0.0.1-obfuscated.20ea29345706ec8c.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-13-0.0.1-obfuscated.6b74e9640ff18648.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-14-0.0.1-obfuscated.551e4429d156bf66.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-15-0.0.1-obfuscated.8625b087aaba4a60.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-16-0.0.1-obfuscated.d5ffa4020f3be99a.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-17-0.0.1-obfuscated.6d13e19b22f50a80.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-18-0.0.1-obfuscated.6d31405044166289.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-19-0.0.1-obfuscated.a9a8d925942f19e4.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-2-0.0.1-obfuscated.b849a136ae4a6b3e.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-3-0.0.1-obfuscated.f9e77ebf6186e2ac.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-4-0.0.1-obfuscated.2c3c155aa52ceb0e.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-5-0.0.1-obfuscated.4e553d17691b3c5a.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-6-0.0.1-obfuscated.f73ddd3ff13e1217.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-7-0.0.1-obfuscated.1628b8729beaab7e.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-8-0.0.1-obfuscated.6da6baef7aa81fff.js +1 -0
- package/static/kuaishou/bundles/weapon/kws-9-0.0.1-obfuscated.8645d55e5323b743.js +1 -0
- package/static/kuaishou/fixtures/current_follow_script_813.network-response +2 -0
- package/static/kuaishou/fixtures/live_ws_maps.json +61 -0
- package/static/kuaishou/fixtures/live_ws_proto.json +1342 -0
- package/static/kuaishou/js/cp-kwf.js +1 -0
- package/static/kuaishou/tools/kwf_oracle.js +799 -0
- package/static/kuaishou/tools/kws_oracle.js +885 -0
- package/static/kuaishou/tools/like_token_oracle.js +70 -0
- package/static/taobao/taobao_js_20260407.js +570 -0
- package/static/tiktok/Tiktok_Request.json +597 -0
- package/static/tiktok/package.json +3 -0
- package/static/tiktok/shop_bsid/core.js +1 -0
- package/static/tiktok/shop_bsid/env_core.js +512 -0
- package/static/tiktok/shop_bsid/loader.js +1 -0
- package/static/tiktok/shop_bsid/run.js +753 -0
- package/static/tiktok/shop_bsid/sign.js +100 -0
- package/static/tiktok/shop_bsid/webmssdk.js +1 -0
- package/static/tiktok/shop_bsid/webmssdk_ex.js +1 -0
- package/static/tiktok/signing/env/env_core.js +512 -0
- package/static/tiktok/signing/env/sign.js +455 -0
- package/static/tiktok/signing/sdk_config.json +230 -0
- package/static/tiktok/signing/source/webmssdk.js +1 -0
- package/static/tiktok/signing/source/webmssdk_ex.js +1 -0
- package/static/tiktok/signing/source/webmssdk_legacy.js +1 -0
- package/static/x/graphql.json +7777 -0
- package/static/x/transaction_l1.json +12 -0
- package/static/xhs/package.json +3 -0
- package/static/xhs/xhs_core/js/b1.js +210 -0
- package/static/xhs/xhs_core/js/mns.js +340 -0
- package/static/xhs/xhs_core/js/mns_0101_keystream.json +5 -0
- package/static/xhs/xhs_core/js/mns_keystreams.json +1 -0
- package/static/xhs/xhs_core/js/sign.js +225 -0
- package/static/xhs/xhs_core/js/websectiga_cli.js +220 -0
- package/static/xhs/xhs_core/js/websectiga_env.js +512 -0
- package/static/xhs/xhs_core/js/xs_common.js +79 -0
- package/static/xhs/xhs_creator/js/profile.js +115 -0
- package/static/xhs/xhs_creator/js/rap_fingerprint_creator.json +6 -0
- package/static/xhs/xhs_creator/js/reference_profile.json +282 -0
- package/static/xhs/xhs_creator/js/xhs_creator_sign.js +29 -0
- package/static/xhs/xhs_creator/js/xhs_creator_signature.js +23 -0
- package/static/xhs/xhs_pc/js/aes_decrypt.js +22 -0
- package/static/xhs/xhs_pc/js/aes_encrypt.js +30 -0
- package/static/xhs/xhs_pc/js/b1.js +9 -0
- package/static/xhs/xhs_pc/js/deflate.js +320 -0
- package/static/xhs/xhs_pc/js/mns.js +4 -0
- package/static/xhs/xhs_pc/js/profile.js +141 -0
- package/static/xhs/xhs_pc/js/rap.js +173 -0
- package/static/xhs/xhs_pc/js/rap_cli.js +30 -0
- package/static/xhs/xhs_pc/js/rap_crypto.js +73 -0
- package/static/xhs/xhs_pc/js/rap_fingerprint_template.json +8 -0
- package/static/xhs/xhs_pc/js/reference_profile.json +262 -0
- package/static/xhs/xhs_pc/js/sign.js +6 -0
- package/static/xhs/xhs_pc/js/web_ssk.js +82 -0
- package/static/xhs/xhs_pc/js/xs_common.js +4 -0
- package/static/xianyu/et_f.js +2 -0
- package/static/xianyu/gen_tfstk.js +410 -0
- package/static/xianyu/goofish_js_version_2.js +568 -0
|
@@ -0,0 +1,1307 @@
|
|
|
1
|
+
const env = require('./env_core');
|
|
2
|
+
const _require = require;
|
|
3
|
+
const _process = process;
|
|
4
|
+
const _Buffer = Buffer;
|
|
5
|
+
const _Array = Array;
|
|
6
|
+
const _fs = require('fs');
|
|
7
|
+
const _path = require('path');
|
|
8
|
+
const _childProcess = require('child_process');
|
|
9
|
+
const _vm = require('vm');
|
|
10
|
+
const _nativeFetch = global.fetch && global.fetch.bind(global);
|
|
11
|
+
const TRANSPORT_BRIDGE = _path.resolve(__dirname, 'http_bridge.py');
|
|
12
|
+
let runtimeInput = {};
|
|
13
|
+
try {
|
|
14
|
+
const stdin = _require('fs').readFileSync(0, 'utf8').trim();
|
|
15
|
+
if (stdin) runtimeInput = JSON.parse(stdin);
|
|
16
|
+
} catch (error) {
|
|
17
|
+
console.error('[input-error]', error && error.message || error);
|
|
18
|
+
}
|
|
19
|
+
const LIVE_NETWORK = runtimeInput.liveNetwork === true;
|
|
20
|
+
const MAX_SOLVE_ATTEMPTS = Math.max(1, Math.min(30,
|
|
21
|
+
Number(runtimeInput.maxSolveAttempts || 8)));
|
|
22
|
+
const runtimeState = {
|
|
23
|
+
latestFp: null,
|
|
24
|
+
latestCheck: null,
|
|
25
|
+
latestChallenge: null,
|
|
26
|
+
lastSolvedChallenge: null,
|
|
27
|
+
solving: false,
|
|
28
|
+
solveAttempts: 0,
|
|
29
|
+
completed: false,
|
|
30
|
+
resultEmitted: false,
|
|
31
|
+
bridgeCookies: Object.create(null),
|
|
32
|
+
cookieUpdates: Object.create(null),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function emitResult(payload) {
|
|
36
|
+
if (!runtimeInput.returnResult || runtimeState.resultEmitted) return;
|
|
37
|
+
runtimeState.resultEmitted = true;
|
|
38
|
+
console.log('__JCAP_RESULT__' + JSON.stringify({
|
|
39
|
+
...payload,
|
|
40
|
+
cookies: runtimeState.cookieUpdates,
|
|
41
|
+
localStorage: snapshotLocalStorage(),
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
const projectRoot = _path.resolve(__dirname, '..', '..', '..');
|
|
45
|
+
const repoRequire = _require('module').createRequire(
|
|
46
|
+
_path.join(projectRoot, 'package.json'));
|
|
47
|
+
const { JSDOM } = repoRequire('jsdom');
|
|
48
|
+
const { createCanvas: createNativeCanvas } = repoRequire('@napi-rs/canvas');
|
|
49
|
+
|
|
50
|
+
const PAGE_URL = String(runtimeInput.pageUrl ||
|
|
51
|
+
'https://passport.jd.com/new/login.aspx?ReturnUrl=https://home.jd.com/index.html');
|
|
52
|
+
const PAGE_ORIGIN = new URL(PAGE_URL).origin;
|
|
53
|
+
const dom = new JSDOM('<!doctype html><html><head></head><body></body></html>', {
|
|
54
|
+
url: PAGE_URL,
|
|
55
|
+
pretendToBeVisual: true,
|
|
56
|
+
runScripts: 'outside-only',
|
|
57
|
+
});
|
|
58
|
+
const rawWindow = dom.window;
|
|
59
|
+
const rawNavigator = rawWindow.navigator;
|
|
60
|
+
const rawDocument = rawWindow.document;
|
|
61
|
+
const rawLocation = rawWindow.location;
|
|
62
|
+
const rawLocalStorage = rawWindow.localStorage;
|
|
63
|
+
const rawPerformance = rawWindow.performance;
|
|
64
|
+
|
|
65
|
+
for (const [key, value] of Object.entries(runtimeInput.localStorage || {})) {
|
|
66
|
+
try { rawLocalStorage.setItem(String(key), String(value)); } catch (_) {}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function snapshotLocalStorage() {
|
|
70
|
+
const snapshot = Object.create(null);
|
|
71
|
+
for (let index = 0; index < rawLocalStorage.length; index++) {
|
|
72
|
+
const key = rawLocalStorage.key(index);
|
|
73
|
+
if (key == null) continue;
|
|
74
|
+
snapshot[String(key)] = String(rawLocalStorage.getItem(key) || '');
|
|
75
|
+
}
|
|
76
|
+
return snapshot;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// The official loader executes this bundle from the 2.8.5 CDN URL. JCAP
|
|
80
|
+
// includes document.currentScript.src in both its device fingerprint (`dcs`)
|
|
81
|
+
// and the WASM base-path calculation. JSDOM leaves currentScript null when a
|
|
82
|
+
// bundle is evaluated from a local file, which is observably different from
|
|
83
|
+
// the production page.
|
|
84
|
+
const JCAP_SCRIPT_URL =
|
|
85
|
+
'https://storage.360buyimg.com/jsresource/jcap/version/v2.8.5/1/jcap_ujb96b.js';
|
|
86
|
+
|
|
87
|
+
// JCAP records Error.stack and encrypts the resulting file table into `cs`.
|
|
88
|
+
// Programmatic DOM events otherwise append run.js/jsdom/node frames that can
|
|
89
|
+
// never occur for a physical browser event. Keep the vendor's own frames and
|
|
90
|
+
// format them exactly like a normal V8 stack before the official code parses
|
|
91
|
+
// them; the vendor source itself remains byte-for-byte unchanged.
|
|
92
|
+
const prepareJcapStack = env.setFuncNative(function prepareStackTrace(error, frames) {
|
|
93
|
+
const kept = Array.isArray(frames) ? frames.filter(frame => {
|
|
94
|
+
try {
|
|
95
|
+
const source = String(frame.getFileName()
|
|
96
|
+
|| frame.getScriptNameOrSourceURL() || '');
|
|
97
|
+
return source.split('?', 1)[0] === JCAP_SCRIPT_URL;
|
|
98
|
+
} catch (_) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}) : [];
|
|
102
|
+
const name = String(error && error.name || 'Error');
|
|
103
|
+
const message = String(error && error.message || '');
|
|
104
|
+
const first = message ? `${name}: ${message}` : name;
|
|
105
|
+
return [first, ...kept.map(frame => ` at ${String(frame)}`)].join('\n');
|
|
106
|
+
}, 'prepareStackTrace', 2);
|
|
107
|
+
try {
|
|
108
|
+
Object.defineProperty(Error, 'prepareStackTrace', {
|
|
109
|
+
configurable: true,
|
|
110
|
+
writable: true,
|
|
111
|
+
enumerable: false,
|
|
112
|
+
value: prepareJcapStack,
|
|
113
|
+
});
|
|
114
|
+
} catch (_) {}
|
|
115
|
+
const runtimeScript = rawDocument.createElement('script');
|
|
116
|
+
runtimeScript.src = JCAP_SCRIPT_URL;
|
|
117
|
+
try {
|
|
118
|
+
Object.defineProperty(rawDocument, 'currentScript', {
|
|
119
|
+
configurable: true,
|
|
120
|
+
get() { return runtimeScript; },
|
|
121
|
+
});
|
|
122
|
+
} catch (_) {}
|
|
123
|
+
|
|
124
|
+
function defineRuntimeValue(target, name, value) {
|
|
125
|
+
try {
|
|
126
|
+
Object.defineProperty(target, name, {
|
|
127
|
+
value, configurable: true, enumerable: true,
|
|
128
|
+
});
|
|
129
|
+
} catch (_) {}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 与已保存的完整请求样本保持同一台 Windows/Chrome 设备画像。JSDOM 的
|
|
133
|
+
// screen 默认为 0x0,navigator 也缺少反爬脚本会读取的硬件字段。
|
|
134
|
+
const runtimeUserAgent = String(runtimeInput.userAgent ||
|
|
135
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' +
|
|
136
|
+
'(KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36');
|
|
137
|
+
for (const [name, value] of Object.entries({
|
|
138
|
+
width: 2560, height: 1440, availWidth: 2560, availHeight: 1392,
|
|
139
|
+
colorDepth: 24, pixelDepth: 24,
|
|
140
|
+
})) defineRuntimeValue(rawWindow.screen, name, value);
|
|
141
|
+
for (const [name, value] of Object.entries({
|
|
142
|
+
innerWidth: 2560, innerHeight: 1297, outerWidth: 2560, outerHeight: 1392,
|
|
143
|
+
screenX: 0, screenY: 0, screenLeft: 0, screenTop: 0, devicePixelRatio: 1,
|
|
144
|
+
})) defineRuntimeValue(rawWindow, name, value);
|
|
145
|
+
for (const [name, value] of Object.entries({
|
|
146
|
+
userAgent: runtimeUserAgent, appVersion: runtimeUserAgent.replace(/^Mozilla\//, ''),
|
|
147
|
+
platform: 'Win32', vendor: 'Google Inc.', language: 'zh-CN',
|
|
148
|
+
languages: Object.freeze(['zh-CN', 'zh', 'en', 'zh-TW', 'ja']),
|
|
149
|
+
hardwareConcurrency: 32, deviceMemory: 8, maxTouchPoints: 0,
|
|
150
|
+
webdriver: false,
|
|
151
|
+
})) defineRuntimeValue(rawNavigator, name, value);
|
|
152
|
+
const runtimeConnection = Object.freeze({
|
|
153
|
+
effectiveType: '4g', downlink: 10, rtt: 50, saveData: false,
|
|
154
|
+
type: 'wifi', bandwidth: 10,
|
|
155
|
+
});
|
|
156
|
+
defineRuntimeValue(rawNavigator, 'connection', runtimeConnection);
|
|
157
|
+
defineRuntimeValue(rawNavigator, 'mozConnection', runtimeConnection);
|
|
158
|
+
defineRuntimeValue(rawNavigator, 'webkitConnection', runtimeConnection);
|
|
159
|
+
defineRuntimeValue(rawNavigator, 'pdfViewerEnabled', true);
|
|
160
|
+
defineRuntimeValue(rawNavigator, 'userActivation', Object.freeze({
|
|
161
|
+
hasBeenActive: false, isActive: false,
|
|
162
|
+
}));
|
|
163
|
+
// Keep the nested set-like object configurable. Freezing it violates the
|
|
164
|
+
// recursive Proxy invariant (the proxy must then return the exact object),
|
|
165
|
+
// which made JCAP's guarded WebGPU probe throw and silently emit gpu="".
|
|
166
|
+
defineRuntimeValue(rawNavigator, 'gpu', {
|
|
167
|
+
wgslLanguageFeatures: { size: 12 },
|
|
168
|
+
});
|
|
169
|
+
defineRuntimeValue(rawWindow, 'chrome', Object.freeze({ runtime: Object.freeze({}) }));
|
|
170
|
+
Object.defineProperty(rawWindow, 'navigator', {
|
|
171
|
+
value: rawNavigator, enumerable: true, configurable: true,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// JCAP probes installed fonts by comparing span.offsetWidth/offsetHeight for
|
|
175
|
+
// `candidate,generic` against the three generic families. JSDOM reports zero
|
|
176
|
+
// for every span, which produces an impossible Windows fingerprint (`fts` is
|
|
177
|
+
// empty). Return deterministic metrics for the common fonts represented by
|
|
178
|
+
// the same profile used by the rest of this runtime.
|
|
179
|
+
const runtimeFonts = new Set([
|
|
180
|
+
'Arial', 'Arial Black', 'Book Antiqua', 'Calibri', 'Cambria', 'Century',
|
|
181
|
+
'Century Gothic', 'Comic Sans MS', 'Consolas', 'Courier', 'Courier New',
|
|
182
|
+
'Georgia', 'Lucida Console', 'Lucida Sans Unicode', 'Microsoft Sans Serif',
|
|
183
|
+
'Microsoft YaHei', 'MS Gothic', 'Segoe UI', 'SimSun', 'Tahoma',
|
|
184
|
+
'Times New Roman', 'Trebuchet MS', 'Verdana',
|
|
185
|
+
]);
|
|
186
|
+
function runtimeFontMetric(node, dimension) {
|
|
187
|
+
const family = String(node && node.style && node.style.fontFamily || '');
|
|
188
|
+
const names = family.split(',').map(value => value.trim().replace(/^['"]|['"]$/g, ''));
|
|
189
|
+
const fallback = names[names.length - 1].toLowerCase();
|
|
190
|
+
const base = fallback.includes('monospace') ? 128
|
|
191
|
+
: fallback.includes('serif') && !fallback.includes('sans') ? 124 : 126;
|
|
192
|
+
const candidate = names[0] || '';
|
|
193
|
+
const installed = names.length > 1 && runtimeFonts.has(candidate);
|
|
194
|
+
if (dimension === 'height') return installed ? 21 + (candidate.length % 2) : 20;
|
|
195
|
+
return installed ? base + 3 + (candidate.length % 11) : base;
|
|
196
|
+
}
|
|
197
|
+
for (const [name, dimension] of [['offsetWidth', 'width'], ['offsetHeight', 'height']]) {
|
|
198
|
+
try {
|
|
199
|
+
Object.defineProperty(rawWindow.HTMLElement.prototype, name, {
|
|
200
|
+
configurable: true,
|
|
201
|
+
get() { return runtimeFontMetric(this, dimension); },
|
|
202
|
+
});
|
|
203
|
+
} catch (_) {}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// 首轮只补已由成功 Chrome 样本确认的基础对象;网络先拦截,不实际发送。
|
|
207
|
+
function canvasDimension(node, name, fallback) {
|
|
208
|
+
const value = Number(node && node[name]);
|
|
209
|
+
return Number.isFinite(value) && value > 0 ? Math.round(value) : fallback;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function ensureNative2d(node, state) {
|
|
213
|
+
const width = canvasDimension(node, 'width', 300);
|
|
214
|
+
const height = canvasDimension(node, 'height', 150);
|
|
215
|
+
if (!state.canvas || state.canvas.width !== width || state.canvas.height !== height) {
|
|
216
|
+
state.canvas = createNativeCanvas(width, height);
|
|
217
|
+
state.context = state.canvas.getContext('2d');
|
|
218
|
+
for (const [name, value] of Object.entries(state.properties)) {
|
|
219
|
+
try { state.context[name] = value; } catch (_) {}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return state.context;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function makeCanvas2dContext(node) {
|
|
226
|
+
const state = { canvas: null, context: null, properties: Object.create(null) };
|
|
227
|
+
node.__jcapNative2d = state;
|
|
228
|
+
return new Proxy({}, {
|
|
229
|
+
get(_target, name) {
|
|
230
|
+
if (name === 'canvas') return node;
|
|
231
|
+
const context = ensureNative2d(node, state);
|
|
232
|
+
const value = context[name];
|
|
233
|
+
return typeof value === 'function' ? value.bind(context) : value;
|
|
234
|
+
},
|
|
235
|
+
set(_target, name, value) {
|
|
236
|
+
state.properties[name] = value;
|
|
237
|
+
try { ensureNative2d(node, state)[name] = value; } catch (_) {}
|
|
238
|
+
return true;
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function FakeWebGLRenderingContext(canvas) {
|
|
244
|
+
this.canvas = canvas;
|
|
245
|
+
this.ARRAY_BUFFER = 34962;
|
|
246
|
+
this.STATIC_DRAW = 35044;
|
|
247
|
+
this.FLOAT = 5126;
|
|
248
|
+
this.TRIANGLE_STRIP = 5;
|
|
249
|
+
this.VERTEX_SHADER = 35633;
|
|
250
|
+
this.FRAGMENT_SHADER = 35632;
|
|
251
|
+
}
|
|
252
|
+
for (const [name, implementation] of Object.entries({
|
|
253
|
+
createBuffer() { return {}; },
|
|
254
|
+
bindBuffer() {},
|
|
255
|
+
bufferData(_target, data) {
|
|
256
|
+
const values = [];
|
|
257
|
+
const length = data && Number(data.length) || 0;
|
|
258
|
+
// env_core wraps objects crossing the synthetic window boundary.
|
|
259
|
+
// Iterating a Proxy-wrapped TypedArray calls its native iterator with
|
|
260
|
+
// the wrong receiver, so copy through indexed access instead.
|
|
261
|
+
for (let index = 0; index < length; index++) values.push(Number(data[index]));
|
|
262
|
+
this.__vertices = values;
|
|
263
|
+
},
|
|
264
|
+
createProgram() { return {}; },
|
|
265
|
+
createShader() { return {}; },
|
|
266
|
+
shaderSource() {},
|
|
267
|
+
compileShader() {},
|
|
268
|
+
attachShader() {},
|
|
269
|
+
linkProgram() {},
|
|
270
|
+
getAttribLocation() { return 0; },
|
|
271
|
+
getUniformLocation() { return {}; },
|
|
272
|
+
useProgram() {},
|
|
273
|
+
enableVertexAttribArray() {},
|
|
274
|
+
vertexAttribPointer() {},
|
|
275
|
+
uniform2f() {},
|
|
276
|
+
drawArrays() {},
|
|
277
|
+
getExtension(name) {
|
|
278
|
+
if (name === 'WEBGL_debug_renderer_info') {
|
|
279
|
+
return { UNMASKED_VENDOR_WEBGL: 37445, UNMASKED_RENDERER_WEBGL: 37446 };
|
|
280
|
+
}
|
|
281
|
+
if (name === 'WEBGL_lose_context') return { loseContext() {} };
|
|
282
|
+
return null;
|
|
283
|
+
},
|
|
284
|
+
getParameter(parameter) {
|
|
285
|
+
if (parameter === 37445) return 'Google Inc. (NVIDIA)';
|
|
286
|
+
if (parameter === 37446) {
|
|
287
|
+
return 'ANGLE (NVIDIA, NVIDIA GeForce RTX 5060 Ti (0x00002D04) ' +
|
|
288
|
+
'Direct3D11 vs_5_0 ps_5_0, D3D11)';
|
|
289
|
+
}
|
|
290
|
+
return null;
|
|
291
|
+
},
|
|
292
|
+
})) {
|
|
293
|
+
FakeWebGLRenderingContext.prototype[name] = env.setFuncNative(
|
|
294
|
+
implementation, name, implementation.length);
|
|
295
|
+
}
|
|
296
|
+
defineRuntimeValue(rawWindow, 'WebGLRenderingContext', FakeWebGLRenderingContext);
|
|
297
|
+
|
|
298
|
+
function webGlDataUrl(node) {
|
|
299
|
+
const width = canvasDimension(node, 'width', 300);
|
|
300
|
+
const height = canvasDimension(node, 'height', 150);
|
|
301
|
+
const canvas = createNativeCanvas(width, height);
|
|
302
|
+
const context = canvas.getContext('2d');
|
|
303
|
+
const vertices = node.__jcapWebGL && node.__jcapWebGL.__vertices;
|
|
304
|
+
const values = Array.isArray(vertices) && vertices.length >= 9
|
|
305
|
+
? vertices : [-.2, -.9, 0, .4, -.26, 0, 0, .732134444, 0];
|
|
306
|
+
const point = index => [
|
|
307
|
+
(values[index] + 1) * width / 2,
|
|
308
|
+
(1 - values[index + 1]) * height / 2,
|
|
309
|
+
];
|
|
310
|
+
const gradient = context.createLinearGradient(0, 0, width, height);
|
|
311
|
+
gradient.addColorStop(0, 'rgba(204, 242, 0, 1)');
|
|
312
|
+
gradient.addColorStop(1, 'rgba(255, 255, 0, 1)');
|
|
313
|
+
context.fillStyle = gradient;
|
|
314
|
+
for (const triangle of [[0, 3, 6], [3, 6, 9]]) {
|
|
315
|
+
if (triangle[2] + 1 >= values.length) continue;
|
|
316
|
+
context.beginPath();
|
|
317
|
+
const first = point(triangle[0]);
|
|
318
|
+
context.moveTo(first[0], first[1]);
|
|
319
|
+
for (const index of triangle.slice(1)) {
|
|
320
|
+
const next = point(index);
|
|
321
|
+
context.lineTo(next[0], next[1]);
|
|
322
|
+
}
|
|
323
|
+
context.closePath();
|
|
324
|
+
context.fill();
|
|
325
|
+
}
|
|
326
|
+
return canvas.toDataURL('image/png');
|
|
327
|
+
}
|
|
328
|
+
rawWindow.HTMLCanvasElement.prototype.getContext = env.setFuncNative(
|
|
329
|
+
function getContext(kind) {
|
|
330
|
+
if (kind === '2d') {
|
|
331
|
+
this.__jcapContextKind = '2d';
|
|
332
|
+
if (!this.__jcapContext2d) this.__jcapContext2d = makeCanvas2dContext(this);
|
|
333
|
+
return this.__jcapContext2d;
|
|
334
|
+
}
|
|
335
|
+
if (kind === 'webgl' || kind === 'experimental-webgl') {
|
|
336
|
+
this.__jcapContextKind = 'webgl';
|
|
337
|
+
if (!this.__jcapWebGL) this.__jcapWebGL = new FakeWebGLRenderingContext(this);
|
|
338
|
+
return this.__jcapWebGL;
|
|
339
|
+
}
|
|
340
|
+
return null;
|
|
341
|
+
},
|
|
342
|
+
'getContext', 1);
|
|
343
|
+
rawWindow.HTMLCanvasElement.prototype.toDataURL = env.setFuncNative(
|
|
344
|
+
function toDataURL() {
|
|
345
|
+
if (this.__jcapContextKind === 'webgl') return webGlDataUrl(this);
|
|
346
|
+
if (!this.__jcapNative2d) this.__jcapContext2d = makeCanvas2dContext(this);
|
|
347
|
+
ensureNative2d(this, this.__jcapNative2d);
|
|
348
|
+
return this.__jcapNative2d.canvas.toDataURL('image/png');
|
|
349
|
+
}, 'toDataURL', 0);
|
|
350
|
+
rawWindow.fetch = env.setFuncNative(async function fetch(url, options) {
|
|
351
|
+
console.log('[capture.fetch]', String(url), options && options.method || 'GET');
|
|
352
|
+
throw new Error('capture-only');
|
|
353
|
+
}, 'fetch', 1);
|
|
354
|
+
rawWindow.Buffer = _Buffer;
|
|
355
|
+
|
|
356
|
+
const CaptureXHR = env.setFuncNative(function XMLHttpRequest() {
|
|
357
|
+
this.readyState = 0;
|
|
358
|
+
this.status = 0;
|
|
359
|
+
this.responseText = '';
|
|
360
|
+
this.response = '';
|
|
361
|
+
this.withCredentials = false;
|
|
362
|
+
this.headers = {};
|
|
363
|
+
this.listeners = {};
|
|
364
|
+
}, 'XMLHttpRequest', 0);
|
|
365
|
+
|
|
366
|
+
function bridgeFetch(target, request) {
|
|
367
|
+
const cookieMap = Object.create(null);
|
|
368
|
+
for (const item of String(request.headers && request.headers.cookie || '').split(';')) {
|
|
369
|
+
const separator = item.indexOf('=');
|
|
370
|
+
if (separator <= 0) continue;
|
|
371
|
+
cookieMap[item.slice(0, separator).trim()] = item.slice(separator + 1).trim();
|
|
372
|
+
}
|
|
373
|
+
Object.assign(cookieMap, runtimeState.bridgeCookies);
|
|
374
|
+
request.headers.cookie = Object.entries(cookieMap)
|
|
375
|
+
.map(([name, value]) => `${name}=${value}`).join('; ');
|
|
376
|
+
try {
|
|
377
|
+
const parsed = new URL(target);
|
|
378
|
+
const transportPrefix = parsed.pathname.endsWith('/cgi-bin/api/check')
|
|
379
|
+
? '[captcha.transport.check]' : '[captcha.transport]';
|
|
380
|
+
console.log(transportPrefix, JSON.stringify({
|
|
381
|
+
method: String(request.method || 'GET').toUpperCase(),
|
|
382
|
+
host: parsed.host,
|
|
383
|
+
path: parsed.pathname,
|
|
384
|
+
headerNames: Object.keys(request.headers || {})
|
|
385
|
+
.map(name => String(name).toLowerCase()).sort(),
|
|
386
|
+
cookieNames: Object.keys(cookieMap).sort(),
|
|
387
|
+
bodyLength: request.body == null ? 0 : String(request.body).length,
|
|
388
|
+
}));
|
|
389
|
+
} catch (_) {}
|
|
390
|
+
const result = _childProcess.spawnSync(
|
|
391
|
+
String(runtimeInput.pythonExecutable || 'python'), [TRANSPORT_BRIDGE], {
|
|
392
|
+
input: JSON.stringify({
|
|
393
|
+
method: request.method || 'GET',
|
|
394
|
+
url: target,
|
|
395
|
+
headers: request.headers || {},
|
|
396
|
+
body: request.body == null ? '' : String(request.body),
|
|
397
|
+
}),
|
|
398
|
+
encoding: 'utf8',
|
|
399
|
+
timeout: 25000,
|
|
400
|
+
windowsHide: true,
|
|
401
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
402
|
+
});
|
|
403
|
+
if (result.error || result.status !== 0) {
|
|
404
|
+
const error = new Error('JCAP transport bridge failed');
|
|
405
|
+
error.cause = { code: result.error && result.error.code || result.status };
|
|
406
|
+
throw error;
|
|
407
|
+
}
|
|
408
|
+
const payload = JSON.parse(String(result.stdout || ''));
|
|
409
|
+
const receivedCookieNames = [];
|
|
410
|
+
for (const setCookie of Array.isArray(payload.setCookies) ? payload.setCookies : []) {
|
|
411
|
+
const pair = String(setCookie || '').split(';', 1)[0];
|
|
412
|
+
const separator = pair.indexOf('=');
|
|
413
|
+
if (separator <= 0) continue;
|
|
414
|
+
const name = pair.slice(0, separator).trim();
|
|
415
|
+
const value = pair.slice(separator + 1).trim();
|
|
416
|
+
if (/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(name)) {
|
|
417
|
+
runtimeState.cookieUpdates[name] = value;
|
|
418
|
+
if (value) runtimeState.bridgeCookies[name] = value;
|
|
419
|
+
else delete runtimeState.bridgeCookies[name];
|
|
420
|
+
receivedCookieNames.push(name);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
try {
|
|
424
|
+
const path = new URL(target).pathname;
|
|
425
|
+
if (path.includes('/cgi-bin/api/')) {
|
|
426
|
+
console.log('[captcha.cookies]', JSON.stringify({
|
|
427
|
+
path, received: receivedCookieNames.sort(),
|
|
428
|
+
jar: Object.keys(runtimeState.bridgeCookies).sort(),
|
|
429
|
+
}));
|
|
430
|
+
}
|
|
431
|
+
} catch (_) {}
|
|
432
|
+
const responseHeaders = Array.isArray(payload.headers) ? payload.headers : [];
|
|
433
|
+
return {
|
|
434
|
+
status: Number(payload.status || 0),
|
|
435
|
+
statusText: String(payload.statusText || ''),
|
|
436
|
+
url: String(payload.url || target),
|
|
437
|
+
headers: { entries: () => responseHeaders[Symbol.iterator]() },
|
|
438
|
+
text: async () => String(payload.body || ''),
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
CaptureXHR.prototype.open = env.setFuncNative(function open(method, url) {
|
|
442
|
+
this.method = String(method);
|
|
443
|
+
this.url = String(url);
|
|
444
|
+
this.readyState = 1;
|
|
445
|
+
}, 'open', 2);
|
|
446
|
+
CaptureXHR.prototype.setRequestHeader = env.setFuncNative(
|
|
447
|
+
function setRequestHeader(name, value) { this.headers[String(name)] = String(value); },
|
|
448
|
+
'setRequestHeader', 2);
|
|
449
|
+
CaptureXHR.prototype.addEventListener = env.setFuncNative(
|
|
450
|
+
function addEventListener(name, callback) { this.listeners[name] = callback; },
|
|
451
|
+
'addEventListener', 2);
|
|
452
|
+
CaptureXHR.prototype.send = env.setFuncNative(function send(body) {
|
|
453
|
+
const text = body == null ? '' : String(body);
|
|
454
|
+
const fields = {};
|
|
455
|
+
try {
|
|
456
|
+
const params = new URLSearchParams(text);
|
|
457
|
+
for (const key of new Set(params.keys())) {
|
|
458
|
+
fields[key] = String(params.get(key) || '').length;
|
|
459
|
+
}
|
|
460
|
+
} catch (_) {}
|
|
461
|
+
let safeUrl = this.url;
|
|
462
|
+
try {
|
|
463
|
+
const parsed = new URL(this.url, PAGE_URL);
|
|
464
|
+
for (const key of ['sid', 'si', 'ct', 'vt', 'token']) {
|
|
465
|
+
if (parsed.searchParams.has(key)) {
|
|
466
|
+
const value = parsed.searchParams.get(key) || '';
|
|
467
|
+
parsed.searchParams.set(key, `<len:${value.length}>`);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
safeUrl = parsed.toString();
|
|
471
|
+
} catch (_) {}
|
|
472
|
+
console.log('[capture.xhr]', JSON.stringify({
|
|
473
|
+
method: this.method, url: safeUrl, headers: this.headers,
|
|
474
|
+
bodyLength: text.length, fields,
|
|
475
|
+
}));
|
|
476
|
+
try {
|
|
477
|
+
const requestPath = new URL(this.url, PAGE_URL).pathname;
|
|
478
|
+
if (requestPath.endsWith('/cgi-bin/api/check')) {
|
|
479
|
+
console.log('[captcha.request]', JSON.stringify({
|
|
480
|
+
method: this.method, path: requestPath,
|
|
481
|
+
bodyLength: text.length, fields,
|
|
482
|
+
headerNames: Object.keys(this.headers).map(name => name.toLowerCase()),
|
|
483
|
+
}));
|
|
484
|
+
}
|
|
485
|
+
} catch (_) {}
|
|
486
|
+
if (LIVE_NETWORK && (_nativeFetch || runtimeInput.pythonExecutable)) {
|
|
487
|
+
const target = new URL(this.url, PAGE_URL).toString();
|
|
488
|
+
const headers = Object.assign({
|
|
489
|
+
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
|
490
|
+
'accept-encoding': 'gzip, deflate, br, zstd',
|
|
491
|
+
origin: PAGE_ORIGIN,
|
|
492
|
+
priority: 'u=1, i',
|
|
493
|
+
referer: PAGE_URL,
|
|
494
|
+
'sec-ch-ua': String(runtimeInput.secChUa ||
|
|
495
|
+
'"Chromium";v="152", "Not?A_Brand";v="24", "Google Chrome";v="152"'),
|
|
496
|
+
'sec-ch-ua-mobile': String(runtimeInput.secChUaMobile || '?0'),
|
|
497
|
+
'sec-ch-ua-platform': String(runtimeInput.secChUaPlatform || '"Windows"'),
|
|
498
|
+
'sec-fetch-dest': 'empty',
|
|
499
|
+
'sec-fetch-mode': 'cors',
|
|
500
|
+
'sec-fetch-site': 'same-site',
|
|
501
|
+
'user-agent': String(rawNavigator.userAgent || ''),
|
|
502
|
+
}, this.headers);
|
|
503
|
+
if (runtimeInput.cookie) headers.cookie = String(runtimeInput.cookie);
|
|
504
|
+
let pending;
|
|
505
|
+
try {
|
|
506
|
+
const request = {
|
|
507
|
+
method: this.method || 'GET', headers,
|
|
508
|
+
body: String(this.method || 'GET').toUpperCase() === 'GET' ? undefined : text,
|
|
509
|
+
redirect: 'follow',
|
|
510
|
+
};
|
|
511
|
+
pending = runtimeInput.pythonExecutable
|
|
512
|
+
? Promise.resolve().then(() => bridgeFetch(target, request))
|
|
513
|
+
: _nativeFetch(target, request);
|
|
514
|
+
} catch (error) {
|
|
515
|
+
console.error('[capture.network-sync-error]', {
|
|
516
|
+
message: error && error.message,
|
|
517
|
+
cause: error && error.cause && error.cause.code,
|
|
518
|
+
});
|
|
519
|
+
if (typeof this.onerror === 'function') this.onerror(error);
|
|
520
|
+
if (typeof this.listeners.error === 'function') this.listeners.error(error);
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
pending.then(async response => {
|
|
524
|
+
this.status = response.status;
|
|
525
|
+
this.statusText = response.statusText;
|
|
526
|
+
this.responseURL = response.url;
|
|
527
|
+
this.responseText = await response.text();
|
|
528
|
+
this.response = this.responseText;
|
|
529
|
+
try {
|
|
530
|
+
const parsedBody = JSON.parse(this.responseText);
|
|
531
|
+
const path = new URL(target).pathname;
|
|
532
|
+
if (path.endsWith('/cgi-bin/api/fp')) runtimeState.latestFp = parsedBody;
|
|
533
|
+
if (path.endsWith('/cgi-bin/api/check')) {
|
|
534
|
+
runtimeState.latestCheck = parsedBody;
|
|
535
|
+
console.log('[captcha.check]', JSON.stringify({
|
|
536
|
+
code: parsedBody && parsedBody.code,
|
|
537
|
+
sCode: parsedBody && parsedBody.s_code,
|
|
538
|
+
tp: parsedBody && parsedBody.tp,
|
|
539
|
+
type: parsedBody && parsedBody.type,
|
|
540
|
+
message: String(parsedBody && parsedBody.msg || '').slice(0, 80),
|
|
541
|
+
stLength: String(parsedBody && parsedBody.st || '').length,
|
|
542
|
+
hasVt: Boolean(parsedBody && parsedBody.vt),
|
|
543
|
+
fields: parsedBody && typeof parsedBody === 'object'
|
|
544
|
+
? Object.keys(parsedBody).sort() : [],
|
|
545
|
+
}));
|
|
546
|
+
if (!(parsedBody && parsedBody.vt)
|
|
547
|
+
&& runtimeState.solveAttempts >= MAX_SOLVE_ATTEMPTS) {
|
|
548
|
+
setTimeout(() => {
|
|
549
|
+
reportEnvironment();
|
|
550
|
+
emitResult({ ok: false, error: 'attempts_exhausted' });
|
|
551
|
+
if (runtimeInput.returnResult) _process.exit(0);
|
|
552
|
+
}, 250);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
if ((path.endsWith('/cgi-bin/api/check') || path.endsWith('/cgi-bin/api/refresh'))
|
|
556
|
+
&& parsedBody && parsedBody.img && Number.isFinite(Number(parsedBody.tp))) {
|
|
557
|
+
runtimeState.latestChallenge = parsedBody;
|
|
558
|
+
const solveDelay = path.endsWith('/cgi-bin/api/refresh') ? 2200 : 350;
|
|
559
|
+
setTimeout(() => autoSolveChallenge().catch(error =>
|
|
560
|
+
console.error('[captcha.solve-error]', error && error.stack || error)), solveDelay);
|
|
561
|
+
}
|
|
562
|
+
} catch (_) {}
|
|
563
|
+
this._responseHeaders = Array.from(response.headers.entries());
|
|
564
|
+
this.readyState = 4;
|
|
565
|
+
if (typeof this.onreadystatechange === 'function') this.onreadystatechange();
|
|
566
|
+
if (typeof this.onload === 'function') this.onload();
|
|
567
|
+
if (typeof this.listeners.load === 'function') this.listeners.load();
|
|
568
|
+
}).catch(error => {
|
|
569
|
+
console.error('[capture.network-error]', {
|
|
570
|
+
message: error && error.message,
|
|
571
|
+
cause: error && error.cause && error.cause.code,
|
|
572
|
+
});
|
|
573
|
+
if (typeof this.onerror === 'function') this.onerror(error);
|
|
574
|
+
if (typeof this.listeners.error === 'function') this.listeners.error(error);
|
|
575
|
+
});
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
setTimeout(() => {
|
|
579
|
+
this.readyState = 4;
|
|
580
|
+
if (typeof this.onreadystatechange === 'function') this.onreadystatechange();
|
|
581
|
+
if (typeof this.onerror === 'function') this.onerror(new Error('capture-only'));
|
|
582
|
+
if (typeof this.listeners.error === 'function') this.listeners.error();
|
|
583
|
+
}, 0);
|
|
584
|
+
}, 'send', 1);
|
|
585
|
+
CaptureXHR.prototype.abort = env.setFuncNative(function abort() {}, 'abort', 0);
|
|
586
|
+
CaptureXHR.prototype.getAllResponseHeaders = env.setFuncNative(function getAllResponseHeaders() {
|
|
587
|
+
return (this._responseHeaders || []).map(([key, value]) => `${key}: ${value}`).join('\r\n');
|
|
588
|
+
}, 'getAllResponseHeaders', 0);
|
|
589
|
+
CaptureXHR.prototype.getResponseHeader = env.setFuncNative(function getResponseHeader(name) {
|
|
590
|
+
const lower = String(name).toLowerCase();
|
|
591
|
+
const pair = (this._responseHeaders || []).find(([key]) => key.toLowerCase() === lower);
|
|
592
|
+
return pair ? pair[1] : null;
|
|
593
|
+
}, 'getResponseHeader', 1);
|
|
594
|
+
|
|
595
|
+
const windowTarget = {
|
|
596
|
+
navigator: rawNavigator,
|
|
597
|
+
document: rawDocument,
|
|
598
|
+
location: rawLocation,
|
|
599
|
+
screen: rawWindow.screen,
|
|
600
|
+
innerWidth: rawWindow.innerWidth,
|
|
601
|
+
innerHeight: rawWindow.innerHeight,
|
|
602
|
+
outerWidth: rawWindow.outerWidth,
|
|
603
|
+
outerHeight: rawWindow.outerHeight,
|
|
604
|
+
screenX: rawWindow.screenX,
|
|
605
|
+
screenY: rawWindow.screenY,
|
|
606
|
+
screenLeft: rawWindow.screenLeft,
|
|
607
|
+
screenTop: rawWindow.screenTop,
|
|
608
|
+
devicePixelRatio: rawWindow.devicePixelRatio,
|
|
609
|
+
chrome: rawWindow.chrome,
|
|
610
|
+
WebGLRenderingContext: rawWindow.WebGLRenderingContext,
|
|
611
|
+
Intl,
|
|
612
|
+
fetch: rawWindow.fetch,
|
|
613
|
+
XMLHttpRequest: CaptureXHR,
|
|
614
|
+
Math,
|
|
615
|
+
Object, Array: _Array, JSON, Date, RegExp, String, Number, Boolean,
|
|
616
|
+
URL, URLSearchParams, FormData, Blob,
|
|
617
|
+
Error, TypeError, Map, Set, WeakMap, WeakSet, Promise, Proxy, Reflect,
|
|
618
|
+
ArrayBuffer, DataView, Uint8Array, Int32Array, Float64Array,
|
|
619
|
+
requestAnimationFrame: rawWindow.requestAnimationFrame.bind(rawWindow),
|
|
620
|
+
cancelAnimationFrame: rawWindow.cancelAnimationFrame.bind(rawWindow),
|
|
621
|
+
localStorage: rawLocalStorage,
|
|
622
|
+
performance: rawPerformance,
|
|
623
|
+
addEventListener: rawWindow.addEventListener.bind(rawWindow),
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
function setElementBox(node, boxFactory) {
|
|
627
|
+
if (!node) return;
|
|
628
|
+
const getBox = typeof boxFactory === 'function' ? boxFactory : () => boxFactory;
|
|
629
|
+
for (const key of ['width', 'height', 'clientWidth', 'clientHeight',
|
|
630
|
+
'offsetWidth', 'offsetHeight']) {
|
|
631
|
+
try {
|
|
632
|
+
Object.defineProperty(node, key, {
|
|
633
|
+
configurable: true,
|
|
634
|
+
// HTML image width/height and CSSOM client/offset dimensions
|
|
635
|
+
// are integer IDL attributes. getBoundingClientRect below is
|
|
636
|
+
// the API that preserves sub-pixel geometry.
|
|
637
|
+
get() {
|
|
638
|
+
return Math.round(Number(
|
|
639
|
+
getBox()[key.replace(/^client|^offset/, '').toLowerCase()] || 0));
|
|
640
|
+
},
|
|
641
|
+
});
|
|
642
|
+
} catch (_) {}
|
|
643
|
+
}
|
|
644
|
+
node.getBoundingClientRect = function getBoundingClientRect() {
|
|
645
|
+
const box = getBox();
|
|
646
|
+
const left = Number(box.left || 0);
|
|
647
|
+
const top = Number(box.top || 0);
|
|
648
|
+
const width = Number(box.width || 0);
|
|
649
|
+
const height = Number(box.height || 0);
|
|
650
|
+
return { x: left, y: top, left, top, width, height,
|
|
651
|
+
right: left + width, bottom: top + height, toJSON() { return this; } };
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function decodeImageSize(src) {
|
|
656
|
+
const match = /^data:image\/(png|jpe?g|webp);base64,([A-Za-z0-9+/=]+)$/.exec(String(src || ''));
|
|
657
|
+
if (!match) return null;
|
|
658
|
+
const data = _Buffer.from(match[2], 'base64');
|
|
659
|
+
if (match[1] === 'png' && data.length >= 24) {
|
|
660
|
+
return { width: data.readUInt32BE(16), height: data.readUInt32BE(20) };
|
|
661
|
+
}
|
|
662
|
+
if ((match[1] === 'jpg' || match[1] === 'jpeg') && data.length > 4) {
|
|
663
|
+
for (let offset = 2; offset + 9 < data.length;) {
|
|
664
|
+
if (data[offset] !== 0xff) { offset++; continue; }
|
|
665
|
+
const marker = data[offset + 1];
|
|
666
|
+
if ([0xc0, 0xc1, 0xc2, 0xc3, 0xc5, 0xc6, 0xc7,
|
|
667
|
+
0xc9, 0xca, 0xcb, 0xcd, 0xce, 0xcf].includes(marker)) {
|
|
668
|
+
return { height: data.readUInt16BE(offset + 5),
|
|
669
|
+
width: data.readUInt16BE(offset + 7) };
|
|
670
|
+
}
|
|
671
|
+
if (marker === 0xd8 || marker === 0xd9) { offset += 2; continue; }
|
|
672
|
+
const length = data.readUInt16BE(offset + 2);
|
|
673
|
+
if (!length) break;
|
|
674
|
+
offset += length + 2;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
return null;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
const delay = milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds));
|
|
681
|
+
|
|
682
|
+
async function dispatchTrace(points, imageSize) {
|
|
683
|
+
const image = rawDocument.querySelector('#cpc_img');
|
|
684
|
+
const canvas = rawDocument.querySelector('#trackLine');
|
|
685
|
+
if (!image || !canvas) return false;
|
|
686
|
+
const displayWidth = 290;
|
|
687
|
+
const displayHeight = imageSize.height * displayWidth / imageSize.width;
|
|
688
|
+
const box = { left: 367, top: 240, width: displayWidth, height: displayHeight };
|
|
689
|
+
setElementBox(image, box);
|
|
690
|
+
setElementBox(canvas, box);
|
|
691
|
+
const scaleX = displayWidth / imageSize.width;
|
|
692
|
+
const scaleY = displayHeight / imageSize.height;
|
|
693
|
+
const displayPoints = points.map(point => [
|
|
694
|
+
box.left + Number(point[0]) * scaleX,
|
|
695
|
+
box.top + Number(point[1]) * scaleY,
|
|
696
|
+
]);
|
|
697
|
+
const expanded = displayPoints.length > 8 ? displayPoints : [];
|
|
698
|
+
if (displayPoints.length <= 8) {
|
|
699
|
+
for (let segment = 0; segment < displayPoints.length - 1; segment++) {
|
|
700
|
+
const start = displayPoints[segment];
|
|
701
|
+
const end = displayPoints[segment + 1];
|
|
702
|
+
const steps = segment === 0 ? 12 : 18;
|
|
703
|
+
for (let index = segment === 0 ? 0 : 1; index <= steps; index++) {
|
|
704
|
+
const ratio = index / steps;
|
|
705
|
+
expanded.push([
|
|
706
|
+
start[0] + (end[0] - start[0]) * ratio,
|
|
707
|
+
start[1] + (end[1] - start[1]) * ratio,
|
|
708
|
+
]);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
const mouse = (type, point, target) => target.dispatchEvent(new rawWindow.MouseEvent(type, {
|
|
713
|
+
bubbles: true, cancelable: true,
|
|
714
|
+
clientX: point[0], clientY: point[1], screenX: point[0], screenY: point[1],
|
|
715
|
+
button: 0, buttons: type === 'mousedown' || type === 'mousemove' ? 1 : 0,
|
|
716
|
+
}));
|
|
717
|
+
mouse('mousedown', expanded[0], canvas);
|
|
718
|
+
for (const point of expanded.slice(1)) {
|
|
719
|
+
await delay(7 + Math.floor(Math.random() * 8));
|
|
720
|
+
mouse('mousemove', point, rawDocument);
|
|
721
|
+
}
|
|
722
|
+
await delay(20);
|
|
723
|
+
mouse('mouseup', expanded[expanded.length - 1], rawDocument);
|
|
724
|
+
return true;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
async function dispatchRotation(angle) {
|
|
728
|
+
const slider = rawDocument.querySelector('#slider-div');
|
|
729
|
+
const dragBox = rawDocument.querySelector('.drag-box');
|
|
730
|
+
if (!slider || !dragBox) return false;
|
|
731
|
+
const sliderWidth = 48;
|
|
732
|
+
const totalWidth = 290;
|
|
733
|
+
const boxLeft = 367;
|
|
734
|
+
const boxTop = 460;
|
|
735
|
+
setElementBox(dragBox, { left: boxLeft, top: boxTop, width: totalWidth, height: 48 });
|
|
736
|
+
setElementBox(slider, () => ({
|
|
737
|
+
left: boxLeft + (Number.parseFloat(slider.style.left || '0') || 0),
|
|
738
|
+
top: boxTop, width: sliderWidth, height: 48,
|
|
739
|
+
}));
|
|
740
|
+
const travel = totalWidth - sliderWidth;
|
|
741
|
+
const start = [boxLeft + sliderWidth / 2, boxTop + 24];
|
|
742
|
+
const end = [start[0] + travel * angle / 360, start[1]];
|
|
743
|
+
const mouse = (type, point, target) => target.dispatchEvent(new rawWindow.MouseEvent(type, {
|
|
744
|
+
bubbles: true, cancelable: true,
|
|
745
|
+
clientX: point[0], clientY: point[1], screenX: point[0], screenY: point[1],
|
|
746
|
+
button: 0, buttons: type === 'mouseup' ? 0 : 1,
|
|
747
|
+
}));
|
|
748
|
+
mouse('mousedown', start, slider);
|
|
749
|
+
for (let index = 1; index <= 35; index++) {
|
|
750
|
+
const ratio = index / 35;
|
|
751
|
+
const ease = 1 - Math.pow(1 - ratio, 2.2);
|
|
752
|
+
const point = [start[0] + (end[0] - start[0]) * ease,
|
|
753
|
+
start[1] + Math.sin(ratio * Math.PI) * 1.5];
|
|
754
|
+
await delay(8 + Math.floor(Math.random() * 7));
|
|
755
|
+
mouse('mousemove', point, rawDocument);
|
|
756
|
+
}
|
|
757
|
+
await delay(25);
|
|
758
|
+
// In a browser the moved slider remains under the pointer. The mouseup
|
|
759
|
+
// therefore bubbles from the slider and is followed by the resulting
|
|
760
|
+
// click; dispatching mouseup directly on document loses JCAP's global
|
|
761
|
+
// click/touche_message record even though the component's xyList works.
|
|
762
|
+
mouse('mouseup', end, slider);
|
|
763
|
+
slider.dispatchEvent(new rawWindow.MouseEvent('click', {
|
|
764
|
+
bubbles: true, cancelable: true,
|
|
765
|
+
clientX: end[0], clientY: end[1], screenX: end[0], screenY: end[1],
|
|
766
|
+
button: 0, buttons: 0,
|
|
767
|
+
}));
|
|
768
|
+
return true;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
async function dispatchSlider(offset, mainSize, slotSize) {
|
|
772
|
+
const main = rawDocument.querySelector('#main_img');
|
|
773
|
+
const slot = rawDocument.querySelector('#slot_img');
|
|
774
|
+
const slider = rawDocument.querySelector('.local_footer .move-img');
|
|
775
|
+
const footer = rawDocument.querySelector('.local_footer');
|
|
776
|
+
if (!main || !slot || !slider || !footer || !mainSize || !slotSize) return false;
|
|
777
|
+
// PC JCAP modal is 310px wide and `.captcha_body` has 3.23% margins on
|
|
778
|
+
// both sides: 310 * (1 - 2 * .0323) = 289.974px. Browsers expose the
|
|
779
|
+
// image/slider container as 290px; using the mobile 281px geometry shifts
|
|
780
|
+
// the submitted displacement by several pixels and the server rejects it.
|
|
781
|
+
const displayWidth = 290;
|
|
782
|
+
const scale = displayWidth / mainSize.width;
|
|
783
|
+
const displayHeight = mainSize.height * scale;
|
|
784
|
+
// tp=30 uses the d33c3516 component: #slot_img has height:100% and keeps
|
|
785
|
+
// its 65:170 source aspect ratio. (The separate tp=40 component makes its
|
|
786
|
+
// slot square; applying that geometry here corrupts the encrypted `mw`.)
|
|
787
|
+
const slotWidth = slotSize.width * scale;
|
|
788
|
+
const boxLeft = 367;
|
|
789
|
+
const imageTop = 240;
|
|
790
|
+
const footerTop = imageTop + displayHeight + 18;
|
|
791
|
+
const sliderWidth = 48;
|
|
792
|
+
setElementBox(main, {
|
|
793
|
+
left: boxLeft, top: imageTop, width: displayWidth, height: displayHeight,
|
|
794
|
+
});
|
|
795
|
+
setElementBox(slot, () => ({
|
|
796
|
+
left: boxLeft, top: imageTop, width: slotWidth, height: displayHeight,
|
|
797
|
+
}));
|
|
798
|
+
setElementBox(footer, {
|
|
799
|
+
left: boxLeft, top: footerTop, width: displayWidth, height: 48,
|
|
800
|
+
});
|
|
801
|
+
setElementBox(slider, () => ({
|
|
802
|
+
left: boxLeft + (Number.parseFloat(slider.style.left || '0') || 0),
|
|
803
|
+
top: footerTop, width: sliderWidth, height: 48,
|
|
804
|
+
}));
|
|
805
|
+
const travel = Math.max(0, Math.min(
|
|
806
|
+
Math.round(Number(offset) * scale), displayWidth - sliderWidth));
|
|
807
|
+
const start = [
|
|
808
|
+
Math.round(boxLeft + sliderWidth / 2 + (Math.random() - 0.5) * 10),
|
|
809
|
+
Math.round(footerTop + 24 + (Math.random() - 0.5) * 8),
|
|
810
|
+
];
|
|
811
|
+
const mouse = (type, point, target) => target.dispatchEvent(new rawWindow.MouseEvent(type, {
|
|
812
|
+
bubbles: true, cancelable: true,
|
|
813
|
+
clientX: point[0], clientY: point[1], screenX: point[0], screenY: point[1],
|
|
814
|
+
button: 0, buttons: type === 'mousedown' || type === 'mousemove' ? 1 : 0,
|
|
815
|
+
}));
|
|
816
|
+
mouse('mousedown', start, slider);
|
|
817
|
+
// The component records only relative clientX/clientY and wall-clock
|
|
818
|
+
// deltas. Generate a fresh minimum-jerk movement for every challenge;
|
|
819
|
+
// reusing one normalized capture is easy for the service to cluster.
|
|
820
|
+
const maxTravel = displayWidth - sliderWidth;
|
|
821
|
+
const canOvershoot = travel + 2 < maxTravel;
|
|
822
|
+
const overshoot = canOvershoot && Math.random() < 0.46
|
|
823
|
+
? 2 + Math.floor(Math.random() * Math.min(4, maxTravel - travel - 1))
|
|
824
|
+
: 0;
|
|
825
|
+
const correctionAt = overshoot ? 0.84 + Math.random() * 0.08 : 1;
|
|
826
|
+
const duration = Math.round(
|
|
827
|
+
390 + travel * (1.15 + Math.random() * 0.85) + Math.random() * 180);
|
|
828
|
+
const cadence = 7.5 + Math.random() * 4.5;
|
|
829
|
+
const pointCount = Math.max(36, Math.min(72, Math.round(duration / cadence)));
|
|
830
|
+
const verticalArc = (Math.random() - 0.5) * Math.min(10, 3 + travel / 32);
|
|
831
|
+
const minimumJerk = value => {
|
|
832
|
+
const p = Math.max(0, Math.min(1, value));
|
|
833
|
+
return p * p * p * (10 + p * (-15 + 6 * p));
|
|
834
|
+
};
|
|
835
|
+
let lastX = 0;
|
|
836
|
+
let lastY = 0;
|
|
837
|
+
let verticalNoise = 0;
|
|
838
|
+
let elapsed = 0;
|
|
839
|
+
for (let index = 1; index <= pointCount; index++) {
|
|
840
|
+
const phase = index / pointCount;
|
|
841
|
+
let x;
|
|
842
|
+
if (overshoot && phase > correctionAt) {
|
|
843
|
+
const correctionPhase = (phase - correctionAt) / (1 - correctionAt);
|
|
844
|
+
x = travel + overshoot * (1 - minimumJerk(correctionPhase));
|
|
845
|
+
} else {
|
|
846
|
+
const forwardPhase = phase / correctionAt;
|
|
847
|
+
x = (travel + overshoot) * minimumJerk(forwardPhase);
|
|
848
|
+
}
|
|
849
|
+
if (index < pointCount) x += (Math.random() - 0.5) * 1.4;
|
|
850
|
+
x = Math.round(Math.max(0, Math.min(maxTravel, x)));
|
|
851
|
+
if (!overshoot || phase <= correctionAt) x = Math.max(lastX, x);
|
|
852
|
+
if (index === pointCount) x = travel;
|
|
853
|
+
|
|
854
|
+
if (index < pointCount && Math.random() < 0.38) {
|
|
855
|
+
verticalNoise += Math.random() < 0.5 ? -1 : 1;
|
|
856
|
+
verticalNoise = Math.max(-2, Math.min(2, verticalNoise));
|
|
857
|
+
}
|
|
858
|
+
const y = index === pointCount
|
|
859
|
+
? Math.round(verticalArc * (0.12 + Math.random() * 0.18))
|
|
860
|
+
: Math.round(Math.sin(Math.PI * phase) * verticalArc + verticalNoise);
|
|
861
|
+
let wait = index === 1
|
|
862
|
+
? 72 + Math.floor(Math.random() * 115)
|
|
863
|
+
: Math.max(6, Math.round(cadence + (Math.random() - 0.5) * 5));
|
|
864
|
+
if (index > 2 && index < pointCount - 2 && Math.random() < 0.055) {
|
|
865
|
+
wait += 8 * (1 + Math.floor(Math.random() * 3));
|
|
866
|
+
}
|
|
867
|
+
await delay(wait);
|
|
868
|
+
mouse('mousemove', [start[0] + x, start[1] + y], rawDocument);
|
|
869
|
+
lastX = x;
|
|
870
|
+
lastY = y;
|
|
871
|
+
elapsed += wait;
|
|
872
|
+
}
|
|
873
|
+
await delay(22 + Math.floor(Math.random() * 34));
|
|
874
|
+
const end = [start[0] + travel, start[1] + lastY];
|
|
875
|
+
// The slider follows the pointer, so release over the slider and let the
|
|
876
|
+
// event bubble to the document.onmouseup handler installed by JCAP.
|
|
877
|
+
mouse('mouseup', end, slider);
|
|
878
|
+
console.log('[captcha.interaction]', JSON.stringify({
|
|
879
|
+
targetNatural: Number(offset).toFixed(2),
|
|
880
|
+
targetDisplay: travel.toFixed(2),
|
|
881
|
+
sliderLeft: String(slider.style.left || ''),
|
|
882
|
+
slotTransform: String(slot.style.transform || slot.style.webkitTransform || ''),
|
|
883
|
+
mainWidth: Number(main.width || 0),
|
|
884
|
+
slotWidth: Number(slot.width || 0),
|
|
885
|
+
points: pointCount + 1,
|
|
886
|
+
elapsed,
|
|
887
|
+
overshoot,
|
|
888
|
+
}));
|
|
889
|
+
return true;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
async function dispatchClick(point, imageSize) {
|
|
893
|
+
const image = rawDocument.querySelector('#cpc_img');
|
|
894
|
+
const overlay = rawDocument.querySelector('.cpc-img-overlay');
|
|
895
|
+
if (!image || !overlay || !imageSize) return false;
|
|
896
|
+
const displayWidth = 290;
|
|
897
|
+
const displayHeight = imageSize.height * displayWidth / imageSize.width;
|
|
898
|
+
const box = { left: 367, top: 240, width: displayWidth, height: displayHeight };
|
|
899
|
+
setElementBox(image, box);
|
|
900
|
+
setElementBox(overlay, box);
|
|
901
|
+
const local = [Number(point[0]) * displayWidth / imageSize.width,
|
|
902
|
+
Number(point[1]) * displayHeight / imageSize.height];
|
|
903
|
+
const absolute = [box.left + local[0], box.top + local[1]];
|
|
904
|
+
const mouse = (type, buttons) => {
|
|
905
|
+
const event = new rawWindow.MouseEvent(type, {
|
|
906
|
+
bubbles: true, cancelable: true,
|
|
907
|
+
clientX: absolute[0], clientY: absolute[1],
|
|
908
|
+
screenX: absolute[0], screenY: absolute[1],
|
|
909
|
+
button: 0, buttons,
|
|
910
|
+
});
|
|
911
|
+
for (const [key, value] of [['offsetX', local[0]], ['offsetY', local[1]],
|
|
912
|
+
['pageX', absolute[0]], ['pageY', absolute[1]]]) {
|
|
913
|
+
try { Object.defineProperty(event, key, { configurable: true, value }); } catch (_) {}
|
|
914
|
+
}
|
|
915
|
+
overlay.dispatchEvent(event);
|
|
916
|
+
};
|
|
917
|
+
mouse('mousedown', 1);
|
|
918
|
+
await delay(55 + Math.floor(Math.random() * 30));
|
|
919
|
+
mouse('mouseup', 0);
|
|
920
|
+
await delay(20);
|
|
921
|
+
mouse('click', 0);
|
|
922
|
+
return true;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
function solveImageLocally(tp, node, auxiliaryNode) {
|
|
926
|
+
const src = node && String(node.getAttribute('src') || '');
|
|
927
|
+
const match = /^data:image\/(png|jpe?g|webp);base64,([A-Za-z0-9+/=]+)$/.exec(src);
|
|
928
|
+
if (!match) return null;
|
|
929
|
+
const solverPath = _path.resolve(__dirname, '..', 'run', 'captcha_solver.py');
|
|
930
|
+
const args = [solverPath, '--tp', String(tp)];
|
|
931
|
+
if (runtimeInput.solverModel) args.push('--model', String(runtimeInput.solverModel));
|
|
932
|
+
if (runtimeInput.orientationModel) {
|
|
933
|
+
args.push('--orientation-model', String(runtimeInput.orientationModel));
|
|
934
|
+
}
|
|
935
|
+
if (auxiliaryNode) {
|
|
936
|
+
const tipSrc = String(auxiliaryNode.getAttribute('src') || '');
|
|
937
|
+
const tipMatch = /^data:image\/(png|jpe?g|webp);base64,([A-Za-z0-9+/=]+)$/.exec(tipSrc);
|
|
938
|
+
if (tipMatch) args.push(tp === 30 ? '--slot-base64' : '--tip-base64', tipMatch[2]);
|
|
939
|
+
}
|
|
940
|
+
if (runtimeInput.dumpDir) {
|
|
941
|
+
const dumpDir = _path.resolve(String(runtimeInput.dumpDir));
|
|
942
|
+
_fs.mkdirSync(dumpDir, { recursive: true });
|
|
943
|
+
args.push('--debug-output', _path.join(
|
|
944
|
+
dumpDir, `solver-${tp}-${runtimeState.solveAttempts}.png`));
|
|
945
|
+
}
|
|
946
|
+
const result = _childProcess.spawnSync(
|
|
947
|
+
String(runtimeInput.pythonExecutable || 'python'), args, {
|
|
948
|
+
input: _Buffer.from(match[2], 'base64'),
|
|
949
|
+
encoding: 'utf8',
|
|
950
|
+
timeout: 25000,
|
|
951
|
+
windowsHide: true,
|
|
952
|
+
maxBuffer: 1024 * 1024,
|
|
953
|
+
});
|
|
954
|
+
if (result.error || result.status !== 0) {
|
|
955
|
+
console.error('[captcha.solver-process]', JSON.stringify({
|
|
956
|
+
status: result.status,
|
|
957
|
+
error: result.error && result.error.message,
|
|
958
|
+
stderr: String(result.stderr || '').slice(0, 300),
|
|
959
|
+
}));
|
|
960
|
+
return null;
|
|
961
|
+
}
|
|
962
|
+
try {
|
|
963
|
+
return JSON.parse(String(result.stdout || '').trim());
|
|
964
|
+
} catch (error) {
|
|
965
|
+
console.error('[captcha.solver-json]', error && error.message);
|
|
966
|
+
return null;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
async function autoSolveChallenge() {
|
|
971
|
+
const response = runtimeState.latestChallenge || runtimeState.latestCheck || {};
|
|
972
|
+
const tp = Number(response.tp);
|
|
973
|
+
if (!runtimeInput.autoSolve || !Number.isFinite(tp) || runtimeState.completed
|
|
974
|
+
|| runtimeState.solving || runtimeState.solveAttempts >= MAX_SOLVE_ATTEMPTS) return;
|
|
975
|
+
const challengeKey = `${tp}:${String(response.img || '').length}:${String(response.img || '').slice(-24)}`;
|
|
976
|
+
if (challengeKey === runtimeState.lastSolvedChallenge) return;
|
|
977
|
+
runtimeState.lastSolvedChallenge = challengeKey;
|
|
978
|
+
runtimeState.solving = true;
|
|
979
|
+
runtimeState.solveAttempts++;
|
|
980
|
+
try {
|
|
981
|
+
if (tp === 2) {
|
|
982
|
+
const image = rawDocument.querySelector('#cpc_img');
|
|
983
|
+
const tip = rawDocument.querySelector('.tip_pic');
|
|
984
|
+
const size = decodeImageSize(image && image.getAttribute('src'));
|
|
985
|
+
if (!size) return;
|
|
986
|
+
const solution = solveImageLocally(tp, image, tip);
|
|
987
|
+
if (!solution) return;
|
|
988
|
+
console.log('[captcha.solve]', JSON.stringify({
|
|
989
|
+
tp, solver: String(solution.solver || 'u2net-masked-template'),
|
|
990
|
+
attempt: runtimeState.solveAttempts, retry: Boolean(solution.retry),
|
|
991
|
+
x: Number(solution.x || 0).toFixed(2),
|
|
992
|
+
y: Number(solution.y || 0).toFixed(2),
|
|
993
|
+
score: Number(solution.score || 0).toFixed(4),
|
|
994
|
+
reason: String(solution.reason || ''),
|
|
995
|
+
}));
|
|
996
|
+
if (solution.retry) {
|
|
997
|
+
const refresh = rawDocument.querySelector('#refreshPng, .jcap_refresh');
|
|
998
|
+
if (refresh) refresh.dispatchEvent(new rawWindow.MouseEvent('click', {
|
|
999
|
+
bubbles: true, cancelable: true,
|
|
1000
|
+
}));
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
await dispatchClick([solution.x, solution.y], size);
|
|
1004
|
+
} else if (tp === 3) {
|
|
1005
|
+
const image = rawDocument.querySelector('#cpc_img');
|
|
1006
|
+
const size = decodeImageSize(image && image.getAttribute('src'));
|
|
1007
|
+
if (!size) return;
|
|
1008
|
+
const solution = solveImageLocally(tp, image);
|
|
1009
|
+
if (!solution) return;
|
|
1010
|
+
if (solution.retry) {
|
|
1011
|
+
console.log('[captcha.solve]', JSON.stringify({
|
|
1012
|
+
tp, solver: 'local-u2net-skeleton', attempt: runtimeState.solveAttempts,
|
|
1013
|
+
retry: true, reason: String(solution.reason || ''),
|
|
1014
|
+
score: Number(solution.score || 0).toFixed(2),
|
|
1015
|
+
}));
|
|
1016
|
+
const refresh = rawDocument.querySelector('#refreshPng, .jcap_refresh');
|
|
1017
|
+
if (refresh) refresh.dispatchEvent(new rawWindow.MouseEvent('click', {
|
|
1018
|
+
bubbles: true, cancelable: true,
|
|
1019
|
+
}));
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
if (!Array.isArray(solution.points) || solution.points.length < 2) return;
|
|
1023
|
+
console.log('[captcha.solve]', JSON.stringify({
|
|
1024
|
+
tp, solver: String(solution.solver || 'local-u2net-skeleton'),
|
|
1025
|
+
attempt: runtimeState.solveAttempts,
|
|
1026
|
+
points: solution.points.length, score: Number(solution.score || 0).toFixed(2),
|
|
1027
|
+
}));
|
|
1028
|
+
await dispatchTrace(solution.points, size);
|
|
1029
|
+
} else if (tp === 26) {
|
|
1030
|
+
const image = rawDocument.querySelector('.slot-content img');
|
|
1031
|
+
const solution = solveImageLocally(tp, image);
|
|
1032
|
+
if (!solution || !Number.isFinite(Number(solution.angle))) return;
|
|
1033
|
+
console.log('[captcha.solve]', JSON.stringify({
|
|
1034
|
+
tp, solver: String(solution.solver || 'orientation-classifier-axis'),
|
|
1035
|
+
attempt: runtimeState.solveAttempts,
|
|
1036
|
+
angle: Number(solution.angle).toFixed(2),
|
|
1037
|
+
score: Number(solution.score || 0).toFixed(4),
|
|
1038
|
+
axisStrength: Number(solution.axisStrength || 0).toFixed(4),
|
|
1039
|
+
}));
|
|
1040
|
+
await dispatchRotation(Number(solution.angle));
|
|
1041
|
+
} else if (tp === 30) {
|
|
1042
|
+
const main = rawDocument.querySelector('#main_img');
|
|
1043
|
+
const slot = rawDocument.querySelector('#slot_img');
|
|
1044
|
+
const mainSize = decodeImageSize(main && main.getAttribute('src'));
|
|
1045
|
+
const slotSize = decodeImageSize(slot && slot.getAttribute('src'));
|
|
1046
|
+
if (!mainSize || !slotSize) return;
|
|
1047
|
+
const solution = solveImageLocally(tp, main, slot);
|
|
1048
|
+
if (!solution || !Number.isFinite(Number(solution.offset))) return;
|
|
1049
|
+
const sliderBiases = Array.isArray(runtimeInput.sliderBiases)
|
|
1050
|
+
? runtimeInput.sliderBiases : [0];
|
|
1051
|
+
const sliderBias = Number(sliderBiases[
|
|
1052
|
+
Math.min(runtimeState.solveAttempts - 1, sliderBiases.length - 1)
|
|
1053
|
+
] || 0);
|
|
1054
|
+
console.log('[captcha.solve]', JSON.stringify({
|
|
1055
|
+
tp, solver: String(solution.solver || 'slider-edge-shape'),
|
|
1056
|
+
attempt: runtimeState.solveAttempts,
|
|
1057
|
+
offset: Number(solution.offset).toFixed(2),
|
|
1058
|
+
bias: sliderBias.toFixed(2),
|
|
1059
|
+
score: Number(solution.score || 0).toFixed(4),
|
|
1060
|
+
margin: Number(solution.margin || 0).toFixed(4),
|
|
1061
|
+
correlation: Number(solution.correlation || 0).toFixed(4),
|
|
1062
|
+
}));
|
|
1063
|
+
if (solution.retry) {
|
|
1064
|
+
const refresh = rawDocument.querySelector('#refreshPng, .jcap_refresh');
|
|
1065
|
+
if (refresh) refresh.dispatchEvent(new rawWindow.MouseEvent('click', {
|
|
1066
|
+
bubbles: true, cancelable: true,
|
|
1067
|
+
}));
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
await dispatchSlider(Number(solution.offset) + sliderBias, mainSize, slotSize);
|
|
1071
|
+
} else {
|
|
1072
|
+
console.log('[captcha.solve]', JSON.stringify({
|
|
1073
|
+
tp, solver: 'unsupported-refresh', attempt: runtimeState.solveAttempts,
|
|
1074
|
+
}));
|
|
1075
|
+
const refresh = rawDocument.querySelector('#refreshPng, .jcap_refresh');
|
|
1076
|
+
if (refresh) refresh.dispatchEvent(new rawWindow.MouseEvent('click', {
|
|
1077
|
+
bubbles: true, cancelable: true,
|
|
1078
|
+
}));
|
|
1079
|
+
}
|
|
1080
|
+
} finally {
|
|
1081
|
+
runtimeState.solving = false;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
// Keep the realm-sensitive intrinsic constructors unwrapped. Emscripten's
|
|
1085
|
+
// emval layer caches globalThis.Array during module initialization and later
|
|
1086
|
+
// uses instanceof before converting JS arrays into std::vector values.
|
|
1087
|
+
const fakeWindow = windowTarget;
|
|
1088
|
+
windowTarget.window = fakeWindow;
|
|
1089
|
+
windowTarget.self = fakeWindow;
|
|
1090
|
+
windowTarget.top = fakeWindow;
|
|
1091
|
+
windowTarget.parent = fakeWindow;
|
|
1092
|
+
windowTarget.globalThis = fakeWindow;
|
|
1093
|
+
const fakeDocument = env.createProxy(rawDocument, 'document', 0);
|
|
1094
|
+
const fakeNavigator = env.createProxy(rawNavigator, 'navigator', 0);
|
|
1095
|
+
const fakeLocation = env.createProxy(rawLocation, 'location', 0);
|
|
1096
|
+
env.init({
|
|
1097
|
+
window: fakeWindow,
|
|
1098
|
+
document: fakeDocument,
|
|
1099
|
+
navigator: fakeNavigator,
|
|
1100
|
+
location: fakeLocation,
|
|
1101
|
+
});
|
|
1102
|
+
// The event recorder writes through bare `localStorage.setItem(...)` while
|
|
1103
|
+
// its reader uses `window.localStorage`. Browsers resolve both names to the
|
|
1104
|
+
// same Storage object; expose that binding explicitly in Node as well.
|
|
1105
|
+
Object.defineProperty(global, 'localStorage', {
|
|
1106
|
+
value: rawLocalStorage, writable: true, configurable: true,
|
|
1107
|
+
});
|
|
1108
|
+
Object.defineProperty(global, 'XMLHttpRequest', {
|
|
1109
|
+
value: CaptureXHR, writable: true, configurable: true,
|
|
1110
|
+
});
|
|
1111
|
+
Object.defineProperty(global, 'screen', {
|
|
1112
|
+
value: rawWindow.screen, writable: true, configurable: true,
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1115
|
+
function reportEnvironment() {
|
|
1116
|
+
const report = env.report();
|
|
1117
|
+
const errors = Array.isArray(report && report.errors) ? report.errors : [];
|
|
1118
|
+
const missing = Object.entries(report && report.undefined || {})
|
|
1119
|
+
.sort((left, right) => Number(right[1]) - Number(left[1]));
|
|
1120
|
+
console.log('[env.summary]', JSON.stringify({
|
|
1121
|
+
errorCount: errors.length,
|
|
1122
|
+
undefinedCount: missing.length,
|
|
1123
|
+
errorPaths: errors.slice(0, 40).map(item => ({
|
|
1124
|
+
path: String(item && item.path || ''),
|
|
1125
|
+
operation: String(item && item.operation || ''),
|
|
1126
|
+
})),
|
|
1127
|
+
undefinedPaths: missing.slice(0, 80).map(([path, count]) => ({
|
|
1128
|
+
path: String(path), count: Number(count),
|
|
1129
|
+
})),
|
|
1130
|
+
}));
|
|
1131
|
+
return report;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
_process.on('uncaughtException', error => {
|
|
1135
|
+
console.error('[uncaught]', error && error.stack || error);
|
|
1136
|
+
reportEnvironment();
|
|
1137
|
+
_process.exitCode = 1;
|
|
1138
|
+
});
|
|
1139
|
+
_process.on('unhandledRejection', error => {
|
|
1140
|
+
console.error('[rejection]', error && error.stack || error);
|
|
1141
|
+
reportEnvironment();
|
|
1142
|
+
_process.exitCode = 1;
|
|
1143
|
+
});
|
|
1144
|
+
|
|
1145
|
+
let exported;
|
|
1146
|
+
try {
|
|
1147
|
+
// Keep the vendor bundle byte-for-byte identical to the current JCAP CDN
|
|
1148
|
+
// asset. Its check payload contains stack/file-path evidence, so execute
|
|
1149
|
+
// it with the production URL as the VM filename as well. A normal Node
|
|
1150
|
+
// require() leaks the local drive path through Error.stack even when the
|
|
1151
|
+
// source bytes themselves are untouched.
|
|
1152
|
+
const vendorPath = _path.resolve(__dirname, '..', 'run', 'jcap_ujb96b.js');
|
|
1153
|
+
const vendorSource = _fs.readFileSync(vendorPath, 'utf8');
|
|
1154
|
+
_vm.runInThisContext(vendorSource, { filename: JCAP_SCRIPT_URL });
|
|
1155
|
+
exported = windowTarget.jdCAP || global.jdCAP;
|
|
1156
|
+
Object.defineProperty(global, 'Buffer', {
|
|
1157
|
+
value: _Buffer, writable: true, configurable: true,
|
|
1158
|
+
});
|
|
1159
|
+
Object.defineProperty(global, 'process', {
|
|
1160
|
+
value: _process, writable: true, configurable: true,
|
|
1161
|
+
});
|
|
1162
|
+
Object.defineProperty(global, 'globalThis', {
|
|
1163
|
+
value: global, writable: true, configurable: true,
|
|
1164
|
+
});
|
|
1165
|
+
windowTarget.Array = _Array;
|
|
1166
|
+
console.log('[runtime.profile]', {
|
|
1167
|
+
screen: `${rawWindow.screen.width}x${rawWindow.screen.height}`,
|
|
1168
|
+
available: `${rawWindow.screen.availWidth}x${rawWindow.screen.availHeight}`,
|
|
1169
|
+
viewport: `${rawWindow.innerWidth}x${rawWindow.innerHeight}`,
|
|
1170
|
+
platform: rawNavigator.platform,
|
|
1171
|
+
webdriver: rawNavigator.webdriver,
|
|
1172
|
+
hardwareConcurrency: rawNavigator.hardwareConcurrency,
|
|
1173
|
+
deviceMemory: rawNavigator.deviceMemory,
|
|
1174
|
+
maxTouchPoints: rawNavigator.maxTouchPoints,
|
|
1175
|
+
});
|
|
1176
|
+
console.log('[realm]', {
|
|
1177
|
+
sameArray: _Array === windowTarget.Array,
|
|
1178
|
+
literalInstance: [] instanceof windowTarget.Array,
|
|
1179
|
+
});
|
|
1180
|
+
console.log('[export]', {
|
|
1181
|
+
commonjs: exported && Object.keys(exported),
|
|
1182
|
+
windowJdCAP: typeof window.jdCAP,
|
|
1183
|
+
globalJdCAP: typeof global.jdCAP,
|
|
1184
|
+
globalCaptcha: typeof global.captcha,
|
|
1185
|
+
});
|
|
1186
|
+
} catch (error) {
|
|
1187
|
+
console.error('[load-error]', error && error.stack || error);
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
if (exported && typeof exported.captcha === 'function') {
|
|
1191
|
+
try {
|
|
1192
|
+
const info = {
|
|
1193
|
+
appType: 3,
|
|
1194
|
+
tdat_version: 99992,
|
|
1195
|
+
host: 'jcap.m.jd.com',
|
|
1196
|
+
tdat_ctx: 'A8RpzPvMpQPEEPZC737Wbcqo4er3yvFj4ubEUKiJqqus7qCcnO2epKHwrvn5qfin9_murquvurqxtb0DAwe3Bgutzs_QE78RyxfGFsXL0soaHBwd0dPT28_U1djaK9bY3tje2yzy8vP0oKan',
|
|
1197
|
+
cs: 1,
|
|
1198
|
+
};
|
|
1199
|
+
const option = {
|
|
1200
|
+
sessionId: String(runtimeInput.sessionId || '0'.repeat(91)),
|
|
1201
|
+
language: '1',
|
|
1202
|
+
autoClose: '0',
|
|
1203
|
+
onSuccess: result => {
|
|
1204
|
+
const vt = String(result && result.vt || '');
|
|
1205
|
+
runtimeState.completed = Boolean(vt);
|
|
1206
|
+
console.log('[success]', Boolean(vt));
|
|
1207
|
+
if (vt) {
|
|
1208
|
+
emitResult({ ok: true, vt });
|
|
1209
|
+
if (runtimeInput.returnResult) setTimeout(() => _process.exit(0), 50);
|
|
1210
|
+
}
|
|
1211
|
+
},
|
|
1212
|
+
onFailure: error => console.log('[failure]', error && error.code,
|
|
1213
|
+
error && (error.message || error.msg)),
|
|
1214
|
+
onCancel: () => console.log('[cancel]'),
|
|
1215
|
+
onLoad: value => {
|
|
1216
|
+
console.log('[onLoad]', typeof value);
|
|
1217
|
+
setTimeout(() => {
|
|
1218
|
+
const root = rawDocument.querySelector('#captcha_dom');
|
|
1219
|
+
const elements = root ? Array.from(root.querySelectorAll('*')) : [];
|
|
1220
|
+
const elementSummary = elements.slice(0, 80).map(node => ({
|
|
1221
|
+
tag: String(node.tagName || '').toLowerCase(),
|
|
1222
|
+
id: String(node.id || ''),
|
|
1223
|
+
className: typeof node.className === 'string' ? node.className : '',
|
|
1224
|
+
}));
|
|
1225
|
+
const imageSummary = elements.filter(node =>
|
|
1226
|
+
String(node.tagName || '').toLowerCase() === 'img').map(node => ({
|
|
1227
|
+
id: String(node.id || ''),
|
|
1228
|
+
className: typeof node.className === 'string' ? node.className : '',
|
|
1229
|
+
srcLength: String(node.getAttribute('src') || '').length,
|
|
1230
|
+
width: Number(node.width || 0),
|
|
1231
|
+
height: Number(node.height || 0),
|
|
1232
|
+
}));
|
|
1233
|
+
const response = runtimeState.latestChallenge || runtimeState.latestCheck || {};
|
|
1234
|
+
const textSummary = elements.filter(node => {
|
|
1235
|
+
const className = typeof node.className === 'string' ? node.className : '';
|
|
1236
|
+
return /(^|\s)(tip_text|local_tip)(\s|$)/.test(className);
|
|
1237
|
+
}).map(node => String(node.textContent || '').trim()).filter(Boolean);
|
|
1238
|
+
if (runtimeInput.dumpDir) {
|
|
1239
|
+
const dumpDir = _path.resolve(String(runtimeInput.dumpDir));
|
|
1240
|
+
_fs.mkdirSync(dumpDir, { recursive: true });
|
|
1241
|
+
const challengeNode = rawDocument.querySelector(
|
|
1242
|
+
'#cpc_img, #main_img, #curve_main_img, .slot-content img, .img-box');
|
|
1243
|
+
const typeSuffix = Number.isFinite(Number(response.tp))
|
|
1244
|
+
? `-${Number(response.tp)}` : '';
|
|
1245
|
+
const dumps = [
|
|
1246
|
+
[`challenge${typeSuffix}`, challengeNode],
|
|
1247
|
+
[`main${typeSuffix}`, rawDocument.querySelector('#main_img')],
|
|
1248
|
+
[`slot${typeSuffix}`, rawDocument.querySelector('#slot_img')],
|
|
1249
|
+
[`tip${typeSuffix}`, rawDocument.querySelector('.tip_pic')],
|
|
1250
|
+
];
|
|
1251
|
+
for (const [name, node] of dumps) {
|
|
1252
|
+
const src = node && String(node.getAttribute('src') || '');
|
|
1253
|
+
const match = /^data:image\/(png|jpe?g|webp);base64,([A-Za-z0-9+/=]+)$/.exec(src);
|
|
1254
|
+
if (!match) continue;
|
|
1255
|
+
const extension = match[1] === 'jpeg' ? 'jpg' : match[1];
|
|
1256
|
+
let target = _path.join(dumpDir, `${name}.${extension}`);
|
|
1257
|
+
for (let index = 2; _fs.existsSync(target); index++) {
|
|
1258
|
+
target = _path.join(dumpDir, `${name}-${index}.${extension}`);
|
|
1259
|
+
}
|
|
1260
|
+
_fs.writeFileSync(target, _Buffer.from(match[2], 'base64'));
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
console.log('[captcha.dom]', JSON.stringify({
|
|
1264
|
+
root: Boolean(root),
|
|
1265
|
+
elements: elementSummary,
|
|
1266
|
+
images: imageSummary,
|
|
1267
|
+
texts: textSummary,
|
|
1268
|
+
}));
|
|
1269
|
+
console.log('[captcha.challenge]', JSON.stringify({
|
|
1270
|
+
code: response.code,
|
|
1271
|
+
tp: response.tp,
|
|
1272
|
+
imgType: typeof response.img,
|
|
1273
|
+
imgLength: typeof response.img === 'string' ? response.img.length : 0,
|
|
1274
|
+
fields: Object.keys(response).sort(),
|
|
1275
|
+
}));
|
|
1276
|
+
autoSolveChallenge().catch(error => console.error('[captcha.solve-error]',
|
|
1277
|
+
error && error.stack || error));
|
|
1278
|
+
}, 150);
|
|
1279
|
+
},
|
|
1280
|
+
};
|
|
1281
|
+
// Passport 的短信验证会传 account;risk_h5 的通用处置页只传 sessionId。
|
|
1282
|
+
// 不要给后者伪造手机号,否则 JCAP 会话契约与 createSid 来源不一致。
|
|
1283
|
+
if (runtimeInput.account) option.account = String(runtimeInput.account);
|
|
1284
|
+
const factory = exported.captcha(info);
|
|
1285
|
+
console.log('[factory]', typeof factory);
|
|
1286
|
+
Promise.resolve(factory(option)).then(instance => {
|
|
1287
|
+
console.log('[instance]', typeof instance,
|
|
1288
|
+
instance && Object.keys(instance));
|
|
1289
|
+
if (instance && typeof instance.create === 'function') {
|
|
1290
|
+
// The official risk page stores `option` in the factory call
|
|
1291
|
+
// and invokes create() with no action argument. Passing option
|
|
1292
|
+
// a second time changes the middleware contract and therefore
|
|
1293
|
+
// the encrypted device/check payload.
|
|
1294
|
+
instance.create();
|
|
1295
|
+
}
|
|
1296
|
+
}).catch(error => console.error('[factory-error]', error && error.stack || error));
|
|
1297
|
+
} catch (error) {
|
|
1298
|
+
console.error('[invoke-error]', error && error.stack || error);
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
setTimeout(() => {
|
|
1303
|
+
if (!runtimeState.completed) emitResult({ ok: false, error: 'timeout' });
|
|
1304
|
+
reportEnvironment();
|
|
1305
|
+
dom.window.close();
|
|
1306
|
+
_process.exit(_process.exitCode || 0);
|
|
1307
|
+
}, Number(runtimeInput.runTimeoutMs || (runtimeInput.autoSolve ? 45000 : 3000)));
|