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,413 @@
|
|
|
1
|
+
import { SpecificAIError } from '../../../core/errors.js';
|
|
2
|
+
import { HttpClient } from '../../../core/http.js';
|
|
3
|
+
import { jsonLoads } from '../../../core/py.js';
|
|
4
|
+
import * as rand from '../../../core/rand.js';
|
|
5
|
+
import { authError, isGuest, scope } from '../../../core/toolkit.js';
|
|
6
|
+
import * as api from './api.js';
|
|
7
|
+
import { ecdhKey, reeKey, sessionDtrait, ticketClientData, ticketGuardVersion } from './crypto.js';
|
|
8
|
+
import { buildBlob, DTRAIT_BROKEN, DTRAIT_HINT, defaultProfile } from './dtrait.js';
|
|
9
|
+
import { BROWSER, WWW, WWW_ONLY } from './profile.js';
|
|
10
|
+
import { ABogus, fakeWebid, md5Hex, randomMsToken, svWebId, XBogus } from './sign.js';
|
|
11
|
+
/**
|
|
12
|
+
* 抖音 web 端的会话(上游 builder/auth.py 的 DouyinAuth):cookie、msToken / webid 的生命周期、
|
|
13
|
+
* bd-ticket-guard 与 dtrait 安全头。
|
|
14
|
+
*
|
|
15
|
+
* cookie 按上游 `auth.cookie` 的语义保存成一张按名字去重的有序表(凭证 main 分区):
|
|
16
|
+
* www-only 的名字挂在 www.douyin.com,其余挂在 .douyin.com;响应的 Set-Cookie 按名字合并,空值表示删除。
|
|
17
|
+
* 创作者中心页面写的 host-only cookie 单独放在 creator 分区。
|
|
18
|
+
*/
|
|
19
|
+
/** msToken 缓存 600 秒(上游 _MS_TTL)。 */
|
|
20
|
+
const MS_TTL = 600_000;
|
|
21
|
+
const ROOT_DOMAIN = '.douyin.com';
|
|
22
|
+
const CREATOR_HOST = 'creator.douyin.com';
|
|
23
|
+
export class Douyin {
|
|
24
|
+
ctx;
|
|
25
|
+
http;
|
|
26
|
+
ab = new ABogus();
|
|
27
|
+
xb = new XBogus();
|
|
28
|
+
/** 扫码轮询期间 msToken 只由 /web/common 轮换(上游 _ms_pinned)。 */
|
|
29
|
+
msPinned = false;
|
|
30
|
+
resolvingWebid = false;
|
|
31
|
+
memWebid = '';
|
|
32
|
+
ecdh = new Map();
|
|
33
|
+
dtrait = new Map();
|
|
34
|
+
identity = null;
|
|
35
|
+
constructor(ctx) {
|
|
36
|
+
this.ctx = ctx;
|
|
37
|
+
this.http = new HttpClient({ browser: BROWSER, os: 'windows', proxy: ctx.config.proxy, timeout: ctx.config.timeout, log: ctx.log });
|
|
38
|
+
}
|
|
39
|
+
get credential() {
|
|
40
|
+
return this.ctx.credential;
|
|
41
|
+
}
|
|
42
|
+
get tokens() {
|
|
43
|
+
return scope(this.credential).tokens;
|
|
44
|
+
}
|
|
45
|
+
get device() {
|
|
46
|
+
return this.credential.device;
|
|
47
|
+
}
|
|
48
|
+
// ---------------------------------------------------------------- cookie
|
|
49
|
+
get jar() {
|
|
50
|
+
return scope(this.credential).cookies;
|
|
51
|
+
}
|
|
52
|
+
get creatorJar() {
|
|
53
|
+
return scope(this.credential, 'creator').cookies;
|
|
54
|
+
}
|
|
55
|
+
cookie(name) {
|
|
56
|
+
return this.jar.find((c) => c.name === name)?.value;
|
|
57
|
+
}
|
|
58
|
+
setCookie(name, value) {
|
|
59
|
+
const c = this.jar.find((x) => x.name === name);
|
|
60
|
+
if (c)
|
|
61
|
+
c.value = value;
|
|
62
|
+
else
|
|
63
|
+
this.jar.push({ name, value, domain: WWW_ONLY.has(name) ? 'www.douyin.com' : ROOT_DOMAIN, path: '/', expires: null });
|
|
64
|
+
}
|
|
65
|
+
setDefault(name, value) {
|
|
66
|
+
const v = this.cookie(name);
|
|
67
|
+
if (v != null && v !== '')
|
|
68
|
+
return v;
|
|
69
|
+
const nv = value();
|
|
70
|
+
this.setCookie(name, nv);
|
|
71
|
+
return nv;
|
|
72
|
+
}
|
|
73
|
+
deleteCookie(name) {
|
|
74
|
+
for (let i = this.jar.length - 1; i >= 0; i--)
|
|
75
|
+
if (this.jar[i].name === name)
|
|
76
|
+
this.jar.splice(i, 1);
|
|
77
|
+
}
|
|
78
|
+
/** 名字 → 值,按存入顺序(上游 auth.cookie)。 */
|
|
79
|
+
cookies() {
|
|
80
|
+
return this.jar.map((c) => [c.name, c.value]);
|
|
81
|
+
}
|
|
82
|
+
/** 上游 cookie_str:全部 cookie,不分域。 */
|
|
83
|
+
get cookieStr() {
|
|
84
|
+
return this.cookies()
|
|
85
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
86
|
+
.join('; ');
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 发往某个 URL 的 cookie:www-only 的名字只发给 www.douyin.com;创作者中心再加上 creator 分区。
|
|
90
|
+
* 顺序与上游的 curl cookie 罐相同:按 domain 首次出现的先后分组,组内按存入顺序。
|
|
91
|
+
*/
|
|
92
|
+
cookiesFor(url) {
|
|
93
|
+
const host = new URL(url).hostname;
|
|
94
|
+
const main = this.jar.map((c) => ({ name: c.name, value: c.value, domain: WWW_ONLY.has(c.name) ? 'www.douyin.com' : ROOT_DOMAIN }));
|
|
95
|
+
const all = host === CREATOR_HOST ? [...main, ...this.creatorJar] : main;
|
|
96
|
+
const domains = [...new Set(all.map((c) => c.domain))];
|
|
97
|
+
const out = {};
|
|
98
|
+
for (const domain of domains) {
|
|
99
|
+
if (!domainMatch(host, domain))
|
|
100
|
+
continue;
|
|
101
|
+
for (const c of all)
|
|
102
|
+
if (c.domain === domain && !(c.name in out))
|
|
103
|
+
out[c.name] = c.value;
|
|
104
|
+
}
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
/** 上游 merge_set_cookies:按名字合并,空值表示删除。创作者中心的 host-only cookie 进 creator 分区。 */
|
|
108
|
+
mergeSetCookies(url, res) {
|
|
109
|
+
const host = new URL(url).hostname;
|
|
110
|
+
for (const line of res.headers.getSetCookie()) {
|
|
111
|
+
const pair = line.split(';', 1)[0];
|
|
112
|
+
const i = pair.indexOf('=');
|
|
113
|
+
if (i <= 0)
|
|
114
|
+
continue;
|
|
115
|
+
const name = pair.slice(0, i).trim();
|
|
116
|
+
const value = pair.slice(i + 1).trim();
|
|
117
|
+
const domain = /;\s*domain=\.?([^;]+)/i.exec(line)?.[1]?.toLowerCase();
|
|
118
|
+
if (host === CREATOR_HOST && (!domain || domain.endsWith(CREATOR_HOST))) {
|
|
119
|
+
const jar = this.creatorJar;
|
|
120
|
+
const at = jar.findIndex((c) => c.name === name);
|
|
121
|
+
if (at >= 0)
|
|
122
|
+
jar.splice(at, 1);
|
|
123
|
+
if (value)
|
|
124
|
+
jar.push({ name, value, domain: domain ? '.' + domain : CREATOR_HOST, path: '/', expires: null });
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (value === '')
|
|
128
|
+
this.deleteCookie(name);
|
|
129
|
+
else
|
|
130
|
+
this.setCookie(name, value);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// ---------------------------------------------------------------- 请求
|
|
134
|
+
/** 发给抖音的请求:没有显式 cookie 头时带上会话 cookie,响应的 Set-Cookie 合并回来。 */
|
|
135
|
+
async request(req, options = {}) {
|
|
136
|
+
const explicit = headerList(req.headers).some(([k]) => k.toLowerCase() === 'cookie');
|
|
137
|
+
const cookies = req.cookies === false || explicit ? req.cookies : this.cookiesFor(req.url);
|
|
138
|
+
const res = await this.http.request({ ...req, cookies });
|
|
139
|
+
if (options.merge !== false && new URL(res.url || req.url).hostname.endsWith('douyin.com'))
|
|
140
|
+
this.mergeSetCookies(req.url, res);
|
|
141
|
+
return res;
|
|
142
|
+
}
|
|
143
|
+
/** 不带会话 cookie 的请求(mssdk、ttwid 注册、上传网关等跨站请求)。 */
|
|
144
|
+
plain(req) {
|
|
145
|
+
return this.http.request({ ...req, cookies: req.cookies ?? false });
|
|
146
|
+
}
|
|
147
|
+
// ---------------------------------------------------------------- 设备与游客态
|
|
148
|
+
/**
|
|
149
|
+
* 每条命令开始时调用(上游 perepare_auth):去掉 cookie 里的旧 msToken,补 s_v_web_id;
|
|
150
|
+
* 游客没有 ttwid 时去注册一个(upstream-map 1.2),结果缓存在游客凭证里。
|
|
151
|
+
*/
|
|
152
|
+
async init() {
|
|
153
|
+
this.deleteCookie('msToken');
|
|
154
|
+
this.setDefault('s_v_web_id', svWebId);
|
|
155
|
+
if (isGuest(this.ctx) && !this.cookie('ttwid')) {
|
|
156
|
+
const ttwid = await api.registerTtwid(this).catch((err) => {
|
|
157
|
+
this.ctx.log.debug(`注册 ttwid 失败:${err.message}`);
|
|
158
|
+
return '';
|
|
159
|
+
});
|
|
160
|
+
if (ttwid)
|
|
161
|
+
this.setCookie('ttwid', ttwid);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/** 惰性 msToken:缓存 600 秒,过期后向 mssdk 换新,失败时用随机值(上游 DouyinAuth.msToken)。 */
|
|
165
|
+
async msToken() {
|
|
166
|
+
const cached = this.tokens.msToken;
|
|
167
|
+
if (cached?.value && (this.msPinned || rand.now() - cached.at < MS_TTL))
|
|
168
|
+
return cached.value;
|
|
169
|
+
const token = await api.mstoken(this, cached?.value ?? '').catch(() => '');
|
|
170
|
+
if (token) {
|
|
171
|
+
this.tokens.msToken = { value: token, at: rand.now() };
|
|
172
|
+
return token;
|
|
173
|
+
}
|
|
174
|
+
return cached?.value || randomMsToken();
|
|
175
|
+
}
|
|
176
|
+
setMsToken(value) {
|
|
177
|
+
if (value)
|
|
178
|
+
this.tokens.msToken = { value, at: rand.now() };
|
|
179
|
+
}
|
|
180
|
+
/** 真实设备号:query/user 的 id,退而求其次抓页面里的 user_unique_id(上游 DouyinAuth.webid)。 */
|
|
181
|
+
async webid() {
|
|
182
|
+
const saved = this.tokens.webid || this.memWebid;
|
|
183
|
+
if (saved)
|
|
184
|
+
return saved;
|
|
185
|
+
if (this.resolvingWebid)
|
|
186
|
+
return fakeWebid();
|
|
187
|
+
this.resolvingWebid = true;
|
|
188
|
+
let wid = '';
|
|
189
|
+
try {
|
|
190
|
+
wid = String((await api.deviceId(this)) ?? '');
|
|
191
|
+
}
|
|
192
|
+
catch (err) {
|
|
193
|
+
this.ctx.log.debug(`取设备号失败:${err.message}`);
|
|
194
|
+
}
|
|
195
|
+
finally {
|
|
196
|
+
this.resolvingWebid = false;
|
|
197
|
+
}
|
|
198
|
+
if (wid)
|
|
199
|
+
this.tokens.webid = wid;
|
|
200
|
+
else {
|
|
201
|
+
wid = await api.pageWebid(this, `${WWW}/discover`);
|
|
202
|
+
if (wid)
|
|
203
|
+
this.tokens.webid = wid;
|
|
204
|
+
else
|
|
205
|
+
this.memWebid = wid = fakeWebid();
|
|
206
|
+
}
|
|
207
|
+
return wid;
|
|
208
|
+
}
|
|
209
|
+
/** 登录用户的数字 uid(上游 get_uid),缓存在凭证里。 */
|
|
210
|
+
async uid() {
|
|
211
|
+
if (this.tokens.uid)
|
|
212
|
+
return String(this.tokens.uid);
|
|
213
|
+
const uid = await api.myUid(this);
|
|
214
|
+
this.tokens.uid = uid;
|
|
215
|
+
return uid;
|
|
216
|
+
}
|
|
217
|
+
/** 评论 / 点赞 / 收藏接口 query 里的 uid = md5(数字 uid),失败时为空(上游 _comment_uid)。 */
|
|
218
|
+
async commentUid() {
|
|
219
|
+
try {
|
|
220
|
+
return md5Hex(await this.uid());
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
return '';
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
get isLogin() {
|
|
227
|
+
return Boolean(this.cookie('sessionid') || this.cookie('sessionid_ss'));
|
|
228
|
+
}
|
|
229
|
+
// ---------------------------------------------------------------- bd-ticket-guard 与 dtrait
|
|
230
|
+
get privateKey() {
|
|
231
|
+
return this.device.private_key || undefined;
|
|
232
|
+
}
|
|
233
|
+
/** ts_sign 与 cookie 里的 bd_ticket_guard_ts_sign_id 是否同属一次登录。 */
|
|
234
|
+
ticketMatchesSession() {
|
|
235
|
+
const tsSign = this.tokens.ts_sign;
|
|
236
|
+
if (!tsSign)
|
|
237
|
+
return false;
|
|
238
|
+
const id = this.cookie('bd_ticket_guard_ts_sign_id');
|
|
239
|
+
return !id || tsSign.startsWith(id);
|
|
240
|
+
}
|
|
241
|
+
/** ECDH 会话密钥,按 (aid, origin) 缓存;拿不到服务端证书时为 null,调用方回退 ECDSA。 */
|
|
242
|
+
async ecdhKey(aid, origin) {
|
|
243
|
+
const prv = this.privateKey;
|
|
244
|
+
if (!prv)
|
|
245
|
+
return null;
|
|
246
|
+
const key = `${aid}|${origin}`;
|
|
247
|
+
if (this.ecdh.has(key))
|
|
248
|
+
return this.ecdh.get(key);
|
|
249
|
+
let out = null;
|
|
250
|
+
try {
|
|
251
|
+
const cert = await api.serverCert(this, aid, this.cookieStr, origin);
|
|
252
|
+
out = ecdhKey(prv, cert);
|
|
253
|
+
}
|
|
254
|
+
catch (err) {
|
|
255
|
+
this.ctx.log.debug(`取服务端证书失败,回退 ECDSA:${err.message}`);
|
|
256
|
+
}
|
|
257
|
+
this.ecdh.set(key, out);
|
|
258
|
+
return out;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* x-tt-session-dtrait 的内层设备特征 blob(上游 session_dtrait_header 的取法):导入的 dtrait_blob 优先,
|
|
262
|
+
* 其次按凭证里的 dtrait_profile 现算,都没有时按随包的默认档案现算。档案无效时为 null。
|
|
263
|
+
*/
|
|
264
|
+
dtraitBlob() {
|
|
265
|
+
const blob = this.device.dtrait_blob;
|
|
266
|
+
if (blob)
|
|
267
|
+
return blob;
|
|
268
|
+
try {
|
|
269
|
+
return buildBlob(this.device.dtrait_profile ?? defaultProfile());
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
this.ctx.log.debug(`dtrait 设备档案不可用:${err.message}`);
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* 按 path 生成 x-tt-session-dtrait;算不出 blob 时为 null(strict 时报错)。
|
|
278
|
+
* 导入的成品头 session_dtrait 绑定了抓取时的 path 和时间,只给非严格、allowStatic 的请求用。
|
|
279
|
+
*/
|
|
280
|
+
dtraitHeader(path, options = {}) {
|
|
281
|
+
const { aid = 6383, origin = WWW, strict = false, allowStatic = true } = options;
|
|
282
|
+
const blob = this.dtraitBlob();
|
|
283
|
+
const staticHeader = this.device.session_dtrait;
|
|
284
|
+
if (!blob && staticHeader && allowStatic && !strict)
|
|
285
|
+
return staticHeader;
|
|
286
|
+
if (!blob) {
|
|
287
|
+
if (strict)
|
|
288
|
+
throw new SpecificAIError('AUTH_REQUIRED', `缺少可按 path 重算的 dtrait 设备素材(${DTRAIT_BROKEN}),这个操作不能省略 x-tt-session-dtrait`, { hint: DTRAIT_HINT });
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
const cacheKey = `${aid}|${origin}`;
|
|
292
|
+
const { header, material } = sessionDtrait(path, blob, this.dtrait.get(cacheKey) ?? null);
|
|
293
|
+
this.dtrait.set(cacheKey, material);
|
|
294
|
+
return header;
|
|
295
|
+
}
|
|
296
|
+
/** 写接口的 x-tt-session-dtrait(上游 Header.with_session_dtrait):必须按 path 重算,不接受成品头,算不出时报错。 */
|
|
297
|
+
withSessionDtrait(h, api, options = {}) {
|
|
298
|
+
h.set('x-tt-session-dtrait', this.dtraitHeader(api, { ...options, strict: true, allowStatic: false }));
|
|
299
|
+
}
|
|
300
|
+
/** 写接口的 bd-ticket-guard 头(上游 Header.with_bd)。 */
|
|
301
|
+
async withBd(h, api, options = {}) {
|
|
302
|
+
const { aid = 6383, origin = WWW, requireDtrait = false } = options;
|
|
303
|
+
if (!this.ticketMatchesSession()) {
|
|
304
|
+
throw authError(this.ctx, '当前账号缺少与 cookie 同一次登录的 ticket / ts_sign,写操作会被拒绝;请用扫码登录,或导入带 ticket 的凭证');
|
|
305
|
+
}
|
|
306
|
+
const prv = this.privateKey;
|
|
307
|
+
if (!prv)
|
|
308
|
+
throw authError(this.ctx, '当前账号缺少 bd-ticket-guard 私钥,请重新扫码登录');
|
|
309
|
+
const key = await this.ecdhKey(aid, origin);
|
|
310
|
+
const trust = this.cookie('_bd_ticket_crypt_cookie');
|
|
311
|
+
const tsSign = String(this.tokens.ts_sign);
|
|
312
|
+
const { data, algo } = ticketClientData(api, String(this.tokens.ticket ?? ''), tsSign, prv, key, rand.nowSeconds(), trust ? 1 : undefined);
|
|
313
|
+
h.set('bd-ticket-guard-client-data', data);
|
|
314
|
+
h.set('bd-ticket-guard-ree-public-key', reeKey(prv));
|
|
315
|
+
h.set('bd-ticket-guard-version', '2');
|
|
316
|
+
h.set('bd-ticket-guard-web-version', String(ticketGuardVersion(tsSign)));
|
|
317
|
+
h.set('bd-ticket-guard-web-sign-type', algo === 'hmac' ? '1' : '0');
|
|
318
|
+
const dtrait = this.dtraitHeader(api, { aid, origin, strict: requireDtrait, allowStatic: !requireDtrait });
|
|
319
|
+
if (dtrait)
|
|
320
|
+
h.set('x-tt-session-dtrait', dtrait);
|
|
321
|
+
}
|
|
322
|
+
/** 只读接口的 4 个 bd-ticket-guard 头,不含 client-data(上游 with_bd_readonly)。 */
|
|
323
|
+
withBdReadonly(h) {
|
|
324
|
+
const prv = this.privateKey;
|
|
325
|
+
if (!prv)
|
|
326
|
+
return;
|
|
327
|
+
try {
|
|
328
|
+
h.set('bd-ticket-guard-ree-public-key', reeKey(prv));
|
|
329
|
+
h.set('bd-ticket-guard-version', '2');
|
|
330
|
+
h.set('bd-ticket-guard-web-version', String(ticketGuardVersion(String(this.tokens.ts_sign ?? ''))));
|
|
331
|
+
h.set('bd-ticket-guard-web-sign-type', String(this.tokens.client_cert ?? '').startsWith('pub.') ? '1' : '0');
|
|
332
|
+
}
|
|
333
|
+
catch { }
|
|
334
|
+
}
|
|
335
|
+
/** 写操作前检查登录态(registry 已保证不是游客,这里防 cookie 不全)。 */
|
|
336
|
+
requireLogin() {
|
|
337
|
+
if (!this.isLogin)
|
|
338
|
+
throw authError(this.ctx, '当前账号的 cookie 里没有 sessionid,请重新登录');
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
function domainMatch(host, domain) {
|
|
342
|
+
return domain.startsWith('.') ? host === domain.slice(1) || host.endsWith(domain) : host === domain;
|
|
343
|
+
}
|
|
344
|
+
function headerList(init) {
|
|
345
|
+
if (!init)
|
|
346
|
+
return [];
|
|
347
|
+
return Array.isArray(init) ? init : Object.entries(init);
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* 抖音风控拒绝时返回 HTTP 200 + 空 body 或 HTML 挑战页,原因只在响应头里(上游 check_risk_response)。
|
|
351
|
+
*/
|
|
352
|
+
export async function riskJson(res) {
|
|
353
|
+
const text = await res.text();
|
|
354
|
+
const trimmed = text.trimStart();
|
|
355
|
+
if (trimmed.startsWith('{') || trimmed.startsWith('['))
|
|
356
|
+
return jsonLoads(text);
|
|
357
|
+
const logid = res.headers.get('x-tt-logid');
|
|
358
|
+
const bd = res.headers.get('x-vc-bdturing-parameters');
|
|
359
|
+
if (bd) {
|
|
360
|
+
let subtype = '';
|
|
361
|
+
try {
|
|
362
|
+
subtype = jsonLoads(Buffer.from(bd, 'base64').toString('utf8')).subtype ?? '';
|
|
363
|
+
}
|
|
364
|
+
catch { }
|
|
365
|
+
throw new SpecificAIError('RISK_CONTROL', `触发人机验证(bdturing ${subtype || '未知类型'}),请在浏览器完成验证或更换 IP、降低频率后重试`, {
|
|
366
|
+
detail: { kind: 'captcha', subtype, logid },
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
const pp = res.headers.get('x-tt-verify-passport-decision');
|
|
370
|
+
if (pp) {
|
|
371
|
+
let scene = '';
|
|
372
|
+
try {
|
|
373
|
+
scene = jsonLoads(pp).event_params.verify_scene ?? '';
|
|
374
|
+
}
|
|
375
|
+
catch { }
|
|
376
|
+
throw new SpecificAIError('RISK_CONTROL', `需要二次身份验证(scene=${scene || '未知'}),多为缺少 x-tt-session-dtrait 或账号风控`, {
|
|
377
|
+
detail: { kind: 'blocked', scene, logid },
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
if (!trimmed) {
|
|
381
|
+
throw new SpecificAIError('RISK_CONTROL', `接口返回空响应(HTTP ${res.status}),通常是签名参数不对或被风控拦截`, { detail: { kind: 'blocked', status: res.status, logid } });
|
|
382
|
+
}
|
|
383
|
+
if (text.includes('__ac_nonce') || text.includes('_$jsvmprt')) {
|
|
384
|
+
throw new SpecificAIError('RISK_CONTROL', `命中 acrawler 挑战页(HTTP ${res.status})`, { detail: { kind: 'blocked', status: res.status, logid } });
|
|
385
|
+
}
|
|
386
|
+
if (/Uifid Not Found/i.test(text)) {
|
|
387
|
+
throw new SpecificAIError('RISK_CONTROL', '这个接口要求页面脚本写入的 UIFID cookie,游客态和缺 UIFID 的 cookie 都会被拦截', {
|
|
388
|
+
hint: '用浏览器登录后导出 cookie:specific-ai douyin auth login --method cookie --cookie @<文件>',
|
|
389
|
+
detail: { kind: 'blocked', reason: 'uifid', status: res.status, body: text.slice(0, 120), logid },
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
throw new SpecificAIError('UPSTREAM', `接口返回的不是 JSON(HTTP ${res.status})`, { detail: { status: res.status, body: text.slice(0, 120), logid } });
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* 业务码检查:status_code(主站、直播)或 code / msg(电商接口)非 0 时映射成 specific-ai 的错误。
|
|
396
|
+
* 登录墙:8、2483,以及直播 webcast 的 20003(User doesn't login,上游 get_live_thousand_ticket_rank 的说明),
|
|
397
|
+
* webcast 的错误说明在 data.message 里。
|
|
398
|
+
*/
|
|
399
|
+
export function check(ctx, body) {
|
|
400
|
+
const code = body?.status_code ?? (body?.code != null && body.code !== 0 && body.code !== '0' && body.msg != null ? body.code : undefined);
|
|
401
|
+
if (code == null || code === 0)
|
|
402
|
+
return body;
|
|
403
|
+
const message = String(body.status_msg ?? body.msg ?? body.message ?? (typeof body.data?.message === 'string' ? body.data.message : ''));
|
|
404
|
+
if (code === 8 || code === 2483 || code === 20003 || /未登录|登录/.test(message))
|
|
405
|
+
throw authError(ctx, message || undefined);
|
|
406
|
+
throw new SpecificAIError('UPSTREAM', message || `抖音返回错误 ${code}`, { detail: { code, message } });
|
|
407
|
+
}
|
|
408
|
+
/** 建立会话并完成设备初始化。 */
|
|
409
|
+
export async function douyin(ctx) {
|
|
410
|
+
const d = new Douyin(ctx);
|
|
411
|
+
await d.init();
|
|
412
|
+
return d;
|
|
413
|
+
}
|