@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
@@ -0,0 +1,127 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import type { IPage } from '../../types.js';
3
+ import { getSelfUid, DoubanReview } from './utils.js';
4
+
5
+ cli({
6
+ site: 'douban',
7
+ name: 'reviews',
8
+ description: '导出个人影评',
9
+ domain: 'movie.douban.com',
10
+ strategy: Strategy.COOKIE,
11
+ args: [
12
+ { name: 'limit', type: 'int', default: 20, help: '导出数量' },
13
+ { name: 'uid', help: '用户ID,不填则使用当前登录账号' },
14
+ { name: 'full', type: 'bool', default: false, help: '获取完整影评内容' },
15
+ ],
16
+ columns: ['movieTitle', 'title', 'myRating', 'votes', 'content', 'url'],
17
+ func: async (page: IPage, kwargs: { limit?: number; uid?: string; full?: boolean }) => {
18
+ const { limit = 20, uid: providedUid, full = false } = kwargs;
19
+
20
+ const uid = providedUid || await getSelfUid(page);
21
+ const reviews = await fetchReviews(page, uid, limit, full);
22
+
23
+ return reviews;
24
+ },
25
+ });
26
+
27
+ async function fetchReviews(
28
+ page: IPage,
29
+ uid: string,
30
+ limit: number,
31
+ full: boolean,
32
+ ): Promise<DoubanReview[]> {
33
+ const reviews: DoubanReview[] = [];
34
+ let start = 0;
35
+ const pageSize = 20;
36
+
37
+ while (true) {
38
+ const url = `https://movie.douban.com/people/${uid}/reviews?start=${start}&sort=time`;
39
+
40
+ await page.goto(url);
41
+
42
+ await page.wait({ time: 1 });
43
+
44
+ const data = await page.evaluate(`
45
+ () => {
46
+ const reviews = [];
47
+
48
+ document.querySelectorAll('.tlst').forEach(el => {
49
+ const movieLinkEl = el.querySelector('.ilst a');
50
+ const reviewTitleEl = el.querySelector('.nlst a[title]');
51
+ const ratingEl = el.querySelector('.clst span[class*="allstar"]');
52
+ const contentEl = el.querySelector('.review-short span');
53
+ const votesEl = el.querySelector('.review-short .pl span');
54
+
55
+ const movieHref = movieLinkEl?.href || '';
56
+ const movieId = movieHref.match(/subject\\/(\\d+)/)?.[1] || '';
57
+ const movieTitle = movieLinkEl?.getAttribute('title') || movieLinkEl?.textContent?.trim() || '';
58
+
59
+ const reviewHref = reviewTitleEl?.href || '';
60
+ const reviewId = reviewHref.match(/reviews\\/(\\d+)/)?.[1] || '';
61
+ const title = reviewTitleEl?.textContent?.trim() || '';
62
+
63
+ let myRating = 0;
64
+ if (ratingEl) {
65
+ const cls = ratingEl.className || '';
66
+ const ratingMatch = cls.match(/allstar(\\d)0/);
67
+ if (ratingMatch) {
68
+ myRating = parseInt(ratingMatch[1], 10) * 2;
69
+ }
70
+ }
71
+
72
+ const votesText = votesEl?.textContent || '';
73
+ const votesMatch = votesText.match(/(\\d+)/);
74
+ const votes = votesMatch ? parseInt(votesMatch[1], 10) : 0;
75
+
76
+ reviews.push({
77
+ reviewId,
78
+ movieId,
79
+ movieTitle,
80
+ title,
81
+ content: contentEl?.textContent?.trim() || '',
82
+ myRating,
83
+ createdAt: '',
84
+ votes,
85
+ url: reviewHref,
86
+ });
87
+ });
88
+
89
+ return reviews;
90
+ }
91
+ `) as DoubanReview[];
92
+
93
+ reviews.push(...data);
94
+
95
+ if (data.length < pageSize) break;
96
+ if (limit > 0 && reviews.length >= limit) break;
97
+
98
+ start += pageSize;
99
+ }
100
+
101
+ const result = reviews.slice(0, limit > 0 ? limit : undefined);
102
+
103
+ if (full && result.length > 0) {
104
+ for (const review of result) {
105
+ if (review.url) {
106
+ const fullContent = await fetchFullReview(page, review.url);
107
+ review.content = fullContent;
108
+ }
109
+ }
110
+ }
111
+
112
+ return result;
113
+ }
114
+
115
+ async function fetchFullReview(page: IPage, reviewUrl: string): Promise<string> {
116
+ await page.goto(reviewUrl);
117
+ await page.wait({ time: 1 });
118
+
119
+ const content = await page.evaluate(`
120
+ () => {
121
+ const contentEl = document.querySelector('.review-content');
122
+ return contentEl?.textContent?.trim() || '';
123
+ }
124
+ `) as string;
125
+
126
+ return content;
127
+ }
@@ -0,0 +1,17 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { searchDouban } from './shared.js';
3
+
4
+ cli({
5
+ site: 'douban',
6
+ name: 'search',
7
+ description: '搜索豆瓣电影、图书或音乐',
8
+ domain: 'search.douban.com',
9
+ strategy: Strategy.COOKIE,
10
+ args: [
11
+ { name: 'type', default: 'movie', choices: ['movie', 'book', 'music'], help: '搜索类型(movie=电影, book=图书, music=音乐)' },
12
+ { name: 'keyword', required: true, positional: true, help: '搜索关键词' },
13
+ { name: 'limit', type: 'int', default: 20, help: '返回结果数量' },
14
+ ],
15
+ columns: ['rank', 'title', 'rating', 'abstract', 'url'],
16
+ func: async (page, args) => searchDouban(page, args.type, args.keyword, Number(args.limit) || 20),
17
+ });
@@ -0,0 +1,165 @@
1
+ import { CliError } from '../../errors.js';
2
+ import type { IPage } from '../../types.js';
3
+
4
+ function clampLimit(limit: number): number {
5
+ return Math.max(1, Math.min(limit || 20, 50));
6
+ }
7
+
8
+ async function ensureDoubanReady(page: IPage): Promise<void> {
9
+ const state = await page.evaluate(`
10
+ (() => {
11
+ const title = (document.title || '').trim();
12
+ const href = (location.href || '').trim();
13
+ const blocked = href.includes('sec.douban.com') || /登录跳转/.test(title) || /异常请求/.test(document.body?.innerText || '');
14
+ return { blocked, title, href };
15
+ })()
16
+ `);
17
+ if (state?.blocked) {
18
+ throw new CliError(
19
+ 'AUTH_REQUIRED',
20
+ 'Douban requires a logged-in browser session before these commands can load data.',
21
+ 'Please sign in to douban.com in the browser that opencli reuses, then rerun the command.',
22
+ );
23
+ }
24
+ }
25
+
26
+ export async function loadDoubanBookHot(page: IPage, limit: number): Promise<any[]> {
27
+ const safeLimit = clampLimit(limit);
28
+ await page.goto('https://book.douban.com/chart');
29
+ await page.wait(4);
30
+ await ensureDoubanReady(page);
31
+ const data = await page.evaluate(`
32
+ (() => {
33
+ const normalize = (value) => (value || '').replace(/\\s+/g, ' ').trim();
34
+ const books = [];
35
+ for (const el of Array.from(document.querySelectorAll('.media.clearfix'))) {
36
+ try {
37
+ const titleEl = el.querySelector('h2 a[href*="/subject/"]');
38
+ const title = normalize(titleEl?.textContent);
39
+ let url = titleEl?.getAttribute('href') || '';
40
+ if (!title || !url) continue;
41
+ if (!url.startsWith('http')) url = 'https://book.douban.com' + url;
42
+
43
+ const info = normalize(el.querySelector('.subject-abstract, .pl, .pub')?.textContent);
44
+ const infoParts = info.split('/').map((part) => part.trim()).filter(Boolean);
45
+ const ratingText = normalize(el.querySelector('.subject-rating .font-small, .rating_nums, .rating')?.textContent);
46
+ const quote = Array.from(el.querySelectorAll('.subject-tags .tag'))
47
+ .map((node) => normalize(node.textContent))
48
+ .filter(Boolean)
49
+ .join(' / ');
50
+
51
+ books.push({
52
+ rank: parseInt(normalize(el.querySelector('.green-num-box')?.textContent), 10) || books.length + 1,
53
+ title,
54
+ rating: parseFloat(ratingText) || 0,
55
+ quote,
56
+ author: infoParts[0] || '',
57
+ publisher: infoParts.find((part) => /出版社|出版公司|Press/i.test(part)) || infoParts[2] || '',
58
+ year: infoParts.find((part) => /\\d{4}(?:-\\d{1,2})?/.test(part))?.match(/\\d{4}/)?.[0] || '',
59
+ price: infoParts.find((part) => /元|USD|\\$|¥/.test(part)) || '',
60
+ url,
61
+ cover: el.querySelector('img')?.getAttribute('src') || '',
62
+ });
63
+ } catch {}
64
+ }
65
+ return books.slice(0, ${safeLimit});
66
+ })()
67
+ `);
68
+ return Array.isArray(data) ? data : [];
69
+ }
70
+
71
+ export async function loadDoubanMovieHot(page: IPage, limit: number): Promise<any[]> {
72
+ const safeLimit = clampLimit(limit);
73
+ await page.goto('https://movie.douban.com/chart');
74
+ await page.wait(4);
75
+ await ensureDoubanReady(page);
76
+ const data = await page.evaluate(`
77
+ (() => {
78
+ const normalize = (value) => (value || '').replace(/\\s+/g, ' ').trim();
79
+ const results = [];
80
+ for (const el of Array.from(document.querySelectorAll('.item'))) {
81
+ const titleEl = el.querySelector('.pl2 a');
82
+ const title = normalize(titleEl?.textContent);
83
+ let url = titleEl?.getAttribute('href') || '';
84
+ if (!title || !url) continue;
85
+ if (!url.startsWith('http')) url = 'https://movie.douban.com' + url;
86
+
87
+ const info = normalize(el.querySelector('.pl2 p')?.textContent);
88
+ const infoParts = info.split('/').map((part) => part.trim()).filter(Boolean);
89
+ const releaseIndex = (() => {
90
+ for (let i = infoParts.length - 1; i >= 0; i -= 1) {
91
+ if (/\\d{4}-\\d{2}-\\d{2}|\\d{4}\\/\\d{2}\\/\\d{2}/.test(infoParts[i])) return i;
92
+ }
93
+ return -1;
94
+ })();
95
+ const directorPart = releaseIndex >= 1 ? infoParts[releaseIndex - 1] : '';
96
+ const regionPart = releaseIndex >= 2 ? infoParts[releaseIndex - 2] : '';
97
+ const yearMatch = info.match(/\\b(19|20)\\d{2}\\b/);
98
+ results.push({
99
+ rank: results.length + 1,
100
+ title,
101
+ rating: parseFloat(normalize(el.querySelector('.rating_nums')?.textContent)) || 0,
102
+ quote: normalize(el.querySelector('.inq')?.textContent),
103
+ director: directorPart.replace(/^导演:\\s*/, ''),
104
+ year: yearMatch?.[0] || '',
105
+ region: regionPart,
106
+ url,
107
+ cover: el.querySelector('img')?.getAttribute('src') || '',
108
+ });
109
+ if (results.length >= ${safeLimit}) break;
110
+ }
111
+ return results;
112
+ })()
113
+ `);
114
+ return Array.isArray(data) ? data : [];
115
+ }
116
+
117
+ export async function searchDouban(page: IPage, type: string, keyword: string, limit: number): Promise<any[]> {
118
+ const safeLimit = clampLimit(limit);
119
+ await page.goto(`https://search.douban.com/${encodeURIComponent(type)}/subject_search?search_text=${encodeURIComponent(keyword)}`);
120
+ await page.wait(2);
121
+ await ensureDoubanReady(page);
122
+ const data = await page.evaluate(`
123
+ (async () => {
124
+ const type = ${JSON.stringify(type)};
125
+ const normalize = (value) => (value || '').replace(/\\s+/g, ' ').trim();
126
+ const seen = new Set();
127
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
128
+
129
+ for (let i = 0; i < 20; i += 1) {
130
+ if (document.querySelector('.item-root .title-text, .item-root .title a')) break;
131
+ await sleep(300);
132
+ }
133
+
134
+ const items = Array.from(document.querySelectorAll('.item-root'));
135
+
136
+ const results = [];
137
+ for (const el of items) {
138
+ const titleEl = el.querySelector('.title-text, .title a, a[title]');
139
+ const title = normalize(titleEl?.textContent) || normalize(titleEl?.getAttribute('title'));
140
+ let url = titleEl?.getAttribute('href') || '';
141
+ if (!title || !url) continue;
142
+ if (!url.startsWith('http')) url = 'https://search.douban.com' + url;
143
+ if (!url.includes('/subject/') || seen.has(url)) continue;
144
+ seen.add(url);
145
+ const ratingText = normalize(el.querySelector('.rating_nums')?.textContent);
146
+ const abstract = normalize(
147
+ el.querySelector('.meta.abstract, .meta, .abstract, p')?.textContent,
148
+ );
149
+ results.push({
150
+ rank: results.length + 1,
151
+ id: url.match(/subject\\/(\\d+)/)?.[1] || '',
152
+ type,
153
+ title,
154
+ rating: ratingText.includes('.') ? parseFloat(ratingText) : 0,
155
+ abstract: abstract.slice(0, 100) + (abstract.length > 100 ? '...' : ''),
156
+ url,
157
+ cover: el.querySelector('img')?.getAttribute('src') || '',
158
+ });
159
+ if (results.length >= ${safeLimit}) break;
160
+ }
161
+ return results;
162
+ })()
163
+ `);
164
+ return Array.isArray(data) ? data : [];
165
+ }
@@ -0,0 +1,76 @@
1
+ site: douban
2
+ name: subject
3
+ description: 获取电影详情
4
+ domain: movie.douban.com
5
+ strategy: cookie
6
+ browser: true
7
+
8
+ args:
9
+ id:
10
+ positional: true
11
+ required: true
12
+ type: str
13
+ description: 电影 ID
14
+
15
+ pipeline:
16
+ - navigate: https://movie.douban.com/subject/${{ args.id }}
17
+
18
+ - evaluate: |
19
+ (async () => {
20
+ const id = '${{ args.id }}';
21
+
22
+ // Wait for page to load
23
+ await new Promise(r => setTimeout(r, 2000));
24
+
25
+ // Extract title
26
+ const titleEl = document.querySelector('span[property="v:itemreviewed"]');
27
+ const title = titleEl?.textContent?.trim() || '';
28
+
29
+ // Extract original title
30
+ const ogTitleEl = document.querySelector('span[property="v:originalTitle"]');
31
+ const originalTitle = ogTitleEl?.textContent?.trim() || '';
32
+
33
+ // Extract year
34
+ const yearEl = document.querySelector('.year');
35
+ const year = yearEl?.textContent?.trim() || '';
36
+
37
+ // Extract rating
38
+ const ratingEl = document.querySelector('strong[property="v:average"]');
39
+ const rating = parseFloat(ratingEl?.textContent || '0');
40
+
41
+ // Extract rating count
42
+ const ratingCountEl = document.querySelector('span[property="v:votes"]');
43
+ const ratingCount = parseInt(ratingCountEl?.textContent || '0', 10);
44
+
45
+ // Extract genres
46
+ const genreEls = document.querySelectorAll('span[property="v:genre"]');
47
+ const genres = Array.from(genreEls).map(el => el.textContent?.trim()).filter(Boolean).join(',');
48
+
49
+ // Extract directors
50
+ const directorEls = document.querySelectorAll('a[rel="v:directedBy"]');
51
+ const directors = Array.from(directorEls).map(el => el.textContent?.trim()).filter(Boolean).join(',');
52
+
53
+ // Extract casts
54
+ const castEls = document.querySelectorAll('a[rel="v:starring"]');
55
+ const casts = Array.from(castEls).slice(0, 5).map(el => el.textContent?.trim()).filter(Boolean).join(',');
56
+
57
+ // Extract summary
58
+ const summaryEl = document.querySelector('span[property="v:summary"]');
59
+ const summary = summaryEl?.textContent?.trim() || '';
60
+
61
+ return [{
62
+ id,
63
+ title,
64
+ originalTitle,
65
+ year,
66
+ rating,
67
+ ratingCount,
68
+ genres,
69
+ directors,
70
+ casts,
71
+ summary: summary.substring(0, 200),
72
+ url: `https://movie.douban.com/subject/${id}`
73
+ }];
74
+ })()
75
+
76
+ columns: [id, title, originalTitle, year, rating, ratingCount, genres, directors, casts, summary, url]
@@ -0,0 +1,70 @@
1
+ site: douban
2
+ name: top250
3
+ description: 豆瓣电影 Top250
4
+ domain: movie.douban.com
5
+ strategy: cookie
6
+ browser: true
7
+
8
+ args:
9
+ limit:
10
+ type: int
11
+ default: 250
12
+ description: 返回结果数量
13
+
14
+ pipeline:
15
+ - navigate: https://movie.douban.com/top250
16
+
17
+ - evaluate: |
18
+ async () => {
19
+ const results = [];
20
+ const limit = ${{ args.limit }};
21
+
22
+ const parsePage = (doc) => {
23
+ const items = doc.querySelectorAll('.item');
24
+ for (const item of items) {
25
+ if (results.length >= limit) break;
26
+
27
+ const rankEl = item.querySelector('.pic em');
28
+ const linkEl = item.querySelector('a');
29
+ const titleEl = item.querySelector('.title');
30
+ const ratingEl = item.querySelector('.rating_num');
31
+
32
+ const href = linkEl?.href || '';
33
+ const matchResult = href.match(/subject\/(\d+)/);
34
+ const id = matchResult ? matchResult[1] : '';
35
+
36
+ const title = titleEl?.textContent?.trim() || '';
37
+ const rank = parseInt(rankEl?.textContent || '0', 10);
38
+ const rating = ratingEl?.textContent?.trim() || '';
39
+
40
+ if (id && title) {
41
+ results.push({
42
+ rank: rank || results.length + 1,
43
+ id,
44
+ title,
45
+ rating: rating ? parseFloat(rating) : 0,
46
+ url: href
47
+ });
48
+ }
49
+ }
50
+ };
51
+
52
+ parsePage(document);
53
+
54
+ for (let start = 25; start < 250 && results.length < limit; start += 25) {
55
+ const resp = await fetch(`https://movie.douban.com/top250?start=${start}`);
56
+ if (!resp.ok) break;
57
+ const html = await resp.text();
58
+ if (!html) break;
59
+
60
+ const doc = new DOMParser().parseFromString(html, 'text/html');
61
+ parsePage(doc);
62
+ await new Promise(r => setTimeout(r, 150));
63
+ }
64
+
65
+ return results;
66
+ }
67
+
68
+ - limit: ${{ args.limit }}
69
+
70
+ columns: [rank, id, title, rating, url]
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Douban movie adapter utilities.
3
+ */
4
+
5
+ import type { IPage } from '../../types.js';
6
+
7
+ /**
8
+ * Get current user's Douban ID from movie.douban.com/mine page
9
+ */
10
+ export async function getSelfUid(page: IPage): Promise<string> {
11
+ await page.goto('https://movie.douban.com/mine');
12
+ await page.wait({ time: 2 });
13
+
14
+ const uid = await page.evaluate(`
15
+ (() => {
16
+ // 方案1: 尝试从全局变量获取
17
+ if (window.__DATA__ && window.__DATA__.uid) {
18
+ return window.__DATA__.uid;
19
+ }
20
+
21
+ // 方案2: 从导航栏用户链接获取
22
+ const navUserLink = document.querySelector('.nav-user-account a');
23
+ if (navUserLink) {
24
+ const href = navUserLink.href || '';
25
+ const match = href.match(/people\\/([^/]+)/);
26
+ if (match) return match[1];
27
+ }
28
+
29
+ // 方案3: 从页面中的个人主页链接获取
30
+ const profileLink = document.querySelector('a[href*="/people/"]');
31
+ if (profileLink) {
32
+ const href = profileLink.getAttribute('href') || profileLink.href || '';
33
+ const match = href.match(/people\\/([^/]+)/);
34
+ if (match) return match[1];
35
+ }
36
+
37
+ // 方案4: 从头部用户名区域获取
38
+ const userLink = document.querySelector('.global-nav-items a[href*="/people/"]');
39
+ if (userLink) {
40
+ const href = userLink.getAttribute('href') || userLink.href || '';
41
+ const match = href.match(/people\\/([^/]+)/);
42
+ if (match) return match[1];
43
+ }
44
+
45
+ return '';
46
+ })()
47
+ `);
48
+ if (!uid) {
49
+ throw new Error('Not logged in to Douban. Please login in Chrome first.');
50
+ }
51
+ return uid;
52
+ }
53
+
54
+ /**
55
+ * Douban mark (viewing record) interface
56
+ */
57
+ export interface DoubanMark {
58
+ movieId: string;
59
+ title: string;
60
+ year: string;
61
+ myRating: number | null;
62
+ myStatus: 'collect' | 'wish' | 'do';
63
+ myComment: string;
64
+ myDate: string;
65
+ url: string;
66
+ }
67
+
68
+ /**
69
+ * Douban review interface
70
+ */
71
+ export interface DoubanReview {
72
+ reviewId: string;
73
+ movieId: string;
74
+ movieTitle: string;
75
+ title: string;
76
+ content: string;
77
+ myRating: number;
78
+ createdAt: string;
79
+ votes: number;
80
+ url: string;
81
+ }
@@ -0,0 +1,43 @@
1
+ site: facebook
2
+ name: add-friend
3
+ description: Send a friend request on Facebook
4
+ domain: www.facebook.com
5
+
6
+ args:
7
+ username:
8
+ type: str
9
+ required: true
10
+ positional: true
11
+ description: Facebook username or profile URL
12
+
13
+ pipeline:
14
+ - navigate:
15
+ url: https://www.facebook.com/${{ args.username }}
16
+ settleMs: 3000
17
+
18
+ - evaluate: |
19
+ (async () => {
20
+ const username = ${{ args.username | json }};
21
+ // Find "Add Friend" button
22
+ const buttons = Array.from(document.querySelectorAll('[role="button"]'));
23
+ const addBtn = buttons.find(b => {
24
+ const text = b.textContent.trim();
25
+ return text === '加好友' || text === 'Add Friend' || text === 'Add friend';
26
+ });
27
+
28
+ if (!addBtn) {
29
+ // Check if already friends
30
+ const isFriend = buttons.some(b => {
31
+ const t = b.textContent.trim();
32
+ return t === '好友' || t === 'Friends' || t.includes('已发送') || t.includes('Pending');
33
+ });
34
+ if (isFriend) return [{ status: 'Already friends or request pending', username }];
35
+ return [{ status: 'Add Friend button not found', username }];
36
+ }
37
+
38
+ addBtn.click();
39
+ await new Promise(r => setTimeout(r, 1500));
40
+ return [{ status: 'Friend request sent', username }];
41
+ })()
42
+
43
+ columns: [status, username]
@@ -0,0 +1,44 @@
1
+ site: facebook
2
+ name: events
3
+ description: Browse Facebook event categories
4
+ domain: www.facebook.com
5
+
6
+ args:
7
+ limit:
8
+ type: int
9
+ default: 15
10
+ description: Number of categories
11
+
12
+ pipeline:
13
+ - navigate:
14
+ url: https://www.facebook.com/events
15
+ settleMs: 3000
16
+
17
+ - evaluate: |
18
+ (() => {
19
+ const limit = ${{ args.limit }};
20
+ // Try actual event items first
21
+ const articles = document.querySelectorAll('[role="article"]');
22
+ if (articles.length > 0) {
23
+ return Array.from(articles).slice(0, limit).map((el, i) => ({
24
+ index: i + 1,
25
+ name: el.textContent.trim().replace(/\s+/g, ' ').substring(0, 120),
26
+ }));
27
+ }
28
+
29
+ // List event categories from sidebar navigation
30
+ const links = Array.from(document.querySelectorAll('[role="navigation"] a'))
31
+ .filter(a => {
32
+ const href = a.href || '';
33
+ const text = a.textContent.trim();
34
+ return href.includes('/events/') && text.length > 1 && text.length < 60 &&
35
+ !href.includes('create');
36
+ });
37
+
38
+ return links.slice(0, limit).map((a, i) => ({
39
+ index: i + 1,
40
+ name: a.textContent.trim(),
41
+ }));
42
+ })()
43
+
44
+ columns: [index, name]
@@ -0,0 +1,63 @@
1
+ site: facebook
2
+ name: feed
3
+ description: Get your Facebook news feed
4
+ domain: www.facebook.com
5
+
6
+ args:
7
+ limit:
8
+ type: int
9
+ default: 10
10
+ description: Number of posts
11
+
12
+ pipeline:
13
+ - navigate:
14
+ url: https://www.facebook.com/
15
+ settleMs: 4000
16
+
17
+ - evaluate: |
18
+ (() => {
19
+ const limit = ${{ args.limit }};
20
+ const posts = document.querySelectorAll('[role="article"]');
21
+ return Array.from(posts)
22
+ .filter(el => {
23
+ const text = el.textContent.trim();
24
+ // Filter out "People you may know" suggestions (both CN and EN)
25
+ return text.length > 30 &&
26
+ !text.startsWith('可能认识') &&
27
+ !text.startsWith('People you may know') &&
28
+ !text.startsWith('People You May Know');
29
+ })
30
+ .slice(0, limit)
31
+ .map((el, i) => {
32
+ // Author from header link
33
+ const headerLink = el.querySelector('h2 a, h3 a, h4 a, strong a');
34
+ const author = headerLink ? headerLink.textContent.trim() : '';
35
+
36
+ // Post text: grab visible spans, filter noise
37
+ const spans = Array.from(el.querySelectorAll('div[dir="auto"]'))
38
+ .map(s => s.textContent.trim())
39
+ .filter(t => t.length > 10 && t.length < 500);
40
+ const content = spans.length > 0 ? spans[0] : '';
41
+
42
+ // Engagement: find like/comment/share counts (CN + EN)
43
+ const allText = el.textContent;
44
+ const likesMatch = allText.match(/所有心情:([\d,.\s]*[\d万亿KMk]+)/) ||
45
+ allText.match(/All:\s*([\d,.KMk]+)/) ||
46
+ allText.match(/([\d,.KMk]+)\s*(?:likes?|reactions?)/i);
47
+ const commentsMatch = allText.match(/([\d,.]+\s*[万亿]?)\s*条评论/) ||
48
+ allText.match(/([\d,.KMk]+)\s*comments?/i);
49
+ const sharesMatch = allText.match(/([\d,.]+\s*[万亿]?)\s*次分享/) ||
50
+ allText.match(/([\d,.KMk]+)\s*shares?/i);
51
+
52
+ return {
53
+ index: i + 1,
54
+ author: author.substring(0, 50),
55
+ content: content.replace(/\n/g, ' ').substring(0, 120),
56
+ likes: likesMatch ? likesMatch[1] : '-',
57
+ comments: commentsMatch ? commentsMatch[1] : '-',
58
+ shares: sharesMatch ? sharesMatch[1] : '-',
59
+ };
60
+ });
61
+ })()
62
+
63
+ columns: [index, author, content, likes, comments, shares]