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,436 @@
1
+ import { createCipheriv, createHash, createHmac } from 'node:crypto';
2
+ import { md5Hex, sha256Hex } from '../../../core/hash.js';
3
+ import { quote } from '../../../core/py.js';
4
+ import * as rand from '../../../core/rand.js';
5
+ import { PROFILE } from './profile.js';
6
+ /**
7
+ * 抖音的纯算签名(上游 utils/sm3.py、ab_pure.py、xbogus_pure.py、secsdk_web_sign.py、strdata_pure.py、dy_util.py)。
8
+ */
9
+ // ================================================================ SM3
10
+ const SM3_IV = [0x7380166f, 0x4914b2b9, 0x172442d7, 0xda8a0600, 0xa96f30bc, 0x163138aa, 0xe38dee4d, 0xb0fb0e4e];
11
+ const rotl = (x, n) => {
12
+ n &= 31;
13
+ return n ? ((x << n) | (x >>> (32 - n))) >>> 0 : x >>> 0;
14
+ };
15
+ const p0 = (x) => (x ^ rotl(x, 9) ^ rotl(x, 17)) >>> 0;
16
+ const p1 = (x) => (x ^ rotl(x, 15) ^ rotl(x, 23)) >>> 0;
17
+ function sm3Block(v, block, offset) {
18
+ const w = new Array(68);
19
+ for (let i = 0; i < 16; i++) {
20
+ const o = offset + i * 4;
21
+ w[i] = ((block[o] << 24) | (block[o + 1] << 16) | (block[o + 2] << 8) | block[o + 3]) >>> 0;
22
+ }
23
+ for (let j = 16; j < 68; j++) {
24
+ w[j] = (p1(w[j - 16] ^ w[j - 9] ^ rotl(w[j - 3], 15)) ^ rotl(w[j - 13], 7) ^ w[j - 6]) >>> 0;
25
+ }
26
+ let [a, b, c, d, e, f, g, h] = v;
27
+ for (let j = 0; j < 64; j++) {
28
+ const tj = j < 16 ? 0x79cc4519 : 0x7a879d8a;
29
+ const ss1 = rotl((rotl(a, 12) + e + rotl(tj, j)) >>> 0, 7);
30
+ const ss2 = (ss1 ^ rotl(a, 12)) >>> 0;
31
+ const ff = j < 16 ? a ^ b ^ c : (a & b) | (a & c) | (b & c);
32
+ const gg = j < 16 ? e ^ f ^ g : (e & f) | (~e & g);
33
+ const tt1 = ((ff >>> 0) + d + ss2 + ((w[j] ^ w[j + 4]) >>> 0)) >>> 0;
34
+ const tt2 = ((gg >>> 0) + h + ss1 + w[j]) >>> 0;
35
+ d = c;
36
+ c = rotl(b, 9);
37
+ b = a;
38
+ a = tt1;
39
+ h = g;
40
+ g = rotl(f, 19);
41
+ f = e;
42
+ e = p0(tt2);
43
+ }
44
+ return [a, b, c, d, e, f, g, h].map((x, i) => (x ^ v[i]) >>> 0);
45
+ }
46
+ export function sm3(message) {
47
+ const msg = typeof message === 'string' ? Buffer.from(message, 'utf8') : message;
48
+ const bitLen = BigInt(msg.length) * 8n;
49
+ const padLen = ((msg.length + 9 + 63) >> 6) << 6;
50
+ const buf = new Uint8Array(padLen);
51
+ buf.set(msg);
52
+ buf[msg.length] = 0x80;
53
+ new DataView(buf.buffer).setBigUint64(padLen - 8, bitLen);
54
+ let v = SM3_IV;
55
+ for (let i = 0; i < padLen; i += 64)
56
+ v = sm3Block(v, buf, i);
57
+ const out = new Uint8Array(32);
58
+ const dv = new DataView(out.buffer);
59
+ v.forEach((x, i) => dv.setUint32(i * 4, x));
60
+ return out;
61
+ }
62
+ // ================================================================ 公用
63
+ function b64Custom(data, alphabet) {
64
+ let out = '';
65
+ for (let i = 0; i < data.length; i += 3) {
66
+ const n = Math.min(3, data.length - i);
67
+ const v = (data[i] << 16) | ((n > 1 ? data[i + 1] : 0) << 8) | (n > 2 ? data[i + 2] : 0);
68
+ out += alphabet[(v >> 18) & 63] + alphabet[(v >> 12) & 63];
69
+ out += n > 1 ? alphabet[(v >> 6) & 63] : '=';
70
+ out += n > 2 ? alphabet[v & 63] : '=';
71
+ }
72
+ return out;
73
+ }
74
+ /** 标准 RC4。 */
75
+ function rc4(key, data) {
76
+ const s = Array.from({ length: 256 }, (_, i) => i);
77
+ let j = 0;
78
+ for (let i = 0; i < 256; i++) {
79
+ j = (j + s[i] + key[i % key.length]) & 255;
80
+ [s[i], s[j]] = [s[j], s[i]];
81
+ }
82
+ const out = [];
83
+ let i = 0;
84
+ j = 0;
85
+ for (let k = 0; k < data.length; k++) {
86
+ i = (i + 1) & 255;
87
+ j = (j + s[i]) & 255;
88
+ [s[i], s[j]] = [s[j], s[i]];
89
+ out.push(data[k] ^ s[(s[i] + s[j]) & 255]);
90
+ }
91
+ return out;
92
+ }
93
+ export { md5Hex, sha256Hex };
94
+ /** 上游 splice_url:值用 `quote(safe='')` 编码,键原样,按插入顺序拼接。 */
95
+ export function spliceUrl(params) {
96
+ const parts = [];
97
+ for (const [k, v] of params)
98
+ parts.push(`${k}=${quote(v == null ? '' : String(v), '')}`);
99
+ return parts.join('&');
100
+ }
101
+ // ================================================================ a_bogus(ab_pure.py)
102
+ const SALT = 'dhzx';
103
+ const SDK_VERSION_CODE = 1;
104
+ const SDK_MINOR_CODE = 12;
105
+ const HOST_ABOGUS = {
106
+ 'www.douyin.com': { sdkMinor: 8, l40: 132, l41: 1, l42: 1 },
107
+ 'login.douyin.com': { sdkMinor: 14, l40: 0, l41: 0, l42: 0 },
108
+ };
109
+ const HOST_APP_IDS = {
110
+ 'www.douyin.com': [6383, 11881],
111
+ 'live.douyin.com': [6383, 7571],
112
+ 'creator.douyin.com': [2906, 33638],
113
+ 'login.douyin.com': [6383, 6241],
114
+ };
115
+ const FORTNIGHT_EPOCH = 1721836800000;
116
+ const COUNTER_INIT = 2;
117
+ const RC4_KEY_BYTE = 211;
118
+ const ALPHABET_S3 = 'ckdp1h4ZKsUB80/Mfvw36XIgR25+WQAlEi7NLboqYTOPuzmFjJnryx9HVGDaStCe';
119
+ const ALPHABET_S4 = 'Dkdpgh2ZmsQB80/MfvV36XI1R45-WUAlEixNLwoqYTOPuzKFjJnry79HbGcaStCe';
120
+ const BROWSER_OFFSET = { Chrome: 0, Firefox: 40, Safari: 81, Edge: 125, Huawei: 170 };
121
+ const BROWSER_TABLE = [
122
+ ['Huawei', [/\bhuawei\b/i]],
123
+ ['Chrome', [/(chrome)\/([\w.]+)(?!.*chromium)/i]],
124
+ ['Edge', [/(edg|edge)\/([\w.]+)/i]],
125
+ ['Firefox', [/\bfocus\/([\w.]+)/i, /fxios\/([-\w.]+)/i, /mobile vr; rv:([\w.]+)\).+firefox/i, /(firefox)\/([\w.]+)/i]],
126
+ ['IE', [/(msie |trident.*rv:)([\w.]+)/i]],
127
+ ['Opera', [/(opera|opr)\/([\w.]+)/i]],
128
+ ['Safari', [/(safari)\/([\w.]+)(?!.*chrome)/i]],
129
+ ];
130
+ const A98_PERM = [
131
+ 34, 44, 56, 61, 73, 29, 70, 45, 35, 49, 38, 66, 51, 68, 28, 48, 64, 47, 30, 71, 26, 55, 31, 69, 59, 40, 62, 63, 27, 72, 41, 74, 57, 52, 42, 39,
132
+ 33, 67, 53, 43, 65, 46, 36, 24, 60, 32, 79, 80, 84, 85,
133
+ ];
134
+ const CHK_FIELDS = [
135
+ 24, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 51, 52, 53, 55, 56, 57, 59, 60, 61, 62, 63, 64, 65, 66,
136
+ 67, 68, 69, 70, 71, 72, 73, 74, 79, 80, 84, 85,
137
+ ];
138
+ /** a_bogus 用的 RC4 变体:S 盒初值倒序,密钥调度 `j = j*S[i] + j + key`。 */
139
+ function rc4Variant(key, data) {
140
+ const s = Array.from({ length: 256 }, (_, i) => 255 - i);
141
+ let j = 0;
142
+ for (let i = 0; i < 256; i++) {
143
+ j = (j * s[i] + j + key[i % key.length]) % 256;
144
+ [s[i], s[j]] = [s[j], s[i]];
145
+ }
146
+ const out = [];
147
+ let i = 0;
148
+ j = 0;
149
+ for (const b of data) {
150
+ i = (i + 1) % 256;
151
+ j = (j + s[i]) % 256;
152
+ [s[i], s[j]] = [s[j], s[i]];
153
+ out.push(b ^ s[(s[i] + s[j]) % 256]);
154
+ }
155
+ return out;
156
+ }
157
+ function strToBytes(s) {
158
+ const out = [];
159
+ for (const ch of s) {
160
+ const c = ch.charCodeAt(0);
161
+ if (c & 0xff00)
162
+ out.push(c >> 8, c & 255);
163
+ else
164
+ out.push(c);
165
+ }
166
+ return out;
167
+ }
168
+ const le = (value, count) => Array.from({ length: count }, (_, i) => Math.floor(value / 2 ** (8 * i)) & 255);
169
+ function blend(c0, c1, r0, r1) {
170
+ return [(r0 & 170) | (c0 & 85), (r0 & 85) | (c0 & 170), (r1 & 170) | (c1 & 85), (r1 & 85) | (c1 & 170)];
171
+ }
172
+ function expand(arr, next) {
173
+ const out = [];
174
+ for (let i = 0; i < arr.length; i += 3) {
175
+ if (i + 2 < arr.length) {
176
+ const r = Math.floor(next() * 1000) & 255;
177
+ out.push((r & 145) | (arr[i] & 110), (r & 66) | (arr[i + 1] & 189), (r & 44) | (arr[i + 2] & 211));
178
+ out.push((arr[i] & 145) | (arr[i + 1] & 66) | (arr[i + 2] & 44));
179
+ }
180
+ else {
181
+ out.push(arr[i]);
182
+ if (i + 1 < arr.length && arr[i + 1])
183
+ out.push(arr[i + 1]);
184
+ }
185
+ }
186
+ return out;
187
+ }
188
+ function escapeDigest(digest, idx, reserved, fallback, force) {
189
+ let v = idx < digest.length ? digest[idx] : fallback;
190
+ while (v === reserved) {
191
+ idx++;
192
+ v = idx < digest.length ? digest[idx] : fallback;
193
+ }
194
+ return force ? reserved : v;
195
+ }
196
+ export function browserName(ua) {
197
+ for (const [name, regs] of BROWSER_TABLE)
198
+ if (regs.some((r) => r.test(ua)))
199
+ return name;
200
+ return 'Other';
201
+ }
202
+ const counterBucket = (c) => (c > 10745 ? 3 : c > 1283 ? 4 : c > 139 ? 5 : 6);
203
+ /**
204
+ * a_bogus 签名器(上游 ABogusPureSigner(fixed=False))。计数器按签名次数递增,
205
+ * 上游是进程级单例;specific-ai 一条命令一个会话,挂在会话上,语义相同。
206
+ */
207
+ export class ABogus {
208
+ counter = COUNTER_INIT;
209
+ ua = PROFILE.ua;
210
+ offsetName = browserName(PROFILE.ua);
211
+ sign(query, body = '', host = 'www.douyin.com') {
212
+ const [aid, pageId] = HOST_APP_IDS[host] ?? HOST_APP_IDS['www.douyin.com'];
213
+ const cfg = HOST_ABOGUS[host];
214
+ const L = {};
215
+ this.counter++;
216
+ L[12] = 3;
217
+ const t = rand.now();
218
+ L[14] = t;
219
+ const v129 = SDK_VERSION_CODE;
220
+ const v14 = cfg?.sdkMinor ?? SDK_MINOR_CODE;
221
+ const h1 = sm3(sm3(query + SALT));
222
+ const h2 = sm3(sm3(body + SALT));
223
+ const uaCipher = rc4Variant([Math.floor(v129 / 256), v129 % 256, v14 % 256], [...this.ua.trim()].map((c) => c.charCodeAt(0)));
224
+ const hUa = sm3(b64Custom(uaCipher, ALPHABET_S3));
225
+ const ink = t - 1;
226
+ L[24] = 41;
227
+ L[25] = [1, 0, 1, 0, 1];
228
+ L[26] = Math.trunc((t - FORTNIGHT_EPOCH) / 1000 / 60 / 60 / 24 / 14);
229
+ L[27] = counterBucket(this.counter);
230
+ L[28] = 3; // (t - t + 3) & 255
231
+ le(t, 6).forEach((b, i) => (L[29 + i] = b));
232
+ [L[35], L[36]] = le(v129, 2);
233
+ const flags = 1 | (Number(browserName(this.ua) === 'Firefox') << 5);
234
+ [L[38], L[39]] = le(flags, 2);
235
+ L[40] = cfg?.l40 ?? 0;
236
+ L[41] = cfg?.l41 ?? 0;
237
+ L[42] = cfg?.l42 ?? 0;
238
+ L[43] = 0;
239
+ le(v14, 4).forEach((b, i) => (L[44 + i] = b));
240
+ [L[48], L[49]] = [h1[9], h1[18]];
241
+ L[51] = escapeDigest(h1, 3, 11, 12, Boolean(flags & 2));
242
+ [L[52], L[53]] = [h2[10], h2[19]];
243
+ L[55] = escapeDigest(h2, 4, 8, 9, Boolean(flags & 4));
244
+ [L[56], L[57]] = [hUa[11], hUa[21]];
245
+ L[59] = escapeDigest(hUa, 5, 12, 13, Boolean(flags & 8));
246
+ le(ink, 6).forEach((b, i) => (L[60 + i] = b));
247
+ L[66] = L[12];
248
+ le(pageId, 4).forEach((b, i) => (L[67 + i] = b));
249
+ le(aid, 4).forEach((b, i) => (L[71 + i] = b));
250
+ L[77] = strToBytes([...PROFILE.geo, 'Win32'].join('|'));
251
+ L[78] = L[77].length;
252
+ [L[79], L[80]] = le(L[78], 2);
253
+ L[81] = `${(t + 3) & 255},`;
254
+ L[82] = strToBytes(L[81]);
255
+ L[83] = L[82].length;
256
+ [L[84], L[85]] = le(L[83], 2);
257
+ const v6 = rand.random() * 65535;
258
+ const a8 = blend(L[25][0], L[25][1], Math.trunc(v6) & 255, (Math.trunc(v6) >> 8) & 255);
259
+ rand.random();
260
+ const r144 = rand.random();
261
+ let check;
262
+ if (flags & 64) {
263
+ const r = Math.floor(r144 * 109);
264
+ check = r + 110 + (r % 2);
265
+ }
266
+ else {
267
+ const r = Math.floor(r144 * 240);
268
+ check = r > 109 ? r + (r % 2) + 1 : r;
269
+ }
270
+ const perm = (Math.floor(rand.random() * 255) & 77) | 2 | 16 | 32 | 128;
271
+ a8.push(...blend(L[25][2], L[25][3], check, perm));
272
+ let chk = 0;
273
+ for (const b of [...a8, ...CHK_FIELDS.map((s) => L[s])])
274
+ chk ^= b;
275
+ L[87] = chk;
276
+ const a98 = [...A98_PERM.map((s) => L[s]), ...L[77], ...L[82], L[87]];
277
+ const hr0 = Math.floor(rand.random() * 65535) & 255;
278
+ const offset = Math.floor(rand.random() * 40) + (BROWSER_OFFSET[this.offsetName] ?? 210);
279
+ L[89] = blend(3, 82, hr0, offset);
280
+ const plain = [...a8, ...expand(a98, rand.random)];
281
+ const cipher = rc4Variant([RC4_KEY_BYTE], plain.map((x) => x & 0xffff));
282
+ return b64Custom([...L[89], ...cipher.map((c) => c & 255)], ALPHABET_S4);
283
+ }
284
+ }
285
+ // ================================================================ X-Bogus(xbogus_pure.py,直播 wss 的 signature)
286
+ const XB_ALPHABET = 'Dkdpgh4ZKsQB80/Mfvw36XI1R25+WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe';
287
+ const XB_ENV_FLAGS = 1 | (1 << 3);
288
+ const XB_V14 = 4 | 8;
289
+ export class XBogus {
290
+ counter = 0;
291
+ sign(stubHex, payload = '') {
292
+ const r1 = rand.random();
293
+ const r2 = rand.random();
294
+ const r3 = rand.random();
295
+ const h1 = createHash('md5').update(createHash('md5').update(payload, 'utf8').digest()).digest();
296
+ const h2 = createHash('md5').update(Buffer.from(stubHex, 'hex')).digest();
297
+ const counter = this.counter + 1;
298
+ this.counter++;
299
+ const plain = [counter & 0x3f, (counter >> 8) & 255, XB_ENV_FLAGS, XB_V14, h1[14], h1[15], h2[14], h2[15], Math.trunc(255 * r2) & 255];
300
+ const chk = plain.reduce((a, b) => a ^ b, 0);
301
+ const keyByte = Math.trunc(255 * r3) & 255;
302
+ const cipher = rc4([keyByte], [...plain, chk]);
303
+ const eef = (1 << 6) | ((Math.trunc(100 * r1) & 1) << 4);
304
+ return b64Custom([eef, keyByte, ...cipher], XB_ALPHABET);
305
+ }
306
+ }
307
+ /** 直播 wss 的 signature(上游 dy_util.generate_signature)。 */
308
+ export function liveSignature(xb, roomId, userUniqueId) {
309
+ const raw = `live_id=1,aid=6383,version_code=180800,webcast_sdk_version=1.0.15,room_id=${roomId},sub_room_id=,sub_channel_id=,did_rule=3,` +
310
+ `user_unique_id=${userUniqueId},device_platform=web,device_type=,ac=,identity=audience`;
311
+ return xb.sign(md5Hex(raw));
312
+ }
313
+ // ================================================================ x-secsdk-web-signature(secsdk_web_sign.py)
314
+ const WEBSIGN_CONST = 'A96D855A08C0A9707F8BEF0D9A527E4E';
315
+ const encodeComponent = (v) => quote(v, "!*'()");
316
+ const unquotePlus = (s) => {
317
+ const bytes = [];
318
+ const src = s.replaceAll('+', ' ');
319
+ for (let i = 0; i < src.length; i++) {
320
+ if (src[i] === '%' && /^[0-9a-fA-F]{2}$/.test(src.slice(i + 1, i + 3))) {
321
+ bytes.push(parseInt(src.slice(i + 1, i + 3), 16));
322
+ i += 2;
323
+ }
324
+ else
325
+ bytes.push(...Buffer.from(src[i], 'utf8'));
326
+ }
327
+ return new TextDecoder('utf-8', { fatal: false }).decode(new Uint8Array(bytes));
328
+ };
329
+ /** 规范化 query:顺序不变,value 先解码再按 encodeURIComponent 重编码,key 只解码。 */
330
+ export function canonicalQuery(query) {
331
+ return query
332
+ .split('&')
333
+ .filter(Boolean)
334
+ .map((pair) => {
335
+ const i = pair.indexOf('=');
336
+ const [k, v] = i < 0 ? [pair, ''] : [pair.slice(0, i), pair.slice(i + 1)];
337
+ return `${unquotePlus(k)}=${encodeComponent(unquotePlus(v))}`;
338
+ })
339
+ .join('&');
340
+ }
341
+ /**
342
+ * 带 timestamp + x-secsdk-web-signature 的完整 URL(上游 Params.signed_url / sign_url)。
343
+ * query 是 `k=v` 原样拼接(上游 Params.toString,不编码)。
344
+ */
345
+ export function signedUrl(base, rawQuery, uifid = '') {
346
+ const kept = rawQuery.split('&').filter((p) => p && !['timestamp', 'x-secsdk-web-signature'].includes(p.split('=', 1)[0]));
347
+ let canon = canonicalQuery(kept.join('&'));
348
+ const names = canon.split('&').filter(Boolean).map((p) => p.split('=', 1)[0]);
349
+ if (!names.includes('uifid') && uifid)
350
+ canon = canon ? `${canon}&uifid=${encodeComponent(uifid)}` : `uifid=${encodeComponent(uifid)}`;
351
+ const ts = rand.nowSeconds();
352
+ const signed = canon ? `${canon}&timestamp=${ts}` : `timestamp=${ts}`;
353
+ let uifidValue = '';
354
+ for (const pair of signed.split('&')) {
355
+ if (pair.startsWith('uifid=')) {
356
+ uifidValue = unquotePlus(pair.slice(6));
357
+ break;
358
+ }
359
+ }
360
+ if (!uifidValue && uifid)
361
+ uifidValue = uifid;
362
+ const signature = md5Hex(`${uifidValue}_${ts}_${WEBSIGN_CONST}_${signed}`);
363
+ return `${base}?${signed}&x-secsdk-web-signature=${signature}`;
364
+ }
365
+ // ================================================================ mssdk strData(strdata_pure.py)
366
+ const STRDATA_ALPHABET = 'Dkdpgh4ZKsQB80/Mfvw36XI1R25+WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe';
367
+ export function encodeStrData(plaintext, nonce) {
368
+ return b64Custom([0x41, nonce, ...rc4([nonce], Buffer.from(plaintext, 'utf8'))], STRDATA_ALPHABET);
369
+ }
370
+ // ================================================================ 小生成器(dy_util.py)
371
+ const MSTOKEN_BASE = 'ABCDEFGHIGKLMNOPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz0123456789=';
372
+ const SV_CHARSET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
373
+ /** 随机 msToken(上游 generate_msToken:randint(0, len-1) 取字符)。 */
374
+ export function randomMsToken(length = 107) {
375
+ let s = '';
376
+ for (let i = 0; i < length; i++)
377
+ s += MSTOKEN_BASE[rand.randint(0, MSTOKEN_BASE.length - 1)];
378
+ return s;
379
+ }
380
+ export function fakeWebid(length = 19) {
381
+ let s = '';
382
+ for (let i = 0; i < length; i++)
383
+ s += String(rand.randint(0, 9));
384
+ return s;
385
+ }
386
+ /** s_v_web_id:`verify_<base36 毫秒>_<8>_<4>_4<3>_[89ab]<3>_<12>`。 */
387
+ export function svWebId() {
388
+ const ts36 = Math.floor(rand.now()).toString(36);
389
+ const r = (k) => rand.string(k, SV_CHARSET);
390
+ const groups = [r(8), r(4), '4' + r(3), rand.choice('89ab') + r(3), r(12)];
391
+ return `verify_${ts36}_${groups.join('_')}`;
392
+ }
393
+ // ================================================================ passport(utils/passport.py、login_api.py)
394
+ export const PASSPORT_APP_KEY = '163e7ce78d58971a41f5b969996d85c2';
395
+ /** 字段加密:UTF-8 字节逐个 XOR 5,非定宽小写十六进制。 */
396
+ export function passportEncrypt(text) {
397
+ return [...Buffer.from(text, 'utf8')].map((b) => (b ^ 5).toString(16)).join('');
398
+ }
399
+ /** passport 的 ts:当天 UTC 12:00 的秒级时间戳。 */
400
+ export function sdkTs(nowMs = rand.now()) {
401
+ const d = new Date(nowMs);
402
+ return String(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), 12) / 1000);
403
+ }
404
+ export function aidSign(path, ts, aid = '6383') {
405
+ const prk = createHmac('sha256', ts).update(PASSPORT_APP_KEY).digest('hex');
406
+ const okm = createHmac('sha256', Buffer.from(prk, 'hex')).update(Buffer.from([1])).digest();
407
+ return createHmac('sha256', okm).update(`aid=${aid}&path=${path}&ts=${ts}`).digest('hex');
408
+ }
409
+ const SIGN_EXCLUDE = new Set(['sign', 'qs', 'msToken', 'a_bogus']);
410
+ /** passport 的 sign / qs:按键名排序后取前 10 个参数,连同 body 做 SHA-256。 */
411
+ export function passportSign(query, data = []) {
412
+ const signable = new Map(query.filter(([k]) => !SIGN_EXCLUDE.has(k)));
413
+ const keys = [...signable.keys()].sort(pyCompare).slice(0, 10);
414
+ const qstr = keys.map((k) => `${k}=${signable.get(k)}`).join('&');
415
+ const body = new Map(data);
416
+ const bstr = [...body.keys()]
417
+ .sort(pyCompare)
418
+ .map((k) => `${k}=${body.get(k)}`)
419
+ .join('&');
420
+ return { sign: sha256Hex(`${qstr}&${bstr}&app_key=${PASSPORT_APP_KEY}`), qs: passportEncrypt(keys.join(',')) };
421
+ }
422
+ /** Python 字符串比较(按码点)。 */
423
+ export function pyCompare(a, b) {
424
+ const x = [...a].map((c) => c.codePointAt(0));
425
+ const y = [...b].map((c) => c.codePointAt(0));
426
+ for (let i = 0; i < Math.min(x.length, y.length); i++)
427
+ if (x[i] !== y[i])
428
+ return x[i] - y[i];
429
+ return x.length - y.length;
430
+ }
431
+ /** challenge 的 AES-256-CBC:key = SHA256(UA),IV = key 后 16 字节,Base64URL(保留 =)。 */
432
+ export function uaAesUrlsafe(plaintext, ua = PROFILE.ua) {
433
+ const key = createHash('sha256').update(ua, 'utf8').digest();
434
+ const cipher = createCipheriv('aes-256-cbc', key, key.subarray(16));
435
+ return Buffer.concat([cipher.update(plaintext), cipher.final()]).toString('base64').replaceAll('+', '-').replaceAll('/', '_');
436
+ }
@@ -0,0 +1,78 @@
1
+ import { SpecificAIError } from '../../../core/errors.js';
2
+ import { jsonLoads, quote } from '../../../core/py.js';
3
+ import { crc32Hex } from './crypto.js';
4
+ import { CREATOR, PROFILE, WWW } from './profile.js';
5
+ /**
6
+ * VOD ApplyUploadInner 之后把字节传到 TOS:小文件直传,大文件 init → 逐片 transfer → finish 合并。
7
+ * 创作者中心(上游 douyin_creator_api 的 upload_video_direct / upload_video_parts)与私信(douyin_im_media 的
8
+ * _upload_source)共用这一份流程;两边上游的差别收在 {@link TosStyle} 里。直传条件「不超过分片大小」
9
+ * 实际就是不超过 3MB(更大的文件分片大小也小于文件本身),与私信的 DIRECT_UPLOAD_LIMIT 相同。
10
+ */
11
+ export const MB = 1024 * 1024;
12
+ /** VOD 网关(ApplyUploadInner / CommitUploadInner)的 Version。 */
13
+ export const VOD_VERSION = '2020-11-19';
14
+ export const CREATOR_TOS = { origin: CREATOR, crcEarly: true, quoteUploadId: false, lenient: false, label: 'TOS' };
15
+ export const IM_TOS = { origin: WWW, crcEarly: false, quoteUploadId: true, lenient: true, label: 'IM TOS' };
16
+ /** 分片大小(上游 _slice_size_for,对齐 SDK getFileSliceLength):≥500MB 用 10MB,≥100MB 用 5MB,否则 3MB。 */
17
+ export function sliceSize(size) {
18
+ return size >= 500 * MB ? 10 * MB : size >= 100 * MB ? 5 * MB : 3 * MB;
19
+ }
20
+ /** TOS 上传头,字段与顺序照抄上游 _tos_headers:服务端下发的 UploadHeader 最后并上(同名的覆盖原位置的值)。 */
21
+ export function tosHeaders(style, node, userId, crc) {
22
+ const h = { authorization: node.auth, referer: `${style.origin}/`, 'user-agent': PROFILE.ua, 'x-storage-u': quote(userId) };
23
+ if (crc != null && style.crcEarly)
24
+ h['content-crc32'] = crc;
25
+ Object.assign(h, {
26
+ 'content-type': 'application/octet-stream',
27
+ accept: '*/*',
28
+ 'accept-language': PROFILE.acceptLanguage,
29
+ origin: style.origin,
30
+ 'sec-fetch-dest': 'empty',
31
+ 'sec-fetch-mode': 'cors',
32
+ 'sec-fetch-site': 'cross-site',
33
+ });
34
+ if (crc != null && !style.crcEarly)
35
+ h['content-crc32'] = crc;
36
+ return Object.entries({ ...h, ...node.upload_header });
37
+ }
38
+ async function tosPost(d, style, url, h, data) {
39
+ const res = await d.plain({ method: 'POST', url, headers: h, body: data, timeout: 300 });
40
+ let body;
41
+ try {
42
+ body = jsonLoads(await res.text());
43
+ }
44
+ catch {
45
+ throw new SpecificAIError('UPSTREAM', `${style.label} 返回的不是 JSON(HTTP ${res.status})`);
46
+ }
47
+ const ok = style.lenient ? [undefined, null, 2000, '2000'].includes(body.code) : body.code === 2000;
48
+ if (!ok)
49
+ throw new SpecificAIError('UPSTREAM', `${style.label} 上传失败:${body.message ?? body.code}`, { detail: { code: body.code ?? null } });
50
+ return body;
51
+ }
52
+ /** 把整份字节传到节点上:不超过分片大小时直传,否则分片(片长至少 5MB)。 */
53
+ export async function tosUpload(d, style, node, data, userId) {
54
+ const size = data.length;
55
+ const slice = sliceSize(size);
56
+ const base = `https://${node.upload_host}/upload/v1/${node.store_uri}`;
57
+ if (size <= slice) {
58
+ await tosPost(d, style, base, tosHeaders(style, node, userId, crc32Hex(data)), data);
59
+ return;
60
+ }
61
+ const part = Math.max(slice, 5 * MB);
62
+ let uploadId = node.upload_id;
63
+ if (!uploadId) {
64
+ uploadId = (await tosPost(d, style, `${base}?uploadmode=part&phase=init`, tosHeaders(style, node, userId))).data?.uploadid;
65
+ if (!uploadId)
66
+ throw new SpecificAIError('UPSTREAM', `${style.label} 分片上传初始化失败`);
67
+ }
68
+ const id = style.quoteUploadId ? quote(uploadId) : uploadId;
69
+ const crcs = [];
70
+ for (let offset = 0, i = 1; offset < size; offset += part, i++) {
71
+ const chunk = data.subarray(offset, offset + part);
72
+ const crc = crc32Hex(chunk);
73
+ await tosPost(d, style, `${base}?uploadid=${id}&part_number=${i}&phase=transfer&part_offset=${offset}`, tosHeaders(style, node, userId, crc), chunk);
74
+ crcs.push(crc);
75
+ d.ctx.log.info(`分片上传 ${i} 片,${Math.min(offset + part, size)}/${size} 字节`);
76
+ }
77
+ await tosPost(d, style, `${base}?uploadmode=part&phase=finish&uploadid=${id}`, tosHeaders(style, node, userId), Buffer.from(crcs.map((c, k) => `${k + 1}:${c}`).join(',')));
78
+ }
@@ -0,0 +1,17 @@
1
+ import bilibili from './bilibili/index.js';
2
+ import douyin from './douyin/index.js';
3
+ import jd from './jd/index.js';
4
+ import kuaishou from './kuaishou/index.js';
5
+ import taobao from './taobao/index.js';
6
+ import tiktok from './tiktok/index.js';
7
+ import weibo from './weibo/index.js';
8
+ import x from './x/index.js';
9
+ import xhs from './xhs/index.js';
10
+ import xianyu from './xianyu/index.js';
11
+ /** 注册表:平台声明只含元数据,handler 按需懒加载。顺序即帮助和文档里的列顺序。 */
12
+ export const PLATFORMS = [xhs, douyin, tiktok, bilibili, kuaishou, weibo, xianyu, taobao, jd, x];
13
+ const byName = new Map(PLATFORMS.flatMap((p) => [p.id, ...p.aliases].map((n) => [n, p])));
14
+ /** 按规范 id 或别名查找平台。 */
15
+ export function findPlatform(name) {
16
+ return byName.get(name.toLowerCase());
17
+ }
@@ -0,0 +1,67 @@
1
+ import { z } from 'zod';
2
+ import { filter } from '../../core/options.js';
3
+ import { checkMsgSend } from '../../core/vocab.js';
4
+ import { definePlatform, handlers } from '../../core/registry.js';
5
+ /** 懒加载 web 端的 handler:只有执行到这条命令时才加载实现。 */
6
+ const { h, impl } = handlers(() => import('./web/commands.js'));
7
+ // 私有选项(AGENTS 4.7 的 jd 行)
8
+ /** 收货地区(上游各接口的 area 参数):影响价格和库存。 */
9
+ const area = { area: z.string().regex(/^\d+([_-]\d+){3}$/, '格式为 省_市_区_镇 的地区编码,例如 1_2800_55812_0').optional().describe('收货地区编码,例如 1_2800_55812_0;默认取 ipLoc-djd cookie') };
10
+ /** 订单时间范围(get_order_list 的 date_range)。 */
11
+ const range = { range: z.string().regex(/^(3m|this_year|\d{4})$/, '取值为 3m、this_year 或四位年份').default('3m').describe('时间范围:3m 近三个月、this_year 今年内、2025 等四位年份') };
12
+ /** 按订单咨询(get_chat_info / send_hello / send_text 的 order_id)。 */
13
+ const order = { order: z.string().optional().describe('订单号或订单详情页 URL:按订单咨询客服') };
14
+ /**
15
+ * msg send:通用规则(AGENTS 4.8:--conversation 不与其他目标并用,--to 与 --item 可以同时用),
16
+ * 再加上 --order:可与 --item 或 --conversation 之一合用,只有 --order 时联系京东自营客服。
17
+ * --to 在 handler 里报 UNSUPPORTED(京东只能联系商家客服)。
18
+ */
19
+ const msgSendCheck = (a, o) => checkMsgSend(a, o, { targets: ['order'] });
20
+ export default definePlatform({
21
+ id: 'jd',
22
+ name: '京东',
23
+ aliases: ['jingdong'],
24
+ item: '商品 SKU',
25
+ endpoints: {
26
+ web: {
27
+ login: { methods: ['qrcode', 'sms', 'cookie'], default: 'qrcode' },
28
+ commands: {
29
+ 'auth login': impl('full', 'authLogin', { note: 'sms 的 --phone 可以带国际前缀,例如 +85291234567' }),
30
+ 'auth status': impl('full', 'authStatus'),
31
+ 'user get': impl('partial', 'userGet', { note: '只支持 me' }),
32
+ 'user collects': impl('full', 'userCollects', { options: area }),
33
+ 'item get': impl('full', 'itemGet', { options: area }),
34
+ 'item search': impl('full', 'itemSearch', { options: { sort: filter.sort('general', 'sales', 'price_asc', 'price_desc', 'comments'), ...area } }),
35
+ 'item related': impl('partial', 'itemRelated', { note: '用第一个相关搜索词的搜索结果', options: area }),
36
+ 'item collect': 'none',
37
+ 'item uncollect': 'none',
38
+ 'comment list': impl('partial', 'commentList', { note: '只有第一页;--limit N 在一次请求里取 N 条' }),
39
+ 'feed list': { upstream: 'none', options: { kind: filter.kind('recommend') } },
40
+ 'keyword suggest': impl('full', 'keywordSuggest'),
41
+ 'keyword hot': impl('full', 'keywordHot'),
42
+ 'msg list': impl('full', 'msgList'),
43
+ 'msg history': impl('full', 'msgHistory'),
44
+ 'msg send': impl('full', 'msgSend', { options: order, check: msgSendCheck }),
45
+ 'msg listen': impl('full', 'msgListen'),
46
+ 'msg read': 'none',
47
+ 'msg revoke': 'none',
48
+ 'msg delete': 'none',
49
+ 'history list': impl('full', 'historyList', { options: area }),
50
+ // 平台扩展(AGENTS 4.7)
51
+ 'order list': { upstream: 'full', summary: '订单列表', args: [], options: range, auth: 'required', paged: true, output: 'Order[]', handler: h('orderList') },
52
+ 'cart count': { upstream: 'full', summary: '购物车商品数量', args: [], options: area, auth: 'required', output: '{count}', handler: h('cartCount') },
53
+ 'coupon list': {
54
+ upstream: 'full',
55
+ summary: '商品可用的优惠券',
56
+ args: [{ name: 'item', summary: '商品 SKU:ID 或 URL' }],
57
+ options: area,
58
+ auth: 'required',
59
+ output: 'Coupon[]',
60
+ handler: h('couponList'),
61
+ },
62
+ },
63
+ },
64
+ app: 'planned',
65
+ pc: 'planned',
66
+ },
67
+ });