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.
Files changed (277) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +177 -0
  3. package/README.ja.md +177 -0
  4. package/README.ko.md +177 -0
  5. package/README.md +174 -2
  6. package/README.zh-TW.md +177 -0
  7. package/assets/banner.txt +3 -0
  8. package/dist/cli/argv.js +98 -0
  9. package/dist/cli/commands/auth.js +11 -0
  10. package/dist/cli/commands/config.js +23 -0
  11. package/dist/cli/commands/doctor.js +134 -0
  12. package/dist/cli/commands/platforms.js +24 -0
  13. package/dist/cli/commands/version.js +4 -0
  14. package/dist/cli/confirm.js +22 -0
  15. package/dist/cli/dispatch.js +440 -0
  16. package/dist/cli/help.js +223 -0
  17. package/dist/cli/hints.js +60 -0
  18. package/dist/cli/main.js +18 -0
  19. package/dist/cli/output.js +66 -0
  20. package/dist/core/auth-handlers.js +41 -0
  21. package/dist/core/auth-store.js +119 -0
  22. package/dist/core/capabilities.js +163 -0
  23. package/dist/core/config.js +121 -0
  24. package/dist/core/cookies.js +164 -0
  25. package/dist/core/errors.js +37 -0
  26. package/dist/core/files.js +89 -0
  27. package/dist/core/fsutil.js +52 -0
  28. package/dist/core/hash.js +4 -0
  29. package/dist/core/http.js +331 -0
  30. package/dist/core/image.js +280 -0
  31. package/dist/core/log.js +40 -0
  32. package/dist/core/login.js +191 -0
  33. package/dist/core/mp4.js +130 -0
  34. package/dist/core/normalize.js +207 -0
  35. package/dist/core/options.js +110 -0
  36. package/dist/core/paths.js +38 -0
  37. package/dist/core/pb.js +59 -0
  38. package/dist/core/py.js +181 -0
  39. package/dist/core/rand.js +131 -0
  40. package/dist/core/registry.js +129 -0
  41. package/dist/core/schemas.js +41 -0
  42. package/dist/core/stream.js +104 -0
  43. package/dist/core/toolkit.js +35 -0
  44. package/dist/core/vm.js +73 -0
  45. package/dist/core/vocab.js +157 -0
  46. package/dist/platforms/_shared/impaas.js +410 -0
  47. package/dist/platforms/bilibili/index.js +216 -0
  48. package/dist/platforms/bilibili/web/api.js +806 -0
  49. package/dist/platforms/bilibili/web/client.js +311 -0
  50. package/dist/platforms/bilibili/web/commands.js +783 -0
  51. package/dist/platforms/bilibili/web/ddddocr.js +342 -0
  52. package/dist/platforms/bilibili/web/gaia.js +130 -0
  53. package/dist/platforms/bilibili/web/geetest-manual.js +154 -0
  54. package/dist/platforms/bilibili/web/geetest-vision.js +232 -0
  55. package/dist/platforms/bilibili/web/geetest.js +450 -0
  56. package/dist/platforms/bilibili/web/normalize.js +234 -0
  57. package/dist/platforms/bilibili/web/profile.js +135 -0
  58. package/dist/platforms/bilibili/web/resolve.js +112 -0
  59. package/dist/platforms/bilibili/web/sign.js +223 -0
  60. package/dist/platforms/bilibili/web/upos.js +102 -0
  61. package/dist/platforms/douyin/index.js +142 -0
  62. package/dist/platforms/douyin/web/api.js +863 -0
  63. package/dist/platforms/douyin/web/client.js +413 -0
  64. package/dist/platforms/douyin/web/commands.js +712 -0
  65. package/dist/platforms/douyin/web/creator.js +601 -0
  66. package/dist/platforms/douyin/web/crypto.js +247 -0
  67. package/dist/platforms/douyin/web/dtrait.js +190 -0
  68. package/dist/platforms/douyin/web/im.js +502 -0
  69. package/dist/platforms/douyin/web/image.js +5 -0
  70. package/dist/platforms/douyin/web/live.js +225 -0
  71. package/dist/platforms/douyin/web/mssdk.js +148 -0
  72. package/dist/platforms/douyin/web/normalize.js +326 -0
  73. package/dist/platforms/douyin/web/passport.js +1247 -0
  74. package/dist/platforms/douyin/web/profile.js +221 -0
  75. package/dist/platforms/douyin/web/proto.js +26 -0
  76. package/dist/platforms/douyin/web/resolve.js +88 -0
  77. package/dist/platforms/douyin/web/sign.js +436 -0
  78. package/dist/platforms/douyin/web/tos.js +78 -0
  79. package/dist/platforms/index.js +17 -0
  80. package/dist/platforms/jd/index.js +67 -0
  81. package/dist/platforms/jd/web/aks.js +115 -0
  82. package/dist/platforms/jd/web/api.js +549 -0
  83. package/dist/platforms/jd/web/chat.js +132 -0
  84. package/dist/platforms/jd/web/client.js +470 -0
  85. package/dist/platforms/jd/web/commands.js +557 -0
  86. package/dist/platforms/jd/web/jcap/helper.js +90 -0
  87. package/dist/platforms/jd/web/jcap/host.js +107 -0
  88. package/dist/platforms/jd/web/jcap/lsd.js +300 -0
  89. package/dist/platforms/jd/web/jcap/solver.js +995 -0
  90. package/dist/platforms/jd/web/jcap.js +150 -0
  91. package/dist/platforms/jd/web/js.js +420 -0
  92. package/dist/platforms/jd/web/login.js +775 -0
  93. package/dist/platforms/jd/web/normalize.js +297 -0
  94. package/dist/platforms/jd/web/profile.js +256 -0
  95. package/dist/platforms/jd/web/util.js +175 -0
  96. package/dist/platforms/jd/web/webm.js +133 -0
  97. package/dist/platforms/kuaishou/index.js +98 -0
  98. package/dist/platforms/kuaishou/web/api.js +694 -0
  99. package/dist/platforms/kuaishou/web/captcha-crypto.js +134 -0
  100. package/dist/platforms/kuaishou/web/captcha-fp.js +180 -0
  101. package/dist/platforms/kuaishou/web/captcha.js +273 -0
  102. package/dist/platforms/kuaishou/web/client.js +487 -0
  103. package/dist/platforms/kuaishou/web/commands.js +863 -0
  104. package/dist/platforms/kuaishou/web/gap.js +144 -0
  105. package/dist/platforms/kuaishou/web/gdfp.js +376 -0
  106. package/dist/platforms/kuaishou/web/gql.js +11 -0
  107. package/dist/platforms/kuaishou/web/live.js +125 -0
  108. package/dist/platforms/kuaishou/web/normalize.js +241 -0
  109. package/dist/platforms/kuaishou/web/oracle.js +329 -0
  110. package/dist/platforms/kuaishou/web/profile.js +259 -0
  111. package/dist/platforms/kuaishou/web/resolve.js +84 -0
  112. package/dist/platforms/kuaishou/web/session.js +592 -0
  113. package/dist/platforms/kuaishou/web/sign.js +568 -0
  114. package/dist/platforms/taobao/index.js +56 -0
  115. package/dist/platforms/taobao/web/api.js +96 -0
  116. package/dist/platforms/taobao/web/client.js +102 -0
  117. package/dist/platforms/taobao/web/commands.js +172 -0
  118. package/dist/platforms/taobao/web/im.js +68 -0
  119. package/dist/platforms/taobao/web/normalize.js +103 -0
  120. package/dist/platforms/taobao/web/profile.js +85 -0
  121. package/dist/platforms/taobao/web/resolve.js +51 -0
  122. package/dist/platforms/taobao/web/sign.js +35 -0
  123. package/dist/platforms/tiktok/index.js +120 -0
  124. package/dist/platforms/tiktok/web/api.js +1234 -0
  125. package/dist/platforms/tiktok/web/client.js +362 -0
  126. package/dist/platforms/tiktok/web/commands.js +821 -0
  127. package/dist/platforms/tiktok/web/im.js +133 -0
  128. package/dist/platforms/tiktok/web/jsrun.js +126 -0
  129. package/dist/platforms/tiktok/web/normalize.js +286 -0
  130. package/dist/platforms/tiktok/web/profile.js +169 -0
  131. package/dist/platforms/tiktok/web/resolve.js +156 -0
  132. package/dist/platforms/tiktok/web/sign.js +585 -0
  133. package/dist/platforms/tiktok/web/upload.js +805 -0
  134. package/dist/platforms/tiktok/web/wire.js +490 -0
  135. package/dist/platforms/weibo/index.js +91 -0
  136. package/dist/platforms/weibo/web/api.js +206 -0
  137. package/dist/platforms/weibo/web/client.js +155 -0
  138. package/dist/platforms/weibo/web/commands.js +202 -0
  139. package/dist/platforms/weibo/web/normalize.js +137 -0
  140. package/dist/platforms/weibo/web/profile.js +246 -0
  141. package/dist/platforms/weibo/web/resolve.js +60 -0
  142. package/dist/platforms/weibo/web/sign.js +85 -0
  143. package/dist/platforms/x/index.js +107 -0
  144. package/dist/platforms/x/web/api.js +443 -0
  145. package/dist/platforms/x/web/article.js +212 -0
  146. package/dist/platforms/x/web/client.js +202 -0
  147. package/dist/platforms/x/web/commands.js +363 -0
  148. package/dist/platforms/x/web/graphql.js +108 -0
  149. package/dist/platforms/x/web/normalize.js +235 -0
  150. package/dist/platforms/x/web/profile.js +99 -0
  151. package/dist/platforms/x/web/resolve.js +44 -0
  152. package/dist/platforms/x/web/transaction.js +171 -0
  153. package/dist/platforms/xhs/index.js +101 -0
  154. package/dist/platforms/xhs/web/api.js +278 -0
  155. package/dist/platforms/xhs/web/client.js +540 -0
  156. package/dist/platforms/xhs/web/commands.js +1046 -0
  157. package/dist/platforms/xhs/web/creator-api.js +609 -0
  158. package/dist/platforms/xhs/web/creator.js +203 -0
  159. package/dist/platforms/xhs/web/js.js +194 -0
  160. package/dist/platforms/xhs/web/login.js +230 -0
  161. package/dist/platforms/xhs/web/normalize.js +166 -0
  162. package/dist/platforms/xhs/web/profile.js +127 -0
  163. package/dist/platforms/xhs/web/push.js +361 -0
  164. package/dist/platforms/xhs/web/resolve.js +69 -0
  165. package/dist/platforms/xhs/web/state.js +368 -0
  166. package/dist/platforms/xianyu/index.js +59 -0
  167. package/dist/platforms/xianyu/web/api.js +312 -0
  168. package/dist/platforms/xianyu/web/client.js +134 -0
  169. package/dist/platforms/xianyu/web/commands.js +305 -0
  170. package/dist/platforms/xianyu/web/im.js +73 -0
  171. package/dist/platforms/xianyu/web/normalize.js +123 -0
  172. package/dist/platforms/xianyu/web/profile.js +146 -0
  173. package/dist/platforms/xianyu/web/resolve.js +48 -0
  174. package/dist/platforms/xianyu/web/sign.js +79 -0
  175. package/package.json +77 -4
  176. package/static/douyin/Live.proto +194 -0
  177. package/static/douyin/Request.proto +89 -0
  178. package/static/douyin/Response.proto +46 -0
  179. package/static/douyin/acrawler_runtime/ac_vm.js +2 -0
  180. package/static/douyin/acrawler_runtime/browser_window_shape.json +1 -0
  181. package/static/douyin/acrawler_runtime/canvas_actual_exact.json +1 -0
  182. package/static/douyin/acrawler_runtime/run_ac_node.js +1005 -0
  183. package/static/douyin/acrawler_runtime/runner_window_keys_baseline.txt +240 -0
  184. package/static/douyin/challenge_profile.json +801 -0
  185. package/static/douyin/challenge_template_runner.js +217 -0
  186. package/static/douyin/dtrait_profile.json +66 -0
  187. package/static/douyin/mstoken_common_profile.json +1 -0
  188. package/static/douyin/mstoken_profile.json +214 -0
  189. package/static/jd/h5st5_env.js +750 -0
  190. package/static/jd/h5st5_lib.js +5 -0
  191. package/static/jd/h5st5_server.js +49 -0
  192. package/static/jd/jcap/env/env_core.js +512 -0
  193. package/static/jd/jcap/env/run.js +1307 -0
  194. package/static/jd/jcap/run/jcap_ujb96b.js +39 -0
  195. package/static/jd/package.json +4 -0
  196. package/static/jd/pc_tk_lib.js +1 -0
  197. package/static/jd/pc_tk_server.js +63 -0
  198. package/static/jd/summer_cryptico_runner.js +50 -0
  199. package/static/jd/webm/env/run.js +453 -0
  200. package/static/jd/webm/run/jdwebm-riskhandle.js +49 -0
  201. package/static/kuaishou/bundles/weapon/kwf-0.0.2.2cee19b4b7dec496.js +1 -0
  202. package/static/kuaishou/bundles/weapon/kws-0-0.0.1-obfuscated.4b7f3de1ab0ddcc2.js +1 -0
  203. package/static/kuaishou/bundles/weapon/kws-1-0.0.1-obfuscated.346ebdb1c78df316.js +1 -0
  204. package/static/kuaishou/bundles/weapon/kws-10-0.0.1-obfuscated.a33db41897b983af.js +1 -0
  205. package/static/kuaishou/bundles/weapon/kws-11-0.0.1-obfuscated.5e0a90af726d8a7e.js +1 -0
  206. package/static/kuaishou/bundles/weapon/kws-12-0.0.1-obfuscated.20ea29345706ec8c.js +1 -0
  207. package/static/kuaishou/bundles/weapon/kws-13-0.0.1-obfuscated.6b74e9640ff18648.js +1 -0
  208. package/static/kuaishou/bundles/weapon/kws-14-0.0.1-obfuscated.551e4429d156bf66.js +1 -0
  209. package/static/kuaishou/bundles/weapon/kws-15-0.0.1-obfuscated.8625b087aaba4a60.js +1 -0
  210. package/static/kuaishou/bundles/weapon/kws-16-0.0.1-obfuscated.d5ffa4020f3be99a.js +1 -0
  211. package/static/kuaishou/bundles/weapon/kws-17-0.0.1-obfuscated.6d13e19b22f50a80.js +1 -0
  212. package/static/kuaishou/bundles/weapon/kws-18-0.0.1-obfuscated.6d31405044166289.js +1 -0
  213. package/static/kuaishou/bundles/weapon/kws-19-0.0.1-obfuscated.a9a8d925942f19e4.js +1 -0
  214. package/static/kuaishou/bundles/weapon/kws-2-0.0.1-obfuscated.b849a136ae4a6b3e.js +1 -0
  215. package/static/kuaishou/bundles/weapon/kws-3-0.0.1-obfuscated.f9e77ebf6186e2ac.js +1 -0
  216. package/static/kuaishou/bundles/weapon/kws-4-0.0.1-obfuscated.2c3c155aa52ceb0e.js +1 -0
  217. package/static/kuaishou/bundles/weapon/kws-5-0.0.1-obfuscated.4e553d17691b3c5a.js +1 -0
  218. package/static/kuaishou/bundles/weapon/kws-6-0.0.1-obfuscated.f73ddd3ff13e1217.js +1 -0
  219. package/static/kuaishou/bundles/weapon/kws-7-0.0.1-obfuscated.1628b8729beaab7e.js +1 -0
  220. package/static/kuaishou/bundles/weapon/kws-8-0.0.1-obfuscated.6da6baef7aa81fff.js +1 -0
  221. package/static/kuaishou/bundles/weapon/kws-9-0.0.1-obfuscated.8645d55e5323b743.js +1 -0
  222. package/static/kuaishou/fixtures/current_follow_script_813.network-response +2 -0
  223. package/static/kuaishou/fixtures/live_ws_maps.json +61 -0
  224. package/static/kuaishou/fixtures/live_ws_proto.json +1342 -0
  225. package/static/kuaishou/js/cp-kwf.js +1 -0
  226. package/static/kuaishou/tools/kwf_oracle.js +799 -0
  227. package/static/kuaishou/tools/kws_oracle.js +885 -0
  228. package/static/kuaishou/tools/like_token_oracle.js +70 -0
  229. package/static/taobao/taobao_js_20260407.js +570 -0
  230. package/static/tiktok/Tiktok_Request.json +597 -0
  231. package/static/tiktok/package.json +3 -0
  232. package/static/tiktok/shop_bsid/core.js +1 -0
  233. package/static/tiktok/shop_bsid/env_core.js +512 -0
  234. package/static/tiktok/shop_bsid/loader.js +1 -0
  235. package/static/tiktok/shop_bsid/run.js +753 -0
  236. package/static/tiktok/shop_bsid/sign.js +100 -0
  237. package/static/tiktok/shop_bsid/webmssdk.js +1 -0
  238. package/static/tiktok/shop_bsid/webmssdk_ex.js +1 -0
  239. package/static/tiktok/signing/env/env_core.js +512 -0
  240. package/static/tiktok/signing/env/sign.js +455 -0
  241. package/static/tiktok/signing/sdk_config.json +230 -0
  242. package/static/tiktok/signing/source/webmssdk.js +1 -0
  243. package/static/tiktok/signing/source/webmssdk_ex.js +1 -0
  244. package/static/tiktok/signing/source/webmssdk_legacy.js +1 -0
  245. package/static/x/graphql.json +7777 -0
  246. package/static/x/transaction_l1.json +12 -0
  247. package/static/xhs/package.json +3 -0
  248. package/static/xhs/xhs_core/js/b1.js +210 -0
  249. package/static/xhs/xhs_core/js/mns.js +340 -0
  250. package/static/xhs/xhs_core/js/mns_0101_keystream.json +5 -0
  251. package/static/xhs/xhs_core/js/mns_keystreams.json +1 -0
  252. package/static/xhs/xhs_core/js/sign.js +225 -0
  253. package/static/xhs/xhs_core/js/websectiga_cli.js +220 -0
  254. package/static/xhs/xhs_core/js/websectiga_env.js +512 -0
  255. package/static/xhs/xhs_core/js/xs_common.js +79 -0
  256. package/static/xhs/xhs_creator/js/profile.js +115 -0
  257. package/static/xhs/xhs_creator/js/rap_fingerprint_creator.json +6 -0
  258. package/static/xhs/xhs_creator/js/reference_profile.json +282 -0
  259. package/static/xhs/xhs_creator/js/xhs_creator_sign.js +29 -0
  260. package/static/xhs/xhs_creator/js/xhs_creator_signature.js +23 -0
  261. package/static/xhs/xhs_pc/js/aes_decrypt.js +22 -0
  262. package/static/xhs/xhs_pc/js/aes_encrypt.js +30 -0
  263. package/static/xhs/xhs_pc/js/b1.js +9 -0
  264. package/static/xhs/xhs_pc/js/deflate.js +320 -0
  265. package/static/xhs/xhs_pc/js/mns.js +4 -0
  266. package/static/xhs/xhs_pc/js/profile.js +141 -0
  267. package/static/xhs/xhs_pc/js/rap.js +173 -0
  268. package/static/xhs/xhs_pc/js/rap_cli.js +30 -0
  269. package/static/xhs/xhs_pc/js/rap_crypto.js +73 -0
  270. package/static/xhs/xhs_pc/js/rap_fingerprint_template.json +8 -0
  271. package/static/xhs/xhs_pc/js/reference_profile.json +262 -0
  272. package/static/xhs/xhs_pc/js/sign.js +6 -0
  273. package/static/xhs/xhs_pc/js/web_ssk.js +82 -0
  274. package/static/xhs/xhs_pc/js/xs_common.js +4 -0
  275. package/static/xianyu/et_f.js +2 -0
  276. package/static/xianyu/gen_tfstk.js +410 -0
  277. package/static/xianyu/goofish_js_version_2.js +568 -0
