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,455 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Pure TikTok Web signing bridge.
|
|
3
|
+
*
|
|
4
|
+
* The downloaded SDK under source/ is never edited. This process supplies a
|
|
5
|
+
* small browser-shaped environment, initializes the SDK, and captures the URL
|
|
6
|
+
* passed to the native fetch. No captured signature is accepted as input.
|
|
7
|
+
*/
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const vm = require('vm');
|
|
11
|
+
const readline = require('readline');
|
|
12
|
+
const env = require('./env_core');
|
|
13
|
+
const _process = process;
|
|
14
|
+
|
|
15
|
+
const nativeCrypto = require('crypto').webcrypto;
|
|
16
|
+
const nativeFetch = typeof global.fetch === 'function' ? global.fetch.bind(global) : null;
|
|
17
|
+
const sourcePath = path.join(__dirname, '../source/webmssdk.js');
|
|
18
|
+
const legacySourcePath = path.join(__dirname, '../source/webmssdk_legacy.js');
|
|
19
|
+
const exPath = path.join(__dirname, '../source/webmssdk_ex.js');
|
|
20
|
+
const cachePath = path.join(__dirname, '../sdk_config.json');
|
|
21
|
+
const defaultConfig = fs.existsSync(cachePath)
|
|
22
|
+
? JSON.parse(fs.readFileSync(cachePath, 'utf8'))
|
|
23
|
+
: {
|
|
24
|
+
aid: 1988, dfp: false, boe: false, intercept: true,
|
|
25
|
+
enablePathList: ['/webcast.*', '/api/post/item_list/', '/api/comment/list/'],
|
|
26
|
+
region: 'sg-tiktok', apiHost: '', mode: 513, isSDK: false,
|
|
27
|
+
custom: { ttwid: '' },
|
|
28
|
+
};
|
|
29
|
+
// Creator Studio project-post paths are signed by WebMssdk. Upload
|
|
30
|
+
// authorization is a separate unsigned GET and is deliberately not enabled
|
|
31
|
+
// here; adding signatures to it would diverge from Chrome's request.
|
|
32
|
+
const signingPaths = [
|
|
33
|
+
...(defaultConfig.enablePathList || ['/webcast.*']),
|
|
34
|
+
'/tiktok/web/project/post/v1/',
|
|
35
|
+
'/tiktok/web/project/post_retry/v1/',
|
|
36
|
+
];
|
|
37
|
+
const runtimeConfig = { ...defaultConfig, enablePathList: signingPaths };
|
|
38
|
+
|
|
39
|
+
function makeStorage(initial = {}) {
|
|
40
|
+
const values = new Map();
|
|
41
|
+
for (const [key, value] of Object.entries(initial || {})) values.set(String(key), String(value));
|
|
42
|
+
return {
|
|
43
|
+
get length() { return values.size; },
|
|
44
|
+
key(i) { return Array.from(values.keys())[i] ?? null; },
|
|
45
|
+
getItem(k) { return values.has(String(k)) ? values.get(String(k)) : null; },
|
|
46
|
+
setItem(k, v) { values.set(String(k), String(v)); },
|
|
47
|
+
removeItem(k) { values.delete(String(k)); },
|
|
48
|
+
clear() { values.clear(); },
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function buildRuntime(input) {
|
|
53
|
+
const listeners = new Map();
|
|
54
|
+
const referer = String(input.referer || 'https://www.tiktok.com/');
|
|
55
|
+
const requestUrl = new URL(String(input.url));
|
|
56
|
+
const metrics = input.metrics || {};
|
|
57
|
+
const screenWidth = Number(metrics.screen_width || 2560);
|
|
58
|
+
const screenHeight = Number(metrics.screen_height || 1440);
|
|
59
|
+
const availWidth = Number(metrics.avail_width || screenWidth);
|
|
60
|
+
const availHeight = Number(metrics.avail_height || 1392);
|
|
61
|
+
const innerWidth = Number(metrics.inner_width || 1297);
|
|
62
|
+
const innerHeight = Number(metrics.inner_height || 800);
|
|
63
|
+
const outerWidth = Number(metrics.outer_width || 1313);
|
|
64
|
+
const outerHeight = Number(metrics.outer_height || 985);
|
|
65
|
+
const webglExtensions = [
|
|
66
|
+
'ANGLE_instanced_arrays', 'EXT_blend_minmax', 'EXT_clip_control',
|
|
67
|
+
'EXT_color_buffer_half_float', 'EXT_depth_clamp', 'EXT_disjoint_timer_query',
|
|
68
|
+
'EXT_float_blend', 'EXT_frag_depth', 'EXT_polygon_offset_clamp',
|
|
69
|
+
'EXT_shader_texture_lod', 'EXT_texture_compression_bptc',
|
|
70
|
+
'EXT_texture_compression_rgtc', 'EXT_texture_filter_anisotropic',
|
|
71
|
+
'EXT_texture_mirror_clamp_to_edge', 'EXT_sRGB', 'KHR_parallel_shader_compile',
|
|
72
|
+
'OES_element_index_uint', 'OES_fbo_render_mipmap', 'OES_standard_derivatives',
|
|
73
|
+
'OES_texture_float', 'OES_texture_float_linear', 'OES_texture_half_float',
|
|
74
|
+
'OES_texture_half_float_linear', 'OES_vertex_array_object',
|
|
75
|
+
'WEBGL_blend_func_extended', 'WEBGL_color_buffer_float',
|
|
76
|
+
'WEBGL_compressed_texture_s3tc', 'WEBGL_compressed_texture_s3tc_srgb',
|
|
77
|
+
'WEBGL_debug_renderer_info', 'WEBGL_debug_shaders', 'WEBGL_depth_texture',
|
|
78
|
+
'WEBGL_draw_buffers', 'WEBGL_lose_context', 'WEBGL_multi_draw', 'WEBGL_polygon_mode',
|
|
79
|
+
];
|
|
80
|
+
const webglValues = new Map([
|
|
81
|
+
[0x0D52, 8], [0x0D56, 24], [0x0D53, 8], [0x8B4D, 32], [0x851C, 16384],
|
|
82
|
+
[0x8DFD, 1024], [0x84E8, 16384], [0x8872, 16], [0x0D33, 16384],
|
|
83
|
+
[0x8DFC, 30], [0x8869, 16], [0x8B4C, 16], [0x8DFB, 4095],
|
|
84
|
+
[0x8B8C, 'WebGL GLSL ES 1.0 (OpenGL ES GLSL ES 1.0 Chromium)'],
|
|
85
|
+
[0x0D57, 0], [0x1F02, 'WebGL 1.0 (OpenGL ES 2.0 Chromium)'],
|
|
86
|
+
[0x9245, 'Google Inc. (NVIDIA)'],
|
|
87
|
+
[0x9246, 'ANGLE (NVIDIA, NVIDIA GeForce RTX 5060 Ti (0x00002D04) Direct3D11 vs_5_0 ps_5_0, D3D11)'],
|
|
88
|
+
[0x84FF, 16],
|
|
89
|
+
]);
|
|
90
|
+
const webglDebugInfo = { UNMASKED_VENDOR_WEBGL: 0x9245, UNMASKED_RENDERER_WEBGL: 0x9246 };
|
|
91
|
+
const anisotropyInfo = { MAX_TEXTURE_MAX_ANISOTROPY_EXT: 0x84FF };
|
|
92
|
+
const fakePerformance = {
|
|
93
|
+
timeOrigin: Date.now() - 1000,
|
|
94
|
+
now: () => Date.now() - (Date.now() - 1000),
|
|
95
|
+
};
|
|
96
|
+
fakePerformance.timing = {
|
|
97
|
+
navigationStart: fakePerformance.timeOrigin,
|
|
98
|
+
unloadEventStart: 0, unloadEventEnd: 0,
|
|
99
|
+
redirectStart: 0, redirectEnd: 0,
|
|
100
|
+
fetchStart: fakePerformance.timeOrigin + 1,
|
|
101
|
+
domainLookupStart: fakePerformance.timeOrigin + 2,
|
|
102
|
+
domainLookupEnd: fakePerformance.timeOrigin + 3,
|
|
103
|
+
connectStart: fakePerformance.timeOrigin + 3,
|
|
104
|
+
secureConnectionStart: fakePerformance.timeOrigin + 4,
|
|
105
|
+
connectEnd: fakePerformance.timeOrigin + 5,
|
|
106
|
+
requestStart: fakePerformance.timeOrigin + 6,
|
|
107
|
+
responseStart: fakePerformance.timeOrigin + 7,
|
|
108
|
+
responseEnd: fakePerformance.timeOrigin + 8,
|
|
109
|
+
domLoading: fakePerformance.timeOrigin + 9,
|
|
110
|
+
domInteractive: fakePerformance.timeOrigin + 10,
|
|
111
|
+
domContentLoadedEventStart: fakePerformance.timeOrigin + 11,
|
|
112
|
+
domContentLoadedEventEnd: fakePerformance.timeOrigin + 12,
|
|
113
|
+
domComplete: fakePerformance.timeOrigin + 13,
|
|
114
|
+
loadEventStart: fakePerformance.timeOrigin + 14,
|
|
115
|
+
loadEventEnd: fakePerformance.timeOrigin + 15,
|
|
116
|
+
};
|
|
117
|
+
fakePerformance.navigation = { type: 0, redirectCount: 0 };
|
|
118
|
+
fakePerformance.getEntriesByType = fakePerformance.getEntriesByType || (() => []);
|
|
119
|
+
fakePerformance.getEntriesByName = fakePerformance.getEntriesByName || (() => []);
|
|
120
|
+
const fakeWebGL = {
|
|
121
|
+
BLUE_BITS: 0x0D52, DEPTH_BITS: 0x0D56, GREEN_BITS: 0x0D53,
|
|
122
|
+
MAX_COMBINED_TEXTURE_IMAGE_UNITS: 0x8B4D, MAX_CUBE_MAP_TEXTURE_SIZE: 0x851C,
|
|
123
|
+
MAX_FRAGMENT_UNIFORM_VECTORS: 0x8DFD, MAX_RENDERBUFFER_SIZE: 0x84E8,
|
|
124
|
+
MAX_TEXTURE_IMAGE_UNITS: 0x8872, MAX_TEXTURE_SIZE: 0x0D33,
|
|
125
|
+
MAX_VARYING_VECTORS: 0x8DFC, MAX_VERTEX_ATTRIBS: 0x8869,
|
|
126
|
+
MAX_VERTEX_TEXTURE_IMAGE_UNITS: 0x8B4C, MAX_VERTEX_UNIFORM_VECTORS: 0x8DFB,
|
|
127
|
+
SHADING_LANGUAGE_VERSION: 0x8B8C, STENCIL_BITS: 0x0D57, VERSION: 0x1F02,
|
|
128
|
+
getSupportedExtensions() { return webglExtensions.slice(); },
|
|
129
|
+
getContextAttributes() { return { alpha: true, antialias: true, depth: true, desynchronized: false, failIfMajorPerformanceCaveat: false, powerPreference: 'default', premultipliedAlpha: true, preserveDrawingBuffer: false, stencil: false, xrCompatible: false }; },
|
|
130
|
+
getParameter(key) { return webglValues.get(key); },
|
|
131
|
+
getExtension(name) { if (name === 'WEBGL_debug_renderer_info') return webglDebugInfo; if (name === 'EXT_texture_filter_anisotropic') return anisotropyInfo; return {}; },
|
|
132
|
+
};
|
|
133
|
+
const userAgent = String(input.user_agent || input.userAgent ||
|
|
134
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' +
|
|
135
|
+
'(KHTML, like Gecko) Chrome/153.0.0.0 Safari/537.36');
|
|
136
|
+
const cookie = String(input.cookie || '');
|
|
137
|
+
const cookieTtwid = (cookie.match(/(?:^|;\s*)ttwid=([^;]*)/) || [])[1] || '';
|
|
138
|
+
const fakeDocument = {
|
|
139
|
+
cookie,
|
|
140
|
+
referrer: referer,
|
|
141
|
+
readyState: 'complete',
|
|
142
|
+
visibilityState: 'visible',
|
|
143
|
+
hidden: false,
|
|
144
|
+
documentElement: { dataset: {}, classList: { contains() { return false; } } },
|
|
145
|
+
head: { appendChild() {}, removeChild() {} },
|
|
146
|
+
body: { appendChild() {}, removeChild() {} },
|
|
147
|
+
createElement(tag) {
|
|
148
|
+
const element = {
|
|
149
|
+
tagName: String(tag).toUpperCase(), style: {}, dataset: {},
|
|
150
|
+
setAttribute() {}, getAttribute() { return null; },
|
|
151
|
+
appendChild() {}, remove() {}, addEventListener() {},
|
|
152
|
+
getContext(type) { return /webgl|experimental-webgl/i.test(String(type)) ? fakeWebGL : null; },
|
|
153
|
+
canPlayType() { return ''; },
|
|
154
|
+
pathname: '', protocol: '', host: '', hostname: '', port: '', search: '', hash: '', href: '',
|
|
155
|
+
};
|
|
156
|
+
return element;
|
|
157
|
+
},
|
|
158
|
+
addEventListener(type, fn) { listeners.set(type, fn); },
|
|
159
|
+
removeEventListener(type) { listeners.delete(type); },
|
|
160
|
+
dispatchEvent(evt) { const fn = listeners.get(evt?.type); if (fn) fn(evt); return true; },
|
|
161
|
+
evaluate() { return null; },
|
|
162
|
+
createEvent(type) {
|
|
163
|
+
const event = { type: String(type || ''), bubbles: false, cancelable: false,
|
|
164
|
+
initEvent(name, bubbles, cancelable) {
|
|
165
|
+
this.type = String(name || ''); this.bubbles = Boolean(bubbles); this.cancelable = Boolean(cancelable);
|
|
166
|
+
}, preventDefault() { this.defaultPrevented = true; }, defaultPrevented: false };
|
|
167
|
+
return event;
|
|
168
|
+
},
|
|
169
|
+
querySelector() { return null; },
|
|
170
|
+
};
|
|
171
|
+
const fakeNavigator = {
|
|
172
|
+
userAgent,
|
|
173
|
+
appCodeName: 'Mozilla', appVersion: userAgent,
|
|
174
|
+
language: String(input.language || 'zh-CN'), languages: ['zh-CN', 'zh', 'en'],
|
|
175
|
+
platform: 'Win32', onLine: true, cookieEnabled: true,
|
|
176
|
+
hardwareConcurrency: 20, deviceMemory: 32, maxTouchPoints: Number(metrics.max_touch_points ?? 10), webdriver: false,
|
|
177
|
+
doNotTrack: null,
|
|
178
|
+
permissions: { query: async () => ({ state: 'prompt', onchange: null }) },
|
|
179
|
+
userAgentData: {
|
|
180
|
+
brands: [
|
|
181
|
+
{ brand: 'Google Chrome', version: '153' },
|
|
182
|
+
{ brand: 'Not_A Brand', version: '8' },
|
|
183
|
+
{ brand: 'Chromium', version: '153' },
|
|
184
|
+
], mobile: false, platform: 'Windows',
|
|
185
|
+
getHighEntropyValues: async () => ({
|
|
186
|
+
platform: 'Windows',
|
|
187
|
+
platformVersion: String(input.platform_version || '19.0.0'),
|
|
188
|
+
architecture: 'x86', bitness: '64', model: '',
|
|
189
|
+
uaFullVersion: String(input.ua_full_version || '153.0.8010.50'),
|
|
190
|
+
fullVersionList: [
|
|
191
|
+
{ brand: 'Google Chrome', version: String(input.ua_full_version || '153.0.8010.50') },
|
|
192
|
+
{ brand: 'Not_A Brand', version: '8.0.0.0' },
|
|
193
|
+
{ brand: 'Chromium', version: String(input.ua_full_version || '153.0.8010.50') },
|
|
194
|
+
],
|
|
195
|
+
}),
|
|
196
|
+
},
|
|
197
|
+
plugins: [
|
|
198
|
+
{ name: 'PDF Viewer', filename: 'internal-pdf-viewer', description: 'Portable Document Format', length: 2 },
|
|
199
|
+
{ name: 'Chrome PDF Viewer', filename: 'internal-pdf-viewer', description: 'Portable Document Format', length: 2 },
|
|
200
|
+
{ name: 'Chromium PDF Viewer', filename: 'internal-pdf-viewer', description: 'Portable Document Format', length: 2 },
|
|
201
|
+
{ name: 'Microsoft Edge PDF Viewer', filename: 'internal-pdf-viewer', description: 'Portable Document Format', length: 2 },
|
|
202
|
+
{ name: 'WebKit built-in PDF', filename: 'internal-pdf-viewer', description: 'Portable Document Format', length: 2 },
|
|
203
|
+
],
|
|
204
|
+
mimeTypes: [
|
|
205
|
+
{ type: 'application/pdf', suffixes: 'pdf', description: 'Portable Document Format' },
|
|
206
|
+
{ type: 'text/pdf', suffixes: 'pdf', description: 'Portable Document Format' },
|
|
207
|
+
],
|
|
208
|
+
getBattery: async () => ({ charging: true, chargingTime: 0, dischargingTime: Infinity, level: 1 }),
|
|
209
|
+
connection: { effectiveType: '4g', rtt: 50, downlink: 10 },
|
|
210
|
+
};
|
|
211
|
+
const fakeLocation = {
|
|
212
|
+
href: referer, origin: requestUrl.origin, protocol: requestUrl.protocol,
|
|
213
|
+
host: requestUrl.host, hostname: requestUrl.hostname,
|
|
214
|
+
pathname: requestUrl.pathname, search: requestUrl.search, hash: '',
|
|
215
|
+
assign() {}, replace() {}, reload() {}, toString() { return this.href; },
|
|
216
|
+
};
|
|
217
|
+
class FakeXHR {
|
|
218
|
+
open(method, url) { this.method = method; this.url = String(url); this.readyState = 1; }
|
|
219
|
+
setRequestHeader() {}
|
|
220
|
+
addEventListener(type, fn) { this[`on${type}`] = fn; }
|
|
221
|
+
getAllResponseHeaders() { return ''; }
|
|
222
|
+
async send(body) {
|
|
223
|
+
if (nativeFetch && /https:\/\/(?:mssdk|mon)\-[^/]+\.tiktok\.com\//i.test(this.url)) {
|
|
224
|
+
try {
|
|
225
|
+
const response = await nativeFetch(this.url, { method: this.method || 'GET', body });
|
|
226
|
+
this.status = response.status;
|
|
227
|
+
this.responseText = await response.text();
|
|
228
|
+
this.readyState = 4;
|
|
229
|
+
this.onload?.(); this.onreadystatechange?.(); this.onloadend?.();
|
|
230
|
+
return;
|
|
231
|
+
} catch (_) {}
|
|
232
|
+
}
|
|
233
|
+
this.status = 200; this.responseText = '{}'; this.readyState = 4;
|
|
234
|
+
this.onload?.(); this.onreadystatechange?.(); this.onloadend?.();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
class FakeWebSocket { addEventListener() {} send() {} close() {} }
|
|
238
|
+
const fakeWindow = {
|
|
239
|
+
document: fakeDocument, navigator: fakeNavigator, location: fakeLocation,
|
|
240
|
+
screen: { width: screenWidth, height: screenHeight, availWidth, availHeight, colorDepth: 24, pixelDepth: 24 },
|
|
241
|
+
innerWidth, innerHeight, outerWidth, outerHeight,
|
|
242
|
+
devicePixelRatio: Number(metrics.device_pixel_ratio || 1), history: { length: Number(metrics.history_length || 7) },
|
|
243
|
+
localStorage: makeStorage(input.local_storage), sessionStorage: makeStorage(input.session_storage),
|
|
244
|
+
performance: fakePerformance, crypto: nativeCrypto,
|
|
245
|
+
ArrayBuffer, Uint8Array, Uint16Array, Uint32Array,
|
|
246
|
+
Int8Array, Int16Array, Int32Array, Float32Array, Float64Array, DataView,
|
|
247
|
+
atob(value) {
|
|
248
|
+
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
|
249
|
+
const input = String(value).replace(/[\t\n\f\r ]/g, '');
|
|
250
|
+
let output = '';
|
|
251
|
+
for (let i = 0; i < input.length;) {
|
|
252
|
+
const c1 = alphabet.indexOf(input.charAt(i++));
|
|
253
|
+
const c2 = alphabet.indexOf(input.charAt(i++));
|
|
254
|
+
const c3 = alphabet.indexOf(input.charAt(i++));
|
|
255
|
+
const c4 = alphabet.indexOf(input.charAt(i++));
|
|
256
|
+
if (c1 < 0 || c2 < 0 || c3 < 0 || c4 < 0) break;
|
|
257
|
+
const b1 = (c1 << 2) | (c2 >> 4);
|
|
258
|
+
output += String.fromCharCode(b1 & 255);
|
|
259
|
+
if (c3 === 64) continue;
|
|
260
|
+
output += String.fromCharCode(((c2 & 15) << 4) | (c3 >> 2));
|
|
261
|
+
if (c4 === 64) continue;
|
|
262
|
+
output += String.fromCharCode(((c3 & 3) << 6) | c4);
|
|
263
|
+
}
|
|
264
|
+
return output;
|
|
265
|
+
},
|
|
266
|
+
btoa(value) {
|
|
267
|
+
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
268
|
+
const input = String(value);
|
|
269
|
+
let output = '';
|
|
270
|
+
for (let i = 0; i < input.length; i += 3) {
|
|
271
|
+
const a = input.charCodeAt(i), b = input.charCodeAt(i + 1), c = input.charCodeAt(i + 2);
|
|
272
|
+
output += alphabet[a >> 2];
|
|
273
|
+
output += alphabet[((a & 3) << 4) | (b >> 4)];
|
|
274
|
+
output += Number.isNaN(b) ? '=' : alphabet[((b & 15) << 2) | (c >> 6)];
|
|
275
|
+
output += Number.isNaN(b) ? '=' : (Number.isNaN(c) ? '=' : alphabet[c & 63]);
|
|
276
|
+
}
|
|
277
|
+
return output;
|
|
278
|
+
},
|
|
279
|
+
Request: global.Request, Headers: global.Headers, URL: global.URL, URLSearchParams: global.URLSearchParams,
|
|
280
|
+
TextEncoder: global.TextEncoder, TextDecoder: global.TextDecoder,
|
|
281
|
+
setTimeout, clearTimeout, setInterval, clearInterval,
|
|
282
|
+
requestAnimationFrame: fn => setTimeout(fn, 16),
|
|
283
|
+
matchMedia: () => ({ matches: false, media: '', addListener() {}, removeListener() {} }),
|
|
284
|
+
addEventListener(type, fn) { listeners.set(`window:${type}`, fn); },
|
|
285
|
+
removeEventListener(type) { listeners.delete(`window:${type}`); },
|
|
286
|
+
dispatchEvent(evt) { const fn = listeners.get(`window:${evt?.type}`); if (fn) fn(evt); return true; },
|
|
287
|
+
getComputedStyle: () => ({ getPropertyValue: () => '' }),
|
|
288
|
+
console, XMLHttpRequest: FakeXHR, WebSocket: FakeWebSocket,
|
|
289
|
+
Event: class Event { constructor(type, init = {}) { this.type = type; Object.assign(this, init); } },
|
|
290
|
+
Element: class Element {}, HTMLElement: class HTMLElement {},
|
|
291
|
+
HTMLCanvasElement: class HTMLCanvasElement {},
|
|
292
|
+
};
|
|
293
|
+
fakeWindow.CustomEvent = class CustomEvent extends fakeWindow.Event { constructor(type, init = {}) { super(type, init); this.detail = init.detail; } };
|
|
294
|
+
fakeWindow.MouseEvent = fakeWindow.Event;
|
|
295
|
+
fakeWindow.TouchEvent = fakeWindow.Event;
|
|
296
|
+
fakeWindow.window = fakeWindow; fakeWindow.self = fakeWindow; fakeWindow.top = fakeWindow;
|
|
297
|
+
fakeWindow.parent = fakeWindow; fakeWindow.globalThis = fakeWindow; fakeWindow.dwInfl = {};
|
|
298
|
+
fakeWindow._mssdk = {
|
|
299
|
+
_sharedCache: input.shared_cache || { slardarErrs: [], ttwid: cookieTtwid, tt_webid: '', tt_webid_v2: '', msNewTokenList: [], coreTiming: [] },
|
|
300
|
+
_enablePathList: signingPaths,
|
|
301
|
+
_enablePathListRegex: signingPaths.map(x => new RegExp(x)),
|
|
302
|
+
cacheOpts: {}, opts: [], umode: 513, _loaderInit: true,
|
|
303
|
+
_urlRewriteRules: [], pppt: 2, ets: -1,
|
|
304
|
+
};
|
|
305
|
+
env.init({ window: fakeWindow, document: fakeDocument, navigator: fakeNavigator, location: fakeLocation });
|
|
306
|
+
for (const [key, value] of Object.entries({
|
|
307
|
+
crypto: nativeCrypto, performance: fakePerformance, Request: global.Request, Headers: global.Headers,
|
|
308
|
+
URL: global.URL, URLSearchParams: global.URLSearchParams, TextEncoder: global.TextEncoder,
|
|
309
|
+
TextDecoder: global.TextDecoder, setTimeout, clearTimeout, setInterval, clearInterval,
|
|
310
|
+
requestAnimationFrame: fakeWindow.requestAnimationFrame,
|
|
311
|
+
})) Object.defineProperty(global, key, { value, configurable: true, writable: true });
|
|
312
|
+
const sdkPath = requiredSignatureKeys(input.url).includes('X-Dynosaur') && fs.existsSync(legacySourcePath)
|
|
313
|
+
? legacySourcePath : sourcePath;
|
|
314
|
+
let sdkSource = fs.readFileSync(sdkPath, 'utf8');
|
|
315
|
+
vm.runInNewContext(sdkSource, fakeWindow, { filename: path.basename(sdkPath) });
|
|
316
|
+
// webmssdk_ex is optional for read-only signing, but loading it keeps the
|
|
317
|
+
// runtime aligned with the browser page when the bundle exposes extensions.
|
|
318
|
+
if (fs.existsSync(exPath)) {
|
|
319
|
+
try { vm.runInNewContext(fs.readFileSync(exPath, 'utf8'), fakeWindow, { filename: 'webmssdk_ex.js' }); }
|
|
320
|
+
catch (_) { /* The extension bundle is optional for this endpoint. */ }
|
|
321
|
+
}
|
|
322
|
+
fakeWindow._mssdk = fakeWindow._mssdk || [];
|
|
323
|
+
fakeWindow._mssdk.cacheOpts = fakeWindow._mssdk.cacheOpts || {};
|
|
324
|
+
fakeWindow._mssdk.cacheOpts[String(runtimeConfig.aid)] = runtimeConfig;
|
|
325
|
+
return { fakeWindow, fakeDocument, requestUrl, userAgent };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function assertUnsigned(url) {
|
|
329
|
+
const parsed = new URL(url);
|
|
330
|
+
for (const key of ['X-Dynosaur', 'X-Gnarly', 'X-Bogus']) {
|
|
331
|
+
if (parsed.searchParams.has(key)) throw new Error(`输入 URL 不允许携带静态 ${key}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function requiredSignatureKeys(url) {
|
|
336
|
+
const pathname = new URL(String(url)).pathname;
|
|
337
|
+
// Creator Studio upload authorization is unsigned; project-post requests
|
|
338
|
+
// carry only msToken/X-Bogus/X-Gnarly. The legacy web API paths additionally
|
|
339
|
+
// carry X-Dynosaur. Never invent a missing field just to satisfy a generic
|
|
340
|
+
// four-field schema.
|
|
341
|
+
if (/\/api\/v1\/video\/upload\/auth\//.test(pathname)) return [];
|
|
342
|
+
if (/\/tiktok\/web\/project\/post(?:_retry)?\/v1\//.test(pathname)) {
|
|
343
|
+
return ['msToken', 'X-Bogus', 'X-Gnarly'];
|
|
344
|
+
}
|
|
345
|
+
return ['X-Dynosaur', 'msToken', 'X-Bogus', 'X-Gnarly'];
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async function sign(input) {
|
|
349
|
+
if (!input || !input.url) throw new Error('signer 需要完整 url');
|
|
350
|
+
const frontierOnly = input.mode === 'frontier_sign';
|
|
351
|
+
if (!frontierOnly && (!input.headers || !Object.keys(input.headers).length)) throw new Error('signer 需要完整 headers');
|
|
352
|
+
const method = String(input.method || 'GET').toUpperCase();
|
|
353
|
+
if (['POST', 'PUT', 'PATCH'].includes(method) && (input.body === undefined || input.body === null)) throw new Error('signer 需要完整 body');
|
|
354
|
+
assertUnsigned(input.url);
|
|
355
|
+
const { fakeWindow, fakeDocument, requestUrl, userAgent } = buildRuntime(input);
|
|
356
|
+
let captured;
|
|
357
|
+
fakeWindow.fetch = async (...args) => {
|
|
358
|
+
const requested = String(args[0] && args[0].url || args[0]);
|
|
359
|
+
// WebMssdk obtains a public, short-lived rule/config response before it
|
|
360
|
+
// signs the first API request. Let that response be fetched normally;
|
|
361
|
+
// only the target TikTok API call is captured and kept offline.
|
|
362
|
+
if (nativeFetch && /https:\/\/(?:mssdk|mon)\-[^/]+\.tiktok\.com\//i.test(requested)) {
|
|
363
|
+
const opts = args[1] || {};
|
|
364
|
+
return nativeFetch(requested, { method: opts.method || 'GET', headers: opts.headers || {} });
|
|
365
|
+
}
|
|
366
|
+
captured = { url: requested, options: args[1] || {} };
|
|
367
|
+
return { status: 200, headers: { get() { return 'application/json'; }, has() { return false; } }, json: async () => ({ status_code: 0 }), text: async () => '{}' };
|
|
368
|
+
};
|
|
369
|
+
if (!fakeWindow.byted_acrawler || typeof fakeWindow.byted_acrawler.init !== 'function') throw new Error('webmssdk 未导出 byted_acrawler');
|
|
370
|
+
await fakeWindow.byted_acrawler.init(runtimeConfig);
|
|
371
|
+
// The real page may initialize the SDK before these lifecycle events. Our
|
|
372
|
+
// VM starts from an already-complete document, so replay the events once to
|
|
373
|
+
// let the SDK collect DOM, screen, WebGL and timing signals.
|
|
374
|
+
for (const type of ['readystatechange', 'DOMContentLoaded', 'load']) {
|
|
375
|
+
fakeDocument.dispatchEvent(new fakeWindow.Event(type));
|
|
376
|
+
fakeWindow.dispatchEvent(new fakeWindow.Event(type));
|
|
377
|
+
}
|
|
378
|
+
// The browser collects the same asynchronous SDK signals before issuing
|
|
379
|
+
// the first API call. Give the local VM one turn to finish those tasks.
|
|
380
|
+
await new Promise(resolve => setTimeout(resolve, 300));
|
|
381
|
+
if (frontierOnly) {
|
|
382
|
+
if (typeof fakeWindow.byted_acrawler.frontierSign !== 'function') throw new Error('SDK 未导出 frontierSign');
|
|
383
|
+
if (input.stub && !/^[0-9a-f]{32}$/.test(String(input.stub))) throw new Error('IM X-MS-STUB 必须为 32 位小写 MD5');
|
|
384
|
+
const result = fakeWindow.byted_acrawler.frontierSign(
|
|
385
|
+
input.stub ? { 'X-MS-STUB': String(input.stub) } : { 'X-MS-PAYLOAD': '' }
|
|
386
|
+
);
|
|
387
|
+
const marker = result && result['X-Bogus'];
|
|
388
|
+
if (typeof marker !== 'string' || marker.length !== 16) throw new Error('frontierSign 未产生浏览器长度 16 的 X-Bogus');
|
|
389
|
+
return { values: { 'X-Bogus': marker }, lengths: { 'X-Bogus': marker.length } };
|
|
390
|
+
}
|
|
391
|
+
const headers = { ...(input.headers || {}) };
|
|
392
|
+
const options = { method, credentials: 'include', headers };
|
|
393
|
+
if (input.body !== undefined && input.body !== null && method !== 'GET' && method !== 'HEAD') options.body = String(input.body);
|
|
394
|
+
await fakeWindow.fetch(String(input.url), options);
|
|
395
|
+
if (!captured?.url) throw new Error('SDK 未产生 native fetch URL');
|
|
396
|
+
// The SDK keeps an internal msToken slot. When the caller already placed
|
|
397
|
+
// the browser cookie token in the unsigned URL, this VM slot is empty and
|
|
398
|
+
// the wrapper appends a second `msToken=` marker. Drop only that empty
|
|
399
|
+
// marker; the non-empty browser token and all calculated fields stay intact.
|
|
400
|
+
const inputToken = new URL(String(input.url)).searchParams.get('msToken') || '';
|
|
401
|
+
let normalizedUrl = String(captured.url).replace(/&msToken=(?=&(?:X-Bogus|X-Gnarly)=)/g, '');
|
|
402
|
+
// Some browser storage snapshots expose an old msToken to the SDK in
|
|
403
|
+
// addition to the fresh token in the unsigned URL. Keep exactly one
|
|
404
|
+
// caller-supplied token while retaining the SDK's calculated signatures.
|
|
405
|
+
let seenMsToken = 0;
|
|
406
|
+
normalizedUrl = normalizedUrl.replace(/([?&])msToken=[^&]*/g, (match, prefix) => {
|
|
407
|
+
if (seenMsToken++ > 0) return '';
|
|
408
|
+
return `${prefix}msToken=${encodeURIComponent(inputToken)}`;
|
|
409
|
+
});
|
|
410
|
+
// Match Chrome's query insertion order when the caller supplied the
|
|
411
|
+
// cookie token before the SDK fields: X-Dynosaur, msToken, X-Bogus,
|
|
412
|
+
// X-Gnarly. This is ordering only; the value is still the caller's token.
|
|
413
|
+
const tokenMatch = normalizedUrl.match(/&msToken=([^&]*)/);
|
|
414
|
+
const dynMatch = normalizedUrl.match(/&X-Dynosaur=[^&]*/);
|
|
415
|
+
if (tokenMatch && dynMatch && tokenMatch.index < dynMatch.index) {
|
|
416
|
+
const tokenPart = tokenMatch[0];
|
|
417
|
+
normalizedUrl = normalizedUrl.replace(tokenPart, '');
|
|
418
|
+
const newDyn = normalizedUrl.match(/&X-Dynosaur=[^&]*/);
|
|
419
|
+
if (newDyn) normalizedUrl = normalizedUrl.slice(0, newDyn.index + newDyn[0].length) + tokenPart + normalizedUrl.slice(newDyn.index + newDyn[0].length);
|
|
420
|
+
}
|
|
421
|
+
const signed = new URL(normalizedUrl);
|
|
422
|
+
const allKeys = ['X-Dynosaur', 'msToken', 'X-Bogus', 'X-Gnarly'];
|
|
423
|
+
const requiredKeys = requiredSignatureKeys(input.url);
|
|
424
|
+
const values = Object.fromEntries(allKeys.map(key => [key, signed.searchParams.get(key) || '']));
|
|
425
|
+
if (requiredKeys.includes('msToken') && signed.searchParams.getAll('msToken').length !== 1) {
|
|
426
|
+
throw new Error('SDK 结果中的 msToken 字段数量不对齐');
|
|
427
|
+
}
|
|
428
|
+
if (inputToken && values.msToken !== inputToken) throw new Error('SDK 结果的 msToken 未与 unsigned 请求对齐');
|
|
429
|
+
const missing = requiredKeys.filter(key => !values[key]);
|
|
430
|
+
if (missing.length) throw new Error(`SDK 纯计算后仍缺少字段: ${missing.join(', ')}`);
|
|
431
|
+
const lengths = Object.fromEntries(Object.entries(values).map(([key, value]) => [key, value.length]));
|
|
432
|
+
if (input.expected_lengths) {
|
|
433
|
+
for (const [key, expected] of Object.entries(input.expected_lengths)) {
|
|
434
|
+
if (lengths[key] !== Number(expected)) throw new Error(`${key} 长度 ${lengths[key]} != 浏览器证据 ${expected}`);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return { url: normalizedUrl, values, lengths, method, body: options.body || '', user_agent: userAgent, origin: requestUrl.origin };
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const rl = readline.createInterface({ input: process.stdin, crlfDelay: Infinity });
|
|
441
|
+
// Do not call process.exit from the close event before the async signer has
|
|
442
|
+
// handled the final line. The browser SDK schedules background timers, so we
|
|
443
|
+
// serialize input lines and explicitly terminate only after the queue drains.
|
|
444
|
+
let pending = Promise.resolve();
|
|
445
|
+
rl.on('line', line => {
|
|
446
|
+
if (!line.trim()) return;
|
|
447
|
+
pending = pending.then(async () => {
|
|
448
|
+
try { _process.stdout.write(JSON.stringify({ ok: true, result: await sign(JSON.parse(line)) }) + '\n'); }
|
|
449
|
+
catch (err) { _process.stdout.write(JSON.stringify({ ok: false, error: String(err && err.message || err), stack: String(err && err.stack || '') }) + '\n'); }
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
rl.on('close', async () => {
|
|
453
|
+
await pending;
|
|
454
|
+
_process.exit(0);
|
|
455
|
+
});
|