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,137 @@
1
+ import * as n from '../../../core/normalize.js';
2
+ import { midToBid } from './sign.js';
3
+ /**
4
+ * 微博原始对象 → 归一化类型(AGENTS 6.2)。
5
+ * weibo.com(ajax)与 m.weibo.cn 的微博对象字段大体相同,差别在图片(pic_infos / pics)、视频(page_info)和正文(text_raw / HTML)。
6
+ */
7
+ export const userUrl = (uid) => `https://weibo.com/u/${uid}`;
8
+ /** 微博的规范链接:weibo.com/<uid>/<mblogid>;不知道作者时用 m.weibo.cn/detail/<mid>。 */
9
+ export function itemUrl(mid, uid, bid) {
10
+ if (uid == null || uid === '')
11
+ return `https://m.weibo.cn/detail/${mid}`;
12
+ return `https://weibo.com/${uid}/${bid || midToBid(mid)}`;
13
+ }
14
+ export function ref(u) {
15
+ const id = u?.idstr ?? u?.id;
16
+ return n.userRef({ id, name: u?.screen_name, url: id == null || id === '' ? null : userUrl(id) });
17
+ }
18
+ /** "21万次播放" / "1,234" → 整数。 */
19
+ function plays(v) {
20
+ if (typeof v === 'number')
21
+ return n.count(v);
22
+ const m = /^\s*([\d.,]+\s*[万亿wW]?)/.exec(String(v ?? ''));
23
+ return m ? n.count(m[1].replace(/\s/g, '')) : null;
24
+ }
25
+ function isVideo(m) {
26
+ return m?.page_info?.type === 'video' || m?.page_info?.object_type === 'video';
27
+ }
28
+ /** 图片:weibo.com 是 pic_infos(按 pic_ids 的顺序),m.weibo.cn 是 pics。 */
29
+ function images(m) {
30
+ if (m?.pic_infos && typeof m.pic_infos === 'object') {
31
+ const ids = Array.isArray(m.pic_ids) && m.pic_ids.length ? m.pic_ids : Object.keys(m.pic_infos);
32
+ return ids
33
+ .map((pid) => m.pic_infos[pid])
34
+ .filter(Boolean)
35
+ .map((p) => {
36
+ const best = p.largest ?? p.mw2000 ?? p.original ?? p.large ?? p.bmiddle;
37
+ return n.media({ id: n.idOrNull(p.pic_id), type: 'image', url: n.url(best?.url), width: n.count(best?.width), height: n.count(best?.height) }, p);
38
+ })
39
+ .filter((x) => x.url);
40
+ }
41
+ if (Array.isArray(m?.pics)) {
42
+ return m.pics
43
+ .map((p) => {
44
+ const large = p.large ?? p;
45
+ return n.media({ id: n.idOrNull(p.pid), type: 'image', url: n.url(large.url), width: n.count(large.geo?.width), height: n.count(large.geo?.height) }, p);
46
+ })
47
+ .filter((x) => x.url);
48
+ }
49
+ return [];
50
+ }
51
+ /** 视频:weibo.com 的 media_info.playback_list 按清晰度从高到低;m.weibo.cn 是 urls。 */
52
+ function video(m) {
53
+ const p = m.page_info ?? {};
54
+ const info = p.media_info ?? {};
55
+ const best = info.playback_list?.[0]?.play_info;
56
+ const url = best?.url ??
57
+ p.urls?.mp4_720p_mp4 ??
58
+ p.urls?.mp4_hd_mp4 ??
59
+ p.urls?.mp4_ld_mp4 ??
60
+ info.mp4_720p_mp4 ??
61
+ info.mp4_hd_url ??
62
+ info.stream_url_hd ??
63
+ info.stream_url;
64
+ if (!url)
65
+ return null;
66
+ return n.media({
67
+ id: n.idOrNull(info.media_id ?? p.object_id),
68
+ type: 'video',
69
+ url: n.url(url),
70
+ width: n.count(best?.width),
71
+ height: n.count(best?.height),
72
+ duration: n.seconds(best?.duration ?? info.duration),
73
+ }, info);
74
+ }
75
+ const VISIBLE = { 0: 'published', 1: 'private', 6: 'published', 10: 'published' };
76
+ /**
77
+ * 一条微博。own 为 true 时(自己的微博)按 visible.type 给出 status:仅自己可见为 private,其余为 published。
78
+ */
79
+ export function mblog(m, own = false) {
80
+ const id = n.id(m.idstr ?? m.mid ?? m.id);
81
+ const author = ref(m.user);
82
+ const vid = isVideo(m) ? video(m) : null;
83
+ const pics = images(m);
84
+ const media = vid ? [vid] : pics;
85
+ const kind = vid ? 'video' : m.page_info?.object_type === 'article' || m.page_info?.type === 'article' ? 'article' : pics.length || Number(m.pic_num) > 0 ? 'image' : 'text';
86
+ const cover = vid ? n.url(m.page_info?.page_pic?.url ?? m.page_info?.page_pic) : (pics[0]?.url ?? null);
87
+ return n.item({
88
+ id,
89
+ kind,
90
+ url: itemUrl(id, author?.id, m.mblogid ?? m.bid),
91
+ title: kind === 'article' ? n.str(m.page_info?.page_title ?? m.page_info?.content1) : null,
92
+ text: n.str(m.text_raw) ?? n.plainText(m.text),
93
+ author,
94
+ created_at: n.time(m.created_at),
95
+ cover,
96
+ media,
97
+ stats: {
98
+ views: vid ? plays(m.page_info?.play_count ?? m.page_info?.media_info?.online_users_number) : null,
99
+ likes: n.count(m.attitudes_count),
100
+ comments: n.count(m.comments_count),
101
+ shares: n.count(m.reposts_count),
102
+ },
103
+ status: own ? (VISIBLE[Number(m.visible?.type ?? 0)] ?? 'published') : null,
104
+ }, m);
105
+ }
106
+ /** weibo.com/ajax/profile/info 的 data.user。 */
107
+ export function user(u) {
108
+ const id = n.id(u.idstr ?? u.id);
109
+ return n.user({
110
+ id,
111
+ name: n.str(u.screen_name),
112
+ handle: n.str(u.domain) ?? n.str(u.weihao),
113
+ avatar: n.url(u.avatar_hd ?? u.avatar_large ?? u.profile_image_url),
114
+ url: userUrl(id),
115
+ bio: n.str(u.description),
116
+ stats: {
117
+ followers: n.count(u.followers_count),
118
+ following: n.count(u.friends_count ?? u.follow_count),
119
+ items: n.count(u.statuses_count),
120
+ likes: n.count(u.status_total_counter?.like_cnt),
121
+ },
122
+ }, u);
123
+ }
124
+ /** buildComments 的一条评论。 */
125
+ export function comment(c, itemId) {
126
+ const id = n.id(c.idstr ?? c.id);
127
+ const root = n.idOrNull(c.rootidstr ?? c.rootid);
128
+ return n.comment({
129
+ id,
130
+ item_id: itemId,
131
+ parent_id: root && root !== id ? root : null,
132
+ author: ref(c.user),
133
+ text: n.str(c.text_raw) ?? n.plainText(c.text) ?? '',
134
+ created_at: n.time(c.created_at),
135
+ stats: { likes: n.count(c.like_counts), replies: n.count(c.total_number) },
136
+ }, c);
137
+ }
@@ -0,0 +1,246 @@
1
+ /**
2
+ * 请求头:逐字照抄上游 utils/weibo_utils.py、weibo_mobile_utils.py、weibo_creator_utils.py,
3
+ * 键名大小写和顺序都不改(对拍测试逐字节比较)。
4
+ *
5
+ * 与上游的差异:上游写死的 `content-length` 不发(上游的 requests 发送前会用真实长度覆盖它,
6
+ * 而 get_post_video_headers 里写的是视频大小、与表单体不符),由 HTTP 库按实际请求体计算。
7
+ */
8
+ export const WEB = 'https://weibo.com';
9
+ export const MOBILE = 'https://m.weibo.cn';
10
+ export const COOKIE_DOMAIN = '.weibo.com';
11
+ /** 上游 HTML 请求头里的 UA(Chrome 132),访客接口也用它。 */
12
+ export const UA_132 = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36';
13
+ const SEC_CH_UA_132 = '"Not A(Brand";v="8", "Chromium";v="132", "Google Chrome";v="132"';
14
+ const UA_130 = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0';
15
+ const SEC_CH_UA_130 = '"Chromium";v="130", "Microsoft Edge";v="130", "Not?A_Brand";v="99"';
16
+ const DOC_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';
17
+ const XHR_ACCEPT = 'application/json, text/plain, */*';
18
+ // ================================================================ utils/weibo_utils.py
19
+ /** get_common_headers:weibo.com 的 ajax 接口。xsrf 取 cookie 里的 XSRF-TOKEN,游客为空串。 */
20
+ export function commonHeaders(xsrf) {
21
+ return [
22
+ ['accept', XHR_ACCEPT],
23
+ ['accept-language', 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6'],
24
+ ['cache-control', 'no-cache'],
25
+ ['client-version', 'v2.46.7'],
26
+ ['pragma', 'no-cache'],
27
+ ['priority', 'u=1, i'],
28
+ ['referer', 'https://weibo.com/u/5266778656'],
29
+ ['sec-ch-ua', '"Chromium";v="128", "Not;A=Brand";v="24", "Microsoft Edge";v="128"'],
30
+ ['sec-ch-ua-mobile', '?0'],
31
+ ['sec-ch-ua-platform', '"Windows"'],
32
+ ['sec-fetch-dest', 'empty'],
33
+ ['sec-fetch-mode', 'cors'],
34
+ ['sec-fetch-site', 'same-origin'],
35
+ ['server-version', 'v2024.08.30.1'],
36
+ ['user-agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0'],
37
+ ['x-requested-with', 'XMLHttpRequest'],
38
+ ['x-xsrf-token', xsrf],
39
+ ];
40
+ }
41
+ /** get_html_headers:weibo.com 首页(取 $CONFIG)。 */
42
+ export function htmlHeaders() {
43
+ return [
44
+ ['accept', DOC_ACCEPT],
45
+ ['accept-language', 'zh-CN,zh;q=0.9'],
46
+ ['cache-control', 'no-cache'],
47
+ ['pragma', 'no-cache'],
48
+ ['priority', 'u=0, i'],
49
+ ['referer', 'https://weibo.com/tv/home'],
50
+ ['sec-ch-ua', SEC_CH_UA_132],
51
+ ['sec-ch-ua-mobile', '?0'],
52
+ ['sec-ch-ua-platform', '"Windows"'],
53
+ ['sec-fetch-dest', 'document'],
54
+ ['sec-fetch-mode', 'navigate'],
55
+ ['sec-fetch-site', 'same-origin'],
56
+ ['sec-fetch-user', '?1'],
57
+ ['upgrade-insecure-requests', '1'],
58
+ ['user-agent', UA_132],
59
+ ];
60
+ }
61
+ // ================================================================ utils/weibo_mobile_utils.py
62
+ /** get_search_headers:m.weibo.cn 的搜索接口。 */
63
+ export function searchHeaders() {
64
+ return [
65
+ ['sec-ch-ua-platform', '"Windows"'],
66
+ ['X-XSRF-TOKEN', 'a1d843'],
67
+ ['Referer', 'https://m.weibo.cn/'],
68
+ ['sec-ch-ua', SEC_CH_UA_130],
69
+ ['sec-ch-ua-mobile', '?0'],
70
+ ['MWeibo-Pwa', '1'],
71
+ ['X-Requested-With', 'XMLHttpRequest'],
72
+ ['User-Agent', UA_130],
73
+ ['Accept', XHR_ACCEPT],
74
+ ];
75
+ }
76
+ /** get_detail_headers:m.weibo.cn 的详情页。 */
77
+ export function detailHeaders() {
78
+ return [
79
+ ['accept', DOC_ACCEPT],
80
+ ['accept-language', 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6'],
81
+ ['cache-control', 'no-cache'],
82
+ ['pragma', 'no-cache'],
83
+ ['priority', 'u=0, i'],
84
+ ['sec-ch-ua', SEC_CH_UA_130],
85
+ ['sec-ch-ua-mobile', '?0'],
86
+ ['sec-ch-ua-platform', '"Windows"'],
87
+ ['sec-fetch-dest', 'document'],
88
+ ['sec-fetch-mode', 'navigate'],
89
+ ['sec-fetch-site', 'none'],
90
+ ['sec-fetch-user', '?1'],
91
+ ['upgrade-insecure-requests', '1'],
92
+ ['user-agent', UA_130],
93
+ ];
94
+ }
95
+ // ================================================================ utils/weibo_creator_utils.py
96
+ /** generate_upload_image_media_headers:picupload 上传图片。 */
97
+ export function uploadImageHeaders() {
98
+ return [
99
+ ['accept', XHR_ACCEPT],
100
+ ['accept-language', 'zh-CN,zh;q=0.9'],
101
+ ['cache-control', 'no-cache'],
102
+ ['content-type', 'application/octet-stream'],
103
+ ['origin', 'https://weibo.com'],
104
+ ['pragma', 'no-cache'],
105
+ ['priority', 'u=1, i'],
106
+ ['referer', 'https://weibo.com/'],
107
+ ['sec-ch-ua', SEC_CH_UA_132],
108
+ ['sec-ch-ua-mobile', '?0'],
109
+ ['sec-ch-ua-platform', '"Windows"'],
110
+ ['sec-fetch-dest', 'empty'],
111
+ ['sec-fetch-mode', 'cors'],
112
+ ['sec-fetch-site', 'same-site'],
113
+ ['user-agent', UA_132],
114
+ ];
115
+ }
116
+ /** generate_video_check_headers:fileplatform check.json。 */
117
+ export function videoCheckHeaders(auth) {
118
+ return [
119
+ ['Accept', XHR_ACCEPT],
120
+ ['Accept-Language', 'zh-CN,zh;q=0.9'],
121
+ ['Cache-Control', 'no-cache'],
122
+ ['Connection', 'keep-alive'],
123
+ ['Content-Type', 'application/x-www-form-urlencoded'],
124
+ ['Origin', 'https://weibo.com'],
125
+ ['Pragma', 'no-cache'],
126
+ ['Referer', 'https://weibo.com/'],
127
+ ['Sec-Fetch-Dest', 'empty'],
128
+ ['Sec-Fetch-Mode', 'cors'],
129
+ ['Sec-Fetch-Site', 'same-site'],
130
+ ['User-Agent', UA_132],
131
+ ['X-Up-Auth', auth],
132
+ ['sec-ch-ua', SEC_CH_UA_132],
133
+ ['sec-ch-ua-mobile', '?0'],
134
+ ['sec-ch-ua-platform', '"Windows"'],
135
+ ];
136
+ }
137
+ /** generate_video_output_headers:multimedia/output 轮询转码结果。 */
138
+ export function videoOutputHeaders() {
139
+ return [
140
+ ['accept', XHR_ACCEPT],
141
+ ['accept-language', 'zh-CN,zh;q=0.9'],
142
+ ['cache-control', 'no-cache'],
143
+ ['pragma', 'no-cache'],
144
+ ['priority', 'u=1, i'],
145
+ ['referer', 'https://weibo.com/upload/channel'],
146
+ ['sec-ch-ua', SEC_CH_UA_132],
147
+ ['sec-ch-ua-mobile', '?0'],
148
+ ['sec-ch-ua-platform', '"Windows"'],
149
+ ['sec-fetch-dest', 'empty'],
150
+ ['sec-fetch-mode', 'cors'],
151
+ ['sec-fetch-site', 'same-origin'],
152
+ ['user-agent', UA_132],
153
+ ];
154
+ }
155
+ /** generate_upload_video_media_headers:up.video.weibocdn.com 上传视频(content-length 见文件头注释)。 */
156
+ export function uploadVideoHeaders(auth) {
157
+ return [
158
+ ['accept', XHR_ACCEPT],
159
+ ['accept-encoding', 'gzip, deflate, br, zstd'],
160
+ ['accept-language', 'zh-CN,zh;q=0.9'],
161
+ ['cache-control', 'no-cache'],
162
+ ['content-type', 'application/octet-stream'],
163
+ ['host', 'up.video.weibocdn.com'],
164
+ ['origin', 'https://weibo.com'],
165
+ ['pragma', 'no-cache'],
166
+ ['priority', 'u=1, i'],
167
+ ['referer', 'https://weibo.com/'],
168
+ ['sec-ch-ua', SEC_CH_UA_132],
169
+ ['sec-ch-ua-mobile', '?0'],
170
+ ['sec-ch-ua-platform', '"Windows"'],
171
+ ['sec-fetch-dest', 'empty'],
172
+ ['sec-fetch-mode', 'cors'],
173
+ ['sec-fetch-site', 'same-site'],
174
+ ['user-agent', UA_132],
175
+ ['x-up-auth', auth],
176
+ ];
177
+ }
178
+ function postHeaders() {
179
+ return [
180
+ ['accept', XHR_ACCEPT],
181
+ ['accept-language', 'zh-CN,zh;q=0.9'],
182
+ ['cache-control', 'no-cache'],
183
+ ['client-version', 'v2.47.25'],
184
+ ['content-type', 'application/x-www-form-urlencoded'],
185
+ ['origin', 'https://weibo.com'],
186
+ ['pragma', 'no-cache'],
187
+ ['priority', 'u=1, i'],
188
+ ['referer', 'https://weibo.com/'],
189
+ ['sec-ch-ua', SEC_CH_UA_132],
190
+ ['sec-ch-ua-mobile', '?0'],
191
+ ['sec-ch-ua-platform', '"Windows"'],
192
+ ['sec-fetch-dest', 'empty'],
193
+ ['sec-fetch-mode', 'cors'],
194
+ ['sec-fetch-site', 'same-origin'],
195
+ ['server-version', 'v2025.01.23.1'],
196
+ ['user-agent', UA_132],
197
+ ['x-requested-with', 'XMLHttpRequest'],
198
+ ];
199
+ }
200
+ /** get_post_image_headers:发图文(statuses/update)。 */
201
+ export function postImageHeaders(xsrf) {
202
+ return [...postHeaders(), ['x-xsrf-token', xsrf]];
203
+ }
204
+ /** get_post_video_headers:发视频(statuses/update)。上游这里不带 x-xsrf-token,照抄。 */
205
+ export function postVideoHeaders() {
206
+ return postHeaders();
207
+ }
208
+ /** get_form_headers:fileplatform init.json,multipart/mixed 的 boundary 带秒级时间戳。 */
209
+ export function formHeaders(t) {
210
+ return [
211
+ ['Accept', XHR_ACCEPT],
212
+ ['Accept-Language', 'zh-CN,zh;q=0.9'],
213
+ ['Cache-Control', 'no-cache'],
214
+ ['Connection', 'keep-alive'],
215
+ ['Content-Type', `multipart/mixed; boundary=2067456weiboPro${t}`],
216
+ ['Origin', 'https://weibo.com'],
217
+ ['Pragma', 'no-cache'],
218
+ ['Referer', 'https://weibo.com/'],
219
+ ['Sec-Fetch-Dest', 'empty'],
220
+ ['Sec-Fetch-Mode', 'cors'],
221
+ ['Sec-Fetch-Site', 'same-site'],
222
+ ['User-Agent', UA_132],
223
+ ['sec-ch-ua', SEC_CH_UA_132],
224
+ ['sec-ch-ua-mobile', '?0'],
225
+ ['sec-ch-ua-platform', '"Windows"'],
226
+ ];
227
+ }
228
+ // ================================================================ 访客(上游没有,见 client.ts)
229
+ /** 新浪访客系统 genvisitor2 的 XHR 头:与访客页里 ufp.util.postData 发出的一致。 */
230
+ export function visitorHeaders(origin, referer) {
231
+ return [
232
+ ['sec-ch-ua-platform', '"Windows"'],
233
+ ['user-agent', UA_132],
234
+ ['sec-ch-ua', SEC_CH_UA_132],
235
+ ['content-type', 'application/x-www-form-urlencoded'],
236
+ ['sec-ch-ua-mobile', '?0'],
237
+ ['accept', '*/*'],
238
+ ['origin', origin],
239
+ ['sec-fetch-site', 'same-origin'],
240
+ ['sec-fetch-mode', 'cors'],
241
+ ['sec-fetch-dest', 'empty'],
242
+ ['referer', referer],
243
+ ['accept-language', 'zh-CN,zh;q=0.9'],
244
+ ['priority', 'u=1, i'],
245
+ ];
246
+ }
@@ -0,0 +1,60 @@
1
+ import { SpecificAIError } from '../../../core/errors.js';
2
+ import * as api from './api.js';
3
+ import { bidToMid } from './sign.js';
4
+ const toMid = (token) => (/^\d+$/.test(token) ? token : bidToMid(token));
5
+ /**
6
+ * 微博:mid(16 位数字)、mblogid(如 OuIv3hbiw),或链接
7
+ * weibo.com/<uid>/<mblogid|mid>、weibo.com/detail/<mid>、m.weibo.cn/detail|status/<mid|mblogid>、m.weibo.cn/<uid>/<mid>。
8
+ */
9
+ export function resolveItem(input) {
10
+ const s = input.trim();
11
+ if (/^\d+$/.test(s) || /^[0-9A-Za-z]{8,10}$/.test(s))
12
+ return { mid: toMid(s), uid: null };
13
+ let url = null;
14
+ try {
15
+ url = new URL(/^https?:\/\//i.test(s) ? s : `https://${s}`);
16
+ }
17
+ catch { }
18
+ if (url && /(^|\.)weibo\.(com|cn)$/.test(url.hostname)) {
19
+ const parts = url.pathname.split('/').filter(Boolean);
20
+ if (parts.length >= 2 && /^(detail|status)$/.test(parts[0]) && /^[0-9A-Za-z]+$/.test(parts[1]))
21
+ return { mid: toMid(parts[1]), uid: null };
22
+ if (parts.length >= 2 && /^\d+$/.test(parts[0]) && /^[0-9A-Za-z]+$/.test(parts[1]))
23
+ return { mid: toMid(parts[1]), uid: parts[0] };
24
+ const q = url.searchParams.get('id') ?? url.searchParams.get('mid');
25
+ if (q && /^[0-9A-Za-z]+$/.test(q))
26
+ return { mid: toMid(q), uid: null };
27
+ }
28
+ throw new SpecificAIError('USAGE', `无法识别的微博:${input}`, { hint: '传 mid、mblogid 或微博链接,例如 https://weibo.com/<uid>/<mblogid>' });
29
+ }
30
+ /** 用户:uid、主页链接(weibo.com/u/<uid>、weibo.com/<uid>、m.weibo.cn/u|profile/<uid>)或 `me`。 */
31
+ export async function resolveUser(w, input) {
32
+ const s = input.trim();
33
+ if (s === 'me') {
34
+ const saved = w.ctx.credential.user?.id;
35
+ if (saved)
36
+ return saved;
37
+ const self = await api.selfInfo(w);
38
+ if (!self)
39
+ throw new SpecificAIError('AUTH_REQUIRED', 'me 表示当前账号,需要先登录', { hint: 'specific-ai weibo auth login' });
40
+ return self.uid;
41
+ }
42
+ if (/^\d+$/.test(s))
43
+ return s;
44
+ let url = null;
45
+ try {
46
+ url = new URL(/^https?:\/\//i.test(s) ? s : `https://${s}`);
47
+ }
48
+ catch { }
49
+ if (url && /(^|\.)weibo\.(com|cn)$/.test(url.hostname)) {
50
+ const parts = url.pathname.split('/').filter(Boolean);
51
+ if (parts.length >= 2 && /^(u|profile)$/.test(parts[0]) && /^\d+$/.test(parts[1]))
52
+ return parts[1];
53
+ if (parts.length >= 1 && /^\d+$/.test(parts[0]))
54
+ return parts[0];
55
+ const uid = url.searchParams.get('uid');
56
+ if (uid && /^\d+$/.test(uid))
57
+ return uid;
58
+ }
59
+ throw new SpecificAIError('USAGE', `无法识别的用户:${input}`, { hint: '传用户 uid(数字)或主页链接 https://weibo.com/u/<uid>' });
60
+ }
@@ -0,0 +1,85 @@
1
+ import { crc32 } from 'node:zlib';
2
+ import { md5Hex } from '../../../core/hash.js';
3
+ import { urlencode } from '../../../core/py.js';
4
+ import * as rand from '../../../core/rand.js';
5
+ /** 上传参数(上游 utils/weibo_creator_utils.py)。 */
6
+ export const APP_SOURCE = '339644097';
7
+ export { md5Hex };
8
+ /** generate_params:CRC32(上游手写的查表实现,与标准 CRC32 相同)、MD5、大小。 */
9
+ export function fileParams(file) {
10
+ return { cs: crc32(file), md5: md5Hex(file), fileSize: file.length };
11
+ }
12
+ /** generate_session_id:md5(`毫秒|大小|文件名|video|秒`)。 */
13
+ export function sessionId(fileSize, fileName) {
14
+ return md5Hex(`${rand.now()}|${fileSize}|${fileName}|video|${rand.nowSeconds()}`);
15
+ }
16
+ /** generate_upload_image_media_params,已 urlencode。 */
17
+ export function uploadImageParams(uid, nick, file) {
18
+ const p = fileParams(file);
19
+ const params = [
20
+ ['file_source', '1'],
21
+ ['cs', String(p.cs)],
22
+ ['ent', 'miniblog'],
23
+ ['appid', APP_SOURCE],
24
+ ['uid', uid],
25
+ ['raw_md5', p.md5],
26
+ ['ori', '1'],
27
+ ['mpos', '1'],
28
+ ['nick', nick],
29
+ ['pri', '0'],
30
+ ['request_id', String(rand.now())],
31
+ ['file_size', String(p.fileSize)],
32
+ ];
33
+ return urlencode(params);
34
+ }
35
+ /** generate_upload_video_media_params,已 urlencode。整个文件作为一个分片上传。 */
36
+ export function uploadVideoParams(uploadId, mediaId, file) {
37
+ const p = fileParams(file);
38
+ const params = [
39
+ ['source', APP_SOURCE],
40
+ ['upload_id', uploadId],
41
+ ['media_id', mediaId],
42
+ ['upload_protocol', 'binary'],
43
+ ['type', 'video'],
44
+ ['client', 'web'],
45
+ ['check', p.md5],
46
+ ['index', '0'],
47
+ ['size', String(p.fileSize)],
48
+ ['start_loc', '0'],
49
+ ['count', '1'],
50
+ ];
51
+ return urlencode(params);
52
+ }
53
+ // ================================================================ mid ↔ mblogid
54
+ /** 微博的 62 进制:mid 从右往左每 7 位十进制一组,每组转成 4 位 62 进制(最高组不补零)。 */
55
+ const ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
56
+ function groups(s, size) {
57
+ const out = [];
58
+ for (let end = s.length; end > 0; end -= size)
59
+ out.unshift(s.slice(Math.max(0, end - size), end));
60
+ return out;
61
+ }
62
+ /** 数字 mid → mblogid(URL 里的 9 位短码,如 OuIv3hbiw)。 */
63
+ export function midToBid(mid) {
64
+ return groups(mid, 7)
65
+ .map((g, i) => {
66
+ let n = Number(g);
67
+ let s = '';
68
+ do {
69
+ s = ALPHABET[n % 62] + s;
70
+ n = Math.floor(n / 62);
71
+ } while (n > 0);
72
+ return i === 0 ? s : s.padStart(4, '0');
73
+ })
74
+ .join('');
75
+ }
76
+ /** mblogid → 数字 mid。 */
77
+ export function bidToMid(bid) {
78
+ return groups(bid, 4)
79
+ .map((g, i) => {
80
+ const n = [...g].reduce((acc, ch) => acc * 62 + ALPHABET.indexOf(ch), 0);
81
+ return i === 0 ? String(n) : String(n).padStart(7, '0');
82
+ })
83
+ .join('')
84
+ .replace(/^0+(?=\d)/, '');
85
+ }
@@ -0,0 +1,107 @@
1
+ import { z } from 'zod';
2
+ import { filter, PUBLISH, QUOTE, visibility } from '../../core/options.js';
3
+ import { definePlatform, handlers } from '../../core/registry.js';
4
+ /** 懒加载 web 端的 handler:只有执行到这条命令时才加载实现。 */
5
+ const { h, impl } = handlers(() => import('./web/commands.js'));
6
+ export default definePlatform({
7
+ id: 'x',
8
+ name: 'X',
9
+ aliases: ['twitter'],
10
+ item: '推文',
11
+ endpoints: {
12
+ web: {
13
+ // 上游的账密登录依赖 Castle token,不移植:只支持从浏览器导入 cookie
14
+ login: { methods: ['cookie'], default: 'cookie' },
15
+ commands: {
16
+ 'auth login': impl('full', 'authLogin'),
17
+ 'auth status': impl('full', 'authStatus'),
18
+ 'user get': impl('full', 'userGet'),
19
+ 'user search': impl('full', 'userSearch'),
20
+ 'user items': impl('full', 'userItems'),
21
+ 'user likes': 'none',
22
+ 'user collects': 'none',
23
+ 'user reposts': 'none',
24
+ 'user followers': 'none',
25
+ 'user following': 'none',
26
+ 'user follow': impl('full', 'userFollow'),
27
+ 'user unfollow': impl('full', 'userUnfollow'),
28
+ 'item get': impl('full', 'itemGet'),
29
+ 'item search': impl('full', 'itemSearch', {
30
+ note: '--type video / image 走媒体搜索,按类型过滤,不能和 --sort latest 一起用',
31
+ options: { sort: filter.sort('general', 'latest'), type: filter.type('all', 'video', 'image') },
32
+ }),
33
+ 'item media': impl('full', 'itemMedia'),
34
+ 'item download': impl('full', 'itemDownload'),
35
+ 'item like': impl('full', 'itemLike'),
36
+ 'item unlike': impl('full', 'itemUnlike'),
37
+ 'item collect': impl('full', 'itemCollect'),
38
+ 'item uncollect': impl('full', 'itemUncollect'),
39
+ 'item repost': impl('full', 'itemRepost'),
40
+ 'item unrepost': impl('full', 'itemUnrepost'),
41
+ 'item publish': impl('full', 'itemPublish', {
42
+ note: '正文超过 280 权重时自动按长推发(需要 Premium);--thread 发 thread',
43
+ supports: ['text', 'image', 'video', 'visibility'],
44
+ options: {
45
+ visibility: visibility('public'),
46
+ quote: QUOTE,
47
+ thread: z.array(z.string()).optional().describe('thread 的后续各条,依次回复上一条'),
48
+ },
49
+ }),
50
+ 'item delete': impl('full', 'itemDelete'),
51
+ 'comment list': impl('full', 'commentList'),
52
+ 'comment replies': 'none',
53
+ 'comment add': impl('full', 'commentAdd'),
54
+ 'comment delete': impl('full', 'commentDelete'),
55
+ 'comment like': impl('full', 'commentLike'),
56
+ 'comment unlike': impl('full', 'commentUnlike'),
57
+ 'feed list': impl('partial', 'feedList', {
58
+ note: 'following 规划中',
59
+ options: { kind: filter.kind('recommend', 'following') },
60
+ }),
61
+ 'keyword suggest': 'none',
62
+ 'keyword hot': 'none',
63
+ 'notice list': 'none',
64
+ 'notice count': 'none',
65
+ 'msg list': impl('partial', 'msgList', { note: '只取收件箱首页(最近 20 个会话),上游没有翻页' }),
66
+ 'msg history': impl('partial', 'msgHistory', { note: '消息端到端加密,只给出占位消息' }),
67
+ // 上游的 send_message 只是占位:X Chat 端到端加密,要先在网页设 passcode、再逆向加密后的发送请求
68
+ 'msg send': 'none',
69
+ 'msg listen': 'none',
70
+ 'msg read': 'none',
71
+ 'msg revoke': 'none',
72
+ 'msg delete': 'none',
73
+ 'media upload': impl('full', 'mediaUpload'),
74
+ 'folder list': 'none',
75
+ 'folder items': 'none',
76
+ 'folder create': 'none',
77
+ 'folder update': 'none',
78
+ 'folder delete': 'none',
79
+ // 平台扩展(AGENTS 4.7)
80
+ 'article publish': {
81
+ upstream: 'full',
82
+ summary: '发文章(Premium 长文):建草稿,写标题、正文、封面,再发布',
83
+ args: [],
84
+ options: {
85
+ title: z.string().optional().describe('标题,不给时取正文第一行的 # 标题'),
86
+ text: z.string().describe('正文(Markdown),@file 表示从文件读取;独占一行的 ![](图片) 会上传成插图,相对路径按该文件所在目录解析'),
87
+ cover: PUBLISH.cover,
88
+ },
89
+ auth: 'required',
90
+ output: '{id url}',
91
+ handler: h('articlePublish'),
92
+ },
93
+ 'article delete': {
94
+ upstream: 'full',
95
+ summary: '删文章(草稿或已发布的)',
96
+ args: [{ name: 'article', summary: '文章:ID 或 URL' }],
97
+ auth: 'required',
98
+ confirm: true,
99
+ output: '{id}',
100
+ handler: h('articleDelete'),
101
+ },
102
+ },
103
+ },
104
+ app: 'planned',
105
+ pc: 'planned',
106
+ },
107
+ });