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,502 @@
1
+ import { SpecificAIError } from '../../../core/errors.js';
2
+ import { compactJson, jsonLoads, parseQsl, urlencode } from '../../../core/py.js';
3
+ import * as rand from '../../../core/rand.js';
4
+ import { riskJson } from './client.js';
5
+ import { ecdsaSign, sigv4, VOD_HOST } from './crypto.js';
6
+ import { imageSize } from './image.js';
7
+ import * as proto from './proto.js';
8
+ import { APP_VERSION, headers, Params, platformParams, PROFILE, WWW } from './profile.js';
9
+ import { md5Hex, spliceUrl } from './sign.js';
10
+ import { IM_TOS, tosUpload, VOD_VERSION } from './tos.js';
11
+ /**
12
+ * PC 私信(上游 builder/proto.py、DouyinAPI.create_conversation / get_identity_security_token / _send_message_raw、
13
+ * dy_apis/douyin_im_media.py、dy_apis/douyin_recv_msg.py)。
14
+ */
15
+ const IMAPI = 'https://imapi.douyin.com';
16
+ const SDK_VERSION = '0.1.8';
17
+ const BUILD_NUMBER = '0d50935:feat/pc-im-groupB';
18
+ const IM_REFERER = `${WWW}/chat?isPopup=1`;
19
+ export const IM_TEXT = 7;
20
+ export const IM_STORY_PICTURE = 27;
21
+ export const IM_STORY_VIDEO = 30;
22
+ export const IM_FILE = 6;
23
+ export const IM_SHARE_AWEME = 8;
24
+ export const IM_SHARE_PHOTOS = 77;
25
+ export const IM_SHARE_WEB = 26;
26
+ export const IM_SHARE_USER = 25;
27
+ /** 上游 ProtoBuilder.build_normal_request(proto3 的零值字段不写)。 */
28
+ function normalRequest(cmd) {
29
+ return {
30
+ cmd,
31
+ sequence_id: rand.randint(10000, 11000),
32
+ sdk_version: SDK_VERSION,
33
+ refer: 3,
34
+ build_number: BUILD_NUMBER,
35
+ device_id: '0',
36
+ device_platform: 'douyin_pc',
37
+ version_code: '360000',
38
+ headers: {
39
+ session_aid: '6383',
40
+ session_did: '0',
41
+ app_name: 'douyin_pc',
42
+ priority_region: 'cn',
43
+ user_agent: PROFILE.ua,
44
+ cookie_enabled: 'true',
45
+ browser_language: 'zh-CN',
46
+ browser_platform: 'Win32',
47
+ browser_name: 'Mozilla',
48
+ browser_version: APP_VERSION,
49
+ browser_online: 'true',
50
+ screen_width: PROFILE.screenWidth,
51
+ screen_height: PROFILE.screenHeight,
52
+ referer: `${WWW}/jingxuan`,
53
+ timezone_name: 'Asia/Shanghai',
54
+ deviceId: '0',
55
+ 'is-retry': '0',
56
+ },
57
+ auth_type: 4,
58
+ biz: 'douyin_web',
59
+ access: 'web_sdk',
60
+ };
61
+ }
62
+ function protoHeaders() {
63
+ return headers('PROTOBUF').set('referer', `${WWW}/`);
64
+ }
65
+ /** 建立(或取回)与某个用户的单聊会话(上游 create_conversation)。 */
66
+ export async function createConversation(d, toUid) {
67
+ const myUid = await d.uid();
68
+ const req = normalRequest(609);
69
+ req.body = { create_conversation_v2_body: { conversation_type: 1, participants: [toUid, myUid] } };
70
+ const prv = d.privateKey;
71
+ if (!prv)
72
+ throw new SpecificAIError('AUTH_REQUIRED', '私信需要 bd-ticket-guard 私钥,请重新扫码登录');
73
+ req.reuqest_sign = ecdsaSign(compactJson({ sign_data: `avatar_url=&idempotent_id=&name=&participants=${toUid},${myUid}`, certType: 'cookie', scene: 'web_protect' }), prv);
74
+ const res = await d.request({ method: 'POST', url: `${IMAPI}/v2/conversation/create`, headers: protoHeaders().list(), body: proto.encode('Request', 'Request', req) });
75
+ const body = proto.decode('Response', 'Response', new Uint8Array(await res.arrayBuffer()));
76
+ const conv = body.body?.create_conversation_v2_body?.conversation_info_list?.[0];
77
+ if (!conv)
78
+ throw new SpecificAIError('UPSTREAM', `创建私信会话失败:${body.error_desc || body.message || ''}`, { detail: { cmd: body.cmd, message: body.message } });
79
+ return { conversationId: conv.conversation_id, shortId: String(conv.conversation_short_id), ticket: conv.ticket };
80
+ }
81
+ /** 私信发送前要取的短时身份安全 token,缓存 240 秒(上游 get_identity_security_token)。 */
82
+ export async function identityToken(d, force = false) {
83
+ const cached = d.identity;
84
+ if (cached?.token && !force && rand.now() - cached.at < 240_000)
85
+ return cached;
86
+ const api = '/passport/safe/get_identity_security_token/';
87
+ const trace = rand.uuid4().replaceAll('-', '').slice(0, 8);
88
+ const p = new Params();
89
+ p.add('passport_jssdk_version', '4.2.3').add('passport_jssdk_type', 'lite').add('is_from_ttaccountsdk', '1').add('aid', '6383').add('language', 'zh');
90
+ p.add('scene', 'web_im').add('auto_retry_req', '0').add('skip_verify', 'false').add('identity_token_force_get_tag', '0').add('biz_trace_id', trace);
91
+ p.add('id_token_version', '1.2.10').add('msToken', await d.msToken());
92
+ p.add('a_bogus', d.ab.sign(spliceUrl(p.pairs())));
93
+ const h = headers('GET').referer(IM_REFERER).set('accept', 'application/json, text/javascript');
94
+ h.set('x-tt-passport-csrf-token', d.cookie('passport_csrf_token') || d.cookie('passport_csrf_token_default') || '');
95
+ h.set('x-tt-passport-trace-id', trace);
96
+ await d.withBd(h, api);
97
+ const body = await riskJson(await d.request({ url: WWW + api, headers: h.list(), query: p.pairs() }));
98
+ const data = body.data ?? {};
99
+ const token = String(data.identity_security_token ?? '');
100
+ if ((body.message != null && body.message !== 'success') || !token) {
101
+ throw new SpecificAIError('RISK_CONTROL', `身份安全 token 获取失败:${data.description ?? body.message ?? ''}`, { detail: { kind: 'blocked', error_code: data.error_code ?? null } });
102
+ }
103
+ d.identity = { token, deviceId: String(data.device_id ?? ''), at: rand.now() };
104
+ return d.identity;
105
+ }
106
+ /** 发一条已经构造好的私信(上游 _send_message_raw),返回 client_message_id。 */
107
+ export async function sendMessage(d, conv, messageType, content) {
108
+ const h = protoHeaders();
109
+ await d.withBd(h, '/v1/message/send');
110
+ const identity = await identityToken(d);
111
+ const clientMessageId = rand.uuid4();
112
+ const req = normalRequest(100);
113
+ req.body = {
114
+ send_message_body: {
115
+ conversation_id: conv.conversationId,
116
+ conversation_type: 1,
117
+ conversation_short_id: conv.shortId,
118
+ content: typeof content === 'string' ? content : compactJson(content),
119
+ ext: [
120
+ { key: 's:mentioned_users', value: '' },
121
+ { key: 's:client_message_id', value: clientMessageId },
122
+ { key: 's:stime', value: `${rand.now()}.${String(Math.floor(rand.random() * 100000)).padStart(5, '0')}` },
123
+ ],
124
+ message_type: messageType,
125
+ ticket: conv.ticket,
126
+ client_message_id: clientMessageId,
127
+ },
128
+ };
129
+ if (identity.token)
130
+ req.headers.identity_security_token = compactJson({ token: identity.token });
131
+ if (identity.deviceId)
132
+ req.headers.identity_security_device_id = identity.deviceId;
133
+ req.headers.identity_security_aid = '';
134
+ const p = new Params().add('msToken', await d.msToken());
135
+ p.add('a_bogus', d.ab.sign(spliceUrl(p.pairs())));
136
+ const fp = d.cookie('s_v_web_id') ?? '';
137
+ p.add('verifyFp', fp).add('fp', fp);
138
+ const res = await d.request({ method: 'POST', url: `${IMAPI}/v1/message/send`, headers: h.list(), query: p.pairs(), body: proto.encode('Request', 'Request', req) });
139
+ const body = proto.decode('Response', 'Response', new Uint8Array(await res.arrayBuffer()));
140
+ if (body.message !== 'OK')
141
+ throw new SpecificAIError('UPSTREAM', `私信发送失败:${body.error_desc || body.message || ''}`, { detail: { message: body.message ?? null, error: body.error_desc ?? null } });
142
+ return clientMessageId;
143
+ }
144
+ /** 文本私信的 content(上游 send_msg)。 */
145
+ export const textContent = (text) => ({ aweType: 700, type: 0, richTextInfos: [], text });
146
+ // ================================================================ 富媒体上传(douyin_im_media.py)
147
+ const UPLOAD_CONFIG_PATH = '/aweme/v1/web/im/upload/config/v2';
148
+ function normalizeSts(cfg) {
149
+ const pick = (...names) => String(names.map((n) => cfg?.[n]).find((v) => v) ?? '');
150
+ const out = {
151
+ AccessKeyID: pick('access_key_id', 'AccessKeyID', 'AccessKeyId'),
152
+ SecretAccessKey: pick('secret_access_key', 'SecretAccessKey'),
153
+ SessionToken: pick('session_token', 'SessionToken'),
154
+ space_name: pick('space_name', 'SpaceName'),
155
+ expire_at: Number(cfg?.expire_at ?? cfg?.ExpiredTime ?? 0) || 0,
156
+ };
157
+ if (!out.AccessKeyID || !out.SecretAccessKey || !out.SessionToken || !out.space_name)
158
+ throw new SpecificAIError('UPSTREAM', 'IM 上传凭证字段不完整');
159
+ return out;
160
+ }
161
+ /** 取四组 IM 上传 STS(上游 get_upload_config)。 */
162
+ export async function uploadConfig(d) {
163
+ const h = headers('GET').referer(IM_REFERER);
164
+ await d.withBd(h, UPLOAD_CONFIG_PATH);
165
+ const p = new Params().update(platformParams());
166
+ p.add('webid', await d.webid());
167
+ const uifid = d.cookie('UIFID');
168
+ if (uifid)
169
+ p.add('uifid', uifid);
170
+ const fp = d.cookie('s_v_web_id');
171
+ if (fp)
172
+ p.add('verifyFp', fp).add('fp', fp);
173
+ p.add('msToken', await d.msToken());
174
+ p.add('a_bogus', d.ab.sign(spliceUrl(p.pairs())));
175
+ const body = await riskJson(await d.request({ url: WWW + UPLOAD_CONFIG_PATH, headers: h.list(), query: p.pairs() }));
176
+ if (body.status_code != null && body.status_code !== 0)
177
+ throw new SpecificAIError('UPSTREAM', `获取 IM 上传配置失败:${body.status_msg ?? body.status_code}`);
178
+ const out = {};
179
+ for (const name of ['public_image_config', 'inner_image_config', 'public_file_config', 'public_image_config_v2'])
180
+ if (body[name])
181
+ out[name] = normalizeSts(body[name]);
182
+ if (!out.public_image_config || !out.inner_image_config || !out.public_file_config)
183
+ throw new SpecificAIError('UPSTREAM', '获取 IM 上传配置字段缺失');
184
+ return out;
185
+ }
186
+ function gatewayHeaders(sign, contentType) {
187
+ const h = {
188
+ accept: '*/*',
189
+ 'accept-language': PROFILE.acceptLanguage,
190
+ origin: WWW,
191
+ referer: `${WWW}/`,
192
+ 'user-agent': PROFILE.ua,
193
+ 'sec-fetch-dest': 'empty',
194
+ 'sec-fetch-mode': 'cors',
195
+ 'sec-fetch-site': 'cross-site',
196
+ ...sign,
197
+ };
198
+ if (contentType)
199
+ h['content-type'] = contentType;
200
+ return Object.entries(h);
201
+ }
202
+ async function applyUpload(d, sts, fileType, size, gcm = false) {
203
+ const query = [
204
+ ['Action', 'ApplyUploadInner'],
205
+ ['Version', VOD_VERSION],
206
+ ['SpaceName', sts.space_name],
207
+ ['FileType', fileType],
208
+ ['IsInner', 1],
209
+ ['NeedFallback', 'true'],
210
+ ['FileSize', size],
211
+ ];
212
+ if (gcm)
213
+ query.push(['OpenGcmEnc', 'true']);
214
+ const res = await d.plain({ url: `https://${VOD_HOST}/`, headers: gatewayHeaders(sigv4(sts, 'GET', query, '', 'vod')), query });
215
+ const body = jsonLoads(await res.text());
216
+ const node = body.Result?.InnerUploadAddress?.UploadNodes?.[0];
217
+ const store = node?.StoreInfos?.[0];
218
+ if (!store)
219
+ throw new SpecificAIError('UPSTREAM', `ApplyUploadInner(${fileType}) 失败`, { detail: { error: body.ResponseMetadata?.Error ?? null } });
220
+ return { store_uri: store.StoreUri ?? '', auth: store.Auth ?? '', upload_id: store.UploadID ?? '', upload_host: node.UploadHost ?? '', session_key: node.SessionKey ?? '', upload_header: node.UploadHeader ?? {} };
221
+ }
222
+ /** 字节传到 TOS(上游 _upload_source):直传上限 3MB(DIRECT_UPLOAD_LIMIT),与创作者中心共用 tos.ts。 */
223
+ const uploadSource = (d, node, data, userId) => tosUpload(d, IM_TOS, node, data, userId);
224
+ async function commitUpload(d, sts, node, functions = []) {
225
+ const body = compactJson({ SessionKey: node.session_key, Functions: functions });
226
+ const query = [
227
+ ['Action', 'CommitUploadInner'],
228
+ ['Version', VOD_VERSION],
229
+ ['SpaceName', sts.space_name],
230
+ ];
231
+ const res = await d.plain({ method: 'POST', url: `https://${VOD_HOST}/`, headers: gatewayHeaders(sigv4(sts, 'POST', query, body, 'vod'), 'text/plain;charset=UTF-8'), query, body });
232
+ const payload = jsonLoads(await res.text());
233
+ const item = payload.Result?.Results?.[0] ?? payload.Result;
234
+ if (!item)
235
+ throw new SpecificAIError('UPSTREAM', 'CommitUploadInner 失败', { detail: { error: payload.ResponseMetadata?.Error ?? null } });
236
+ return item;
237
+ }
238
+ /** 上传一张图并返回 type 27 的 content(上游 upload_image)。 */
239
+ export async function uploadImage(d, file, config) {
240
+ const userId = await d.uid().catch(() => '');
241
+ const gif = file.filename.toLowerCase().endsWith('.gif');
242
+ const cfg = config ?? (await uploadConfig(d));
243
+ const sts = cfg.public_image_config;
244
+ const policy = gif ? { 'policy-set': 'still', 'still-width': '480', 'still-height': '480' } : { 'policy-set': 'check,thumb,medium,large' };
245
+ const action = [{ name: 'Encryption', input: { Config: { copies: 'cipher_v2' } }, PolicyParams: policy }];
246
+ const node = await applyUpload(d, sts, 'image', file.data.length);
247
+ await uploadSource(d, node, file.data, userId);
248
+ const item = await commitUpload(d, sts, node, action);
249
+ const enc = item.Encryption ?? {};
250
+ const extra = enc.Extra ?? {};
251
+ const size = imageSize(file.data);
252
+ const md5 = enc.SourceMd5 ?? item.SourceMd5 ?? '';
253
+ return {
254
+ resource_url: { oid: enc.Uri ?? item.Uri ?? '', skey: enc.SecretKey ?? item.SecretKey ?? '', data_size: Number(extra.img_size ?? file.data.length) || file.data.length, md5 },
255
+ cover_height: Number(extra.img_height ?? size.height) || size.height,
256
+ cover_width: Number(extra.img_width ?? size.width) || size.width,
257
+ check_pics: [],
258
+ md5,
259
+ from_gallery: 1,
260
+ aweType: gif ? 2703 : 2702,
261
+ };
262
+ }
263
+ /** 上传视频(封面走 inner 空间,不加密)并返回 type 30 的 content(上游 upload_video)。 */
264
+ export async function uploadVideo(d, video, cover) {
265
+ const userId = await d.uid().catch(() => '');
266
+ const cfg = await uploadConfig(d);
267
+ const publicSts = cfg.public_image_config;
268
+ const innerSts = { ...publicSts, space_name: cfg.inner_image_config.space_name };
269
+ const coverNode = await applyUpload(d, innerSts, 'image', cover.data.length);
270
+ await uploadSource(d, coverNode, cover.data, userId);
271
+ const coverItem = await commitUpload(d, innerSts, coverNode);
272
+ const coverUri = coverItem.Encryption?.Uri ?? coverItem.Uri ?? coverItem.uri ?? '';
273
+ const action = [{ name: 'Encryption', input: { Config: { copies: 'cipher_v2', aes_chunk_size: '524288' } }, PolicyParams: { 'policy-set': 'medium' } }];
274
+ const node = await applyUpload(d, publicSts, 'video', video.data.length);
275
+ await uploadSource(d, node, video.data, userId);
276
+ const item = await commitUpload(d, publicSts, node, action);
277
+ const enc = item.Encryption ?? {};
278
+ const extra = enc.Extra ?? {};
279
+ const meta = item.VideoMeta ?? item.SourceInfo ?? {};
280
+ return {
281
+ video: { tkey: enc.Uri ?? item.Uri ?? '', md5: enc.SourceMd5 ?? item.SourceMd5 ?? '', skey: enc.SecretKey ?? item.SecretKey ?? '' },
282
+ poster: { oid: extra.thumb_uri || coverUri, md5: extra.thumb_md5 ?? '', skey: extra.thumb_secret ?? '' },
283
+ height: Number(meta.Height ?? 0) || 0,
284
+ width: Number(meta.Width ?? 0) || 0,
285
+ check_pics: coverUri ? [coverUri] : [],
286
+ };
287
+ }
288
+ /** 私信文件附件的上限(上游 MAX_IM_FILE_SIZE)。 */
289
+ export const MAX_IM_FILE = 10 * 1024 * 1024;
290
+ /** 上传一个文件附件并返回 type 6 的 content(上游 upload_file:public_file_config、object 类型、GCM 加密)。 */
291
+ export async function uploadFile(d, file) {
292
+ const userId = await d.uid().catch(() => '');
293
+ if (file.data.length > MAX_IM_FILE)
294
+ throw new SpecificAIError('USAGE', '抖音私信的文件附件不能超过 10MB');
295
+ const cfg = await uploadConfig(d);
296
+ const sts = cfg.public_file_config;
297
+ const node = await applyUpload(d, sts, 'object', file.data.length, true);
298
+ await uploadSource(d, node, file.data, userId);
299
+ const item = await commitUpload(d, sts, node);
300
+ const enc = item.Encryption ?? {};
301
+ const dot = file.filename.lastIndexOf('.');
302
+ return {
303
+ aweType: 15001,
304
+ name: file.filename,
305
+ data_size: file.data.length,
306
+ md5: enc.SourceMd5 || item.SourceMd5 || '',
307
+ skey: enc.SecretKey || item.SecretKey || '',
308
+ uri: enc.Uri || item.Uri || '',
309
+ format: dot > 0 ? file.filename.slice(dot + 1).toLowerCase() : '',
310
+ };
311
+ }
312
+ /** Python 的真值:None、''、0、False、空列表、空字典为假。 */
313
+ function truthy(v) {
314
+ if (v == null || v === '' || v === 0 || v === false)
315
+ return false;
316
+ if (Array.isArray(v))
317
+ return v.length > 0;
318
+ if (typeof v === 'object')
319
+ return Object.keys(v).length > 0;
320
+ return true;
321
+ }
322
+ /** Python 的 `a or b or c`。 */
323
+ function or(...values) {
324
+ for (const v of values)
325
+ if (truthy(v))
326
+ return v;
327
+ return values.at(-1);
328
+ }
329
+ const isDict = (v) => v != null && typeof v === 'object' && !Array.isArray(v);
330
+ const pyInt = (v) => Math.trunc(Number(v)) || 0;
331
+ const pyStr = (v) => (v == null ? '' : String(v));
332
+ /** 图片 / 封面值归一成卡片用的 {uri, url_list, width, height}(上游 _url_object)。 */
333
+ export function urlObject(value, width = 0, height = 0) {
334
+ if (isDict(value)) {
335
+ const obj = { ...value };
336
+ let urls = or(obj.url_list, obj.urlList, obj.urls, []);
337
+ urls = typeof urls === 'string' ? [urls] : [...urls];
338
+ const uri = or(obj.uri, obj.url, urls.length ? urls[0] : '');
339
+ obj.uri = or(uri, '');
340
+ obj.url_list = urls.length ? urls : truthy(uri) ? [uri] : [];
341
+ if (truthy(width) && !truthy(obj.width))
342
+ obj.width = pyInt(width);
343
+ if (truthy(height) && !truthy(obj.height))
344
+ obj.height = pyInt(height);
345
+ return obj;
346
+ }
347
+ if (Array.isArray(value))
348
+ value = value.length ? value[0] : '';
349
+ const s = pyStr(value);
350
+ const obj = { uri: s, url_list: s ? [s] : [] };
351
+ if (truthy(width))
352
+ obj.width = pyInt(width);
353
+ if (truthy(height))
354
+ obj.height = pyInt(height);
355
+ return obj;
356
+ }
357
+ function authorValues(detail) {
358
+ const author = isDict(or(detail.author, detail.user, {})) ? or(detail.author, detail.user, {}) : {};
359
+ return [
360
+ pyStr(or(author.uid, author.user_id, detail.uid, detail.profile_uid, '')),
361
+ pyStr(or(author.sec_uid, author.sec_user_id, detail.secUID, detail.sec_uid, '')),
362
+ pyStr(or(author.nickname, author.name, detail.content_name, '')),
363
+ ];
364
+ }
365
+ /** 卡片封面(上游 _detail_cover):视频取 video.cover,图文取第一张图。 */
366
+ function detailCover(detail, photos) {
367
+ const video = isDict(detail.video) ? detail.video : {};
368
+ let value;
369
+ let width;
370
+ let height;
371
+ if (photos) {
372
+ const images = or(detail.images, detail.image_list, detail.image_infos, []);
373
+ let first = Array.isArray(images) ? (images.length ? images[0] : {}) : (images ?? {});
374
+ if (!isDict(first))
375
+ first = { url_list: first };
376
+ const f = first;
377
+ value = or(f.display_image, f.cover, f);
378
+ width = or(f.width, detail.cover_width, 0);
379
+ height = or(f.height, detail.cover_height, 0);
380
+ }
381
+ else {
382
+ const cover = or(video.cover, video.origin_cover, {});
383
+ value = or(cover, detail.cover_url, detail.cover, '');
384
+ width = or(video.width, isDict(cover) ? cover.width : 0, detail.cover_width, 0);
385
+ height = or(video.height, isDict(cover) ? cover.height : 0, detail.cover_height, 0);
386
+ }
387
+ if (!truthy(width) && isDict(value))
388
+ width = or(value.width, 0);
389
+ if (!truthy(height) && isDict(value))
390
+ height = or(value.height, 0);
391
+ return [urlObject(value, width, height), pyInt(or(width, 0)), pyInt(or(height, 0))];
392
+ }
393
+ const TRACK_KEYS = ['profile_uid', 'profile_sec_uid', 'scene_type', 'send_source', 'publish_way', 'hot_spot_create_time', 'ecom_share_track_params'];
394
+ function shareCommon(detail, uid, photos) {
395
+ const [authorUid, authorSecUid, authorName] = authorValues(detail);
396
+ const u = pyStr(or(uid, authorUid, ''));
397
+ const itemId = pyStr(or(detail.aweme_id, detail.itemId, detail.item_id, ''));
398
+ const [cover, width, height] = detailCover(detail, photos);
399
+ const title = pyStr(or(detail.desc, detail.title, detail.content_title, ''));
400
+ const shareId = u && itemId ? `${u}_${Math.trunc(rand.now())}_${itemId}` : '';
401
+ let aiExt = or(detail.ai_ext, '{}');
402
+ if (typeof aiExt === 'object')
403
+ aiExt = compactJson(aiExt);
404
+ return { uid: u, secUid: pyStr(or(authorSecUid, '')), name: pyStr(or(authorName, '')), itemId, cover, width, height, title, shareId, aiExt };
405
+ }
406
+ function flags(detail) {
407
+ return {
408
+ is_aigc: truthy(detail.is_aigc ?? false),
409
+ is_hot_spot_video: truthy(detail.is_hot_spot_video ?? false),
410
+ is_live_photo: pyInt(or(detail.is_live_photo ?? 0, 0)),
411
+ is_slides: truthy(detail.is_slides ?? false),
412
+ is_story: truthy(detail.is_story ?? false),
413
+ is_text: pyInt(or(detail.is_text ?? 0, 0)),
414
+ };
415
+ }
416
+ function withTrack(payload, detail) {
417
+ for (const key of TRACK_KEYS)
418
+ if (detail[key] != null)
419
+ payload[key] = detail[key];
420
+ return payload;
421
+ }
422
+ /** 视频分享卡片,type 8(上游 build_share_aweme_content,传作品详情;uid 是分享者自己)。 */
423
+ export function shareAwemeContent(detail, uid) {
424
+ const c = shareCommon(detail, uid, false);
425
+ return withTrack({
426
+ aweType: 800,
427
+ awemeType: 0,
428
+ content_name: c.name,
429
+ content_title: c.title,
430
+ content_thumb: { ...c.cover },
431
+ cover_height: c.height,
432
+ cover_url: { ...c.cover },
433
+ cover_width: c.width,
434
+ itemId: c.itemId,
435
+ secUID: c.secUid,
436
+ uid: c.uid,
437
+ share_id: c.shareId,
438
+ share_with_timestamp: 0,
439
+ ...flags(detail),
440
+ create_id: pyStr(or(detail.create_id, '')),
441
+ share_info: or(detail.share_info, []),
442
+ anchor_info: or(detail.anchor_info, {}),
443
+ poi_track_params: or(detail.poi_track_params, {}),
444
+ ai_ext: c.aiExt,
445
+ }, detail);
446
+ }
447
+ /** 图文分享卡片,type 77(上游 build_share_photos_content)。 */
448
+ export function sharePhotosContent(detail, uid) {
449
+ const c = shareCommon(detail, uid, true);
450
+ const images = or(detail.images, detail.image_list, detail.image_infos, []);
451
+ const imageCount = or(detail.image_count, Array.isArray(images) ? images.length : 0, 1);
452
+ return withTrack({
453
+ aweType: 0,
454
+ awemeType: 68,
455
+ content_name: c.name,
456
+ content_title: c.title,
457
+ content_thumb: { ...c.cover },
458
+ cover_height: c.height,
459
+ cover_url: { ...c.cover },
460
+ cover_url_v2: { ...c.cover },
461
+ cover_width: c.width,
462
+ image_count: pyInt(or(imageCount, 1)),
463
+ image_index: 0,
464
+ itemId: c.itemId,
465
+ secUID: c.secUid,
466
+ uid: c.uid,
467
+ share_id: c.shareId,
468
+ share_with_timestamp: 0,
469
+ ...flags(detail),
470
+ share_info: or(detail.share_info, []),
471
+ anchor_info: or(detail.anchor_info, {}),
472
+ poi_track_params: or(detail.poi_track_params, {}),
473
+ ai_ext: c.aiExt,
474
+ }, detail);
475
+ }
476
+ /** 网页卡片,type 26(上游 build_share_web_content):PC 端只渲染带 pc_iframe_src 的链接,没有就把原链接补进去。 */
477
+ export function shareWebContent(url) {
478
+ let target = url;
479
+ const m = /^([^:/?#]+):\/\/([^/?#]*)([^?#]*)(?:\?([^#]*))?(?:#(.*))?$/.exec(target);
480
+ if (m) {
481
+ const query = parseQsl(m[4] ?? '');
482
+ if (!query.some(([k, v]) => k === 'pc_iframe_src' && v)) {
483
+ query.push(['pc_iframe_src', target]);
484
+ const q = urlencode(query);
485
+ target = `${m[1]}://${m[2]}${m[3]}${q ? `?${q}` : ''}${m[5] ? `#${m[5]}` : ''}`;
486
+ }
487
+ }
488
+ return { link_url: target, cover_url: '', title: '', desc: '' };
489
+ }
490
+ /** 用户名片,type 25(上游 build_user_card_content,content 为空、字段显式给出)。 */
491
+ export function userCardContent(u) {
492
+ return { uid: u.uid, secUID: u.secUid, name: u.name, avatar: urlObject(u.avatar), cover_items: [], cover_url: [] };
493
+ }
494
+ // ================================================================ 私信长连(douyin_recv_msg.py)
495
+ const APP_KEY = 'e1bd35ec9db7b8d846de66ed140b1ad9';
496
+ const FP_ID = '9';
497
+ /** frontier-im 的 wss 地址:access_key = md5(fpId + appKey + deviceId + 盐)。 */
498
+ export function frontierUrl(deviceId, sessionid) {
499
+ const accessKey = md5Hex(`${FP_ID}${APP_KEY}${deviceId}f8a69f1719916z`);
500
+ const p = new Params().add('aid', '6383').add('device_platform', 'douyin_pc').add('fpid', FP_ID).add('device_id', deviceId).add('token', sessionid).add('access_key', accessKey);
501
+ return `wss://frontier-im.douyin.com/ws/v2?${p.raw()}`;
502
+ }
@@ -0,0 +1,5 @@
1
+ import { imageSize as headerSize } from '../../../core/image.js';
2
+ /** 从文件头读图片宽高,读不出来时为 0(上游用 Pillow 的 _image_size)。 */
3
+ export function imageSize(data) {
4
+ return headerSize(data) ?? { width: 0, height: 0 };
5
+ }