@jackwener/opencli 1.1.1 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (523) hide show
  1. package/.github/workflows/build-extension.yml +3 -3
  2. package/.github/workflows/ci.yml +6 -6
  3. package/.github/workflows/doc-check.yml +3 -3
  4. package/.github/workflows/e2e-headed.yml +2 -2
  5. package/.github/workflows/pkg-pr-new.yml +2 -2
  6. package/.github/workflows/release.yml +3 -3
  7. package/.github/workflows/security.yml +2 -2
  8. package/CONTRIBUTING.md +39 -1
  9. package/README.md +13 -10
  10. package/README.zh-CN.md +43 -17
  11. package/SKILL.md +10 -5
  12. package/dist/browser/cdp.d.ts +4 -4
  13. package/dist/browser/cdp.js +39 -16
  14. package/dist/browser/daemon-client.d.ts +4 -2
  15. package/dist/browser/daemon-client.js +17 -4
  16. package/dist/browser/dom-helpers.js +38 -7
  17. package/dist/browser/dom-snapshot.d.ts +86 -0
  18. package/dist/browser/dom-snapshot.js +729 -0
  19. package/dist/browser/dom-snapshot.test.d.ts +11 -0
  20. package/dist/browser/dom-snapshot.test.js +212 -0
  21. package/dist/browser/index.d.ts +2 -0
  22. package/dist/browser/index.js +1 -0
  23. package/dist/browser/mcp.js +3 -1
  24. package/dist/browser/page.d.ts +14 -24
  25. package/dist/browser/page.js +46 -6
  26. package/dist/build-manifest.d.ts +11 -4
  27. package/dist/build-manifest.js +59 -21
  28. package/dist/build-manifest.test.js +58 -2
  29. package/dist/cli-manifest.json +3856 -1509
  30. package/dist/cli.js +66 -0
  31. package/dist/clis/barchart/greeks.js +1 -1
  32. package/dist/clis/barchart/options.js +1 -1
  33. package/dist/clis/barchart/quote.js +1 -1
  34. package/dist/clis/bilibili/download.js +1 -1
  35. package/dist/clis/bilibili/following.js +1 -1
  36. package/dist/clis/bilibili/subtitle.js +1 -1
  37. package/dist/clis/bilibili/user-videos.js +1 -1
  38. package/dist/clis/boss/batchgreet.js +10 -97
  39. package/dist/clis/boss/chatlist.js +8 -25
  40. package/dist/clis/boss/chatmsg.js +11 -42
  41. package/dist/clis/boss/common.d.ts +92 -0
  42. package/dist/clis/boss/common.js +223 -0
  43. package/dist/clis/boss/detail.js +7 -49
  44. package/dist/clis/boss/exchange.js +13 -79
  45. package/dist/clis/boss/greet.js +18 -145
  46. package/dist/clis/boss/invite.js +26 -121
  47. package/dist/clis/boss/joblist.js +6 -31
  48. package/dist/clis/boss/mark.js +12 -85
  49. package/dist/clis/boss/recommend.js +10 -49
  50. package/dist/clis/boss/resume.js +18 -118
  51. package/dist/clis/boss/search.js +12 -60
  52. package/dist/clis/boss/send.js +17 -151
  53. package/dist/clis/boss/stats.js +18 -69
  54. package/dist/clis/coupang/add-to-cart.js +1 -1
  55. package/dist/clis/devto/tag.yaml +34 -0
  56. package/dist/clis/devto/top.yaml +29 -0
  57. package/dist/clis/devto/user.yaml +33 -0
  58. package/dist/clis/douban/book-hot.d.ts +1 -0
  59. package/dist/clis/douban/book-hot.js +14 -0
  60. package/dist/clis/douban/marks.d.ts +1 -0
  61. package/dist/clis/douban/marks.js +115 -0
  62. package/dist/clis/douban/movie-hot.d.ts +1 -0
  63. package/dist/clis/douban/movie-hot.js +14 -0
  64. package/dist/clis/douban/reviews.d.ts +1 -0
  65. package/dist/clis/douban/reviews.js +106 -0
  66. package/dist/clis/douban/search.d.ts +1 -0
  67. package/dist/clis/douban/search.js +16 -0
  68. package/dist/clis/douban/shared.d.ts +4 -0
  69. package/dist/clis/douban/shared.js +155 -0
  70. package/dist/clis/douban/subject.yaml +76 -0
  71. package/dist/clis/douban/top250.yaml +70 -0
  72. package/dist/clis/douban/utils.d.ts +35 -0
  73. package/dist/clis/douban/utils.js +48 -0
  74. package/dist/clis/facebook/add-friend.yaml +43 -0
  75. package/dist/clis/facebook/events.yaml +44 -0
  76. package/dist/clis/facebook/feed.yaml +63 -0
  77. package/dist/clis/facebook/friends.yaml +42 -0
  78. package/dist/clis/facebook/groups.yaml +50 -0
  79. package/dist/clis/facebook/join-group.yaml +44 -0
  80. package/dist/clis/facebook/memories.yaml +39 -0
  81. package/dist/clis/facebook/notifications.yaml +40 -0
  82. package/dist/clis/facebook/profile.yaml +37 -0
  83. package/dist/clis/facebook/search.yaml +46 -0
  84. package/dist/clis/google/news.d.ts +5 -0
  85. package/dist/clis/google/news.js +58 -0
  86. package/dist/clis/google/search.d.ts +10 -0
  87. package/dist/clis/google/search.js +127 -0
  88. package/dist/clis/google/suggest.d.ts +5 -0
  89. package/dist/clis/google/suggest.js +34 -0
  90. package/dist/clis/google/trends.d.ts +5 -0
  91. package/dist/clis/google/trends.js +38 -0
  92. package/dist/clis/google/utils.d.ts +9 -0
  93. package/dist/clis/google/utils.js +23 -0
  94. package/dist/clis/google/utils.test.d.ts +1 -0
  95. package/dist/clis/google/utils.test.js +75 -0
  96. package/dist/clis/grok/ask.d.ts +14 -0
  97. package/dist/clis/grok/ask.js +257 -65
  98. package/dist/clis/grok/ask.test.d.ts +1 -0
  99. package/dist/clis/grok/ask.test.js +36 -0
  100. package/dist/clis/instagram/comment.yaml +52 -0
  101. package/dist/clis/instagram/explore.yaml +43 -0
  102. package/dist/clis/instagram/follow.yaml +41 -0
  103. package/dist/clis/instagram/followers.yaml +51 -0
  104. package/dist/clis/instagram/following.yaml +51 -0
  105. package/dist/clis/instagram/like.yaml +46 -0
  106. package/dist/clis/instagram/profile.yaml +42 -0
  107. package/dist/clis/instagram/save.yaml +46 -0
  108. package/dist/clis/instagram/saved.yaml +40 -0
  109. package/dist/clis/instagram/search.yaml +43 -0
  110. package/dist/clis/instagram/unfollow.yaml +38 -0
  111. package/dist/clis/instagram/unlike.yaml +46 -0
  112. package/dist/clis/instagram/unsave.yaml +46 -0
  113. package/dist/clis/instagram/user.yaml +54 -0
  114. package/dist/clis/jike/repost.js +1 -1
  115. package/dist/clis/jimeng/generate.yaml +1 -0
  116. package/dist/clis/linux-do/category.yaml +1 -0
  117. package/dist/clis/lobsters/active.yaml +29 -0
  118. package/dist/clis/lobsters/hot.yaml +29 -0
  119. package/dist/clis/lobsters/newest.yaml +29 -0
  120. package/dist/clis/lobsters/tag.yaml +34 -0
  121. package/dist/clis/medium/feed.d.ts +1 -0
  122. package/dist/clis/medium/feed.js +15 -0
  123. package/dist/clis/medium/search.d.ts +1 -0
  124. package/dist/clis/medium/search.js +15 -0
  125. package/dist/clis/medium/shared.d.ts +5 -0
  126. package/dist/clis/medium/shared.js +78 -0
  127. package/dist/clis/medium/user.d.ts +1 -0
  128. package/dist/clis/medium/user.js +15 -0
  129. package/dist/clis/reddit/comment.js +1 -1
  130. package/dist/clis/reddit/read.js +1 -1
  131. package/dist/clis/reddit/save.js +1 -1
  132. package/dist/clis/reddit/subreddit.yaml +1 -0
  133. package/dist/clis/reddit/subscribe.js +1 -1
  134. package/dist/clis/reddit/upvote.js +1 -1
  135. package/dist/clis/sinablog/article.d.ts +1 -0
  136. package/dist/clis/sinablog/article.js +14 -0
  137. package/dist/clis/sinablog/hot.d.ts +1 -0
  138. package/dist/clis/sinablog/hot.js +14 -0
  139. package/dist/clis/sinablog/search.d.ts +1 -0
  140. package/dist/clis/sinablog/search.js +51 -0
  141. package/dist/clis/sinablog/shared.d.ts +7 -0
  142. package/dist/clis/sinablog/shared.js +187 -0
  143. package/dist/clis/sinablog/user.d.ts +1 -0
  144. package/dist/clis/sinablog/user.js +15 -0
  145. package/dist/clis/substack/feed.d.ts +1 -0
  146. package/dist/clis/substack/feed.js +15 -0
  147. package/dist/clis/substack/publication.d.ts +1 -0
  148. package/dist/clis/substack/publication.js +15 -0
  149. package/dist/clis/substack/search.d.ts +1 -0
  150. package/dist/clis/substack/search.js +77 -0
  151. package/dist/clis/substack/shared.d.ts +4 -0
  152. package/dist/clis/substack/shared.js +129 -0
  153. package/dist/clis/tiktok/comment.yaml +66 -0
  154. package/dist/clis/tiktok/explore.yaml +39 -0
  155. package/dist/clis/tiktok/follow.yaml +39 -0
  156. package/dist/clis/tiktok/following.yaml +46 -0
  157. package/dist/clis/tiktok/friends.yaml +47 -0
  158. package/dist/clis/tiktok/like.yaml +38 -0
  159. package/dist/clis/tiktok/live.yaml +51 -0
  160. package/dist/clis/tiktok/notifications.yaml +52 -0
  161. package/dist/clis/tiktok/profile.yaml +45 -0
  162. package/dist/clis/tiktok/save.yaml +34 -0
  163. package/dist/clis/tiktok/search.yaml +46 -0
  164. package/dist/clis/tiktok/unfollow.yaml +44 -0
  165. package/dist/clis/tiktok/unlike.yaml +38 -0
  166. package/dist/clis/tiktok/unsave.yaml +36 -0
  167. package/dist/clis/tiktok/user.yaml +44 -0
  168. package/dist/clis/twitter/download.d.ts +1 -1
  169. package/dist/clis/twitter/download.js +3 -3
  170. package/dist/clis/twitter/followers.js +1 -1
  171. package/dist/clis/twitter/following.js +1 -1
  172. package/dist/clis/twitter/thread.js +1 -1
  173. package/dist/clis/twitter/timeline.d.ts +23 -0
  174. package/dist/clis/twitter/timeline.js +42 -14
  175. package/dist/clis/twitter/timeline.test.d.ts +1 -0
  176. package/dist/clis/twitter/timeline.test.js +102 -0
  177. package/dist/clis/wikipedia/random.d.ts +1 -0
  178. package/dist/clis/wikipedia/random.js +19 -0
  179. package/dist/clis/wikipedia/search.js +3 -3
  180. package/dist/clis/wikipedia/summary.js +4 -9
  181. package/dist/clis/wikipedia/trending.d.ts +1 -0
  182. package/dist/clis/wikipedia/trending.js +35 -0
  183. package/dist/clis/wikipedia/utils.d.ts +28 -0
  184. package/dist/clis/wikipedia/utils.js +13 -0
  185. package/dist/clis/xiaohongshu/creator-note-detail.js +1 -1
  186. package/dist/clis/xiaohongshu/creator-note-detail.test.js +2 -0
  187. package/dist/clis/xiaohongshu/creator-notes.test.js +2 -0
  188. package/dist/clis/xiaohongshu/download.js +1 -1
  189. package/dist/clis/xueqiu/earnings-date.yaml +69 -0
  190. package/dist/clis/xueqiu/search.yaml +2 -1
  191. package/dist/clis/xueqiu/stock.yaml +2 -0
  192. package/dist/clis/yahoo-finance/quote.js +1 -1
  193. package/dist/commanderAdapter.js +13 -7
  194. package/dist/daemon.js +21 -0
  195. package/dist/discovery.d.ts +8 -0
  196. package/dist/discovery.js +105 -19
  197. package/dist/doctor.js +3 -1
  198. package/dist/doctor.test.js +46 -2
  199. package/dist/engine.test.d.ts +0 -3
  200. package/dist/engine.test.js +74 -6
  201. package/dist/execution.d.ts +4 -2
  202. package/dist/execution.js +31 -7
  203. package/dist/explore.d.ts +76 -3
  204. package/dist/explore.js +11 -4
  205. package/dist/generate.d.ts +41 -2
  206. package/dist/generate.js +5 -4
  207. package/dist/main.js +2 -1
  208. package/dist/pipeline/executor.d.ts +4 -2
  209. package/dist/pipeline/executor.js +54 -15
  210. package/dist/pipeline/executor.test.js +33 -6
  211. package/dist/pipeline/registry.d.ts +1 -1
  212. package/dist/pipeline/steps/browser.d.ts +7 -7
  213. package/dist/pipeline/steps/browser.js +15 -7
  214. package/dist/pipeline/steps/fetch.d.ts +1 -1
  215. package/dist/pipeline/steps/fetch.js +11 -7
  216. package/dist/pipeline/steps/transform.d.ts +6 -5
  217. package/dist/pipeline/steps/transform.js +30 -9
  218. package/dist/pipeline/template.d.ts +6 -6
  219. package/dist/pipeline/template.js +43 -5
  220. package/dist/pipeline/template.test.js +18 -0
  221. package/dist/pipeline/transform.test.js +11 -0
  222. package/dist/plugin.d.ts +31 -0
  223. package/dist/plugin.js +216 -0
  224. package/dist/plugin.test.d.ts +4 -0
  225. package/dist/plugin.test.js +76 -0
  226. package/dist/registry-api.d.ts +11 -0
  227. package/dist/registry-api.js +9 -0
  228. package/dist/registry.d.ts +11 -0
  229. package/dist/registry.js +6 -1
  230. package/dist/synthesize.d.ts +94 -4
  231. package/dist/synthesize.js +5 -4
  232. package/dist/types.d.ts +39 -26
  233. package/dist/validate.js +8 -2
  234. package/docs/.vitepress/config.mts +6 -4
  235. package/docs/adapters/browser/barchart.md +6 -5
  236. package/docs/adapters/browser/bilibili.md +9 -0
  237. package/docs/adapters/browser/devto.md +35 -0
  238. package/docs/adapters/browser/douban.md +38 -0
  239. package/docs/adapters/browser/facebook.md +36 -0
  240. package/docs/adapters/browser/google.md +62 -0
  241. package/docs/adapters/browser/grok.md +26 -8
  242. package/docs/adapters/browser/instagram.md +46 -0
  243. package/docs/adapters/browser/lobsters.md +32 -0
  244. package/docs/adapters/browser/medium.md +32 -0
  245. package/docs/adapters/browser/reddit.md +9 -0
  246. package/docs/adapters/browser/sinablog.md +36 -0
  247. package/docs/adapters/browser/substack.md +38 -0
  248. package/docs/adapters/browser/tiktok.md +68 -0
  249. package/docs/adapters/browser/wikipedia.md +11 -2
  250. package/docs/adapters/browser/xueqiu.md +10 -0
  251. package/docs/adapters/browser/yahoo-finance.md +6 -5
  252. package/docs/adapters/desktop/antigravity.md +6 -0
  253. package/docs/adapters/desktop/chatgpt.md +2 -1
  254. package/docs/adapters/desktop/codex.md +5 -1
  255. package/docs/adapters/desktop/cursor.md +4 -0
  256. package/docs/adapters/desktop/discord.md +7 -7
  257. package/docs/adapters/index.md +1 -4
  258. package/docs/guide/getting-started.md +1 -0
  259. package/docs/guide/plugins.md +153 -0
  260. package/docs/zh/guide/plugins.md +107 -0
  261. package/extension/dist/background.js +91 -23
  262. package/extension/src/background.ts +82 -29
  263. package/extension/src/cdp.ts +42 -1
  264. package/package.json +10 -5
  265. package/scripts/clean-dist.cjs +13 -0
  266. package/src/browser/cdp.ts +71 -31
  267. package/src/browser/daemon-client.ts +21 -5
  268. package/src/browser/dom-helpers.ts +38 -7
  269. package/src/browser/dom-snapshot.test.ts +249 -0
  270. package/src/browser/dom-snapshot.ts +770 -0
  271. package/src/browser/index.ts +2 -0
  272. package/src/browser/mcp.ts +3 -1
  273. package/src/browser/page.ts +57 -21
  274. package/src/build-manifest.test.ts +70 -2
  275. package/src/build-manifest.ts +94 -26
  276. package/src/cli.ts +71 -2
  277. package/src/clis/barchart/greeks.ts +1 -1
  278. package/src/clis/barchart/options.ts +1 -1
  279. package/src/clis/barchart/quote.ts +1 -1
  280. package/src/clis/bilibili/download.ts +1 -1
  281. package/src/clis/bilibili/following.ts +1 -1
  282. package/src/clis/bilibili/subtitle.ts +1 -1
  283. package/src/clis/bilibili/user-videos.ts +1 -1
  284. package/src/clis/boss/batchgreet.ts +14 -106
  285. package/src/clis/boss/chatlist.ts +12 -26
  286. package/src/clis/boss/chatmsg.ts +16 -40
  287. package/src/clis/boss/common.ts +287 -0
  288. package/src/clis/boss/detail.ts +8 -54
  289. package/src/clis/boss/exchange.ts +15 -89
  290. package/src/clis/boss/greet.ts +23 -160
  291. package/src/clis/boss/invite.ts +36 -133
  292. package/src/clis/boss/joblist.ts +7 -36
  293. package/src/clis/boss/mark.ts +13 -94
  294. package/src/clis/boss/recommend.ts +12 -57
  295. package/src/clis/boss/resume.ts +19 -124
  296. package/src/clis/boss/search.ts +13 -66
  297. package/src/clis/boss/send.ts +21 -161
  298. package/src/clis/boss/stats.ts +19 -74
  299. package/src/clis/coupang/add-to-cart.ts +1 -1
  300. package/src/clis/devto/tag.yaml +34 -0
  301. package/src/clis/devto/top.yaml +29 -0
  302. package/src/clis/devto/user.yaml +33 -0
  303. package/src/clis/douban/book-hot.ts +15 -0
  304. package/src/clis/douban/marks.ts +135 -0
  305. package/src/clis/douban/movie-hot.ts +15 -0
  306. package/src/clis/douban/reviews.ts +127 -0
  307. package/src/clis/douban/search.ts +17 -0
  308. package/src/clis/douban/shared.ts +165 -0
  309. package/src/clis/douban/subject.yaml +76 -0
  310. package/src/clis/douban/top250.yaml +70 -0
  311. package/src/clis/douban/utils.ts +81 -0
  312. package/src/clis/facebook/add-friend.yaml +43 -0
  313. package/src/clis/facebook/events.yaml +44 -0
  314. package/src/clis/facebook/feed.yaml +63 -0
  315. package/src/clis/facebook/friends.yaml +42 -0
  316. package/src/clis/facebook/groups.yaml +50 -0
  317. package/src/clis/facebook/join-group.yaml +44 -0
  318. package/src/clis/facebook/memories.yaml +39 -0
  319. package/src/clis/facebook/notifications.yaml +40 -0
  320. package/src/clis/facebook/profile.yaml +37 -0
  321. package/src/clis/facebook/search.yaml +46 -0
  322. package/src/clis/google/news.ts +66 -0
  323. package/src/clis/google/search.ts +133 -0
  324. package/src/clis/google/suggest.ts +40 -0
  325. package/src/clis/google/trends.ts +44 -0
  326. package/src/clis/google/utils.test.ts +82 -0
  327. package/src/clis/google/utils.ts +24 -0
  328. package/src/clis/grok/ask.test.ts +53 -0
  329. package/src/clis/grok/ask.ts +300 -69
  330. package/src/clis/instagram/comment.yaml +52 -0
  331. package/src/clis/instagram/explore.yaml +43 -0
  332. package/src/clis/instagram/follow.yaml +41 -0
  333. package/src/clis/instagram/followers.yaml +51 -0
  334. package/src/clis/instagram/following.yaml +51 -0
  335. package/src/clis/instagram/like.yaml +46 -0
  336. package/src/clis/instagram/profile.yaml +42 -0
  337. package/src/clis/instagram/save.yaml +46 -0
  338. package/src/clis/instagram/saved.yaml +40 -0
  339. package/src/clis/instagram/search.yaml +43 -0
  340. package/src/clis/instagram/unfollow.yaml +38 -0
  341. package/src/clis/instagram/unlike.yaml +46 -0
  342. package/src/clis/instagram/unsave.yaml +46 -0
  343. package/src/clis/instagram/user.yaml +54 -0
  344. package/src/clis/jike/repost.ts +1 -1
  345. package/src/clis/jimeng/generate.yaml +1 -0
  346. package/src/clis/linux-do/category.yaml +1 -0
  347. package/src/clis/lobsters/active.yaml +29 -0
  348. package/src/clis/lobsters/hot.yaml +29 -0
  349. package/src/clis/lobsters/newest.yaml +29 -0
  350. package/src/clis/lobsters/tag.yaml +34 -0
  351. package/src/clis/medium/feed.ts +16 -0
  352. package/src/clis/medium/search.ts +16 -0
  353. package/src/clis/medium/shared.ts +83 -0
  354. package/src/clis/medium/user.ts +16 -0
  355. package/src/clis/reddit/comment.ts +1 -1
  356. package/src/clis/reddit/read.ts +1 -1
  357. package/src/clis/reddit/save.ts +1 -1
  358. package/src/clis/reddit/subreddit.yaml +1 -0
  359. package/src/clis/reddit/subscribe.ts +1 -1
  360. package/src/clis/reddit/upvote.ts +1 -1
  361. package/src/clis/sinablog/article.ts +15 -0
  362. package/src/clis/sinablog/hot.ts +15 -0
  363. package/src/clis/sinablog/search.ts +56 -0
  364. package/src/clis/sinablog/shared.ts +198 -0
  365. package/src/clis/sinablog/user.ts +16 -0
  366. package/src/clis/substack/feed.ts +16 -0
  367. package/src/clis/substack/publication.ts +16 -0
  368. package/src/clis/substack/search.ts +91 -0
  369. package/src/clis/substack/shared.ts +132 -0
  370. package/src/clis/tiktok/comment.yaml +66 -0
  371. package/src/clis/tiktok/explore.yaml +39 -0
  372. package/src/clis/tiktok/follow.yaml +39 -0
  373. package/src/clis/tiktok/following.yaml +46 -0
  374. package/src/clis/tiktok/friends.yaml +47 -0
  375. package/src/clis/tiktok/like.yaml +38 -0
  376. package/src/clis/tiktok/live.yaml +51 -0
  377. package/src/clis/tiktok/notifications.yaml +52 -0
  378. package/src/clis/tiktok/profile.yaml +45 -0
  379. package/src/clis/tiktok/save.yaml +34 -0
  380. package/src/clis/tiktok/search.yaml +46 -0
  381. package/src/clis/tiktok/unfollow.yaml +44 -0
  382. package/src/clis/tiktok/unlike.yaml +38 -0
  383. package/src/clis/tiktok/unsave.yaml +36 -0
  384. package/src/clis/tiktok/user.yaml +44 -0
  385. package/src/clis/twitter/download.ts +3 -3
  386. package/src/clis/twitter/followers.ts +1 -1
  387. package/src/clis/twitter/following.ts +1 -1
  388. package/src/clis/twitter/thread.ts +1 -1
  389. package/src/clis/twitter/timeline.test.ts +109 -0
  390. package/src/clis/twitter/timeline.ts +59 -19
  391. package/src/clis/wikipedia/random.ts +19 -0
  392. package/src/clis/wikipedia/search.ts +10 -4
  393. package/src/clis/wikipedia/summary.ts +4 -9
  394. package/src/clis/wikipedia/trending.ts +41 -0
  395. package/src/clis/wikipedia/utils.ts +31 -0
  396. package/src/clis/xiaohongshu/creator-note-detail.test.ts +2 -0
  397. package/src/clis/xiaohongshu/creator-note-detail.ts +1 -1
  398. package/src/clis/xiaohongshu/creator-notes.test.ts +2 -0
  399. package/src/clis/xiaohongshu/download.ts +1 -1
  400. package/src/clis/xueqiu/earnings-date.yaml +69 -0
  401. package/src/clis/xueqiu/search.yaml +2 -1
  402. package/src/clis/xueqiu/stock.yaml +2 -0
  403. package/src/clis/yahoo-finance/quote.ts +1 -1
  404. package/src/commanderAdapter.ts +17 -10
  405. package/src/daemon.ts +23 -0
  406. package/src/discovery.ts +134 -24
  407. package/src/doctor.test.ts +59 -2
  408. package/src/doctor.ts +4 -2
  409. package/src/engine.test.ts +79 -6
  410. package/src/execution.ts +42 -16
  411. package/src/explore.ts +77 -9
  412. package/src/generate.ts +58 -9
  413. package/src/main.ts +2 -1
  414. package/src/pipeline/executor.test.ts +35 -6
  415. package/src/pipeline/executor.ts +68 -19
  416. package/src/pipeline/registry.ts +3 -3
  417. package/src/pipeline/steps/browser.ts +24 -15
  418. package/src/pipeline/steps/fetch.ts +18 -13
  419. package/src/pipeline/steps/transform.ts +40 -15
  420. package/src/pipeline/template.test.ts +18 -0
  421. package/src/pipeline/template.ts +86 -13
  422. package/src/pipeline/transform.test.ts +15 -2
  423. package/src/plugin.test.ts +86 -0
  424. package/src/plugin.ts +254 -0
  425. package/src/registry-api.ts +12 -0
  426. package/src/registry.ts +19 -1
  427. package/src/synthesize.ts +102 -21
  428. package/src/types.ts +44 -12
  429. package/src/validate.ts +19 -4
  430. package/tests/e2e/browser-public.test.ts +11 -0
  431. package/tests/e2e/public-commands.test.ts +64 -0
  432. package/dist/clis/feishu/new.d.ts +0 -1
  433. package/dist/clis/feishu/new.js +0 -27
  434. package/dist/clis/feishu/read.d.ts +0 -1
  435. package/dist/clis/feishu/read.js +0 -40
  436. package/dist/clis/feishu/search.d.ts +0 -1
  437. package/dist/clis/feishu/search.js +0 -30
  438. package/dist/clis/feishu/send.d.ts +0 -1
  439. package/dist/clis/feishu/send.js +0 -39
  440. package/dist/clis/feishu/status.d.ts +0 -1
  441. package/dist/clis/feishu/status.js +0 -28
  442. package/dist/clis/neteasemusic/like.d.ts +0 -1
  443. package/dist/clis/neteasemusic/like.js +0 -25
  444. package/dist/clis/neteasemusic/lyrics.d.ts +0 -1
  445. package/dist/clis/neteasemusic/lyrics.js +0 -47
  446. package/dist/clis/neteasemusic/next.d.ts +0 -1
  447. package/dist/clis/neteasemusic/next.js +0 -26
  448. package/dist/clis/neteasemusic/play.d.ts +0 -1
  449. package/dist/clis/neteasemusic/play.js +0 -26
  450. package/dist/clis/neteasemusic/playing.d.ts +0 -1
  451. package/dist/clis/neteasemusic/playing.js +0 -59
  452. package/dist/clis/neteasemusic/playlist.d.ts +0 -1
  453. package/dist/clis/neteasemusic/playlist.js +0 -46
  454. package/dist/clis/neteasemusic/prev.d.ts +0 -1
  455. package/dist/clis/neteasemusic/prev.js +0 -25
  456. package/dist/clis/neteasemusic/search.d.ts +0 -1
  457. package/dist/clis/neteasemusic/search.js +0 -52
  458. package/dist/clis/neteasemusic/status.d.ts +0 -1
  459. package/dist/clis/neteasemusic/status.js +0 -16
  460. package/dist/clis/neteasemusic/volume.d.ts +0 -1
  461. package/dist/clis/neteasemusic/volume.js +0 -54
  462. package/dist/clis/wechat/chats.d.ts +0 -1
  463. package/dist/clis/wechat/chats.js +0 -28
  464. package/dist/clis/wechat/contacts.d.ts +0 -1
  465. package/dist/clis/wechat/contacts.js +0 -28
  466. package/dist/clis/wechat/read.d.ts +0 -1
  467. package/dist/clis/wechat/read.js +0 -58
  468. package/dist/clis/wechat/search.d.ts +0 -1
  469. package/dist/clis/wechat/search.js +0 -31
  470. package/dist/clis/wechat/send.d.ts +0 -1
  471. package/dist/clis/wechat/send.js +0 -42
  472. package/dist/clis/wechat/status.d.ts +0 -1
  473. package/dist/clis/wechat/status.js +0 -29
  474. package/dist/pipeline.d.ts +0 -7
  475. package/dist/pipeline.js +0 -7
  476. package/docs/adapters/browser/github.md +0 -26
  477. package/docs/adapters/desktop/feishu.md +0 -20
  478. package/docs/adapters/desktop/neteasemusic.md +0 -31
  479. package/docs/adapters/desktop/wechat.md +0 -28
  480. package/src/clis/antigravity/README.md +0 -5
  481. package/src/clis/antigravity/README.zh-CN.md +0 -51
  482. package/src/clis/chaoxing/README.md +0 -14
  483. package/src/clis/chaoxing/README.zh-CN.md +0 -35
  484. package/src/clis/chatgpt/README.md +0 -5
  485. package/src/clis/chatgpt/README.zh-CN.md +0 -44
  486. package/src/clis/chatwise/README.md +0 -5
  487. package/src/clis/chatwise/README.zh-CN.md +0 -38
  488. package/src/clis/codex/README.md +0 -5
  489. package/src/clis/codex/README.zh-CN.md +0 -33
  490. package/src/clis/cursor/README.md +0 -5
  491. package/src/clis/cursor/README.zh-CN.md +0 -33
  492. package/src/clis/discord-app/README.md +0 -5
  493. package/src/clis/discord-app/README.zh-CN.md +0 -28
  494. package/src/clis/feishu/README.md +0 -5
  495. package/src/clis/feishu/README.zh-CN.md +0 -20
  496. package/src/clis/feishu/new.ts +0 -32
  497. package/src/clis/feishu/read.ts +0 -48
  498. package/src/clis/feishu/search.ts +0 -35
  499. package/src/clis/feishu/send.ts +0 -46
  500. package/src/clis/feishu/status.ts +0 -34
  501. package/src/clis/neteasemusic/README.md +0 -5
  502. package/src/clis/neteasemusic/README.zh-CN.md +0 -31
  503. package/src/clis/neteasemusic/like.ts +0 -28
  504. package/src/clis/neteasemusic/lyrics.ts +0 -53
  505. package/src/clis/neteasemusic/next.ts +0 -30
  506. package/src/clis/neteasemusic/play.ts +0 -30
  507. package/src/clis/neteasemusic/playing.ts +0 -62
  508. package/src/clis/neteasemusic/playlist.ts +0 -51
  509. package/src/clis/neteasemusic/prev.ts +0 -29
  510. package/src/clis/neteasemusic/search.ts +0 -58
  511. package/src/clis/neteasemusic/status.ts +0 -18
  512. package/src/clis/neteasemusic/volume.ts +0 -61
  513. package/src/clis/notion/README.md +0 -5
  514. package/src/clis/notion/README.zh-CN.md +0 -29
  515. package/src/clis/wechat/README.md +0 -5
  516. package/src/clis/wechat/README.zh-CN.md +0 -28
  517. package/src/clis/wechat/chats.ts +0 -33
  518. package/src/clis/wechat/contacts.ts +0 -33
  519. package/src/clis/wechat/read.ts +0 -72
  520. package/src/clis/wechat/search.ts +0 -36
  521. package/src/clis/wechat/send.ts +0 -49
  522. package/src/clis/wechat/status.ts +0 -35
  523. package/src/pipeline.ts +0 -8
