@jackwener/opencli 1.4.0 → 1.5.0

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 (514) hide show
  1. package/.github/actions/setup-chrome/action.yml +5 -4
  2. package/.github/workflows/build-extension.yml +2 -6
  3. package/.github/workflows/ci.yml +37 -3
  4. package/.github/workflows/e2e-headed.yml +16 -3
  5. package/CHANGELOG.md +23 -0
  6. package/PRIVACY.md +57 -0
  7. package/README.md +36 -7
  8. package/README.zh-CN.md +13 -6
  9. package/SKILL.md +103 -2
  10. package/dist/browser/cdp.d.ts +2 -1
  11. package/dist/browser/discover.d.ts +4 -1
  12. package/dist/browser/discover.js +6 -2
  13. package/dist/browser/errors.d.ts +2 -2
  14. package/dist/browser/errors.js +4 -12
  15. package/dist/browser/mcp.d.ts +2 -1
  16. package/dist/build-manifest.d.ts +2 -0
  17. package/dist/build-manifest.js +39 -14
  18. package/dist/build-manifest.test.js +21 -0
  19. package/dist/capabilityRouting.d.ts +2 -0
  20. package/dist/capabilityRouting.js +2 -1
  21. package/dist/cli-manifest.json +1838 -151
  22. package/dist/cli.js +34 -3
  23. package/dist/clis/36kr/article.d.ts +1 -0
  24. package/dist/clis/36kr/article.js +62 -0
  25. package/dist/clis/36kr/hot.d.ts +3 -0
  26. package/dist/clis/36kr/hot.js +80 -0
  27. package/dist/clis/36kr/hot.test.d.ts +1 -0
  28. package/dist/clis/36kr/hot.test.js +15 -0
  29. package/dist/clis/36kr/news.d.ts +1 -0
  30. package/dist/clis/36kr/news.js +51 -0
  31. package/dist/clis/36kr/news.test.d.ts +1 -0
  32. package/dist/clis/36kr/news.test.js +85 -0
  33. package/dist/clis/36kr/search.d.ts +1 -0
  34. package/dist/clis/36kr/search.js +72 -0
  35. package/dist/clis/apple-podcasts/search.js +2 -1
  36. package/dist/clis/arxiv/search.js +2 -2
  37. package/dist/clis/bbc/news.js +0 -1
  38. package/dist/clis/bilibili/comments.d.ts +5 -0
  39. package/dist/clis/bilibili/comments.js +40 -0
  40. package/dist/clis/bilibili/comments.test.d.ts +1 -0
  41. package/dist/clis/bilibili/comments.test.js +82 -0
  42. package/dist/clis/chatgpt/ask.js +29 -14
  43. package/dist/clis/chatgpt/ax.d.ts +6 -0
  44. package/dist/clis/chatgpt/ax.js +172 -1
  45. package/dist/clis/chatgpt/model.d.ts +1 -0
  46. package/dist/clis/chatgpt/model.js +24 -0
  47. package/dist/clis/chatgpt/send.js +12 -3
  48. package/dist/clis/ctrip/search.js +0 -1
  49. package/dist/clis/douban/download.d.ts +1 -0
  50. package/dist/clis/douban/download.js +67 -0
  51. package/dist/clis/douban/download.test.d.ts +1 -0
  52. package/dist/clis/douban/download.test.js +170 -0
  53. package/dist/clis/douban/photos.d.ts +1 -0
  54. package/dist/clis/douban/photos.js +34 -0
  55. package/dist/clis/douban/utils.d.ts +25 -0
  56. package/dist/clis/douban/utils.js +190 -1
  57. package/dist/clis/douban/utils.test.d.ts +1 -0
  58. package/dist/clis/douban/utils.test.js +64 -0
  59. package/dist/clis/douyin/_shared/browser-fetch.d.ts +10 -0
  60. package/dist/clis/douyin/_shared/browser-fetch.js +30 -0
  61. package/dist/clis/douyin/_shared/browser-fetch.test.d.ts +1 -0
  62. package/dist/clis/douyin/_shared/browser-fetch.test.js +31 -0
  63. package/dist/clis/douyin/_shared/creation-id.d.ts +1 -0
  64. package/dist/clis/douyin/_shared/creation-id.js +5 -0
  65. package/dist/clis/douyin/_shared/creation-id.test.d.ts +1 -0
  66. package/dist/clis/douyin/_shared/creation-id.test.js +22 -0
  67. package/dist/clis/douyin/_shared/imagex-upload.d.ts +20 -0
  68. package/dist/clis/douyin/_shared/imagex-upload.js +53 -0
  69. package/dist/clis/douyin/_shared/imagex-upload.test.d.ts +1 -0
  70. package/dist/clis/douyin/_shared/imagex-upload.test.js +87 -0
  71. package/dist/clis/douyin/_shared/sts2.d.ts +8 -0
  72. package/dist/clis/douyin/_shared/sts2.js +15 -0
  73. package/dist/clis/douyin/_shared/text-extra.d.ts +18 -0
  74. package/dist/clis/douyin/_shared/text-extra.js +15 -0
  75. package/dist/clis/douyin/_shared/text-extra.test.d.ts +1 -0
  76. package/dist/clis/douyin/_shared/text-extra.test.js +37 -0
  77. package/dist/clis/douyin/_shared/timing.d.ts +2 -0
  78. package/dist/clis/douyin/_shared/timing.js +22 -0
  79. package/dist/clis/douyin/_shared/timing.test.d.ts +1 -0
  80. package/dist/clis/douyin/_shared/timing.test.js +28 -0
  81. package/dist/clis/douyin/_shared/tos-upload-short-read.test.d.ts +11 -0
  82. package/dist/clis/douyin/_shared/tos-upload-short-read.test.js +83 -0
  83. package/dist/clis/douyin/_shared/tos-upload.d.ts +53 -0
  84. package/dist/clis/douyin/_shared/tos-upload.js +295 -0
  85. package/dist/clis/douyin/_shared/tos-upload.test.d.ts +1 -0
  86. package/dist/clis/douyin/_shared/tos-upload.test.js +229 -0
  87. package/dist/clis/douyin/_shared/transcode.d.ts +27 -0
  88. package/dist/clis/douyin/_shared/transcode.js +45 -0
  89. package/dist/clis/douyin/_shared/transcode.test.d.ts +1 -0
  90. package/dist/clis/douyin/_shared/transcode.test.js +93 -0
  91. package/dist/clis/douyin/_shared/types.d.ts +26 -0
  92. package/dist/clis/douyin/_shared/types.js +1 -0
  93. package/dist/clis/douyin/activities.d.ts +1 -0
  94. package/dist/clis/douyin/activities.js +20 -0
  95. package/dist/clis/douyin/activities.test.d.ts +1 -0
  96. package/dist/clis/douyin/activities.test.js +22 -0
  97. package/dist/clis/douyin/collections.d.ts +1 -0
  98. package/dist/clis/douyin/collections.js +22 -0
  99. package/dist/clis/douyin/collections.test.d.ts +1 -0
  100. package/dist/clis/douyin/collections.test.js +23 -0
  101. package/dist/clis/douyin/delete.d.ts +1 -0
  102. package/dist/clis/douyin/delete.js +18 -0
  103. package/dist/clis/douyin/delete.test.d.ts +1 -0
  104. package/dist/clis/douyin/delete.test.js +11 -0
  105. package/dist/clis/douyin/draft.d.ts +14 -0
  106. package/dist/clis/douyin/draft.js +237 -0
  107. package/dist/clis/douyin/draft.test.d.ts +1 -0
  108. package/dist/clis/douyin/draft.test.js +11 -0
  109. package/dist/clis/douyin/drafts.d.ts +1 -0
  110. package/dist/clis/douyin/drafts.js +23 -0
  111. package/dist/clis/douyin/drafts.test.d.ts +1 -0
  112. package/dist/clis/douyin/drafts.test.js +11 -0
  113. package/dist/clis/douyin/hashtag.d.ts +1 -0
  114. package/dist/clis/douyin/hashtag.js +45 -0
  115. package/dist/clis/douyin/hashtag.test.d.ts +1 -0
  116. package/dist/clis/douyin/hashtag.test.js +25 -0
  117. package/dist/clis/douyin/location.d.ts +1 -0
  118. package/dist/clis/douyin/location.js +24 -0
  119. package/dist/clis/douyin/location.test.d.ts +1 -0
  120. package/dist/clis/douyin/location.test.js +23 -0
  121. package/dist/clis/douyin/profile.d.ts +1 -0
  122. package/dist/clis/douyin/profile.js +28 -0
  123. package/dist/clis/douyin/profile.test.d.ts +1 -0
  124. package/dist/clis/douyin/profile.test.js +11 -0
  125. package/dist/clis/douyin/publish.d.ts +14 -0
  126. package/dist/clis/douyin/publish.js +288 -0
  127. package/dist/clis/douyin/publish.test.d.ts +1 -0
  128. package/dist/clis/douyin/publish.test.js +38 -0
  129. package/dist/clis/douyin/stats.d.ts +1 -0
  130. package/dist/clis/douyin/stats.js +27 -0
  131. package/dist/clis/douyin/stats.test.d.ts +1 -0
  132. package/dist/clis/douyin/stats.test.js +22 -0
  133. package/dist/clis/douyin/update.d.ts +1 -0
  134. package/dist/clis/douyin/update.js +31 -0
  135. package/dist/clis/douyin/update.test.d.ts +1 -0
  136. package/dist/clis/douyin/update.test.js +11 -0
  137. package/dist/clis/douyin/videos.d.ts +1 -0
  138. package/dist/clis/douyin/videos.js +34 -0
  139. package/dist/clis/douyin/videos.test.d.ts +1 -0
  140. package/dist/clis/douyin/videos.test.js +11 -0
  141. package/dist/clis/hackernews/search.yaml +1 -1
  142. package/dist/clis/imdb/person.d.ts +1 -0
  143. package/dist/clis/imdb/person.js +203 -0
  144. package/dist/clis/imdb/reviews.d.ts +1 -0
  145. package/dist/clis/imdb/reviews.js +88 -0
  146. package/dist/clis/imdb/search.d.ts +1 -0
  147. package/dist/clis/imdb/search.js +161 -0
  148. package/dist/clis/imdb/title.d.ts +1 -0
  149. package/dist/clis/imdb/title.js +93 -0
  150. package/dist/clis/imdb/top.d.ts +1 -0
  151. package/dist/clis/imdb/top.js +53 -0
  152. package/dist/clis/imdb/trending.d.ts +1 -0
  153. package/dist/clis/imdb/trending.js +52 -0
  154. package/dist/clis/imdb/utils.d.ts +46 -0
  155. package/dist/clis/imdb/utils.js +285 -0
  156. package/dist/clis/imdb/utils.test.d.ts +1 -0
  157. package/dist/clis/imdb/utils.test.js +88 -0
  158. package/dist/clis/instagram/search.yaml +2 -1
  159. package/dist/clis/jd/item.d.ts +4 -0
  160. package/dist/clis/jd/item.js +16 -15
  161. package/dist/clis/jd/item.test.js +16 -1
  162. package/dist/clis/linux-do/categories.yaml +38 -9
  163. package/dist/clis/linux-do/category.d.ts +1 -0
  164. package/dist/clis/linux-do/category.js +36 -0
  165. package/dist/clis/linux-do/feed.d.ts +45 -0
  166. package/dist/clis/linux-do/feed.js +397 -0
  167. package/dist/clis/linux-do/feed.test.d.ts +1 -0
  168. package/dist/clis/linux-do/feed.test.js +118 -0
  169. package/dist/clis/linux-do/hot.d.ts +1 -0
  170. package/dist/clis/linux-do/hot.js +25 -0
  171. package/dist/clis/linux-do/latest.d.ts +1 -0
  172. package/dist/clis/linux-do/latest.js +18 -0
  173. package/dist/clis/linux-do/search.yaml +3 -1
  174. package/dist/clis/linux-do/tags.yaml +41 -0
  175. package/dist/clis/linux-do/topic.yaml +41 -3
  176. package/dist/clis/linux-do/user-posts.yaml +67 -0
  177. package/dist/clis/linux-do/user-topics.yaml +54 -0
  178. package/dist/clis/medium/search.js +1 -1
  179. package/dist/clis/paperreview/commands.test.d.ts +3 -0
  180. package/dist/clis/paperreview/commands.test.js +243 -0
  181. package/dist/clis/paperreview/feedback.d.ts +1 -0
  182. package/dist/clis/paperreview/feedback.js +52 -0
  183. package/dist/clis/paperreview/review.d.ts +1 -0
  184. package/dist/clis/paperreview/review.js +37 -0
  185. package/dist/clis/paperreview/submit.d.ts +1 -0
  186. package/dist/clis/paperreview/submit.js +85 -0
  187. package/dist/clis/paperreview/utils.d.ts +46 -0
  188. package/dist/clis/paperreview/utils.js +197 -0
  189. package/dist/clis/paperreview/utils.test.d.ts +1 -0
  190. package/dist/clis/paperreview/utils.test.js +49 -0
  191. package/dist/clis/producthunt/browse.d.ts +1 -0
  192. package/dist/clis/producthunt/browse.js +99 -0
  193. package/dist/clis/producthunt/hot.d.ts +1 -0
  194. package/dist/clis/producthunt/hot.js +110 -0
  195. package/dist/clis/producthunt/posts.d.ts +1 -0
  196. package/dist/clis/producthunt/posts.js +28 -0
  197. package/dist/clis/producthunt/today.d.ts +1 -0
  198. package/dist/clis/producthunt/today.js +35 -0
  199. package/dist/clis/producthunt/utils.d.ts +29 -0
  200. package/dist/clis/producthunt/utils.js +99 -0
  201. package/dist/clis/producthunt/utils.test.d.ts +1 -0
  202. package/dist/clis/producthunt/utils.test.js +64 -0
  203. package/dist/clis/reuters/search.js +0 -1
  204. package/dist/clis/twitter/article.js +4 -28
  205. package/dist/clis/twitter/likes.d.ts +24 -0
  206. package/dist/clis/twitter/likes.js +217 -0
  207. package/dist/clis/twitter/likes.test.d.ts +1 -0
  208. package/dist/clis/twitter/likes.test.js +85 -0
  209. package/dist/clis/twitter/profile.js +4 -28
  210. package/dist/clis/twitter/search.js +7 -4
  211. package/dist/clis/twitter/search.test.js +56 -2
  212. package/dist/clis/twitter/shared.d.ts +6 -0
  213. package/dist/clis/twitter/shared.js +35 -0
  214. package/dist/clis/twitter/timeline.js +2 -13
  215. package/dist/clis/weibo/comments.d.ts +1 -0
  216. package/dist/clis/weibo/comments.js +53 -0
  217. package/dist/clis/weibo/feed.d.ts +1 -0
  218. package/dist/clis/weibo/feed.js +56 -0
  219. package/dist/clis/weibo/hot.js +0 -1
  220. package/dist/clis/weibo/me.d.ts +1 -0
  221. package/dist/clis/weibo/me.js +76 -0
  222. package/dist/clis/weibo/post.d.ts +1 -0
  223. package/dist/clis/weibo/post.js +75 -0
  224. package/dist/clis/weibo/user.d.ts +1 -0
  225. package/dist/clis/weibo/user.js +63 -0
  226. package/dist/clis/weibo/utils.d.ts +6 -0
  227. package/dist/clis/weibo/utils.js +30 -0
  228. package/dist/clis/weixin/download.d.ts +17 -0
  229. package/dist/clis/weixin/download.js +88 -20
  230. package/dist/clis/weread/book.js +2 -2
  231. package/dist/clis/weread/commands.test.d.ts +3 -0
  232. package/dist/clis/weread/commands.test.js +43 -0
  233. package/dist/clis/weread/highlights.js +2 -2
  234. package/dist/clis/weread/notebooks.js +2 -2
  235. package/dist/clis/weread/notes.js +3 -3
  236. package/dist/clis/weread/search.js +3 -2
  237. package/dist/clis/weread/shelf.js +2 -2
  238. package/dist/clis/weread/utils.d.ts +4 -4
  239. package/dist/clis/weread/utils.js +32 -14
  240. package/dist/clis/weread/utils.test.js +1 -28
  241. package/dist/clis/xiaohongshu/comments.d.ts +5 -0
  242. package/dist/clis/xiaohongshu/comments.js +74 -0
  243. package/dist/clis/xiaohongshu/comments.test.d.ts +1 -0
  244. package/dist/clis/xiaohongshu/comments.test.js +79 -0
  245. package/dist/clis/xiaohongshu/publish.js +114 -18
  246. package/dist/clis/xiaohongshu/publish.test.d.ts +1 -0
  247. package/dist/clis/xiaohongshu/publish.test.js +119 -0
  248. package/dist/clis/xueqiu/search.yaml +2 -1
  249. package/dist/clis/yahoo-finance/quote.js +0 -1
  250. package/dist/clis/youtube/channel.d.ts +1 -0
  251. package/dist/clis/youtube/channel.js +150 -0
  252. package/dist/clis/youtube/comments.d.ts +1 -0
  253. package/dist/clis/youtube/comments.js +95 -0
  254. package/dist/clis/youtube/search.js +0 -1
  255. package/dist/clis/zhihu/search.yaml +2 -1
  256. package/dist/commanderAdapter.d.ts +1 -0
  257. package/dist/commanderAdapter.js +176 -29
  258. package/dist/commanderAdapter.test.d.ts +1 -0
  259. package/dist/commanderAdapter.test.js +62 -0
  260. package/dist/daemon.js +17 -1
  261. package/dist/discovery.js +8 -14
  262. package/dist/doctor.d.ts +1 -0
  263. package/dist/doctor.js +9 -2
  264. package/dist/download/index.js +63 -51
  265. package/dist/download/index.test.js +17 -4
  266. package/dist/errors.d.ts +3 -1
  267. package/dist/errors.js +15 -32
  268. package/dist/execution.d.ts +1 -3
  269. package/dist/execution.js +21 -1
  270. package/dist/external-clis.yaml +0 -17
  271. package/dist/hooks.js +2 -0
  272. package/dist/main.js +5 -0
  273. package/dist/output.js +5 -1
  274. package/dist/pipeline/executor.js +3 -4
  275. package/dist/plugin-manifest.d.ts +70 -0
  276. package/dist/plugin-manifest.js +160 -0
  277. package/dist/plugin-manifest.test.d.ts +4 -0
  278. package/dist/plugin-manifest.test.js +179 -0
  279. package/dist/plugin.d.ts +38 -5
  280. package/dist/plugin.js +267 -33
  281. package/dist/plugin.test.js +220 -3
  282. package/dist/registry.d.ts +4 -0
  283. package/dist/registry.js +2 -0
  284. package/dist/runtime-detect.d.ts +21 -0
  285. package/dist/runtime-detect.js +32 -0
  286. package/dist/runtime-detect.test.d.ts +1 -0
  287. package/dist/runtime-detect.test.js +27 -0
  288. package/dist/runtime.js +1 -1
  289. package/dist/serialization.d.ts +2 -0
  290. package/dist/serialization.js +6 -0
  291. package/dist/types.d.ts +1 -0
  292. package/dist/update-check.d.ts +22 -0
  293. package/dist/update-check.js +112 -0
  294. package/dist/weixin-download.test.d.ts +1 -0
  295. package/dist/weixin-download.test.js +30 -0
  296. package/dist/weread-private-api-regression.test.d.ts +1 -0
  297. package/dist/weread-private-api-regression.test.js +122 -0
  298. package/dist/weread-search-regression.test.d.ts +1 -0
  299. package/dist/weread-search-regression.test.js +39 -0
  300. package/dist/yaml-schema.d.ts +3 -0
  301. package/dist/yaml-schema.js +18 -1
  302. package/docs/.vitepress/config.mts +17 -0
  303. package/docs/adapters/browser/36kr.md +47 -0
  304. package/docs/adapters/browser/douban.md +14 -0
  305. package/docs/adapters/browser/douyin.md +75 -0
  306. package/docs/adapters/browser/imdb.md +47 -0
  307. package/docs/adapters/browser/jd.md +2 -2
  308. package/docs/adapters/browser/linux-do.md +181 -20
  309. package/docs/adapters/browser/paperreview.md +43 -0
  310. package/docs/adapters/browser/producthunt.md +49 -0
  311. package/docs/adapters/browser/twitter.md +6 -0
  312. package/docs/adapters/desktop/chatgpt.md +5 -0
  313. package/docs/adapters/index.md +12 -3
  314. package/docs/advanced/download.md +4 -0
  315. package/docs/advanced/rate-limiter-plugin.md +99 -0
  316. package/docs/guide/electron-app-cli.md +200 -0
  317. package/docs/guide/getting-started.md +1 -0
  318. package/docs/guide/plugins.md +87 -0
  319. package/docs/zh/guide/electron-app-cli.md +188 -0
  320. package/docs/zh/guide/getting-started.md +1 -0
  321. package/docs/zh/guide/plugins.md +65 -0
  322. package/extension/dist/background.js +508 -518
  323. package/extension/manifest.json +6 -2
  324. package/extension/package.json +2 -1
  325. package/extension/popup.html +84 -0
  326. package/extension/popup.js +25 -0
  327. package/extension/scripts/package-release.mjs +179 -0
  328. package/extension/src/background.ts +22 -1
  329. package/package.json +4 -1
  330. package/scripts/postinstall.js +10 -0
  331. package/src/browser/cdp.ts +2 -1
  332. package/src/browser/discover.ts +8 -3
  333. package/src/browser/errors.ts +13 -14
  334. package/src/browser/mcp.ts +2 -1
  335. package/src/build-manifest.test.ts +23 -0
  336. package/src/build-manifest.ts +40 -15
  337. package/src/capabilityRouting.ts +2 -1
  338. package/src/cli.ts +35 -3
  339. package/src/clis/36kr/article.ts +69 -0
  340. package/src/clis/36kr/hot.test.ts +19 -0
  341. package/src/clis/36kr/hot.ts +100 -0
  342. package/src/clis/36kr/news.test.ts +90 -0
  343. package/src/clis/36kr/news.ts +54 -0
  344. package/src/clis/36kr/search.ts +78 -0
  345. package/src/clis/apple-podcasts/search.ts +2 -1
  346. package/src/clis/arxiv/search.ts +2 -2
  347. package/src/clis/bbc/news.ts +0 -1
  348. package/src/clis/bilibili/comments.test.ts +102 -0
  349. package/src/clis/bilibili/comments.ts +44 -0
  350. package/src/clis/chatgpt/ask.ts +28 -14
  351. package/src/clis/chatgpt/ax.ts +180 -1
  352. package/src/clis/chatgpt/model.ts +27 -0
  353. package/src/clis/chatgpt/send.ts +16 -6
  354. package/src/clis/ctrip/search.ts +0 -1
  355. package/src/clis/douban/download.test.ts +196 -0
  356. package/src/clis/douban/download.ts +78 -0
  357. package/src/clis/douban/photos.ts +36 -0
  358. package/src/clis/douban/utils.test.ts +97 -0
  359. package/src/clis/douban/utils.ts +232 -1
  360. package/src/clis/douyin/_shared/browser-fetch.test.ts +38 -0
  361. package/src/clis/douyin/_shared/browser-fetch.ts +45 -0
  362. package/src/clis/douyin/_shared/creation-id.test.ts +26 -0
  363. package/src/clis/douyin/_shared/creation-id.ts +8 -0
  364. package/src/clis/douyin/_shared/imagex-upload.test.ts +113 -0
  365. package/src/clis/douyin/_shared/imagex-upload.ts +76 -0
  366. package/src/clis/douyin/_shared/sts2.ts +20 -0
  367. package/src/clis/douyin/_shared/text-extra.test.ts +42 -0
  368. package/src/clis/douyin/_shared/text-extra.ts +33 -0
  369. package/src/clis/douyin/_shared/timing.test.ts +38 -0
  370. package/src/clis/douyin/_shared/timing.ts +22 -0
  371. package/src/clis/douyin/_shared/tos-upload-short-read.test.ts +102 -0
  372. package/src/clis/douyin/_shared/tos-upload.test.ts +281 -0
  373. package/src/clis/douyin/_shared/tos-upload.ts +444 -0
  374. package/src/clis/douyin/_shared/transcode.test.ts +117 -0
  375. package/src/clis/douyin/_shared/transcode.ts +78 -0
  376. package/src/clis/douyin/_shared/types.ts +29 -0
  377. package/src/clis/douyin/activities.test.ts +25 -0
  378. package/src/clis/douyin/activities.ts +23 -0
  379. package/src/clis/douyin/collections.test.ts +26 -0
  380. package/src/clis/douyin/collections.ts +25 -0
  381. package/src/clis/douyin/delete.test.ts +12 -0
  382. package/src/clis/douyin/delete.ts +20 -0
  383. package/src/clis/douyin/draft.test.ts +12 -0
  384. package/src/clis/douyin/draft.ts +282 -0
  385. package/src/clis/douyin/drafts.test.ts +12 -0
  386. package/src/clis/douyin/drafts.ts +27 -0
  387. package/src/clis/douyin/hashtag.test.ts +28 -0
  388. package/src/clis/douyin/hashtag.ts +56 -0
  389. package/src/clis/douyin/location.test.ts +26 -0
  390. package/src/clis/douyin/location.ts +27 -0
  391. package/src/clis/douyin/profile.test.ts +12 -0
  392. package/src/clis/douyin/profile.ts +37 -0
  393. package/src/clis/douyin/publish.test.ts +45 -0
  394. package/src/clis/douyin/publish.ts +340 -0
  395. package/src/clis/douyin/stats.test.ts +25 -0
  396. package/src/clis/douyin/stats.ts +30 -0
  397. package/src/clis/douyin/update.test.ts +12 -0
  398. package/src/clis/douyin/update.ts +43 -0
  399. package/src/clis/douyin/videos.test.ts +12 -0
  400. package/src/clis/douyin/videos.ts +49 -0
  401. package/src/clis/hackernews/search.yaml +1 -1
  402. package/src/clis/imdb/person.ts +232 -0
  403. package/src/clis/imdb/reviews.ts +111 -0
  404. package/src/clis/imdb/search.ts +179 -0
  405. package/src/clis/imdb/title.ts +121 -0
  406. package/src/clis/imdb/top.ts +67 -0
  407. package/src/clis/imdb/trending.ts +66 -0
  408. package/src/clis/imdb/utils.test.ts +117 -0
  409. package/src/clis/imdb/utils.ts +305 -0
  410. package/src/clis/instagram/search.yaml +2 -1
  411. package/src/clis/jd/item.test.ts +18 -1
  412. package/src/clis/jd/item.ts +18 -15
  413. package/src/clis/linux-do/categories.yaml +38 -9
  414. package/src/clis/linux-do/category.ts +37 -0
  415. package/src/clis/linux-do/feed.test.ts +132 -0
  416. package/src/clis/linux-do/feed.ts +501 -0
  417. package/src/clis/linux-do/hot.ts +26 -0
  418. package/src/clis/linux-do/latest.ts +19 -0
  419. package/src/clis/linux-do/search.yaml +3 -1
  420. package/src/clis/linux-do/tags.yaml +41 -0
  421. package/src/clis/linux-do/topic.yaml +41 -3
  422. package/src/clis/linux-do/user-posts.yaml +67 -0
  423. package/src/clis/linux-do/user-topics.yaml +54 -0
  424. package/src/clis/medium/search.ts +1 -1
  425. package/src/clis/paperreview/commands.test.ts +283 -0
  426. package/src/clis/paperreview/feedback.ts +64 -0
  427. package/src/clis/paperreview/review.ts +47 -0
  428. package/src/clis/paperreview/submit.ts +119 -0
  429. package/src/clis/paperreview/utils.test.ts +68 -0
  430. package/src/clis/paperreview/utils.ts +276 -0
  431. package/src/clis/producthunt/browse.ts +109 -0
  432. package/src/clis/producthunt/hot.ts +127 -0
  433. package/src/clis/producthunt/posts.ts +29 -0
  434. package/src/clis/producthunt/today.ts +37 -0
  435. package/src/clis/producthunt/utils.test.ts +72 -0
  436. package/src/clis/producthunt/utils.ts +122 -0
  437. package/src/clis/reuters/search.ts +0 -1
  438. package/src/clis/twitter/article.ts +5 -28
  439. package/src/clis/twitter/likes.test.ts +91 -0
  440. package/src/clis/twitter/likes.ts +256 -0
  441. package/src/clis/twitter/profile.ts +5 -28
  442. package/src/clis/twitter/search.test.ts +71 -2
  443. package/src/clis/twitter/search.ts +8 -4
  444. package/src/clis/twitter/shared.ts +45 -0
  445. package/src/clis/twitter/timeline.ts +2 -13
  446. package/src/clis/weibo/comments.ts +54 -0
  447. package/src/clis/weibo/feed.ts +57 -0
  448. package/src/clis/weibo/hot.ts +0 -1
  449. package/src/clis/weibo/me.ts +77 -0
  450. package/src/clis/weibo/post.ts +77 -0
  451. package/src/clis/weibo/user.ts +64 -0
  452. package/src/clis/weibo/utils.ts +32 -0
  453. package/src/clis/weixin/download.ts +114 -20
  454. package/src/clis/weread/book.ts +2 -2
  455. package/src/clis/weread/commands.test.ts +57 -0
  456. package/src/clis/weread/highlights.ts +2 -2
  457. package/src/clis/weread/notebooks.ts +2 -2
  458. package/src/clis/weread/notes.ts +3 -3
  459. package/src/clis/weread/search.ts +3 -2
  460. package/src/clis/weread/shelf.ts +2 -2
  461. package/src/clis/weread/utils.test.ts +1 -32
  462. package/src/clis/weread/utils.ts +41 -16
  463. package/src/clis/xiaohongshu/comments.test.ts +96 -0
  464. package/src/clis/xiaohongshu/comments.ts +81 -0
  465. package/src/clis/xiaohongshu/publish.test.ts +137 -0
  466. package/src/clis/xiaohongshu/publish.ts +129 -18
  467. package/src/clis/xueqiu/search.yaml +2 -1
  468. package/src/clis/yahoo-finance/quote.ts +0 -1
  469. package/src/clis/youtube/channel.ts +155 -0
  470. package/src/clis/youtube/comments.ts +97 -0
  471. package/src/clis/youtube/search.ts +0 -1
  472. package/src/clis/zhihu/search.yaml +2 -1
  473. package/src/commanderAdapter.test.ts +78 -0
  474. package/src/commanderAdapter.ts +188 -24
  475. package/src/daemon.ts +19 -1
  476. package/src/discovery.ts +8 -15
  477. package/src/doctor.ts +13 -2
  478. package/src/download/index.test.ts +14 -4
  479. package/src/download/index.ts +67 -55
  480. package/src/errors.ts +25 -66
  481. package/src/execution.ts +28 -3
  482. package/src/external-clis.yaml +0 -17
  483. package/src/hooks.ts +1 -0
  484. package/src/main.ts +6 -0
  485. package/src/output.ts +3 -1
  486. package/src/pipeline/executor.ts +4 -6
  487. package/src/plugin-manifest.test.ts +223 -0
  488. package/src/plugin-manifest.ts +206 -0
  489. package/src/plugin.test.ts +246 -2
  490. package/src/plugin.ts +338 -36
  491. package/src/registry.ts +6 -1
  492. package/src/runtime-detect.test.ts +30 -0
  493. package/src/runtime-detect.ts +36 -0
  494. package/src/runtime.ts +1 -1
  495. package/src/serialization.ts +4 -0
  496. package/src/types.ts +1 -0
  497. package/src/update-check.ts +114 -0
  498. package/src/weixin-download.test.ts +64 -0
  499. package/src/weread-private-api-regression.test.ts +150 -0
  500. package/src/weread-search-regression.test.ts +44 -0
  501. package/src/yaml-schema.ts +20 -0
  502. package/tests/e2e/browser-auth.test.ts +13 -9
  503. package/tests/e2e/browser-public-extended.test.ts +162 -0
  504. package/tests/e2e/browser-public.test.ts +55 -136
  505. package/tests/e2e/helpers.ts +2 -1
  506. package/tests/e2e/public-commands.test.ts +37 -3
  507. package/tests/smoke/api-health.test.ts +1 -1
  508. package/vitest.config.ts +34 -17
  509. package/dist/clis/linux-do/category.yaml +0 -51
  510. package/dist/clis/linux-do/hot.yaml +0 -50
  511. package/dist/clis/linux-do/latest.yaml +0 -40
  512. package/src/clis/linux-do/category.yaml +0 -51
  513. package/src/clis/linux-do/hot.yaml +0 -50
  514. package/src/clis/linux-do/latest.yaml +0 -40
