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,487 @@
|
|
|
1
|
+
import { SpecificAIError } from '../../../core/errors.js';
|
|
2
|
+
import { HttpClient } from '../../../core/http.js';
|
|
3
|
+
import { compactJson, jsonLoads, quote } from '../../../core/py.js';
|
|
4
|
+
import * as rand from '../../../core/rand.js';
|
|
5
|
+
import { authError, scope } from '../../../core/toolkit.js';
|
|
6
|
+
import { BROWSER, buildHeaders, CP, ID_HOST, LIVE, LIVE_SENTRY_BAGGAGE, PRODUCT_CP, PRODUCT_LIVE, PRODUCT_WWW, PUBLISH_REFERER, RECO_REFERER, WWW, } from './profile.js';
|
|
7
|
+
import { graphqlRiskAsRest, isRisk, passCaptcha } from './captcha.js';
|
|
8
|
+
import { reportFingerprint, Session } from './session.js';
|
|
9
|
+
import { axiosQuery, buildSignInput, CAVER, CP_PROJECT_INFO, cpNeedSign, HxFalconSigner, liveNeedSign, liveSignUrl, loginNeedSign, needSig3, needSign, SDK_VERSION_LIVE, Sig3Signer, } from './sign.js';
|
|
10
|
+
const WEAPON_EPHEMERAL = new Set(['kwscode', 'kwssectoken']);
|
|
11
|
+
const COOKIE_DOMAIN = '.kuaishou.com';
|
|
12
|
+
/** 子站点分区:创作者中心(cp)、直播(live),其余(www、passport、设备)在 main。 */
|
|
13
|
+
function scopeOf(name) {
|
|
14
|
+
if (name.startsWith('kuaishou.web.cp.') || name === 'ks_onvideo_token')
|
|
15
|
+
return 'cp';
|
|
16
|
+
if (name.startsWith('kuaishou.live.') || name === 'client_key')
|
|
17
|
+
return 'live';
|
|
18
|
+
return 'main';
|
|
19
|
+
}
|
|
20
|
+
/** 凭证里的 cookie(main → cp → live),不含 6 分钟票据。 */
|
|
21
|
+
export function credentialCookies(credential) {
|
|
22
|
+
const out = [];
|
|
23
|
+
for (const name of ['main', 'cp', 'live']) {
|
|
24
|
+
for (const c of credential.scopes[name]?.cookies ?? [])
|
|
25
|
+
if (!WEAPON_EPHEMERAL.has(c.name))
|
|
26
|
+
out.push([c.name, c.value]);
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
export class Ks {
|
|
31
|
+
ctx;
|
|
32
|
+
http;
|
|
33
|
+
log;
|
|
34
|
+
s;
|
|
35
|
+
/** www 与 cp 共用的 sig4 引擎(上游 _hxfalcon_signer)。 */
|
|
36
|
+
falcon = new HxFalconSigner();
|
|
37
|
+
falconLive = new HxFalconSigner(SDK_VERSION_LIVE);
|
|
38
|
+
falconLogin = new HxFalconSigner();
|
|
39
|
+
sig3 = new Sig3Signer();
|
|
40
|
+
/** 最近一次自动过滑块没通过时服务端的回复(报 RISK_CONTROL 时放进 detail.verify)。 */
|
|
41
|
+
lastCaptcha = null;
|
|
42
|
+
/** CP 发布权限预检的缓存(_cp_publish_authority_response)。 */
|
|
43
|
+
cpAuthority = null;
|
|
44
|
+
cpLastVideoFinish = null;
|
|
45
|
+
constructor(ctx) {
|
|
46
|
+
this.ctx = ctx;
|
|
47
|
+
this.http = new HttpClient({ browser: BROWSER, os: 'windows', proxy: ctx.config.proxy, timeout: ctx.config.timeout, log: ctx.log });
|
|
48
|
+
this.log = ctx.log;
|
|
49
|
+
this.s = new Session(this);
|
|
50
|
+
}
|
|
51
|
+
// ---------------------------------------------------------------- 会话
|
|
52
|
+
/**
|
|
53
|
+
* 相当于上游的 `KuaishouAuth.initialize(cookie_str)`:解析凭证 cookie → 物化 webweapon 票据
|
|
54
|
+
* → 没有 wid 时做一次设备指纹上报 → 回到原来的站点。游客第一次运行时 cookie 为空,这一步同时生成设备身份。
|
|
55
|
+
*/
|
|
56
|
+
async init(cookies = credentialCookies(this.ctx.credential)) {
|
|
57
|
+
const s = this.s;
|
|
58
|
+
s.prepare(cookies);
|
|
59
|
+
s.restoreCounter(String(this.ctx.credential.device.kwfcv1 ?? ''));
|
|
60
|
+
s.liveBootstrapEnabled = true;
|
|
61
|
+
s.ensureBrowserDefaults();
|
|
62
|
+
await s.current();
|
|
63
|
+
const previous = s.cookies.get('kwpsecproductname') ?? PRODUCT_WWW;
|
|
64
|
+
await bootstrapDeviceFingerprint(this);
|
|
65
|
+
s.useSite(previous === PRODUCT_CP ? CP : previous === PRODUCT_LIVE ? LIVE : WWW);
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
/** 会话状态写回凭证(由 core 落盘):kwscode / kwssectoken 不落盘;product 归位到 www。 */
|
|
69
|
+
save(credential = this.ctx.credential) {
|
|
70
|
+
const parts = { main: [], cp: [], live: [] };
|
|
71
|
+
for (const [name, value] of this.s.cookies) {
|
|
72
|
+
if (WEAPON_EPHEMERAL.has(name) || !name)
|
|
73
|
+
continue;
|
|
74
|
+
const v = name === 'kwpsecproductname' ? PRODUCT_WWW : value;
|
|
75
|
+
parts[scopeOf(name)].push({ name, value: v, domain: COOKIE_DOMAIN, path: '/', expires: null });
|
|
76
|
+
}
|
|
77
|
+
for (const [name, cookies] of Object.entries(parts)) {
|
|
78
|
+
if (name !== 'main' && !cookies.length && !credential.scopes[name])
|
|
79
|
+
continue;
|
|
80
|
+
scope(credential, name).cookies = cookies;
|
|
81
|
+
}
|
|
82
|
+
credential.device.kwfcv1 = this.s.kwfcv1;
|
|
83
|
+
}
|
|
84
|
+
// ---------------------------------------------------------------- 发送
|
|
85
|
+
async send(req) {
|
|
86
|
+
const headers = req.cookie ? [...req.headers, ['cookie', req.cookie]] : req.headers;
|
|
87
|
+
return this.http.request({
|
|
88
|
+
method: req.method,
|
|
89
|
+
url: req.url,
|
|
90
|
+
query: req.query,
|
|
91
|
+
headers,
|
|
92
|
+
body: req.body,
|
|
93
|
+
cookies: req.cookie ? undefined : false,
|
|
94
|
+
redirect: req.redirect,
|
|
95
|
+
timeout: req.timeout,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
async json(res) {
|
|
99
|
+
const text = await res.text();
|
|
100
|
+
try {
|
|
101
|
+
return jsonLoads(text);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
throw new SpecificAIError('UPSTREAM', `快手返回的不是 JSON(HTTP ${res.status})`, { detail: { status: res.status, body: text.slice(0, 300) } });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// ---------------------------------------------------------------- www REST / GraphQL(kuaishou_api.py)
|
|
108
|
+
/** KuaishouAPI._post:JSON body,kww 头,白名单接口带 __NS_hxfalcon。 */
|
|
109
|
+
async wwwPost(api, body, o = {}) {
|
|
110
|
+
const referer = o.referer ?? RECO_REFERER;
|
|
111
|
+
const h = buildHeaders('POST');
|
|
112
|
+
h.set('referer', referer);
|
|
113
|
+
h.set('origin', WWW);
|
|
114
|
+
const kww = await this.s.kww();
|
|
115
|
+
if (kww)
|
|
116
|
+
h.set('kww', kww);
|
|
117
|
+
for (const [k, v] of o.extraHeaders ?? [])
|
|
118
|
+
h.set(k.toLowerCase(), v);
|
|
119
|
+
if (body == null)
|
|
120
|
+
h.remove('content-type');
|
|
121
|
+
const params = [];
|
|
122
|
+
if (needSign(api)) {
|
|
123
|
+
params.push(['__NS_hxfalcon', this.falcon.sign(buildSignInput(api, {}, body, 'application/json', { omitEmptyBody: true }))], ['caver', CAVER]);
|
|
124
|
+
}
|
|
125
|
+
const data = body == null ? undefined : compactJson(body);
|
|
126
|
+
const site = o.cookieProfile ?? 'www';
|
|
127
|
+
const query = axiosQuery(params);
|
|
128
|
+
const url = `${WWW}${api}${query ? '?' + query : ''}`;
|
|
129
|
+
const post = async () => this.json(await this.send({ method: 'POST', url, headers: h.get(), cookie: await this.s.cookieHeader(site), body: data }));
|
|
130
|
+
let result = await post();
|
|
131
|
+
// 撞上滑块风控就过一次验证码;验证可能轮换短期票据,重发前重新序列化同一条 Cookie 线序
|
|
132
|
+
if (isRisk(result) && (await passCaptcha(this, result, referer)))
|
|
133
|
+
result = await post();
|
|
134
|
+
if (api === '/rest/v/profile/feed' && this.s.wwwPhase !== 'relogin')
|
|
135
|
+
this.s.wwwPhase = 'refreshed';
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
/** KuaishouAPI._get:只有 profile/get。撞上滑块同 wwwPost。 */
|
|
139
|
+
async wwwGet(api, o = {}) {
|
|
140
|
+
const referer = o.referer ?? RECO_REFERER;
|
|
141
|
+
const h = buildHeaders('GET');
|
|
142
|
+
h.set('referer', referer);
|
|
143
|
+
const kww = await this.s.kww();
|
|
144
|
+
if (kww)
|
|
145
|
+
h.set('kww', kww);
|
|
146
|
+
const params = [];
|
|
147
|
+
if (needSign(api))
|
|
148
|
+
params.push(['__NS_hxfalcon', this.falcon.sign(buildSignInput(api, {}, null, 'application/json', { omitEmptyBody: true }))], ['caver', CAVER]);
|
|
149
|
+
const query = axiosQuery(params);
|
|
150
|
+
const url = `${WWW}${api}${query ? '?' + query : ''}`;
|
|
151
|
+
const get = async () => this.json(await this.send({ method: 'GET', url, headers: h.get(), cookie: await this.s.cookieHeader('www') }));
|
|
152
|
+
let result = await get();
|
|
153
|
+
if (isRisk(result) && (await passCaptcha(this, result, referer)))
|
|
154
|
+
result = await get();
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
/** KuaishouAPI.graphql:POST /graphql,不签名;短视频详情页按 operation 选 Cookie 线序。 */
|
|
158
|
+
async graphql(operation, variables, query, referer, cookieSite = 'www_graphql') {
|
|
159
|
+
const h = buildHeaders('POST', 'www', '*/*');
|
|
160
|
+
h.set('referer', referer);
|
|
161
|
+
h.set('origin', WWW);
|
|
162
|
+
const kww = await this.s.kww();
|
|
163
|
+
if (kww)
|
|
164
|
+
h.set('kww', kww);
|
|
165
|
+
const data = compactJson({ operationName: operation, variables, query });
|
|
166
|
+
let site = cookieSite;
|
|
167
|
+
const path = new URL(referer).pathname;
|
|
168
|
+
if ((cookieSite === 'www_graphql' || cookieSite === 'www_graphql_detail') && path.startsWith('/short-video/')) {
|
|
169
|
+
const shape = `${operation}(${Object.keys(variables).join(',')})`;
|
|
170
|
+
const bootstrap = ['visionLoginConfig(key)', 'checkLoginQuery()', 'visionShortVideoReco(page,photoId)', 'commentListQuery(photoId,pcursor)', 'visionBaseEmoticons()'];
|
|
171
|
+
if (shape === 'visionVideoDetail(photoId,page)')
|
|
172
|
+
site = 'www_graphql_detail_video_success';
|
|
173
|
+
else if (operation === 'commentListQuery' && this.s.history.length)
|
|
174
|
+
site = 'www_graphql_comment_initial';
|
|
175
|
+
else if (bootstrap.includes(shape))
|
|
176
|
+
site = 'www_graphql_detail_bootstrap';
|
|
177
|
+
else if (operation === 'visionSubCommentList')
|
|
178
|
+
site = 'www_graphql_subcomment_initial';
|
|
179
|
+
else if (shape === 'visionConfigQuery()')
|
|
180
|
+
site = 'www_graphql_detail_refreshed';
|
|
181
|
+
else
|
|
182
|
+
site = 'www_graphql_detail_initial';
|
|
183
|
+
}
|
|
184
|
+
const post = async () => this.json(await this.send({ method: 'POST', url: `${WWW}/graphql`, headers: h.get(), cookie: await this.s.cookieHeader(site), body: data }));
|
|
185
|
+
let result = await post();
|
|
186
|
+
// GraphQL 侧(评论、详情)同样会撞风控:挑战改成 REST 的形状交给同一个求解器
|
|
187
|
+
if (isRisk(result) && (await passCaptcha(this, graphqlRiskAsRest(result), referer)))
|
|
188
|
+
result = await post();
|
|
189
|
+
checkGraphql(this.ctx, result, this.lastCaptcha);
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
192
|
+
// ---------------------------------------------------------------- 登录站(login_api.py)
|
|
193
|
+
/**
|
|
194
|
+
* KuaishouLoginAPI._post_form:form-urlencoded,按传入顺序;名单内接口带 __NS_hxfalcon(form 参与签名);
|
|
195
|
+
* cookie 为整份 `auth.cookie`。
|
|
196
|
+
*/
|
|
197
|
+
async loginForm(api, fields, pageOrigin, timeout) {
|
|
198
|
+
const body = fields.map(([k, v]) => `${k}=${quote(v, '')}`).join('&');
|
|
199
|
+
let url = `${ID_HOST}${api}`;
|
|
200
|
+
if (loginNeedSign(api)) {
|
|
201
|
+
const sig = this.falconLogin.sign(buildSignInput(api, {}, Object.fromEntries(fields), 'application/x-www-form-urlencoded'));
|
|
202
|
+
url = `${url}?__NS_hxfalcon=${sig}&caver=${CAVER}`;
|
|
203
|
+
}
|
|
204
|
+
const h = buildHeaders('POST', 'login');
|
|
205
|
+
h.set('referer', `${pageOrigin}/`);
|
|
206
|
+
h.set('origin', pageOrigin);
|
|
207
|
+
h.set('sec-fetch-site', 'same-site');
|
|
208
|
+
const kww = await this.s.kww();
|
|
209
|
+
if (kww)
|
|
210
|
+
h.set('kww', kww);
|
|
211
|
+
const cookie = await this.s.currentLine();
|
|
212
|
+
const res = await this.send({ method: 'POST', url, headers: h.get(), cookie, body, timeout });
|
|
213
|
+
const data = await res
|
|
214
|
+
.clone()
|
|
215
|
+
.json()
|
|
216
|
+
.catch(() => ({}));
|
|
217
|
+
return [data, res];
|
|
218
|
+
}
|
|
219
|
+
// ---------------------------------------------------------------- 直播站(live_api.py)
|
|
220
|
+
/** 房间 / 首页 / 主播页对应的当前 Cookie 线序(_current_live_profile)。 */
|
|
221
|
+
liveProfile(referer) {
|
|
222
|
+
const context = liveContext(referer);
|
|
223
|
+
const phase = this.s.livePhase[context] ?? 'initial';
|
|
224
|
+
if (context === 'room')
|
|
225
|
+
return this.roomProfile(phase);
|
|
226
|
+
if (context === 'home')
|
|
227
|
+
return this.homeProfile(phase);
|
|
228
|
+
return `live_${context}_${phase}`;
|
|
229
|
+
}
|
|
230
|
+
roomProfile(phase) {
|
|
231
|
+
return this.s.liveOnly && ['initial', 'login', 'authenticated'].includes(phase) ? `live_room_current_${phase}` : `live_room_${phase}`;
|
|
232
|
+
}
|
|
233
|
+
homeProfile(phase) {
|
|
234
|
+
const current = ['initial', 'login', 'login_bootstrap', 'authenticated_1', 'authenticated_2', 'authenticated_3'];
|
|
235
|
+
return this.s.liveOnly && current.includes(phase) ? `live_home_current_${phase}` : `live_home_${phase}`;
|
|
236
|
+
}
|
|
237
|
+
/** KuaishouLiveAPI._get / _post:axios 头、Sentry 头、kww;名单内接口按 realUrl 签 sig4(query 排序、body 不参与)。 */
|
|
238
|
+
async live(method, api, o) {
|
|
239
|
+
const sentry = o.sentry ?? PAGE_LOAD_SENTRY.has(api);
|
|
240
|
+
const h = buildHeaders(method, 'live');
|
|
241
|
+
h.set('referer', o.referer);
|
|
242
|
+
if (method === 'POST')
|
|
243
|
+
h.set('origin', LIVE);
|
|
244
|
+
if (sentry) {
|
|
245
|
+
const [trace, span] = this.s.nextLiveSentry(o.referer);
|
|
246
|
+
h.set('sentry-trace', `${trace}-${span}-0`);
|
|
247
|
+
h.set('baggage', LIVE_SENTRY_BAGGAGE);
|
|
248
|
+
}
|
|
249
|
+
const kww = await this.s.kww();
|
|
250
|
+
if (kww)
|
|
251
|
+
h.set('kww', kww);
|
|
252
|
+
const profile = o.cookieProfile ?? this.liveProfile(o.referer);
|
|
253
|
+
const cookie = await this.s.cookieHeader(profile);
|
|
254
|
+
const signed = liveNeedSign(api);
|
|
255
|
+
let query = o.query ?? [];
|
|
256
|
+
if (signed && query.length)
|
|
257
|
+
query = [...query].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
258
|
+
const params = [];
|
|
259
|
+
if (signed) {
|
|
260
|
+
const sig = this.falconLive.sign(buildSignInput(liveSignUrl(api), Object.fromEntries(query), null));
|
|
261
|
+
params.push(['__NS_hxfalcon', sig], ['caver', CAVER]);
|
|
262
|
+
}
|
|
263
|
+
params.push(...query);
|
|
264
|
+
const qs = axiosQuery(params);
|
|
265
|
+
const data = method === 'POST' && o.body != null ? compactJson(o.body) : undefined;
|
|
266
|
+
const res = await this.send({ method, url: `${LIVE}${api}${qs ? '?' + qs : ''}`, headers: h.get(), cookie, body: data });
|
|
267
|
+
const result = await res
|
|
268
|
+
.clone()
|
|
269
|
+
.json()
|
|
270
|
+
.catch(() => ({}));
|
|
271
|
+
return [result, res];
|
|
272
|
+
}
|
|
273
|
+
// ---------------------------------------------------------------- 创作者中心(publish_api.py)
|
|
274
|
+
/**
|
|
275
|
+
* KuaishouPublishAPI._post:body 末尾追加 api_ph;cp 大多数接口走 sig3,少数走 sig4(带 projectInfo);
|
|
276
|
+
* 上传链路先切到对应的 webweapon 上下文。
|
|
277
|
+
*/
|
|
278
|
+
async cpPost(api, body = {}, o = {}) {
|
|
279
|
+
let upload = o.upload;
|
|
280
|
+
if (!upload)
|
|
281
|
+
upload = CP_ATLAS_PATHS.has(api) ? 'atlas' : CP_VIDEO_PATHS.has(api) ? 'video' : undefined;
|
|
282
|
+
if (upload === 'atlas')
|
|
283
|
+
this.s.useCpAtlas();
|
|
284
|
+
else if (upload === 'video')
|
|
285
|
+
await this.s.useCpUpload();
|
|
286
|
+
const merged = { ...body };
|
|
287
|
+
if (this.s.cpApiPh)
|
|
288
|
+
merged['kuaishou.web.cp.api_ph'] = this.s.cpApiPh;
|
|
289
|
+
const withKww = o.withKww ?? true;
|
|
290
|
+
const http11 = CP_HTTP11_PATHS.has(api);
|
|
291
|
+
const style = http11 ? 'cp_submit' : (o.style ?? 'cp');
|
|
292
|
+
const h = buildHeaders('POST', style);
|
|
293
|
+
h.set('referer', o.referer ?? PUBLISH_REFERER);
|
|
294
|
+
h.set('origin', CP);
|
|
295
|
+
if (withKww) {
|
|
296
|
+
const kww = await this.s.kww();
|
|
297
|
+
if (kww)
|
|
298
|
+
h.set('kww', kww);
|
|
299
|
+
}
|
|
300
|
+
if (o.creator) {
|
|
301
|
+
h.set('returnsetrootdomainloginurl', 'true');
|
|
302
|
+
if (style === 'cp_creator_axios')
|
|
303
|
+
h.set('x-requested-with', 'XMLHttpRequest');
|
|
304
|
+
}
|
|
305
|
+
const params = [];
|
|
306
|
+
if (cpNeedSign(api)) {
|
|
307
|
+
const sig = this.falcon.sign(buildSignInput(api, {}, merged, 'application/json', { projectInfo: CP_PROJECT_INFO, omitEmptyBody: true }));
|
|
308
|
+
params.push(['__NS_hxfalcon', sig], ['caver', CAVER]);
|
|
309
|
+
}
|
|
310
|
+
else if (needSig3(api)) {
|
|
311
|
+
params.push(['__NS_sig3', this.sig3.sign({}, merged, 'json')]);
|
|
312
|
+
}
|
|
313
|
+
const data = compactJson(merged);
|
|
314
|
+
// 上游在这两个接口上还显式带 connection / content-length / host:它们是传输层字段,交给 HTTP 库生成
|
|
315
|
+
const site = o.wireSite ?? (http11 ? 'cp_video_submit' : upload === 'atlas' ? 'cp_atlas' : upload === 'video' ? 'cp_upload' : withKww ? 'cp' : 'cp_creator');
|
|
316
|
+
const cookie = await this.s.cookieHeader(site);
|
|
317
|
+
const qs = axiosQuery(params);
|
|
318
|
+
const res = await this.send({ method: 'POST', url: `${CP}${api}${qs ? '?' + qs : ''}`, headers: h.get(), cookie, body: data });
|
|
319
|
+
const result = await this.json(res);
|
|
320
|
+
if (o.creator && !withKww) {
|
|
321
|
+
if (api === '/rest/bamboo/pc/hotspot/show')
|
|
322
|
+
this.s.cpPhase = 'warmed';
|
|
323
|
+
else if (api === '/rest/cp/works/v2/collection/tab')
|
|
324
|
+
this.s.cpPhase = 'refreshed';
|
|
325
|
+
}
|
|
326
|
+
return result;
|
|
327
|
+
}
|
|
328
|
+
// ---------------------------------------------------------------- 结果检查
|
|
329
|
+
/** www REST / cp 的 `result`;1 以外映射成 specific-ai 错误。 */
|
|
330
|
+
check(body, what = '快手') {
|
|
331
|
+
const risk = riskOf(body, this.lastCaptcha);
|
|
332
|
+
if (risk)
|
|
333
|
+
throw risk;
|
|
334
|
+
const code = body?.result;
|
|
335
|
+
if (code === 1 || code === undefined)
|
|
336
|
+
return body;
|
|
337
|
+
if (code === 109)
|
|
338
|
+
throw authError(this.ctx, `${what}需要登录(result=${code})`);
|
|
339
|
+
// result=2:上游没有定义。实测(2026-09-28)登录态正常、其他接口可用时,用户搜索在频繁触发滑块后返回 2,按风控处理
|
|
340
|
+
if (code === 2) {
|
|
341
|
+
throw new SpecificAIError('RISK_CONTROL', `${what}被快手限制(result=2),多为风控,稍后再试`, {
|
|
342
|
+
hint: `specific-ai ${this.ctx.platform.id} auth status 可确认登录态是否正常`,
|
|
343
|
+
detail: { kind: 'blocked', result: code },
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
throw new SpecificAIError('UPSTREAM', String(body?.error_msg ?? body?.message ?? `${what}返回错误 ${code}`), { detail: { result: code } });
|
|
347
|
+
}
|
|
348
|
+
/** 直播站 `{data: {result}}`。 */
|
|
349
|
+
checkLive(body) {
|
|
350
|
+
const risk = riskOf(body);
|
|
351
|
+
if (risk)
|
|
352
|
+
throw risk;
|
|
353
|
+
const code = body?.data?.result;
|
|
354
|
+
if (code === undefined || code === 1)
|
|
355
|
+
return body?.data ?? {};
|
|
356
|
+
if (code === 2)
|
|
357
|
+
throw new SpecificAIError('RISK_CONTROL', '快手直播风控拦截(result=2),浏览器打开同一直播间也需要先过滑块验证', { detail: { kind: 'blocked', result: code } });
|
|
358
|
+
if (code === 109)
|
|
359
|
+
throw authError(this.ctx, '直播站需要登录');
|
|
360
|
+
throw new SpecificAIError('UPSTREAM', String(body?.data?.error_msg ?? `快手直播返回错误 ${code}`), { detail: { result: code } });
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/** 页面加载期间带 Sentry 头的直播接口(page_load_sentry_paths)。 */
|
|
364
|
+
const PAGE_LOAD_SENTRY = new Set([
|
|
365
|
+
'/live_api/baseuser/userinfo',
|
|
366
|
+
'/live_api/category/classify',
|
|
367
|
+
'/live_api/web/pay/get-pay',
|
|
368
|
+
'/live_api/baseuser/userFollowCount',
|
|
369
|
+
'/live_api/category/simple',
|
|
370
|
+
'/live_api/interestMask/list',
|
|
371
|
+
'/live_api/emoji/gift-list',
|
|
372
|
+
'/live_api/emoji/icon',
|
|
373
|
+
'/live_api/emoji/allgifts',
|
|
374
|
+
'/live_api/liveroom/reco',
|
|
375
|
+
'/live_api/baseuser/userLogin',
|
|
376
|
+
'/live_api/liveroom/recall',
|
|
377
|
+
'/live_api/liveroom/websocketinfo',
|
|
378
|
+
'/live_api/home/list',
|
|
379
|
+
'/live_api/home/category',
|
|
380
|
+
'/live_api/profile/public',
|
|
381
|
+
'/live_api/baseuser/userinfo/byid',
|
|
382
|
+
'/live_api/baseuser/userinfo/sensitive',
|
|
383
|
+
'/live_api/profileInterestMask/list',
|
|
384
|
+
'/live_api/profile/interestlist',
|
|
385
|
+
]);
|
|
386
|
+
const CP_VIDEO_PATHS = new Set([
|
|
387
|
+
'/rest/wd/relation/isFollow',
|
|
388
|
+
'/rest/cp/works/v2/video/pc/realize/entrance',
|
|
389
|
+
'/rest/cp/works/v2/video/pc/upload/pre',
|
|
390
|
+
'/rest/cp/works/v2/video/pc/realize/banner/entrance',
|
|
391
|
+
'/rest/cp/works/v2/video/pc/publish/time',
|
|
392
|
+
'/rest/v2/creator/activity/pc/list',
|
|
393
|
+
'/rest/cp/works/v2/collection/canAdd',
|
|
394
|
+
'/rest/v2/creator/activity/pc/tab',
|
|
395
|
+
'/rest/v2/creator/activity/pc/filter',
|
|
396
|
+
'/rest/cp/works/v2/video/pc/upload/finish',
|
|
397
|
+
'/rest/cp/works/v4/video/pc/upload/material/specified',
|
|
398
|
+
'/rest/cp/works/v4/video/pc/cover/edit/recommend/submit',
|
|
399
|
+
'/rest/cp/works/v4/video/pc/cover/edit/recommend/query',
|
|
400
|
+
'/rest/cp/works/v2/video/pc/publishInfo/snapshot/save',
|
|
401
|
+
'/rest/cp/works/v4/video/pc/upload/cover/extract',
|
|
402
|
+
'/rest/cp/works/v4/video/pc/upload/cover/extract/query',
|
|
403
|
+
'/rest/cp/works/v2/video/pc/submit',
|
|
404
|
+
'/rest/cp/works/v2/common/pc/report',
|
|
405
|
+
]);
|
|
406
|
+
const CP_ATLAS_PATHS = new Set([
|
|
407
|
+
'/rest/cp/works/atlas/pc/publishInfo/snapshot/info',
|
|
408
|
+
'/rest/cp/works/v2/collection/canAddAtlas',
|
|
409
|
+
'/rest/cp/works/atlas/pc/upload/pre',
|
|
410
|
+
'/rest/cp/works/atlas/pc/upload/single/finish',
|
|
411
|
+
'/rest/cp/works/atlas/pc/publishInfo/snapshot/save',
|
|
412
|
+
'/rest/cp/works/atlas/pc/upload/finish',
|
|
413
|
+
'/rest/cp/works/atlas/pc/publish/submit',
|
|
414
|
+
]);
|
|
415
|
+
const CP_HTTP11_PATHS = new Set(['/rest/cp/works/v2/common/pc/report', '/rest/cp/works/v2/video/pc/submit']);
|
|
416
|
+
/** 直播页的上下文:首页、房间页 /u/<eid>、主播页 /profile/<eid>。 */
|
|
417
|
+
export function liveContext(referer) {
|
|
418
|
+
if (referer === `${LIVE}/`)
|
|
419
|
+
return 'home';
|
|
420
|
+
const u = new URL(referer);
|
|
421
|
+
if (/^\/u\/[^/?#]+$/.test(u.pathname))
|
|
422
|
+
return 'room';
|
|
423
|
+
return 'profile';
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* 滑块风控:REST `data.result == 400002`,GraphQL `errors` + `data.captcha.url`。
|
|
427
|
+
* www 的 REST / GraphQL 已经自动过过一次(见 captcha.ts),走到这里说明没过,或者是不自动过的站点(直播、创作者中心)。
|
|
428
|
+
*/
|
|
429
|
+
function riskOf(body, verify = null) {
|
|
430
|
+
if (!isRisk(body))
|
|
431
|
+
return null;
|
|
432
|
+
const data = body?.data ?? {};
|
|
433
|
+
const url = data.result === 400002 ? data.url : data.captcha?.url;
|
|
434
|
+
return new SpecificAIError('RISK_CONTROL', '快手要求滑块验证(风控):自动验证没有通过(直播、创作者中心的接口不自动验证),稍后再试', {
|
|
435
|
+
detail: { kind: 'captcha', url, ...(verify ? { verify } : {}) },
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
function checkGraphql(ctx, body, verify = null) {
|
|
439
|
+
const risk = riskOf(body, verify);
|
|
440
|
+
if (risk)
|
|
441
|
+
throw risk;
|
|
442
|
+
const errors = body?.errors;
|
|
443
|
+
if (Array.isArray(errors) && errors.length) {
|
|
444
|
+
const message = String(errors[0]?.message ?? 'GraphQL 错误');
|
|
445
|
+
if (/login|登录/i.test(message))
|
|
446
|
+
throw authError(ctx, message);
|
|
447
|
+
throw new SpecificAIError('UPSTREAM', `快手 GraphQL 错误:${message}`, { detail: { errors } });
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
// ================================================================ 设备指纹(login_api.bootstrap_device_fingerprint)
|
|
451
|
+
/**
|
|
452
|
+
* CP 页的官方 `kwf getData(1) → /s/w/p` 链,换回服务端下发的 wid。已有合法 wid 时不重复上报。
|
|
453
|
+
*/
|
|
454
|
+
export async function bootstrapDeviceFingerprint(ks, force = false) {
|
|
455
|
+
const s = ks.s;
|
|
456
|
+
const validDidv = (v) => /^\d{13}$/.test(v);
|
|
457
|
+
let didv = s.didv;
|
|
458
|
+
if (!validDidv(didv))
|
|
459
|
+
didv = s.cookies.get('didv') ?? '';
|
|
460
|
+
if (!validDidv(didv)) {
|
|
461
|
+
didv = String(rand.now());
|
|
462
|
+
s.update({ didv });
|
|
463
|
+
}
|
|
464
|
+
else
|
|
465
|
+
s.didv = didv;
|
|
466
|
+
const existing = s.cookies.get('wid') ?? '';
|
|
467
|
+
if (/^\d{17}$/.test(existing) && !force)
|
|
468
|
+
return { wid: existing, didv, reused: true };
|
|
469
|
+
if (!s.did)
|
|
470
|
+
throw new SpecificAIError('ERROR', '设备指纹上报缺少 did');
|
|
471
|
+
s.useSite(CP);
|
|
472
|
+
const cookie = await s.currentLine();
|
|
473
|
+
const wid = await reportFingerprint(ks, {
|
|
474
|
+
did: s.did,
|
|
475
|
+
product: PRODUCT_CP,
|
|
476
|
+
referer: `${CP}/`,
|
|
477
|
+
href: PUBLISH_REFERER,
|
|
478
|
+
cookie,
|
|
479
|
+
kwfcv1: s.kwfcv1,
|
|
480
|
+
currentKwfv1: s.kwfv1,
|
|
481
|
+
});
|
|
482
|
+
s.update([
|
|
483
|
+
['wid', wid],
|
|
484
|
+
['didv', didv],
|
|
485
|
+
]);
|
|
486
|
+
return { wid, didv, reused: false };
|
|
487
|
+
}
|