@jackwener/opencli 1.1.1 → 1.2.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 (509) hide show
  1. package/CONTRIBUTING.md +39 -1
  2. package/README.md +9 -10
  3. package/README.zh-CN.md +39 -17
  4. package/SKILL.md +10 -5
  5. package/dist/browser/cdp.d.ts +4 -4
  6. package/dist/browser/cdp.js +39 -16
  7. package/dist/browser/daemon-client.d.ts +2 -1
  8. package/dist/browser/dom-helpers.js +38 -7
  9. package/dist/browser/dom-snapshot.d.ts +86 -0
  10. package/dist/browser/dom-snapshot.js +729 -0
  11. package/dist/browser/dom-snapshot.test.d.ts +11 -0
  12. package/dist/browser/dom-snapshot.test.js +212 -0
  13. package/dist/browser/index.d.ts +2 -0
  14. package/dist/browser/index.js +1 -0
  15. package/dist/browser/page.d.ts +14 -24
  16. package/dist/browser/page.js +37 -4
  17. package/dist/build-manifest.d.ts +11 -4
  18. package/dist/build-manifest.js +59 -21
  19. package/dist/build-manifest.test.js +58 -2
  20. package/dist/cli-manifest.json +3856 -1509
  21. package/dist/cli.js +66 -0
  22. package/dist/clis/barchart/greeks.js +1 -1
  23. package/dist/clis/barchart/options.js +1 -1
  24. package/dist/clis/barchart/quote.js +1 -1
  25. package/dist/clis/bilibili/download.js +1 -1
  26. package/dist/clis/bilibili/following.js +1 -1
  27. package/dist/clis/bilibili/subtitle.js +1 -1
  28. package/dist/clis/bilibili/user-videos.js +1 -1
  29. package/dist/clis/boss/batchgreet.js +10 -97
  30. package/dist/clis/boss/chatlist.js +8 -25
  31. package/dist/clis/boss/chatmsg.js +11 -42
  32. package/dist/clis/boss/common.d.ts +92 -0
  33. package/dist/clis/boss/common.js +223 -0
  34. package/dist/clis/boss/detail.js +7 -49
  35. package/dist/clis/boss/exchange.js +13 -79
  36. package/dist/clis/boss/greet.js +18 -145
  37. package/dist/clis/boss/invite.js +26 -121
  38. package/dist/clis/boss/joblist.js +6 -31
  39. package/dist/clis/boss/mark.js +12 -85
  40. package/dist/clis/boss/recommend.js +10 -49
  41. package/dist/clis/boss/resume.js +18 -118
  42. package/dist/clis/boss/search.js +12 -60
  43. package/dist/clis/boss/send.js +17 -151
  44. package/dist/clis/boss/stats.js +18 -69
  45. package/dist/clis/coupang/add-to-cart.js +1 -1
  46. package/dist/clis/devto/tag.yaml +34 -0
  47. package/dist/clis/devto/top.yaml +29 -0
  48. package/dist/clis/devto/user.yaml +33 -0
  49. package/dist/clis/douban/book-hot.d.ts +1 -0
  50. package/dist/clis/douban/book-hot.js +14 -0
  51. package/dist/clis/douban/marks.d.ts +1 -0
  52. package/dist/clis/douban/marks.js +115 -0
  53. package/dist/clis/douban/movie-hot.d.ts +1 -0
  54. package/dist/clis/douban/movie-hot.js +14 -0
  55. package/dist/clis/douban/reviews.d.ts +1 -0
  56. package/dist/clis/douban/reviews.js +106 -0
  57. package/dist/clis/douban/search.d.ts +1 -0
  58. package/dist/clis/douban/search.js +16 -0
  59. package/dist/clis/douban/shared.d.ts +4 -0
  60. package/dist/clis/douban/shared.js +155 -0
  61. package/dist/clis/douban/subject.yaml +76 -0
  62. package/dist/clis/douban/top250.yaml +70 -0
  63. package/dist/clis/douban/utils.d.ts +35 -0
  64. package/dist/clis/douban/utils.js +48 -0
  65. package/dist/clis/facebook/add-friend.yaml +43 -0
  66. package/dist/clis/facebook/events.yaml +44 -0
  67. package/dist/clis/facebook/feed.yaml +63 -0
  68. package/dist/clis/facebook/friends.yaml +42 -0
  69. package/dist/clis/facebook/groups.yaml +50 -0
  70. package/dist/clis/facebook/join-group.yaml +44 -0
  71. package/dist/clis/facebook/memories.yaml +39 -0
  72. package/dist/clis/facebook/notifications.yaml +40 -0
  73. package/dist/clis/facebook/profile.yaml +37 -0
  74. package/dist/clis/facebook/search.yaml +46 -0
  75. package/dist/clis/google/news.d.ts +5 -0
  76. package/dist/clis/google/news.js +58 -0
  77. package/dist/clis/google/search.d.ts +10 -0
  78. package/dist/clis/google/search.js +127 -0
  79. package/dist/clis/google/suggest.d.ts +5 -0
  80. package/dist/clis/google/suggest.js +34 -0
  81. package/dist/clis/google/trends.d.ts +5 -0
  82. package/dist/clis/google/trends.js +38 -0
  83. package/dist/clis/google/utils.d.ts +9 -0
  84. package/dist/clis/google/utils.js +23 -0
  85. package/dist/clis/google/utils.test.d.ts +1 -0
  86. package/dist/clis/google/utils.test.js +75 -0
  87. package/dist/clis/grok/ask.d.ts +14 -0
  88. package/dist/clis/grok/ask.js +257 -65
  89. package/dist/clis/grok/ask.test.d.ts +1 -0
  90. package/dist/clis/grok/ask.test.js +36 -0
  91. package/dist/clis/instagram/comment.yaml +52 -0
  92. package/dist/clis/instagram/explore.yaml +43 -0
  93. package/dist/clis/instagram/follow.yaml +41 -0
  94. package/dist/clis/instagram/followers.yaml +51 -0
  95. package/dist/clis/instagram/following.yaml +51 -0
  96. package/dist/clis/instagram/like.yaml +46 -0
  97. package/dist/clis/instagram/profile.yaml +42 -0
  98. package/dist/clis/instagram/save.yaml +46 -0
  99. package/dist/clis/instagram/saved.yaml +40 -0
  100. package/dist/clis/instagram/search.yaml +43 -0
  101. package/dist/clis/instagram/unfollow.yaml +38 -0
  102. package/dist/clis/instagram/unlike.yaml +46 -0
  103. package/dist/clis/instagram/unsave.yaml +46 -0
  104. package/dist/clis/instagram/user.yaml +54 -0
  105. package/dist/clis/jike/repost.js +1 -1
  106. package/dist/clis/jimeng/generate.yaml +1 -0
  107. package/dist/clis/linux-do/category.yaml +1 -0
  108. package/dist/clis/lobsters/active.yaml +29 -0
  109. package/dist/clis/lobsters/hot.yaml +29 -0
  110. package/dist/clis/lobsters/newest.yaml +29 -0
  111. package/dist/clis/lobsters/tag.yaml +34 -0
  112. package/dist/clis/medium/feed.d.ts +1 -0
  113. package/dist/clis/medium/feed.js +15 -0
  114. package/dist/clis/medium/search.d.ts +1 -0
  115. package/dist/clis/medium/search.js +15 -0
  116. package/dist/clis/medium/shared.d.ts +5 -0
  117. package/dist/clis/medium/shared.js +78 -0
  118. package/dist/clis/medium/user.d.ts +1 -0
  119. package/dist/clis/medium/user.js +15 -0
  120. package/dist/clis/reddit/comment.js +1 -1
  121. package/dist/clis/reddit/read.js +1 -1
  122. package/dist/clis/reddit/save.js +1 -1
  123. package/dist/clis/reddit/subreddit.yaml +1 -0
  124. package/dist/clis/reddit/subscribe.js +1 -1
  125. package/dist/clis/reddit/upvote.js +1 -1
  126. package/dist/clis/sinablog/article.d.ts +1 -0
  127. package/dist/clis/sinablog/article.js +14 -0
  128. package/dist/clis/sinablog/hot.d.ts +1 -0
  129. package/dist/clis/sinablog/hot.js +14 -0
  130. package/dist/clis/sinablog/search.d.ts +1 -0
  131. package/dist/clis/sinablog/search.js +51 -0
  132. package/dist/clis/sinablog/shared.d.ts +7 -0
  133. package/dist/clis/sinablog/shared.js +187 -0
  134. package/dist/clis/sinablog/user.d.ts +1 -0
  135. package/dist/clis/sinablog/user.js +15 -0
  136. package/dist/clis/substack/feed.d.ts +1 -0
  137. package/dist/clis/substack/feed.js +15 -0
  138. package/dist/clis/substack/publication.d.ts +1 -0
  139. package/dist/clis/substack/publication.js +15 -0
  140. package/dist/clis/substack/search.d.ts +1 -0
  141. package/dist/clis/substack/search.js +77 -0
  142. package/dist/clis/substack/shared.d.ts +4 -0
  143. package/dist/clis/substack/shared.js +129 -0
  144. package/dist/clis/tiktok/comment.yaml +66 -0
  145. package/dist/clis/tiktok/explore.yaml +39 -0
  146. package/dist/clis/tiktok/follow.yaml +39 -0
  147. package/dist/clis/tiktok/following.yaml +46 -0
  148. package/dist/clis/tiktok/friends.yaml +47 -0
  149. package/dist/clis/tiktok/like.yaml +38 -0
  150. package/dist/clis/tiktok/live.yaml +51 -0
  151. package/dist/clis/tiktok/notifications.yaml +52 -0
  152. package/dist/clis/tiktok/profile.yaml +45 -0
  153. package/dist/clis/tiktok/save.yaml +34 -0
  154. package/dist/clis/tiktok/search.yaml +46 -0
  155. package/dist/clis/tiktok/unfollow.yaml +44 -0
  156. package/dist/clis/tiktok/unlike.yaml +38 -0
  157. package/dist/clis/tiktok/unsave.yaml +36 -0
  158. package/dist/clis/tiktok/user.yaml +44 -0
  159. package/dist/clis/twitter/download.d.ts +1 -1
  160. package/dist/clis/twitter/download.js +3 -3
  161. package/dist/clis/twitter/followers.js +1 -1
  162. package/dist/clis/twitter/following.js +1 -1
  163. package/dist/clis/twitter/thread.js +1 -1
  164. package/dist/clis/twitter/timeline.d.ts +23 -0
  165. package/dist/clis/twitter/timeline.js +42 -14
  166. package/dist/clis/twitter/timeline.test.d.ts +1 -0
  167. package/dist/clis/twitter/timeline.test.js +102 -0
  168. package/dist/clis/wikipedia/random.d.ts +1 -0
  169. package/dist/clis/wikipedia/random.js +19 -0
  170. package/dist/clis/wikipedia/search.js +3 -3
  171. package/dist/clis/wikipedia/summary.js +4 -9
  172. package/dist/clis/wikipedia/trending.d.ts +1 -0
  173. package/dist/clis/wikipedia/trending.js +35 -0
  174. package/dist/clis/wikipedia/utils.d.ts +28 -0
  175. package/dist/clis/wikipedia/utils.js +13 -0
  176. package/dist/clis/xiaohongshu/creator-note-detail.js +1 -1
  177. package/dist/clis/xiaohongshu/creator-note-detail.test.js +2 -0
  178. package/dist/clis/xiaohongshu/creator-notes.test.js +2 -0
  179. package/dist/clis/xiaohongshu/download.js +1 -1
  180. package/dist/clis/xueqiu/earnings-date.yaml +69 -0
  181. package/dist/clis/xueqiu/search.yaml +2 -1
  182. package/dist/clis/xueqiu/stock.yaml +2 -0
  183. package/dist/clis/yahoo-finance/quote.js +1 -1
  184. package/dist/commanderAdapter.js +13 -7
  185. package/dist/discovery.d.ts +8 -0
  186. package/dist/discovery.js +105 -19
  187. package/dist/doctor.js +3 -1
  188. package/dist/doctor.test.js +46 -2
  189. package/dist/engine.test.d.ts +0 -3
  190. package/dist/engine.test.js +74 -6
  191. package/dist/execution.d.ts +4 -2
  192. package/dist/execution.js +31 -7
  193. package/dist/explore.d.ts +76 -3
  194. package/dist/explore.js +11 -4
  195. package/dist/generate.d.ts +41 -2
  196. package/dist/generate.js +5 -4
  197. package/dist/main.js +2 -1
  198. package/dist/pipeline/executor.d.ts +2 -2
  199. package/dist/pipeline/executor.js +2 -2
  200. package/dist/pipeline/executor.test.js +33 -6
  201. package/dist/pipeline/registry.d.ts +1 -1
  202. package/dist/pipeline/steps/browser.d.ts +7 -7
  203. package/dist/pipeline/steps/browser.js +15 -7
  204. package/dist/pipeline/steps/fetch.d.ts +1 -1
  205. package/dist/pipeline/steps/fetch.js +11 -7
  206. package/dist/pipeline/steps/transform.d.ts +6 -5
  207. package/dist/pipeline/steps/transform.js +30 -9
  208. package/dist/pipeline/template.d.ts +6 -6
  209. package/dist/pipeline/template.js +43 -5
  210. package/dist/pipeline/template.test.js +18 -0
  211. package/dist/pipeline/transform.test.js +11 -0
  212. package/dist/plugin.d.ts +31 -0
  213. package/dist/plugin.js +216 -0
  214. package/dist/plugin.test.d.ts +4 -0
  215. package/dist/plugin.test.js +76 -0
  216. package/dist/registry-api.d.ts +11 -0
  217. package/dist/registry-api.js +9 -0
  218. package/dist/registry.d.ts +11 -0
  219. package/dist/registry.js +6 -1
  220. package/dist/synthesize.d.ts +94 -4
  221. package/dist/synthesize.js +5 -4
  222. package/dist/types.d.ts +39 -26
  223. package/dist/validate.js +8 -2
  224. package/docs/.vitepress/config.mts +6 -4
  225. package/docs/adapters/browser/barchart.md +6 -5
  226. package/docs/adapters/browser/bilibili.md +9 -0
  227. package/docs/adapters/browser/devto.md +35 -0
  228. package/docs/adapters/browser/douban.md +38 -0
  229. package/docs/adapters/browser/facebook.md +36 -0
  230. package/docs/adapters/browser/google.md +62 -0
  231. package/docs/adapters/browser/grok.md +26 -8
  232. package/docs/adapters/browser/instagram.md +46 -0
  233. package/docs/adapters/browser/lobsters.md +32 -0
  234. package/docs/adapters/browser/medium.md +32 -0
  235. package/docs/adapters/browser/reddit.md +9 -0
  236. package/docs/adapters/browser/sinablog.md +36 -0
  237. package/docs/adapters/browser/substack.md +38 -0
  238. package/docs/adapters/browser/tiktok.md +68 -0
  239. package/docs/adapters/browser/wikipedia.md +11 -2
  240. package/docs/adapters/browser/xueqiu.md +10 -0
  241. package/docs/adapters/browser/yahoo-finance.md +6 -5
  242. package/docs/adapters/desktop/antigravity.md +6 -0
  243. package/docs/adapters/desktop/chatgpt.md +2 -1
  244. package/docs/adapters/desktop/codex.md +5 -1
  245. package/docs/adapters/desktop/cursor.md +4 -0
  246. package/docs/adapters/desktop/discord.md +7 -7
  247. package/docs/adapters/index.md +1 -4
  248. package/docs/guide/getting-started.md +1 -0
  249. package/docs/guide/plugins.md +153 -0
  250. package/docs/zh/guide/plugins.md +107 -0
  251. package/extension/src/background.ts +18 -11
  252. package/package.json +10 -5
  253. package/scripts/clean-dist.cjs +13 -0
  254. package/src/browser/cdp.ts +71 -31
  255. package/src/browser/daemon-client.ts +2 -1
  256. package/src/browser/dom-helpers.ts +38 -7
  257. package/src/browser/dom-snapshot.test.ts +249 -0
  258. package/src/browser/dom-snapshot.ts +770 -0
  259. package/src/browser/index.ts +2 -0
  260. package/src/browser/page.ts +50 -19
  261. package/src/build-manifest.test.ts +70 -2
  262. package/src/build-manifest.ts +94 -26
  263. package/src/cli.ts +71 -2
  264. package/src/clis/barchart/greeks.ts +1 -1
  265. package/src/clis/barchart/options.ts +1 -1
  266. package/src/clis/barchart/quote.ts +1 -1
  267. package/src/clis/bilibili/download.ts +1 -1
  268. package/src/clis/bilibili/following.ts +1 -1
  269. package/src/clis/bilibili/subtitle.ts +1 -1
  270. package/src/clis/bilibili/user-videos.ts +1 -1
  271. package/src/clis/boss/batchgreet.ts +14 -106
  272. package/src/clis/boss/chatlist.ts +12 -26
  273. package/src/clis/boss/chatmsg.ts +16 -40
  274. package/src/clis/boss/common.ts +287 -0
  275. package/src/clis/boss/detail.ts +8 -54
  276. package/src/clis/boss/exchange.ts +15 -89
  277. package/src/clis/boss/greet.ts +23 -160
  278. package/src/clis/boss/invite.ts +36 -133
  279. package/src/clis/boss/joblist.ts +7 -36
  280. package/src/clis/boss/mark.ts +13 -94
  281. package/src/clis/boss/recommend.ts +12 -57
  282. package/src/clis/boss/resume.ts +19 -124
  283. package/src/clis/boss/search.ts +13 -66
  284. package/src/clis/boss/send.ts +21 -161
  285. package/src/clis/boss/stats.ts +19 -74
  286. package/src/clis/coupang/add-to-cart.ts +1 -1
  287. package/src/clis/devto/tag.yaml +34 -0
  288. package/src/clis/devto/top.yaml +29 -0
  289. package/src/clis/devto/user.yaml +33 -0
  290. package/src/clis/douban/book-hot.ts +15 -0
  291. package/src/clis/douban/marks.ts +135 -0
  292. package/src/clis/douban/movie-hot.ts +15 -0
  293. package/src/clis/douban/reviews.ts +127 -0
  294. package/src/clis/douban/search.ts +17 -0
  295. package/src/clis/douban/shared.ts +165 -0
  296. package/src/clis/douban/subject.yaml +76 -0
  297. package/src/clis/douban/top250.yaml +70 -0
  298. package/src/clis/douban/utils.ts +81 -0
  299. package/src/clis/facebook/add-friend.yaml +43 -0
  300. package/src/clis/facebook/events.yaml +44 -0
  301. package/src/clis/facebook/feed.yaml +63 -0
  302. package/src/clis/facebook/friends.yaml +42 -0
  303. package/src/clis/facebook/groups.yaml +50 -0
  304. package/src/clis/facebook/join-group.yaml +44 -0
  305. package/src/clis/facebook/memories.yaml +39 -0
  306. package/src/clis/facebook/notifications.yaml +40 -0
  307. package/src/clis/facebook/profile.yaml +37 -0
  308. package/src/clis/facebook/search.yaml +46 -0
  309. package/src/clis/google/news.ts +66 -0
  310. package/src/clis/google/search.ts +133 -0
  311. package/src/clis/google/suggest.ts +40 -0
  312. package/src/clis/google/trends.ts +44 -0
  313. package/src/clis/google/utils.test.ts +82 -0
  314. package/src/clis/google/utils.ts +24 -0
  315. package/src/clis/grok/ask.test.ts +53 -0
  316. package/src/clis/grok/ask.ts +300 -69
  317. package/src/clis/instagram/comment.yaml +52 -0
  318. package/src/clis/instagram/explore.yaml +43 -0
  319. package/src/clis/instagram/follow.yaml +41 -0
  320. package/src/clis/instagram/followers.yaml +51 -0
  321. package/src/clis/instagram/following.yaml +51 -0
  322. package/src/clis/instagram/like.yaml +46 -0
  323. package/src/clis/instagram/profile.yaml +42 -0
  324. package/src/clis/instagram/save.yaml +46 -0
  325. package/src/clis/instagram/saved.yaml +40 -0
  326. package/src/clis/instagram/search.yaml +43 -0
  327. package/src/clis/instagram/unfollow.yaml +38 -0
  328. package/src/clis/instagram/unlike.yaml +46 -0
  329. package/src/clis/instagram/unsave.yaml +46 -0
  330. package/src/clis/instagram/user.yaml +54 -0
  331. package/src/clis/jike/repost.ts +1 -1
  332. package/src/clis/jimeng/generate.yaml +1 -0
  333. package/src/clis/linux-do/category.yaml +1 -0
  334. package/src/clis/lobsters/active.yaml +29 -0
  335. package/src/clis/lobsters/hot.yaml +29 -0
  336. package/src/clis/lobsters/newest.yaml +29 -0
  337. package/src/clis/lobsters/tag.yaml +34 -0
  338. package/src/clis/medium/feed.ts +16 -0
  339. package/src/clis/medium/search.ts +16 -0
  340. package/src/clis/medium/shared.ts +83 -0
  341. package/src/clis/medium/user.ts +16 -0
  342. package/src/clis/reddit/comment.ts +1 -1
  343. package/src/clis/reddit/read.ts +1 -1
  344. package/src/clis/reddit/save.ts +1 -1
  345. package/src/clis/reddit/subreddit.yaml +1 -0
  346. package/src/clis/reddit/subscribe.ts +1 -1
  347. package/src/clis/reddit/upvote.ts +1 -1
  348. package/src/clis/sinablog/article.ts +15 -0
  349. package/src/clis/sinablog/hot.ts +15 -0
  350. package/src/clis/sinablog/search.ts +56 -0
  351. package/src/clis/sinablog/shared.ts +198 -0
  352. package/src/clis/sinablog/user.ts +16 -0
  353. package/src/clis/substack/feed.ts +16 -0
  354. package/src/clis/substack/publication.ts +16 -0
  355. package/src/clis/substack/search.ts +91 -0
  356. package/src/clis/substack/shared.ts +132 -0
  357. package/src/clis/tiktok/comment.yaml +66 -0
  358. package/src/clis/tiktok/explore.yaml +39 -0
  359. package/src/clis/tiktok/follow.yaml +39 -0
  360. package/src/clis/tiktok/following.yaml +46 -0
  361. package/src/clis/tiktok/friends.yaml +47 -0
  362. package/src/clis/tiktok/like.yaml +38 -0
  363. package/src/clis/tiktok/live.yaml +51 -0
  364. package/src/clis/tiktok/notifications.yaml +52 -0
  365. package/src/clis/tiktok/profile.yaml +45 -0
  366. package/src/clis/tiktok/save.yaml +34 -0
  367. package/src/clis/tiktok/search.yaml +46 -0
  368. package/src/clis/tiktok/unfollow.yaml +44 -0
  369. package/src/clis/tiktok/unlike.yaml +38 -0
  370. package/src/clis/tiktok/unsave.yaml +36 -0
  371. package/src/clis/tiktok/user.yaml +44 -0
  372. package/src/clis/twitter/download.ts +3 -3
  373. package/src/clis/twitter/followers.ts +1 -1
  374. package/src/clis/twitter/following.ts +1 -1
  375. package/src/clis/twitter/thread.ts +1 -1
  376. package/src/clis/twitter/timeline.test.ts +109 -0
  377. package/src/clis/twitter/timeline.ts +59 -19
  378. package/src/clis/wikipedia/random.ts +19 -0
  379. package/src/clis/wikipedia/search.ts +10 -4
  380. package/src/clis/wikipedia/summary.ts +4 -9
  381. package/src/clis/wikipedia/trending.ts +41 -0
  382. package/src/clis/wikipedia/utils.ts +31 -0
  383. package/src/clis/xiaohongshu/creator-note-detail.test.ts +2 -0
  384. package/src/clis/xiaohongshu/creator-note-detail.ts +1 -1
  385. package/src/clis/xiaohongshu/creator-notes.test.ts +2 -0
  386. package/src/clis/xiaohongshu/download.ts +1 -1
  387. package/src/clis/xueqiu/earnings-date.yaml +69 -0
  388. package/src/clis/xueqiu/search.yaml +2 -1
  389. package/src/clis/xueqiu/stock.yaml +2 -0
  390. package/src/clis/yahoo-finance/quote.ts +1 -1
  391. package/src/commanderAdapter.ts +17 -10
  392. package/src/discovery.ts +134 -24
  393. package/src/doctor.test.ts +59 -2
  394. package/src/doctor.ts +4 -2
  395. package/src/engine.test.ts +79 -6
  396. package/src/execution.ts +42 -16
  397. package/src/explore.ts +77 -9
  398. package/src/generate.ts +58 -9
  399. package/src/main.ts +2 -1
  400. package/src/pipeline/executor.test.ts +35 -6
  401. package/src/pipeline/executor.ts +11 -7
  402. package/src/pipeline/registry.ts +3 -3
  403. package/src/pipeline/steps/browser.ts +24 -15
  404. package/src/pipeline/steps/fetch.ts +18 -13
  405. package/src/pipeline/steps/transform.ts +40 -15
  406. package/src/pipeline/template.test.ts +18 -0
  407. package/src/pipeline/template.ts +86 -13
  408. package/src/pipeline/transform.test.ts +15 -2
  409. package/src/plugin.test.ts +86 -0
  410. package/src/plugin.ts +254 -0
  411. package/src/registry-api.ts +12 -0
  412. package/src/registry.ts +19 -1
  413. package/src/synthesize.ts +102 -21
  414. package/src/types.ts +44 -12
  415. package/src/validate.ts +19 -4
  416. package/tests/e2e/browser-public.test.ts +11 -0
  417. package/tests/e2e/public-commands.test.ts +64 -0
  418. package/dist/clis/feishu/new.d.ts +0 -1
  419. package/dist/clis/feishu/new.js +0 -27
  420. package/dist/clis/feishu/read.d.ts +0 -1
  421. package/dist/clis/feishu/read.js +0 -40
  422. package/dist/clis/feishu/search.d.ts +0 -1
  423. package/dist/clis/feishu/search.js +0 -30
  424. package/dist/clis/feishu/send.d.ts +0 -1
  425. package/dist/clis/feishu/send.js +0 -39
  426. package/dist/clis/feishu/status.d.ts +0 -1
  427. package/dist/clis/feishu/status.js +0 -28
  428. package/dist/clis/neteasemusic/like.d.ts +0 -1
  429. package/dist/clis/neteasemusic/like.js +0 -25
  430. package/dist/clis/neteasemusic/lyrics.d.ts +0 -1
  431. package/dist/clis/neteasemusic/lyrics.js +0 -47
  432. package/dist/clis/neteasemusic/next.d.ts +0 -1
  433. package/dist/clis/neteasemusic/next.js +0 -26
  434. package/dist/clis/neteasemusic/play.d.ts +0 -1
  435. package/dist/clis/neteasemusic/play.js +0 -26
  436. package/dist/clis/neteasemusic/playing.d.ts +0 -1
  437. package/dist/clis/neteasemusic/playing.js +0 -59
  438. package/dist/clis/neteasemusic/playlist.d.ts +0 -1
  439. package/dist/clis/neteasemusic/playlist.js +0 -46
  440. package/dist/clis/neteasemusic/prev.d.ts +0 -1
  441. package/dist/clis/neteasemusic/prev.js +0 -25
  442. package/dist/clis/neteasemusic/search.d.ts +0 -1
  443. package/dist/clis/neteasemusic/search.js +0 -52
  444. package/dist/clis/neteasemusic/status.d.ts +0 -1
  445. package/dist/clis/neteasemusic/status.js +0 -16
  446. package/dist/clis/neteasemusic/volume.d.ts +0 -1
  447. package/dist/clis/neteasemusic/volume.js +0 -54
  448. package/dist/clis/wechat/chats.d.ts +0 -1
  449. package/dist/clis/wechat/chats.js +0 -28
  450. package/dist/clis/wechat/contacts.d.ts +0 -1
  451. package/dist/clis/wechat/contacts.js +0 -28
  452. package/dist/clis/wechat/read.d.ts +0 -1
  453. package/dist/clis/wechat/read.js +0 -58
  454. package/dist/clis/wechat/search.d.ts +0 -1
  455. package/dist/clis/wechat/search.js +0 -31
  456. package/dist/clis/wechat/send.d.ts +0 -1
  457. package/dist/clis/wechat/send.js +0 -42
  458. package/dist/clis/wechat/status.d.ts +0 -1
  459. package/dist/clis/wechat/status.js +0 -29
  460. package/dist/pipeline.d.ts +0 -7
  461. package/dist/pipeline.js +0 -7
  462. package/docs/adapters/browser/github.md +0 -26
  463. package/docs/adapters/desktop/feishu.md +0 -20
  464. package/docs/adapters/desktop/neteasemusic.md +0 -31
  465. package/docs/adapters/desktop/wechat.md +0 -28
  466. package/src/clis/antigravity/README.md +0 -5
  467. package/src/clis/antigravity/README.zh-CN.md +0 -51
  468. package/src/clis/chaoxing/README.md +0 -14
  469. package/src/clis/chaoxing/README.zh-CN.md +0 -35
  470. package/src/clis/chatgpt/README.md +0 -5
  471. package/src/clis/chatgpt/README.zh-CN.md +0 -44
  472. package/src/clis/chatwise/README.md +0 -5
  473. package/src/clis/chatwise/README.zh-CN.md +0 -38
  474. package/src/clis/codex/README.md +0 -5
  475. package/src/clis/codex/README.zh-CN.md +0 -33
  476. package/src/clis/cursor/README.md +0 -5
  477. package/src/clis/cursor/README.zh-CN.md +0 -33
  478. package/src/clis/discord-app/README.md +0 -5
  479. package/src/clis/discord-app/README.zh-CN.md +0 -28
  480. package/src/clis/feishu/README.md +0 -5
  481. package/src/clis/feishu/README.zh-CN.md +0 -20
  482. package/src/clis/feishu/new.ts +0 -32
  483. package/src/clis/feishu/read.ts +0 -48
  484. package/src/clis/feishu/search.ts +0 -35
  485. package/src/clis/feishu/send.ts +0 -46
  486. package/src/clis/feishu/status.ts +0 -34
  487. package/src/clis/neteasemusic/README.md +0 -5
  488. package/src/clis/neteasemusic/README.zh-CN.md +0 -31
  489. package/src/clis/neteasemusic/like.ts +0 -28
  490. package/src/clis/neteasemusic/lyrics.ts +0 -53
  491. package/src/clis/neteasemusic/next.ts +0 -30
  492. package/src/clis/neteasemusic/play.ts +0 -30
  493. package/src/clis/neteasemusic/playing.ts +0 -62
  494. package/src/clis/neteasemusic/playlist.ts +0 -51
  495. package/src/clis/neteasemusic/prev.ts +0 -29
  496. package/src/clis/neteasemusic/search.ts +0 -58
  497. package/src/clis/neteasemusic/status.ts +0 -18
  498. package/src/clis/neteasemusic/volume.ts +0 -61
  499. package/src/clis/notion/README.md +0 -5
  500. package/src/clis/notion/README.zh-CN.md +0 -29
  501. package/src/clis/wechat/README.md +0 -5
  502. package/src/clis/wechat/README.zh-CN.md +0 -28
  503. package/src/clis/wechat/chats.ts +0 -33
  504. package/src/clis/wechat/contacts.ts +0 -33
  505. package/src/clis/wechat/read.ts +0 -72
  506. package/src/clis/wechat/search.ts +0 -36
  507. package/src/clis/wechat/send.ts +0 -49
  508. package/src/clis/wechat/status.ts +0 -35
  509. package/src/pipeline.ts +0 -8