@@ -99,7 +99,7 @@ cli({
99
99
  strategy: Strategy.COOKIE,
100
100
  browser: true,
101
101
  args: [
102
- { name: 'product-id', required: false, help: 'Coupang product ID' },
102
+ { name: 'product-id', positional: true, required: false, help: 'Coupang product ID' },
103
103
  { name: 'url', required: false, help: 'Canonical product URL' },
104
104
  ],
105
105
  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 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { loadDoubanBookHot } from './shared.js';
3
+ cli({
4
+ site: 'douban',
5
+ name: 'book-hot',
6
+ description: '豆瓣图书热门榜单',
7
+ domain: 'book.douban.com',
8
+ strategy: Strategy.COOKIE,
9
+ args: [
10
+ { name: 'limit', type: 'int', default: 20, help: '返回的图书数量' },
11
+ ],
12
+ columns: ['rank', 'title', 'rating', 'quote', 'author', 'publisher', 'year', 'url'],
13
+ func: async (page, args) => loadDoubanBookHot(page, Number(args.limit) || 20),
14
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,115 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { getSelfUid } from './utils.js';
3
+ cli({
4
+ site: 'douban',
5
+ name: 'marks',
6
+ description: '导出个人观影标记',
7
+ domain: 'movie.douban.com',
8
+ strategy: Strategy.COOKIE,
9
+ args: [
10
+ {
11
+ name: 'status',
12
+ default: 'collect',
13
+ choices: ['collect', 'wish', 'do', 'all'],
14
+ help: '标记类型: collect(看过), wish(想看), do(在看), all(全部)'
15
+ },
16
+ { name: 'limit', type: 'int', default: 50, help: '导出数量, 0 表示全部' },
17
+ { name: 'uid', help: '用户ID,不填则使用当前登录账号' },
18
+ ],
19
+ columns: ['title', 'year', 'myRating', 'myStatus', 'myDate', 'myComment', 'url'],
20
+ func: async (page, kwargs) => {
21
+ const { status = 'collect', limit = 50, uid: providedUid } = kwargs;
22
+ const uid = providedUid || await getSelfUid(page);
23
+ const statuses = status === 'all'
24
+ ? ['collect', 'wish', 'do']
25
+ : [status];
26
+ const allMarks = [];
27
+ for (const s of statuses) {
28
+ const remaining = limit > 0 ? limit - allMarks.length : 0;
29
+ if (limit > 0 && remaining <= 0)
30
+ break;
31
+ const marks = await fetchMarks(page, uid, s, remaining);
32
+ allMarks.push(...marks);
33
+ }
34
+ return allMarks.slice(0, limit > 0 ? limit : undefined);
35
+ },
36
+ });
37
+ async function fetchMarks(page, uid, status, limit) {
38
+ const marks = [];
39
+ let offset = 0;
40
+ const pageSize = 30;
41
+ while (true) {
42
+ const url = `https://movie.douban.com/people/${uid}/${status}?start=${offset}&sort=time&rating=all&filter=all&mode=grid`;
43
+ await page.goto(url);
44
+ await page.wait({ time: 2 });
45
+ const pageMarks = await page.evaluate(`
46
+ () => {
47
+ const results = [];
48
+
49
+ const items = document.querySelectorAll('.item');
50
+
51
+ items.forEach(item => {
52
+ const titleLink = item.querySelector('.info a[href*="/subject/"]');
53
+ if (!titleLink) return;
54
+
55
+ const titleEl = titleLink.querySelector('em');
56
+ const titleText = titleEl?.textContent?.trim() || titleLink.textContent?.trim() || '';
57
+ const title = titleText.split('/')[0].trim();
58
+ const href = titleLink.href || '';
59
+
60
+ const idMatch = href.match(/subject\\/(\\d+)/);
61
+ const movieId = idMatch ? idMatch[1] : '';
62
+
63
+ if (!movieId || !title) return;
64
+
65
+ const ratingSpan = item.querySelector('span[class*="rating"]');
66
+ let myRating = null;
67
+ if (ratingSpan) {
68
+ const cls = ratingSpan.className || '';
69
+ const ratingMatch = cls.match(/rating(\\d)-t/);
70
+ if (ratingMatch) {
71
+ myRating = parseInt(ratingMatch[1], 10) * 2;
72
+ }
73
+ }
74
+
75
+ const dateSpan = item.querySelector('.date');
76
+ const myDate = dateSpan?.textContent?.trim() || '';
77
+
78
+ const commentSpan = item.querySelector('.comment');
79
+ const myComment = commentSpan?.textContent?.trim() || '';
80
+
81
+ const introSpan = item.querySelector('.intro');
82
+ let year = '';
83
+ if (introSpan) {
84
+ const introText = introSpan.textContent || '';
85
+ const yearMatch = introText.match(/(\\d{4})/);
86
+ year = yearMatch ? yearMatch[1] : '';
87
+ }
88
+
89
+ results.push({
90
+ movieId,
91
+ title,
92
+ year,
93
+ myRating,
94
+ myStatus: '${status}',
95
+ myComment,
96
+ myDate,
97
+ url: href || 'https://movie.douban.com/subject/' + movieId
98
+ });
99
+ });
100
+
101
+ return results;
102
+ }
103
+ `);
104
+ if (!pageMarks || pageMarks.length === 0)
105
+ break;
106
+ marks.push(...pageMarks);
107
+ if (pageMarks.length < pageSize)
108
+ break;
109
+ if (limit > 0 && marks.length >= limit)
110
+ break;
111
+ offset += pageSize;
112
+ await new Promise(resolve => setTimeout(resolve, 1000));
113
+ }
114
+ return marks;
115
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { loadDoubanMovieHot } from './shared.js';
3
+ cli({
4
+ site: 'douban',
5
+ name: 'movie-hot',
6
+ description: '豆瓣电影热门榜单',
7
+ domain: 'movie.douban.com',
8
+ strategy: Strategy.COOKIE,
9
+ args: [
10
+ { name: 'limit', type: 'int', default: 20, help: '返回的电影数量' },
11
+ ],
12
+ columns: ['rank', 'title', 'rating', 'quote', 'director', 'year', 'region', 'url'],
13
+ func: async (page, args) => loadDoubanMovieHot(page, Number(args.limit) || 20),
14
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,106 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { getSelfUid } from './utils.js';
3
+ cli({
4
+ site: 'douban',
5
+ name: 'reviews',
6
+ description: '导出个人影评',
7
+ domain: 'movie.douban.com',
8
+ strategy: Strategy.COOKIE,
9
+ args: [
10
+ { name: 'limit', type: 'int', default: 20, help: '导出数量' },
11
+ { name: 'uid', help: '用户ID,不填则使用当前登录账号' },
12
+ { name: 'full', type: 'bool', default: false, help: '获取完整影评内容' },
13
+ ],
14
+ columns: ['movieTitle', 'title', 'myRating', 'votes', 'content', 'url'],
15
+ func: async (page, kwargs) => {
16
+ const { limit = 20, uid: providedUid, full = false } = kwargs;
17
+ const uid = providedUid || await getSelfUid(page);
18
+ const reviews = await fetchReviews(page, uid, limit, full);
19
+ return reviews;
20
+ },
21
+ });
22
+ async function fetchReviews(page, uid, limit, full) {
23
+ const reviews = [];
24
+ let start = 0;
25
+ const pageSize = 20;
26
+ while (true) {
27
+ const url = `https://movie.douban.com/people/${uid}/reviews?start=${start}&sort=time`;
28
+ await page.goto(url);
29
+ await page.wait({ time: 1 });
30
+ const data = await page.evaluate(`
31
+ () => {
32
+ const reviews = [];
33
+
34
+ document.querySelectorAll('.tlst').forEach(el => {
35
+ const movieLinkEl = el.querySelector('.ilst a');
36
+ const reviewTitleEl = el.querySelector('.nlst a[title]');
37
+ const ratingEl = el.querySelector('.clst span[class*="allstar"]');
38
+ const contentEl = el.querySelector('.review-short span');
39
+ const votesEl = el.querySelector('.review-short .pl span');
40
+
41
+ const movieHref = movieLinkEl?.href || '';
42
+ const movieId = movieHref.match(/subject\\/(\\d+)/)?.[1] || '';
43
+ const movieTitle = movieLinkEl?.getAttribute('title') || movieLinkEl?.textContent?.trim() || '';
44
+
45
+ const reviewHref = reviewTitleEl?.href || '';
46
+ const reviewId = reviewHref.match(/reviews\\/(\\d+)/)?.[1] || '';
47
+ const title = reviewTitleEl?.textContent?.trim() || '';
48
+
49
+ let myRating = 0;
50
+ if (ratingEl) {
51
+ const cls = ratingEl.className || '';
52
+ const ratingMatch = cls.match(/allstar(\\d)0/);
53
+ if (ratingMatch) {
54
+ myRating = parseInt(ratingMatch[1], 10) * 2;
55
+ }
56
+ }
57
+
58
+ const votesText = votesEl?.textContent || '';
59
+ const votesMatch = votesText.match(/(\\d+)/);
60
+ const votes = votesMatch ? parseInt(votesMatch[1], 10) : 0;
61
+
62
+ reviews.push({
63
+ reviewId,
64
+ movieId,
65
+ movieTitle,
66
+ title,
67
+ content: contentEl?.textContent?.trim() || '',
68
+ myRating,
69
+ createdAt: '',
70
+ votes,
71
+ url: reviewHref,
72
+ });
73
+ });
74
+
75
+ return reviews;
76
+ }
77
+ `);
78
+ reviews.push(...data);
79
+ if (data.length < pageSize)
80
+ break;
81
+ if (limit > 0 && reviews.length >= limit)
82
+ break;
83
+ start += pageSize;
84
+ }
85
+ const result = reviews.slice(0, limit > 0 ? limit : undefined);
86
+ if (full && result.length > 0) {
87
+ for (const review of result) {
88
+ if (review.url) {
89
+ const fullContent = await fetchFullReview(page, review.url);
90
+ review.content = fullContent;
91
+ }
92
+ }
93
+ }
94
+ return result;
95
+ }
96
+ async function fetchFullReview(page, reviewUrl) {
97
+ await page.goto(reviewUrl);
98
+ await page.wait({ time: 1 });
99
+ const content = await page.evaluate(`
100
+ () => {
101
+ const contentEl = document.querySelector('.review-content');
102
+ return contentEl?.textContent?.trim() || '';
103
+ }
104
+ `);
105
+ return content;
106
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,16 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { searchDouban } from './shared.js';
3
+ cli({
4
+ site: 'douban',
5
+ name: 'search',
6
+ description: '搜索豆瓣电影、图书或音乐',
7
+ domain: 'search.douban.com',
8
+ strategy: Strategy.COOKIE,
9
+ args: [
10
+ { name: 'type', default: 'movie', choices: ['movie', 'book', 'music'], help: '搜索类型(movie=电影, book=图书, music=音乐)' },
11
+ { name: 'keyword', required: true, positional: true, help: '搜索关键词' },
12
+ { name: 'limit', type: 'int', default: 20, help: '返回结果数量' },
13
+ ],
14
+ columns: ['rank', 'title', 'rating', 'abstract', 'url'],
15
+ func: async (page, args) => searchDouban(page, args.type, args.keyword, Number(args.limit) || 20),
16
+ });
@@ -0,0 +1,4 @@
1
+ import type { IPage } from '../../types.js';
2
+ export declare function loadDoubanBookHot(page: IPage, limit: number): Promise<any[]>;
3
+ export declare function loadDoubanMovieHot(page: IPage, limit: number): Promise<any[]>;
4
+ export declare function searchDouban(page: IPage, type: string, keyword: string, limit: number): Promise<any[]>;
@@ -0,0 +1,155 @@
1
+ import { CliError } from '../../errors.js';
2
+ function clampLimit(limit) {
3
+ return Math.max(1, Math.min(limit || 20, 50));
4
+ }
5
+ async function ensureDoubanReady(page) {
6
+ const state = await page.evaluate(`
7
+ (() => {
8
+ const title = (document.title || '').trim();
9
+ const href = (location.href || '').trim();
10
+ const blocked = href.includes('sec.douban.com') || /登录跳转/.test(title) || /异常请求/.test(document.body?.innerText || '');
11
+ return { blocked, title, href };
12
+ })()
13
+ `);
14
+ if (state?.blocked) {
15
+ throw new CliError('AUTH_REQUIRED', 'Douban requires a logged-in browser session before these commands can load data.', 'Please sign in to douban.com in the browser that opencli reuses, then rerun the command.');
16
+ }
17
+ }
18
+ export async function loadDoubanBookHot(page, limit) {
19
+ const safeLimit = clampLimit(limit);
20
+ await page.goto('https://book.douban.com/chart');
21
+ await page.wait(4);
22
+ await ensureDoubanReady(page);
23
+ const data = await page.evaluate(`
24
+ (() => {
25
+ const normalize = (value) => (value || '').replace(/\\s+/g, ' ').trim();
26
+ const books = [];
27
+ for (const el of Array.from(document.querySelectorAll('.media.clearfix'))) {
28
+ try {
29
+ const titleEl = el.querySelector('h2 a[href*="/subject/"]');
30
+ const title = normalize(titleEl?.textContent);
31
+ let url = titleEl?.getAttribute('href') || '';
32
+ if (!title || !url) continue;
33
+ if (!url.startsWith('http')) url = 'https://book.douban.com' + url;
34
+
35
+ const info = normalize(el.querySelector('.subject-abstract, .pl, .pub')?.textContent);
36
+ const infoParts = info.split('/').map((part) => part.trim()).filter(Boolean);
37
+ const ratingText = normalize(el.querySelector('.subject-rating .font-small, .rating_nums, .rating')?.textContent);
38
+ const quote = Array.from(el.querySelectorAll('.subject-tags .tag'))
39
+ .map((node) => normalize(node.textContent))
40
+ .filter(Boolean)
41
+ .join(' / ');
42
+
43
+ books.push({
44
+ rank: parseInt(normalize(el.querySelector('.green-num-box')?.textContent), 10) || books.length + 1,
45
+ title,
46
+ rating: parseFloat(ratingText) || 0,
47
+ quote,
48
+ author: infoParts[0] || '',
49
+ publisher: infoParts.find((part) => /出版社|出版公司|Press/i.test(part)) || infoParts[2] || '',
50
+ year: infoParts.find((part) => /\\d{4}(?:-\\d{1,2})?/.test(part))?.match(/\\d{4}/)?.[0] || '',
51
+ price: infoParts.find((part) => /元|USD|\\$|¥/.test(part)) || '',
52
+ url,
53
+ cover: el.querySelector('img')?.getAttribute('src') || '',
54
+ });
55
+ } catch {}
56
+ }
57
+ return books.slice(0, ${safeLimit});
58
+ })()
59
+ `);
60
+ return Array.isArray(data) ? data : [];
61
+ }
62
+ export async function loadDoubanMovieHot(page, limit) {
63
+ const safeLimit = clampLimit(limit);
64
+ await page.goto('https://movie.douban.com/chart');
65
+ await page.wait(4);
66
+ await ensureDoubanReady(page);
67
+ const data = await page.evaluate(`
68
+ (() => {
69
+ const normalize = (value) => (value || '').replace(/\\s+/g, ' ').trim();
70
+ const results = [];
71
+ for (const el of Array.from(document.querySelectorAll('.item'))) {
72
+ const titleEl = el.querySelector('.pl2 a');
73
+ const title = normalize(titleEl?.textContent);
74
+ let url = titleEl?.getAttribute('href') || '';
75
+ if (!title || !url) continue;
76
+ if (!url.startsWith('http')) url = 'https://movie.douban.com' + url;
77
+
78
+ const info = normalize(el.querySelector('.pl2 p')?.textContent);
79
+ const infoParts = info.split('/').map((part) => part.trim()).filter(Boolean);
80
+ const releaseIndex = (() => {
81
+ for (let i = infoParts.length - 1; i >= 0; i -= 1) {
82
+ if (/\\d{4}-\\d{2}-\\d{2}|\\d{4}\\/\\d{2}\\/\\d{2}/.test(infoParts[i])) return i;
83
+ }
84
+ return -1;
85
+ })();
86
+ const directorPart = releaseIndex >= 1 ? infoParts[releaseIndex - 1] : '';
87
+ const regionPart = releaseIndex >= 2 ? infoParts[releaseIndex - 2] : '';
88
+ const yearMatch = info.match(/\\b(19|20)\\d{2}\\b/);
89
+ results.push({
90
+ rank: results.length + 1,
91
+ title,
92
+ rating: parseFloat(normalize(el.querySelector('.rating_nums')?.textContent)) || 0,
93
+ quote: normalize(el.querySelector('.inq')?.textContent),
94
+ director: directorPart.replace(/^导演:\\s*/, ''),
95
+ year: yearMatch?.[0] || '',
96
+ region: regionPart,
97
+ url,
98
+ cover: el.querySelector('img')?.getAttribute('src') || '',
99
+ });
100
+ if (results.length >= ${safeLimit}) break;
101
+ }
102
+ return results;
103
+ })()
104
+ `);
105
+ return Array.isArray(data) ? data : [];
106
+ }
107
+ export async function searchDouban(page, type, keyword, limit) {
108
+ const safeLimit = clampLimit(limit);
109
+ await page.goto(`https://search.douban.com/${encodeURIComponent(type)}/subject_search?search_text=${encodeURIComponent(keyword)}`);
110
+ await page.wait(2);
111
+ await ensureDoubanReady(page);
112
+ const data = await page.evaluate(`
113
+ (async () => {
114
+ const type = ${JSON.stringify(type)};
115
+ const normalize = (value) => (value || '').replace(/\\s+/g, ' ').trim();
116
+ const seen = new Set();
117
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
118
+
119
+ for (let i = 0; i < 20; i += 1) {
120
+ if (document.querySelector('.item-root .title-text, .item-root .title a')) break;
121
+ await sleep(300);
122
+ }
123
+
124
+ const items = Array.from(document.querySelectorAll('.item-root'));
125
+
126
+ const results = [];
127
+ for (const el of items) {
128
+ const titleEl = el.querySelector('.title-text, .title a, a[title]');
129
+ const title = normalize(titleEl?.textContent) || normalize(titleEl?.getAttribute('title'));
130
+ let url = titleEl?.getAttribute('href') || '';
131
+ if (!title || !url) continue;
132
+ if (!url.startsWith('http')) url = 'https://search.douban.com' + url;
133
+ if (!url.includes('/subject/') || seen.has(url)) continue;
134
+ seen.add(url);
135
+ const ratingText = normalize(el.querySelector('.rating_nums')?.textContent);
136
+ const abstract = normalize(
137
+ el.querySelector('.meta.abstract, .meta, .abstract, p')?.textContent,
138
+ );
139
+ results.push({
140
+ rank: results.length + 1,
141
+ id: url.match(/subject\\/(\\d+)/)?.[1] || '',
142
+ type,
143
+ title,
144
+ rating: ratingText.includes('.') ? parseFloat(ratingText) : 0,
145
+ abstract: abstract.slice(0, 100) + (abstract.length > 100 ? '...' : ''),
146
+ url,
147
+ cover: el.querySelector('img')?.getAttribute('src') || '',
148
+ });
149
+ if (results.length >= ${safeLimit}) break;
150
+ }
151
+ return results;
152
+ })()
153
+ `);
154
+ return Array.isArray(data) ? data : [];
155
+ }
@@ -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]