@@ -39,6 +39,7 @@ describe('twitter search command', () => {
39
39
  legacy: {
40
40
  full_text: 'hello world',
41
41
  favorite_count: 7,
42
+ created_at: 'Thu Mar 26 10:30:00 +0000 2026',
42
43
  },
43
44
  core: {
44
45
  user_results: {
@@ -68,13 +69,14 @@ describe('twitter search command', () => {
68
69
  ]),
69
70
  };
70
71
 
71
- const result = await command!.func!(page as any, { query: 'from:alice', limit: 5 });
72
+ const result = await command!.func!(page as any, { query: 'from:alice', filter: 'top', limit: 5 });
72
73
 
73
74
  expect(result).toEqual([
74
75
  {
75
76
  id: '1',
76
77
  author: 'alice',
77
78
  text: 'hello world',
79
+ created_at: 'Thu Mar 26 10:30:00 +0000 2026',
78
80
  likes: 7,
79
81
  views: '12',
80
82
  url: 'https://x.com/i/status/1',
@@ -84,6 +86,73 @@ describe('twitter search command', () => {
84
86
  expect(evaluate).toHaveBeenCalledTimes(4);
85
87
  });
86
88
 
89
+ it('uses f=live in search URL when filter is live', async () => {
90
+ const command = getRegistry().get('twitter/search');
91
+
92
+ const evaluate = vi.fn()
93
+ .mockResolvedValueOnce(undefined)
94
+ .mockResolvedValueOnce('/search');
95
+
96
+ const page = {
97
+ goto: vi.fn().mockResolvedValue(undefined),
98
+ wait: vi.fn().mockResolvedValue(undefined),
99
+ installInterceptor: vi.fn().mockResolvedValue(undefined),
100
+ evaluate,
101
+ autoScroll: vi.fn().mockResolvedValue(undefined),
102
+ getInterceptedRequests: vi.fn().mockResolvedValue([]),
103
+ };
104
+
105
+ await command!.func!(page as any, { query: 'breaking news', filter: 'live', limit: 5 });
106
+
107
+ const pushStateCall = evaluate.mock.calls[0][0] as string;
108
+ expect(pushStateCall).toContain('f=live');
109
+ expect(pushStateCall).toContain(encodeURIComponent('breaking news'));
110
+ });
111
+
112
+ it('uses f=top in search URL when filter is top', async () => {
113
+ const command = getRegistry().get('twitter/search');
114
+
115
+ const evaluate = vi.fn()
116
+ .mockResolvedValueOnce(undefined)
117
+ .mockResolvedValueOnce('/search');
118
+
119
+ const page = {
120
+ goto: vi.fn().mockResolvedValue(undefined),
121
+ wait: vi.fn().mockResolvedValue(undefined),
122
+ installInterceptor: vi.fn().mockResolvedValue(undefined),
123
+ evaluate,
124
+ autoScroll: vi.fn().mockResolvedValue(undefined),
125
+ getInterceptedRequests: vi.fn().mockResolvedValue([]),
126
+ };
127
+
128
+ await command!.func!(page as any, { query: 'test', filter: 'top', limit: 5 });
129
+
130
+ const pushStateCall = evaluate.mock.calls[0][0] as string;
131
+ expect(pushStateCall).toContain('f=top');
132
+ });
133
+
134
+ it('falls back to top when filter is omitted', async () => {
135
+ const command = getRegistry().get('twitter/search');
136
+
137
+ const evaluate = vi.fn()
138
+ .mockResolvedValueOnce(undefined)
139
+ .mockResolvedValueOnce('/search');
140
+
141
+ const page = {
142
+ goto: vi.fn().mockResolvedValue(undefined),
143
+ wait: vi.fn().mockResolvedValue(undefined),
144
+ installInterceptor: vi.fn().mockResolvedValue(undefined),
145
+ evaluate,
146
+ autoScroll: vi.fn().mockResolvedValue(undefined),
147
+ getInterceptedRequests: vi.fn().mockResolvedValue([]),
148
+ };
149
+
150
+ await command!.func!(page as any, { query: 'test', limit: 5 });
151
+
152
+ const pushStateCall = evaluate.mock.calls[0][0] as string;
153
+ expect(pushStateCall).toContain('f=top');
154
+ });
155
+
87
156
  it('throws with the final path after both attempts fail', async () => {
88
157
  const command = getRegistry().get('twitter/search');
89
158
  expect(command?.func).toBeTypeOf('function');
@@ -103,7 +172,7 @@ describe('twitter search command', () => {
103
172
  getInterceptedRequests: vi.fn(),
104
173
  };
105
174
 
106
- await expect(command!.func!(page as any, { query: 'from:alice', limit: 5 }))
175
+ await expect(command!.func!(page as any, { query: 'from:alice', filter: 'top', limit: 5 }))
107
176
  .rejects
108
177
  .toThrow('Final path: /login');
109
178
  expect(page.autoScroll).not.toHaveBeenCalled();
@@ -9,8 +9,8 @@ import type { IPage } from '../../types.js';
9
9
  * pushState + popstate. A second attempt is enough for the intermittent cases
10
10
  * reported in issue #353 while keeping the flow narrowly scoped.
11
11
  */
12
- async function navigateToSearch(page: Pick<IPage, 'evaluate' | 'wait'>, query: string): Promise<void> {
13
- const searchUrl = JSON.stringify(`/search?q=${encodeURIComponent(query)}&f=top`);
12
+ async function navigateToSearch(page: Pick<IPage, 'evaluate' | 'wait'>, query: string, filter: string): Promise<void> {
13
+ const searchUrl = JSON.stringify(`/search?q=${encodeURIComponent(query)}&f=${filter}`);
14
14
  let lastPath = '';
15
15
 
16
16
  for (let attempt = 1; attempt <= 2; attempt++) {
@@ -46,11 +46,13 @@ cli({
46
46
  browser: true,
47
47
  args: [
48
48
  { name: 'query', type: 'string', required: true, positional: true },
49
+ { name: 'filter', type: 'string', default: 'top', choices: ['top', 'live'] },
49
50
  { name: 'limit', type: 'int', default: 15 },
50
51
  ],
51
- columns: ['id', 'author', 'text', 'likes', 'views', 'url'],
52
+ columns: ['id', 'author', 'text', 'created_at', 'likes', 'views', 'url'],
52
53
  func: async (page, kwargs) => {
53
54
  const query = kwargs.query;
55
+ const filter = kwargs.filter === 'live' ? 'live' : 'top';
54
56
 
55
57
  // 1. Navigate to x.com/explore (has a search input at the top)
56
58
  await page.goto('https://x.com/explore');
@@ -66,7 +68,7 @@ cli({
66
68
  // a full page reload, so the interceptor stays alive.
67
69
  // Note: the previous approach (nativeSetter + Enter keydown on the
68
70
  // search input) does not reliably trigger Twitter's form submission.
69
- await navigateToSearch(page, query);
71
+ await navigateToSearch(page, query, filter);
70
72
 
71
73
  // 4. Scroll to trigger additional pagination
72
74
  await page.autoScroll({ times: 3, delayMs: 2000 });
@@ -99,10 +101,12 @@ cli({
99
101
 
100
102
  // Twitter moved screen_name from legacy to core
101
103
  const tweetUser = tweet.core?.user_results?.result;
104
+
102
105
  results.push({
103
106
  id: tweet.rest_id,
104
107
  author: tweetUser?.core?.screen_name || tweetUser?.legacy?.screen_name || 'unknown',
105
108
  text: tweet.note_tweet?.note_tweet_results?.result?.text || tweet.legacy?.full_text || '',
109
+ created_at: tweet.legacy?.created_at || '',
106
110
  likes: tweet.legacy?.favorite_count || 0,
107
111
  views: tweet.views?.count || '0',
108
112
  url: `https://x.com/i/status/${tweet.rest_id}`
@@ -0,0 +1,45 @@
1
+ import type { IPage } from '../../types.js';
2
+
3
+ const QUERY_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
4
+
5
+ export function sanitizeQueryId(resolved: unknown, fallbackId: string): string {
6
+ return typeof resolved === 'string' && QUERY_ID_PATTERN.test(resolved) ? resolved : fallbackId;
7
+ }
8
+
9
+ export async function resolveTwitterQueryId(
10
+ page: Pick<IPage, 'evaluate'>,
11
+ operationName: string,
12
+ fallbackId: string,
13
+ ): Promise<string> {
14
+ const resolved = await page.evaluate(`async () => {
15
+ const operationName = ${JSON.stringify(operationName)};
16
+ try {
17
+ const ghResp = await fetch('https://raw.githubusercontent.com/fa0311/twitter-openapi/refs/heads/main/src/config/placeholder.json');
18
+ if (ghResp.ok) {
19
+ const data = await ghResp.json();
20
+ const entry = data?.[operationName];
21
+ if (entry && entry.queryId) return entry.queryId;
22
+ }
23
+ } catch {}
24
+ try {
25
+ const scripts = performance.getEntriesByType('resource')
26
+ .filter(r => r.name.includes('client-web') && r.name.endsWith('.js'))
27
+ .map(r => r.name);
28
+ for (const scriptUrl of scripts.slice(0, 15)) {
29
+ try {
30
+ const text = await (await fetch(scriptUrl)).text();
31
+ const re = new RegExp('queryId:"([A-Za-z0-9_-]+)"[^}]{0,200}operationName:"' + operationName + '"');
32
+ const match = text.match(re);
33
+ if (match) return match[1];
34
+ } catch {}
35
+ }
36
+ } catch {}
37
+ return null;
38
+ }`);
39
+
40
+ return sanitizeQueryId(resolved, fallbackId);
41
+ }
42
+
43
+ export const __test__ = {
44
+ sanitizeQueryId,
45
+ };
@@ -1,5 +1,6 @@
1
1
  import { AuthRequiredError, CommandExecutionError } from '../../errors.js';
2
2
  import { cli, Strategy } from '../../registry.js';
3
+ import { resolveTwitterQueryId } from './shared.js';
3
4
 
4
5
  // ── Twitter GraphQL constants ──────────────────────────────────────────
5
6
 
@@ -198,19 +199,7 @@ cli({
198
199
  if (!ct0) throw new AuthRequiredError('x.com', 'Not logged into x.com (no ct0 cookie)');
199
200
 
200
201
  // Dynamically resolve queryId for the selected endpoint
201
- const resolved = await page.evaluate(`async () => {
202
- try {
203
- const ghResp = await fetch('https://raw.githubusercontent.com/fa0311/twitter-openapi/refs/heads/main/src/config/placeholder.json');
204
- if (ghResp.ok) {
205
- const data = await ghResp.json();
206
- const entry = data['${endpoint}'];
207
- if (entry && entry.queryId) return entry.queryId;
208
- }
209
- } catch {}
210
- return null;
211
- }`);
212
- // Validate queryId format to prevent injection from untrusted upstream
213
- const queryId = typeof resolved === 'string' && /^[A-Za-z0-9_-]+$/.test(resolved) ? resolved : fallbackQueryId;
202
+ const queryId = await resolveTwitterQueryId(page, endpoint, fallbackQueryId);
214
203
 
215
204
  // Build auth headers
216
205
  const headers = JSON.stringify({
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Weibo comments — get comments on a post.
3
+ */
4
+ import { cli, Strategy } from '../../registry.js';
5
+
6
+ cli({
7
+ site: 'weibo',
8
+ name: 'comments',
9
+ description: 'Get comments on a Weibo post',
10
+ domain: 'weibo.com',
11
+ strategy: Strategy.COOKIE,
12
+ args: [
13
+ { name: 'id', required: true, positional: true, help: 'Post ID (numeric idstr)' },
14
+ { name: 'limit', type: 'int', default: 20, help: 'Number of comments (max 50)' },
15
+ ],
16
+ columns: ['rank', 'author', 'text', 'likes', 'replies', 'time'],
17
+ func: async (page, kwargs) => {
18
+ const count = Math.min(kwargs.limit || 20, 50);
19
+ await page.goto('https://weibo.com');
20
+ await page.wait(2);
21
+
22
+ const id = String(kwargs.id);
23
+ const data = await page.evaluate(`
24
+ (async () => {
25
+ const id = ${JSON.stringify(id)};
26
+ const count = ${count};
27
+ const strip = (html) => (html || '').replace(/<[^>]+>/g, '').replace(/&nbsp;/g, ' ').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&').trim();
28
+
29
+ const url = '/ajax/statuses/buildComments?flow=0&is_reload=1&id=' + id + '&is_show_bulletin=2&is_mix=0&count=' + count;
30
+ const resp = await fetch(url, {credentials: 'include'});
31
+ if (!resp.ok) return {error: 'HTTP ' + resp.status};
32
+ const data = await resp.json();
33
+ if (!data.ok) return {error: 'API error: ' + (data.msg || 'unknown')};
34
+
35
+ return (data.data || []).map((c, i) => {
36
+ const item = {
37
+ rank: i + 1,
38
+ author: c.user?.screen_name || '',
39
+ text: strip(c.text || ''),
40
+ likes: c.like_count || 0,
41
+ replies: c.total_number || 0,
42
+ time: c.created_at || '',
43
+ };
44
+ if (c.reply_comment) {
45
+ item.reply_to = (c.reply_comment.user?.screen_name || '') + ': ' + strip(c.reply_comment.text || '').substring(0, 80);
46
+ }
47
+ return item;
48
+ });
49
+ })()
50
+ `);
51
+ if (!Array.isArray(data)) return [];
52
+ return data;
53
+ },
54
+ });
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Weibo feed — home timeline from followed users.
3
+ */
4
+ import { cli, Strategy } from '../../registry.js';
5
+ import { getSelfUid } from './utils.js';
6
+
7
+ cli({
8
+ site: 'weibo',
9
+ name: 'feed',
10
+ description: 'Weibo home timeline (posts from followed users)',
11
+ domain: 'weibo.com',
12
+ strategy: Strategy.COOKIE,
13
+ args: [
14
+ { name: 'limit', type: 'int', default: 15, help: 'Number of posts (max 50)' },
15
+ ],
16
+ columns: ['author', 'text', 'reposts', 'comments', 'likes', 'time', 'url'],
17
+ func: async (page, kwargs) => {
18
+ const count = Math.min(kwargs.limit || 15, 50);
19
+ await page.goto('https://weibo.com');
20
+ await page.wait(2);
21
+ const uid = await getSelfUid(page);
22
+
23
+ const data = await page.evaluate(`
24
+ (async () => {
25
+ const uid = ${JSON.stringify(uid)};
26
+ const count = ${count};
27
+ const listId = '10001' + uid;
28
+ const strip = (html) => (html || '').replace(/<[^>]+>/g, '').replace(/&nbsp;/g, ' ').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&').trim();
29
+
30
+ const resp = await fetch('/ajax/feed/unreadfriendstimeline?list_id=' + listId + '&refresh=4&since_id=0&count=' + count, {credentials: 'include'});
31
+ if (!resp.ok) return {error: 'HTTP ' + resp.status};
32
+ const data = await resp.json();
33
+ if (!data.ok) return {error: 'API error: ' + (data.msg || 'unknown')};
34
+
35
+ return (data.statuses || []).slice(0, count).map(s => {
36
+ const u = s.user || {};
37
+ const item = {
38
+ author: u.screen_name || '',
39
+ text: (s.text_raw || strip(s.text || '')).substring(0, 200),
40
+ reposts: s.reposts_count || 0,
41
+ comments: s.comments_count || 0,
42
+ likes: s.attitudes_count || 0,
43
+ time: s.created_at || '',
44
+ url: 'https://weibo.com/' + (u.id || '') + '/' + (s.mblogid || ''),
45
+ };
46
+ if (s.retweeted_status) {
47
+ const rt = s.retweeted_status;
48
+ item.retweeted = (rt.user?.screen_name || '[deleted]') + ': ' + (rt.text_raw || strip(rt.text || '')).substring(0, 100);
49
+ }
50
+ return item;
51
+ });
52
+ })()
53
+ `);
54
+ if (!Array.isArray(data)) return [];
55
+ return data;
56
+ },
57
+ });
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * Weibo hot search — browser cookie API.
3
- * Source: bb-sites/weibo/hot.js
4
3
  */
5
4
  import { cli, Strategy } from '../../registry.js';
6
5
 
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Weibo me — current logged-in user profile.
3
+ */
4
+ import { cli, Strategy } from '../../registry.js';
5
+ import { CommandExecutionError } from '../../errors.js';
6
+ import { getSelfUid } from './utils.js';
7
+
8
+ cli({
9
+ site: 'weibo',
10
+ name: 'me',
11
+ description: 'My Weibo profile info',
12
+ domain: 'weibo.com',
13
+ strategy: Strategy.COOKIE,
14
+ args: [],
15
+ columns: ['screen_name', 'uid', 'followers', 'following', 'statuses', 'verified', 'location'],
16
+ func: async (page) => {
17
+ await page.goto('https://weibo.com');
18
+ await page.wait(2);
19
+ const uid = await getSelfUid(page);
20
+
21
+ const data = await page.evaluate(`
22
+ (async () => {
23
+ const uid = ${JSON.stringify(uid)};
24
+
25
+ // Try Vue store first
26
+ const app = document.querySelector('#app')?.__vue_app__;
27
+ const store = app?.config?.globalProperties?.$store;
28
+ const cfg = store?.state?.config?.config;
29
+ const u = cfg?.user;
30
+
31
+ // Fetch detail info
32
+ const detailResp = await fetch('/ajax/profile/detail?uid=' + uid, {credentials: 'include'});
33
+ const detail = detailResp.ok ? await detailResp.json() : null;
34
+ const d = detail?.data || {};
35
+
36
+ if (u && u.id) {
37
+ return {
38
+ screen_name: u.screen_name,
39
+ uid: u.id,
40
+ followers: u.followers_count,
41
+ following: u.friends_count,
42
+ statuses: u.statuses_count,
43
+ verified: u.verified || false,
44
+ location: u.location || '',
45
+ description: u.description || d.description || '',
46
+ avatar: u.avatar_hd || u.avatar_large || '',
47
+ profile_url: 'https://weibo.com' + (u.profile_url || '/u/' + u.id),
48
+ };
49
+ }
50
+
51
+ // Fallback: fetch profile API
52
+ const resp = await fetch('/ajax/profile/info?uid=' + uid, {credentials: 'include'});
53
+ if (!resp.ok) return {error: 'HTTP ' + resp.status};
54
+ const info = await resp.json();
55
+ if (!info.ok) return {error: 'API error'};
56
+ const p = info.data?.user;
57
+ if (!p) return {error: 'User data not found'};
58
+ return {
59
+ screen_name: p.screen_name,
60
+ uid: p.id,
61
+ followers: p.followers_count,
62
+ following: p.friends_count,
63
+ statuses: p.statuses_count,
64
+ verified: p.verified || false,
65
+ location: p.location || '',
66
+ description: p.description || d.description || '',
67
+ avatar: p.avatar_hd || p.avatar_large || '',
68
+ profile_url: 'https://weibo.com' + (p.profile_url || '/u/' + p.id),
69
+ };
70
+ })()
71
+ `);
72
+
73
+ if (!data || typeof data !== 'object') throw new CommandExecutionError('Failed to fetch profile');
74
+ if ((data as Record<string, unknown>).error) throw new CommandExecutionError(String((data as Record<string, unknown>).error));
75
+ return data as Record<string, unknown>;
76
+ },
77
+ });
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Weibo post — get a single post by ID.
3
+ */
4
+ import { cli, Strategy } from '../../registry.js';
5
+ import { CommandExecutionError } from '../../errors.js';
6
+
7
+ cli({
8
+ site: 'weibo',
9
+ name: 'post',
10
+ description: 'Get a single Weibo post',
11
+ domain: 'weibo.com',
12
+ strategy: Strategy.COOKIE,
13
+ args: [
14
+ { name: 'id', required: true, positional: true, help: 'Post ID (numeric idstr or mblogid from URL)' },
15
+ ],
16
+ columns: ['field', 'value'],
17
+ func: async (page, kwargs) => {
18
+ await page.goto('https://weibo.com');
19
+ await page.wait(2);
20
+
21
+ const id = String(kwargs.id);
22
+ const data = await page.evaluate(`
23
+ (async () => {
24
+ const id = ${JSON.stringify(id)};
25
+ const strip = (html) => (html || '').replace(/<[^>]+>/g, '').replace(/&nbsp;/g, ' ').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&').trim();
26
+
27
+ const resp = await fetch('/ajax/statuses/show?id=' + encodeURIComponent(id), {credentials: 'include'});
28
+ if (!resp.ok) return {error: 'HTTP ' + resp.status};
29
+ const s = await resp.json();
30
+ if (!s.ok && !s.idstr) return {error: 'Post not found'};
31
+
32
+ // Fetch long text if needed
33
+ let fullText = s.text_raw || strip(s.text || '');
34
+ if (s.isLongText || s.is_long_text) {
35
+ try {
36
+ const ltResp = await fetch('/ajax/statuses/longtext?id=' + s.idstr, {credentials: 'include'});
37
+ if (ltResp.ok) {
38
+ const lt = await ltResp.json();
39
+ if (lt.data?.longTextContent) fullText = strip(lt.data.longTextContent);
40
+ }
41
+ } catch {}
42
+ }
43
+
44
+ const u = s.user || {};
45
+ const result = {
46
+ id: s.idstr || String(s.id),
47
+ mblogid: s.mblogid,
48
+ author: u.screen_name || '',
49
+ text: fullText,
50
+ created_at: s.created_at,
51
+ source: strip(s.source || ''),
52
+ reposts: s.reposts_count || 0,
53
+ comments: s.comments_count || 0,
54
+ likes: s.attitudes_count || 0,
55
+ pic_count: s.pic_num || 0,
56
+ url: 'https://weibo.com/' + (u.id || '') + '/' + (s.mblogid || ''),
57
+ };
58
+
59
+ if (s.retweeted_status) {
60
+ const rt = s.retweeted_status;
61
+ result.retweeted_from = (rt.user?.screen_name || '[deleted]');
62
+ result.retweeted_text = rt.text_raw || strip(rt.text || '');
63
+ }
64
+
65
+ return result;
66
+ })()
67
+ `);
68
+
69
+ if (!data || typeof data !== 'object') throw new CommandExecutionError('Failed to fetch post');
70
+ if ((data as Record<string, unknown>).error) throw new CommandExecutionError(String((data as Record<string, unknown>).error));
71
+
72
+ return Object.entries(data as Record<string, unknown>).map(([field, value]) => ({
73
+ field,
74
+ value: String(value),
75
+ }));
76
+ },
77
+ });
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Weibo user — get user profile by uid or screen_name.
3
+ */
4
+ import { cli, Strategy } from '../../registry.js';
5
+ import { CommandExecutionError } from '../../errors.js';
6
+
7
+ cli({
8
+ site: 'weibo',
9
+ name: 'user',
10
+ description: 'Get Weibo user profile',
11
+ domain: 'weibo.com',
12
+ strategy: Strategy.COOKIE,
13
+ args: [
14
+ { name: 'id', required: true, positional: true, help: 'User ID (numeric uid) or screen name' },
15
+ ],
16
+ columns: ['screen_name', 'uid', 'followers', 'following', 'statuses', 'verified', 'description', 'location', 'url'],
17
+ func: async (page, kwargs) => {
18
+ await page.goto('https://weibo.com');
19
+ await page.wait(2);
20
+
21
+ const id = String(kwargs.id);
22
+ const data = await page.evaluate(`
23
+ (async () => {
24
+ const id = ${JSON.stringify(id)};
25
+ const isUid = /^\\d+$/.test(id);
26
+ const query = isUid ? 'uid=' + id : 'screen_name=' + encodeURIComponent(id);
27
+
28
+ const resp = await fetch('/ajax/profile/info?' + query, {credentials: 'include'});
29
+ if (!resp.ok) return {error: 'HTTP ' + resp.status};
30
+ const data = await resp.json();
31
+ if (!data.ok || !data.data?.user) return {error: 'User not found'};
32
+
33
+ const u = data.data.user;
34
+
35
+ // Fetch detail info
36
+ const detailResp = await fetch('/ajax/profile/detail?uid=' + u.id, {credentials: 'include'});
37
+ const detail = detailResp.ok ? await detailResp.json() : null;
38
+ const d = detail?.data || {};
39
+
40
+ return {
41
+ screen_name: u.screen_name,
42
+ uid: u.id,
43
+ followers: u.followers_count,
44
+ following: u.friends_count,
45
+ statuses: u.statuses_count,
46
+ verified: u.verified || false,
47
+ verified_reason: u.verified_reason || '',
48
+ description: u.description || d.description || '',
49
+ location: u.location || '',
50
+ gender: u.gender === 'm' ? 'male' : u.gender === 'f' ? 'female' : '',
51
+ avatar: u.avatar_hd || u.avatar_large || '',
52
+ url: 'https://weibo.com' + (u.profile_url || '/u/' + u.id),
53
+ birthday: d.birthday || '',
54
+ created_at: d.created_at || '',
55
+ ip_location: d.ip_location || '',
56
+ };
57
+ })()
58
+ `);
59
+
60
+ if (!data || typeof data !== 'object') throw new CommandExecutionError('Failed to fetch user profile');
61
+ if ((data as Record<string, unknown>).error) throw new CommandExecutionError(String((data as Record<string, unknown>).error));
62
+ return data as Record<string, unknown>;
63
+ },
64
+ });
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Shared Weibo utilities — uid extraction.
3
+ */
4
+
5
+ import type { IPage } from '../../types.js';
6
+ import { AuthRequiredError } from '../../errors.js';
7
+
8
+ /** Get the currently logged-in user's uid from Vue store or config API. */
9
+ export async function getSelfUid(page: IPage): Promise<string> {
10
+ const uid = await page.evaluate(`
11
+ (() => {
12
+ const app = document.querySelector('#app')?.__vue_app__;
13
+ const store = app?.config?.globalProperties?.$store;
14
+ const uid = store?.state?.config?.config?.uid;
15
+ if (uid) return String(uid);
16
+ return null;
17
+ })()
18
+ `);
19
+ if (uid) return uid as string;
20
+
21
+ // Fallback: config API
22
+ const config = await page.evaluate(`
23
+ (async () => {
24
+ const resp = await fetch('/ajax/config/get_config', {credentials: 'include'});
25
+ if (!resp.ok) return null;
26
+ const data = await resp.json();
27
+ return data.ok && data.data?.uid ? String(data.data.uid) : null;
28
+ })()
29
+ `);
30
+ if (config) return config as string;
31
+ throw new AuthRequiredError('weibo.com');
32
+ }