@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
@@ -0,0 +1,72 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { parseFeed, pickVoteCount, PRODUCTHUNT_CATEGORY_SLUGS } from './utils.js';
3
+
4
+ const SAMPLE_ATOM = `<?xml version="1.0" encoding="UTF-8"?>
5
+ <feed xmlns="http://www.w3.org/2005/Atom">
6
+ <title>Product Hunt</title>
7
+ <entry>
8
+ <id>tag:www.producthunt.com,2005:Post/1001</id>
9
+ <published>2026-03-26T10:00:00-07:00</published>
10
+ <title>Awesome AI Tool</title>
11
+ <content type="html">&lt;p&gt;The best AI tool ever made&lt;/p&gt;&lt;p&gt;&lt;a href="..."&gt;Discussion&lt;/a&gt;&lt;/p&gt;</content>
12
+ <author><name>Jane Doe</name></author>
13
+ <link rel="alternate" type="text/html" href="https://www.producthunt.com/products/awesome-ai-tool"/>
14
+ </entry>
15
+ <entry>
16
+ <id>tag:www.producthunt.com,2005:Post/1002</id>
17
+ <published>2026-03-25T08:00:00-07:00</published>
18
+ <title>Dev Helper</title>
19
+ <content type="html">&lt;p&gt;Speeds up your workflow&lt;/p&gt;</content>
20
+ <author><name>John Smith</name></author>
21
+ <link rel="alternate" type="text/html" href="https://www.producthunt.com/products/dev-helper"/>
22
+ </entry>
23
+ </feed>`;
24
+
25
+ describe('parseFeed', () => {
26
+ it('parses entries into ranked posts', () => {
27
+ const posts = parseFeed(SAMPLE_ATOM);
28
+ expect(posts).toHaveLength(2);
29
+ expect(posts[0].rank).toBe(1);
30
+ expect(posts[0].name).toBe('Awesome AI Tool');
31
+ expect(posts[0].author).toBe('Jane Doe');
32
+ expect(posts[0].date).toBe('2026-03-26');
33
+ expect(posts[0].url).toBe('https://www.producthunt.com/products/awesome-ai-tool');
34
+ expect(posts[0].tagline).toContain('best AI tool');
35
+ });
36
+
37
+ it('strips HTML and Discussion link from tagline', () => {
38
+ const posts = parseFeed(SAMPLE_ATOM);
39
+ expect(posts[0].tagline).not.toContain('<p>');
40
+ expect(posts[0].tagline).not.toContain('Discussion');
41
+ });
42
+
43
+ it('returns empty array for empty feed', () => {
44
+ expect(parseFeed('<feed></feed>')).toHaveLength(0);
45
+ });
46
+
47
+ it('assigns sequential ranks', () => {
48
+ const posts = parseFeed(SAMPLE_ATOM);
49
+ expect(posts.map(p => p.rank)).toEqual([1, 2]);
50
+ });
51
+
52
+ it('prefers vote-like candidates over unrelated numeric badges', () => {
53
+ const votes = pickVoteCount([
54
+ { text: '12', className: 'font-semibold', inButton: false, inReviewLink: false },
55
+ { text: '98', className: 'vote-button', inButton: true, inReviewLink: false },
56
+ ]);
57
+ expect(votes).toBe('98');
58
+ });
59
+
60
+ it('ignores numbers inside review links', () => {
61
+ const votes = pickVoteCount([
62
+ { text: '120', className: 'text-secondary', inButton: false, inReviewLink: true },
63
+ { text: '45', className: 'vote-button', inButton: true, inReviewLink: false },
64
+ ]);
65
+ expect(votes).toBe('45');
66
+ });
67
+
68
+ it('shares category slugs across commands', () => {
69
+ expect(PRODUCTHUNT_CATEGORY_SLUGS).toContain('developer-tools');
70
+ expect(PRODUCTHUNT_CATEGORY_SLUGS).toContain('ai-agents');
71
+ });
72
+ });
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Product Hunt shared helpers.
3
+ */
4
+
5
+ export interface PhPost {
6
+ rank: number;
7
+ name: string;
8
+ tagline: string;
9
+ author: string;
10
+ date: string;
11
+ url: string;
12
+ }
13
+
14
+ export interface ProductHuntVoteCandidate {
15
+ text: string;
16
+ tagName?: string;
17
+ className?: string;
18
+ role?: string;
19
+ inButton?: boolean;
20
+ inReviewLink?: boolean;
21
+ }
22
+
23
+ export const PRODUCTHUNT_CATEGORY_SLUGS = [
24
+ 'ai-agents',
25
+ 'ai-coding-agents',
26
+ 'ai-code-editors',
27
+ 'ai-chatbots',
28
+ 'ai-workflow-automation',
29
+ 'vibe-coding',
30
+ 'developer-tools',
31
+ 'productivity',
32
+ 'design-creative',
33
+ 'marketing-sales',
34
+ 'no-code-platforms',
35
+ 'llms',
36
+ 'finance',
37
+ 'social-community',
38
+ 'engineering-development',
39
+ ] as const;
40
+
41
+ const UA = 'Mozilla/5.0 (compatible; opencli/1.0)';
42
+
43
+ /**
44
+ * Fetch Product Hunt Atom RSS feed.
45
+ * @param category Optional category slug (e.g. "ai", "developer-tools")
46
+ */
47
+ export async function fetchFeed(category?: string): Promise<PhPost[]> {
48
+ const url = category
49
+ ? `https://www.producthunt.com/feed?category=${encodeURIComponent(category)}`
50
+ : 'https://www.producthunt.com/feed';
51
+
52
+ const resp = await fetch(url, { headers: { 'User-Agent': UA } });
53
+ if (!resp.ok) return [];
54
+ const xml = await resp.text();
55
+ return parseFeed(xml);
56
+ }
57
+
58
+ export function parseFeed(xml: string): PhPost[] {
59
+ const posts: PhPost[] = [];
60
+ const entryRegex = /<entry>([\s\S]*?)<\/entry>/g;
61
+ let match;
62
+ let rank = 1;
63
+
64
+ while ((match = entryRegex.exec(xml))) {
65
+ const block = match[1];
66
+
67
+ const name = block.match(/<title>([\s\S]*?)<\/title>/)?.[1]?.trim() ?? '';
68
+ const author = block.match(/<name>([\s\S]*?)<\/name>/)?.[1]?.trim() ?? '';
69
+ const pubRaw = block.match(/<published>(.*?)<\/published>/)?.[1]?.trim() ?? '';
70
+ const date = pubRaw.slice(0, 10);
71
+ const link = block.match(/<link[^>]*href="([^"]+)"/)?.[1]?.trim() ?? '';
72
+
73
+ // Extract tagline from HTML content (first <p> text)
74
+ const contentRaw = block.match(/<content[^>]*>([\s\S]*?)<\/content>/)?.[1] ?? '';
75
+ const contentDecoded = contentRaw
76
+ .replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&').replace(/&quot;/g, '"');
77
+ const tagline = contentDecoded
78
+ .replace(/<[^>]+>/g, ' ')
79
+ .replace(/\s+/g, ' ')
80
+ .replace(/\s*Discussion\s*\|?\s*/gi, '')
81
+ .replace(/\s*\|?\s*Link\s*$/gi, '')
82
+ .trim()
83
+ .slice(0, 120);
84
+
85
+ if (name) {
86
+ posts.push({ rank: rank++, name, tagline, author, date, url: link });
87
+ }
88
+ }
89
+ return posts;
90
+ }
91
+
92
+ export function pickVoteCount(candidates: ProductHuntVoteCandidate[]): string {
93
+ const scored = candidates
94
+ .map((candidate) => {
95
+ const text = String(candidate.text ?? '').trim();
96
+ if (!/^\d+$/.test(text)) return null;
97
+ if (candidate.inReviewLink) return null;
98
+
99
+ const value = parseInt(text, 10);
100
+ if (!Number.isFinite(value) || value <= 0) return null;
101
+
102
+ const signal = `${candidate.tagName ?? ''} ${candidate.className ?? ''} ${candidate.role ?? ''}`.toLowerCase();
103
+ let score = 0;
104
+ if (candidate.inButton) score += 4;
105
+ if (signal.includes('vote') || signal.includes('upvote')) score += 3;
106
+ if (signal.includes('button')) score += 1;
107
+ return { text, score, value };
108
+ })
109
+ .filter((candidate): candidate is { text: string; score: number; value: number } => Boolean(candidate))
110
+ .sort((a, b) => {
111
+ if (b.score !== a.score) return b.score - a.score;
112
+ if (b.value !== a.value) return b.value - a.value;
113
+ return a.text.localeCompare(b.text);
114
+ });
115
+
116
+ return scored[0]?.text ?? '';
117
+ }
118
+
119
+ /** Format ISO date string to YYYY-MM-DD */
120
+ export function toDate(iso: string): string {
121
+ return iso.slice(0, 10);
122
+ }
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * Reuters news search — API with HTML fallback.
3
- * Source: bb-sites/reuters/search.js
4
3
  */
