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,311 @@
|
|
|
1
|
+
import { SpecificAIError } from '../../../core/errors.js';
|
|
2
|
+
import { parseJson } from '../../../core/http.js';
|
|
3
|
+
import { compactJson, jsonLoads } from '../../../core/py.js';
|
|
4
|
+
import * as rand from '../../../core/rand.js';
|
|
5
|
+
import { authError, httpClient, isGuest, scope } from '../../../core/toolkit.js';
|
|
6
|
+
import * as api from './api.js';
|
|
7
|
+
import { fingerCore, fingerPayload } from './gaia.js';
|
|
8
|
+
import { API, BROWSER, COOKIE_DOMAIN, COOKIE_ORDER, headers, PROFILE } from './profile.js';
|
|
9
|
+
import { bLsid, correspondPath, dmImgParams, encWbi, liveBuvid, mixinKey, murmur3Hex, sid, TICKET_KEY_ID, ticketHexSign, uuidInfoc } from './sign.js';
|
|
10
|
+
/**
|
|
11
|
+
* B 站 web 端的会话(上游 builder/auth.py 的 BiliAuth + utils/http_util.py):
|
|
12
|
+
* 设备 cookie、bili_ticket、WBI 密钥的生命周期,以及带风控退避重试的请求。
|
|
13
|
+
*/
|
|
14
|
+
/** 触发重试的业务错误码与 HTTP 状态(上游 RETRYABLE_CODES / RETRYABLE_STATUS)。 */
|
|
15
|
+
const RETRYABLE_CODES = new Set([-352, -799, -509, -412]);
|
|
16
|
+
const RETRYABLE_STATUS = new Set([412, 429, 502, 503, 504]);
|
|
17
|
+
/** WBI 密钥缓存半小时。 */
|
|
18
|
+
const MIXIN_TTL = 1800_000;
|
|
19
|
+
/** bili_ticket 提前 1 小时换新。 */
|
|
20
|
+
const TICKET_SKEW = 3600;
|
|
21
|
+
export class Bili {
|
|
22
|
+
ctx;
|
|
23
|
+
http;
|
|
24
|
+
jar;
|
|
25
|
+
constructor(ctx) {
|
|
26
|
+
this.ctx = ctx;
|
|
27
|
+
this.http = httpClient(ctx, { browser: BROWSER, os: 'windows', cookieOrder: COOKIE_ORDER });
|
|
28
|
+
this.jar = this.http.jar;
|
|
29
|
+
}
|
|
30
|
+
get csrf() {
|
|
31
|
+
return this.jar.get('bili_jct') ?? '';
|
|
32
|
+
}
|
|
33
|
+
get mid() {
|
|
34
|
+
return this.jar.get('DedeUserID') ?? '';
|
|
35
|
+
}
|
|
36
|
+
get isLogin() {
|
|
37
|
+
return Boolean(this.jar.get('SESSDATA'));
|
|
38
|
+
}
|
|
39
|
+
setCookie(name, value) {
|
|
40
|
+
this.jar.set(name, value, COOKIE_DOMAIN);
|
|
41
|
+
}
|
|
42
|
+
setDefault(name, value) {
|
|
43
|
+
if (!this.jar.has(name))
|
|
44
|
+
this.setCookie(name, value());
|
|
45
|
+
}
|
|
46
|
+
// ---------------------------------------------------------------- 设备初始化
|
|
47
|
+
/**
|
|
48
|
+
* 每条命令开始时调用,相当于上游的 `from_cookie()` / `anonymous()`:
|
|
49
|
+
* - 没有设备 cookie 的游客:完整跑一遍匿名设备初始化(spi → bili_ticket → 指纹上报),结果缓存在 guest.json;
|
|
50
|
+
* - 登录用户缺设备 cookie 时补齐;
|
|
51
|
+
* - 会话级的 sid / PVID / b_lsid 每次重新生成(b_lsid)或补齐。
|
|
52
|
+
*/
|
|
53
|
+
async init() {
|
|
54
|
+
if (isGuest(this.ctx) && !this.jar.has('buvid3')) {
|
|
55
|
+
await this.anonymous();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (!this.jar.has('buvid3')) {
|
|
59
|
+
for (const [name, value] of await this.deviceCookies())
|
|
60
|
+
this.setDefault(name, () => value);
|
|
61
|
+
}
|
|
62
|
+
fillSessionCookies((name) => this.jar.has(name), (name, value) => this.setCookie(name, value));
|
|
63
|
+
if (isGuest(this.ctx) && Number(this.jar.get('bili_ticket_expires') ?? 0) - TICKET_SKEW < rand.nowSeconds()) {
|
|
64
|
+
await this.refreshTicket();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async anonymous() {
|
|
68
|
+
this.jar.cookies.splice(0);
|
|
69
|
+
for (const [name, value] of await this.deviceCookies())
|
|
70
|
+
this.setCookie(name, value);
|
|
71
|
+
await this.refreshTicket();
|
|
72
|
+
await this.reportFinger();
|
|
73
|
+
}
|
|
74
|
+
/** 服务端只下发 buvid3 / buvid4,其余由浏览器 JS 本地生成(上游 build_device_cookies)。 */
|
|
75
|
+
async deviceCookies() {
|
|
76
|
+
const spi = await this.get(`${API}/x/frontend/finger/spi`, { headers: headers('GET').get() });
|
|
77
|
+
return [
|
|
78
|
+
['buvid3', spi.b_3],
|
|
79
|
+
['b_nut', String(rand.nowSeconds())],
|
|
80
|
+
['_uuid', uuidInfoc()],
|
|
81
|
+
['home_feed_column', '5'],
|
|
82
|
+
['buvid4', spi.b_4],
|
|
83
|
+
['browser_resolution', PROFILE.browserResolution],
|
|
84
|
+
['buvid_fp', murmur3Hex(compactJson(fingerCore()))],
|
|
85
|
+
['LIVE_BUVID', liveBuvid()],
|
|
86
|
+
['theme-tip-show', 'SHOWED'],
|
|
87
|
+
['CURRENT_FNVAL', '4048'],
|
|
88
|
+
['sid', sid()],
|
|
89
|
+
['PVID', '1'],
|
|
90
|
+
['ogv_device_support_dolby', '0'],
|
|
91
|
+
['ogv_device_support_hdr', '0'],
|
|
92
|
+
['b_lsid', bLsid()],
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
/** 换取 bili_ticket,响应里顺带的 WBI 密钥一并缓存(上游 refresh_ticket)。 */
|
|
96
|
+
async refreshTicket() {
|
|
97
|
+
const ts = rand.nowSeconds();
|
|
98
|
+
const data = await this.post(`${API}/bapis/bilibili.api.ticket.v1.Ticket/GenWebTicket`, {
|
|
99
|
+
headers: headers('POST').get(),
|
|
100
|
+
query: [
|
|
101
|
+
['key_id', TICKET_KEY_ID],
|
|
102
|
+
['hexsign', ticketHexSign(ts)],
|
|
103
|
+
['context[ts]', ts],
|
|
104
|
+
['csrf', this.csrf],
|
|
105
|
+
],
|
|
106
|
+
});
|
|
107
|
+
const expires = Number(data.created_at ?? rand.nowSeconds()) + Number(data.ttl ?? 259200);
|
|
108
|
+
this.setCookie('bili_ticket', data.ticket);
|
|
109
|
+
this.setCookie('bili_ticket_expires', String(expires));
|
|
110
|
+
if (data.nav?.img && data.nav.sub)
|
|
111
|
+
this.saveMixin(mixinKey(data.nav.img, data.nav.sub));
|
|
112
|
+
}
|
|
113
|
+
/** 明文指纹上报 ExClimbWuzhi。失败只记日志:它只影响风控评分,不影响功能。 */
|
|
114
|
+
async reportFinger(referer = 'https://www.bilibili.com/') {
|
|
115
|
+
const h = headers('POST', { accept: '*/*' }).referer(referer).set('content-type', 'application/json;charset=UTF-8');
|
|
116
|
+
try {
|
|
117
|
+
const payload = compactJson(fingerPayload(this.jar.get('_uuid') ?? '', referer));
|
|
118
|
+
await this.post(`${API}/x/internal/gaia-gateway/ExClimbWuzhi`, { headers: h.get(), json: { payload } }, { raw: true });
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
this.ctx.log.debug(`ExClimbWuzhi 上报失败:${err.message}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// ---------------------------------------------------------------- WBI
|
|
125
|
+
saveMixin(key) {
|
|
126
|
+
this.ctx.credential.extra.wbi = { key, at: rand.now() };
|
|
127
|
+
}
|
|
128
|
+
/** 当前的 mixin_key:缓存半小时,过期后从 nav 重新派生。 */
|
|
129
|
+
async mixinKey() {
|
|
130
|
+
const cached = this.ctx.credential.extra.wbi;
|
|
131
|
+
if (cached?.key && rand.now() - cached.at < MIXIN_TTL)
|
|
132
|
+
return cached.key;
|
|
133
|
+
const nav = await this.json({ url: `${API}/x/web-interface/nav`, headers: headers('GET').get() });
|
|
134
|
+
const img = nav.data?.wbi_img;
|
|
135
|
+
if (!img?.img_url)
|
|
136
|
+
throw new SpecificAIError('UPSTREAM', 'nav 没有返回 wbi_img,无法签名', { detail: { code: nav.code } });
|
|
137
|
+
const key = mixinKey(img.img_url, img.sub_url);
|
|
138
|
+
this.saveMixin(key);
|
|
139
|
+
return key;
|
|
140
|
+
}
|
|
141
|
+
/** 签名后的 query:原参数顺序 + w_rid + wts。 */
|
|
142
|
+
async wbi(params) {
|
|
143
|
+
return encWbi(params, await this.mixinKey());
|
|
144
|
+
}
|
|
145
|
+
/** 带 dm_img_* 指纹再签名,空间类接口需要。 */
|
|
146
|
+
async wbiDm(params, sampleCount = 0) {
|
|
147
|
+
return this.wbi([...params, ...dmImgParams(sampleCount)]);
|
|
148
|
+
}
|
|
149
|
+
// ---------------------------------------------------------------- 请求
|
|
150
|
+
/** 带风控退避重试的原始请求(上游 http_util.request)。 */
|
|
151
|
+
request(req, retries = 3) {
|
|
152
|
+
return retrying(() => this.http.request(req), this.ctx.signal, retries);
|
|
153
|
+
}
|
|
154
|
+
/** 请求并解析 JSON,不检查业务码。 */
|
|
155
|
+
async json(req) {
|
|
156
|
+
const res = await this.request(req);
|
|
157
|
+
if (res.status === 412 || res.status === 429) {
|
|
158
|
+
throw new SpecificAIError('RISK_CONTROL', `B 站拒绝了请求(HTTP ${res.status}),请稍后再试`, { detail: { kind: 'rate_limit', status: res.status } });
|
|
159
|
+
}
|
|
160
|
+
return parseJson(res);
|
|
161
|
+
}
|
|
162
|
+
/** 请求二进制内容(弹幕 protobuf 等):HTTP 状态不是 200、或者返回的是 JSON 错误时报错,不把错误页当数据解。 */
|
|
163
|
+
async bytes(req) {
|
|
164
|
+
const res = await this.request(req);
|
|
165
|
+
if (res.status === 412 || res.status === 429) {
|
|
166
|
+
throw new SpecificAIError('RISK_CONTROL', `B 站拒绝了请求(HTTP ${res.status}),请稍后再试`, { detail: { kind: 'rate_limit', status: res.status } });
|
|
167
|
+
}
|
|
168
|
+
if (res.status !== 200)
|
|
169
|
+
throw new SpecificAIError('UPSTREAM', `B 站返回 HTTP ${res.status}`, { detail: { status: res.status } });
|
|
170
|
+
if ((res.headers.get('content-type') ?? '').includes('json')) {
|
|
171
|
+
const body = await parseJson(res);
|
|
172
|
+
check(this.ctx, body);
|
|
173
|
+
throw new SpecificAIError('UPSTREAM', 'B 站返回了 JSON,不是二进制数据', { detail: { code: body?.code } });
|
|
174
|
+
}
|
|
175
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
176
|
+
}
|
|
177
|
+
/** 请求并检查业务码,返回 data。raw 为 true 时返回整个 JSON。 */
|
|
178
|
+
async call(req, options = {}) {
|
|
179
|
+
const body = await this.json(req);
|
|
180
|
+
check(this.ctx, body);
|
|
181
|
+
return (options.raw ? body : body.data);
|
|
182
|
+
}
|
|
183
|
+
get(url, req = {}, options) {
|
|
184
|
+
return this.call({ ...req, url, method: 'GET' }, options);
|
|
185
|
+
}
|
|
186
|
+
post(url, req = {}, options) {
|
|
187
|
+
return this.call({ ...req, url, method: 'POST' }, options);
|
|
188
|
+
}
|
|
189
|
+
/** 写操作前检查登录态(registry 已保证不是游客,这里防 cookie 不全)。 */
|
|
190
|
+
requireLogin() {
|
|
191
|
+
if (!this.isLogin || !this.csrf)
|
|
192
|
+
throw authError(this.ctx, '当前账号缺少 SESSDATA / bili_jct,请重新登录');
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* 会话级 cookie(上游 BiliAuth.from_cookie):sid、PVID 缺了才补,b_lsid 每次重新生成。
|
|
197
|
+
* 上游 `cookie.setdefault('sid', gen_sid())` 总会先算出 gen_sid(),随机数照样消耗。
|
|
198
|
+
*/
|
|
199
|
+
export function fillSessionCookies(has, set) {
|
|
200
|
+
const newSid = sid();
|
|
201
|
+
if (!has('sid'))
|
|
202
|
+
set('sid', newSid);
|
|
203
|
+
if (!has('PVID'))
|
|
204
|
+
set('PVID', '1');
|
|
205
|
+
set('b_lsid', bLsid());
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* 风控退避重试(上游 http_util.request):HTTP 200 但业务码在 RETRYABLE_CODES 里、或 HTTP 状态在 RETRYABLE_STATUS 里时,
|
|
209
|
+
* 依次等 1、2、4 秒再发,最多再发 retries 次,返回最后一次的响应。B 站接口和极验共用。
|
|
210
|
+
*/
|
|
211
|
+
export async function retrying(send, signal, retries = 3) {
|
|
212
|
+
let res;
|
|
213
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
214
|
+
res = await send();
|
|
215
|
+
if (res.status === 200) {
|
|
216
|
+
const code = await peekCode(res);
|
|
217
|
+
if (code === undefined || !RETRYABLE_CODES.has(code))
|
|
218
|
+
return res;
|
|
219
|
+
}
|
|
220
|
+
else if (!RETRYABLE_STATUS.has(res.status))
|
|
221
|
+
return res;
|
|
222
|
+
if (attempt < retries)
|
|
223
|
+
await rand.sleep(1000 * 2 ** attempt, signal);
|
|
224
|
+
}
|
|
225
|
+
return res;
|
|
226
|
+
}
|
|
227
|
+
async function peekCode(res) {
|
|
228
|
+
if (!(res.headers.get('content-type') ?? '').includes('json'))
|
|
229
|
+
return undefined;
|
|
230
|
+
try {
|
|
231
|
+
const body = jsonLoads(await res.clone().text());
|
|
232
|
+
return typeof body?.code === 'number' ? body.code : undefined;
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/** 请求过于频繁(-509、-799)和按 IP 限流的 -412(request was banned,与 HTTP 412 同属限流);其余风控码(-352 风控校验失败)为 blocked。 */
|
|
239
|
+
const RATE_LIMIT_CODES = new Set([-509, -799, -412]);
|
|
240
|
+
/** 业务码映射成 specific-ai 的错误(AGENTS 6.4)。 */
|
|
241
|
+
export function check(ctx, body) {
|
|
242
|
+
const code = body?.code;
|
|
243
|
+
if (code === 0 || code === undefined)
|
|
244
|
+
return;
|
|
245
|
+
const message = String(body.message ?? body.msg ?? '');
|
|
246
|
+
if (code === -101 || code === -111 || code === 86095)
|
|
247
|
+
throw authError(ctx, message || undefined);
|
|
248
|
+
if (RETRYABLE_CODES.has(code)) {
|
|
249
|
+
const kind = RATE_LIMIT_CODES.has(code) ? 'rate_limit' : 'blocked';
|
|
250
|
+
throw new SpecificAIError('RISK_CONTROL', `B 站风控拦截:${message || code}`, { detail: { kind, code, message } });
|
|
251
|
+
}
|
|
252
|
+
if (code === 340022 || code === 10031 || code === -625) {
|
|
253
|
+
throw new SpecificAIError('RISK_CONTROL', `需要人机验证:${message || code}`, { detail: { kind: 'captcha', code, message } });
|
|
254
|
+
}
|
|
255
|
+
throw new SpecificAIError('UPSTREAM', message || `B 站返回错误 ${code}`, { detail: { code, message } });
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Cookie 续期(上游 refresh_cookies):每天最多检查一次 cookie/info,需要时换新的 SESSDATA 并确认。
|
|
259
|
+
* 失败只记警告,当前 cookie 往往还能用一阵。响应里的 Set-Cookie 由 HttpClient 并进 cookie 罐,命令结束时由 core 落盘。
|
|
260
|
+
*/
|
|
261
|
+
export async function refreshCookies(b) {
|
|
262
|
+
const tokens = scope(b.ctx.credential).tokens;
|
|
263
|
+
const old = tokens.refresh_token;
|
|
264
|
+
if (!old || !b.isLogin)
|
|
265
|
+
return;
|
|
266
|
+
const checked = Number(b.ctx.credential.extra.refresh_checked_at ?? 0);
|
|
267
|
+
if (rand.now() - checked < 86_400_000)
|
|
268
|
+
return;
|
|
269
|
+
b.ctx.credential.extra.refresh_checked_at = rand.now();
|
|
270
|
+
try {
|
|
271
|
+
const info = await api.cookieInfo(b);
|
|
272
|
+
if (info.code !== 0)
|
|
273
|
+
throw new Error(`查 cookie/info 失败:${info.code} ${info.message ?? ''}`);
|
|
274
|
+
if (!info.data?.refresh)
|
|
275
|
+
return;
|
|
276
|
+
// 用服务端给的毫秒时间戳,避开本地时钟偏移
|
|
277
|
+
const csrf = await api.refreshCsrf(b, correspondPath(info.data.timestamp || rand.now()));
|
|
278
|
+
if (!csrf)
|
|
279
|
+
throw new Error('correspond 页面没有返回 refresh_csrf(路径过期或登录态失效)');
|
|
280
|
+
const before = b.jar.get('SESSDATA');
|
|
281
|
+
const r = await api.cookieRefresh(b, csrf, old);
|
|
282
|
+
if (r.code !== 0)
|
|
283
|
+
throw new Error(`换 Cookie 失败:${r.code} ${r.message ?? ''}`);
|
|
284
|
+
if (b.jar.get('SESSDATA') === before)
|
|
285
|
+
throw new Error('换 Cookie 成功,但响应没有带新的 SESSDATA');
|
|
286
|
+
// 旧 refresh_token 已经用掉:响应里没有新的就删掉,之后不再尝试续期
|
|
287
|
+
if (r.data?.refresh_token)
|
|
288
|
+
tokens.refresh_token = r.data.refresh_token;
|
|
289
|
+
else {
|
|
290
|
+
delete tokens.refresh_token;
|
|
291
|
+
b.ctx.log.warn('续期响应没有新的 refresh_token,之后不能自动续期;过期后请重新登录');
|
|
292
|
+
}
|
|
293
|
+
// 两个参数分属新旧两代:csrf 用刷新后的 bili_jct,refresh_token 用刷新前的
|
|
294
|
+
const confirm = await api.confirmRefresh(b, old);
|
|
295
|
+
if (confirm.code !== 0) {
|
|
296
|
+
b.ctx.log.warn(`登录态已续期,但确认更新失败:${confirm.code} ${confirm.message ?? ''}(新 Cookie 可用,旧会话没有失效)`);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
b.ctx.log.info('登录态已自动续期');
|
|
300
|
+
}
|
|
301
|
+
catch (err) {
|
|
302
|
+
b.ctx.log.warn(`登录态续期失败:${err.message}`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
/** 建立会话,完成设备初始化;登录用户顺带做每日一次的续期检查。 */
|
|
306
|
+
export async function bili(ctx) {
|
|
307
|
+
const b = new Bili(ctx);
|
|
308
|
+
await b.init();
|
|
309
|
+
await refreshCookies(b);
|
|
310
|
+
return b;
|
|
311
|
+
}
|