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,247 @@
1
+ import { createCipheriv, createECDH, createHash, createHmac, createPrivateKey, createPublicKey } from 'node:crypto';
2
+ import { sha256Hex } from '../../../core/hash.js';
3
+ import { compactJson } from '../../../core/py.js';
4
+ import * as rand from '../../../core/rand.js';
5
+ /**
6
+ * bd-ticket-guard、x-tt-session-dtrait、ImageX/VOD 网关签名(上游 utils/bd_ticket.py、dtrait.py、imagex_sign.py、passport.py)。
7
+ *
8
+ * 随机数一律经 core/rand:ECDSA 的 k、密钥生成与 RSA 填充都按上游 python-ecdsa / os.urandom 的消耗方式取字节,
9
+ * 对拍时与上游逐字节一致;平时 rand.bytes 走 crypto.randomBytes。
10
+ */
11
+ // ================================================================ P-256
12
+ const P256_ORDER = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551n;
13
+ const SPKI_PREFIX = Buffer.from('3059301306072a8648ce3d020106082a8648ce3d03010703420004', 'hex');
14
+ const toBig = (buf) => BigInt('0x' + (Buffer.from(buf).toString('hex') || '0'));
15
+ const toBuf32 = (n) => Buffer.from(n.toString(16).padStart(64, '0'), 'hex');
16
+ function modInv(a, m) {
17
+ let [r0, r1] = [((a % m) + m) % m, m];
18
+ let [s0, s1] = [1n, 0n];
19
+ while (r1) {
20
+ const q = r0 / r1;
21
+ [r0, r1] = [r1, r0 - q * r1];
22
+ [s0, s1] = [s1, s0 - q * s1];
23
+ }
24
+ return ((s0 % m) + m) % m;
25
+ }
26
+ /** python-ecdsa 的 randrange(order):取 33 字节,截前 256 位加 1,落在 [1, order) 为止。 */
27
+ function randrange(order = P256_ORDER) {
28
+ for (;;) {
29
+ const ent = rand.bytes(33);
30
+ const num = (toBig(ent) >> 8n) + 1n;
31
+ if (num > 0n && num < order)
32
+ return num;
33
+ }
34
+ }
35
+ /** 私钥标量:接受 PEM(SEC1 / PKCS8)或十六进制。 */
36
+ function privateScalar(prv) {
37
+ if (prv.includes('-----BEGIN')) {
38
+ const jwk = createPrivateKey(prv).export({ format: 'jwk' });
39
+ return toBig(Buffer.from(jwk.d, 'base64url'));
40
+ }
41
+ return BigInt('0x' + prv.trim());
42
+ }
43
+ /** k·G 的未压缩点(借 ECDH 做标量乘)。 */
44
+ function mulG(k) {
45
+ const ecdh = createECDH('prime256v1');
46
+ ecdh.setPrivateKey(toBuf32(k));
47
+ return ecdh.getPublicKey();
48
+ }
49
+ /** 生成登录用的 P-256 密钥对,返回 SEC1 PEM(上游 generate_ec_keypair)。 */
50
+ export function generateEcKey() {
51
+ const d = randrange();
52
+ const pub = mulG(d);
53
+ const jwk = { kty: 'EC', crv: 'P-256', d: toBuf32(d).toString('base64url'), x: pub.subarray(1, 33).toString('base64url'), y: pub.subarray(33).toString('base64url') };
54
+ return createPrivateKey({ key: jwk, format: 'jwk' }).export({ type: 'sec1', format: 'pem' }).toString();
55
+ }
56
+ /** bd-ticket-guard-ree-public-key:base64(04 || X || Y)。 */
57
+ export function reeKey(prv) {
58
+ return mulG(privateScalar(prv)).toString('base64');
59
+ }
60
+ function derInt(n) {
61
+ let hex = n.toString(16);
62
+ if (hex.length % 2)
63
+ hex = '0' + hex;
64
+ let b = Buffer.from(hex, 'hex');
65
+ if (b[0] & 0x80)
66
+ b = Buffer.concat([Buffer.from([0]), b]);
67
+ return Buffer.concat([Buffer.from([0x02, b.length]), b]);
68
+ }
69
+ /** ECDSA-SHA256,DER 编码(python-ecdsa 的 sign(..., sigencode_der),不做 low-s)。 */
70
+ export function ecdsaSign(message, prv) {
71
+ const d = privateScalar(prv);
72
+ const z = toBig(createHash('sha256').update(message, 'utf8').digest());
73
+ for (;;) {
74
+ const k = randrange();
75
+ const r = toBig(mulG(k).subarray(1, 33)) % P256_ORDER;
76
+ const s = (modInv(k, P256_ORDER) * ((z + ((d * r) % P256_ORDER)) % P256_ORDER)) % P256_ORDER;
77
+ if (!r || !s)
78
+ continue;
79
+ const body = Buffer.concat([derInt(r), derInt(s)]);
80
+ return Buffer.concat([Buffer.from([0x30, body.length]), body]).toString('base64');
81
+ }
82
+ }
83
+ /** 服务端证书里的 65 字节公钥点:`pub.<b64>` 或 PEM 证书。 */
84
+ function serverPoint(cert) {
85
+ if (cert.startsWith('pub.'))
86
+ return Buffer.from(cert.slice(4), 'base64');
87
+ const der = Buffer.from(cert.replace(/-----[^-]+-----/g, '').replace(/\s+/g, ''), 'base64');
88
+ const i = der.indexOf(SPKI_PREFIX);
89
+ if (i < 0)
90
+ throw new Error('服务端证书中未找到 P-256 公钥');
91
+ const start = i + SPKI_PREFIX.length;
92
+ return Buffer.concat([Buffer.from([4]), der.subarray(start, start + 64)]);
93
+ }
94
+ function hkdf(ikm, length = 32) {
95
+ const prk = createHmac('sha256', Buffer.alloc(32)).update(ikm).digest();
96
+ let okm = Buffer.alloc(0);
97
+ let block = Buffer.alloc(0);
98
+ for (let counter = 1; okm.length < length; counter++) {
99
+ block = createHmac('sha256', prk).update(Buffer.concat([block, Buffer.from([counter])])).digest();
100
+ okm = Buffer.concat([okm, block]);
101
+ }
102
+ return okm.subarray(0, length);
103
+ }
104
+ /** ECDH(客户端私钥, 服务端公钥) → HKDF-SHA256 → 32 字节 HMAC 密钥。 */
105
+ export function ecdhKey(prv, serverCert) {
106
+ const ecdh = createECDH('prime256v1');
107
+ ecdh.setPrivateKey(toBuf32(privateScalar(prv)));
108
+ return hkdf(ecdh.computeSecret(serverPoint(serverCert)));
109
+ }
110
+ export function hmacSign(message, key) {
111
+ return createHmac('sha256', key).update(message, 'utf8').digest('base64');
112
+ }
113
+ /** bd-ticket-guard-client-data:有 ECDH 密钥走 HMAC,否则 ECDSA。 */
114
+ export function ticketClientData(api, ticket, tsSign, prv, key, timestamp = rand.nowSeconds(), tTrust) {
115
+ const content = `ticket=${ticket}&path=${api}&timestamp=${timestamp}`;
116
+ const algo = key ? 'hmac' : 'ecdsa';
117
+ const payload = {
118
+ ts_sign: tsSign,
119
+ req_content: 'ticket,path,timestamp',
120
+ req_sign: key ? hmacSign(content, key) : ecdsaSign(content, prv),
121
+ timestamp,
122
+ };
123
+ if (tTrust !== undefined)
124
+ payload.t_trust = tTrust;
125
+ return { data: Buffer.from(compactJson(payload), 'utf8').toString('base64'), algo };
126
+ }
127
+ /** web-version 由 ts_sign 前缀决定:ts.1 → 1,其余 → 2。 */
128
+ export const ticketGuardVersion = (tsSign) => (tsSign.startsWith('ts.1') ? 1 : 2);
129
+ /** bd_ticket_guard_client_data:把自己的公钥交给服务端(base64(JSON) 再 encodeURIComponent)。 */
130
+ export function clientDataCookie(prv) {
131
+ const payload = {
132
+ 'bd-ticket-guard-version': 2,
133
+ 'bd-ticket-guard-iteration-version': 1,
134
+ 'bd-ticket-guard-ree-public-key': reeKey(prv),
135
+ 'bd-ticket-guard-web-version': 2,
136
+ };
137
+ return encodeURIComponent(Buffer.from(compactJson(payload)).toString('base64'));
138
+ }
139
+ /** bd_ticket_guard_client_data_v2:对 `sec_ts=<sec_ts>` 做 HMAC(ECDH 密钥)。 */
140
+ export function clientDataV2Cookie(prv, secTs, serverCert, tsSign = '') {
141
+ const payload = { ree_public_key: reeKey(prv) };
142
+ if (tsSign)
143
+ payload.ts_sign = tsSign;
144
+ payload.req_content = 'sec_ts';
145
+ payload.req_sign = hmacSign('sec_ts=' + secTs, ecdhKey(prv, serverCert));
146
+ payload.sec_ts = secTs;
147
+ return encodeURIComponent(Buffer.from(compactJson(payload)).toString('base64'));
148
+ }
149
+ // ================================================================ x-tt-session-dtrait
150
+ /** 登录页 bundle 内置的 d0 公钥(上游 dtrait.py 的 _BUILTIN_TRAIT_PK1_B64)。 */
151
+ const BUILTIN_PK1 = Buffer.from('LS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JSUJDZ0tDQVFFQTQrZHZ2WTd1TStvcGMrbkxHL0R1bVNlRm83YVZjSW0xTE8rbVVJcldwclJ6UDBhMUdwRVEKNHF0TzlN' +
152
+ 'UmYvbHdFSXgzOCs0Qlo0WE9HemV2VnR1VXZmSU9VRTdBVHRRVzdGS0pmNVBuU0xDSTYvazB2bDFGQwpMVVNWbUVQNnFQSnJJalo0elhvcWkzeXVOWisxb2RiUkEvL0dIZ2NnU3l5' +
153
+ 'eWFMcXp3amtwV0dYb3VNWW12WXNTCnBway9mdjJFV0FCc3RQTnhXYTRFT0JDYWRUVVBrWE5RNzZOQkVQOXh6ZkpTMjB3aUR2MW9TL3ZLdnJTVXBXY0oKbmF6a2tCdnFRYmJBcVZi' +
154
+ 'UUZURi9EUGlrcHB1NlpUNmxHSVh2SktDcmVlRmlIQTJxSzZ0UzE4U1dWSFc5QVJ6MQorcGpCMWVxSUlZdG9oV3BUMkI0ME9DNE84dFZlQkFuYmlRSURBUUFCCi0tLS0tRU5EIFJT' +
155
+ 'QSBQVUJMSUMgS0VZLS0tLS0=', 'base64').toString('ascii');
156
+ const PK1_VERSION = 'd0';
157
+ const DTRAIT_SDK_VERSION = '1.0.0.16';
158
+ function rsaPkcs1v15(pem, message) {
159
+ const jwk = createPublicKey(pem).export({ format: 'jwk' });
160
+ const n = toBig(Buffer.from(jwk.n, 'base64url'));
161
+ const e = toBig(Buffer.from(jwk.e, 'base64url'));
162
+ const k = Buffer.from(jwk.n, 'base64url').length;
163
+ const psLen = k - message.length - 3;
164
+ const ps = [];
165
+ while (ps.length < psLen)
166
+ for (const b of rand.bytes(psLen - ps.length))
167
+ if (b !== 0)
168
+ ps.push(b);
169
+ const em = toBig(Buffer.concat([Buffer.from([0, 2]), Buffer.from(ps.slice(0, psLen)), Buffer.from([0]), message]));
170
+ let c = 1n;
171
+ let base = em % n;
172
+ for (let x = e; x > 0n; x >>= 1n) {
173
+ if (x & 1n)
174
+ c = (c * base) % n;
175
+ base = (base * base) % n;
176
+ }
177
+ return Buffer.from(c.toString(16).padStart(k * 2, '0'), 'hex');
178
+ }
179
+ function aes128cbc(key, iv, plaintext) {
180
+ const cipher = createCipheriv('aes-128-cbc', key, iv);
181
+ return Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
182
+ }
183
+ /**
184
+ * 按请求 path 生成 x-tt-session-dtrait。同一会话复用 RSA 加密后的 AES 密钥(第一段),
185
+ * 每次请求重新取 IV 并按 path 加密 payload(第二段)。首次调用时的随机消耗顺序:key → iv → RSA 填充。
186
+ */
187
+ export function sessionDtrait(path, blob, material) {
188
+ let iv;
189
+ if (!material) {
190
+ const keyHex = Buffer.from(rand.bytes(16)).toString('hex');
191
+ iv = rand.bytes(16);
192
+ material = { keyHex, encKey: rsaPkcs1v15(BUILTIN_PK1, Buffer.from(keyHex, 'ascii')) };
193
+ }
194
+ else
195
+ iv = rand.bytes(16);
196
+ const payload = compactJson({ dtrait: blob, timestamp: rand.nowSeconds(), sdkVersion: DTRAIT_SDK_VERSION, path });
197
+ const cipher = aes128cbc(Buffer.from(material.keyHex, 'hex'), iv, payload);
198
+ const header = `${PK1_VERSION}_${material.encKey.toString('base64')}_${Buffer.concat([iv, cipher]).toString('base64')}`;
199
+ return { header, material };
200
+ }
201
+ // ================================================================ ImageX / VOD 网关的 AWS SigV4
202
+ export const IMAGEX_HOST = 'imagex.bytedanceapi.com';
203
+ export const VOD_HOST = 'vod.bytedanceapi.com';
204
+ const REGION = 'cn-north-1';
205
+ const rfc3986 = (s) => encodeURIComponent(s).replace(/[!'()*]/g, (c) => '%' + c.charCodeAt(0).toString(16).toUpperCase());
206
+ function canonicalQuery(query) {
207
+ const enc = query.map(([k, v]) => [rfc3986(String(k)), rfc3986(String(v))]);
208
+ enc.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0));
209
+ return enc.map(([k, v]) => `${k}=${v}`).join('&');
210
+ }
211
+ /** 返回要合入请求的头:authorization、x-amz-date、x-amz-security-token(POST 另有 x-amz-content-sha256)。 */
212
+ export function sigv4(sts, method, query, body = '', service = 'imagex') {
213
+ const d = new Date(rand.now());
214
+ const pad = (n) => String(n).padStart(2, '0');
215
+ const dateStamp = `${d.getUTCFullYear()}${pad(d.getUTCMonth() + 1)}${pad(d.getUTCDate())}`;
216
+ const amzDate = `${dateStamp}T${pad(d.getUTCHours())}${pad(d.getUTCMinutes())}${pad(d.getUTCSeconds())}Z`;
217
+ const payloadHash = sha256Hex(body);
218
+ const signed = { 'x-amz-date': amzDate, 'x-amz-security-token': sts.SessionToken };
219
+ if (method === 'POST')
220
+ signed['x-amz-content-sha256'] = payloadHash;
221
+ const names = Object.keys(signed).sort();
222
+ const canonical = [method, '/', canonicalQuery(query), names.map((k) => `${k}:${signed[k]}\n`).join(''), names.join(';'), payloadHash].join('\n');
223
+ const scope = `${dateStamp}/${REGION}/${service}/aws4_request`;
224
+ const toSign = ['AWS4-HMAC-SHA256', amzDate, scope, sha256Hex(canonical)].join('\n');
225
+ let key = createHmac('sha256', 'AWS4' + sts.SecretAccessKey).update(dateStamp).digest();
226
+ for (const part of [REGION, service, 'aws4_request'])
227
+ key = createHmac('sha256', key).update(part).digest();
228
+ const signature = createHmac('sha256', key).update(toSign).digest('hex');
229
+ const out = {
230
+ authorization: `AWS4-HMAC-SHA256 Credential=${sts.AccessKeyID}/${scope}, SignedHeaders=${names.join(';')}, Signature=${signature}`,
231
+ 'x-amz-date': amzDate,
232
+ 'x-amz-security-token': sts.SessionToken,
233
+ };
234
+ if (method === 'POST')
235
+ out['x-amz-content-sha256'] = payloadHash;
236
+ return out;
237
+ }
238
+ /** content-crc32:小写 8 位十六进制。 */
239
+ export function crc32Hex(data) {
240
+ let c = ~0;
241
+ for (const b of data) {
242
+ c ^= b;
243
+ for (let k = 0; k < 8; k++)
244
+ c = (c >>> 1) ^ (0xedb88320 & -(c & 1));
245
+ }
246
+ return ((~c) >>> 0).toString(16).padStart(8, '0');
247
+ }
@@ -0,0 +1,190 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { SpecificAIError } from '../../../core/errors.js';
3
+ import { staticFile } from '../../../core/paths.js';
4
+ /**
5
+ * x-tt-session-dtrait 里的内层设备特征 blob(上游 utils/dtrait_features.py 的 build_blob、utils/dtrait_profile.py)。
6
+ *
7
+ * 结构逆自 `@byted/uc-secure-dtrait-core` 的字节码 VM:`[1 字节头][bool 位图][字符串特征段]`,整体 base64。
8
+ * - 头字节 = `(reserved << 6) | (dTraitType << 5) | (accessType << 4) | version`
9
+ * - bool 位图:`(floor(maxIdx/32)+1) * 5` 字节,第 n 个 bool 落在 `buf[5*(floor(n/32)+1) - floor((n%32)/8) - 1]` 的第 `n%8` 位
10
+ * - 字符串特征:34 条,每条 5 字节 = `[tag][murmur3 大端 4 字节]`;`str_1..str_33` → tag 32..64,`str_34` → 71
11
+ *
12
+ * canvas / WebGL / audio / 字体像素这类特征必须真实渲染,档案里直接存 murmur3 结果(render_hashes);
13
+ * 其余由档案字段现算。随包的默认档案 static/douyin/dtrait_profile.json 是上游从 Chrome 153 DevTools 取证固化的,原样复制。
14
+ */
15
+ /** 算不出 blob 时的说明:默认档案随包提供,只有导入的档案坏了(或安装不完整)才会走到这里。 */
16
+ export const DTRAIT_BROKEN = '凭证里的 dtrait_profile 无效,或随包的默认设备档案缺失';
17
+ export const DTRAIT_HINT = '用 specific-ai douyin auth login --method cookie --cookie @<凭证 JSON> 重新导入有效的 dtrait_profile / dtrait_blob,或把它们设为 null 改用内置档案';
18
+ /** 档案无效时的错误(上游 _normalise_profile 在加载时就调 build_blob 校验)。 */
19
+ function invalid(message) {
20
+ throw new SpecificAIError('USAGE', `dtrait_profile 无效:${message}`);
21
+ }
22
+ const isObject = (v) => v != null && typeof v === 'object' && !Array.isArray(v);
23
+ /** str_N 的 tag(上游 STR_TAGS)。 */
24
+ const strTag = (n) => (n <= 33 ? 31 + n : 71);
25
+ /**
26
+ * Math 指纹的取值(str_11 / str_12,上游 math_features)。入参全是字面常量(VM 的 fn#313),结果只取决于浏览器的 V8,
27
+ * 所以直接写 Chrome 的值(与上游测试里 Chrome 153 抓到的 blob 一致),不在运行时现算:Node 自带的 V8 与 Chrome 的不同,
28
+ * Node 24 实测 Math.atanh(0.5)、Math.log(3)、Math.expm1(1) 都差 1 ULP(上游同样把 expm1(1) 写成常量 _V8_EXPM1_1)。
29
+ * 数值转字符串就是 JS 的 Number::toString(上游 js_number_to_str)。
30
+ */
31
+ const MATH = {
32
+ tan: -1.4214488238747245, // Math.tan(-1e300)
33
+ atanh: 0.5493061443340549, // Math.atanh(0.5)
34
+ atanhPolyfill: 0.5493061443340549, // SDK 自带的 atanh polyfill:Math.log((1 + 0.5) / (1 - 0.5)) / 2
35
+ cos: -0.8390715290095377, // Math.cos(10.000000000123)
36
+ expm1: 1.7182818284590453, // Math.expm1(1)
37
+ powPi: 1.9275814160560206e-50, // Math.pow(Math.PI, -100)
38
+ sin: 0.8178819121159085, // Math.sin(-1e300)
39
+ };
40
+ /** Math 指纹的特征串(VM 的 fn#313 拼接顺序)。 */
41
+ export function mathFeatures() {
42
+ const m = MATH;
43
+ return {
44
+ 11: `${m.tan},${m.atanh},${m.atanhPolyfill},${m.cos}`,
45
+ 12: `${m.expm1},${m.powPi},${m.sin},${m.tan}`,
46
+ };
47
+ }
48
+ /** MurmurHash3 x86 32 位,按 UTF-8 字节算(上游 murmur3_32,与 SDK 内的实现一致)。 */
49
+ export function murmur3(s, seed = 0) {
50
+ const data = Buffer.from(s, 'utf8');
51
+ const c1 = 0xcc9e2d51;
52
+ const c2 = 0x1b873593;
53
+ let h = seed | 0;
54
+ const n = data.length & ~3;
55
+ for (let i = 0; i < n; i += 4) {
56
+ let k = data.readUInt32LE(i);
57
+ k = Math.imul(k, c1);
58
+ k = (k << 15) | (k >>> 17);
59
+ k = Math.imul(k, c2);
60
+ h ^= k;
61
+ h = (h << 13) | (h >>> 19);
62
+ h = (Math.imul(h, 5) + 0xe6546b64) | 0;
63
+ }
64
+ if (data.length > n) {
65
+ let k = 0;
66
+ for (let i = n; i < data.length; i++)
67
+ k |= data[i] << (8 * (i - n));
68
+ k = Math.imul(k, c1);
69
+ k = (k << 15) | (k >>> 17);
70
+ k = Math.imul(k, c2);
71
+ h ^= k;
72
+ }
73
+ h ^= data.length;
74
+ h ^= h >>> 16;
75
+ h = Math.imul(h, 0x85ebca6b);
76
+ h ^= h >>> 13;
77
+ h = Math.imul(h, 0xc2b2ae35);
78
+ h ^= h >>> 16;
79
+ return h >>> 0;
80
+ }
81
+ /** 直接当特征串用的字段(上游对它们直接取 murmur3,必须是字符串)。 */
82
+ const STRING_FIELDS = ['ua', 'notification_permission', 'hook_score'];
83
+ /** 用逗号拼接的字符串列表。 */
84
+ const LIST_FIELDS = ['languages', 'str16_list', 'str18_list'];
85
+ /** 拼进特征串的标量:字符串或数字。 */
86
+ const SCALAR_FIELDS = [
87
+ 'downlink', 'effective_type', 'language', 'vendor', 'platform', 'str17_tail', 'locale', 'timezone', 'str29_head', 'device_memory',
88
+ 'hardware_concurrency', 'max_touch_points', 'avail_height', 'avail_left', 'avail_top', 'avail_width', 'screen_height', 'screen_width',
89
+ 'color_depth', 'pixel_depth', 'device_pixel_ratio',
90
+ ];
91
+ function checkFields(p) {
92
+ for (const k of STRING_FIELDS)
93
+ if (typeof p[k] !== 'string')
94
+ invalid(`缺少字符串字段 ${k}`);
95
+ for (const k of LIST_FIELDS) {
96
+ const v = p[k];
97
+ if (!Array.isArray(v) || v.some((x) => typeof x !== 'string'))
98
+ invalid(`${k} 要是字符串数组`);
99
+ }
100
+ for (const k of SCALAR_FIELDS) {
101
+ const v = p[k];
102
+ if (typeof v !== 'string' && !(typeof v === 'number' && Number.isFinite(v)))
103
+ invalid(`缺少字段 ${k}`);
104
+ }
105
+ for (const k of ['reserved', 'dtrait_type', 'version'])
106
+ if (p[k] != null && !Number.isInteger(p[k]))
107
+ invalid(`${k} 要是整数`);
108
+ }
109
+ /** 从档案字段现算的特征串,键是 str_N 的 N(上游 computed_features)。 */
110
+ export function computedFeatures(p) {
111
+ return {
112
+ ...mathFeatures(),
113
+ 14: `${p.downlink},${p.effective_type}`,
114
+ 15: `${p.language},${p.languages.join(',')}`,
115
+ 16: `${p.str16_list.join(',')},${p.vendor}`,
116
+ 17: `${p.platform},${p.str17_tail}`,
117
+ 18: p.str18_list.join(','),
118
+ 19: p.ua,
119
+ 27: `${p.locale}+${p.timezone}`,
120
+ 28: p.notification_permission,
121
+ 29: `${p.str29_head},${p.device_memory},${p.hardware_concurrency},${p.max_touch_points}`,
122
+ 30: `${p.avail_height},${p.avail_left},${p.avail_top},${p.avail_width}`,
123
+ 31: `${p.screen_height},${p.screen_width}`,
124
+ 32: `${p.color_depth},${p.pixel_depth},${p.device_pixel_ratio}`,
125
+ 34: p.hook_score,
126
+ };
127
+ }
128
+ /** bools / render_hashes 的键转成序号(上游 _normalise_profile 的 `int(key)`)。 */
129
+ function indexed(p, field) {
130
+ const v = p[field];
131
+ if (!isObject(v))
132
+ invalid(`${field} 要是对象`);
133
+ const out = new Map();
134
+ for (const [k, x] of Object.entries(v)) {
135
+ const n = Number(k);
136
+ if (!k.trim() || !Number.isInteger(n) || n < 0)
137
+ invalid(`${field} 的键 ${k} 不是序号`);
138
+ out.set(n, x);
139
+ }
140
+ return out;
141
+ }
142
+ /** bool 特征的位图(上游 _bool_buffer,即 VM 的 getBoolBuffer)。 */
143
+ function boolBuffer(bools) {
144
+ if (!bools.size)
145
+ return Buffer.alloc(0);
146
+ const keys = [...bools.keys()].sort((a, b) => a - b);
147
+ const buf = Buffer.alloc((Math.floor(keys.at(-1) / 32) + 1) * 5);
148
+ for (const n of keys) {
149
+ if (n % 32 === 0)
150
+ buf[n / 32] = n / 8;
151
+ if (bools.get(n)) {
152
+ const i = 5 * (Math.floor(n / 32) + 1) - Math.floor((n % 32) / 8) - 1;
153
+ buf[i] = buf[i] | (1 << (n % 8));
154
+ }
155
+ }
156
+ return buf;
157
+ }
158
+ /**
159
+ * 由设备档案生成内层 dtrait blob(base64)。档案的结构同 static/douyin/dtrait_profile.json:
160
+ * `render_hashes`(渲染类特征的 murmur3)、`bools`,加上可算特征需要的字段。档案不完整时报 USAGE。
161
+ * accessType:central 与 edge 侧都是 0(实测两者头字节都是 0x20)。
162
+ */
163
+ export function buildBlob(profile, accessType = 0) {
164
+ if (!isObject(profile))
165
+ invalid('要是 JSON 对象');
166
+ const bools = indexed(profile, 'bools');
167
+ for (const [n, v] of bools)
168
+ if (typeof v !== 'boolean')
169
+ invalid(`bools 的 ${n} 要是 true / false`);
170
+ const values = indexed(profile, 'render_hashes');
171
+ checkFields(profile);
172
+ for (const [n, s] of Object.entries(computedFeatures(profile)))
173
+ values.set(Number(n), murmur3(s));
174
+ const body = Buffer.alloc(34 * 5);
175
+ for (let n = 1; n <= 34; n++) {
176
+ const v = values.get(n);
177
+ if (typeof v !== 'number' || !Number.isInteger(v) || v < 0 || v > 0xffffffff)
178
+ invalid(`render_hashes 缺少 ${n},或不是 32 位无符号整数`);
179
+ body[(n - 1) * 5] = strTag(n);
180
+ body.writeUInt32BE(v, (n - 1) * 5 + 1);
181
+ }
182
+ const num = (k, fallback) => (profile[k] ?? fallback);
183
+ const head = ((num('reserved', 0) & 0x3) << 6) | ((num('dtrait_type', 1) & 0x1) << 5) | ((accessType & 0x1) << 4) | (num('version', 0) & 0xf);
184
+ return Buffer.concat([Buffer.from([head]), boolBuffer(bools), body]).toString('base64');
185
+ }
186
+ let defaults;
187
+ /** 随包的默认设备档案(上游 load_dtrait_profile 的默认路径 utils/dtrait_profile.json)。 */
188
+ export function defaultProfile() {
189
+ return (defaults ??= JSON.parse(readFileSync(staticFile('douyin', 'dtrait_profile.json'), 'utf8')));
190
+ }