5
4
  import { cli, Strategy } from '../../registry.js';
6
5
 
@@ -1,5 +1,8 @@
1
1
  import { AuthRequiredError, CommandExecutionError } from '../../errors.js';
2
2
  import { cli, Strategy } from '../../registry.js';
3
+ import { resolveTwitterQueryId } from './shared.js';
4
+
5
+ const TWEET_RESULT_BY_REST_ID_QUERY_ID = '7xflPyRiUxGVbJd4uWmbfg';
3
6
 
4
7
  cli({
5
8
  site: 'twitter',
@@ -21,6 +24,7 @@ cli({
21
24
  // Navigate to the tweet page for cookie context
22
25
  await page.goto(`https://x.com/i/status/${tweetId}`);
23
26
  await page.wait(3);
27
+ const queryId = await resolveTwitterQueryId(page, 'TweetResultByRestId', TWEET_RESULT_BY_REST_ID_QUERY_ID);
24
28
 
25
29
  const result = await page.evaluate(`
26
30
  async () => {
@@ -56,34 +60,7 @@ cli({
56
60
  withArticlePlainText: true,
57
61
  });
58
62
 
59
- // Dynamically resolve queryId: GitHub community source → JS bundle scan → hardcoded fallback
60
- async function resolveQueryId(operationName, fallbackId) {
61
- try {
62
- const ghResp = await fetch('https://raw.githubusercontent.com/fa0311/twitter-openapi/refs/heads/main/src/config/placeholder.json');
63
- if (ghResp.ok) {
64
- const data = await ghResp.json();
65
- const entry = data[operationName];
66
- if (entry && entry.queryId) return entry.queryId;
67
- }
68
- } catch {}
69
- try {
70
- const scripts = performance.getEntriesByType('resource')
71
- .filter(r => r.name.includes('client-web') && r.name.endsWith('.js'))
72
- .map(r => r.name);
73
- for (const scriptUrl of scripts.slice(0, 15)) {
74
- try {
75
- const text = await (await fetch(scriptUrl)).text();
76
- const re = new RegExp('queryId:"([A-Za-z0-9_-]+)"[^}]{0,200}operationName:"' + operationName + '"');
77
- const m = text.match(re);
78
- if (m) return m[1];
79
- } catch {}
80
- }
81
- } catch {}
82
- return fallbackId;
83
- }
84
-
85
- const queryId = await resolveQueryId('TweetResultByRestId', '7xflPyRiUxGVbJd4uWmbfg');
86
- const url = '/i/api/graphql/' + queryId + '/TweetResultByRestId?variables='
63
+ const url = '/i/api/graphql/' + ${JSON.stringify(queryId)} + '/TweetResultByRestId?variables='
87
64
  + encodeURIComponent(variables)
88
65
  + '&features=' + encodeURIComponent(features)
89
66
  + '&fieldToggles=' + encodeURIComponent(fieldToggles);
@@ -0,0 +1,91 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { __test__ } from './likes.js';
3
+
4
+ describe('twitter likes helpers', () => {
5
+ it('falls back when queryId contains unsafe characters', () => {
6
+ expect(__test__.sanitizeQueryId('safe_Query-123', 'fallback')).toBe('safe_Query-123');
7
+ expect(__test__.sanitizeQueryId('bad"id', 'fallback')).toBe('fallback');
8
+ expect(__test__.sanitizeQueryId('bad/id', 'fallback')).toBe('fallback');
9
+ expect(__test__.sanitizeQueryId(null, 'fallback')).toBe('fallback');
10
+ });
11
+
12
+ it('builds likes url with the provided queryId', () => {
13
+ const url = __test__.buildLikesUrl('query123', '42', 20, 'cursor-1');
14
+
15
+ expect(url).toContain('/i/api/graphql/query123/Likes');
16
+ expect(decodeURIComponent(url)).toContain('"userId":"42"');
17
+ expect(decodeURIComponent(url)).toContain('"cursor":"cursor-1"');
18
+ });
19
+
20
+ it('parses likes timeline entries and bottom cursor', () => {
21
+ const payload = {
22
+ data: {
23
+ user: {
24
+ result: {
25
+ timeline_v2: {
26
+ timeline: {
27
+ instructions: [
28
+ {
29
+ entries: [
30
+ {
31
+ entryId: 'tweet-1',
32
+ content: {
33
+ itemContent: {
34
+ tweet_results: {
35
+ result: {
36
+ rest_id: '1',
37
+ legacy: {
38
+ full_text: 'liked post',
39
+ favorite_count: 7,
40
+ retweet_count: 2,
41
+ created_at: 'now',
42
+ },
43
+ core: {
44
+ user_results: {
45
+ result: {
46
+ legacy: {
47
+ screen_name: 'alice',
48
+ name: 'Alice',
49
+ },
50
+ },
51
+ },
52
+ },
53
+ },
54
+ },
55
+ },
56
+ },
57
+ },
58
+ {
59
+ entryId: 'cursor-bottom-1',
60
+ content: {
61
+ entryType: 'TimelineTimelineCursor',
62
+ cursorType: 'Bottom',
63
+ value: 'cursor-next',
64
+ },
65
+ },
66
+ ],
67
+ },
68
+ ],
69
+ },
70
+ },
71
+ },
72
+ },
73
+ },
74
+ };
75
+
76
+ const result = __test__.parseLikes(payload, new Set());
77
+
78
+ expect(result.nextCursor).toBe('cursor-next');
79
+ expect(result.tweets).toHaveLength(1);
80
+ expect(result.tweets[0]).toMatchObject({
81
+ id: '1',
82
+ author: 'alice',
83
+ name: 'Alice',
84
+ text: 'liked post',
85
+ likes: 7,
86
+ retweets: 2,
87
+ created_at: 'now',
88
+ url: 'https://x.com/alice/status/1',
89
+ });
90
+ });
91
+ });
@@ -0,0 +1,256 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { AuthRequiredError, CommandExecutionError } from '../../errors.js';
3
+ import { resolveTwitterQueryId, sanitizeQueryId } from './shared.js';
4
+
5
+ const BEARER_TOKEN = 'AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA';
6
+ const LIKES_QUERY_ID = 'RozQdCp4CilQzrcuU0NY5w';
7
+ const USER_BY_SCREEN_NAME_QUERY_ID = 'qRednkZG-rn1P6b48NINmQ';
8
+
9
+ const FEATURES = {
10
+ rweb_video_screen_enabled: false,
11
+ profile_label_improvements_pcf_label_in_post_enabled: true,
12
+ responsive_web_profile_redirect_enabled: false,
13
+ rweb_tipjar_consumption_enabled: false,
14
+ verified_phone_label_enabled: false,
15
+ creator_subscriptions_tweet_preview_api_enabled: true,
16
+ responsive_web_graphql_timeline_navigation_enabled: true,
17
+ responsive_web_graphql_skip_user_profile_image_extensions_enabled: false,
18
+ premium_content_api_read_enabled: false,
19
+ communities_web_enable_tweet_community_results_fetch: true,
20
+ c9s_tweet_anatomy_moderator_badge_enabled: true,
21
+ responsive_web_grok_analyze_button_fetch_trends_enabled: false,
22
+ responsive_web_grok_analyze_post_followups_enabled: true,
23
+ responsive_web_jetfuel_frame: true,
24
+ responsive_web_grok_share_attachment_enabled: true,
25
+ responsive_web_grok_annotations_enabled: true,
26
+ articles_preview_enabled: true,
27
+ responsive_web_edit_tweet_api_enabled: true,
28
+ graphql_is_translatable_rweb_tweet_is_translatable_enabled: true,
29
+ view_counts_everywhere_api_enabled: true,
30
+ longform_notetweets_consumption_enabled: true,
31
+ responsive_web_twitter_article_tweet_consumption_enabled: true,
32
+ tweet_awards_web_tipping_enabled: false,
33
+ content_disclosure_indicator_enabled: true,
34
+ content_disclosure_ai_generated_indicator_enabled: true,
35
+ responsive_web_grok_show_grok_translated_post: false,
36
+ responsive_web_grok_analysis_button_from_backend: true,
37
+ post_ctas_fetch_enabled: false,
38
+ freedom_of_speech_not_reach_fetch_enabled: true,
39
+ standardized_nudges_misinfo: true,
40
+ tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled: true,
41
+ longform_notetweets_rich_text_read_enabled: true,
42
+ longform_notetweets_inline_media_enabled: false,
43
+ responsive_web_grok_image_annotation_enabled: true,
44
+ responsive_web_grok_imagine_annotation_enabled: true,
45
+ responsive_web_grok_community_note_auto_translation_is_enabled: false,
46
+ responsive_web_enhance_cards_enabled: false
47
+ };
48
+
49
+ interface LikedTweet {
50
+ id: string;
51
+ author: string;
52
+ name: string;
53
+ text: string;
54
+ likes: number;
55
+ retweets: number;
56
+ created_at: string;
57
+ url: string;
58
+ }
59
+
60
+ function buildLikesUrl(queryId: string, userId: string, count: number, cursor?: string | null): string {
61
+ const vars: Record<string, any> = {
62
+ userId,
63
+ count,
64
+ includePromotedContent: false,
65
+ withClientEventToken: false,
66
+ withBirdwatchNotes: false,
67
+ withVoice: true
68
+ };
69
+ if (cursor) vars.cursor = cursor;
70
+
71
+ return `/i/api/graphql/${queryId}/Likes`
72
+ + `?variables=${encodeURIComponent(JSON.stringify(vars))}`
73
+ + `&features=${encodeURIComponent(JSON.stringify(FEATURES))}`;
74
+ }
75
+
76
+ function buildUserByScreenNameUrl(queryId: string, screenName: string): string {
77
+ const vars = JSON.stringify({ screen_name: screenName, withSafetyModeUserFields: true });
78
+ const feats = JSON.stringify({
79
+ hidden_profile_subscriptions_enabled: true,
80
+ rweb_tipjar_consumption_enabled: true,
81
+ responsive_web_graphql_exclude_directive_enabled: true,
82
+ verified_phone_label_enabled: false,
83
+ subscriptions_verification_info_is_identity_verified_enabled: true,
84
+ subscriptions_verification_info_verified_since_enabled: true,
85
+ highlights_tweets_tab_ui_enabled: true,
86
+ responsive_web_twitter_article_notes_tab_enabled: true,
87
+ subscriptions_feature_can_gift_premium: true,
88
+ creator_subscriptions_tweet_preview_api_enabled: true,
89
+ responsive_web_graphql_skip_user_profile_image_extensions_enabled: false,
90
+ responsive_web_graphql_timeline_navigation_enabled: true,
91
+ });
92
+
93
+ return `/i/api/graphql/${queryId}/UserByScreenName`
94
+ + `?variables=${encodeURIComponent(vars)}`
95
+ + `&features=${encodeURIComponent(feats)}`;
96
+ }
97
+
98
+ function extractLikedTweet(result: any, seen: Set<string>): LikedTweet | null {
99
+ if (!result) return null;
100
+ const tw = result.tweet || result;
101
+ const legacy = tw.legacy || {};
102
+ if (!tw.rest_id || seen.has(tw.rest_id)) return null;
103
+ seen.add(tw.rest_id);
104
+
105
+ const user = tw.core?.user_results?.result;
106
+ const screenName = user?.legacy?.screen_name || user?.core?.screen_name || 'unknown';
107
+ const displayName = user?.legacy?.name || user?.core?.name || '';
108
+ const noteText = tw.note_tweet?.note_tweet_results?.result?.text;
109
+
110
+ return {
111
+ id: tw.rest_id,
112
+ author: screenName,
113
+ name: displayName,
114
+ text: noteText || legacy.full_text || '',
115
+ likes: legacy.favorite_count || 0,
116
+ retweets: legacy.retweet_count || 0,
117
+ created_at: legacy.created_at || '',
118
+ url: `https://x.com/${screenName}/status/${tw.rest_id}`,
119
+ };
120
+ }
121
+
122
+ function parseLikes(data: any, seen: Set<string>): { tweets: LikedTweet[]; nextCursor: string | null } {
123
+ const tweets: LikedTweet[] = [];
124
+ let nextCursor: string | null = null;
125
+
126
+ const instructions =
127
+ data?.data?.user?.result?.timeline_v2?.timeline?.instructions
128
+ || data?.data?.user?.result?.timeline?.timeline?.instructions
129
+ || [];
130
+
131
+ for (const inst of instructions) {
132
+ for (const entry of inst.entries || []) {
133
+ const content = entry.content;
134
+
135
+ if (content?.entryType === 'TimelineTimelineCursor' || content?.__typename === 'TimelineTimelineCursor') {
136
+ if (content.cursorType === 'Bottom' || content.cursorType === 'ShowMore') nextCursor = content.value;
137
+ continue;
138
+ }
139
+ if (entry.entryId?.startsWith('cursor-bottom-') || entry.entryId?.startsWith('cursor-showMore-')) {
140
+ nextCursor = content?.value || content?.itemContent?.value || nextCursor;
141
+ continue;
142
+ }
143
+
144
+ const direct = extractLikedTweet(content?.itemContent?.tweet_results?.result, seen);
145
+ if (direct) {
146
+ tweets.push(direct);
147
+ continue;
148
+ }
149
+
150
+ for (const item of content?.items || []) {
151
+ const nested = extractLikedTweet(item.item?.itemContent?.tweet_results?.result, seen);
152
+ if (nested) tweets.push(nested);
153
+ }
154
+ }
155
+ }
156
+
157
+ return { tweets, nextCursor };
158
+ }
159
+
160
+ cli({
161
+ site: 'twitter',
162
+ name: 'likes',
163
+ description: 'Fetch liked tweets of a Twitter user',
164
+ domain: 'x.com',
165
+ strategy: Strategy.COOKIE,
166
+ browser: true,
167
+ args: [
168
+ { name: 'username', type: 'string', positional: true, help: 'Twitter screen name (without @). Defaults to logged-in user.' },
169
+ { name: 'limit', type: 'int', default: 20 },
170
+ ],
171
+ columns: ['author', 'name', 'text', 'likes', 'url'],
172
+ func: async (page, kwargs) => {
173
+ const limit = kwargs.limit || 20;
174
+ let username = (kwargs.username || '').replace(/^@/, '');
175
+
176
+ await page.goto('https://x.com');
177
+ await page.wait(3);
178
+
179
+ const ct0 = await page.evaluate(`() => {
180
+ return document.cookie.split(';').map(c => c.trim()).find(c => c.startsWith('ct0='))?.split('=')[1] || null;
181
+ }`);
182
+ if (!ct0) throw new AuthRequiredError('x.com', 'Not logged into x.com (no ct0 cookie)');
183
+
184
+ // If no username provided, detect the logged-in user
185
+ if (!username) {
186
+ const href = await page.evaluate(`() => {
187
+ const link = document.querySelector('a[data-testid="AppTabBar_Profile_Link"]');
188
+ return link ? link.getAttribute('href') : null;
189
+ }`);
190
+ if (!href) throw new AuthRequiredError('x.com', 'Could not detect logged-in user. Are you logged in?');
191
+ username = href.replace('/', '');
192
+ }
193
+
194
+ const likesQueryId = await resolveTwitterQueryId(page, 'Likes', LIKES_QUERY_ID);
195
+ const userByScreenNameQueryId = await resolveTwitterQueryId(
196
+ page,
197
+ 'UserByScreenName',
198
+ USER_BY_SCREEN_NAME_QUERY_ID,
199
+ );
200
+
201
+ const headers = JSON.stringify({
202
+ 'Authorization': `Bearer ${decodeURIComponent(BEARER_TOKEN)}`,
203
+ 'X-Csrf-Token': ct0,
204
+ 'X-Twitter-Auth-Type': 'OAuth2Session',
205
+ 'X-Twitter-Active-User': 'yes',
206
+ });
207
+
208
+ // Get userId from screen_name
209
+ const userId = await page.evaluate(`async () => {
210
+ const screenName = ${JSON.stringify(username)};
211
+ const url = ${JSON.stringify(buildUserByScreenNameUrl(userByScreenNameQueryId, username))};
212
+ const resp = await fetch(url, { headers: ${headers}, credentials: 'include' });
213
+ if (!resp.ok) return null;
214
+ const d = await resp.json();
215
+ return d.data?.user?.result?.rest_id || null;
216
+ }`);
217
+
218
+ if (!userId) {
219
+ throw new CommandExecutionError(`Could not find user @${username}`);
220
+ }
221
+
222
+ const allTweets: LikedTweet[] = [];
223
+ const seen = new Set<string>();
224
+ let cursor: string | null = null;
225
+
226
+ for (let i = 0; i < 5 && allTweets.length < limit; i++) {
227
+ const fetchCount = Math.min(100, limit - allTweets.length + 10);
228
+ const apiUrl = buildLikesUrl(likesQueryId, userId, fetchCount, cursor);
229
+
230
+ const data = await page.evaluate(`async () => {
231
+ const r = await fetch("${apiUrl}", { headers: ${headers}, credentials: 'include' });
232
+ return r.ok ? await r.json() : { error: r.status };
233
+ }`);
234
+
235
+ if (data?.error) {
236
+ if (allTweets.length === 0) throw new CommandExecutionError(`HTTP ${data.error}: Failed to fetch likes. queryId may have expired.`);
237
+ break;
238
+ }
239
+
240
+ const { tweets, nextCursor } = parseLikes(data, seen);
241
+ allTweets.push(...tweets);
242
+
243
+ if (!nextCursor || nextCursor === cursor) break;
244
+ cursor = nextCursor;
245
+ }
246
+
247
+ return allTweets.slice(0, limit);
248
+ },
249
+ });
250
+
251
+ export const __test__ = {
252
+ sanitizeQueryId,
253
+ buildLikesUrl,
254
+ buildUserByScreenNameUrl,
255
+ parseLikes,
256
+ };
@@ -1,5 +1,8 @@
1
1
  import { AuthRequiredError, CommandExecutionError } from '../../errors.js';
2
2
  import { cli, Strategy } from '../../registry.js';
3
+ import { resolveTwitterQueryId } from './shared.js';
4
+
5
+ const USER_BY_SCREEN_NAME_QUERY_ID = 'qRednkZG-rn1P6b48NINmQ';
3
6
 
4
7
  cli({
5
8
  site: 'twitter',
@@ -30,6 +33,7 @@ cli({
30
33
  // Navigate directly to the user's profile page (gives us cookie context)
31
34
  await page.goto(`https://x.com/${username}`);
32
35
  await page.wait(3);
36
+ const queryId = await resolveTwitterQueryId(page, 'UserByScreenName', USER_BY_SCREEN_NAME_QUERY_ID);
33
37
 
34
38
  const result = await page.evaluate(`
35
39
  async () => {
@@ -64,34 +68,7 @@ cli({
64
68
  responsive_web_graphql_timeline_navigation_enabled: true,
65
69
  });
66
70
 
67
- // Dynamically resolve queryId: GitHub community source → JS bundle scan → hardcoded fallback
68
- async function resolveQueryId(operationName, fallbackId) {
69
- try {
70
- const ghResp = await fetch('https://raw.githubusercontent.com/fa0311/twitter-openapi/refs/heads/main/src/config/placeholder.json');
71
- if (ghResp.ok) {
72
- const data = await ghResp.json();
73
- const entry = data[operationName];
74
- if (entry && entry.queryId) return entry.queryId;
75
- }
76
- } catch {}
77
- try {
78
- const scripts = performance.getEntriesByType('resource')
79
- .filter(r => r.name.includes('client-web') && r.name.endsWith('.js'))
80
- .map(r => r.name);
81
- for (const scriptUrl of scripts.slice(0, 15)) {
82
- try {
83
- const text = await (await fetch(scriptUrl)).text();
84
- const re = new RegExp('queryId:"([A-Za-z0-9_-]+)"[^}]{0,200}operationName:"' + operationName + '"');
85
- const m = text.match(re);
86
- if (m) return m[1];
87
- } catch {}
88
- }
89
- } catch {}
90
- return fallbackId;
91
- }
92
-
93
- const queryId = await resolveQueryId('UserByScreenName', 'qRednkZG-rn1P6b48NINmQ');
94
- const url = '/i/api/graphql/' + queryId + '/UserByScreenName?variables='
71
+ const url = '/i/api/graphql/' + ${JSON.stringify(queryId)} + '/UserByScreenName?variables='
95
72
  + encodeURIComponent(variables)
96
73
  + '&features=' + encodeURIComponent(features);
97
74