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,863 @@
1
+ import { compactJson, jsonDumps, jsonLoads, quote, urlencode } from '../../../core/py.js';
2
+ import * as rand from '../../../core/rand.js';
3
+ import { authError } from '../../../core/toolkit.js';
4
+ import { riskJson } from './client.js';
5
+ import { strDataReport } from './mssdk.js';
6
+ import { APP_VERSION, headers, LIVE, livePlatformParams, Params, platformParams, PROFILE, WWW } from './profile.js';
7
+ import { randomMsToken, signedUrl, spliceUrl } from './sign.js';
8
+ /**
9
+ * 上游 dy_apis/douyin_api.py 等的请求构造,一个函数对应一个上游方法,字段与顺序照抄(对拍测试逐字节比较)。
10
+ * webid / msToken / csrf 等附带请求发生的时机也与上游一致。返回平台原始 JSON。
11
+ */
12
+ const LIVE_HOST = 'live.douyin.com';
13
+ // ---------------------------------------------------------------- 公共片段
14
+ async function withWebId(d, p) {
15
+ p.add('webid', await d.webid());
16
+ }
17
+ function withUifid(d, p) {
18
+ const v = d.cookie('UIFID');
19
+ if (v)
20
+ p.add('uifid', v);
21
+ }
22
+ function headerUifid(d, h) {
23
+ const v = d.cookie('UIFID');
24
+ if (v)
25
+ h.set('uifid', v);
26
+ return h;
27
+ }
28
+ function withVerifyFp(d, p) {
29
+ const fp = d.cookie('s_v_web_id');
30
+ if (fp)
31
+ p.add('verifyFp', fp).add('fp', fp);
32
+ }
33
+ function fp(d, p) {
34
+ const v = d.cookie('s_v_web_id') ?? '';
35
+ p.add('verifyFp', v).add('fp', v);
36
+ }
37
+ async function withMsToken(d, p) {
38
+ p.add('msToken', await d.msToken());
39
+ }
40
+ /** 上游 with_a_bogus(data):签名输入是 splice_url 后的 query 与 body。 */
41
+ function withABogus(d, p, data, host = 'www.douyin.com') {
42
+ p.add('a_bogus', d.ab.sign(spliceUrl(p.pairs()), data ? spliceUrl(data) : '', host));
43
+ }
44
+ /** 上游 with_platform(auth=auth, url=...):公共组 + webid + uifid + verifyFp/fp。 */
45
+ async function withPlatformAuth(d, p, rtt = '0') {
46
+ p.update(platformParams(rtt));
47
+ await withWebId(d, p);
48
+ withUifid(d, p);
49
+ withVerifyFp(d, p);
50
+ }
51
+ async function getJson(d, url, h, p, timeout) {
52
+ const res = await d.request({ url, headers: h.list(), query: p?.pairs(), timeout });
53
+ return riskJson(res);
54
+ }
55
+ async function getSigned(d, base, h, p) {
56
+ return getJson(d, signedUrl(base, p.raw(), d.cookie('UIFID') ?? ''), h);
57
+ }
58
+ /** x-secsdk-csrf-token:HEAD 一次 abtest_config,取响应头 X-Ware-Csrf-Token 的第 2 段(上游 generate_csrf_token)。 */
59
+ export async function csrfToken(d, cookieStr, origin = WWW, path = '/service/2/abtest_config/', referer) {
60
+ try {
61
+ const res = await d.http.request({
62
+ method: 'HEAD',
63
+ url: origin + path,
64
+ headers: [
65
+ ['x-secsdk-csrf-request', '1'],
66
+ ['referer', referer ?? origin + '/'],
67
+ ['user-agent', PROFILE.ua],
68
+ ['x-secsdk-csrf-version', '1.2.22'],
69
+ ['accept', '*/*'],
70
+ ['accept-language', PROFILE.acceptLanguage],
71
+ ],
72
+ cookies: cookieObject(cookieStr),
73
+ });
74
+ const parts = (res.headers.get('x-ware-csrf-token') ?? '').split(',');
75
+ return parts.length > 4 ? parts[1] : null;
76
+ }
77
+ catch {
78
+ return null;
79
+ }
80
+ }
81
+ async function withCsrf(d, h) {
82
+ const token = await csrfToken(d, d.cookieStr);
83
+ if (token != null)
84
+ h.set('x-secsdk-csrf-token', token);
85
+ }
86
+ /** trans_cookies:cookie 串 → 有序对象(空片段跳过)。 */
87
+ export function cookieObject(cookieStr) {
88
+ const out = {};
89
+ for (const item of cookieStr.split(';')) {
90
+ const s = item.trim();
91
+ const i = s.indexOf('=');
92
+ if (i <= 0)
93
+ continue;
94
+ out[s.slice(0, i)] = s.slice(i + 1);
95
+ }
96
+ return out;
97
+ }
98
+ // ================================================================ 游客态:ttwid、msToken、设备号
99
+ /** ttwid 注册(上游 DYLoginApi.register_ttwid)。 */
100
+ export async function registerTtwid(d) {
101
+ const body = jsonDumps({
102
+ region: 'cn',
103
+ aid: 1768,
104
+ needFid: false,
105
+ service: 'www.douyin.com',
106
+ migrate_info: { ticket: '', source: 'node' },
107
+ cbUrlProtocol: 'https',
108
+ union: true,
109
+ });
110
+ const res = await d.plain({
111
+ method: 'POST',
112
+ url: 'https://ttwid.bytedance.com/ttwid/union/register/',
113
+ headers: [
114
+ ['content-type', 'application/json'],
115
+ ['user-agent', PROFILE.ua],
116
+ ],
117
+ body,
118
+ timeout: 20,
119
+ });
120
+ for (const line of res.headers.getSetCookie()) {
121
+ const m = /^ttwid=([^;]*)/.exec(line);
122
+ if (m)
123
+ return m[1];
124
+ }
125
+ return '';
126
+ }
127
+ function mssdkHeaders(storageAccess = false) {
128
+ const h = [
129
+ ['sec-ch-ua-platform', PROFILE.secChUaPlatform],
130
+ ['referer', `${WWW}/`],
131
+ ['user-agent', PROFILE.ua],
132
+ ['sec-ch-ua', PROFILE.secChUa],
133
+ ['content-type', 'text/plain;charset=UTF-8'],
134
+ ['sec-ch-ua-mobile', '?0'],
135
+ ['accept', '*/*'],
136
+ ['accept-language', 'zh-CN,zh;q=0.9'],
137
+ ['cache-control', 'no-cache'],
138
+ ['origin', WWW],
139
+ ['pragma', 'no-cache'],
140
+ ['priority', 'u=1, i'],
141
+ ['sec-fetch-dest', 'empty'],
142
+ ['sec-fetch-mode', 'cors'],
143
+ ['sec-fetch-site', 'cross-site'],
144
+ ];
145
+ if (storageAccess)
146
+ h.push(['sec-fetch-storage-access', 'active']);
147
+ return h;
148
+ }
149
+ function msTokenOf(res) {
150
+ const token = res.headers.get('x-ms-token') ?? '';
151
+ if (token)
152
+ return token;
153
+ return /msToken=([^;]+)/.exec(res.headers.get('set-cookie') ?? '')?.[1] ?? '';
154
+ }
155
+ /** mssdk /web/r/token 换 msToken;续期时把旧 token 挂在 query 上(上游 utils/mstoken.get_mstoken)。 */
156
+ export async function mstoken(d, previous = '') {
157
+ const body = strDataReport();
158
+ let url = 'https://mssdk.bytedance.com/web/r/token?ms_appid=6383';
159
+ if (previous)
160
+ url += '&msToken=' + quote(previous, '');
161
+ const res = await d.plain({ method: 'POST', url, headers: mssdkHeaders(), body, timeout: 25 });
162
+ return msTokenOf(res);
163
+ }
164
+ /** mssdk /web/common 轮换 msToken(上游 refresh_common_mstoken):完整上报或 5 分钟一次的行为心跳。响应的 cookie 并回会话(cookie_sink)。 */
165
+ export async function commonMstoken(d, current, body) {
166
+ let url = 'https://mssdk.bytedance.com/web/common?ms_appid=6383';
167
+ if (current)
168
+ url += '&msToken=' + quote(current, '');
169
+ const res = await d.plain({ method: 'POST', url, headers: mssdkHeaders(true), body, timeout: 25 });
170
+ for (const line of res.headers.getSetCookie()) {
171
+ const pair = line.split(';', 1)[0];
172
+ const i = pair.indexOf('=');
173
+ if (i > 0 && pair.slice(i + 1).trim())
174
+ d.setCookie(pair.slice(0, i).trim(), pair.slice(i + 1).trim());
175
+ }
176
+ return msTokenOf(res);
177
+ }
178
+ /** 设备号(上游 get_device_id):query/user 的 id。 */
179
+ export async function deviceId(d) {
180
+ const refer = `${WWW}/discover`;
181
+ const h = headerUifid(d, headers('GET').set('referer', refer));
182
+ const p = new Params().update(platformParams()).add('publish_video_strategy_type', '2');
183
+ await withWebId(d, p);
184
+ p.add('msToken', randomMsToken());
185
+ fp(d, p);
186
+ withABogus(d, p);
187
+ const body = await getJson(d, `${WWW}/aweme/v1/web/query/user`, h, p);
188
+ return body.id;
189
+ }
190
+ /** 服务端渲染页面里的 user_unique_id(上游 generate_webid);拿不到返回空串。 */
191
+ export async function pageWebid(d, url) {
192
+ try {
193
+ const h = headers('DOC').set('cookie', d.cookieStr).set('upgrade-insecure-requests', '1');
194
+ const text = await (await d.http.request({ url, headers: h.list() })).text();
195
+ return /\\"user_unique_id\\":\\"(.*?)\\"/.exec(text)?.[1] ?? '';
196
+ }
197
+ catch {
198
+ return '';
199
+ }
200
+ }
201
+ /** 自己的数字 uid(上游 get_my_uid)。没有 user_uid 或为 0 表示没登录,报 AUTH_*(游客 AUTH_REQUIRED,账号 AUTH_EXPIRED)。 */
202
+ export async function myUid(d) {
203
+ const refer = `${WWW}/`;
204
+ const h = headerUifid(d, headers('GET').set('referer', refer));
205
+ const p = new Params().add('publish_video_strategy_type', '2').update(platformParams());
206
+ withUifid(d, p);
207
+ await withWebId(d, p);
208
+ fp(d, p);
209
+ withABogus(d, p);
210
+ const body = await getJson(d, `${WWW}/aweme/v1/web/query/user/`, h, p);
211
+ const uid = String(body.user_uid ?? '').trim();
212
+ if (!/^\d+$/.test(uid) || /^0+$/.test(uid))
213
+ throw authError(d.ctx, 'query/user 没有返回 user_uid,cookie 无效或已过期');
214
+ return uid;
215
+ }
216
+ /** 自己的 sec_uid(上游 get_my_sec_uid):创作者中心 user/info,主站 HTML 兜底。 */
217
+ export async function mySecUid(d) {
218
+ try {
219
+ const res = await d.request({
220
+ url: 'https://creator.douyin.com/web/api/media/user/info/',
221
+ headers: [
222
+ ['accept', 'application/json, text/plain, */*'],
223
+ ['accept-language', PROFILE.acceptLanguage],
224
+ ['referer', 'https://creator.douyin.com/creator-micro/home'],
225
+ ['user-agent', PROFILE.ua],
226
+ ],
227
+ timeout: 20,
228
+ });
229
+ const user = (jsonLoads(await res.text()) ?? {}).user ?? {};
230
+ if (user.sec_uid)
231
+ return { secUid: user.sec_uid, user };
232
+ }
233
+ catch { }
234
+ const res = await d.request({ url: `${WWW}/user/self`, headers: headers('GET').list(), query: [['from_tab_name', 'main']] });
235
+ const found = /\\"secUid\\":\\"(.*?)\\"/.exec(await res.text())?.[1];
236
+ if (!found)
237
+ throw new Error('未取到 sec_uid:创作者接口与主站 HTML 都没拿到,请检查登录态');
238
+ return { secUid: found, user: null };
239
+ }
240
+ // ================================================================ 作品、用户、评论、搜索
241
+ export async function userWorks(d, secUid, maxCursor = '0', own = false) {
242
+ const userUrl = `${WWW}/user/${secUid}`;
243
+ const h = headerUifid(d, headers('GET').referer(userUrl));
244
+ const p = new Params();
245
+ p.add('device_platform', 'webapp').add('aid', '6383').add('channel', 'channel_pc_web');
246
+ p.add('sec_user_id', secUid).add('max_cursor', maxCursor).add('locate_query', 'false').add('show_live_replay_strategy', '1');
247
+ p.add('need_time_list', maxCursor === '0' ? '1' : '0').add('time_list_query', '0').add('whale_cut_token', '').add('cut_version', '1');
248
+ p.add('count', '18').add('publish_video_strategy_type', '2').add('from_user_page', own ? '0' : '1');
249
+ p.add('update_version_code', '170400').add('pc_client_type', '1').add('pc_libra_divert', 'Windows').add('support_h265', '1').add('support_dash', '1');
250
+ p.add('cpu_core_num', PROFILE.cpuCoreNum).add('version_code', '290100').add('version_name', '29.1.0').add('cookie_enabled', 'true');
251
+ p.add('screen_width', PROFILE.screenWidth).add('screen_height', PROFILE.screenHeight).add('browser_language', 'zh-CN').add('browser_platform', 'Win32');
252
+ p.add('browser_name', PROFILE.browserName).add('browser_version', PROFILE.browserVersion).add('browser_online', 'true').add('engine_name', 'Blink');
253
+ p.add('engine_version', PROFILE.engineVersion).add('os_name', 'Windows').add('os_version', '10').add('device_memory', PROFILE.deviceMemory);
254
+ p.add('platform', 'PC').add('downlink', '10').add('effective_type', '4g').add('round_trip_time', '0');
255
+ await withWebId(d, p);
256
+ withUifid(d, p);
257
+ await withMsToken(d, p);
258
+ withABogus(d, p);
259
+ withVerifyFp(d, p);
260
+ return getSigned(d, `${WWW}/aweme/v1/web/aweme/post/`, h, p);
261
+ }
262
+ export async function workInfo(d, awemeId) {
263
+ const h = headerUifid(d, headers('GET').referer(`${WWW}/video/${awemeId}`));
264
+ const p = new Params();
265
+ p.add('device_platform', 'webapp').add('aid', '6383').add('channel', 'channel_pc_web').add('aweme_id', awemeId);
266
+ p.add('request_source', '600').add('origin_type', 'video_page');
267
+ p.update(platformParams('50', '190500', '19.5.0'));
268
+ await withWebId(d, p);
269
+ withUifid(d, p);
270
+ withVerifyFp(d, p);
271
+ await withMsToken(d, p);
272
+ withABogus(d, p);
273
+ return getSigned(d, `${WWW}/aweme/v1/web/aweme/detail/`, h, p);
274
+ }
275
+ export async function comments(d, awemeId, cursor = '0') {
276
+ const h = headerUifid(d, headers('GET').referer(`${WWW}/video/${awemeId}`));
277
+ d.withBdReadonly(h);
278
+ const p = new Params();
279
+ p.add('device_platform', 'webapp').add('aid', '6383').add('channel', 'channel_pc_web').add('aweme_id', awemeId).add('cursor', cursor);
280
+ p.add('count', '5').add('item_type', '0').add('whale_cut_token', '').add('cut_version', '1').add('rcFT', '');
281
+ p.update(platformParams('0'));
282
+ await withWebId(d, p);
283
+ withUifid(d, p);
284
+ withVerifyFp(d, p);
285
+ await withMsToken(d, p);
286
+ withABogus(d, p);
287
+ return getJson(d, `${WWW}/aweme/v1/web/comment/list/`, h, p);
288
+ }
289
+ export async function replies(d, awemeId, commentId, cursor = '0', count = '3') {
290
+ const h = headers('GET').referer(`${WWW}/video/${awemeId}`);
291
+ const p = new Params();
292
+ p.add('device_platform', 'webapp').add('aid', '6383').add('channel', 'channel_pc_web').add('item_id', awemeId).add('comment_id', commentId);
293
+ p.add('cut_version', '1').add('cursor', cursor).add('count', count).add('item_type', '0');
294
+ p.update(platformParams('0'));
295
+ await withWebId(d, p);
296
+ await withMsToken(d, p);
297
+ withABogus(d, p);
298
+ fp(d, p);
299
+ return getJson(d, `${WWW}/aweme/v1/web/comment/list/reply/`, h, p);
300
+ }
301
+ export async function userInfo(d, secUid) {
302
+ const h = headerUifid(d, headers('GET').referer(`${WWW}/user/${secUid}`));
303
+ const p = new Params();
304
+ p.add('device_platform', 'webapp').add('aid', '6383').add('channel', 'channel_pc_web').add('publish_video_strategy_type', '2');
305
+ p.add('source', 'channel_pc_web').add('sec_user_id', secUid).add('personal_center_strategy', '1').add('profile_other_record_enable', '1').add('land_to', '1');
306
+ p.update(platformParams('0'));
307
+ await withWebId(d, p);
308
+ withUifid(d, p);
309
+ await withMsToken(d, p);
310
+ withABogus(d, p);
311
+ withVerifyFp(d, p);
312
+ return getJson(d, `${WWW}/aweme/v1/web/user/profile/other/`, h, p);
313
+ }
314
+ /**
315
+ * 综合搜索(上游 search_general_work)。后端不会从 is_filter_search 反推筛选条件,五个筛选值作为 JSON 一起放进
316
+ * filter_selected(紧凑、不转义非 ASCII,键序照上游);没有筛选时也发,值为默认值。
317
+ */
318
+ export async function searchGeneral(d, keyword, offset = '0', searchId = '', f = {}) {
319
+ const sortType = f.sortType ?? '0';
320
+ const publishTime = f.publishTime ?? '0';
321
+ const filterDuration = f.filterDuration ?? '';
322
+ const searchRange = f.searchRange ?? '';
323
+ const contentType = f.contentType ?? '';
324
+ const filtered = sortType !== '0' || publishTime !== '0' || Boolean(filterDuration || searchRange || contentType);
325
+ const filterSelected = compactJson({
326
+ sort_type: sortType,
327
+ publish_time: publishTime,
328
+ filter_duration: filterDuration,
329
+ search_range: searchRange,
330
+ content_type: contentType,
331
+ });
332
+ const refer = `${WWW}/search/${quote(keyword)}?aid=${rand.uuid4()}&type=general`;
333
+ const h = headerUifid(d, headers('GET').referer(refer));
334
+ const p = new Params();
335
+ p.add('device_platform', 'webapp').add('aid', '6383').add('channel', 'channel_pc_web').add('search_channel', 'aweme_general');
336
+ p.add('enable_history', '1').add('filter_selected', filterSelected).add('keyword', keyword).add('search_source', 'tab_search').add('query_correct_type', '1');
337
+ p.add('is_filter_search', filtered ? '1' : '0').add('from_group_id', '').add('disable_rs', '0').add('offset', offset).add('count', '15');
338
+ p.add('need_filter_settings', offset === '0' ? '1' : '0').add('list_type', 'single').add('pc_search_top_1_params', '{"enable_ai_search_top_1":1}');
339
+ p.add('search_id', searchId);
340
+ p.update(platformParams('0', '190600', '19.6.0'));
341
+ await withWebId(d, p);
342
+ withUifid(d, p);
343
+ await withMsToken(d, p);
344
+ withABogus(d, p);
345
+ withVerifyFp(d, p);
346
+ return getJson(d, `${WWW}/aweme/v1/web/general/search/single/`, h, p);
347
+ }
348
+ /**
349
+ * 视频频道搜索(上游 search_video_work):筛选值都进 query。翻页时带上一页响应头的 X-Tt-Logid 作为 search_id。
350
+ * 返回 [下一页的 search_id, 原始 JSON]。
351
+ */
352
+ export async function searchVideo(d, keyword, offset = '0', count = '16', f = {}, searchId = '') {
353
+ const refer = `${WWW}/search/${quote(keyword)}?aid=${rand.uuid4()}&type=video`;
354
+ const h = headers('GET').referer(refer);
355
+ const p = new Params();
356
+ p.add('device_platform', 'webapp').add('aid', '6383').add('channel', 'channel_pc_web').add('search_channel', 'aweme_video_web');
357
+ p.add('enable_history', '1').add('sort_type', f.sortType ?? '0').add('publish_time', f.publishTime ?? '0');
358
+ p.add('filter_duration', f.filterDuration ?? '').add('search_range', f.searchRange ?? '0');
359
+ p.add('keyword', keyword).add('search_source', 'normal_search').add('query_correct_type', '1').add('is_filter_search', '1');
360
+ p.add('from_group_id', '').add('offset', offset).add('count', count).add('need_filter_settings', offset === '0' ? '1' : '0');
361
+ if (searchId)
362
+ p.add('search_id', searchId);
363
+ p.add('list_type', 'single').add('pc_search_top_1_params', '');
364
+ p.update(platformParams('50'));
365
+ await withWebId(d, p);
366
+ await withMsToken(d, p);
367
+ withABogus(d, p);
368
+ fp(d, p);
369
+ const res = await d.request({ url: `${WWW}/aweme/v1/web/search/item/`, headers: h.list(), query: p.pairs() });
370
+ const next = res.headers.get('x-tt-logid') ?? '';
371
+ return [next, await riskJson(res)];
372
+ }
373
+ /** 用户搜索(上游 search_user)。fans:0_1k / 1k_1w / 1w_10w / 10w_100w / 100w_;userType:common_user / enterprise_user / personal_user。 */
374
+ export async function searchUser(d, keyword, offset = '0', count = '25', fans = '', userType = '') {
375
+ const refer = `${WWW}/search/${quote(keyword)}?type=user`;
376
+ const h = headerUifid(d, headers('GET').referer(refer));
377
+ const hasFilter = Boolean(fans || userType);
378
+ const p = new Params();
379
+ p.add('device_platform', 'webapp').add('aid', '6383').add('channel', 'channel_pc_web').add('search_channel', 'aweme_user_web');
380
+ if (hasFilter)
381
+ p.add('search_filter_value', `{"douyin_user_fans":["${fans}"],"douyin_user_type":["${userType}"]}`);
382
+ p.add('keyword', keyword).add('search_source', 'normal_search').add('query_correct_type', '1').add('is_filter_search', hasFilter ? '1' : '0');
383
+ p.add('from_group_id', '').add('disable_rs', '0').add('offset', offset).add('count', count).add('need_filter_settings', offset === '0' ? '1' : '0');
384
+ p.add('list_type', 'single').add('pc_search_top_1_params', '{"enable_ai_search_top_1":1}');
385
+ p.update(platformParams('50').slice(3));
386
+ await withWebId(d, p);
387
+ withUifid(d, p);
388
+ await withMsToken(d, p);
389
+ withABogus(d, p);
390
+ fp(d, p);
391
+ return getJson(d, `${WWW}/aweme/v1/web/discover/search/`, h, p);
392
+ }
393
+ export async function searchLive(d, keyword, offset = '0', count = '15') {
394
+ const refer = `${WWW}/search/${quote(keyword)}?aid=${rand.uuid4()}&type=live`;
395
+ const h = headerUifid(d, headers('GET').referer(refer));
396
+ const p = new Params();
397
+ p.add('device_platform', 'webapp').add('aid', '6383').add('channel', 'channel_pc_web').add('search_channel', 'aweme_live');
398
+ p.add('keyword', keyword).add('search_source', 'normal_search').add('query_correct_type', '1').add('is_filter_search', '0');
399
+ p.add('from_group_id', '').add('disable_rs', '0').add('offset', offset).add('count', count).add('need_filter_settings', offset === '0' ? '1' : '0');
400
+ p.add('list_type', 'single').add('pc_search_top_1_params', '{"enable_ai_search_top_1":1}');
401
+ p.update(platformParams('0'));
402
+ await withWebId(d, p);
403
+ withUifid(d, p);
404
+ await withMsToken(d, p);
405
+ withABogus(d, p);
406
+ withVerifyFp(d, p);
407
+ return getJson(d, `${WWW}/aweme/v1/web/live/search/`, h, p);
408
+ }
409
+ /** 用户喜欢的作品(上游 get_user_favorite,接口是 aweme/favorite)。 */
410
+ export async function userFavorite(d, secUid, maxCursor = '0', count = '18') {
411
+ const refer = `${WWW}/user/${secUid}?showTab=like`;
412
+ const h = headerUifid(d, headers('GET').referer(refer));
413
+ d.withBdReadonly(h);
414
+ const p = new Params();
415
+ p.add('device_platform', 'webapp').add('aid', '6383').add('channel', 'channel_pc_web').add('sec_user_id', secUid).add('max_cursor', maxCursor);
416
+ p.add('min_cursor', '0').add('whale_cut_token', '').add('cut_version', '1').add('count', count).add('publish_video_strategy_type', '2');
417
+ p.update(platformParams('0'));
418
+ await withWebId(d, p);
419
+ withUifid(d, p);
420
+ withVerifyFp(d, p);
421
+ withABogus(d, p);
422
+ return getSigned(d, `${WWW}/aweme/v1/web/aweme/favorite/`, h, p);
423
+ }
424
+ /** 收藏夹列表(上游 get_collect_list 只取 cursor=0 的第一页;翻页时换成上一页响应的 cursor)。 */
425
+ export async function collectList(d, cursor = '0', count = '20') {
426
+ const h = headerUifid(d, headers('GET')).referer(`${WWW}/?recommend=1`);
427
+ const p = new Params();
428
+ p.add('device_platform', 'webapp').add('aid', '6383').add('channel', 'channel_pc_web').add('cursor', cursor).add('count', count);
429
+ p.update(platformParams('0'));
430
+ await withWebId(d, p);
431
+ withUifid(d, p);
432
+ withVerifyFp(d, p);
433
+ await withMsToken(d, p);
434
+ withABogus(d, p);
435
+ return getSigned(d, `${WWW}/aweme/v1/web/collects/list/`, h, p);
436
+ }
437
+ export async function followers(d, userId, secUid, maxTime = '', count = '20') {
438
+ if (!maxTime || maxTime === '0')
439
+ maxTime = String(rand.nowSeconds());
440
+ const h = headerUifid(d, headers('GET')).referer(`${WWW}/user/${secUid}`);
441
+ const p = new Params();
442
+ p.add('device_platform', 'webapp').add('aid', '6383').add('channel', 'channel_pc_web').add('user_id', userId).add('sec_user_id', secUid);
443
+ p.add('offset', '0').add('min_time', '0').add('max_time', maxTime).add('count', count).add('source_type', maxTime === '0' ? '2' : '1');
444
+ p.add('gps_access', '0').add('address_book_access', '0');
445
+ p.update(platformParams('0'));
446
+ await withWebId(d, p);
447
+ withUifid(d, p);
448
+ withVerifyFp(d, p);
449
+ await withMsToken(d, p);
450
+ withABogus(d, p);
451
+ return getJson(d, `${WWW}/aweme/v1/web/user/follower/list/`, h, p);
452
+ }
453
+ export async function following(d, userId, secUid, maxTime = '0', count = '20') {
454
+ const h = headers('GET').referer(`${WWW}/user/${secUid}`);
455
+ d.withBdReadonly(h);
456
+ const p = new Params();
457
+ p.add('device_platform', 'webapp').add('aid', '6383').add('channel', 'channel_pc_web').add('user_id', userId).add('sec_user_id', secUid);
458
+ p.add('offset', '0').add('min_time', '0').add('max_time', maxTime).add('count', count).add('source_type', maxTime === '0' ? '2' : '1');
459
+ p.add('gps_access', '0').add('address_book_access', '0').add('is_top', '1').add('pc_client_type', '1').add('pc_libra_divert', 'Windows');
460
+ p.add('support_h265', '1').add('support_dash', '1').add('webcast_sdk_version', '170400').add('webcast_version_code', '170400');
461
+ p.add('version_code', '170400').add('version_name', '17.4.0').add('cookie_enabled', 'true').add('screen_width', PROFILE.screenWidth);
462
+ p.add('screen_height', PROFILE.screenHeight).add('browser_language', 'zh-CN').add('browser_platform', 'Win32').add('browser_name', PROFILE.browserName);
463
+ p.add('browser_version', PROFILE.browserVersion).add('browser_online', 'true').add('engine_name', 'Blink').add('engine_version', PROFILE.engineVersion);
464
+ p.add('os_name', 'Windows').add('os_version', '10').add('cpu_core_num', PROFILE.cpuCoreNum).add('device_memory', PROFILE.deviceMemory);
465
+ p.add('platform', 'PC').add('downlink', '10').add('effective_type', '4g').add('round_trip_time', '0');
466
+ await withWebId(d, p);
467
+ withVerifyFp(d, p);
468
+ await withMsToken(d, p);
469
+ withABogus(d, p);
470
+ return getSigned(d, `${WWW}/aweme/v1/web/user/following/list/`, h, p);
471
+ }
472
+ export async function notices(d, minTime = '0', maxTime = '0', count = '10', group = '960') {
473
+ const h = headerUifid(d, headers('GET')).referer(`${WWW}/?recommend=1`);
474
+ const p = new Params();
475
+ p.add('device_platform', 'webapp').add('aid', '6383').add('channel', 'channel_pc_web').add('is_new_notice', '1').add('is_mark_read', '1');
476
+ p.add('notice_group', group).add('count', count).add('min_time', minTime).add('max_time', maxTime);
477
+ p.update(platformParams('0'));
478
+ await withWebId(d, p);
479
+ withUifid(d, p);
480
+ await withMsToken(d, p);
481
+ withABogus(d, p);
482
+ fp(d, p);
483
+ const body = await getJson(d, `${WWW}/aweme/v1/web/notice/`, h, p);
484
+ if (!body.notice_list?.length && body.notice_list_v2)
485
+ body.notice_list = body.notice_list_v2;
486
+ return body;
487
+ }
488
+ export async function feed(d, count = '20', refreshIndex = '2') {
489
+ const h = headers('GET').referer(`${WWW}/`);
490
+ const p = new Params();
491
+ p.add('device_platform', 'webapp').add('aid', '6383').add('channel', 'channel_pc_web').add('module_id', '3003101').add('count', count);
492
+ p.add('filterGids', '').add('presented_ids', '').add('refresh_index', refreshIndex).add('refer_id', '').add('refer_type', '10');
493
+ p.add('awemePcRecRawData', '{"is_client":false}').add('Seo-Flag', '0').add('install_time', '1715480185').add('pc_client_type', '1');
494
+ p.add('update_version_code', '170400').add('version_code', '170400').add('version_name', '17.4.0').add('cookie_enabled', 'true');
495
+ p.add('screen_width', PROFILE.screenWidth).add('screen_height', PROFILE.screenHeight).add('browser_language', 'zh-CN').add('browser_platform', 'Win32');
496
+ p.add('browser_name', PROFILE.browserName).add('browser_version', PROFILE.browserVersion).add('browser_online', 'true').add('engine_name', 'Blink');
497
+ p.add('engine_version', PROFILE.engineVersion).add('os_name', 'Windows').add('os_version', '10').add('cpu_core_num', PROFILE.cpuCoreNum);
498
+ p.add('device_memory', PROFILE.deviceMemory).add('platform', 'PC').add('downlink', '10').add('effective_type', '4g').add('round_trip_time', '100');
499
+ await withWebId(d, p);
500
+ await withMsToken(d, p);
501
+ withABogus(d, p);
502
+ fp(d, p);
503
+ return getJson(d, `${WWW}/aweme/v1/web/module/feed/`, h, p);
504
+ }
505
+ // ================================================================ 写操作:点赞、收藏、评论
506
+ export async function digg(d, awemeId, type) {
507
+ const api = '/aweme/v1/web/commit/item/digg/';
508
+ const refer = `${WWW}/discover?modal_id=${awemeId}`;
509
+ const h = headers('FORM');
510
+ d.withBdReadonly(h);
511
+ // 上游 digg 仍是「算得出就带」(成品头也行),没有像收藏 / 收藏夹移动那样改用 with_session_dtrait
512
+ const dt = d.dtraitHeader(api);
513
+ if (dt)
514
+ h.set('x-tt-session-dtrait', dt);
515
+ await withCsrf(d, h);
516
+ headerUifid(d, h).set('origin', WWW).set('referer', refer);
517
+ const p = new Params().update(platformParams('0'));
518
+ await withWebId(d, p);
519
+ withUifid(d, p);
520
+ withVerifyFp(d, p);
521
+ await withMsToken(d, p);
522
+ withABogus(d, p);
523
+ p.add('uid', await d.commentUid());
524
+ const res = await d.request({
525
+ method: 'POST',
526
+ url: WWW + api,
527
+ headers: h.list(),
528
+ query: p.pairs(),
529
+ form: [
530
+ ['aweme_id', awemeId],
531
+ ['item_type', '0'],
532
+ ['type', type],
533
+ ],
534
+ });
535
+ return riskJson(res);
536
+ }
537
+ export async function collect(d, awemeId, action) {
538
+ const api = '/aweme/v1/web/aweme/collect/';
539
+ const h = headers('FORM').referer(`${WWW}/?recommend=1`);
540
+ d.withBdReadonly(h);
541
+ // 收藏是写接口,与收藏夹移动一样要带按 path 重算的 dtrait
542
+ d.withSessionDtrait(h, api);
543
+ await withCsrf(d, h);
544
+ headerUifid(d, h).set('origin', WWW);
545
+ const p = new Params().add('device_platform', 'webapp').add('aid', '6383').add('channel', 'channel_pc_web').add('pc_client_type', '1');
546
+ p.update(platformParams('0'));
547
+ await withWebId(d, p);
548
+ withUifid(d, p);
549
+ withVerifyFp(d, p);
550
+ await withMsToken(d, p);
551
+ const data = [
552
+ ['action', action],
553
+ ['aweme_id', awemeId],
554
+ ['aweme_type', '0'],
555
+ ];
556
+ withABogus(d, p, data);
557
+ p.add('uid', await d.commentUid());
558
+ const res = await d.request({ method: 'POST', url: WWW + api, headers: h.list(), query: p.pairs(), form: data });
559
+ return riskJson(res);
560
+ }
561
+ /** 收藏夹移动的 query(上游 move / remove_collect_aweme 手写的那组,键按字母序)。 */
562
+ async function collectsMoveParams(d, awemeId, collectName, collectId, remove) {
563
+ const p = new Params();
564
+ p.add('aid', '6383').add('browser_language', 'zh-CN').add('browser_name', PROFILE.browserName).add('browser_online', 'true');
565
+ p.add('browser_platform', PROFILE.platform).add('browser_version', PROFILE.browserVersion).add('channel', 'channel_pc_web').add('collects_name', collectName);
566
+ p.add('cookie_enabled', 'true').add('cpu_core_num', PROFILE.cpuCoreNum).add('device_memory', PROFILE.deviceMemory).add('device_platform', 'webapp');
567
+ p.add('downlink', '10').add('effective_type', '4g').add('engine_name', 'Blink').add('engine_version', PROFILE.engineVersion);
568
+ if (remove)
569
+ p.add('from_collects_id', collectId);
570
+ p.add('item_ids', awemeId).add('item_type', '2');
571
+ if (!remove)
572
+ p.add('move_collects_list', collectId);
573
+ p.add('os_name', 'Windows').add('os_version', PROFILE.osVersion).add('pc_client_type', '1').add('platform', 'PC').add('round_trip_time', '50');
574
+ p.add('screen_height', PROFILE.screenHeight).add('screen_width', PROFILE.screenWidth);
575
+ if (!remove)
576
+ p.add('to_collects_id', collectId).add('update_collects_sort', 'true');
577
+ p.add('update_version_code', '170400').add('version_code', '170400').add('version_name', '17.4.0');
578
+ await withWebId(d, p);
579
+ fp(d, p);
580
+ await withMsToken(d, p);
581
+ withABogus(d, p);
582
+ return p;
583
+ }
584
+ /** 把已收藏的作品移进某个收藏夹(上游 move_collect_aweme)。 */
585
+ export async function collectMove(d, awemeId, collectName, collectId) {
586
+ const api = '/aweme/v1/web/collects/video/move/';
587
+ const h = headers('FORM').referer(`${WWW}/?recommend=1`);
588
+ d.withBdReadonly(h);
589
+ d.withSessionDtrait(h, api);
590
+ await withCsrf(d, h);
591
+ headerUifid(d, h).set('origin', WWW);
592
+ const p = await collectsMoveParams(d, awemeId, collectName, collectId, false);
593
+ return riskJson(await d.request({ method: 'POST', url: WWW + api, headers: h.list(), query: p.pairs() }));
594
+ }
595
+ /** 把作品从某个收藏夹移出(上游 remove_collect_aweme,同一个接口)。 */
596
+ export async function collectRemove(d, awemeId, collectName, collectId) {
597
+ const api = '/aweme/v1/web/collects/video/move/';
598
+ const h = headers('FORM').referer(`${WWW}/user/self?showTab=favorite_collection`);
599
+ d.withBdReadonly(h);
600
+ d.withSessionDtrait(h, api);
601
+ await withCsrf(d, h);
602
+ headerUifid(d, h).set('origin', WWW);
603
+ const p = await collectsMoveParams(d, awemeId, collectName, collectId, true);
604
+ return riskJson(await d.request({ method: 'POST', url: WWW + api, headers: h.list(), query: p.pairs() }));
605
+ }
606
+ export async function publishComment(d, awemeId, text, replyId = '', replyToReplyId = '') {
607
+ const api = '/aweme/v1/web/comment/publish';
608
+ const refer = `${WWW}/video/${awemeId}`;
609
+ const h = headers('FORM').set('origin', WWW).referer(refer);
610
+ await d.withBd(h, api, { requireDtrait: true });
611
+ await withCsrf(d, h);
612
+ headerUifid(d, h);
613
+ const p = new Params();
614
+ p.add('app_name', 'aweme').add('enter_from', 'video_detail').add('previous_page', 'video_detail').add('device_platform', 'webapp').add('aid', '6383');
615
+ p.add('channel', 'channel_pc_web').add('pc_client_type', '1').add('pc_libra_divert', 'Windows').add('update_version_code', '170400');
616
+ p.add('support_h265', '1').add('support_dash', '1').add('version_code', '170400').add('version_name', '17.4.0').add('cookie_enabled', 'true');
617
+ p.add('screen_width', PROFILE.screenWidth).add('screen_height', PROFILE.screenHeight).add('browser_language', 'zh-CN').add('browser_platform', 'Win32');
618
+ p.add('browser_name', PROFILE.browserName).add('browser_version', PROFILE.browserVersion).add('browser_online', 'true').add('engine_name', 'Blink');
619
+ p.add('engine_version', PROFILE.engineVersion).add('os_name', 'Windows').add('os_version', '10').add('cpu_core_num', PROFILE.cpuCoreNum);
620
+ p.add('device_memory', PROFILE.deviceMemory).add('platform', 'PC').add('downlink', '10').add('effective_type', '4g').add('round_trip_time', '50');
621
+ await withWebId(d, p);
622
+ withUifid(d, p);
623
+ fp(d, p);
624
+ await withMsToken(d, p);
625
+ const data = [['aweme_id', awemeId]];
626
+ if (replyId)
627
+ data.push(['reply_id', replyId]);
628
+ if (replyToReplyId)
629
+ data.push(['reply_to_reply_id', replyToReplyId]);
630
+ data.push(['comment_send_celltime', '0'], ['comment_video_celltime', '0'], ['one_level_comment_rank', '-1'], ['paste_edit_method', 'non_paste'], ['text', text], ['text_extra', '[]']);
631
+ withABogus(d, p, data);
632
+ const uid = await d.commentUid();
633
+ if (uid)
634
+ p.add('uid', uid);
635
+ const res = await d.request({ method: 'POST', url: WWW + api, headers: h.list(), query: p.pairs(), form: data });
636
+ return riskJson(res);
637
+ }
638
+ // ================================================================ 商品
639
+ function liveEcomHeaders(url) {
640
+ return headers('GET').referer(url).remove('sec-ch-ua', 'sec-ch-ua-mobile', 'sec-ch-ua-platform');
641
+ }
642
+ /** 直播间正在讲解的商品(上游 get_live_production,接口 /live/promotions/pop/v3/)。 */
643
+ export async function liveProduction(d, url, roomId, authorId, sceneId = '1001') {
644
+ const h = headerUifid(d, liveEcomHeaders(url));
645
+ const entrance = JSON.stringify({ room_id: roomId, anchor_id: authorId, carrier_type: 'live_popup_card', ecom_scene_id: sceneId });
646
+ const p = new Params();
647
+ p.add('device_platform', 'webapp').add('aid', '6383').add('channel', 'channel_pc_web').add('room_id', roomId).add('author_id', authorId);
648
+ p.add('live_scene_id', '0').add('entrance_info', quote(entrance, ''));
649
+ p.update(livePlatformParams());
650
+ await withWebId(d, p);
651
+ withUifid(d, p);
652
+ await withMsToken(d, p);
653
+ withABogus(d, p, undefined, LIVE_HOST);
654
+ const res = await d.request({ url: `${LIVE}/live/promotions/pop/v3/`, headers: h.list(), query: p.pairs() });
655
+ const buf = await res.clone().arrayBuffer();
656
+ if (!buf.byteLength)
657
+ return { promotions: [] };
658
+ return riskJson(res);
659
+ }
660
+ /** 商品详情(上游 get_live_production_detail,接口 /aweme/v2/shop/promotion/pack/detail/)。 */
661
+ export async function productDetail(d, url, promotionId, originType = '638303') {
662
+ const h = headers('FORM').set('origin', LIVE).referer(url);
663
+ await withCsrf(d, h);
664
+ headerUifid(d, h).remove('sec-ch-ua', 'sec-ch-ua-mobile', 'sec-ch-ua-platform');
665
+ const p = new Params().add('is_h5', '1').add('origin_type', originType).add('device_platform', 'webapp').add('aid', '6383').add('channel', 'channel_pc_web');
666
+ p.update(livePlatformParams());
667
+ await withWebId(d, p);
668
+ withUifid(d, p);
669
+ await withMsToken(d, p);
670
+ const data = [
671
+ ['is_h5', '1'],
672
+ ['bff_type', '2'],
673
+ ['origin_type', originType],
674
+ ['promotion_id', promotionId],
675
+ ];
676
+ withABogus(d, p, data, LIVE_HOST);
677
+ const res = await d.request({ method: 'POST', url: `${LIVE}/aweme/v2/shop/promotion/pack/detail/`, headers: h.list(), query: p.pairs(), form: data });
678
+ return riskJson(res);
679
+ }
680
+ /** 商品评价(上游 get_product_comments)。 */
681
+ export async function productComments(d, productId, shopId, cursor = '0', count = '10', sortType = '0', tagId = '', statId = '') {
682
+ const h = headerUifid(d, headers('GET').referer(`${WWW}/`));
683
+ const p = new Params().add('product_id', productId).add('shop_id', shopId).add('cursor', cursor).add('count', count);
684
+ p.add('stat_id', statId).add('tag_id', tagId).add('sort_type', sortType);
685
+ await withPlatformAuth(d, p);
686
+ await withMsToken(d, p);
687
+ withABogus(d, p);
688
+ return getJson(d, `${WWW}/aweme/v1/web/ecom/product/comments/`, h, p);
689
+ }
690
+ /** 商品评价的分类计数:好评 / 差评 / 有图等标签(上游 get_product_comment_counter)。 */
691
+ export async function productCommentCounter(d, productId, shopId, statId = '') {
692
+ const h = headerUifid(d, headers('GET').referer(`${WWW}/`));
693
+ const p = new Params().add('product_id', productId).add('shop_id', shopId).add('stat_id', statId);
694
+ await withPlatformAuth(d, p);
695
+ await withMsToken(d, p);
696
+ withABogus(d, p);
697
+ return getJson(d, `${WWW}/aweme/v1/web/ecom/product/comment/counter/`, h, p);
698
+ }
699
+ /** 直播间页面(上游 get_live_info):从服务端渲染的脚本里取 roomId、user_unique_id、主播 id。 */
700
+ export async function liveInfo(d, webRid) {
701
+ const res = await d.request({
702
+ url: `${LIVE}/${webRid}`,
703
+ headers: [
704
+ ['accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7'],
705
+ ['accept-language', 'zh-CN,zh;q=0.9,zh-TW;q=0.8,en;q=0.7,ja;q=0.6'],
706
+ ['cache-control', 'no-cache'],
707
+ ['pragma', 'no-cache'],
708
+ ['priority', 'u=0, i'],
709
+ ['referer', 'https://live.douyin.com/?from_nav=1'],
710
+ ['sec-ch-ua', '"Not)A;Brand";v="8", "Chromium";v="138", "Google Chrome";v="138"'],
711
+ ['sec-ch-ua-mobile', '?0'],
712
+ ['sec-ch-ua-platform', '"Windows"'],
713
+ ['sec-fetch-dest', 'empty'],
714
+ ['sec-fetch-mode', 'navigate'],
715
+ ['sec-fetch-site', 'same-origin'],
716
+ ['upgrade-insecure-requests', '1'],
717
+ ['user-agent', PROFILE.ua],
718
+ ],
719
+ timeout: 30,
720
+ });
721
+ const text = await res.text();
722
+ const ttwid = /(?:^|;\s*)ttwid=([^;]*)/.exec(res.headers.getSetCookie().find((c) => c.startsWith('ttwid=')) ?? '')?.[1] ?? d.cookie('ttwid') ?? '';
723
+ const scripts = [...text.matchAll(/<script\b[^>]*\bnonce\b[^>]*>([\s\S]*?)<\/script>/gi)].map((m) => m[1]);
724
+ for (const s of scripts) {
725
+ if (!s.includes('roomId'))
726
+ continue;
727
+ const userId = /\\"user_unique_id\\":\\"(\d+)\\"/.exec(s)?.[1];
728
+ const roomId = /\\"roomId\\":\\"(\d+)\\"/.exec(s)?.[1];
729
+ const info = /\\"roomInfo\\":\{\\"room\\":\{\\"id_str\\":\\".*?\\",\\"status\\":(.*?),\\"status_str\\":\\".*?\\",\\"title\\":\\"(.*?)\\"/.exec(s);
730
+ const anchorId = /\\"anchor\\":\{\\"id_str\\":\\"(\d+)\\"/.exec(s)?.[1];
731
+ const secUid = /\\"sec_uid\\":\\"(.*?)\\"/.exec(s)?.[1];
732
+ if (userId && roomId && info && anchorId && secUid) {
733
+ return { room_id: roomId, user_id: userId, user_unique_id: userId, anchor_id: anchorId, sec_uid: secUid, ttwid, room_status: info[1], room_title: info[2] };
734
+ }
735
+ }
736
+ if (text.includes('roomId')) {
737
+ const first = (re) => re.exec(text)?.[1] ?? '';
738
+ const roomId = first(/\\"roomId\\"\s*:\s*\\"(\d+)\\"/);
739
+ const userId = first(/\\"user_unique_id\\"\s*:\s*\\"(\d+)\\"/);
740
+ const anchorId = first(/\\"anchor\\"\s*:\s*\\\{[^{}]*?\\"id_str\\"\s*:\s*\\"(\d+)\\"/);
741
+ const secUid = first(/\\"sec_uid\\"\s*:\s*\\"([^"]+)\\"/);
742
+ if (roomId && userId)
743
+ return { room_id: roomId, user_id: userId, user_unique_id: userId, anchor_id: anchorId || userId, sec_uid: secUid, ttwid };
744
+ }
745
+ return null;
746
+ }
747
+ /** 直播 GET 公共参数(上游 _get_live_web)。 */
748
+ async function liveWeb(d, api, endpoint, webRid = '', enterFrom = 'link_share') {
749
+ const h = headers('GET').referer(webRid ? `${LIVE}/${webRid}` : LIVE);
750
+ const p = new Params();
751
+ p.add('aid', '6383').add('app_name', 'douyin_web').add('live_id', '1').add('device_platform', 'web').add('language', 'zh-CN');
752
+ p.add('enter_from', enterFrom).add('cookie_enabled', 'true').add('screen_width', PROFILE.screenWidth).add('screen_height', PROFILE.screenHeight);
753
+ p.add('browser_language', 'zh-CN').add('browser_platform', PROFILE.platform).add('browser_name', PROFILE.browserName);
754
+ p.add('browser_version', PROFILE.browserVersion).add('os_name', 'Windows').add('os_version', '10');
755
+ for (const [k, v] of endpoint)
756
+ if (v != null)
757
+ p.add(k, v);
758
+ await withMsToken(d, p);
759
+ withABogus(d, p, undefined, LIVE_HOST);
760
+ return getJson(d, LIVE + api, h, p, 30);
761
+ }
762
+ /** 房间资料(上游 get_live_room_enter)。 */
763
+ export function liveRoomEnter(d, webRid) {
764
+ return liveWeb(d, '/webcast/room/web/enter/', [['web_rid', webRid]], webRid);
765
+ }
766
+ /** 直播间贡献榜(上游 get_live_contribution_rank)。 */
767
+ export function liveRank(d, roomId, anchorId, secAnchorId, webRid = '') {
768
+ return liveWeb(d, '/webcast/ranklist/audience/', [
769
+ ['webcast_sdk_version', '2450'],
770
+ ['room_id', roomId],
771
+ ['anchor_id', anchorId],
772
+ ['sec_anchor_id', secAnchorId],
773
+ ['ignoreToast', 'true'],
774
+ ['rank_type', '30'],
775
+ ['update_scene', null],
776
+ ], webRid, 'web_live');
777
+ }
778
+ /** 直播间千票榜,页面上的「1000 贡献用户」(上游 get_live_thousand_ticket_rank,接口 ranklist/paygrade_seats)。 */
779
+ export function liveThousandRank(d, roomId, webRid = '', seatsType = '2') {
780
+ return liveWeb(d, '/webcast/ranklist/paygrade_seats/', [
781
+ ['webcast_sdk_version', '2450'],
782
+ ['room_id', roomId],
783
+ ['seats_type', seatsType],
784
+ ], webRid, 'web_live');
785
+ }
786
+ /** 弹幕长连前的 im/fetch(上游 get_webcast_detail),返回 protobuf 字节。 */
787
+ export async function webcastFetch(d, userId, roomId, url) {
788
+ const h = headers('FORM').set('origin', LIVE).referer(url);
789
+ await withCsrf(d, h);
790
+ const p = new Params();
791
+ p.add('resp_content_type', 'protobuf').add('did_rule', '3').add('device_id', '').add('app_name', 'douyin_web').add('endpoint', 'live_pc');
792
+ p.add('support_wrds', '1').add('user_unique_id', userId).add('identity', 'audience').add('need_persist_msg_count', '15').add('insert_task_id', '');
793
+ p.add('live_reason', '').add('room_id', roomId).add('version_code', '180800').add('last_rtt', '0').add('live_id', '1').add('aid', '6383');
794
+ p.add('fetch_rule', '1').add('cursor', '').add('internal_ext', '').add('device_platform', 'web').add('cookie_enabled', 'true');
795
+ p.add('screen_width', PROFILE.screenWidth).add('screen_height', PROFILE.screenHeight).add('browser_language', 'zh-CN').add('browser_platform', PROFILE.platform);
796
+ p.add('browser_name', 'Mozilla').add('browser_version', APP_VERSION).add('browser_online', 'true').add('tz_name', 'Asia/Shanghai');
797
+ await withMsToken(d, p);
798
+ withABogus(d, p, undefined, LIVE_HOST);
799
+ const res = await d.request({ url: `${LIVE}/webcast/im/fetch/`, headers: h.list(), query: p.pairs() });
800
+ return new Uint8Array(await res.arrayBuffer());
801
+ }
802
+ function liveBase(p) {
803
+ p.add('aid', '6383').add('app_name', 'douyin_web').add('live_id', '1').add('device_platform', 'web').add('language', 'zh-CN');
804
+ return p;
805
+ }
806
+ /** 直播间点赞(上游 diggLiveRoom)。 */
807
+ export async function liveLike(d, roomId, count = '1') {
808
+ const h = headers('FORM').set('origin', WWW);
809
+ await withCsrf(d, h);
810
+ h.referer(`${LIVE}/${roomId}`);
811
+ const p = liveBase(new Params());
812
+ p.add('enter_from', 'web_live').add('cookie_enabled', 'true').add('screen_width', PROFILE.screenWidth).add('screen_height', PROFILE.screenHeight);
813
+ p.add('browser_language', 'zh-CN').add('browser_platform', 'Win32').add('browser_name', PROFILE.browserName).add('browser_version', PROFILE.browserVersion);
814
+ p.add('room_id', roomId).add('count', count);
815
+ await withMsToken(d, p);
816
+ withABogus(d, p, []);
817
+ const res = await d.request({ method: 'POST', url: `${LIVE}/webcast/room/like/`, headers: h.list(), query: p.pairs(), form: [] });
818
+ return riskJson(res);
819
+ }
820
+ /** 发直播间评论(上游 sendMsgInRoom,GET /webcast/room/chat/)。 */
821
+ export async function liveChat(d, roomId, content, webRid = roomId) {
822
+ const api = '/webcast/room/chat/';
823
+ const h = headers('GET').set('Origin', LIVE);
824
+ await d.withBd(h, api, { origin: LIVE });
825
+ await withCsrf(d, h);
826
+ h.referer(`${LIVE}/${webRid}`);
827
+ const p = liveBase(new Params());
828
+ p.add('enter_from', 'link_share').add('cookie_enabled', 'true').add('screen_width', PROFILE.screenWidth).add('screen_height', PROFILE.screenHeight);
829
+ p.add('browser_language', 'zh-CN').add('browser_platform', 'Win32').add('browser_name', PROFILE.browserName).add('browser_version', PROFILE.browserVersion);
830
+ p.add('room_id', roomId).add('content', content).add('type', '0');
831
+ await withMsToken(d, p);
832
+ withABogus(d, p, undefined, LIVE_HOST);
833
+ return getJson(d, LIVE + api, h, p);
834
+ }
835
+ /** 服务端 ecies 证书(上游 utils/bd_ticket.fetch_server_cert)。 */
836
+ export async function serverCert(d, aid, cookieStr, origin = WWW, options = {}) {
837
+ const query = [
838
+ ['aid', String(aid)],
839
+ ['is_from_ttaccountsdk', '1'],
840
+ ['msToken', options.msToken || randomMsToken()],
841
+ ];
842
+ query.push(['a_bogus', d.ab.sign(urlencode(query))]);
843
+ const url = `${origin}/passport/ticket_guard/get_client_cert/?${urlencode(query)}`;
844
+ const h = [
845
+ ['x-tt-session-dtrait', options.dtrait ?? ''],
846
+ ['referer', options.referer ?? `${origin}/`],
847
+ ['user-agent', PROFILE.ua],
848
+ ['accept', 'application/json'],
849
+ ];
850
+ const csrf = options.csrf ?? (await csrfToken(d, cookieStr));
851
+ if (csrf)
852
+ h.push(['x-secsdk-csrf-token', csrf]);
853
+ h.push(['content-type', 'application/x-www-form-urlencoded'], ['cookie', cookieStr]);
854
+ h.push(['accept-language', 'zh-CN,zh;q=0.9'], ['origin', origin], ['priority', 'u=1, i'], ['sec-fetch-dest', 'empty'], ['sec-fetch-mode', 'cors'], ['sec-fetch-site', 'same-origin']);
855
+ const res = await d.http.request({ method: 'POST', url, headers: h, body: `server_data=1,aid=${aid}`, timeout: 15 });
856
+ const body = jsonLoads(await res.text());
857
+ if (body.message !== 'success')
858
+ throw new Error(`获取服务端证书失败:${body.message ?? ''}`);
859
+ const cert = body.data?.server_cert;
860
+ if (!cert)
861
+ throw new Error('服务端证书为空');
862
+ return cert;
863
+ }