@@ -0,0 +1,592 @@
1
+ import { SpecificAIError } from '../../../core/errors.js';
2
+ import { compactJson, jsonLoads } from '../../../core/py.js';
3
+ import * as rand from '../../../core/rand.js';
4
+ import { genFingerprintReport, genKwfv1, genKwscode } from './oracle.js';
5
+ import { ACCEPT_ENCODING, ACCEPT_LANGUAGE, ACCEPT_LANGUAGE_SHORT, CAPTCHA_HOST, GDFP, HREF_CP, HREF_WWW, PRODUCT_CAPTCHA, PRODUCT_CP, PRODUCT_LIVE, PRODUCT_WWW, RAW_VALUE_PROFILES, SEQUENCES, siteDefaults, STALE_PAIR_PROFILES, UA, } from './profile.js';
6
+ import { weaponDecrypt, weaponEncrypt } from './sign.js';
7
+ /**
8
+ * 会话状态(上游 builder/auth.py 的 KuaishouAuth + utils/sign/kww_pure.py 的 KwwSigner + webweapon_boot.py)。
9
+ *
10
+ * - `cookies` 是上游的 `_cookie`:一个名字一个值、按插入顺序的映射,www / cp / live 三个站点共用;
11
+ * 发请求时由 {@link Session.cookieHeader} 按各页面实抓的线序序列化(含同名重复字段)。
12
+ * - webweapon:`kwfv1` 与 `kwfcv1` 是持久的浏览器状态;`kwscode` / `kwssectoken` 只有 6 分钟有效期,
13
+ * 每条命令开始时经 gdfp `/s/w/c` + 官方 kws 脚本现算,不落盘。
14
+ * - 页面 `kww` 是新页面冻结的 kwfv1 快照,之后 Cookie 里的 kwfv1 轮换不影响它。
15
+ */
16
+ const QUARTET = ['kwpsecproductname', 'kwfv1', 'kwssectoken', 'kwscode'];
17
+ const COOKIE_TTL = 6 * 60 * 1000;
18
+ const preflightDone = new WeakMap();
19
+ /**
20
+ * 浏览器对跨站 `POST /s/w/c` 的 CORS 预检(Chrome 缓存 1800 秒)。按 (origin, referer, url) 在本次会话内只做一次。
21
+ */
22
+ async function corsPreflight(t, url, origin, referer) {
23
+ let done = preflightDone.get(t.http);
24
+ if (!done)
25
+ preflightDone.set(t.http, (done = new Set()));
26
+ const key = `${origin}|${referer}|${url}`;
27
+ if (done.has(key))
28
+ return;
29
+ const res = await t.http.request({
30
+ method: 'OPTIONS',
31
+ url,
32
+ cookies: false,
33
+ headers: [
34
+ ['accept', '*/*'],
35
+ ['access-control-request-headers', 'content-type'],
36
+ ['access-control-request-method', 'POST'],
37
+ ['origin', origin],
38
+ ['sec-fetch-mode', 'cors'],
39
+ ['user-agent', UA],
40
+ ['accept-encoding', ACCEPT_ENCODING],
41
+ ['accept-language', ACCEPT_LANGUAGE],
42
+ ['priority', 'u=1, i'],
43
+ ['referer', referer],
44
+ ['sec-fetch-dest', 'empty'],
45
+ ['sec-fetch-site', 'cross-site'],
46
+ ],
47
+ });
48
+ if (res.status !== 200)
49
+ throw new SpecificAIError('UPSTREAM', `gdfp /s/w/c 预检失败:HTTP ${res.status}`, { detail: { kind: 'webweapon' } });
50
+ done.add(key);
51
+ }
52
+ /** fetch_config:`POST gdfp /s/w/c` 换票,拿到 kwf / kws 脚本地址与 secToken。 */
53
+ export async function fetchConfig(t, did, product, referer) {
54
+ const url = `${GDFP}/s/w/c`;
55
+ const u = new URL(referer);
56
+ const origin = `${u.protocol}//${u.host}`;
57
+ await corsPreflight(t, url, origin, referer);
58
+ const plain = compactJson({ productName: product, ts: rand.now(), did });
59
+ const res = await t.http.request({
60
+ method: 'POST',
61
+ url,
62
+ cookies: false,
63
+ body: compactJson({ data: weaponEncrypt(plain) }),
64
+ headers: [
65
+ ['user-agent', UA],
66
+ ['content-type', 'application/json'],
67
+ ['referer', referer],
68
+ ['accept', '*/*'],
69
+ ['accept-encoding', ACCEPT_ENCODING],
70
+ ['accept-language', ACCEPT_LANGUAGE],
71
+ ['origin', origin],
72
+ ['priority', 'u=1, i'],
73
+ ['sec-fetch-dest', 'empty'],
74
+ ['sec-fetch-mode', 'cors'],
75
+ ['sec-fetch-site', 'cross-site'],
76
+ ],
77
+ });
78
+ const text = await res.text();
79
+ let envelope;
80
+ try {
81
+ envelope = jsonLoads(text);
82
+ }
83
+ catch {
84
+ throw new SpecificAIError('UPSTREAM', `gdfp /s/w/c 返回的不是 JSON(HTTP ${res.status})`, { detail: { kind: 'webweapon', body: text.slice(0, 200) } });
85
+ }
86
+ if (envelope?.result !== 1) {
87
+ throw new SpecificAIError('UPSTREAM', `gdfp 换票失败:result=${envelope?.result} ${envelope?.error_msg ?? ''}`, { detail: { kind: 'webweapon', result: envelope?.result } });
88
+ }
89
+ const data = jsonLoads(weaponDecrypt(String(envelope.dataRsp)));
90
+ return { fpUrl: String(data.fpUrl ?? ''), signUrl: String(data.signUrl ?? ''), secToken: String(data.secToken ?? ''), raw: data };
91
+ }
92
+ /** report_fingerprint:官方 kwf 的 getData(1) → `POST gdfp /s/w/p`,换回服务端下发的 17 位 wid。 */
93
+ export async function reportFingerprint(t, o) {
94
+ const report = genFingerprintReport({ did: o.did, href: o.href, cookie: o.cookie, kwfcv1: o.kwfcv1, currentKwfv1: o.currentKwfv1 });
95
+ if (!report)
96
+ throw new SpecificAIError('UPSTREAM', '官方 kwf 脚本没有产出指纹上报数据', { detail: { kind: 'webweapon' } });
97
+ const body = compactJson(new Map([['1', o.did], ['2', o.product], ['9', rand.now()], ['data', report], ['p', 'w']]));
98
+ const res = await t.http.request({
99
+ method: 'POST',
100
+ url: `${GDFP}/s/w/p`,
101
+ cookies: false,
102
+ body,
103
+ headers: [
104
+ ['user-agent', UA],
105
+ ['content-type', 'application/json'],
106
+ ['referer', o.referer],
107
+ ['accept', '*/*'],
108
+ ['accept-encoding', ACCEPT_ENCODING],
109
+ ['accept-language', ACCEPT_LANGUAGE_SHORT],
110
+ ['origin', o.referer.replace(/\/+$/, '')],
111
+ ['priority', 'u=1, i'],
112
+ ['sec-fetch-dest', 'empty'],
113
+ ['sec-fetch-mode', 'cors'],
114
+ ['sec-fetch-site', 'cross-site'],
115
+ ],
116
+ });
117
+ const data = await res.json().catch(() => ({}));
118
+ const wid = data?.c === 200 ? String(data.r ?? '') : '';
119
+ if (!/^\d{17}$/.test(wid))
120
+ throw new SpecificAIError('UPSTREAM', '快手设备指纹上报被拒绝', { detail: { kind: 'webweapon', response: data } });
121
+ return wid;
122
+ }
123
+ // ================================================================ KwwSigner(kww_pure.py)
124
+ /** 页面级 kww 快照与当前 webweapon Cookie 的独立持有者。 */
125
+ export class KwwSigner {
126
+ t;
127
+ href;
128
+ did;
129
+ product;
130
+ kwfv1;
131
+ kwfcv1;
132
+ snapshot;
133
+ kwscode;
134
+ secToken;
135
+ fromCookie;
136
+ issuedAt;
137
+ config = null;
138
+ constructor(t, kwfv1, href, did, product, kwscode, kwssectoken, kwwSnapshot, kwfcv1) {
139
+ this.t = t;
140
+ this.href = href;
141
+ this.did = did;
142
+ this.product = product;
143
+ this.kwfv1 = kwfv1 || '';
144
+ this.snapshot = kwwSnapshot == null ? this.kwfv1 : kwwSnapshot || '';
145
+ this.kwfcv1 = kwfcv1 || '';
146
+ this.kwscode = kwscode || '';
147
+ this.secToken = kwssectoken || '';
148
+ this.fromCookie = Boolean(kwscode && kwssectoken);
149
+ this.issuedAt = this.fromCookie ? rand.now() : 0;
150
+ }
151
+ get kwwSnapshot() {
152
+ return this.snapshot;
153
+ }
154
+ /** 当前页面冻结的 kww;还没有快照时先完成 exact bootstrap。 */
155
+ async sign() {
156
+ if (!this.snapshot) {
157
+ if (!this.kwfv1)
158
+ await this.refreshIfNeeded();
159
+ if (!this.kwfv1)
160
+ throw new SpecificAIError('UPSTREAM', 'webweapon 没有产出 kwfv1', { detail: { kind: 'webweapon' } });
161
+ this.snapshot = this.kwfv1;
162
+ }
163
+ return this.snapshot;
164
+ }
165
+ /** 源码 `if (!r || !o)`:有值就一直用,缺失或超过 6 分钟才重新引导。 */
166
+ async refreshIfNeeded() {
167
+ if (this.kwfv1 && this.kwscode && this.secToken && rand.now() - this.issuedAt < COOKIE_TTL)
168
+ return;
169
+ await this.bootstrap();
170
+ }
171
+ /** `/s/w/c` 换票 → 官方 kwf 脚本算 kwfv1 → 官方 kws 脚本算 kwscode。 */
172
+ async bootstrap() {
173
+ if (!this.did)
174
+ throw new SpecificAIError('ERROR', 'webweapon 引导缺少 did');
175
+ const cfg = await fetchConfig(this.t, this.did, this.product || PRODUCT_WWW, this.href || 'https://www.kuaishou.com/');
176
+ if (!(cfg.fpUrl && cfg.signUrl && cfg.secToken))
177
+ throw new SpecificAIError('UPSTREAM', 'gdfp /s/w/c 缺少 fpUrl / signUrl / secToken', { detail: { kind: 'webweapon' } });
178
+ if (cfg.secToken.length !== 88)
179
+ throw new SpecificAIError('UPSTREAM', 'gdfp secToken 长度异常', { detail: { kind: 'webweapon' } });
180
+ const href = this.href || HREF_WWW;
181
+ const state = genKwfv1({ did: this.did, href, kwfcv1: this.kwfcv1, currentKwfv1: this.kwfv1, scriptPath: cfg.fpUrl });
182
+ this.kwfcv1 = state.kwfcv1 || this.kwfcv1;
183
+ if (!state.value)
184
+ throw new SpecificAIError('UPSTREAM', '官方 kwf 脚本没有产出 kwfv1', { detail: { kind: 'webweapon' } });
185
+ this.kwfv1 = state.value;
186
+ const kwscode = await genKwscode({ secToken: cfg.secToken, did: this.did, href, signUrl: cfg.signUrl, http: this.t.http });
187
+ this.secToken = cfg.secToken;
188
+ this.kwscode = kwscode;
189
+ this.config = cfg;
190
+ this.fromCookie = false;
191
+ this.issuedAt = rand.now();
192
+ this.t.log.debug(`webweapon 已引导:${this.product}`);
193
+ }
194
+ async cookies() {
195
+ await this.refreshIfNeeded();
196
+ if (!this.kwfv1)
197
+ throw new SpecificAIError('UPSTREAM', 'webweapon 没有产出 kwfv1', { detail: { kind: 'webweapon' } });
198
+ return { kwfv1: this.kwfv1, kwscode: this.kwscode, kwssectoken: this.secToken, kwpsecproductname: this.product };
199
+ }
200
+ }
201
+ export class Session {
202
+ t;
203
+ /** 上游 `_cookie`。 */
204
+ cookies = new Map();
205
+ kwfv1 = '';
206
+ kwfcv1 = '';
207
+ kwwSnapshot = '';
208
+ did = '';
209
+ didv = '';
210
+ userId = null;
211
+ cpApiPh = '';
212
+ history = [];
213
+ wwwPhase = 'initial';
214
+ cpPhase = 'initial';
215
+ livePhase = { home: 'initial', room: 'initial', profile: 'initial' };
216
+ liveHomeLoginPayCount = 0;
217
+ liveRoomLoginPayCount = 0;
218
+ liveSentry = new Map();
219
+ liveBootstrapEnabled = false;
220
+ selfEid = '';
221
+ signer;
222
+ constructor(t) {
223
+ this.t = t;
224
+ }
225
+ cookie(name) {
226
+ return this.cookies.get(name) ?? '';
227
+ }
228
+ newSigner(kwfv1, href, product, snapshot) {
229
+ return new KwwSigner(this.t, kwfv1, href, this.did, product, this.cookie('kwscode'), this.cookie('kwssectoken'), snapshot, this.kwfcv1);
230
+ }
231
+ /** prepare_auth:解析 cookie,补齐 did / didv / product,配置 webweapon。只解析、不出网。 */
232
+ prepare(cookies) {
233
+ this.cookies.clear();
234
+ for (const [k, v] of cookies)
235
+ if (k)
236
+ this.cookies.set(k, v);
237
+ this.wwwPhase = 'initial';
238
+ this.cpPhase = 'initial';
239
+ this.livePhase = { home: 'initial', room: 'initial', profile: 'initial' };
240
+ this.liveHomeLoginPayCount = 0;
241
+ this.liveRoomLoginPayCount = 0;
242
+ this.liveSentry.clear();
243
+ if (!this.cookie('did'))
244
+ this.cookies.set('did', 'web_' + rand.hex(16));
245
+ const [href, product] = siteDefaults(this.cookie('kwpsecproductname') || PRODUCT_WWW);
246
+ this.cookies.set('kwpsecproductname', product);
247
+ this.kwfv1 = this.cookie('kwfv1');
248
+ this.kwfcv1 = '';
249
+ this.did = this.cookie('did');
250
+ this.didv = this.cookie('didv') || String(rand.now());
251
+ this.cookies.set('didv', this.didv);
252
+ this.userId = this.cookie('userId') || this.cookie('userid') || null;
253
+ this.cpApiPh = this.cookie('kuaishou.web.cp.api_ph');
254
+ this.signer = this.newSigner(this.kwfv1, href, product, this.kwfv1);
255
+ this.kwwSnapshot = this.kwfv1;
256
+ return this;
257
+ }
258
+ /** 恢复持久化的 localStorage 计数器(prepare_state 的 webweapon.kwfcv1)。 */
259
+ restoreCounter(kwfcv1) {
260
+ this.kwfcv1 = kwfcv1;
261
+ this.signer.kwfcv1 = kwfcv1;
262
+ }
263
+ /** _ensure_browser_defaults:只补用户 CK 里没有的稳定默认值。 */
264
+ ensureBrowserDefaults() {
265
+ const product = this.cookie('kwpsecproductname') || PRODUCT_WWW;
266
+ if (!this.cookies.has('kpf'))
267
+ this.cookies.set('kpf', 'PC_WEB');
268
+ if (!this.cookies.has('clientid'))
269
+ this.cookies.set('clientid', '3');
270
+ if (!this.cookies.has('kpn'))
271
+ this.cookies.set('kpn', product === PRODUCT_LIVE ? 'GAME_ZONE' : 'KUAISHOU_VISION');
272
+ }
273
+ /**
274
+ * 上游的 `auth.cookie` 属性:必要时完成 webweapon 引导,把当前 quartet 写回 cookie,
275
+ * 返回非 quartet 字段在前、quartet 在后的合并映射。
276
+ */
277
+ async current() {
278
+ const generated = await this.signer.cookies();
279
+ const previous = QUARTET.map((k) => this.cookie(k));
280
+ const now = QUARTET.map((k) => generated[k] ?? '');
281
+ if (previous.every(Boolean) && previous.join('\0') !== now.join('\0')) {
282
+ if (!this.history.some((h) => h.join('\0') === previous.join('\0')))
283
+ this.history.push(previous);
284
+ if (this.wwwPhase === 'refreshed' && previous.slice(2).join('\0') !== now.slice(2).join('\0'))
285
+ this.wwwPhase = 'security_refreshed';
286
+ }
287
+ for (const k of QUARTET)
288
+ if (generated[k])
289
+ this.cookies.set(k, generated[k]);
290
+ if (generated.kwfv1)
291
+ this.kwfv1 = generated.kwfv1;
292
+ this.kwfcv1 = this.signer.kwfcv1 || '';
293
+ const merged = new Map();
294
+ for (const [k, v] of this.cookies)
295
+ if (k && !QUARTET.includes(k))
296
+ merged.set(k, v);
297
+ const product = this.cookie('kwpsecproductname') || generated.kwpsecproductname;
298
+ if (product)
299
+ merged.set('kwpsecproductname', product);
300
+ for (const k of ['kwfv1', 'kwssectoken', 'kwscode']) {
301
+ const v = generated[k] || this.cookie(k);
302
+ if (v)
303
+ merged.set(k, v);
304
+ }
305
+ return merged;
306
+ }
307
+ /** `k=v; k=v` 形式(上游把 `auth.cookie` 交给 curl_cffi 时的顺序)。 */
308
+ async currentLine() {
309
+ return [...(await this.current())].map(([k, v]) => `${k}=${v}`).join('; ');
310
+ }
311
+ /** 请求头 kww:页面冻结的快照,Cookie 轮换不影响它。 */
312
+ async kww() {
313
+ this.kwwSnapshot = await this.signer.sign();
314
+ this.kwfcv1 = this.signer.kwfcv1 || '';
315
+ return this.kwwSnapshot;
316
+ }
317
+ /** cookie_header:按页面、阶段选择 Chrome 实抓的 Cookie 线序并序列化(保留同名重复字段)。 */
318
+ async cookieHeader(site = 'www') {
319
+ let profile = site.toLowerCase();
320
+ if (profile === 'www')
321
+ profile = this.wwwPhase === 'security_refreshed' ? 'www_security_refreshed' : this.wwwPhase === 'refreshed' ? 'www_refreshed' : 'www_initial';
322
+ else if (profile === 'www_graphql_detail')
323
+ profile = 'www_graphql_detail_initial';
324
+ else if (profile === 'cp_creator')
325
+ profile = this.cpPhase === 'refreshed' ? 'cp_creator_refreshed' : this.cpPhase === 'warmed' ? 'cp_creator_warmed' : 'cp_creator_initial';
326
+ else if (profile === 'www_graphql')
327
+ profile = this.wwwPhase !== 'initial' && this.wwwPhase !== 'relogin' ? 'www_graphql_refreshed' : 'www_graphql_initial';
328
+ // 验证码 iframe 的 config / 图片 / verify 用同一条 Cookie 线序,只有请求头不同
329
+ else if (['captcha_config', 'captcha_image', 'captcha_verify'].includes(profile))
330
+ profile = 'captcha';
331
+ const values = RAW_VALUE_PROFILES.has(profile) ? new Map(this.cookies) : await this.current();
332
+ const sequence = SEQUENCES[profile] ?? SEQUENCES.www_initial;
333
+ const stale = this.history.at(-1);
334
+ const occurrences = new Map();
335
+ const out = [];
336
+ for (const key of sequence) {
337
+ let value = values.get(key);
338
+ if (value == null || value === '')
339
+ continue;
340
+ const n = (occurrences.get(key) ?? 0) + 1;
341
+ occurrences.set(key, n);
342
+ // iframe 在自己的路径下写了 verification-captcha,与父页(www)的产品 cookie 并存
343
+ if (profile === 'captcha' && key === 'kwpsecproductname')
344
+ value = n === 1 ? PRODUCT_CAPTCHA : this.cookie('kwpsecproductname') || PRODUCT_WWW;
345
+ if (stale && STALE_PAIR_PROFILES.has(profile) && (key === 'kwssectoken' || key === 'kwscode') && n % 2 === 1) {
346
+ value = key === 'kwssectoken' ? stale[2] : stale[3];
347
+ }
348
+ if (stale && (profile.startsWith('www_graphql_') || profile.startsWith('www_relogin_')) && key === 'kwpsecproductname' && n === 2 && stale[0])
349
+ value = stale[0];
350
+ out.push(`${key}=${value}`);
351
+ }
352
+ return out.join('; ');
353
+ }
354
+ /** update_cookies:并入服务端新下发的 cookie;页面已冻结的 kww 保留。 */
355
+ update(issued) {
356
+ const pairs = Array.isArray(issued) ? issued : Object.entries(issued);
357
+ if (!pairs.length)
358
+ return this;
359
+ for (const [k, v] of pairs)
360
+ if (v != null)
361
+ this.cookies.set(k, String(v));
362
+ this.userId = this.cookie('userId') || this.cookie('userid') || this.userId;
363
+ this.did = this.cookie('did') || this.did;
364
+ this.cpApiPh = this.cookie('kuaishou.web.cp.api_ph') || this.cpApiPh;
365
+ const previousKww = this.signer?.kwwSnapshot ?? '';
366
+ const kwfv1 = pairs.find(([k]) => k === 'kwfv1')?.[1];
367
+ if (kwfv1)
368
+ this.kwfv1 = kwfv1;
369
+ const [href, product] = siteDefaults(this.cookie('kwpsecproductname') || PRODUCT_WWW);
370
+ this.signer = this.newSigner(this.kwfv1, href, product, previousKww);
371
+ this.kwwSnapshot = previousKww;
372
+ return this;
373
+ }
374
+ /** use_site:切到某个站点的 webweapon 页面(www / cp / live 的 product 与 href 不同)。 */
375
+ useSite(site) {
376
+ const value = site.toLowerCase();
377
+ const previous = this.cookie('kwpsecproductname');
378
+ let product;
379
+ if (value.includes('cp.kuaishou.com') || value === 'cp' || value === PRODUCT_CP) {
380
+ product = PRODUCT_CP;
381
+ this.cpPhase = 'initial';
382
+ }
383
+ else if (value.includes('live.kuaishou.com') || value === 'live' || value === PRODUCT_LIVE.toLowerCase()) {
384
+ product = PRODUCT_LIVE;
385
+ }
386
+ else {
387
+ product = PRODUCT_WWW;
388
+ this.wwwPhase = 'initial';
389
+ }
390
+ if (previous && previous !== product)
391
+ this.history = [];
392
+ this.cookies.set('kwpsecproductname', product);
393
+ if (product === PRODUCT_LIVE) {
394
+ this.livePhase = { home: 'initial', room: 'initial', profile: 'initial' };
395
+ this.liveHomeLoginPayCount = 0;
396
+ this.liveRoomLoginPayCount = 0;
397
+ }
398
+ const [href] = siteDefaults(product);
399
+ const snapshot = this.kwfv1 || this.cookie('kwfv1');
400
+ this.signer = this.newSigner(this.kwfv1, href, product, snapshot);
401
+ this.kwwSnapshot = snapshot;
402
+ return this;
403
+ }
404
+ rememberQuartet() {
405
+ const current = QUARTET.map((k) => this.cookie(k));
406
+ if (current.every(Boolean) && !this.history.some((h) => h.join('\0') === current.join('\0')))
407
+ this.history.push(current);
408
+ }
409
+ /**
410
+ * use_cp_upload:选择视频后的 CP 上传上下文。product 回到 kuaishou-vision;
411
+ * 页面 kww 保持 174 字符,而上传 Cookie 的 kwfv1 要换成 CP 0.1.1 kwf 的 218 字符(kwfcv1=999 的那一代)。
412
+ */
413
+ async useCpUpload() {
414
+ const product = PRODUCT_WWW;
415
+ const previous = this.signer;
416
+ if (this.cookie('kwpsecproductname') !== product)
417
+ this.rememberQuartet();
418
+ this.cookies.set('kwpsecproductname', product);
419
+ const snapshot = previous.href === HREF_CP ? previous.kwwSnapshot : this.kwfv1 || this.cookie('kwfv1');
420
+ if (this.liveBootstrapEnabled && this.kwfv1.length !== 218) {
421
+ const upload = new KwwSigner(this.t, '', HREF_CP, this.did, PRODUCT_CP, '', '', snapshot, '999');
422
+ await upload.bootstrap();
423
+ upload.product = product;
424
+ this.signer = upload;
425
+ const generated = await upload.cookies();
426
+ this.kwfv1 = upload.kwfv1;
427
+ this.kwfcv1 = upload.kwfcv1;
428
+ for (const k of ['kwfv1', 'kwssectoken', 'kwscode'])
429
+ if (generated[k])
430
+ this.cookies.set(k, generated[k]);
431
+ }
432
+ else {
433
+ this.signer = new KwwSigner(this.t, this.kwfv1, HREF_CP, this.did, product, this.cookie('kwscode'), this.cookie('kwssectoken'), snapshot, this.kwfcv1);
434
+ }
435
+ this.kwwSnapshot = snapshot;
436
+ }
437
+ /** use_cp_atlas:图文编辑 / 上传上下文,product 保持 onvideo-cp。 */
438
+ useCpAtlas() {
439
+ const product = PRODUCT_CP;
440
+ const previous = this.signer;
441
+ if (this.cookie('kwpsecproductname') !== product)
442
+ this.rememberQuartet();
443
+ this.cookies.set('kwpsecproductname', product);
444
+ const snapshot = previous.href === HREF_CP ? previous.kwwSnapshot : this.kwfv1 || this.cookie('kwfv1');
445
+ this.signer = new KwwSigner(this.t, this.kwfv1, HREF_CP, this.did, product, this.cookie('kwscode'), this.cookie('kwssectoken'), snapshot, this.kwfcv1);
446
+ this.kwwSnapshot = snapshot;
447
+ }
448
+ /**
449
+ * prepare_captcha_context:验证码 iframe 自己起一个 webweapon 实例(产品名 verification-captcha),
450
+ * 把当前 kwfv1 冻结成 iframe 的 kww,按 /s/w/c 下发的 fpUrl / signUrl 现算新票据,覆盖根域的 kwfv1 / kws*。
451
+ * 父页已冻结的 kww 保留;产品 cookie 与父页的并存(Cookie 线序 `captcha`)。
452
+ */
453
+ async prepareCaptchaContext(iframeUrl) {
454
+ const fail = (message) => new SpecificAIError('UPSTREAM', message, { detail: { kind: 'captcha' } });
455
+ if (!iframeUrl.startsWith(`${CAPTCHA_HOST}/iframe/`))
456
+ throw fail('验证码 iframe 地址不是 captcha.zt.kuaishou.com');
457
+ const snapshot = (await this.current()).get('kwfv1') ?? '';
458
+ if (!snapshot)
459
+ throw fail('验证码 iframe 初始化前缺少当前 kwfv1');
460
+ // iframe 拿到新的 secToken 并执行下发的 signUrl,即使父页的票据还有效
461
+ const signer = new KwwSigner(this.t, snapshot, iframeUrl, this.did, PRODUCT_CAPTCHA, '', '', snapshot, '');
462
+ const issued = await signer.cookies();
463
+ if (issued.kwpsecproductname !== PRODUCT_CAPTCHA)
464
+ throw fail('验证码 webweapon 的产品名漂移');
465
+ if (issued.kwssectoken.length !== 88)
466
+ throw fail('验证码 webweapon 的 secToken 长度异常');
467
+ if (!/^[0-9a-f]{64}$/.test(issued.kwscode))
468
+ throw fail('验证码 signUrl 没有产出 64 位小写 hex 的 kwscode');
469
+ this.update([
470
+ ['kwfv1', issued.kwfv1],
471
+ ['kwssectoken', issued.kwssectoken],
472
+ ['kwscode', issued.kwscode],
473
+ ]);
474
+ await this.current();
475
+ const line = await this.cookieHeader('captcha');
476
+ const pairs = line.split('; ').map((part) => {
477
+ const i = part.indexOf('=');
478
+ return [part.slice(0, i), part.slice(i + 1)];
479
+ });
480
+ if (pairs.map(([k]) => k).join() !== SEQUENCES.captcha.join())
481
+ throw fail(`验证码 Cookie 字段缺失或顺序不对:${pairs.map(([k]) => k).join(', ')}`);
482
+ const group = (name) => pairs.filter(([k]) => k === name).map(([, v]) => v);
483
+ if (group('kwpsecproductname').join() !== `${PRODUCT_CAPTCHA},${PRODUCT_WWW}`)
484
+ throw fail('验证码的两个产品 cookie 值或顺序不对');
485
+ const kwfv1 = group('kwfv1');
486
+ if (group('kwssectoken')[0]?.length !== 88 || group('kwscode')[0]?.length !== 64 || kwfv1.length !== 1 || ![174, 218].includes(kwfv1[0].length)) {
487
+ throw fail('验证码 webweapon cookie 长度偏离官方脚本输出');
488
+ }
489
+ // gdfp 的 document.cookie 解析把重复的产品名收拢成后一个值(位置取第一个),其余只含验证码路径可见的 cookie
490
+ const telemetry = new Map();
491
+ for (const [k, v] of pairs)
492
+ telemetry.set(k, v);
493
+ const cfg = signer.config;
494
+ return {
495
+ kww: await signer.sign(),
496
+ cookies: [...telemetry],
497
+ cookieHeader: line,
498
+ scriptUrls: [cfg?.fpUrl, cfg?.signUrl].filter((u) => Boolean(u)),
499
+ };
500
+ }
501
+ /** apply_live_logout:只删登出响应确认删掉的 cookie;设备标识与 webweapon 票据保留(与 Chrome 一致)。 */
502
+ applyLiveLogout(o) {
503
+ if (o.passport)
504
+ for (const k of ['userId', 'userid', 'passToken'])
505
+ this.cookies.delete(k);
506
+ if (o.live)
507
+ for (const k of ['userId', 'userid', 'kuaishou.live.web_st', 'kuaishou.live.web_ph'])
508
+ this.cookies.delete(k);
509
+ this.userId = this.cookie('userId') || this.cookie('userid') || null;
510
+ if (o.live) {
511
+ this.livePhase.room = 'initial';
512
+ this.livePhase.home = 'initial';
513
+ this.liveHomeLoginPayCount = 0;
514
+ this.liveRoomLoginPayCount = 0;
515
+ }
516
+ }
517
+ advanceLive(context, phase) {
518
+ this.livePhase[context] = phase;
519
+ }
520
+ static nonzeroHex(bytes) {
521
+ for (;;) {
522
+ const v = rand.hex(bytes);
523
+ if (/[^0]/.test(v))
524
+ return v;
525
+ }
526
+ }
527
+ /** 一次直播页导航的 Sentry transaction:同页请求共用 32 位 trace id。 */
528
+ beginLiveTransaction(referer) {
529
+ const trace = Session.nonzeroHex(16);
530
+ this.liveSentry.set(referer, { trace, spans: new Set() });
531
+ return trace;
532
+ }
533
+ /** (页面 trace id, 新的 16 位 span id)。 */
534
+ nextLiveSentry(referer) {
535
+ let state = this.liveSentry.get(referer);
536
+ if (!state) {
537
+ this.beginLiveTransaction(referer);
538
+ state = this.liveSentry.get(referer);
539
+ }
540
+ for (;;) {
541
+ const span = Session.nonzeroHex(8);
542
+ if (!state.spans.has(span)) {
543
+ state.spans.add(span);
544
+ return [state.trace, span];
545
+ }
546
+ }
547
+ }
548
+ /** 是否只有直播站票据、没有创作者中心票据(直接打开直播页的会话形态)。 */
549
+ get liveOnly() {
550
+ return !(this.cookie('kuaishou.web.cp.api_st') || this.cookie('kuaishou.web.cp.api_ph'));
551
+ }
552
+ }
553
+ // ================================================================ 响应工具
554
+ /** response_cookies:Set-Cookie 下发的 名字 → 值(删除用的过期 cookie 不算)。 */
555
+ export function responseCookies(res) {
556
+ const out = [];
557
+ for (const line of res.headers.getSetCookie()) {
558
+ const [pair, ...attrs] = line.split(';');
559
+ const i = pair.indexOf('=');
560
+ if (i <= 0)
561
+ continue;
562
+ const name = pair.slice(0, i).trim();
563
+ const value = pair.slice(i + 1).trim();
564
+ const expired = attrs.some((a) => {
565
+ const [k, v = ''] = a.split('=').map((s) => s.trim());
566
+ if (k.toLowerCase() === 'max-age')
567
+ return Number(v) <= 0;
568
+ if (k.toLowerCase() === 'expires')
569
+ return Date.parse(v) <= rand.now();
570
+ return false;
571
+ });
572
+ if (expired)
573
+ continue;
574
+ const j = out.findIndex(([k]) => k === name);
575
+ if (j >= 0)
576
+ out[j] = [name, value];
577
+ else
578
+ out.push([name, value]);
579
+ }
580
+ return out;
581
+ }
582
+ /** 按顺序合并:已有的键原位覆盖,新键追加(Python dict.update)。 */
583
+ export function mergeIssued(into, more) {
584
+ for (const [k, v] of Array.isArray(more) ? more : Object.entries(more)) {
585
+ const i = into.findIndex(([n]) => n === k);
586
+ if (i >= 0)
587
+ into[i] = [k, v];
588
+ else
589
+ into.push([k, v]);
590
+ }
591
+ return into;
592
+ }