@jackwener/opencli 1.3.2 → 1.4.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 (508) hide show
  1. package/.github/pull_request_template.md +3 -1
  2. package/.github/workflows/build-extension.yml +7 -1
  3. package/.github/workflows/ci.yml +29 -3
  4. package/.github/workflows/docs.yml +1 -1
  5. package/.github/workflows/e2e-headed.yml +20 -0
  6. package/.github/workflows/release.yml +1 -1
  7. package/.github/workflows/security.yml +0 -3
  8. package/CHANGELOG.md +55 -0
  9. package/CONTRIBUTING.md +6 -3
  10. package/README.md +37 -10
  11. package/README.zh-CN.md +37 -10
  12. package/SKILL.md +7 -2
  13. package/TESTING.md +1 -0
  14. package/chatwise-opencli.ps1 +82 -0
  15. package/dist/analysis.d.ts +38 -0
  16. package/dist/analysis.js +166 -0
  17. package/dist/browser/cdp.d.ts +0 -4
  18. package/dist/browser/cdp.js +59 -38
  19. package/dist/browser/cdp.test.d.ts +1 -0
  20. package/dist/browser/cdp.test.js +52 -0
  21. package/dist/browser/daemon-client.js +2 -1
  22. package/dist/browser/discover.js +2 -1
  23. package/dist/browser/dom-snapshot.d.ts +2 -2
  24. package/dist/browser/dom-snapshot.js +54 -1
  25. package/dist/browser/dom-snapshot.test.js +36 -0
  26. package/dist/browser/errors.js +2 -1
  27. package/dist/browser/index.d.ts +3 -2
  28. package/dist/browser/index.js +2 -1
  29. package/dist/browser/mcp.d.ts +0 -2
  30. package/dist/browser/mcp.js +2 -3
  31. package/dist/browser/page.d.ts +4 -3
  32. package/dist/browser/page.js +44 -35
  33. package/dist/browser/stealth.d.ts +16 -0
  34. package/dist/browser/stealth.js +155 -0
  35. package/dist/browser.test.js +47 -1
  36. package/dist/build-manifest.js +15 -9
  37. package/dist/build-manifest.test.js +12 -0
  38. package/dist/cascade.js +4 -2
  39. package/dist/cli-manifest.json +639 -258
  40. package/dist/cli.js +57 -29
  41. package/dist/clis/_shared/desktop-commands.d.ts +22 -0
  42. package/dist/clis/_shared/desktop-commands.js +108 -0
  43. package/dist/clis/antigravity/serve.js +5 -2
  44. package/dist/clis/arxiv/search.js +1 -1
  45. package/dist/clis/bilibili/dynamic.test.d.ts +1 -0
  46. package/dist/clis/bilibili/dynamic.test.js +68 -0
  47. package/dist/clis/bilibili/favorite.js +4 -2
  48. package/dist/clis/bilibili/following.js +3 -2
  49. package/dist/clis/bilibili/subtitle.js +8 -7
  50. package/dist/clis/bilibili/utils.js +2 -2
  51. package/dist/clis/boss/batchgreet.js +1 -1
  52. package/dist/clis/boss/chatlist.js +1 -1
  53. package/dist/clis/boss/chatmsg.js +1 -1
  54. package/dist/clis/boss/detail.js +1 -1
  55. package/dist/clis/boss/exchange.js +1 -1
  56. package/dist/clis/boss/greet.js +1 -1
  57. package/dist/clis/boss/invite.js +1 -1
  58. package/dist/clis/boss/joblist.js +1 -1
  59. package/dist/clis/boss/mark.js +4 -3
  60. package/dist/clis/boss/recommend.js +1 -1
  61. package/dist/clis/boss/resume.js +1 -1
  62. package/dist/clis/boss/search.js +1 -1
  63. package/dist/clis/boss/send.js +5 -4
  64. package/dist/clis/boss/stats.js +1 -1
  65. package/dist/clis/chatgpt/ask.js +4 -0
  66. package/dist/clis/chatgpt/new.js +5 -1
  67. package/dist/clis/chatgpt/read.js +5 -1
  68. package/dist/clis/chatgpt/send.js +2 -1
  69. package/dist/clis/chatgpt/status.js +5 -1
  70. package/dist/clis/chatwise/ask.js +8 -2
  71. package/dist/clis/chatwise/export.js +2 -0
  72. package/dist/clis/chatwise/history.js +2 -0
  73. package/dist/clis/chatwise/model.js +8 -3
  74. package/dist/clis/chatwise/new.js +3 -18
  75. package/dist/clis/chatwise/read.js +2 -0
  76. package/dist/clis/chatwise/screenshot.js +3 -27
  77. package/dist/clis/chatwise/send.js +8 -2
  78. package/dist/clis/chatwise/shared.d.ts +2 -0
  79. package/dist/clis/chatwise/shared.js +6 -0
  80. package/dist/clis/chatwise/status.js +3 -22
  81. package/dist/clis/codex/ask.js +6 -2
  82. package/dist/clis/codex/dump.js +2 -25
  83. package/dist/clis/codex/new.js +2 -25
  84. package/dist/clis/codex/screenshot.js +2 -27
  85. package/dist/clis/codex/send.js +6 -4
  86. package/dist/clis/codex/status.js +2 -22
  87. package/dist/clis/cursor/ask.js +2 -1
  88. package/dist/clis/cursor/composer.js +2 -1
  89. package/dist/clis/cursor/dump.js +2 -25
  90. package/dist/clis/cursor/new.js +2 -18
  91. package/dist/clis/cursor/read.js +2 -1
  92. package/dist/clis/cursor/screenshot.js +1 -30
  93. package/dist/clis/cursor/send.js +2 -1
  94. package/dist/clis/cursor/status.js +2 -21
  95. package/dist/clis/dictionary/examples.yaml +25 -0
  96. package/dist/clis/dictionary/search.yaml +27 -0
  97. package/dist/clis/dictionary/synonyms.yaml +25 -0
  98. package/dist/clis/douban/book-hot.js +1 -1
  99. package/dist/clis/douban/movie-hot.js +1 -1
  100. package/dist/clis/douban/search.js +1 -1
  101. package/dist/clis/douban/utils.d.ts +4 -1
  102. package/dist/clis/douban/utils.js +156 -1
  103. package/dist/clis/doubao/ask.js +1 -1
  104. package/dist/clis/doubao/new.js +1 -1
  105. package/dist/clis/doubao/read.js +1 -1
  106. package/dist/clis/doubao/send.js +1 -1
  107. package/dist/clis/doubao/status.js +1 -1
  108. package/dist/clis/doubao-app/ask.js +1 -1
  109. package/dist/clis/doubao-app/new.js +1 -1
  110. package/dist/clis/doubao-app/read.js +1 -1
  111. package/dist/clis/doubao-app/send.js +1 -1
  112. package/dist/clis/grok/ask.d.ts +4 -0
  113. package/dist/clis/grok/ask.js +28 -10
  114. package/dist/clis/grok/ask.test.js +18 -0
  115. package/dist/clis/jd/item.d.ts +1 -0
  116. package/dist/clis/jd/item.js +96 -0
  117. package/dist/clis/jd/item.test.d.ts +1 -0
  118. package/dist/clis/jd/item.test.js +28 -0
  119. package/dist/clis/jike/feed.js +1 -1
  120. package/dist/clis/jike/search.js +1 -1
  121. package/dist/clis/linkedin/search.js +5 -4
  122. package/dist/clis/linkedin/timeline.d.ts +21 -0
  123. package/dist/clis/linkedin/timeline.js +503 -0
  124. package/dist/clis/linkedin/timeline.test.d.ts +1 -0
  125. package/dist/clis/linkedin/timeline.test.js +81 -0
  126. package/dist/clis/medium/feed.js +1 -1
  127. package/dist/clis/medium/search.js +1 -1
  128. package/dist/clis/medium/user.js +1 -1
  129. package/dist/clis/medium/{shared.js → utils.js} +2 -1
  130. package/dist/clis/pixiv/detail.yaml +49 -0
  131. package/dist/clis/pixiv/download.d.ts +7 -0
  132. package/dist/clis/pixiv/download.js +78 -0
  133. package/dist/clis/pixiv/download.test.d.ts +1 -0
  134. package/dist/clis/pixiv/download.test.js +87 -0
  135. package/dist/clis/pixiv/illusts.d.ts +8 -0
  136. package/dist/clis/pixiv/illusts.js +65 -0
  137. package/dist/clis/pixiv/illusts.test.d.ts +1 -0
  138. package/dist/clis/pixiv/illusts.test.js +99 -0
  139. package/dist/clis/pixiv/ranking.yaml +53 -0
  140. package/dist/clis/pixiv/search.d.ts +6 -0
  141. package/dist/clis/pixiv/search.js +43 -0
  142. package/dist/clis/pixiv/search.test.d.ts +1 -0
  143. package/dist/clis/pixiv/search.test.js +83 -0
  144. package/dist/clis/pixiv/test-utils.d.ts +12 -0
  145. package/dist/clis/pixiv/test-utils.js +23 -0
  146. package/dist/clis/pixiv/user.yaml +46 -0
  147. package/dist/clis/pixiv/utils.d.ts +27 -0
  148. package/dist/clis/pixiv/utils.js +49 -0
  149. package/dist/clis/reddit/comment.js +2 -1
  150. package/dist/clis/reddit/read.js +4 -3
  151. package/dist/clis/reddit/read.test.d.ts +1 -0
  152. package/dist/clis/reddit/read.test.js +28 -0
  153. package/dist/clis/reddit/save.js +2 -1
  154. package/dist/clis/reddit/saved.js +7 -3
  155. package/dist/clis/reddit/subscribe.js +2 -1
  156. package/dist/clis/reddit/upvote.js +2 -1
  157. package/dist/clis/reddit/upvoted.js +7 -3
  158. package/dist/clis/sinablog/article.js +1 -1
  159. package/dist/clis/sinablog/hot.js +1 -1
  160. package/dist/clis/sinablog/user.js +1 -1
  161. package/dist/clis/substack/feed.js +1 -1
  162. package/dist/clis/substack/publication.js +1 -1
  163. package/dist/clis/substack/search.js +3 -2
  164. package/dist/clis/substack/{shared.js → utils.js} +3 -2
  165. package/dist/clis/tiktok/search.yaml +2 -1
  166. package/dist/clis/twitter/accept.js +2 -1
  167. package/dist/clis/twitter/article.js +4 -1
  168. package/dist/clis/twitter/block.js +2 -1
  169. package/dist/clis/twitter/bookmark.js +2 -1
  170. package/dist/clis/twitter/bookmarks.js +3 -2
  171. package/dist/clis/twitter/delete.js +2 -1
  172. package/dist/clis/twitter/follow.js +2 -1
  173. package/dist/clis/twitter/followers.js +3 -2
  174. package/dist/clis/twitter/following.js +3 -2
  175. package/dist/clis/twitter/hide-reply.js +2 -1
  176. package/dist/clis/twitter/like.js +2 -1
  177. package/dist/clis/twitter/notifications.js +2 -1
  178. package/dist/clis/twitter/post.js +2 -1
  179. package/dist/clis/twitter/profile.js +5 -2
  180. package/dist/clis/twitter/reply-dm.js +2 -1
  181. package/dist/clis/twitter/reply.js +2 -1
  182. package/dist/clis/twitter/search.js +30 -13
  183. package/dist/clis/twitter/search.test.d.ts +1 -0
  184. package/dist/clis/twitter/search.test.js +104 -0
  185. package/dist/clis/twitter/thread.js +2 -2
  186. package/dist/clis/twitter/timeline.js +3 -2
  187. package/dist/clis/twitter/trending.js +3 -2
  188. package/dist/clis/twitter/unblock.js +2 -1
  189. package/dist/clis/twitter/unbookmark.js +2 -1
  190. package/dist/clis/twitter/unfollow.js +2 -1
  191. package/dist/clis/v2ex/daily.js +3 -2
  192. package/dist/clis/v2ex/me.js +3 -2
  193. package/dist/clis/v2ex/notifications.js +4 -4
  194. package/dist/clis/web/read.d.ts +16 -0
  195. package/dist/clis/web/read.js +202 -0
  196. package/dist/clis/xueqiu/danjuan-utils.d.ts +55 -0
  197. package/dist/clis/xueqiu/danjuan-utils.js +126 -0
  198. package/dist/clis/xueqiu/danjuan-utils.test.d.ts +1 -0
  199. package/dist/clis/xueqiu/danjuan-utils.test.js +41 -0
  200. package/dist/clis/xueqiu/fund-holdings.d.ts +1 -0
  201. package/dist/clis/xueqiu/fund-holdings.js +28 -0
  202. package/dist/clis/xueqiu/fund-snapshot.d.ts +1 -0
  203. package/dist/clis/xueqiu/fund-snapshot.js +25 -0
  204. package/dist/clis/youtube/transcript.js +5 -4
  205. package/dist/clis/youtube/video.js +3 -2
  206. package/dist/constants.d.ts +2 -0
  207. package/dist/constants.js +2 -0
  208. package/dist/daemon.js +9 -4
  209. package/dist/discovery.js +11 -10
  210. package/dist/doctor.js +4 -2
  211. package/dist/download/index.d.ts +4 -12
  212. package/dist/download/index.js +33 -12
  213. package/dist/download/index.test.js +79 -2
  214. package/dist/download/media-download.js +4 -2
  215. package/dist/engine.test.js +76 -4
  216. package/dist/execution.d.ts +1 -9
  217. package/dist/execution.js +56 -46
  218. package/dist/explore.js +12 -111
  219. package/dist/external-clis.yaml +0 -8
  220. package/dist/external.js +7 -5
  221. package/dist/external.test.js +4 -0
  222. package/dist/generate.d.ts +0 -9
  223. package/dist/generate.js +4 -20
  224. package/dist/hooks.d.ts +46 -0
  225. package/dist/hooks.js +56 -0
  226. package/dist/hooks.test.d.ts +4 -0
  227. package/dist/hooks.test.js +92 -0
  228. package/dist/interceptor.js +70 -23
  229. package/dist/main.js +2 -0
  230. package/dist/output.js +12 -6
  231. package/dist/pipeline/executor.js +1 -1
  232. package/dist/pipeline/steps/browser.js +1 -3
  233. package/dist/pipeline/steps/download.js +42 -26
  234. package/dist/pipeline/steps/download.test.d.ts +1 -0
  235. package/dist/pipeline/steps/download.test.js +101 -0
  236. package/dist/pipeline/steps/fetch.js +40 -22
  237. package/dist/pipeline/steps/fetch.test.d.ts +1 -0
  238. package/dist/pipeline/steps/fetch.test.js +123 -0
  239. package/dist/pipeline/steps/transform.js +2 -6
  240. package/dist/pipeline/template.js +66 -52
  241. package/dist/pipeline/template.test.js +28 -0
  242. package/dist/pipeline/transform.test.js +18 -0
  243. package/dist/plugin.d.ts +40 -1
  244. package/dist/plugin.js +214 -17
  245. package/dist/plugin.test.d.ts +1 -1
  246. package/dist/plugin.test.js +219 -3
  247. package/dist/record.js +6 -98
  248. package/dist/registry-api.d.ts +2 -0
  249. package/dist/registry-api.js +1 -0
  250. package/dist/registry.d.ts +5 -2
  251. package/dist/registry.js +1 -2
  252. package/dist/runtime.d.ts +0 -1
  253. package/dist/runtime.js +14 -4
  254. package/dist/snapshotFormatter.d.ts +7 -14
  255. package/dist/snapshotFormatter.js +38 -78
  256. package/dist/utils.d.ts +9 -0
  257. package/dist/utils.js +29 -0
  258. package/dist/validate.js +3 -5
  259. package/dist/yaml-schema.d.ts +26 -0
  260. package/dist/yaml-schema.js +5 -0
  261. package/docs/.vitepress/config.mts +3 -0
  262. package/docs/adapters/browser/dictionary.md +27 -0
  263. package/docs/adapters/browser/douban.md +18 -8
  264. package/docs/adapters/browser/jd.md +27 -0
  265. package/docs/adapters/browser/linkedin.md +6 -0
  266. package/docs/adapters/browser/pixiv.md +92 -0
  267. package/docs/adapters/browser/web.md +30 -0
  268. package/docs/adapters/browser/wikipedia.md +0 -9
  269. package/docs/adapters/browser/xueqiu.md +27 -9
  270. package/docs/adapters/desktop/antigravity.md +0 -3
  271. package/docs/adapters/index.md +11 -9
  272. package/docs/comparison.md +125 -0
  273. package/docs/developer/contributing.md +21 -2
  274. package/docs/developer/testing.md +14 -8
  275. package/docs/developer/ts-adapter.md +18 -0
  276. package/docs/developer/yaml-adapter.md +16 -0
  277. package/docs/guide/plugins.md +10 -0
  278. package/docs/zh/guide/plugins.md +10 -0
  279. package/extension/dist/background.js +519 -444
  280. package/extension/manifest.json +1 -1
  281. package/extension/package.json +1 -1
  282. package/extension/src/background.test.ts +46 -1
  283. package/extension/src/background.ts +108 -33
  284. package/extension/src/cdp.ts +9 -9
  285. package/package.json +3 -2
  286. package/scripts/check-doc-coverage.sh +2 -0
  287. package/src/analysis.ts +170 -0
  288. package/src/browser/cdp.test.ts +66 -0
  289. package/src/browser/cdp.ts +64 -41
  290. package/src/browser/daemon-client.ts +4 -3
  291. package/src/browser/discover.ts +2 -1
  292. package/src/browser/dom-snapshot.test.ts +42 -0
  293. package/src/browser/dom-snapshot.ts +56 -3
  294. package/src/browser/errors.ts +2 -1
  295. package/src/browser/index.ts +3 -2
  296. package/src/browser/mcp.ts +2 -4
  297. package/src/browser/page.ts +43 -35
  298. package/src/browser/stealth.ts +156 -0
  299. package/src/browser.test.ts +51 -1
  300. package/src/build-manifest.test.ts +14 -0
  301. package/src/build-manifest.ts +13 -32
  302. package/src/cascade.ts +5 -3
  303. package/src/cli.ts +66 -34
  304. package/src/clis/_shared/desktop-commands.ts +121 -0
  305. package/src/clis/antigravity/serve.ts +6 -3
  306. package/src/clis/arxiv/search.ts +1 -1
  307. package/src/clis/bilibili/dynamic.test.ts +79 -0
  308. package/src/clis/bilibili/favorite.ts +5 -2
  309. package/src/clis/bilibili/following.ts +3 -2
  310. package/src/clis/bilibili/subtitle.ts +8 -7
  311. package/src/clis/bilibili/utils.ts +2 -2
  312. package/src/clis/boss/batchgreet.ts +1 -1
  313. package/src/clis/boss/chatlist.ts +1 -1
  314. package/src/clis/boss/chatmsg.ts +1 -1
  315. package/src/clis/boss/detail.ts +1 -1
  316. package/src/clis/boss/exchange.ts +1 -1
  317. package/src/clis/boss/greet.ts +1 -1
  318. package/src/clis/boss/invite.ts +1 -1
  319. package/src/clis/boss/joblist.ts +1 -1
  320. package/src/clis/boss/mark.ts +4 -3
  321. package/src/clis/boss/recommend.ts +1 -1
  322. package/src/clis/boss/resume.ts +1 -1
  323. package/src/clis/boss/search.ts +1 -1
  324. package/src/clis/boss/send.ts +5 -4
  325. package/src/clis/boss/stats.ts +1 -1
  326. package/src/clis/chatgpt/ask.ts +5 -0
  327. package/src/clis/chatgpt/new.ts +7 -2
  328. package/src/clis/chatgpt/read.ts +7 -2
  329. package/src/clis/chatgpt/send.ts +3 -2
  330. package/src/clis/chatgpt/status.ts +6 -1
  331. package/src/clis/chatwise/ask.ts +7 -2
  332. package/src/clis/chatwise/export.ts +2 -0
  333. package/src/clis/chatwise/history.ts +2 -0
  334. package/src/clis/chatwise/model.ts +7 -3
  335. package/src/clis/chatwise/new.ts +3 -20
  336. package/src/clis/chatwise/read.ts +2 -0
  337. package/src/clis/chatwise/screenshot.ts +3 -32
  338. package/src/clis/chatwise/send.ts +7 -2
  339. package/src/clis/chatwise/shared.ts +8 -0
  340. package/src/clis/chatwise/status.ts +3 -24
  341. package/src/clis/codex/ask.ts +5 -2
  342. package/src/clis/codex/dump.ts +2 -27
  343. package/src/clis/codex/new.ts +2 -28
  344. package/src/clis/codex/screenshot.ts +2 -32
  345. package/src/clis/codex/send.ts +5 -4
  346. package/src/clis/codex/status.ts +2 -24
  347. package/src/clis/cursor/ask.ts +2 -1
  348. package/src/clis/cursor/composer.ts +2 -1
  349. package/src/clis/cursor/dump.ts +2 -27
  350. package/src/clis/cursor/new.ts +2 -20
  351. package/src/clis/cursor/read.ts +2 -1
  352. package/src/clis/cursor/screenshot.ts +1 -36
  353. package/src/clis/cursor/send.ts +2 -1
  354. package/src/clis/cursor/status.ts +2 -22
  355. package/src/clis/dictionary/examples.yaml +25 -0
  356. package/src/clis/dictionary/search.yaml +27 -0
  357. package/src/clis/dictionary/synonyms.yaml +25 -0
  358. package/src/clis/douban/book-hot.ts +1 -1
  359. package/src/clis/douban/movie-hot.ts +1 -1
  360. package/src/clis/douban/search.ts +1 -1
  361. package/src/clis/douban/utils.ts +165 -1
  362. package/src/clis/doubao/ask.ts +1 -1
  363. package/src/clis/doubao/new.ts +1 -1
  364. package/src/clis/doubao/read.ts +1 -1
  365. package/src/clis/doubao/send.ts +1 -1
  366. package/src/clis/doubao/status.ts +1 -1
  367. package/src/clis/doubao-app/ask.ts +1 -1
  368. package/src/clis/doubao-app/new.ts +1 -1
  369. package/src/clis/doubao-app/read.ts +1 -1
  370. package/src/clis/doubao-app/send.ts +1 -1
  371. package/src/clis/grok/ask.test.ts +25 -0
  372. package/src/clis/grok/ask.ts +25 -12
  373. package/src/clis/jd/item.test.ts +35 -0
  374. package/src/clis/jd/item.ts +101 -0
  375. package/src/clis/jike/feed.ts +1 -1
  376. package/src/clis/jike/search.ts +1 -1
  377. package/src/clis/linkedin/search.ts +5 -4
  378. package/src/clis/linkedin/timeline.test.ts +99 -0
  379. package/src/clis/linkedin/timeline.ts +532 -0
  380. package/src/clis/medium/feed.ts +1 -1
  381. package/src/clis/medium/search.ts +1 -1
  382. package/src/clis/medium/user.ts +1 -1
  383. package/src/clis/medium/{shared.ts → utils.ts} +2 -1
  384. package/src/clis/pixiv/detail.yaml +49 -0
  385. package/src/clis/pixiv/download.test.ts +114 -0
  386. package/src/clis/pixiv/download.ts +91 -0
  387. package/src/clis/pixiv/illusts.test.ts +115 -0
  388. package/src/clis/pixiv/illusts.ts +78 -0
  389. package/src/clis/pixiv/ranking.yaml +53 -0
  390. package/src/clis/pixiv/search.test.ts +97 -0
  391. package/src/clis/pixiv/search.ts +53 -0
  392. package/src/clis/pixiv/test-utils.ts +29 -0
  393. package/src/clis/pixiv/user.yaml +46 -0
  394. package/src/clis/pixiv/utils.ts +62 -0
  395. package/src/clis/reddit/comment.ts +2 -1
  396. package/src/clis/reddit/read.test.ts +34 -0
  397. package/src/clis/reddit/read.ts +4 -3
  398. package/src/clis/reddit/save.ts +2 -1
  399. package/src/clis/reddit/saved.ts +6 -2
  400. package/src/clis/reddit/subscribe.ts +2 -1
  401. package/src/clis/reddit/upvote.ts +2 -1
  402. package/src/clis/reddit/upvoted.ts +6 -2
  403. package/src/clis/sinablog/article.ts +1 -1
  404. package/src/clis/sinablog/hot.ts +1 -1
  405. package/src/clis/sinablog/user.ts +1 -1
  406. package/src/clis/substack/feed.ts +1 -1
  407. package/src/clis/substack/publication.ts +1 -1
  408. package/src/clis/substack/search.ts +3 -2
  409. package/src/clis/substack/{shared.ts → utils.ts} +3 -2
  410. package/src/clis/tiktok/search.yaml +2 -1
  411. package/src/clis/twitter/accept.ts +2 -1
  412. package/src/clis/twitter/article.ts +3 -1
  413. package/src/clis/twitter/block.ts +2 -1
  414. package/src/clis/twitter/bookmark.ts +2 -1
  415. package/src/clis/twitter/bookmarks.ts +3 -2
  416. package/src/clis/twitter/delete.ts +2 -1
  417. package/src/clis/twitter/follow.ts +2 -1
  418. package/src/clis/twitter/followers.ts +3 -2
  419. package/src/clis/twitter/following.ts +3 -2
  420. package/src/clis/twitter/hide-reply.ts +2 -1
  421. package/src/clis/twitter/like.ts +2 -1
  422. package/src/clis/twitter/notifications.ts +2 -1
  423. package/src/clis/twitter/post.ts +2 -1
  424. package/src/clis/twitter/profile.ts +4 -2
  425. package/src/clis/twitter/reply-dm.ts +2 -1
  426. package/src/clis/twitter/reply.ts +2 -1
  427. package/src/clis/twitter/search.test.ts +113 -0
  428. package/src/clis/twitter/search.ts +38 -14
  429. package/src/clis/twitter/thread.ts +2 -2
  430. package/src/clis/twitter/timeline.ts +3 -2
  431. package/src/clis/twitter/trending.ts +3 -2
  432. package/src/clis/twitter/unblock.ts +2 -1
  433. package/src/clis/twitter/unbookmark.ts +2 -1
  434. package/src/clis/twitter/unfollow.ts +2 -1
  435. package/src/clis/v2ex/daily.ts +3 -2
  436. package/src/clis/v2ex/me.ts +3 -2
  437. package/src/clis/v2ex/notifications.ts +3 -4
  438. package/src/clis/web/read.ts +210 -0
  439. package/src/clis/xueqiu/danjuan-utils.test.ts +49 -0
  440. package/src/clis/xueqiu/danjuan-utils.ts +176 -0
  441. package/src/clis/xueqiu/fund-holdings.ts +32 -0
  442. package/src/clis/xueqiu/fund-snapshot.ts +27 -0
  443. package/src/clis/youtube/transcript.ts +5 -4
  444. package/src/clis/youtube/video.ts +3 -2
  445. package/src/constants.ts +3 -0
  446. package/src/daemon.ts +7 -5
  447. package/src/discovery.ts +12 -34
  448. package/src/doctor.ts +5 -3
  449. package/src/download/index.test.ts +93 -2
  450. package/src/download/index.ts +44 -23
  451. package/src/download/media-download.ts +5 -3
  452. package/src/engine.test.ts +84 -3
  453. package/src/execution.ts +62 -46
  454. package/src/explore.ts +21 -90
  455. package/src/external-clis.yaml +0 -8
  456. package/src/external.test.ts +9 -0
  457. package/src/external.ts +12 -10
  458. package/src/generate.ts +4 -41
  459. package/src/hooks.test.ts +126 -0
  460. package/src/hooks.ts +90 -0
  461. package/src/interceptor.ts +73 -23
  462. package/src/main.ts +2 -0
  463. package/src/output.ts +14 -6
  464. package/src/pipeline/executor.ts +1 -1
  465. package/src/pipeline/steps/browser.ts +1 -3
  466. package/src/pipeline/steps/download.test.ts +136 -0
  467. package/src/pipeline/steps/download.ts +47 -34
  468. package/src/pipeline/steps/fetch.test.ts +179 -0
  469. package/src/pipeline/steps/fetch.ts +39 -23
  470. package/src/pipeline/steps/transform.ts +2 -6
  471. package/src/pipeline/template.test.ts +28 -0
  472. package/src/pipeline/template.ts +67 -79
  473. package/src/pipeline/transform.test.ts +20 -0
  474. package/src/plugin.test.ts +251 -3
  475. package/src/plugin.ts +265 -21
  476. package/src/record.ts +12 -84
  477. package/src/registry-api.ts +2 -0
  478. package/src/registry.ts +7 -4
  479. package/src/runtime.ts +14 -4
  480. package/src/snapshotFormatter.ts +43 -121
  481. package/src/utils.ts +39 -0
  482. package/src/validate.ts +3 -6
  483. package/src/yaml-schema.ts +28 -0
  484. package/tests/e2e/browser-auth.test.ts +25 -0
  485. package/tests/e2e/plugin-management.test.ts +137 -0
  486. package/tests/e2e/public-commands.test.ts +34 -1
  487. package/vitest.config.ts +19 -1
  488. package/.github/workflows/pkg-pr-new.yml +0 -30
  489. package/dist/clis/douban/shared.d.ts +0 -4
  490. package/dist/clis/douban/shared.js +0 -155
  491. package/src/clis/douban/shared.ts +0 -165
  492. /package/dist/clis/boss/{common.d.ts → utils.d.ts} +0 -0
  493. /package/dist/clis/boss/{common.js → utils.js} +0 -0
  494. /package/dist/clis/doubao/{common.d.ts → utils.d.ts} +0 -0
  495. /package/dist/clis/doubao/{common.js → utils.js} +0 -0
  496. /package/dist/clis/doubao-app/{common.d.ts → utils.d.ts} +0 -0
  497. /package/dist/clis/doubao-app/{common.js → utils.js} +0 -0
  498. /package/dist/clis/jike/{shared.d.ts → utils.d.ts} +0 -0
  499. /package/dist/clis/jike/{shared.js → utils.js} +0 -0
  500. /package/dist/clis/medium/{shared.d.ts → utils.d.ts} +0 -0
  501. /package/dist/clis/sinablog/{shared.d.ts → utils.d.ts} +0 -0
  502. /package/dist/clis/sinablog/{shared.js → utils.js} +0 -0
  503. /package/dist/clis/substack/{shared.d.ts → utils.d.ts} +0 -0
  504. /package/src/clis/boss/{common.ts → utils.ts} +0 -0
  505. /package/src/clis/doubao/{common.ts → utils.ts} +0 -0
  506. /package/src/clis/doubao-app/{common.ts → utils.ts} +0 -0
  507. /package/src/clis/jike/{shared.ts → utils.ts} +0 -0
  508. /package/src/clis/sinablog/{shared.ts → utils.ts} +0 -0
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Pixiv download — download all images from an illustration.
3
+ *
4
+ * Pixiv's CDN (i.pximg.net) requires Referer: https://www.pixiv.net/ header.
5
+ * Uses the /ajax/illust/{id}/pages API to get original-quality image URLs.
6
+ */
7
+ import * as fs from 'node:fs';
8
+ import * as path from 'node:path';
9
+ import { cli, Strategy } from '../../registry.js';
10
+ import { formatCookieHeader, httpDownload } from '../../download/index.js';
11
+ import { formatBytes } from '../../download/progress.js';
12
+ import { CommandExecutionError, getErrorMessage } from '../../errors.js';
13
+ import { pixivFetch } from './utils.js';
14
+ cli({
15
+ site: 'pixiv',
16
+ name: 'download',
17
+ description: 'Download illustration images from Pixiv',
18
+ domain: 'www.pixiv.net',
19
+ strategy: Strategy.COOKIE,
20
+ args: [
21
+ { name: 'illust-id', positional: true, required: true, help: 'Illustration ID' },
22
+ { name: 'output', default: './pixiv-downloads', help: 'Output directory' },
23
+ ],
24
+ columns: ['index', 'type', 'status', 'size'],
25
+ func: async (page, kwargs) => {
26
+ const illustId = String(kwargs['illust-id'] ?? '');
27
+ const output = String(kwargs.output ?? './pixiv-downloads');
28
+ if (!/^\d+$/.test(illustId)) {
29
+ throw new CommandExecutionError(`Invalid illustration ID: ${illustId}`);
30
+ }
31
+ // pixivFetch handles navigate + error checking; returns the response body directly
32
+ const pages = await pixivFetch(page, `/ajax/illust/${illustId}/pages`, {
33
+ notFoundMsg: `Illustration not found: ${illustId}`,
34
+ }) || [];
35
+ if (pages.length === 0) {
36
+ return [{ index: 0, type: '-', status: 'failed', size: 'No images found' }];
37
+ }
38
+ // Extract cookies for authenticated downloads
39
+ const cookies = formatCookieHeader(await page.getCookies({ domain: 'pixiv.net' }));
40
+ // Create output directory
41
+ const outputDir = path.join(output, illustId);
42
+ fs.mkdirSync(outputDir, { recursive: true });
43
+ const results = [];
44
+ for (let i = 0; i < pages.length; i++) {
45
+ const p = pages[i];
46
+ const url = p.urls?.original || p.urls?.regular || '';
47
+ if (!url) {
48
+ results.push({ index: i + 1, type: 'image', status: 'failed', size: 'No URL' });
49
+ continue;
50
+ }
51
+ try {
52
+ const ext = path.extname(new URL(url).pathname) || '.jpg';
53
+ const filename = `${illustId}_p${i}${ext}`;
54
+ const destPath = path.join(outputDir, filename);
55
+ const result = await httpDownload(url, destPath, {
56
+ cookies,
57
+ headers: { Referer: 'https://www.pixiv.net/' },
58
+ timeout: 60000,
59
+ });
60
+ results.push({
61
+ index: i + 1,
62
+ type: 'image',
63
+ status: result.success ? 'success' : 'failed',
64
+ size: result.success ? formatBytes(result.size) : (result.error || 'unknown error'),
65
+ });
66
+ }
67
+ catch (err) {
68
+ results.push({
69
+ index: i + 1,
70
+ type: 'image',
71
+ status: 'failed',
72
+ size: getErrorMessage(err),
73
+ });
74
+ }
75
+ }
76
+ return results;
77
+ },
78
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,87 @@
1
+ import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { getRegistry } from '../../registry.js';
3
+ import { AuthRequiredError, CommandExecutionError } from '../../errors.js';
4
+ import { createPageMock } from './test-utils.js';
5
+ // Mock download dependencies before importing the adapter
6
+ const { mockHttpDownload, mockMkdirSync } = vi.hoisted(() => ({
7
+ mockHttpDownload: vi.fn(),
8
+ mockMkdirSync: vi.fn(),
9
+ }));
10
+ vi.mock('../../download/index.js', () => ({
11
+ formatCookieHeader: vi.fn().mockReturnValue('cookie=value'),
12
+ httpDownload: mockHttpDownload,
13
+ }));
14
+ vi.mock('node:fs', () => ({
15
+ mkdirSync: mockMkdirSync,
16
+ }));
17
+ // Now import the adapter (after mocks are set up)
18
+ await import('./download.js');
19
+ let cmd;
20
+ beforeAll(() => {
21
+ cmd = getRegistry().get('pixiv/download');
22
+ expect(cmd?.func).toBeTypeOf('function');
23
+ });
24
+ describe('pixiv download', () => {
25
+ beforeEach(() => {
26
+ mockHttpDownload.mockReset();
27
+ mockMkdirSync.mockReset();
28
+ });
29
+ it('throws CommandExecutionError on invalid illust ID', async () => {
30
+ const page = createPageMock([]);
31
+ await expect(cmd.func(page, { 'illust-id': 'abc', output: '/tmp/test' })).rejects.toThrow(CommandExecutionError);
32
+ });
33
+ it('throws AuthRequiredError on 403', async () => {
34
+ const page = createPageMock([{ __httpError: 403 }]);
35
+ await expect(cmd.func(page, { 'illust-id': '12345', output: '/tmp/test' })).rejects.toThrow(AuthRequiredError);
36
+ });
37
+ it('throws CommandExecutionError on 404', async () => {
38
+ const page = createPageMock([{ __httpError: 404 }]);
39
+ await expect(cmd.func(page, { 'illust-id': '12345', output: '/tmp/test' })).rejects.toThrow(CommandExecutionError);
40
+ });
41
+ it('throws CommandExecutionError on non-auth HTTP failure', async () => {
42
+ const page = createPageMock([{ __httpError: 500 }]);
43
+ await expect(cmd.func(page, { 'illust-id': '12345', output: '/tmp/test' })).rejects.toThrow(CommandExecutionError);
44
+ });
45
+ it('returns failure when no images found', async () => {
46
+ const page = createPageMock([{ body: [] }]);
47
+ const result = (await cmd.func(page, { 'illust-id': '12345', output: '/tmp/test' }));
48
+ expect(result).toEqual([{ index: 0, type: '-', status: 'failed', size: 'No images found' }]);
49
+ });
50
+ it('downloads images with Referer header', async () => {
51
+ mockHttpDownload.mockResolvedValue({ success: true, size: 1024000 });
52
+ const page = createPageMock([
53
+ {
54
+ body: [
55
+ { urls: { original: 'https://i.pximg.net/img-original/img/2025/01/01/00/00/00/12345_p0.png' } },
56
+ { urls: { original: 'https://i.pximg.net/img-original/img/2025/01/01/00/00/00/12345_p1.jpg' } },
57
+ ],
58
+ },
59
+ ]);
60
+ const result = (await cmd.func(page, { 'illust-id': '12345', output: '/tmp/test' }));
61
+ expect(result).toHaveLength(2);
62
+ expect(result[0]).toMatchObject({ index: 1, type: 'image', status: 'success' });
63
+ expect(result[1]).toMatchObject({ index: 2, type: 'image', status: 'success' });
64
+ // Verify Referer header was passed
65
+ expect(mockHttpDownload).toHaveBeenCalledTimes(2);
66
+ const firstCallOpts = mockHttpDownload.mock.calls[0][2];
67
+ expect(firstCallOpts.headers).toEqual({ Referer: 'https://www.pixiv.net/' });
68
+ });
69
+ it('handles individual download failures gracefully', async () => {
70
+ mockHttpDownload
71
+ .mockResolvedValueOnce({ success: true, size: 512000 })
72
+ .mockRejectedValueOnce(new Error('Connection timeout'));
73
+ const page = createPageMock([
74
+ {
75
+ body: [
76
+ { urls: { original: 'https://i.pximg.net/img/12345_p0.png' } },
77
+ { urls: { original: 'https://i.pximg.net/img/12345_p1.png' } },
78
+ ],
79
+ },
80
+ ]);
81
+ const result = (await cmd.func(page, { 'illust-id': '12345', output: '/tmp/test' }));
82
+ expect(result).toHaveLength(2);
83
+ expect(result[0].status).toBe('success');
84
+ expect(result[1].status).toBe('failed');
85
+ expect(result[1].size).toBe('Connection timeout');
86
+ });
87
+ });
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Pixiv illusts — list illustrations by an artist.
3
+ *
4
+ * Two-step process:
5
+ * 1. Fetch all illust IDs from the user's profile
6
+ * 2. Batch-fetch details for the most recent ones (max 48 IDs per request)
7
+ */
8
+ export {};
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Pixiv illusts — list illustrations by an artist.
3
+ *
4
+ * Two-step process:
5
+ * 1. Fetch all illust IDs from the user's profile
6
+ * 2. Batch-fetch details for the most recent ones (max 48 IDs per request)
7
+ */
8
+ import { cli, Strategy } from '../../registry.js';
9
+ import { CommandExecutionError } from '../../errors.js';
10
+ import { pixivFetch, BATCH_SIZE } from './utils.js';
11
+ cli({
12
+ site: 'pixiv',
13
+ name: 'illusts',
14
+ description: "List a Pixiv artist's illustrations",
15
+ domain: 'www.pixiv.net',
16
+ strategy: Strategy.COOKIE,
17
+ args: [
18
+ { name: 'user-id', positional: true, required: true, help: 'Pixiv user ID' },
19
+ { name: 'limit', type: 'int', default: 20, help: 'Number of results' },
20
+ ],
21
+ columns: ['rank', 'title', 'illust_id', 'pages', 'bookmarks', 'tags', 'created'],
22
+ func: async (page, kwargs) => {
23
+ const userId = String(kwargs['user-id'] ?? '');
24
+ const limit = Number(kwargs.limit) || 20;
25
+ if (!/^\d+$/.test(userId)) {
26
+ throw new CommandExecutionError(`Invalid user ID: ${userId}`);
27
+ }
28
+ // Step 1: get all illust IDs
29
+ const profileBody = await pixivFetch(page, `/ajax/user/${userId}/profile/all`, {
30
+ notFoundMsg: `User not found: ${userId}`,
31
+ });
32
+ const allIds = Object.keys(profileBody?.illusts || {})
33
+ .sort((a, b) => Number(b) - Number(a))
34
+ .slice(0, limit);
35
+ if (allIds.length === 0)
36
+ return [];
37
+ // Step 2: batch fetch details (Pixiv supports up to ~48 IDs per request)
38
+ const allWorks = {};
39
+ for (let offset = 0; offset < allIds.length; offset += BATCH_SIZE) {
40
+ const batch = allIds.slice(offset, offset + BATCH_SIZE);
41
+ const idsParam = batch.map(id => `ids[]=${id}`).join('&');
42
+ // pixivFetch navigates on each call; for subsequent batches we re-navigate,
43
+ // which is fine — the cookie is already attached.
44
+ const detailBody = await pixivFetch(page, `/ajax/user/${userId}/profile/illusts?${idsParam}&work_category=illustManga&is_first_page=${offset === 0 ? 1 : 0}`);
45
+ Object.assign(allWorks, detailBody?.works || {});
46
+ }
47
+ return allIds
48
+ .map((id, i) => {
49
+ const w = allWorks[id];
50
+ if (!w)
51
+ return null;
52
+ return {
53
+ rank: i + 1,
54
+ title: w.title || '',
55
+ illust_id: w.id,
56
+ pages: w.pageCount || 1,
57
+ bookmarks: w.bookmarkCount || 0,
58
+ tags: (w.tags || []).slice(0, 5).join(', '),
59
+ created: (w.createDate || '').split('T')[0],
60
+ url: 'https://www.pixiv.net/artworks/' + w.id,
61
+ };
62
+ })
63
+ .filter(Boolean);
64
+ },
65
+ });
@@ -0,0 +1 @@
1
+ import './illusts.js';
@@ -0,0 +1,99 @@
1
+ import { beforeAll, describe, expect, it } from 'vitest';
2
+ import { getRegistry } from '../../registry.js';
3
+ import { AuthRequiredError, CommandExecutionError } from '../../errors.js';
4
+ import { createPageMock } from './test-utils.js';
5
+ import './illusts.js';
6
+ let cmd;
7
+ beforeAll(() => {
8
+ cmd = getRegistry().get('pixiv/illusts');
9
+ expect(cmd?.func).toBeTypeOf('function');
10
+ });
11
+ describe('pixiv illusts', () => {
12
+ it('throws CommandExecutionError on invalid user ID', async () => {
13
+ const page = createPageMock([]);
14
+ await expect(cmd.func(page, { 'user-id': 'abc', limit: 5 })).rejects.toThrow(CommandExecutionError);
15
+ });
16
+ it('throws AuthRequiredError on 401', async () => {
17
+ const page = createPageMock([{ __httpError: 401 }]);
18
+ await expect(cmd.func(page, { 'user-id': '11', limit: 5 })).rejects.toThrow(AuthRequiredError);
19
+ });
20
+ it('throws generic error on non-auth HTTP failure', async () => {
21
+ const page = createPageMock([{ __httpError: 500 }]);
22
+ await expect(cmd.func(page, { 'user-id': '11', limit: 5 })).rejects.toThrow(CommandExecutionError);
23
+ });
24
+ it('returns empty array when user has no illusts', async () => {
25
+ const page = createPageMock([
26
+ { body: { illusts: {} } },
27
+ ]);
28
+ const result = await cmd.func(page, { 'user-id': '11', limit: 5 });
29
+ expect(result).toEqual([]);
30
+ });
31
+ it('fetches illust IDs then batch-fetches details', async () => {
32
+ const page = createPageMock([
33
+ // Step 1: profile/all returns illust IDs
34
+ {
35
+ body: {
36
+ illusts: { '99999': null, '88888': null, '77777': null },
37
+ },
38
+ },
39
+ // Step 2: batch detail response
40
+ {
41
+ body: {
42
+ works: {
43
+ '99999': {
44
+ id: '99999',
45
+ title: 'Latest Work',
46
+ pageCount: 2,
47
+ bookmarkCount: 300,
48
+ tags: ['original', 'fantasy'],
49
+ createDate: '2025-01-15T12:00:00+09:00',
50
+ },
51
+ '88888': {
52
+ id: '88888',
53
+ title: 'Older Work',
54
+ pageCount: 1,
55
+ bookmarkCount: 150,
56
+ tags: ['landscape'],
57
+ createDate: '2024-12-01T10:00:00+09:00',
58
+ },
59
+ },
60
+ },
61
+ },
62
+ ]);
63
+ const result = (await cmd.func(page, { 'user-id': '11', limit: 3 }));
64
+ // Should be sorted newest first (99999 > 88888 > 77777)
65
+ expect(result).toHaveLength(2); // 77777 has no detail data, filtered out
66
+ expect(result[0]).toMatchObject({
67
+ rank: 1,
68
+ title: 'Latest Work',
69
+ illust_id: '99999',
70
+ pages: 2,
71
+ bookmarks: 300,
72
+ created: '2025-01-15',
73
+ });
74
+ expect(result[1]).toMatchObject({
75
+ rank: 2,
76
+ title: 'Older Work',
77
+ illust_id: '88888',
78
+ });
79
+ });
80
+ it('respects the limit on illust IDs fetched', async () => {
81
+ const page = createPageMock([
82
+ {
83
+ body: {
84
+ illusts: { '100': null, '200': null, '300': null, '400': null, '500': null },
85
+ },
86
+ },
87
+ {
88
+ body: {
89
+ works: {
90
+ '500': { id: '500', title: 'W5', pageCount: 1, bookmarkCount: 0, tags: [], createDate: '' },
91
+ '400': { id: '400', title: 'W4', pageCount: 1, bookmarkCount: 0, tags: [], createDate: '' },
92
+ },
93
+ },
94
+ },
95
+ ]);
96
+ const result = (await cmd.func(page, { 'user-id': '11', limit: 2 }));
97
+ expect(result).toHaveLength(2);
98
+ });
99
+ });
@@ -0,0 +1,53 @@
1
+ site: pixiv
2
+ name: ranking
3
+ description: Pixiv illustration rankings (daily/weekly/monthly)
4
+ domain: www.pixiv.net
5
+ strategy: cookie
6
+ browser: true
7
+
8
+ args:
9
+ mode:
10
+ type: str
11
+ default: daily
12
+ description: Ranking mode
13
+ choices: [daily, weekly, monthly, rookie, original, male, female, daily_r18, weekly_r18]
14
+ page:
15
+ type: int
16
+ default: 1
17
+ description: Page number
18
+ limit:
19
+ type: int
20
+ default: 20
21
+ description: Number of results
22
+
23
+ pipeline:
24
+ - navigate: https://www.pixiv.net
25
+
26
+ - evaluate: |
27
+ (async () => {
28
+ const mode = ${{ args.mode | json }};
29
+ const page = ${{ args.page | json }};
30
+ const limit = ${{ args.limit | json }};
31
+ const res = await fetch(
32
+ 'https://www.pixiv.net/ranking.php?mode=' + mode + '&p=' + page + '&format=json',
33
+ { credentials: 'include' }
34
+ );
35
+ if (!res.ok) {
36
+ if (res.status === 401 || res.status === 403) throw new Error('Authentication required — please log in to Pixiv in Chrome');
37
+ throw new Error('Pixiv request failed (HTTP ' + res.status + ')');
38
+ }
39
+ const data = await res.json();
40
+ const items = (data?.contents || []).slice(0, limit);
41
+ return items.map((item, i) => ({
42
+ rank: item.rank,
43
+ title: item.title,
44
+ author: item.user_name,
45
+ user_id: item.user_id,
46
+ illust_id: item.illust_id,
47
+ pages: item.illust_page_count,
48
+ bookmarks: item.illust_bookmark_count,
49
+ url: 'https://www.pixiv.net/artworks/' + item.illust_id
50
+ }));
51
+ })()
52
+
53
+ columns: [rank, title, author, illust_id, pages, bookmarks]
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Pixiv search — search illustrations by keyword/tag.
3
+ *
4
+ * Uses the internal Ajax search API with browser cookies for authentication.
5
+ */
6
+ export {};
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Pixiv search — search illustrations by keyword/tag.
3
+ *
4
+ * Uses the internal Ajax search API with browser cookies for authentication.
5
+ */
6
+ import { cli, Strategy } from '../../registry.js';
7
+ import { pixivFetch } from './utils.js';
8
+ cli({
9
+ site: 'pixiv',
10
+ name: 'search',
11
+ description: 'Search Pixiv illustrations by keyword',
12
+ domain: 'www.pixiv.net',
13
+ strategy: Strategy.COOKIE,
14
+ args: [
15
+ { name: 'query', positional: true, required: true, help: 'Search keyword or tag' },
16
+ { name: 'limit', type: 'int', default: 20, help: 'Number of results' },
17
+ { name: 'order', type: 'str', default: 'date_d', help: 'Sort order', choices: ['date_d', 'date', 'popular_d', 'popular_male_d', 'popular_female_d'] },
18
+ { name: 'mode', type: 'str', default: 'all', help: 'Search mode', choices: ['all', 'safe', 'r18'] },
19
+ { name: 'page', type: 'int', default: 1, help: 'Page number' },
20
+ ],
21
+ columns: ['rank', 'title', 'author', 'illust_id', 'pages', 'bookmarks', 'tags'],
22
+ func: async (page, kwargs) => {
23
+ const { query, limit = 20, order = 'date_d', mode = 'all', page: pageNum = 1 } = kwargs;
24
+ const encoded = encodeURIComponent(query);
25
+ // Pixiv search API requires the keyword in BOTH the URL path and the `word` query param.
26
+ const body = await pixivFetch(page, `/ajax/search/illustrations/${encoded}`, { params: { word: query, order, mode, p: pageNum, s_mode: 's_tag_full', type: 'illust_and_ugoira' } });
27
+ const items = body?.illust?.data || [];
28
+ return items
29
+ .filter((item) => item.id)
30
+ .slice(0, Number(limit))
31
+ .map((item, i) => ({
32
+ rank: i + 1,
33
+ title: item.title || '',
34
+ author: item.userName || '',
35
+ user_id: item.userId || '',
36
+ illust_id: item.id,
37
+ pages: item.pageCount || 1,
38
+ bookmarks: item.bookmarkCount || 0,
39
+ tags: (item.tags || []).slice(0, 5).join(', '),
40
+ url: 'https://www.pixiv.net/artworks/' + item.id,
41
+ }));
42
+ },
43
+ });
@@ -0,0 +1 @@
1
+ import './search.js';
@@ -0,0 +1,83 @@
1
+ import { beforeAll, describe, expect, it } from 'vitest';
2
+ import { getRegistry } from '../../registry.js';
3
+ import { AuthRequiredError, CommandExecutionError } from '../../errors.js';
4
+ import { createPageMock } from './test-utils.js';
5
+ import './search.js';
6
+ let cmd;
7
+ beforeAll(() => {
8
+ cmd = getRegistry().get('pixiv/search');
9
+ expect(cmd?.func).toBeTypeOf('function');
10
+ });
11
+ describe('pixiv search', () => {
12
+ it('throws AuthRequiredError on 401', async () => {
13
+ const page = createPageMock([{ __httpError: 401 }]);
14
+ await expect(cmd.func(page, { query: '初音ミク', limit: 5 })).rejects.toThrow(AuthRequiredError);
15
+ });
16
+ it('throws generic error on non-auth HTTP failure', async () => {
17
+ const page = createPageMock([{ __httpError: 500 }]);
18
+ await expect(cmd.func(page, { query: 'test', limit: 5 })).rejects.toThrow(CommandExecutionError);
19
+ });
20
+ it('returns ranked results with correct fields', async () => {
21
+ const page = createPageMock([
22
+ {
23
+ body: {
24
+ illust: {
25
+ data: [
26
+ {
27
+ id: '12345',
28
+ title: 'Miku Illustration',
29
+ userName: 'artist1',
30
+ userId: '100',
31
+ pageCount: 3,
32
+ bookmarkCount: 500,
33
+ tags: ['初音ミク', 'VOCALOID', 'ミク'],
34
+ },
35
+ {
36
+ id: '67890',
37
+ title: 'Another Art',
38
+ userName: 'artist2',
39
+ userId: '200',
40
+ pageCount: 1,
41
+ bookmarkCount: 100,
42
+ tags: ['オリジナル'],
43
+ },
44
+ ],
45
+ },
46
+ },
47
+ },
48
+ ]);
49
+ const result = (await cmd.func(page, { query: '初音ミク', limit: 10 }));
50
+ expect(result).toHaveLength(2);
51
+ expect(result[0]).toMatchObject({
52
+ rank: 1,
53
+ title: 'Miku Illustration',
54
+ author: 'artist1',
55
+ illust_id: '12345',
56
+ pages: 3,
57
+ bookmarks: 500,
58
+ });
59
+ expect(result[1]).toMatchObject({ rank: 2, illust_id: '67890' });
60
+ });
61
+ it('respects the limit parameter', async () => {
62
+ const page = createPageMock([
63
+ {
64
+ body: {
65
+ illust: {
66
+ data: [
67
+ { id: '1', title: 'A', userName: 'u1', userId: '1', pageCount: 1, bookmarkCount: 0, tags: [] },
68
+ { id: '2', title: 'B', userName: 'u2', userId: '2', pageCount: 1, bookmarkCount: 0, tags: [] },
69
+ { id: '3', title: 'C', userName: 'u3', userId: '3', pageCount: 1, bookmarkCount: 0, tags: [] },
70
+ ],
71
+ },
72
+ },
73
+ },
74
+ ]);
75
+ const result = (await cmd.func(page, { query: 'test', limit: 2 }));
76
+ expect(result).toHaveLength(2);
77
+ });
78
+ it('returns empty array when no results', async () => {
79
+ const page = createPageMock([{ body: { illust: { data: [] } } }]);
80
+ const result = await cmd.func(page, { query: 'nonexistent', limit: 10 });
81
+ expect(result).toEqual([]);
82
+ });
83
+ });
@@ -0,0 +1,12 @@
1
+ import type { IPage } from '../../types.js';
2
+ /**
3
+ * Create a minimal page mock with only the methods commonly used by Pixiv adapters.
4
+ *
5
+ * Since all TS adapters now go through `pixivFetch` which calls `page.evaluate`,
6
+ * the evaluate results should match the raw Pixiv Ajax response format:
7
+ * - Success: `{ body: ... }` (pixivFetch returns the `body` field)
8
+ * - HTTP error: `{ __httpError: <status> }` (pixivFetch detects and throws)
9
+ *
10
+ * Additional methods can be overridden via the overrides parameter.
11
+ */
12
+ export declare function createPageMock(evaluateResults: any[], overrides?: Partial<IPage>): IPage;
@@ -0,0 +1,23 @@
1
+ import { vi } from 'vitest';
2
+ /**
3
+ * Create a minimal page mock with only the methods commonly used by Pixiv adapters.
4
+ *
5
+ * Since all TS adapters now go through `pixivFetch` which calls `page.evaluate`,
6
+ * the evaluate results should match the raw Pixiv Ajax response format:
7
+ * - Success: `{ body: ... }` (pixivFetch returns the `body` field)
8
+ * - HTTP error: `{ __httpError: <status> }` (pixivFetch detects and throws)
9
+ *
10
+ * Additional methods can be overridden via the overrides parameter.
11
+ */
12
+ export function createPageMock(evaluateResults, overrides) {
13
+ const evaluate = vi.fn();
14
+ for (const result of evaluateResults) {
15
+ evaluate.mockResolvedValueOnce(result);
16
+ }
17
+ return {
18
+ goto: vi.fn().mockResolvedValue(undefined),
19
+ evaluate,
20
+ getCookies: vi.fn().mockResolvedValue([]),
21
+ ...overrides,
22
+ };
23
+ }
@@ -0,0 +1,46 @@
1
+ site: pixiv
2
+ name: user
3
+ description: View Pixiv artist profile
4
+ domain: www.pixiv.net
5
+ strategy: cookie
6
+ browser: true
7
+
8
+ args:
9
+ uid:
10
+ type: str
11
+ required: true
12
+ positional: true
13
+ description: Pixiv user ID
14
+
15
+ pipeline:
16
+ - navigate: https://www.pixiv.net
17
+
18
+ - evaluate: |
19
+ (async () => {
20
+ const uid = ${{ args.uid | json }};
21
+ const res = await fetch(
22
+ 'https://www.pixiv.net/ajax/user/' + uid + '?full=1',
23
+ { credentials: 'include' }
24
+ );
25
+ if (!res.ok) {
26
+ if (res.status === 401 || res.status === 403) throw new Error('Authentication required — please log in to Pixiv in Chrome');
27
+ if (res.status === 404) throw new Error('User not found: ' + uid);
28
+ throw new Error('Pixiv request failed (HTTP ' + res.status + ')');
29
+ }
30
+ const data = await res.json();
31
+ const b = data?.body;
32
+ if (!b) throw new Error('User not found');
33
+ return [{
34
+ user_id: uid,
35
+ name: b.name,
36
+ premium: b.premium ? 'Yes' : 'No',
37
+ following: b.following,
38
+ illusts: typeof b.illusts === 'object' ? Object.keys(b.illusts).length : (b.illusts || 0),
39
+ manga: typeof b.manga === 'object' ? Object.keys(b.manga).length : (b.manga || 0),
40
+ novels: typeof b.novels === 'object' ? Object.keys(b.novels).length : (b.novels || 0),
41
+ comment: (b.comment || '').slice(0, 80),
42
+ url: 'https://www.pixiv.net/users/' + uid
43
+ }];
44
+ })()
45
+
46
+ columns: [user_id, name, premium, following, illusts, manga, novels, comment]
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Pixiv shared helpers: authenticated Ajax fetch with standard error handling.
3
+ *
4
+ * All Pixiv Ajax APIs return `{ error: false, body: ... }` on success.
5
+ * On failure the HTTP status code is used to distinguish auth (401/403),
6
+ * not-found (404), and other errors.
7
+ */
8
+ import type { IPage } from '../../types.js';
9
+ /**
10
+ * Navigate to Pixiv (to attach cookies) then fetch a Pixiv Ajax API endpoint.
11
+ *
12
+ * Handles the common navigate → evaluate(fetch) → error-check pattern used
13
+ * by every Pixiv TS adapter.
14
+ *
15
+ * @param page - Browser page instance
16
+ * @param path - API path, e.g. '/ajax/illust/12345'
17
+ * @param opts - Optional query params
18
+ * @returns - The parsed `body` from the JSON response
19
+ * @throws AuthRequiredError on 401/403
20
+ * @throws CommandExecutionError on 404 or other HTTP errors
21
+ */
22
+ export declare function pixivFetch(page: IPage, path: string, opts?: {
23
+ params?: Record<string, string | number>;
24
+ notFoundMsg?: string;
25
+ }): Promise<any>;
26
+ /** Maximum number of illust IDs per batch detail request (Pixiv server limit). */
27
+ export declare const BATCH_SIZE = 48;