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,206 @@
1
+ import { SpecificAIError } from '../../../core/errors.js';
2
+ import { parseJson } from '../../../core/http.js';
3
+ import { jsonDumps, jsonLoads } from '../../../core/py.js';
4
+ import * as rand from '../../../core/rand.js';
5
+ import { commonHeaders, detailHeaders, formHeaders, htmlHeaders, MOBILE, postImageHeaders, postVideoHeaders, searchHeaders, uploadImageHeaders, uploadVideoHeaders, videoCheckHeaders, videoOutputHeaders, WEB, } from './profile.js';
6
+ import { APP_SOURCE, sessionId, uploadImageParams, uploadVideoParams } from './sign.js';
7
+ const CONFIG_RE = /try\{window\.\$CONFIG = (.*?);\}catch\(e\)\{window\.\$CONFIG/;
8
+ /**
9
+ * get_self_info:首页 HTML 里的 window.$CONFIG。与上游一样跟随跳转(登录态可能先经过 SSO 跳转);
10
+ * 没登录时最后落在访客页 / 登录页,没有 $CONFIG,返回 null。
11
+ */
12
+ export async function selfInfo(w) {
13
+ const page = await w.html('com', { url: `${WEB}/`, headers: htmlHeaders(), redirect: 'follow' });
14
+ const raw = CONFIG_RE.exec(page.text)?.[1];
15
+ if (!raw)
16
+ return null;
17
+ let config;
18
+ try {
19
+ config = jsonLoads(raw);
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ const uid = config?.user?.id;
25
+ if (uid == null || uid === '')
26
+ return null;
27
+ return { uid: String(uid), nick: String(config.user.watermark?.nick ?? config.user.screen_name ?? '') };
28
+ }
29
+ /** getUserInfo:用户资料。 */
30
+ export function userInfo(w, uid) {
31
+ return w.json('com', { url: `${WEB}/ajax/profile/info`, headers: commonHeaders(w.xsrf), query: [['uid', uid]] });
32
+ }
33
+ /** getUserPosted:用户发布的微博,page 从 1 开始,since_id 取上一页返回的 data.since_id。 */
34
+ export function userPosted(w, uid, page, sinceId = '') {
35
+ const params = [
36
+ ['uid', uid],
37
+ ['page', String(page)],
38
+ ['feature', '0'],
39
+ ];
40
+ if (sinceId)
41
+ params.push(['since_id', sinceId]);
42
+ return w.json('com', { url: `${WEB}/ajax/statuses/mymblog`, headers: commonHeaders(w.xsrf), query: params });
43
+ }
44
+ /**
45
+ * getWordComments:一级评论。上游只取第一页;翻页时带上一页返回的 max_id(位置与网页一致,在 count 之前)。
46
+ */
47
+ export function comments(w, uid, mid, maxId) {
48
+ const params = [
49
+ ['is_reload', '1'],
50
+ ['id', mid],
51
+ ['is_show_bulletin', '2'],
52
+ ['is_mix', '0'],
53
+ ];
54
+ if (maxId)
55
+ params.push(['max_id', maxId]);
56
+ params.push(['count', '10'], ['uid', uid], ['fetch_level', '0'], ['locale', 'zh-CN']);
57
+ return w.json('com', { url: `${WEB}/ajax/statuses/buildComments`, headers: commonHeaders(w.xsrf), query: params });
58
+ }
59
+ // ================================================================ WeiboMobileApis(m.weibo.cn)
60
+ const RENDER_RE = /var \$render_data = \[([\s\S]*?)\]\[0\] \|\| \{\};\n {2}var __wb_performance_data=\{v:"v8",m:"mainsite",pwa:1,sw:0\};/;
61
+ /** getWorkInfo:详情页 HTML 里的 $render_data,返回其中的对象(含 status)。 */
62
+ export async function mobileDetail(w, id) {
63
+ const page = await w.html('cn', { url: `${MOBILE}/detail/${id}`, headers: detailHeaders() });
64
+ const raw = RENDER_RE.exec(page.text)?.[1];
65
+ if (!raw) {
66
+ if (page.status >= 300 && page.status < 400) {
67
+ throw new SpecificAIError('AUTH_REQUIRED', `m.weibo.cn 要求登录后查看这条微博`, { detail: { status: page.status, location: page.location } });
68
+ }
69
+ throw new SpecificAIError('UPSTREAM', `微博 ${id} 不存在、已删除或不可见`, { detail: { status: page.status } });
70
+ }
71
+ try {
72
+ return jsonLoads(raw);
73
+ }
74
+ catch {
75
+ throw new SpecificAIError('UPSTREAM', '无法解析微博详情页', { detail: { status: page.status } });
76
+ }
77
+ }
78
+ /** searchSome:综合搜索(容器 100103type=1)。 */
79
+ export function mobileSearch(w, query, page = 1) {
80
+ return w.json('cn', {
81
+ url: `${MOBILE}/api/container/getIndex`,
82
+ headers: searchHeaders(),
83
+ query: [
84
+ ['containerid', `100103type=1&q=${query}`],
85
+ ['page_type', 'searchall'],
86
+ ['page', String(page)],
87
+ ],
88
+ });
89
+ }
90
+ // ================================================================ WeiboCreaterApis
91
+ /** 上传类接口(picupload / fileplatform)的错误:返回 `{error, error_code}`。 */
92
+ function checkUpload(body, what) {
93
+ const error = body?.error ?? body?.errmsg;
94
+ if (!error)
95
+ return;
96
+ if (error === 'user need identity authentication') {
97
+ throw new SpecificAIError('UPSTREAM', '上传视频需要先在微博完成实名认证', { detail: { error, error_code: body.error_code ?? null } });
98
+ }
99
+ throw new SpecificAIError('UPSTREAM', `${what}失败:${error}`, { detail: { error, error_code: body.error_code ?? null } });
100
+ }
101
+ /** video_init:申请上传,返回 upload_id / media_id / auth。 */
102
+ export async function videoInit(w, file) {
103
+ const t = String(rand.nowSeconds());
104
+ const size = file.length;
105
+ const params = [
106
+ ['source', APP_SOURCE],
107
+ ['size', size],
108
+ ['name', 'video.mp4'],
109
+ ['type', 'video'],
110
+ ['client', 'web'],
111
+ ['session_id', sessionId(size, 'video.mp4')],
112
+ ];
113
+ const data = `--2067456weiboPro${t}\r\nContent-Disposition: form-data; name="biz_file"\r\n\r\n` +
114
+ '{"mediaprops":"{\\"screenshot\\":1}"}' +
115
+ `\r\n--2067456weiboPro${t}--\r\n`;
116
+ const res = await w.request({ method: 'POST', url: 'https://fileplatform.api.weibo.com/2/fileplatform/init.json', headers: formHeaders(t), query: params, body: data });
117
+ const body = await parseJson(res);
118
+ checkUpload(body, '申请上传视频');
119
+ return body;
120
+ }
121
+ /** upload_image_file:上传一张图片,返回 `{pic: {pid, ...}}`。 */
122
+ export async function uploadImage(w, uid, nick, file) {
123
+ const url = `https://picupload.weibo.com/interface/upload.php?${uploadImageParams(uid, nick, file)}`;
124
+ const body = await parseJson(await w.request({ method: 'POST', url, headers: uploadImageHeaders(), body: file }));
125
+ checkUpload(body, '上传图片');
126
+ if (!body?.pic?.pid)
127
+ throw new SpecificAIError('UPSTREAM', '上传图片失败:没有返回 pid', { detail: { body } });
128
+ return body;
129
+ }
130
+ /** upload_video_file:整个文件作为一个分片上传。cookie 与上游一样发 weibo.com 的整串。 */
131
+ export async function uploadVideo(w, uploadId, mediaId, file, auth) {
132
+ const url = `https://up.video.weibocdn.com/2/fileplatform/upload.json?${uploadVideoParams(uploadId, mediaId, file)}`;
133
+ const body = await parseJson(await w.request({ method: 'POST', url, headers: uploadVideoHeaders(auth), body: file, cookies: w.webCookies() }));
134
+ checkUpload(body, '上传视频');
135
+ return body;
136
+ }
137
+ /** video_check:通知上传完成。 */
138
+ export async function videoCheck(w, uploadId, mediaId, fileSize, auth) {
139
+ const form = [
140
+ ['source', APP_SOURCE],
141
+ ['upload_id', uploadId],
142
+ ['media_id', mediaId],
143
+ ['upload_protocol', 'binary'],
144
+ ['count', '1'],
145
+ ['action', 'finish'],
146
+ ['size', String(fileSize)],
147
+ ['client', 'web'],
148
+ ['status', ''],
149
+ ];
150
+ const res = await w.request({ method: 'POST', url: 'https://fileplatform.api.weibo.com/2/fileplatform/check.json', headers: videoCheckHeaders(auth), form });
151
+ const body = await parseJson(res);
152
+ checkUpload(body, '确认上传视频');
153
+ return body;
154
+ }
155
+ /** video_output:转码结果;data 为空表示还没转完。 */
156
+ export async function videoOutput(w, mediaId) {
157
+ return w.json('com', {
158
+ url: `${WEB}/ajax/multimedia/output`,
159
+ headers: videoOutputHeaders(),
160
+ query: [
161
+ ['source', APP_SOURCE],
162
+ ['ids', mediaId],
163
+ ['labels', 'screenshot'],
164
+ ],
165
+ });
166
+ }
167
+ /** post_weibo 里图文的 statuses/update:pic_id 是 `[{"type": "image/jpeg", "pid": ...}]`。 */
168
+ export function postImage(w, content, visible, pids) {
169
+ const picId = jsonDumps(pids.map((pid) => ({ type: 'image/jpeg', pid })));
170
+ return w.json('com', {
171
+ method: 'POST',
172
+ url: `${WEB}/ajax/statuses/update`,
173
+ headers: postImageHeaders(w.xsrf),
174
+ form: [
175
+ ['content', content],
176
+ ['visible', visible],
177
+ ['vote', ''],
178
+ ['media', ''],
179
+ ['pic_id', picId],
180
+ ],
181
+ });
182
+ }
183
+ /** post_weibo 里视频的 statuses/update。 */
184
+ export function postVideo(w, content, visible, mediaId) {
185
+ const media = {
186
+ titles: [{ title: '', default: 'true' }],
187
+ covers: [{ url: '' }],
188
+ free_duration: { start: 0, end: 30 },
189
+ type: 'video',
190
+ media_id: mediaId,
191
+ resource: { video_down: 1 },
192
+ homemade: { channel_ids: [''], type: 0 },
193
+ approval_reprint: '1',
194
+ };
195
+ return w.json('com', {
196
+ method: 'POST',
197
+ url: `${WEB}/ajax/statuses/update`,
198
+ headers: postVideoHeaders(),
199
+ form: [
200
+ ['content', content],
201
+ ['visible', visible],
202
+ ['vote', ''],
203
+ ['media', jsonDumps(media)],
204
+ ],
205
+ });
206
+ }
@@ -0,0 +1,155 @@
1
+ import { SpecificAIError } from '../../../core/errors.js';
2
+ import { jsonLoads, quote } from '../../../core/py.js';
3
+ import * as rand from '../../../core/rand.js';
4
+ import { authError, httpClient } from '../../../core/toolkit.js';
5
+ import { MOBILE, visitorHeaders, WEB } from './profile.js';
6
+ const SITE_URL = { com: `${WEB}/`, cn: `${MOBILE}/` };
7
+ /** m.weibo.cn 的访客页。 */
8
+ const VISITOR = { host: 'visitor.passport.weibo.cn', entry: 'sinawap', domain: '.weibo.cn' };
9
+ export class Weibo {
10
+ ctx;
11
+ http;
12
+ jar;
13
+ /** 本次命令里刚生成过访客 cookie:再遇到登录墙就不再重试。 */
14
+ fresh = false;
15
+ constructor(ctx) {
16
+ this.ctx = ctx;
17
+ this.http = httpClient(ctx);
18
+ this.jar = this.http.jar;
19
+ }
20
+ /** weibo.com 的 XSRF-TOKEN,没有时为空串(上游缺它会直接报错)。 */
21
+ get xsrf() {
22
+ return this.jar.forUrl(SITE_URL.com).find((c) => c.name === 'XSRF-TOKEN')?.value ?? '';
23
+ }
24
+ /** 是否带着登录 cookie(weibo.com 的 SUB)。 */
25
+ get isLogin() {
26
+ return this.hasSub('com');
27
+ }
28
+ /** 上游的 `cookies=` 会把整串 cookie 发给任何域名(包括 weibocdn.com),这里按 weibo.com 取。 */
29
+ webCookies() {
30
+ return this.jar.toObject(SITE_URL.com);
31
+ }
32
+ request(req) {
33
+ return this.http.request(req);
34
+ }
35
+ // ---------------------------------------------------------------- 访客
36
+ hasSub(site) {
37
+ return this.jar.forUrl(SITE_URL[site]).some((c) => c.name === 'SUB');
38
+ }
39
+ /** m.weibo.cn 还没有访客 cookie 时先生成。 */
40
+ async ensure(site) {
41
+ if (site === 'cn' && !this.hasSub('cn'))
42
+ await this.visitor();
43
+ }
44
+ /** 生成 m.weibo.cn 的访客身份:与访客页里 ufp.util.postData 发出的 genvisitor2 请求一致,SUB / SUBP 由 Set-Cookie 写进 cookie 罐。 */
45
+ async visitor() {
46
+ const v = VISITOR;
47
+ const origin = `https://${v.host}`;
48
+ const referer = `${origin}/visitor/visitor?entry=${v.entry}&a=enter&url=${quote(SITE_URL.cn, '')}&domain=${v.domain}`;
49
+ for (const c of this.jar.forUrl(SITE_URL.cn).filter((x) => x.name === 'SUB' || x.name === 'SUBP'))
50
+ this.jar.delete(c.name, c.domain);
51
+ const res = await this.request({
52
+ method: 'POST',
53
+ url: `${origin}/visitor/genvisitor2`,
54
+ headers: visitorHeaders(origin, referer),
55
+ form: [
56
+ ['cb', 'visitor_gray_callback'],
57
+ ['ver', '20250916'],
58
+ ['request_id', ''],
59
+ ['tid', ''],
60
+ ['from', 'weibo'],
61
+ ['webdriver', 'false'],
62
+ ['rid', String(rand.now())],
63
+ ['return_url', SITE_URL.cn],
64
+ ],
65
+ });
66
+ riskCheck(res.status);
67
+ const text = await res.text();
68
+ let body = null;
69
+ try {
70
+ body = jsonLoads(/\((\{[\s\S]*\})\)/.exec(text)?.[1] ?? 'null');
71
+ }
72
+ catch { }
73
+ if (body?.retcode !== 20000000 || !this.hasSub('cn')) {
74
+ throw new SpecificAIError('RISK_CONTROL', `生成微博访客身份失败:${body?.msg ?? `HTTP ${res.status}`}`, {
75
+ detail: { kind: 'blocked', status: res.status, retcode: body?.retcode ?? null },
76
+ });
77
+ }
78
+ this.fresh = true;
79
+ this.ctx.log.debug(`已生成微博访客身份(${v.host})`);
80
+ }
81
+ /** m.weibo.cn 的访客 cookie 可能已失效:本次还没重新生成过时,重新生成并返回 true。 */
82
+ async renewVisitor(site) {
83
+ if (site !== 'cn' || this.fresh)
84
+ return false;
85
+ await this.visitor();
86
+ return true;
87
+ }
88
+ // ---------------------------------------------------------------- 请求
89
+ /**
90
+ * 请求 JSON 接口,检查登录墙与业务错误,返回整个 JSON。
91
+ * m.weibo.cn 的访客身份遇到登录墙(ok=-100)时,重新生成一次访客身份再试。
92
+ */
93
+ async json(site, req) {
94
+ await this.ensure(site);
95
+ let body = await this.parse(await this.request(req));
96
+ if (body.ok === -100 && (await this.renewVisitor(site)))
97
+ body = await this.parse(await this.request(req));
98
+ check(this.ctx, body, site);
99
+ return body;
100
+ }
101
+ /**
102
+ * 请求 HTML 页面,默认不跟随跳转。访客身份被 302 到访客页时,重新生成一次访客身份再试。
103
+ */
104
+ async html(site, req) {
105
+ await this.ensure(site);
106
+ let res = await this.request({ redirect: 'manual', ...req });
107
+ if (isVisitorRedirect(res) && (await this.renewVisitor(site)))
108
+ res = await this.request({ redirect: 'manual', ...req });
109
+ riskCheck(res.status);
110
+ return { status: res.status, location: res.headers.get('location'), text: await res.text() };
111
+ }
112
+ async parse(res) {
113
+ const text = await res.text();
114
+ try {
115
+ return jsonLoads(text);
116
+ }
117
+ catch {
118
+ riskCheck(res.status);
119
+ throw new SpecificAIError('UPSTREAM', `微博返回的不是 JSON(HTTP ${res.status})`, { detail: { status: res.status, body: text.slice(0, 300) } });
120
+ }
121
+ }
122
+ /** 写操作前检查登录态(registry 已保证有账号,这里防 cookie 不全)。 */
123
+ requireLogin() {
124
+ if (!this.isLogin)
125
+ throw authError(this.ctx, '当前账号缺少登录 cookie(SUB),请重新登录');
126
+ }
127
+ }
128
+ function isVisitorRedirect(res) {
129
+ return res.status >= 300 && res.status < 400 && /\/visitor\/visitor/.test(res.headers.get('location') ?? '');
130
+ }
131
+ /** 没有正文的拦截状态码:432 是微博的反爬拦截。 */
132
+ export function riskCheck(status) {
133
+ if (status === 432 || status === 418 || status === 429) {
134
+ throw new SpecificAIError('RISK_CONTROL', `微博拒绝了请求(HTTP ${status}),请稍后再试`, {
135
+ detail: { kind: status === 429 ? 'rate_limit' : 'blocked', status },
136
+ });
137
+ }
138
+ }
139
+ /** 登录墙与业务错误映射成 specific-ai 的错误(AGENTS 6.4)。 */
140
+ export function check(ctx, body, site = 'com') {
141
+ const ok = body?.ok;
142
+ if (ok === undefined || ok === 1)
143
+ return;
144
+ const message = String(body.message ?? body.msg ?? '');
145
+ if (ok === -100 || /登录/.test(message)) {
146
+ // m.weibo.cn 只用访客身份访问,登录 weibo.com 也解决不了
147
+ if (site === 'cn')
148
+ throw new SpecificAIError('AUTH_REQUIRED', `m.weibo.cn 要求登录后查看${message ? `:${message}` : ''}`);
149
+ throw authError(ctx, message ? `微博要求登录:${message}` : undefined);
150
+ }
151
+ if (/拥堵|频繁|稍后/.test(message)) {
152
+ throw new SpecificAIError('RISK_CONTROL', `微博限流:${message}`, { detail: { kind: 'rate_limit', ok, message } });
153
+ }
154
+ throw new SpecificAIError('UPSTREAM', message || `微博返回错误 ok=${ok}`, { detail: { ok, errno: body.errno ?? null, message } });
155
+ }
@@ -0,0 +1,202 @@
1
+ import { SpecificAIError } from '../../../core/errors.js';
2
+ import { readMedia } from '../../../core/files.js';
3
+ import { cookieCredential, finishLogin, loginContext } from '../../../core/login.js';
4
+ import * as n from '../../../core/normalize.js';
5
+ import * as rand from '../../../core/rand.js';
6
+ import { authError, paged } from '../../../core/toolkit.js';
7
+ import * as api from './api.js';
8
+ import { Weibo } from './client.js';
9
+ import * as norm from './normalize.js';
10
+ import { COOKIE_DOMAIN } from './profile.js';
11
+ import { resolveItem, resolveUser } from './resolve.js';
12
+ // ================================================================ auth
13
+ export async function authLogin(ctx) {
14
+ const credential = cookieCredential(ctx, COOKIE_DOMAIN);
15
+ const w = new Weibo(loginContext(ctx, credential));
16
+ const self = await api.selfInfo(w);
17
+ if (!self)
18
+ throw new SpecificAIError('AUTH_REQUIRED', '登录没有成功:cookie 无效或已过期', { hint: '在浏览器登录 weibo.com 后,复制请求头里完整的 Cookie' });
19
+ return finishLogin(ctx, credential, { id: self.uid, name: n.str(self.nick), url: norm.userUrl(self.uid) });
20
+ }
21
+ /** 登录态的过期时间:cookie ALF 是 SUB 的过期秒数(可能带 `02_` 前缀)。 */
22
+ function expiresAt(w) {
23
+ const alf = /(\d{9,})$/.exec(w.jar.get('ALF') ?? '')?.[1];
24
+ const sub = w.jar.forUrl('https://weibo.com/').find((c) => c.name === 'SUB')?.expires;
25
+ return n.time(alf ?? sub);
26
+ }
27
+ export async function authStatus(ctx) {
28
+ const w = new Weibo(ctx);
29
+ // 没有账号时凭证是空的,不用请求
30
+ const self = w.isLogin ? await api.selfInfo(w) : null;
31
+ return {
32
+ logged_in: Boolean(self),
33
+ user: self ? n.userRef({ id: self.uid, name: self.nick, url: norm.userUrl(self.uid) }) : null,
34
+ method: self ? ctx.credential.method : null,
35
+ expires_at: self ? expiresAt(w) : null,
36
+ };
37
+ }
38
+ /** 需要登录的操作:取当前账号的 uid 与昵称(上游 post_weibo 每次都先调 get_self_info)。 */
39
+ async function self(w) {
40
+ w.requireLogin();
41
+ const me = await api.selfInfo(w);
42
+ if (!me)
43
+ throw authError(w.ctx);
44
+ return me;
45
+ }
46
+ // ================================================================ user
47
+ export async function userGet(ctx) {
48
+ const w = new Weibo(ctx);
49
+ const uid = await resolveUser(w, ctx.args.user);
50
+ const body = await api.userInfo(w, uid);
51
+ const u = body.data?.user;
52
+ if (!u)
53
+ throw new SpecificAIError('UPSTREAM', `用户 ${uid} 不存在或不可见`, { detail: { uid } });
54
+ return norm.user(u);
55
+ }
56
+ /** cursor:`<page>:<since_id>`。 */
57
+ export async function userItems(ctx) {
58
+ const w = new Weibo(ctx);
59
+ const uid = await resolveUser(w, ctx.args.user);
60
+ const cursor = ctx.cursor ?? '1:';
61
+ const at = cursor.indexOf(':');
62
+ const page = Number(at < 0 ? cursor : cursor.slice(0, at)) || 1;
63
+ const body = await api.userPosted(w, uid, page, at < 0 ? '' : cursor.slice(at + 1));
64
+ const own = uid === ctx.credential.user?.id;
65
+ const list = (body.data?.list ?? []).map((m) => norm.mblog(m, own));
66
+ const next = n.str(body.data?.since_id);
67
+ return paged(list, `${page + 1}:${next ?? ''}`, Boolean(next));
68
+ }
69
+ // ================================================================ item
70
+ export async function itemGet(ctx) {
71
+ const w = new Weibo(ctx);
72
+ const { mid } = resolveItem(ctx.args.item);
73
+ const d = await api.mobileDetail(w, mid);
74
+ if (!d?.status)
75
+ throw new SpecificAIError('UPSTREAM', `微博 ${mid} 不存在、已删除或不可见`);
76
+ return norm.mblog(d.status, d.status.user?.id != null && String(d.status.user.id) === ctx.credential.user?.id);
77
+ }
78
+ /** 搜索结果:顶层 card_type=9 的卡片,以及 card_group 里的 card_type=9。 */
79
+ function searchMblogs(cards) {
80
+ const out = [];
81
+ const seen = new Set();
82
+ const add = (c) => {
83
+ if (c?.card_type !== 9 || !c.mblog)
84
+ return;
85
+ const id = String(c.mblog.mid ?? c.mblog.id);
86
+ if (!seen.has(id))
87
+ (seen.add(id), out.push(c.mblog));
88
+ };
89
+ for (const c of cards ?? []) {
90
+ add(c);
91
+ for (const g of c.card_group ?? [])
92
+ add(g);
93
+ }
94
+ return out;
95
+ }
96
+ /**
97
+ * 只取第一页:m.weibo.cn 用访客身份访问(登录 cookie 不发往 .weibo.cn),访客从第二页起就是登录墙;
98
+ * 上游 searchSome 的 page 参数同样翻不过去。
99
+ */
100
+ export async function itemSearch(ctx) {
101
+ const w = new Weibo(ctx);
102
+ const body = await api.mobileSearch(w, ctx.args.keyword, 1);
103
+ return paged(searchMblogs(body.data?.cards).map((m) => norm.mblog(m)), null, false);
104
+ }
105
+ /** post_weibo 的 type(请求里的 visible):0 公开、1 仅自己可见、6 朋友圈可见、10 粉丝可见。 */
106
+ const VISIBLE = { public: '0', private: '1', friends: '6', fans: '10' };
107
+ const MAX_IMAGES = 15;
108
+ /** 上游 post_weibo 的正文:话题追加成 ` #话题# `,地点追加成 ` #地点[地点]#`。 */
109
+ function content(text, topics, location) {
110
+ let s = text;
111
+ for (const t of topics)
112
+ s += ` #${t}# `;
113
+ if (location)
114
+ s += ` #${location}[地点]#`;
115
+ return s;
116
+ }
117
+ export async function itemPublish(ctx) {
118
+ const o = ctx.options;
119
+ const images = o.image ?? [];
120
+ if (images.length && o.video)
121
+ throw new SpecificAIError('USAGE', '--image 和 --video 不能同时使用');
122
+ if (images.length > MAX_IMAGES)
123
+ throw new SpecificAIError('USAGE', `微博最多 ${MAX_IMAGES} 张图片`);
124
+ const text = o.text ?? '';
125
+ if (!text && !images.length && !o.video)
126
+ throw new SpecificAIError('USAGE', '需要 --text、--image 或 --video', { hint: 'specific-ai weibo item publish --text "正文" [--image <图片>...]' });
127
+ const w = new Weibo(ctx);
128
+ const me = await self(w);
129
+ const body = content(text, o.topic ?? [], o.poiName ?? '');
130
+ const visible = VISIBLE[o.visibility ?? 'public'] ?? '0';
131
+ let result;
132
+ if (o.video) {
133
+ const file = await readMedia(w.http, o.video);
134
+ const mediaId = await uploadVideoFile(w, file.data, true);
135
+ result = await api.postVideo(w, body, visible, mediaId);
136
+ }
137
+ else {
138
+ const pids = [];
139
+ for (const input of images) {
140
+ const file = await readMedia(w.http, input);
141
+ pids.push(String((await api.uploadImage(w, me.uid, me.nick, file.data)).pic.pid));
142
+ }
143
+ result = await api.postImage(w, body, visible, pids);
144
+ }
145
+ const d = result.data;
146
+ if (!d)
147
+ throw new SpecificAIError('UPSTREAM', '发布没有返回微博', { detail: { body: result } });
148
+ return norm.mblog(d, true);
149
+ }
150
+ /** 视频上传:init → 整个文件一次上传 → check;waitOutput 时再轮询转码结果(上游 post_weibo 的做法)。返回 media_id。 */
151
+ async function uploadVideoFile(w, data, waitOutput) {
152
+ const init = await api.videoInit(w, data);
153
+ const { upload_id: uploadId, media_id: mediaId, auth } = init;
154
+ if (uploadId == null || mediaId == null || !auth)
155
+ throw new SpecificAIError('UPSTREAM', '申请上传视频没有返回 upload_id / media_id', { detail: { body: init } });
156
+ await api.uploadVideo(w, String(uploadId), String(mediaId), data, String(auth));
157
+ await api.videoCheck(w, String(uploadId), String(mediaId), data.length, String(auth));
158
+ if (waitOutput) {
159
+ // 上游每 2 秒轮询一次、没有上限;这里最多等 5 分钟
160
+ for (let i = 0;; i++) {
161
+ const out = await api.videoOutput(w, String(mediaId));
162
+ if (out.data && Object.keys(out.data).length > 0)
163
+ break;
164
+ if (i >= 150)
165
+ throw new SpecificAIError('UPSTREAM', '视频转码超时(5 分钟)', { detail: { media_id: String(mediaId) } });
166
+ w.ctx.log.info('视频转码中…');
167
+ await rand.sleep(2000, w.ctx.signal);
168
+ }
169
+ }
170
+ return mediaId;
171
+ }
172
+ // ================================================================ comment
173
+ export async function commentList(ctx) {
174
+ const w = new Weibo(ctx);
175
+ const ref = resolveItem(ctx.args.item);
176
+ let uid = ref.uid;
177
+ if (!uid) {
178
+ const d = await api.mobileDetail(w, ref.mid);
179
+ uid = d?.status?.user?.id != null ? String(d.status.user.id) : null;
180
+ if (!uid)
181
+ throw new SpecificAIError('UPSTREAM', `微博 ${ref.mid} 不存在、已删除或不可见`);
182
+ }
183
+ const body = await api.comments(w, uid, ref.mid, ctx.cursor ?? undefined);
184
+ const list = (body.data ?? []).map((c) => norm.comment(c, ref.mid));
185
+ const next = n.str(body.max_id);
186
+ return paged(list, next, Boolean(next && next !== '0' && list.length));
187
+ }
188
+ // ================================================================ media
189
+ export async function mediaUpload(ctx) {
190
+ const w = new Weibo(ctx);
191
+ const file = await readMedia(w.http, ctx.args.file);
192
+ if (file.contentType.startsWith('video/')) {
193
+ w.requireLogin();
194
+ // 还没发布的视频没有可播放的地址(转码结果只有截图),url 为空串;发布时用的是 id(media_id)
195
+ const mediaId = String(await uploadVideoFile(w, file.data, false));
196
+ return n.media({ id: mediaId, type: 'video', url: null });
197
+ }
198
+ const me = await self(w);
199
+ const d = await api.uploadImage(w, me.uid, me.nick, file.data);
200
+ const pic = d.pic;
201
+ return n.media({ id: String(pic.pid), type: 'image', url: `https://wx1.sinaimg.cn/large/${pic.pid}.jpg`, width: n.count(pic.width), height: n.count(pic.height) }, d);
202
+ }