@@ -1,11 +1,14 @@
1
1
  /**
2
2
  * BOSS直聘 send message — via UI automation on chat page.
3
3
  *
4
- * Flow: navigate to chat click on user in list type in editor → send.
5
- * BOSS chat uses MQTT (not HTTP) for messaging, so we must go through the UI.
4
+ * BOSS chat uses MQTT (not HTTP) for messaging, so we must go through the UI
5
+ * rather than making direct API calls.
6
6
  */
7
7
  import { cli, Strategy } from '../../registry.js';
8
- import type { IPage } from '../../types.js';
8
+ import {
9
+ requirePage, navigateToChat, findFriendByUid,
10
+ clickCandidateInList, typeAndSendMessage,
11
+ } from './common.js';
9
12
 
10
13
  cli({
11
14
  site: 'boss',
@@ -13,181 +16,38 @@ cli({
13
16
  description: 'BOSS直聘发送聊天消息',
14
17
  domain: 'www.zhipin.com',
15
18
  strategy: Strategy.COOKIE,
16
-
19
+ navigateBefore: false,
17
20
  browser: true,
18
21
  args: [
19
- { name: 'uid', required: true, help: 'Encrypted UID of the candidate (from chatlist)' },
22
+ { name: 'uid', positional: true, required: true, help: 'Encrypted UID of the candidate (from chatlist)' },
20
23
  { name: 'text', required: true, positional: true, help: 'Message text to send' },
21
24
  ],
22
25
  columns: ['status', 'detail'],
23
- func: async (page: IPage | null, kwargs) => {
24
- if (!page) throw new Error('Browser page required');
25
-
26
- const uid = kwargs.uid;
27
- const text = kwargs.text;
28
-
29
- // Step 1: Navigate to chat page
30
- await page.goto('https://www.zhipin.com/web/chat/index');
31
- await page.wait({ time: 3 });
32
-
33
- // Step 2: Find friend in list to get their numeric uid, then click
34
- const friendData: any = await page.evaluate(`
35
- async () => {
36
- return new Promise((resolve, reject) => {
37
- const xhr = new XMLHttpRequest();
38
- xhr.open('GET', 'https://www.zhipin.com/wapi/zprelation/friend/getBossFriendListV2.json?page=1&status=0&jobId=0', true);
39
- xhr.withCredentials = true;
40
- xhr.timeout = 15000;
41
- xhr.setRequestHeader('Accept', 'application/json');
42
- xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(e); } };
43
- xhr.onerror = () => reject(new Error('Network Error'));
44
- xhr.send();
45
- });
46
- }
47
- `);
48
-
49
- if (friendData.code !== 0) {
50
- if (friendData.code === 7 || friendData.code === 37) {
51
- throw new Error('Cookie 已过期!请在当前 Chrome 浏览器中重新登录 BOSS 直聘。');
52
- }
53
- throw new Error('获取好友列表失败: ' + (friendData.message || friendData.code));
54
- }
55
-
56
- let target: any = null;
57
- const allFriends = friendData.zpData?.friendList || [];
58
- target = allFriends.find((f: any) => f.encryptUid === uid);
26
+ func: async (page, kwargs) => {
27
+ requirePage(page);
59
28
 
60
- if (!target) {
61
- for (let p = 2; p <= 5; p++) {
62
- const moreUrl = `https://www.zhipin.com/wapi/zprelation/friend/getBossFriendListV2.json?page=${p}&status=0&jobId=0`;
63
- const moreData: any = await page.evaluate(`
64
- async () => {
65
- return new Promise((resolve, reject) => {
66
- const xhr = new XMLHttpRequest();
67
- xhr.open('GET', '${moreUrl}', true);
68
- xhr.withCredentials = true;
69
- xhr.timeout = 15000;
70
- xhr.setRequestHeader('Accept', 'application/json');
71
- xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(e); } };
72
- xhr.onerror = () => reject(new Error('Network Error'));
73
- xhr.send();
74
- });
75
- }
76
- `);
77
- if (moreData.code === 0) {
78
- const list = moreData.zpData?.friendList || [];
79
- target = list.find((f: any) => f.encryptUid === uid);
80
- if (target) break;
81
- if (list.length === 0) break;
82
- }
83
- }
84
- }
85
-
86
- if (!target) throw new Error('未找到该候选人,请确认 uid 是否正确');
29
+ await navigateToChat(page, 3);
87
30
 
88
- const numericUid = target.uid;
89
- const friendName = target.name || '候选人';
31
+ const friend = await findFriendByUid(page, kwargs.uid, { maxPages: 5 });
32
+ if (!friend) throw new Error('未找到该候选人,请确认 uid 是否正确');
90
33
 
91
- // Step 3: Click on the user in the chat list to open conversation
92
- const clicked: any = await page.evaluate(`
93
- async () => {
94
- // The geek-item has id like _748787762-0
95
- const item = document.querySelector('#_${numericUid}-0') || document.querySelector('[id^="_${numericUid}"]');
96
- if (item) {
97
- item.click();
98
- return { clicked: true, id: item.id };
99
- }
100
- // Fallback: try clicking by iterating geek items
101
- const items = document.querySelectorAll('.geek-item');
102
- for (const el of items) {
103
- if (el.id && el.id.startsWith('_${numericUid}')) {
104
- el.click();
105
- return { clicked: true, id: el.id };
106
- }
107
- }
108
- return { clicked: false };
109
- }
110
- `);
34
+ const numericUid = friend.uid;
35
+ const friendName = friend.name || '候选人';
111
36
 
112
- if (!clicked.clicked) {
37
+ const clicked = await clickCandidateInList(page, numericUid);
38
+ if (!clicked) {
113
39
  throw new Error('无法在聊天列表中找到该用户,请确认聊天列表中有此人');
114
40
  }
115
41
 
116
- // Step 4: Wait for the conversation to load and input area to appear
117
42
  await page.wait({ time: 2 });
118
43
 
119
- // Step 5: Find the message editor and type
120
- const typed: any = await page.evaluate(`
121
- async () => {
122
- // Look for the chat editor - BOSS uses contenteditable div or textarea
123
- const selectors = [
124
- '.chat-editor [contenteditable="true"]',
125
- '.chat-input [contenteditable="true"]',
126
- '.message-editor [contenteditable="true"]',
127
- '.chat-conversation [contenteditable="true"]',
128
- '[contenteditable="true"]',
129
- '.chat-editor textarea',
130
- '.chat-input textarea',
131
- 'textarea',
132
- ];
133
-
134
- for (const sel of selectors) {
135
- const el = document.querySelector(sel);
136
- if (el && el.offsetParent !== null) {
137
- el.focus();
138
-
139
- if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {
140
- el.value = ${JSON.stringify(text)};
141
- el.dispatchEvent(new Event('input', { bubbles: true }));
142
- el.dispatchEvent(new Event('change', { bubbles: true }));
143
- } else {
144
- // contenteditable
145
- el.textContent = '';
146
- el.focus();
147
- document.execCommand('insertText', false, ${JSON.stringify(text)});
148
- el.dispatchEvent(new Event('input', { bubbles: true }));
149
- }
150
-
151
- return { found: true, selector: sel, tag: el.tagName };
152
- }
153
- }
154
-
155
- // Debug: list all visible elements in chat-conversation
156
- const conv = document.querySelector('.chat-conversation');
157
- const allEls = conv ? Array.from(conv.querySelectorAll('*')).filter(e => e.offsetParent !== null).map(e => e.tagName + '.' + (e.className?.substring?.(0, 50) || '')).slice(0, 30) : [];
158
-
159
- return { found: false, visibleElements: allEls };
160
- }
161
- `);
162
-
163
- if (!typed.found) {
164
- throw new Error('找不到消息输入框。可能的元素: ' + JSON.stringify(typed.visibleElements || []));
165
- }
166
-
167
- await page.wait({ time: 0.5 });
168
-
169
- // Step 6: Click the send button (Enter key doesn't trigger send on BOSS)
170
- const sent: any = await page.evaluate(`
171
- async () => {
172
- // The send button is .submit inside .submit-content
173
- const btn = document.querySelector('.conversation-editor .submit')
174
- || document.querySelector('.submit-content .submit')
175
- || document.querySelector('.conversation-operate .submit');
176
- if (btn) {
177
- btn.click();
178
- return { clicked: true };
179
- }
180
- return { clicked: false };
181
- }
182
- `);
183
-
184
- if (!sent.clicked) {
185
- // Fallback: try Enter key
186
- await page.pressKey('Enter');
44
+ const sent = await typeAndSendMessage(page, kwargs.text);
45
+ if (!sent) {
46
+ throw new Error('找不到消息输入框');
187
47
  }
188
48
 
189
49
  await page.wait({ time: 1 });
190
50
 
191
- return [{ status: '✅ 发送成功', detail: `已向 ${friendName} 发送: ${text}` }];
51
+ return [{ status: '✅ 发送成功', detail: `已向 ${friendName} 发送: ${kwargs.text}` }];
192
52
  },
193
53
  });
@@ -1,13 +1,8 @@
1
1
  /**
2
2
  * BOSS直聘 stats — job statistics overview.
3
- *
4
- * Uses /wapi/zpchat/chatHelper/statistics for total friend count,
5
- * and /wapi/zpjob/job/chatted/jobList for per-job info.
6
- * Since BOSS doesn't expose detailed per-job stats via API,
7
- * we show what's available: job status, chat info, and total stats.
8
3
  */
9
4
  import { cli, Strategy } from '../../registry.js';
10
- import type { IPage } from '../../types.js';
5
+ import { requirePage, navigateToChat, bossFetch, fetchFriendList, verbose } from './common.js';
11
6
 
12
7
  cli({
13
8
  site: 'boss',
@@ -15,88 +10,39 @@ cli({
15
10
  description: 'BOSS直聘职位数据统计',
16
11
  domain: 'www.zhipin.com',
17
12
  strategy: Strategy.COOKIE,
13
+ navigateBefore: false,
18
14
  browser: true,
19
15
  args: [
20
16
  { name: 'job-id', default: '', help: 'Encrypted job ID (show all if empty)' },
21
17
  ],
22
18
  columns: ['job_name', 'salary', 'city', 'status', 'total_chats', 'encrypt_job_id'],
23
- func: async (page: IPage | null, kwargs) => {
24
- if (!page) throw new Error('Browser page required');
19
+ func: async (page, kwargs) => {
20
+ requirePage(page);
21
+ verbose('Fetching job statistics...');
25
22
 
26
23
  const filterJobId = kwargs['job-id'] || '';
27
24
 
28
- if (process.env.OPENCLI_VERBOSE) {
29
- console.error('[opencli:boss] Fetching job statistics...');
30
- }
31
-
32
- await page.goto('https://www.zhipin.com/web/chat/index');
33
- await page.wait({ time: 2 });
25
+ await navigateToChat(page);
34
26
 
35
27
  // Get job list
36
- const jobData: any = await page.evaluate(`
37
- async () => {
38
- return new Promise((resolve, reject) => {
39
- const xhr = new XMLHttpRequest();
40
- xhr.open('GET', 'https://www.zhipin.com/wapi/zpjob/job/chatted/jobList', true);
41
- xhr.withCredentials = true;
42
- xhr.timeout = 15000;
43
- xhr.setRequestHeader('Accept', 'application/json');
44
- xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(new Error('JSON parse failed')); } };
45
- xhr.onerror = () => reject(new Error('Network Error'));
46
- xhr.ontimeout = () => reject(new Error('Timeout'));
47
- xhr.send();
48
- });
49
- }
50
- `);
51
-
52
- if (jobData.code !== 0) {
53
- if (jobData.code === 7 || jobData.code === 37) {
54
- throw new Error('Cookie 已过期!请在当前 Chrome 浏览器中重新登录 BOSS 直聘。');
55
- }
56
- throw new Error(`API error: ${jobData.message} (code=${jobData.code})`);
57
- }
58
-
59
- // Get total chat stats
60
- const chatStats: any = await page.evaluate(`
61
- async () => {
62
- return new Promise((resolve, reject) => {
63
- const xhr = new XMLHttpRequest();
64
- xhr.open('GET', 'https://www.zhipin.com/wapi/zpchat/chatHelper/statistics', true);
65
- xhr.withCredentials = true;
66
- xhr.timeout = 10000;
67
- xhr.setRequestHeader('Accept', 'application/json');
68
- xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { resolve({}); } };
69
- xhr.onerror = () => resolve({});
70
- xhr.send();
71
- });
72
- }
73
- `);
28
+ const jobData = await bossFetch(page, 'https://www.zhipin.com/wapi/zpjob/job/chatted/jobList');
74
29
 
30
+ // Get total chat stats (non-critical, allow failure)
31
+ const chatStats = await bossFetch(page, 'https://www.zhipin.com/wapi/zpchat/chatHelper/statistics', {
32
+ allowNonZero: true,
33
+ });
75
34
  const totalFriends = chatStats.zpData?.totalFriendCount || 0;
76
35
 
77
- // Get per-job chat counts from friend list
78
- const friendData: any = await page.evaluate(`
79
- async () => {
80
- return new Promise((resolve, reject) => {
81
- const xhr = new XMLHttpRequest();
82
- xhr.open('GET', 'https://www.zhipin.com/wapi/zprelation/friend/getBossFriendListV2.json?page=1&status=0&jobId=0', true);
83
- xhr.withCredentials = true;
84
- xhr.timeout = 15000;
85
- xhr.setRequestHeader('Accept', 'application/json');
86
- xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { resolve({}); } };
87
- xhr.onerror = () => resolve({});
88
- xhr.send();
89
- });
90
- }
91
- `);
36
+ // Get per-job chat counts from friend list (non-critical)
37
+ let friendList: any[] = [];
38
+ try {
39
+ friendList = await fetchFriendList(page);
40
+ } catch { /* ignore */ }
92
41
 
93
- // Count chats per job
94
42
  const jobChatCounts: Record<string, number> = {};
95
- if (friendData.code === 0) {
96
- for (const f of (friendData.zpData?.friendList || [])) {
97
- const jobName = f.jobName || 'unknown';
98
- jobChatCounts[jobName] = (jobChatCounts[jobName] || 0) + 1;
99
- }
43
+ for (const f of friendList) {
44
+ const jobName = f.jobName || 'unknown';
45
+ jobChatCounts[jobName] = (jobChatCounts[jobName] || 0) + 1;
100
46
  }
101
47
 
102
48
  let jobs = jobData.zpData || [];
@@ -113,7 +59,6 @@ cli({
113
59
  encrypt_job_id: j.encryptJobId || '',
114
60
  }));
115
61
 
116
- // Add summary row
117
62
  if (!filterJobId && results.length > 0) {
118
63
  results.push({
119
64
  job_name: '--- 总计 ---',
@@ -102,7 +102,7 @@ cli({
102
102
  strategy: Strategy.COOKIE,
103
103
  browser: true,
104
104
  args: [
105
- { name: 'product-id', required: false, help: 'Coupang product ID' },
105
+ { name: 'product-id', positional: true, required: false, help: 'Coupang product ID' },
106
106
  { name: 'url', required: false, help: 'Canonical product URL' },
107
107
  ],
108
108
  columns: ['ok', 'product_id', 'url', 'message'],
@@ -0,0 +1,34 @@
1
+ site: devto
2
+ name: tag
3
+ description: Latest DEV.to articles for a specific tag
4
+ domain: dev.to
5
+ strategy: public
6
+ browser: false
7
+
8
+ args:
9
+ tag:
10
+ type: str
11
+ required: true
12
+ positional: true
13
+ description: "Tag name (e.g. javascript, python, webdev)"
14
+ limit:
15
+ type: int
16
+ default: 20
17
+ description: Number of articles
18
+
19
+ pipeline:
20
+ - fetch:
21
+ url: https://dev.to/api/articles?tag=${{ args.tag }}&per_page=${{ args.limit }}
22
+
23
+ - map:
24
+ rank: ${{ index + 1 }}
25
+ title: ${{ item.title }}
26
+ author: ${{ item.user.username }}
27
+ reactions: ${{ item.public_reactions_count }}
28
+ comments: ${{ item.comments_count }}
29
+ tags: ${{ item.tag_list | join(', ') }}
30
+ url: ${{ item.url }}
31
+
32
+ - limit: ${{ args.limit }}
33
+
34
+ columns: [rank, title, author, reactions, comments, tags]
@@ -0,0 +1,29 @@
1
+ site: devto
2
+ name: top
3
+ description: Top DEV.to articles of the day
4
+ domain: dev.to
5
+ strategy: public
6
+ browser: false
7
+
8
+ args:
9
+ limit:
10
+ type: int
11
+ default: 20
12
+ description: Number of articles
13
+
14
+ pipeline:
15
+ - fetch:
16
+ url: https://dev.to/api/articles?top=1&per_page=${{ args.limit }}
17
+
18
+ - map:
19
+ rank: ${{ index + 1 }}
20
+ title: ${{ item.title }}
21
+ author: ${{ item.user.username }}
22
+ reactions: ${{ item.public_reactions_count }}
23
+ comments: ${{ item.comments_count }}
24
+ tags: ${{ item.tag_list | join(', ') }}
25
+ url: ${{ item.url }}
26
+
27
+ - limit: ${{ args.limit }}
28
+
29
+ columns: [rank, title, author, reactions, comments, tags]
@@ -0,0 +1,33 @@
1
+ site: devto
2
+ name: user
3
+ description: Recent DEV.to articles from a specific user
4
+ domain: dev.to
5
+ strategy: public
6
+ browser: false
7
+
8
+ args:
9
+ username:
10
+ type: str
11
+ required: true
12
+ positional: true
13
+ description: "DEV.to username (e.g. ben, thepracticaldev)"
14
+ limit:
15
+ type: int
16
+ default: 20
17
+ description: Number of articles
18
+
19
+ pipeline:
20
+ - fetch:
21
+ url: https://dev.to/api/articles?username=${{ args.username }}&per_page=${{ args.limit }}
22
+
23
+ - map:
24
+ rank: ${{ index + 1 }}
25
+ title: ${{ item.title }}
26
+ reactions: ${{ item.public_reactions_count }}
27
+ comments: ${{ item.comments_count }}
28
+ tags: ${{ item.tag_list | join(', ') }}
29
+ url: ${{ item.url }}
30
+
31
+ - limit: ${{ args.limit }}
32
+
33
+ columns: [rank, title, reactions, comments, tags]
@@ -0,0 +1,15 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { loadDoubanBookHot } from './shared.js';
3
+
4
+ cli({
5
+ site: 'douban',
6
+ name: 'book-hot',
7
+ description: '豆瓣图书热门榜单',
8
+ domain: 'book.douban.com',
9
+ strategy: Strategy.COOKIE,
10
+ args: [
11
+ { name: 'limit', type: 'int', default: 20, help: '返回的图书数量' },
12
+ ],
13
+ columns: ['rank', 'title', 'rating', 'quote', 'author', 'publisher', 'year', 'url'],
14
+ func: async (page, args) => loadDoubanBookHot(page, Number(args.limit) || 20),
15
+ });
@@ -0,0 +1,135 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import type { IPage } from '../../types.js';
3
+ import { DoubanMark, getSelfUid } from './utils.js';
4
+
5
+ cli({
6
+ site: 'douban',
7
+ name: 'marks',
8
+ description: '导出个人观影标记',
9
+ domain: 'movie.douban.com',
10
+ strategy: Strategy.COOKIE,
11
+ args: [
12
+ {
13
+ name: 'status',
14
+ default: 'collect',
15
+ choices: ['collect', 'wish', 'do', 'all'],
16
+ help: '标记类型: collect(看过), wish(想看), do(在看), all(全部)'
17
+ },
18
+ { name: 'limit', type: 'int', default: 50, help: '导出数量, 0 表示全部' },
19
+ { name: 'uid', help: '用户ID,不填则使用当前登录账号' },
20
+ ],
21
+ columns: ['title', 'year', 'myRating', 'myStatus', 'myDate', 'myComment', 'url'],
22
+ func: async (page: IPage, kwargs: { status?: string; limit?: number; uid?: string }) => {
23
+ const { status = 'collect', limit = 50, uid: providedUid } = kwargs;
24
+
25
+ const uid = providedUid || await getSelfUid(page);
26
+
27
+ const statuses = status === 'all'
28
+ ? ['collect', 'wish', 'do']
29
+ : [status];
30
+
31
+ const allMarks: DoubanMark[] = [];
32
+
33
+ for (const s of statuses) {
34
+ const remaining = limit > 0 ? limit - allMarks.length : 0;
35
+ if (limit > 0 && remaining <= 0) break;
36
+
37
+ const marks = await fetchMarks(page, uid, s, remaining);
38
+ allMarks.push(...marks);
39
+ }
40
+
41
+ return allMarks.slice(0, limit > 0 ? limit : undefined);
42
+ },
43
+ });
44
+
45
+ async function fetchMarks(
46
+ page: IPage,
47
+ uid: string,
48
+ status: string,
49
+ limit: number
50
+ ): Promise<DoubanMark[]> {
51
+ const marks: DoubanMark[] = [];
52
+ let offset = 0;
53
+ const pageSize = 30;
54
+
55
+ while (true) {
56
+ const url = `https://movie.douban.com/people/${uid}/${status}?start=${offset}&sort=time&rating=all&filter=all&mode=grid`;
57
+
58
+ await page.goto(url);
59
+
60
+ await page.wait({ time: 2 });
61
+
62
+ const pageMarks = await page.evaluate(`
63
+ () => {
64
+ const results = [];
65
+
66
+ const items = document.querySelectorAll('.item');
67
+
68
+ items.forEach(item => {
69
+ const titleLink = item.querySelector('.info a[href*="/subject/"]');
70
+ if (!titleLink) return;
71
+
72
+ const titleEl = titleLink.querySelector('em');
73
+ const titleText = titleEl?.textContent?.trim() || titleLink.textContent?.trim() || '';
74
+ const title = titleText.split('/')[0].trim();
75
+ const href = titleLink.href || '';
76
+
77
+ const idMatch = href.match(/subject\\/(\\d+)/);
78
+ const movieId = idMatch ? idMatch[1] : '';
79
+
80
+ if (!movieId || !title) return;
81
+
82
+ const ratingSpan = item.querySelector('span[class*="rating"]');
83
+ let myRating = null;
84
+ if (ratingSpan) {
85
+ const cls = ratingSpan.className || '';
86
+ const ratingMatch = cls.match(/rating(\\d)-t/);
87
+ if (ratingMatch) {
88
+ myRating = parseInt(ratingMatch[1], 10) * 2;
89
+ }
90
+ }
91
+
92
+ const dateSpan = item.querySelector('.date');
93
+ const myDate = dateSpan?.textContent?.trim() || '';
94
+
95
+ const commentSpan = item.querySelector('.comment');
96
+ const myComment = commentSpan?.textContent?.trim() || '';
97
+
98
+ const introSpan = item.querySelector('.intro');
99
+ let year = '';
100
+ if (introSpan) {
101
+ const introText = introSpan.textContent || '';
102
+ const yearMatch = introText.match(/(\\d{4})/);
103
+ year = yearMatch ? yearMatch[1] : '';
104
+ }
105
+
106
+ results.push({
107
+ movieId,
108
+ title,
109
+ year,
110
+ myRating,
111
+ myStatus: '${status}',
112
+ myComment,
113
+ myDate,
114
+ url: href || 'https://movie.douban.com/subject/' + movieId
115
+ });
116
+ });
117
+
118
+ return results;
119
+ }
120
+ `) as DoubanMark[];
121
+
122
+ if (!pageMarks || pageMarks.length === 0) break;
123
+
124
+ marks.push(...pageMarks);
125
+
126
+ if (pageMarks.length < pageSize) break;
127
+ if (limit > 0 && marks.length >= limit) break;
128
+
129
+ offset += pageSize;
130
+
131
+ await new Promise(resolve => setTimeout(resolve, 1000));
132
+ }
133
+
134
+ return marks;
135
+ }
@@ -0,0 +1,15 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { loadDoubanMovieHot } from './shared.js';
3
+
4
+ cli({
5
+ site: 'douban',
6
+ name: 'movie-hot',
7
+ description: '豆瓣电影热门榜单',
8
+ domain: 'movie.douban.com',
9
+ strategy: Strategy.COOKIE,
10
+ args: [
11
+ { name: 'limit', type: 'int', default: 20, help: '返回的电影数量' },
12
+ ],
13
+ columns: ['rank', 'title', 'rating', 'quote', 'director', 'year', 'region', 'url'],
14
+ func: async (page, args) => loadDoubanMovieHot(page, Number(args.limit) || 20),
15
+ });