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,134 @@
1
+ import { crc32 } from 'node:zlib';
2
+ import { quote } from '../../../core/py.js';
3
+ /**
4
+ * 滑块验证码 `verifyParam` 的加密(上游 utils/sign/captcha_crypto.py,对应 encrypt.js 的 `$encrypt`)。
5
+ *
6
+ * 32 字节定长头 + 流密码密文:魔数、头长度、协议版本、0x1001、6、crc32(appId)、2、crc32(密文)、密文长度,均为小端。
7
+ * 密文是逐字节 `b ^ keystream`,keystream 来自三路 LFSR 组合生成器。没有随机数也没有时间戳。
8
+ */
9
+ const FEEDBACK = [0x80000062, 0x40000020, 0x10000002];
10
+ const MASK = [0x7fffffff, 0x3fffffff, 0x0fffffff];
11
+ const HIGH = [0x80000000, 0xc0000000, 0xf0000000];
12
+ /** q()/f27 里的种子;只有密钥装填后寄存器为 0 才用到。 */
13
+ const SEED = [324508639, 610839776, 4256789809];
14
+ /** 顶层闭包变量 v:`$encrypt` 调 q(payload, v) 时传进去的密钥。 */
15
+ export const CIPHER_KEY = 'BvWTr0uRBGH366Yb';
16
+ const HEADER_LEN = 32;
17
+ const MAGIC = 0xdeadc0de;
18
+ const VERSION_HEX = '00000000000000000003';
19
+ const FIELD_1001 = 4097;
20
+ const CONST_6 = 6;
21
+ const CONST_2 = 2;
22
+ /** 页面 d702 包装器传进来的 appId。 */
23
+ export const APP_ID = 'c7b645db-65e8-401f-b38c-4c07c5fff247';
24
+ const u16le = (v) => Uint8Array.of(v & 0xff, (v >>> 8) & 0xff);
25
+ const u32le = (v) => Uint8Array.of(v & 0xff, (v >>> 8) & 0xff, (v >>> 16) & 0xff, (v >>> 24) & 0xff);
26
+ /** q() + z():三路 LFSR,每字节输出 8 bit 的钥匙流。 */
27
+ class Lfsr3 {
28
+ r;
29
+ constructor(key = CIPHER_KEY) {
30
+ // 密钥循环补齐到 12 字节;三条寄存器都用 key[4..7] 装填
31
+ const raw = [...key].map((ch) => ch.charCodeAt(0) & 0xff);
32
+ while (raw.length < 12)
33
+ raw.push(raw[raw.length - key.length]);
34
+ const regs = [...SEED];
35
+ for (let i = 0; i < 4; i++) {
36
+ const byte = raw[i + 4];
37
+ for (let r = 0; r < 3; r++)
38
+ regs[r] = ((regs[r] << 8) | byte) >>> 0;
39
+ }
40
+ this.r = regs.map((v, i) => v || SEED[i]);
41
+ }
42
+ next() {
43
+ const r = this.r;
44
+ let b1 = r[1] & 1;
45
+ let b2 = r[2] & 1;
46
+ let out = 0;
47
+ for (let i = 0; i < 8; i++) {
48
+ if (r[0] & 1) {
49
+ r[0] = ((r[0] ^ (FEEDBACK[0] >>> 1)) | HIGH[0]) >>> 0;
50
+ if (r[1] & 1) {
51
+ r[1] = ((r[1] ^ (FEEDBACK[1] >>> 1)) | HIGH[1]) >>> 0;
52
+ b1 = 1;
53
+ }
54
+ else {
55
+ r[1] = ((r[1] >>> 1) & MASK[1]) >>> 0;
56
+ b1 = 0;
57
+ }
58
+ }
59
+ else {
60
+ r[0] = ((r[0] >>> 1) & MASK[0]) >>> 0;
61
+ if (r[2] & 1) {
62
+ r[2] = ((r[2] ^ (FEEDBACK[2] >>> 1)) | HIGH[2]) >>> 0;
63
+ b2 = 1;
64
+ }
65
+ else {
66
+ r[2] = ((r[2] >>> 1) & MASK[2]) >>> 0;
67
+ b2 = 0;
68
+ }
69
+ }
70
+ out = ((out << 1) | (b1 ^ b2)) & 0xff;
71
+ }
72
+ return out;
73
+ }
74
+ apply(data) {
75
+ const out = new Uint8Array(data.length);
76
+ for (let i = 0; i < data.length; i++)
77
+ out[i] = (data[i] ^ this.next()) & 0xff;
78
+ return out;
79
+ }
80
+ }
81
+ /** d702 的 `h()`:逐个 UTF-16 码元取低 8 位(不是 UTF-8,非 ASCII 字符会“丢字”,与浏览器一致)。 */
82
+ export function toBytes(payload) {
83
+ const out = new Uint8Array(payload.length);
84
+ for (let i = 0; i < payload.length; i++)
85
+ out[i] = payload.charCodeAt(i) & 0xff;
86
+ return out;
87
+ }
88
+ /** `$encrypt`:明文 → 带 32 字节头的密文块。 */
89
+ export function encrypt(payload, appId = APP_ID) {
90
+ const plain = typeof payload === 'string' ? toBytes(payload) : payload;
91
+ const cipher = new Lfsr3().apply(plain);
92
+ return Buffer.concat([
93
+ u32le(MAGIC),
94
+ u16le(HEADER_LEN),
95
+ Buffer.from(VERSION_HEX, 'hex'),
96
+ u16le(FIELD_1001),
97
+ Uint8Array.of(CONST_6),
98
+ u32le(crc32(Buffer.from(appId, 'utf8')) >>> 0),
99
+ Uint8Array.of(CONST_2),
100
+ u32le(crc32(cipher) >>> 0),
101
+ u32le(cipher.length),
102
+ cipher,
103
+ ]);
104
+ }
105
+ /** `$encrypt` 的逆:剥掉 32 字节头,用同一钥匙流还原明文(latin-1)。 */
106
+ export function decrypt(blob) {
107
+ const b = Buffer.from(blob);
108
+ if (b.length < HEADER_LEN)
109
+ throw new Error(`密文块太短:${b.length} < ${HEADER_LEN}`);
110
+ if (b.readUInt32LE(0) !== MAGIC)
111
+ throw new Error('魔数不对');
112
+ const cipher = b.subarray(HEADER_LEN);
113
+ if (b.readUInt32LE(28) !== cipher.length)
114
+ throw new Error('长度字段与实际密文不符');
115
+ if (b.readUInt32LE(24) !== crc32(cipher) >>> 0)
116
+ throw new Error('密文 CRC32 校验失败');
117
+ return Buffer.from(new Lfsr3().apply(cipher)).toString('latin1');
118
+ }
119
+ /** d702 里的 `qs.stringify`:按插入顺序、键值都编码、跳过 null / undefined、布尔写成 true / false。 */
120
+ export function qsStringify(params) {
121
+ const pairs = Array.isArray(params) ? params : Object.entries(params);
122
+ const out = [];
123
+ for (const [key, value] of pairs) {
124
+ if (value == null)
125
+ continue;
126
+ const v = typeof value === 'boolean' ? (value ? 'true' : 'false') : String(value);
127
+ out.push(`${quote(String(key), '*-._')}=${quote(v, '*-._')}`);
128
+ }
129
+ return out.join('&');
130
+ }
131
+ /** 参数 → 可以直接放进请求体的 base64 `verifyParam`。 */
132
+ export function verifyParam(params, appId = APP_ID) {
133
+ return Buffer.from(encrypt(qsStringify(params), appId)).toString('base64');
134
+ }
@@ -0,0 +1,180 @@
1
+ import { compactJson } from '../../../core/py.js';
2
+ import * as rand from '../../../core/rand.js';
3
+ import { PROFILE, RESOLUTION } from './profile.js';
4
+ /**
5
+ * 滑块验证码提交时的浏览器指纹块(上游 utils/captcha_fp.py):`gpuInfo`(4 键)与 `captchaExtraParam`(65 键)。
6
+ * 默认值是上游在 Chrome 151 的验证码 iframe 里用 CDP 取回的真值,键序即 JSON.stringify 的输出顺序,不能重排。
7
+ * UA、时区、语言、CPU、平台、分辨率与几何(key3–key14、key37、key39)取自统一的浏览器档案 {@link PROFILE},
8
+ * 与请求头、gdfp 载荷保持一致;canvas / webgl / 字体 / 音频等哈希和 key35 / key36 是这台浏览器稳定的环境哈希,不随机改写。
9
+ * 事件耗时与鼠标轨迹每次现算(原样重放会被判成回放,服务端回 350014)。
10
+ *
11
+ * 上游还可以用 `KuaishouAuth.set_captcha_fingerprint` 换成浏览器现抓的指纹(只有 Python API),specific-ai 没有移植,
12
+ * 始终用这里的默认值。
13
+ */
14
+ export const GPU_INFO = {
15
+ glRenderer: 'WebKit WebGL',
16
+ glVendor: 'WebKit',
17
+ unmaskRenderer: PROFILE.webglRenderer,
18
+ unmaskVendor: PROFILE.webglVendor,
19
+ };
20
+ /** Ga() 的完整产物。没有形如整数的键,普通对象的插入顺序就是输出顺序。 */
21
+ export const CAPTCHA_EXTRA_PARAM = {
22
+ ua: PROFILE.ua,
23
+ userAgent: PROFILE.ua,
24
+ timeZone: PROFILE.timeZone,
25
+ language: PROFILE.language,
26
+ cpuCoreCnt: String(PROFILE.cpuCores),
27
+ platform: PROFILE.platform,
28
+ riskBrowser: 'false',
29
+ webDriver: 'false',
30
+ exactRiskBrowser: 'false',
31
+ webDriverDeep: 'false',
32
+ exactRiskBrowser2: 'false',
33
+ webDriverDeep2: 'false',
34
+ battery: '1',
35
+ plugins: '1a68ba429dd293b14e41a28b6535aa590',
36
+ resolution: RESOLUTION,
37
+ pixelDepth: '24',
38
+ colorDepth: '24',
39
+ canvasGraphFingerPrint: '16d9e8c3c415322269b38d1449ec153ea',
40
+ canvasGraph: '16d9e8c3c415322269b38d1449ec153ea',
41
+ canvasTextFingerPrintEn: '11f6fbca159129e1aa778346fbe0b9c6d',
42
+ canvasTextEn: '11f6fbca159129e1aa778346fbe0b9c6d',
43
+ canvasTextFingerPrintZh: '1c086b7340d764f7c141910da1af587f2',
44
+ canvasTextZh: '1c086b7340d764f7c141910da1af587f2',
45
+ webglGraphFingerPrint: '1537e3a006691fc8474abd41438c08a5d',
46
+ webglGraph: '1537e3a006691fc8474abd41438c08a5d',
47
+ webglGPUFingerPrint: '1192e522040bbe560567d4321fed3c16a',
48
+ webglGpu: '1192e522040bbe560567d4321fed3c16a',
49
+ cssFontFingerPrintEn: '1e4c353075d9fe0c911b00a7f7dba2f26',
50
+ fontListEn: '1e4c353075d9fe0c911b00a7f7dba2f26',
51
+ cssFontFingerPrintZh: '154ef9bb94c26d3f7b091868f7a41c387',
52
+ fontListZh: '154ef9bb94c26d3f7b091868f7a41c387',
53
+ voiceFingerPrint: '1e49aa834eb6783f919c45022f2fcf7ef',
54
+ audioTriangle: '1e49aa834eb6783f919c45022f2fcf7ef',
55
+ nativeFunc: '1973dcbb27a04c3a2ee240d9d2549e105',
56
+ // 占位;每次请求都换成会话的 did
57
+ key1: 'web_' + '0'.repeat(32),
58
+ key2: 1786880034017,
59
+ key3: PROFILE.ua,
60
+ key4: PROFILE.productSub,
61
+ key5: PROFILE.language,
62
+ key6: PROFILE.product,
63
+ // screen.width / height、availWidth / availHeight、innerHeight、innerWidth(取屏幕宽)、outerHeight、outerWidth
64
+ key7: PROFILE.screenWidth,
65
+ key8: PROFILE.screenHeight,
66
+ key9: PROFILE.availWidth,
67
+ key10: PROFILE.availHeight,
68
+ key11: PROFILE.innerHeight,
69
+ key12: PROFILE.screenWidth,
70
+ key13: PROFILE.outerHeight,
71
+ key14: PROFILE.outerWidth,
72
+ key15: '00000111',
73
+ key16: 1,
74
+ key17: 1,
75
+ key18: ['0,103,-1,-1,-1,prepare1'],
76
+ key19: { prepare1: '0,103,-1,-1,-1' },
77
+ key20: ['0,102,-1,-1,-1,prepare1'],
78
+ key21: { prepare1: '0,102,-1,-1,-1' },
79
+ key22: ['0,102,-1,-1,-1,prepare1'],
80
+ key23: { prepare1: '0,102,-1,-1,-1' },
81
+ key24: ['0,102,-1,-1,-1,prepare1'],
82
+ key25: { prepare1: '0,102,-1,-1,-1' },
83
+ key26: {
84
+ key27: [
85
+ '0,1,40012,1310,169,prepare1',
86
+ '1,1,40015,1305,153,prepare1',
87
+ '2,1,40017,1301,140,prepare1',
88
+ '3,1,40022,1293,119,prepare1',
89
+ '4,1,40026,1284,93,prepare1',
90
+ '5,1,40030,1278,75,prepare1',
91
+ '6,1,40033,1275,66,prepare1',
92
+ '7,1,41016,-1,-1,prepare1',
93
+ '8,1,41017,-1,-1,prepare1',
94
+ '9,1,41022,1385,2,prepare1',
95
+ ],
96
+ key28: [],
97
+ key29: [],
98
+ key30: [],
99
+ key31: { prepare1: '9,1,41022,1385,2' },
100
+ key32: {},
101
+ key33: {},
102
+ key34: {},
103
+ },
104
+ key35: 'c1740e16914faedad278c0a1dea75811',
105
+ key36: 'f22a94013fc94e90e2af2798023a1985',
106
+ key37: PROFILE.devicePixelRatio,
107
+ key38: 'not support',
108
+ key39: PROFILE.cpuCores,
109
+ };
110
+ /**
111
+ * _session_fields:每次验证都现算的事件耗时与鼠标轨迹(key18–key26)。key35 / key36 是稳定的浏览器哈希,不在这里。
112
+ * 上游用 `random.Random(now_ms ^ random.getrandbits(32))` 新建一个生成器;这里直接用全局随机数,
113
+ * 先消耗一次对应 getrandbits(32) 的那个数,后续调用顺序与上游相同。
114
+ */
115
+ export function sessionFields(_nowMs) {
116
+ void rand.random();
117
+ const timing = () => {
118
+ const s = `0,${rand.randint(88, 132)},-1,-1,-1`;
119
+ return [[`${s},prepare1`], { prepare1: s }];
120
+ };
121
+ const [k18, k19] = timing();
122
+ const [k20, k21] = timing();
123
+ const [k22, k23] = timing();
124
+ const [k24, k25] = timing();
125
+ let t = rand.randint(38000, 46000);
126
+ let x = rand.randint(1240, 1400);
127
+ let y = rand.randint(120, 210);
128
+ const track = [];
129
+ const n = rand.randint(8, 14);
130
+ for (let i = 0; i < n; i++) {
131
+ t += rand.randint(2, 9);
132
+ x += rand.randint(-9, 3);
133
+ y += rand.randint(-22, 6);
134
+ // 真实样本里后面几条的 x/y 是 -1(元素外 / 未捕获)
135
+ if (i >= 7 && rand.random() < 0.4)
136
+ track.push(`${i},1,${t},-1,-1,prepare1`);
137
+ else
138
+ track.push(`${i},1,${t},${x},${y},prepare1`);
139
+ }
140
+ const tail = track.at(-1);
141
+ const last = tail.slice(0, tail.lastIndexOf(','));
142
+ return {
143
+ key18: k18,
144
+ key19: k19,
145
+ key20: k20,
146
+ key21: k21,
147
+ key22: k22,
148
+ key23: k23,
149
+ key24: k24,
150
+ key25: k25,
151
+ key26: { key27: track, key28: [], key29: [], key30: [], key31: { prepare1: last }, key32: {}, key33: {}, key34: {} },
152
+ };
153
+ }
154
+ /** `gpuInfo` 字段的值(JSON 字符串,四个已知键在前)。 */
155
+ export function gpuInfoJson(overrides = {}) {
156
+ const info = { ...GPU_INFO, ...overrides };
157
+ const ordered = {};
158
+ for (const k of ['glRenderer', 'glVendor', 'unmaskRenderer', 'unmaskVendor'])
159
+ if (k in info)
160
+ ordered[k] = info[k];
161
+ for (const [k, v] of Object.entries(info))
162
+ if (!(k in ordered))
163
+ ordered[k] = v;
164
+ return compactJson(ordered);
165
+ }
166
+ /**
167
+ * `captchaExtraParam` 字段的值(JSON 字符串):key1 = did,key2 = 当前毫秒,会话字段现算。
168
+ * overrides 先合并,再刷新 key1 / key2 与会话字段:浏览器快照里的 key2、key18–key26 属于旧的 iframe 实例,
169
+ * 原样重放正是 350014 的原因。
170
+ */
171
+ export function captchaExtraParamJson(o = {}) {
172
+ const data = { ...CAPTCHA_EXTRA_PARAM, ...o.overrides };
173
+ const stamp = o.nowMs ?? rand.now();
174
+ if (o.did)
175
+ data.key1 = o.did;
176
+ data.key2 = stamp;
177
+ if (o.freshSession ?? true)
178
+ Object.assign(data, sessionFields(stamp));
179
+ return compactJson(data);
180
+ }
@@ -0,0 +1,273 @@
1
+ import { SpecificAIError } from '../../../core/errors.js';
2
+ import { compactJson, jsonLoads, parseQsl, pyFloatStr, pyRound, quote } from '../../../core/py.js';
3
+ import * as rand from '../../../core/rand.js';
4
+ import { verifyParam } from './captcha-crypto.js';
5
+ import { captchaExtraParamJson, gpuInfoJson } from './captcha-fp.js';
6
+ import { findGapX } from './gap.js';
7
+ import * as gdfp from './gdfp.js';
8
+ import { ACCEPT_AXIOS, ACCEPT_ENCODING, ACCEPT_LANGUAGE, CAPTCHA_HOST, RECO_REFERER, UA } from './profile.js';
9
+ /**
10
+ * 快手滑块验证码(captcha.zt.kuaishou.com,上游 utils/captcha.py 与 KuaishouAPI._pass_captcha)。
11
+ *
12
+ * 风控接口返回 `{"data":{"result":400002,"url":"…/iframe/index.html?captchaSession=…"}}`(GraphQL 是
13
+ * `errors` + `data.captcha.url`)时:验证码 iframe 的 webweapon 引导 → config → 下载背景图与滑块图 → 找缺口 →
14
+ * 造轨迹 → gdfp manMachine 预检 → `$encrypt` → 提交 verifyUrl2(kSecretApiVerify)。通过后调用方重新序列化
15
+ * 同一条 Cookie 线序,把原请求重发一次。
16
+ */
17
+ const CONFIG_URL = `${CAPTCHA_HOST}/rest/zt/captcha/sliding/config`;
18
+ const VERIFY_URL = `${CAPTCHA_HOST}/rest/zt/captcha/sliding/kSecretApiVerify`;
19
+ const TIMEOUT = 30;
20
+ /** Python 的真值判断:空数组 / 空对象 / 空串 / 0 / null 为假。 */
21
+ function truthy(v) {
22
+ if (Array.isArray(v))
23
+ return v.length > 0;
24
+ if (v && typeof v === 'object')
25
+ return Object.keys(v).length > 0;
26
+ return Boolean(v);
27
+ }
28
+ /** _is_risk:REST `data.result == 400002` 且有 `data.url`,或 GraphQL `errors` + `data.captcha.url`。 */
29
+ export function isRisk(resp) {
30
+ const data = resp?.data && typeof resp.data === 'object' ? resp.data : {};
31
+ if (data.result === 400002 && truthy(data.url))
32
+ return true;
33
+ return truthy(data.captcha?.url) && truthy(resp?.errors);
34
+ }
35
+ /** _graphql_risk_as_rest:GraphQL 的挑战改成 REST 的形状,交给同一个求解器。 */
36
+ export function graphqlRiskAsRest(resp) {
37
+ const url = resp?.data?.captcha?.url;
38
+ return url ? { data: { result: 400002, url } } : (resp ?? {});
39
+ }
40
+ /** extract_captcha_url:验证码 iframe 的完整地址(它的 query 会带进 Referer,不能截短)。 */
41
+ export function extractCaptchaUrl(risk) {
42
+ const data = truthy(risk?.data) ? risk.data : risk;
43
+ return String(data?.url || data?.captcha?.url || '');
44
+ }
45
+ /** extract_session:iframe 地址里的 captchaSession。 */
46
+ export function extractSession(risk) {
47
+ const url = extractCaptchaUrl(risk);
48
+ if (!url)
49
+ return '';
50
+ const q = url.indexOf('?');
51
+ if (q < 0)
52
+ return '';
53
+ const hash = url.indexOf('#', q);
54
+ const query = url.slice(q + 1, hash < 0 ? undefined : hash);
55
+ return parseQsl(query).find(([k, v]) => k === 'captchaSession' && v !== '')?.[1] ?? '';
56
+ }
57
+ /**
58
+ * build_trajectory:先加速后减速、末段带小幅回调与抖动的拖动轨迹(t 是毫秒偏移)。
59
+ * distanceInt 表示 distance 在上游是 int(缺口 x 减 disX)还是 float(兜底的 1.0)。
60
+ */
61
+ export function buildTrajectory(distance, distanceInt, startY = 0) {
62
+ const points = [];
63
+ let t = 0;
64
+ let x = 0;
65
+ let xInt = false;
66
+ let v = 0;
67
+ // 前 70%~80% 加速,之后减速
68
+ const mid = distance * (0.7 + rand.random() * 0.1);
69
+ while (x < distance) {
70
+ const a = x < mid ? 2.0 + rand.random() * 2.0 : -(2.5 + rand.random() * 2.0);
71
+ const dt = 8 + rand.random() * 14;
72
+ v = Math.max(0.4, v + (a * dt) / 100.0);
73
+ // min(distance, x + v):相等时 Python 返回第一个参数
74
+ if (x + v < distance) {
75
+ x = x + v;
76
+ xInt = false;
77
+ }
78
+ else {
79
+ x = distance;
80
+ xInt = distanceInt;
81
+ }
82
+ t += Math.trunc(dt);
83
+ points.push({ x: xInt ? x : pyRound(x, 2), xInt, y: startY + pyRound(rand.gauss(0, 0.8), 2), t });
84
+ }
85
+ // 冲过头一点再拉回来
86
+ for (const back of [2.2, 1.1, 0.4, 0.0]) {
87
+ t += Math.trunc(20 + rand.random() * 40);
88
+ points.push({ x: pyRound(distance - back, 2), xInt: false, y: startY + pyRound(rand.gauss(0, 0.6), 2), t });
89
+ }
90
+ return points;
91
+ }
92
+ /** format_trajectory:`x|y|dt` 逗号连接,dt 相对第一个点。 */
93
+ export function formatTrajectory(points) {
94
+ if (!points.length)
95
+ return '';
96
+ const t0 = points[0].t;
97
+ return points.map((p) => `${p.xInt ? String(p.x) : pyFloatStr(p.x)}|${pyFloatStr(p.y)}|${p.t - t0}`).join(',');
98
+ }
99
+ /** Python 的 `int(v)`:数字截断,字符串按十进制解析。 */
100
+ function pyInt(v) {
101
+ const n = typeof v === 'number' ? v : Number(String(v).trim());
102
+ if (!Number.isFinite(n))
103
+ throw new SpecificAIError('UPSTREAM', `验证码配置里的数值不合法:${String(v)}`, { detail: { kind: 'captcha' } });
104
+ return Math.trunc(n);
105
+ }
106
+ /** SlidingCaptcha:一次滑块验证会话。 */
107
+ export class SlidingCaptcha {
108
+ ks;
109
+ o;
110
+ config = {};
111
+ constructor(ks, o) {
112
+ this.ks = ks;
113
+ this.o = o;
114
+ }
115
+ /** 各请求的 Chrome 请求头:config / verify 是 XHR(带 kww),图片是 `<img>` 子资源(不带)。 */
116
+ headers(kind) {
117
+ const h = kind === 'image'
118
+ ? [
119
+ ['user-agent', UA],
120
+ ['accept', 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8'],
121
+ ['accept-encoding', ACCEPT_ENCODING],
122
+ ['accept-language', ACCEPT_LANGUAGE],
123
+ ['referer', this.o.referer],
124
+ ['sec-fetch-dest', 'image'],
125
+ ['sec-fetch-mode', 'no-cors'],
126
+ ['sec-fetch-site', 'same-origin'],
127
+ ]
128
+ : [
129
+ ['user-agent', UA],
130
+ ['accept', ACCEPT_AXIOS],
131
+ ['accept-encoding', ACCEPT_ENCODING],
132
+ ['accept-language', ACCEPT_LANGUAGE],
133
+ ['origin', CAPTCHA_HOST],
134
+ ['referer', this.o.referer],
135
+ ['sec-fetch-dest', 'empty'],
136
+ ['sec-fetch-mode', 'cors'],
137
+ ['sec-fetch-site', 'same-origin'],
138
+ ];
139
+ if (kind === 'config')
140
+ h.push(['content-type', 'application/x-www-form-urlencoded']);
141
+ else if (kind === 'verify')
142
+ h.push(['content-type', 'application/json']);
143
+ if (kind !== 'image' && this.o.kww)
144
+ h.push(['kww', this.o.kww]);
145
+ return h;
146
+ }
147
+ /** 第 1 步:拿 captchaSn 与图片尺寸。 */
148
+ async loadConfig() {
149
+ if (!this.o.kww || !this.o.cookieHeader)
150
+ throw new SpecificAIError('UPSTREAM', '验证码 config 请求缺少页面 kww 或 Cookie', { detail: { kind: 'captcha' } });
151
+ const res = await this.ks.send({ method: 'POST', url: CONFIG_URL, headers: this.headers('config'), cookie: this.o.cookieHeader, body: `captchaSession=${quote(this.o.session, '')}`, timeout: TIMEOUT });
152
+ this.config = await this.ks.json(res);
153
+ if (this.config?.result !== 1)
154
+ this.ks.log.debug('验证码 config 失败', this.config);
155
+ return this.config;
156
+ }
157
+ /** 第 2 步:下背景图与滑块图。 */
158
+ async loadImages() {
159
+ const sn = String(this.config.captchaSn);
160
+ const get = async (url) => {
161
+ const res = await this.ks.send({ method: 'GET', url, query: [['captchaSn', sn]], headers: this.headers('image'), cookie: this.o.cookieHeader, timeout: TIMEOUT });
162
+ return new Uint8Array(await res.arrayBuffer());
163
+ };
164
+ const bg = await get(String(this.config.bgPicUrl));
165
+ const cut = await get(String(this.config.cutPicUrl));
166
+ return [bg, cut];
167
+ }
168
+ /** 走完整条:config → 下图 → 找缺口 → 造轨迹 → gdfp 预检 → 加密提交。返回 verify 的响应。 */
169
+ async solve() {
170
+ if (!Object.keys(this.config).length)
171
+ await this.loadConfig();
172
+ if (this.config?.result !== 1)
173
+ return this.config;
174
+ const [bg, cut] = await this.loadImages();
175
+ const gapX = await findGapX(bg, cut);
176
+ const disX = pyInt(this.config.disX ?? 0);
177
+ // max(1.0, gap_x - dis_x):差值大于 1 时是 int,否则是 float 的 1.0
178
+ const diff = gapX - disX;
179
+ const distanceInt = diff > 1;
180
+ const distance = distanceInt ? diff : 1;
181
+ this.ks.log.debug(`验证码缺口 x=${gapX},起点 disX=${disX},移动 ${distance.toFixed(1)}px`);
182
+ const points = buildTrajectory(distance, distanceInt, pyInt(this.config.disY ?? 0));
183
+ // 提交前先做 gdfp manMachine 预检,不做服务端回 350014 anti check err
184
+ await this.precheck();
185
+ const payload = [
186
+ ['captchaSn', this.config.captchaSn],
187
+ ['bgDisWidth', pyInt(this.config.bgPicWidth)],
188
+ ['bgDisHeight', pyInt(this.config.bgPicHeight)],
189
+ ['cutDisWidth', pyInt(this.config.cutPicWidth)],
190
+ ['cutDisHeight', pyInt(this.config.cutPicHeight)],
191
+ ['relativeX', Math.round(disX + distance)],
192
+ ['relativeY', pyInt(this.config.disY ?? 0)],
193
+ ['trajectory', formatTrajectory(points)],
194
+ ['gpuInfo', gpuInfoJson()],
195
+ ['captchaExtraParam', captchaExtraParamJson({ did: this.o.did })],
196
+ ];
197
+ return this.submit(payload);
198
+ }
199
+ /** gdfp manMachine 预检:SDK_INIT → core → whole,任何一步不通过都不提交 verify。 */
200
+ async precheck() {
201
+ const r = await gdfp.report(this.ks, {
202
+ did: this.o.did,
203
+ cookies: this.o.cookies,
204
+ parentUrl: this.o.parentUrl,
205
+ iframeUrl: this.o.referer,
206
+ ua: UA,
207
+ scriptUrls: this.o.scriptUrls,
208
+ });
209
+ const reportUrls = r.config?.reportConfig?.reportUrls ?? [];
210
+ if (r.init?.result !== 1 || r.config?.switch !== 1 || r.config?.status !== 1 || !reportUrls.length || r.core?.result !== 1 || r.whole?.result !== 1) {
211
+ throw new SpecificAIError('UPSTREAM', '验证码 gdfp 预检没有完整通过,不提交 verify', { detail: { kind: 'captcha' } });
212
+ }
213
+ }
214
+ /** 把载荷加密成 verifyParam 提交到 verifyUrl2(明文 verifyUrl 服务端回 350013)。 */
215
+ async submit(payload) {
216
+ if (!this.o.kww || !this.o.cookieHeader)
217
+ throw new SpecificAIError('UPSTREAM', '验证码 verify 缺少页面 kww 或 Cookie', { detail: { kind: 'captcha' } });
218
+ const res = await this.ks.send({
219
+ method: 'POST',
220
+ url: String(this.config.verifyUrl2 || VERIFY_URL),
221
+ headers: this.headers('verify'),
222
+ cookie: this.o.cookieHeader,
223
+ body: compactJson({ verifyParam: verifyParam(payload) }),
224
+ timeout: TIMEOUT,
225
+ });
226
+ const text = await res.text();
227
+ try {
228
+ return jsonLoads(text);
229
+ }
230
+ catch {
231
+ return { _status: res.status, _text: text.slice(0, 200) };
232
+ }
233
+ }
234
+ }
235
+ /**
236
+ * _pass_captcha:撞上滑块时过一次验证码。通过返回 true;没过或出错返回 false,调用方按原样返回风控响应
237
+ * (最后报 RISK_CONTROL)。referer 是触发风控的业务页。
238
+ */
239
+ export async function passCaptcha(ks, risk, referer) {
240
+ const session = extractSession(risk);
241
+ if (!session)
242
+ return false;
243
+ ks.log.info('快手要求滑块验证,正在自动验证…');
244
+ let result;
245
+ try {
246
+ const iframeUrl = extractCaptchaUrl(risk);
247
+ const context = await ks.s.prepareCaptchaContext(iframeUrl);
248
+ const solver = new SlidingCaptcha(ks, {
249
+ session,
250
+ cookies: context.cookies,
251
+ did: ks.s.did,
252
+ referer: iframeUrl,
253
+ kww: context.kww,
254
+ cookieHeader: context.cookieHeader,
255
+ parentUrl: referer || RECO_REFERER,
256
+ scriptUrls: context.scriptUrls,
257
+ });
258
+ result = await solver.solve();
259
+ }
260
+ catch (err) {
261
+ ks.log.warn(`自动过滑块出错:${err.message}`);
262
+ ks.lastCaptcha = { error: err.message };
263
+ return false;
264
+ }
265
+ const ok = result?.result === 1;
266
+ if (ok)
267
+ ks.log.info('滑块验证通过,重发原请求');
268
+ else
269
+ ks.log.warn(`滑块验证没有通过:${JSON.stringify(result).slice(0, 120)}`);
270
+ // 没过时把服务端的回复(result、error_msg)带进最后的 RISK_CONTROL,方便判断是缺口不对(350002)还是人机检查不认(350014)
271
+ ks.lastCaptcha = ok ? null : { result: result?.result ?? null, error_msg: result?.error_msg ?? result?.message ?? null };
272
+ return ok;
273
+ }