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,169 @@
|
|
|
1
|
+
import { quote, quotePlus } from '../../../core/py.js';
|
|
2
|
+
/**
|
|
3
|
+
* 浏览器画像、请求头与 query 构造(上游 builder/auth.py 的常量、builder/header.py、builder/params.py)。
|
|
4
|
+
* UA 是上游取证用的 Chrome 153;TLS 指纹用 wreq-js 最新的 Chrome 画像。
|
|
5
|
+
*/
|
|
6
|
+
export const PROFILE = {
|
|
7
|
+
ua: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.0.0 Safari/537.36',
|
|
8
|
+
secChUa: '"Google Chrome";v="153", "Not_A Brand";v="8", "Chromium";v="153"',
|
|
9
|
+
secChUaPlatform: '"Windows"',
|
|
10
|
+
acceptLanguage: 'zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7,ja;q=0.6',
|
|
11
|
+
clientAbVersions: '70508271,72437276,73720540,75360573,75657507,75843653,75878361,' +
|
|
12
|
+
'76074824,76122410,76179551,76403724,76499369,76626930,76651230,' +
|
|
13
|
+
'76669176,76713103,76740102,76792575,76805936,76813520,76815102,' +
|
|
14
|
+
'76826749,76839930,76840792,76887350,76889673,76907405,76933422,' +
|
|
15
|
+
'76945954,76950455,77005875,77011497,77014490,77056281,77064650,' +
|
|
16
|
+
'77071444,77093404,77120364,77141446,77150562,77155134,70138197,' +
|
|
17
|
+
'70156809,70405643,71057832,71200802,71381811,71803300,72360691,' +
|
|
18
|
+
'72408100,72854054,72892778,73171280,73208420,73989921,74276218,' +
|
|
19
|
+
'74844724,75330961',
|
|
20
|
+
};
|
|
21
|
+
export const BROWSER = 'chrome';
|
|
22
|
+
export const ORIGIN = 'https://www.tiktok.com';
|
|
23
|
+
export const WEBCAST = 'https://webcast.tiktok.com';
|
|
24
|
+
export const SHOP = 'https://shop.tiktok.com';
|
|
25
|
+
export const COOKIE_DOMAIN = '.tiktok.com';
|
|
26
|
+
/** 有序请求头,对应上游的 OrderedDict:改值不动位置,新键追加到末尾。 */
|
|
27
|
+
export class Headers {
|
|
28
|
+
map = new Map();
|
|
29
|
+
set(key, value) {
|
|
30
|
+
if (value != null)
|
|
31
|
+
this.map.set(key, String(value));
|
|
32
|
+
return this;
|
|
33
|
+
}
|
|
34
|
+
get(key) {
|
|
35
|
+
return this.map.get(key);
|
|
36
|
+
}
|
|
37
|
+
has(key) {
|
|
38
|
+
return this.map.has(key);
|
|
39
|
+
}
|
|
40
|
+
delete(key) {
|
|
41
|
+
this.map.delete(key);
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
update(values) {
|
|
45
|
+
for (const [k, v] of values instanceof Map ? values : Object.entries(values ?? {}))
|
|
46
|
+
this.set(k, v);
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
/** 按给定顺序重排;不在表里的键保持原顺序排在后面。drop 为 true 时丢掉表外的键。 */
|
|
50
|
+
reorder(order, drop = false) {
|
|
51
|
+
const next = new Map();
|
|
52
|
+
for (const k of order)
|
|
53
|
+
if (this.map.has(k))
|
|
54
|
+
next.set(k, this.map.get(k));
|
|
55
|
+
if (!drop)
|
|
56
|
+
for (const [k, v] of this.map)
|
|
57
|
+
if (!next.has(k))
|
|
58
|
+
next.set(k, v);
|
|
59
|
+
this.map = next;
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
/** 发送用的键值对。cookie 由 cookie 罐按 URL 补上(位置由 core 决定),这里去掉。 */
|
|
63
|
+
pairs() {
|
|
64
|
+
return [...this.map].filter(([k]) => k !== 'cookie');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** 上游 HeaderBuilder.build:只放浏览器在该类请求里实际发出的字段。cookie 占位,保持与上游相同的相对顺序。 */
|
|
68
|
+
export function buildHeaders(type, b, o) {
|
|
69
|
+
const h = new Headers();
|
|
70
|
+
if (type === 'DOC') {
|
|
71
|
+
h.set('accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7');
|
|
72
|
+
h.set('accept-language', b.acceptLanguage);
|
|
73
|
+
h.set('cache-control', 'no-cache');
|
|
74
|
+
h.set('pragma', 'no-cache');
|
|
75
|
+
h.set('priority', 'u=0, i');
|
|
76
|
+
h.set('referer', o.referer);
|
|
77
|
+
h.set('sec-ch-ua', b.secChUa);
|
|
78
|
+
h.set('sec-ch-ua-mobile', '?0');
|
|
79
|
+
h.set('sec-ch-ua-platform', b.secChUaPlatform);
|
|
80
|
+
h.set('sec-fetch-dest', 'document');
|
|
81
|
+
h.set('sec-fetch-mode', 'navigate');
|
|
82
|
+
h.set('sec-fetch-site', 'same-origin');
|
|
83
|
+
h.set('sec-fetch-user', '?1');
|
|
84
|
+
h.set('upgrade-insecure-requests', '1');
|
|
85
|
+
h.set('user-agent', b.ua);
|
|
86
|
+
h.set('cookie', '');
|
|
87
|
+
return h;
|
|
88
|
+
}
|
|
89
|
+
h.set('sec-ch-ua-platform', b.secChUaPlatform);
|
|
90
|
+
h.set('referer', o.referer);
|
|
91
|
+
h.set('user-agent', b.ua);
|
|
92
|
+
h.set('sec-ch-ua', b.secChUa);
|
|
93
|
+
h.set('sec-ch-ua-mobile', '?0');
|
|
94
|
+
if (type === 'POST')
|
|
95
|
+
h.set('content-type', 'application/json');
|
|
96
|
+
else if (type === 'FORM')
|
|
97
|
+
h.set('content-type', 'application/x-www-form-urlencoded');
|
|
98
|
+
h.set('accept', '*/*');
|
|
99
|
+
h.set('accept-encoding', 'gzip, deflate, br, zstd');
|
|
100
|
+
h.set('accept-language', b.acceptLanguage);
|
|
101
|
+
h.set('cookie', '');
|
|
102
|
+
if (o.origin)
|
|
103
|
+
h.set('origin', o.origin);
|
|
104
|
+
if (o.contentLength != null)
|
|
105
|
+
h.set('content-length', o.contentLength);
|
|
106
|
+
h.set('priority', 'u=1, i');
|
|
107
|
+
h.set('sec-fetch-dest', 'empty');
|
|
108
|
+
h.set('sec-fetch-mode', 'cors');
|
|
109
|
+
h.set('sec-fetch-site', o.secFetchSite ?? 'same-origin');
|
|
110
|
+
return h;
|
|
111
|
+
}
|
|
112
|
+
// ================================================================ query(builder/params.py)
|
|
113
|
+
const SIGNATURE_SAFE = new Set(['X-Dynosaur', 'X-Gnarly', 'msToken']);
|
|
114
|
+
/** 保持插入顺序、按 TikTok 浏览器规则转义的 query。 */
|
|
115
|
+
export class Params {
|
|
116
|
+
spacePlusKeys;
|
|
117
|
+
pairs = [];
|
|
118
|
+
constructor(values = [], spacePlusKeys = new Set()) {
|
|
119
|
+
this.spacePlusKeys = spacePlusKeys;
|
|
120
|
+
for (const [k, v] of values)
|
|
121
|
+
this.add(k, v);
|
|
122
|
+
}
|
|
123
|
+
/** add_param:同名键只保留最新的一个,追加到末尾。 */
|
|
124
|
+
add(key, value) {
|
|
125
|
+
const v = pyValue(value);
|
|
126
|
+
this.pairs = this.pairs.filter(([k]) => k !== key);
|
|
127
|
+
this.pairs.push([key, v]);
|
|
128
|
+
return this;
|
|
129
|
+
}
|
|
130
|
+
/** add_pair:允许重复键(直播 im/fetch 的两个 version_code)。 */
|
|
131
|
+
addPair(key, value) {
|
|
132
|
+
this.pairs.push([key, pyValue(value)]);
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
update(values) {
|
|
136
|
+
const entries = Symbol.iterator in Object(values) ? values : Object.entries(values);
|
|
137
|
+
for (const [k, v] of entries)
|
|
138
|
+
this.add(k, v);
|
|
139
|
+
return this;
|
|
140
|
+
}
|
|
141
|
+
/** 原样复制(保留重复键)。 */
|
|
142
|
+
clone() {
|
|
143
|
+
const p = new Params([], this.spacePlusKeys);
|
|
144
|
+
p.pairs = this.pairs.map(([k, v]) => [k, v]);
|
|
145
|
+
return p;
|
|
146
|
+
}
|
|
147
|
+
get(key) {
|
|
148
|
+
return this.pairs.findLast(([k]) => k === key)?.[1];
|
|
149
|
+
}
|
|
150
|
+
toQuery() {
|
|
151
|
+
return this.pairs
|
|
152
|
+
.map(([key, value]) => {
|
|
153
|
+
const safe = SIGNATURE_SAFE.has(key) ? '/=+.-_~' : key === 'next' ? ':' : '-_.~';
|
|
154
|
+
const enc = this.spacePlusKeys.has(key) ? quotePlus : quote;
|
|
155
|
+
return `${quote(key, '-_.~')}=${enc(value, safe)}`;
|
|
156
|
+
})
|
|
157
|
+
.join('&');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/** Python 的 `"" if value is None else str(value)`。 */
|
|
161
|
+
function pyValue(value) {
|
|
162
|
+
if (value == null)
|
|
163
|
+
return '';
|
|
164
|
+
if (value === true)
|
|
165
|
+
return 'True';
|
|
166
|
+
if (value === false)
|
|
167
|
+
return 'False';
|
|
168
|
+
return String(value);
|
|
169
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { SpecificAIError } from '../../../core/errors.js';
|
|
2
|
+
import { jsonLoads } from '../../../core/py.js';
|
|
3
|
+
import * as api from './api.js';
|
|
4
|
+
import { hydration } from './client.js';
|
|
5
|
+
import { ORIGIN } from './profile.js';
|
|
6
|
+
/** 参数归一化(AGENTS 4.8):ID、URL、分享短链、@用户名、secUid、`me`。 */
|
|
7
|
+
const SHORT_RE = /^https?:\/\/(vt\.tiktok\.com|vm\.tiktok\.com|www\.tiktok\.com\/t)\//i;
|
|
8
|
+
/** 分享短链跟一次跳转,取 Location。 */
|
|
9
|
+
export async function expand(t, input) {
|
|
10
|
+
const s = input.trim();
|
|
11
|
+
if (!SHORT_RE.test(s))
|
|
12
|
+
return s;
|
|
13
|
+
const res = await t.send({ url: s, redirect: 'manual', cookies: false, headers: [['user-agent', t.ua]] });
|
|
14
|
+
return res.headers.get('location') ?? s;
|
|
15
|
+
}
|
|
16
|
+
/** 作品:纯数字 ID、视频 / 图文页 URL、分享短链。 */
|
|
17
|
+
export async function resolveItem(t, input) {
|
|
18
|
+
const s = await expand(t, input);
|
|
19
|
+
const m = /tiktok\.com\/@([^/?#]+)\/(?:video|photo)\/(\d+)/.exec(s);
|
|
20
|
+
if (m)
|
|
21
|
+
return { id: m[2], handle: decodeURIComponent(m[1]), url: `${ORIGIN}/@${m[1]}/video/${m[2]}` };
|
|
22
|
+
const id = /(?:^|\/(?:video|photo|v)\/)(\d{15,21})(?:\.html)?(?:[/?#]|$)/.exec(s)?.[1];
|
|
23
|
+
if (id)
|
|
24
|
+
return { id, handle: null, url: `${ORIGIN}/@_/video/${id}` };
|
|
25
|
+
throw new SpecificAIError('USAGE', `无法识别的作品:${input}`, { hint: '传作品 ID 或视频链接,例如 https://www.tiktok.com/@tiktok/video/7300000000000000000' });
|
|
26
|
+
}
|
|
27
|
+
const SEC_UID_RE = /^MS4wLj[A-Za-z0-9_-]{20,}$/;
|
|
28
|
+
/** 用户:@用户名、主页 URL、secUid、数字 uid、`me`。 */
|
|
29
|
+
export async function parseUser(t, input) {
|
|
30
|
+
const s = (await expand(t, input)).trim();
|
|
31
|
+
if (s === 'me') {
|
|
32
|
+
if (!t.loggedIn)
|
|
33
|
+
throw new SpecificAIError('AUTH_REQUIRED', 'me 表示当前账号,需要先登录', { hint: 'specific-ai tiktok auth login' });
|
|
34
|
+
const me = await currentUser(t);
|
|
35
|
+
return { handle: me.uniqueId ?? null, secUid: me.secUid ?? null, id: me.uid ?? t.uid };
|
|
36
|
+
}
|
|
37
|
+
const url = /tiktok\.com\/@([^/?#]+)/.exec(s)?.[1];
|
|
38
|
+
if (url)
|
|
39
|
+
return { handle: decodeURIComponent(url), secUid: null, id: null };
|
|
40
|
+
if (SEC_UID_RE.test(s))
|
|
41
|
+
return { handle: null, secUid: s, id: null };
|
|
42
|
+
if (/^\d{5,21}$/.test(s))
|
|
43
|
+
return { handle: null, secUid: null, id: s };
|
|
44
|
+
const handle = /^@?([A-Za-z0-9_.]{1,64})$/.exec(s)?.[1];
|
|
45
|
+
if (handle)
|
|
46
|
+
return { handle, secUid: null, id: null };
|
|
47
|
+
throw new SpecificAIError('USAGE', `无法识别的用户:${input}`, { hint: '传用户名(@tiktok)、主页链接、secUid 或 me' });
|
|
48
|
+
}
|
|
49
|
+
/** 登录用户(首页 hydration 的 app-context.user)。 */
|
|
50
|
+
export async function currentUser(t) {
|
|
51
|
+
const html = await t.document(`${ORIGIN}/`, `${ORIGIN}/`);
|
|
52
|
+
const user = hydration(html)?.['webapp.app-context']?.user;
|
|
53
|
+
if (!user?.uid && !user?.uniqueId)
|
|
54
|
+
throw new SpecificAIError('AUTH_EXPIRED', 'TikTok 首页没有返回当前登录用户,登录态可能已失效', { hint: 'specific-ai tiktok auth login' });
|
|
55
|
+
return user;
|
|
56
|
+
}
|
|
57
|
+
/** 主页 SSR 里的 userInfo(上游 get_user_info:webapp.user-detail)。 */
|
|
58
|
+
export async function userDetail(t, handle) {
|
|
59
|
+
const html = await api.userHtml(t, `${ORIGIN}/@${encodeURIComponent(handle).replace(/%40/g, '@')}`);
|
|
60
|
+
const detail = hydration(html)?.['webapp.user-detail'] ?? matchUserDetail(html);
|
|
61
|
+
if (!detail)
|
|
62
|
+
throw new SpecificAIError('UPSTREAM', '页面中没有 webapp.user-detail', { hint: '确认用户名是否正确' });
|
|
63
|
+
if (detail.statusCode && detail.statusCode !== 0) {
|
|
64
|
+
throw new SpecificAIError('UPSTREAM', `用户不存在或不可见:@${handle}`, { detail: { statusCode: detail.statusCode, statusMsg: detail.statusMsg } });
|
|
65
|
+
}
|
|
66
|
+
return detail.userInfo;
|
|
67
|
+
}
|
|
68
|
+
/** 上游的正则取法:`"webapp.user-detail":(.*?),"webapp.a-b"`。 */
|
|
69
|
+
function matchUserDetail(html) {
|
|
70
|
+
const m = /"webapp\.user-detail":(.*?),"webapp\.a-b"/.exec(html);
|
|
71
|
+
if (!m)
|
|
72
|
+
return null;
|
|
73
|
+
try {
|
|
74
|
+
return jsonLoads(m[1]);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** 数字 uid → 用户名(私信页的用户卡片接口,需要登录)。 */
|
|
81
|
+
async function handleById(t, id) {
|
|
82
|
+
const r = await api.imUserProfile(t, [id]);
|
|
83
|
+
const handle = r.users?.[0]?.im_user_profile?.unique_id;
|
|
84
|
+
if (!handle)
|
|
85
|
+
throw new SpecificAIError('UPSTREAM', `找不到用户 ${id}`);
|
|
86
|
+
return handle;
|
|
87
|
+
}
|
|
88
|
+
/** 需要完整资料时:返回 userInfo(含 user.id / secUid / uniqueId)。 */
|
|
89
|
+
export async function resolveUserInfo(t, input) {
|
|
90
|
+
const u = await parseUser(t, input);
|
|
91
|
+
if (u.handle)
|
|
92
|
+
return userDetail(t, u.handle);
|
|
93
|
+
if (u.id)
|
|
94
|
+
return userDetail(t, await handleById(t, u.id));
|
|
95
|
+
throw new SpecificAIError('USAGE', '只给了 secUid,无法取用户资料', { hint: '传用户名(@tiktok)或主页链接' });
|
|
96
|
+
}
|
|
97
|
+
/** 只需要 secUid 的列表类接口。 */
|
|
98
|
+
export async function resolveSecUid(t, input) {
|
|
99
|
+
const u = await parseUser(t, input);
|
|
100
|
+
if (u.secUid)
|
|
101
|
+
return u.secUid;
|
|
102
|
+
const info = await resolveUserInfo(t, input);
|
|
103
|
+
const sec = info?.user?.secUid;
|
|
104
|
+
if (!sec)
|
|
105
|
+
throw new SpecificAIError('UPSTREAM', '用户资料里没有 secUid');
|
|
106
|
+
return sec;
|
|
107
|
+
}
|
|
108
|
+
const ROOM_ID_RE = /^\d{10,21}$/;
|
|
109
|
+
export const isRoomId = (input) => ROOM_ID_RE.test(input.trim());
|
|
110
|
+
/** 只有房间号时取主播信息:进房(上游 enter_live_room)拿房间对象里的 owner。 */
|
|
111
|
+
export async function enterRoom(t, roomId) {
|
|
112
|
+
const r = await api.enterLiveRoom(t, roomId);
|
|
113
|
+
const room = r.data ?? {};
|
|
114
|
+
const owner = room.owner ?? {};
|
|
115
|
+
const hostId = owner.id_str ?? room.owner_user_id_str ?? owner.id ?? room.owner_user_id;
|
|
116
|
+
if (!hostId)
|
|
117
|
+
throw new SpecificAIError('UPSTREAM', `直播间 ${roomId} 的进房结果里没有主播信息`, { detail: { status: room.status } });
|
|
118
|
+
return { roomId: String(room.id_str ?? roomId), handle: owner.display_id ?? null, hostId: String(hostId), data: null, webcast: room };
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* 直播间:@主播、主播主页或直播间 URL(查 /api-live/user/room 得到房间号)、纯数字房间号。
|
|
122
|
+
* 只给房间号、又需要主播信息(点赞、排行榜、发弹幕、直播间信息)时,先进房取房间对象。
|
|
123
|
+
*/
|
|
124
|
+
export async function resolveRoom(t, input, o = {}) {
|
|
125
|
+
const s = (await expand(t, input)).trim();
|
|
126
|
+
if (ROOM_ID_RE.test(s)) {
|
|
127
|
+
if (o.needHost)
|
|
128
|
+
return enterRoom(t, s);
|
|
129
|
+
return { roomId: s, handle: null, hostId: null, data: null };
|
|
130
|
+
}
|
|
131
|
+
const handle = /tiktok\.com\/@([^/?#]+)/.exec(s)?.[1] ?? /^@?([A-Za-z0-9_.]{1,64})$/.exec(s)?.[1];
|
|
132
|
+
if (!handle)
|
|
133
|
+
throw new SpecificAIError('USAGE', `无法识别的直播间:${input}`, { hint: '传主播用户名(@tiktok)、直播间链接或房间号' });
|
|
134
|
+
const r = await api.liveUserRoom(t, decodeURIComponent(handle));
|
|
135
|
+
const u = r.data?.user ?? {};
|
|
136
|
+
const roomId = String(u.roomId || r.data?.liveRoom?.roomId || '');
|
|
137
|
+
if (!roomId)
|
|
138
|
+
throw new SpecificAIError('UPSTREAM', `@${handle} 当前没有直播间`, { detail: { status: u.status } });
|
|
139
|
+
return { roomId, handle: u.uniqueId ?? decodeURIComponent(handle), hostId: u.id ? String(u.id) : null, data: r.data };
|
|
140
|
+
}
|
|
141
|
+
/** Shop 商品:PDP URL(/pdp/<slug>/<id>、/view/product/<id>)或纯数字 ID。 */
|
|
142
|
+
export function resolveProduct(input) {
|
|
143
|
+
const s = input.trim();
|
|
144
|
+
const m = /shop\.tiktok\.com(?:\/[^?#]*?)?(?:\/pdp\/(?:[^/?#]+\/)?|\/view\/product\/)(\d+)/.exec(s);
|
|
145
|
+
if (m) {
|
|
146
|
+
const url = s.split('#')[0].split('?')[0];
|
|
147
|
+
return { id: m[1], url: /\/pdp\//.test(url) ? url : `https://shop.tiktok.com/view/product/${m[1]}` };
|
|
148
|
+
}
|
|
149
|
+
if (/^\d{10,21}$/.test(s))
|
|
150
|
+
return { id: s, url: `https://shop.tiktok.com/view/product/${s}` };
|
|
151
|
+
throw new SpecificAIError('USAGE', `无法识别的商品:${input}`, { hint: '传商品 ID 或 https://shop.tiktok.com/.../pdp/<商品ID> 链接' });
|
|
152
|
+
}
|
|
153
|
+
/** 是否像商品参数(`comment list` 自动识别商品 URL)。 */
|
|
154
|
+
export function isProductInput(input) {
|
|
155
|
+
return /shop\.tiktok\.com\//.test(input);
|
|
156
|
+
}
|