@jackwener/opencli 1.6.1 → 1.6.2

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 (384) hide show
  1. package/CONTRIBUTING.md +1 -1
  2. package/README.md +27 -45
  3. package/README.zh-CN.md +32 -34
  4. package/autoresearch/browse-tasks.json +18 -20
  5. package/autoresearch/commands/debug.ts +163 -0
  6. package/autoresearch/commands/fix.ts +145 -0
  7. package/autoresearch/commands/plan.ts +88 -0
  8. package/autoresearch/commands/run.ts +138 -0
  9. package/autoresearch/config.ts +82 -0
  10. package/autoresearch/engine.ts +359 -0
  11. package/autoresearch/eval-all.ts +127 -0
  12. package/autoresearch/eval-browse.ts +1 -1
  13. package/autoresearch/eval-publish.ts +238 -0
  14. package/autoresearch/eval-save.ts +249 -0
  15. package/autoresearch/eval-skill.ts +14 -8
  16. package/autoresearch/eval-v2ex.ts +220 -0
  17. package/autoresearch/eval-zhihu.ts +230 -0
  18. package/autoresearch/logger.ts +69 -0
  19. package/autoresearch/presets/combined-reliability.ts +27 -0
  20. package/autoresearch/presets/index.ts +23 -0
  21. package/autoresearch/presets/operate-reliability.ts +24 -0
  22. package/autoresearch/presets/save-reliability.ts +26 -0
  23. package/autoresearch/presets/skill-quality.ts +20 -0
  24. package/autoresearch/presets/v2ex-reliability.ts +24 -0
  25. package/autoresearch/presets/zhihu-reliability.ts +25 -0
  26. package/autoresearch/publish-tasks.json +345 -0
  27. package/autoresearch/run-save.sh +11 -0
  28. package/autoresearch/save-adapters/xhs-explore-deep.ts +64 -0
  29. package/autoresearch/save-adapters/xhs-note-comments.ts +61 -0
  30. package/autoresearch/save-adapters/xhs-search-full.ts +62 -0
  31. package/autoresearch/save-adapters/zhihu-hot-detail.ts +52 -0
  32. package/autoresearch/save-adapters/zhihu-question-full.ts +57 -0
  33. package/autoresearch/save-adapters/zhihu-search-detail.ts +53 -0
  34. package/autoresearch/save-tasks.json +281 -0
  35. package/autoresearch/v2ex-tasks.json +899 -0
  36. package/autoresearch/zhihu-tasks.json +848 -0
  37. package/dist/browser/base-page.d.ts +4 -2
  38. package/dist/browser/base-page.js +37 -4
  39. package/dist/browser/bridge.js +10 -8
  40. package/dist/browser/cdp.js +2 -6
  41. package/dist/browser/daemon-client.d.ts +11 -1
  42. package/dist/browser/daemon-client.js +3 -0
  43. package/dist/browser/dom-helpers.d.ts +4 -2
  44. package/dist/browser/dom-helpers.js +42 -31
  45. package/dist/browser/dom-snapshot.js +23 -1
  46. package/dist/browser/page.d.ts +7 -2
  47. package/dist/browser/page.js +112 -30
  48. package/dist/browser.test.js +1 -1
  49. package/dist/build-manifest.d.ts +1 -0
  50. package/dist/build-manifest.js +1 -0
  51. package/dist/cli-manifest.json +1135 -184
  52. package/dist/cli.d.ts +2 -0
  53. package/dist/cli.js +48 -7
  54. package/dist/cli.test.d.ts +1 -0
  55. package/dist/cli.test.js +88 -0
  56. package/dist/clis/1688/item.d.ts +70 -0
  57. package/dist/clis/1688/item.js +187 -0
  58. package/dist/clis/1688/item.test.d.ts +1 -0
  59. package/dist/clis/1688/item.test.js +67 -0
  60. package/dist/clis/1688/search.d.ts +56 -0
  61. package/dist/clis/1688/search.js +309 -0
  62. package/dist/clis/1688/search.test.d.ts +1 -0
  63. package/dist/clis/1688/search.test.js +75 -0
  64. package/dist/clis/1688/shared.d.ts +112 -0
  65. package/dist/clis/1688/shared.js +514 -0
  66. package/dist/clis/1688/shared.test.d.ts +1 -0
  67. package/dist/clis/1688/shared.test.js +57 -0
  68. package/dist/clis/1688/store.d.ts +45 -0
  69. package/dist/clis/1688/store.js +226 -0
  70. package/dist/clis/1688/store.test.d.ts +1 -0
  71. package/dist/clis/1688/store.test.js +62 -0
  72. package/dist/clis/amazon/bestsellers.d.ts +0 -20
  73. package/dist/clis/amazon/bestsellers.js +6 -129
  74. package/dist/clis/amazon/bestsellers.test.js +12 -3
  75. package/dist/clis/amazon/movers-shakers.d.ts +1 -0
  76. package/dist/clis/amazon/movers-shakers.js +7 -0
  77. package/dist/clis/amazon/new-releases.d.ts +1 -0
  78. package/dist/clis/amazon/new-releases.js +7 -0
  79. package/dist/clis/amazon/rankings.d.ts +59 -0
  80. package/dist/clis/amazon/rankings.js +226 -0
  81. package/dist/clis/amazon/rankings.test.d.ts +1 -0
  82. package/dist/clis/amazon/rankings.test.js +41 -0
  83. package/dist/clis/amazon/shared.d.ts +11 -0
  84. package/dist/clis/amazon/shared.js +121 -11
  85. package/dist/clis/amazon/shared.test.js +11 -0
  86. package/dist/clis/bilibili/comments.js +2 -2
  87. package/dist/clis/bilibili/comments.test.js +3 -2
  88. package/dist/clis/bilibili/download.js +2 -1
  89. package/dist/clis/bilibili/subtitle.js +4 -3
  90. package/dist/clis/bilibili/subtitle.test.js +2 -1
  91. package/dist/clis/bilibili/utils.d.ts +5 -0
  92. package/dist/clis/bilibili/utils.js +30 -0
  93. package/dist/clis/bilibili/utils.test.d.ts +1 -0
  94. package/dist/clis/bilibili/utils.test.js +17 -0
  95. package/dist/clis/douban/marks.js +1 -1
  96. package/dist/clis/douban/subject.yaml +50 -19
  97. package/dist/clis/doubao/utils.js +32 -12
  98. package/dist/clis/douyin/_shared/browser-fetch.test.js +0 -1
  99. package/dist/clis/douyin/_shared/transcode.test.js +0 -2
  100. package/dist/clis/douyin/draft.test.js +0 -2
  101. package/dist/clis/facebook/search.test.js +0 -2
  102. package/dist/clis/gemini/ask.js +9 -3
  103. package/dist/clis/gemini/ask.test.d.ts +1 -0
  104. package/dist/clis/gemini/ask.test.js +100 -0
  105. package/dist/clis/gemini/reply-state.test.d.ts +1 -0
  106. package/dist/clis/gemini/reply-state.test.js +641 -0
  107. package/dist/clis/gemini/utils.d.ts +44 -1
  108. package/dist/clis/gemini/utils.js +528 -61
  109. package/dist/clis/gemini/utils.test.js +149 -2
  110. package/dist/clis/hupu/detail.d.ts +1 -0
  111. package/dist/clis/hupu/detail.js +72 -0
  112. package/dist/clis/hupu/hot.yaml +43 -0
  113. package/dist/clis/hupu/like.d.ts +1 -0
  114. package/dist/clis/hupu/like.js +75 -0
  115. package/dist/clis/hupu/reply.d.ts +1 -0
  116. package/dist/clis/hupu/reply.js +71 -0
  117. package/dist/clis/hupu/search.d.ts +1 -0
  118. package/dist/clis/hupu/search.js +59 -0
  119. package/dist/clis/hupu/unlike.d.ts +1 -0
  120. package/dist/clis/hupu/unlike.js +75 -0
  121. package/dist/clis/hupu/utils.d.ts +20 -0
  122. package/dist/clis/hupu/utils.js +319 -0
  123. package/dist/clis/instagram/_shared/private-publish.d.ts +138 -0
  124. package/dist/clis/instagram/_shared/private-publish.js +1030 -0
  125. package/dist/clis/instagram/_shared/private-publish.test.d.ts +1 -0
  126. package/dist/clis/instagram/_shared/private-publish.test.js +705 -0
  127. package/dist/clis/instagram/_shared/protocol-capture.d.ts +26 -0
  128. package/dist/clis/instagram/_shared/protocol-capture.js +282 -0
  129. package/dist/clis/instagram/_shared/protocol-capture.test.d.ts +1 -0
  130. package/dist/clis/instagram/_shared/protocol-capture.test.js +114 -0
  131. package/dist/clis/instagram/_shared/runtime-info.d.ts +9 -0
  132. package/dist/clis/instagram/_shared/runtime-info.js +81 -0
  133. package/dist/clis/instagram/note.d.ts +1 -0
  134. package/dist/clis/instagram/note.js +222 -0
  135. package/dist/clis/instagram/note.test.d.ts +1 -0
  136. package/dist/clis/instagram/note.test.js +81 -0
  137. package/dist/clis/instagram/post.d.ts +4 -0
  138. package/dist/clis/instagram/post.js +1496 -0
  139. package/dist/clis/instagram/post.test.d.ts +1 -0
  140. package/dist/clis/instagram/post.test.js +1647 -0
  141. package/dist/clis/instagram/reel.d.ts +1 -0
  142. package/dist/clis/instagram/reel.js +826 -0
  143. package/dist/clis/instagram/reel.test.d.ts +1 -0
  144. package/dist/clis/instagram/reel.test.js +167 -0
  145. package/dist/clis/instagram/story.d.ts +1 -0
  146. package/dist/clis/instagram/story.js +115 -0
  147. package/dist/clis/instagram/story.test.d.ts +1 -0
  148. package/dist/clis/instagram/story.test.js +167 -0
  149. package/dist/clis/sinafinance/stock-rank.d.ts +4 -0
  150. package/dist/clis/sinafinance/stock-rank.js +65 -0
  151. package/dist/clis/substack/utils.test.js +0 -2
  152. package/dist/clis/twitter/post.js +72 -45
  153. package/dist/clis/twitter/post.test.d.ts +1 -0
  154. package/dist/clis/twitter/post.test.js +116 -0
  155. package/dist/clis/twitter/reply.d.ts +12 -0
  156. package/dist/clis/twitter/reply.js +257 -35
  157. package/dist/clis/twitter/reply.test.d.ts +1 -0
  158. package/dist/clis/twitter/reply.test.js +151 -0
  159. package/dist/clis/xianyu/chat.d.ts +7 -0
  160. package/dist/clis/xianyu/chat.js +146 -0
  161. package/dist/clis/xianyu/chat.test.d.ts +1 -0
  162. package/dist/clis/xianyu/chat.test.js +15 -0
  163. package/dist/clis/xianyu/item.d.ts +7 -0
  164. package/dist/clis/xianyu/item.js +152 -0
  165. package/dist/clis/xianyu/item.test.d.ts +1 -0
  166. package/dist/clis/xianyu/item.test.js +56 -0
  167. package/dist/clis/xianyu/search.d.ts +10 -0
  168. package/dist/clis/xianyu/search.js +134 -0
  169. package/dist/clis/xianyu/search.test.d.ts +1 -0
  170. package/dist/clis/xianyu/search.test.js +17 -0
  171. package/dist/clis/xianyu/utils.d.ts +1 -0
  172. package/dist/clis/xianyu/utils.js +8 -0
  173. package/dist/clis/xiaoe/catalog.yaml +129 -0
  174. package/dist/clis/xiaoe/content.yaml +43 -0
  175. package/dist/clis/xiaoe/courses.yaml +73 -0
  176. package/dist/clis/xiaoe/detail.yaml +39 -0
  177. package/dist/clis/xiaoe/play-url.yaml +124 -0
  178. package/dist/clis/xiaohongshu/comments.test.js +0 -2
  179. package/dist/clis/xiaohongshu/creator-note-detail.test.js +0 -2
  180. package/dist/clis/xiaohongshu/creator-notes.test.js +0 -2
  181. package/dist/clis/xiaohongshu/download.test.js +0 -2
  182. package/dist/clis/xiaohongshu/note.test.js +0 -2
  183. package/dist/clis/xiaohongshu/publish.test.js +0 -2
  184. package/dist/clis/xiaohongshu/search.js +29 -20
  185. package/dist/clis/xiaohongshu/search.test.js +56 -48
  186. package/dist/clis/yuanbao/ask.d.ts +21 -0
  187. package/dist/clis/yuanbao/ask.js +427 -0
  188. package/dist/clis/yuanbao/ask.test.d.ts +1 -0
  189. package/dist/clis/yuanbao/ask.test.js +124 -0
  190. package/dist/clis/yuanbao/new.d.ts +1 -0
  191. package/dist/clis/yuanbao/new.js +70 -0
  192. package/dist/clis/yuanbao/new.test.d.ts +1 -0
  193. package/dist/clis/yuanbao/new.test.js +30 -0
  194. package/dist/clis/yuanbao/shared.d.ts +13 -0
  195. package/dist/clis/yuanbao/shared.js +49 -0
  196. package/dist/clis/zhihu/question.js +30 -19
  197. package/dist/clis/zhihu/question.test.js +34 -16
  198. package/dist/commanderAdapter.js +8 -4
  199. package/dist/commanderAdapter.test.js +42 -0
  200. package/dist/completion.js +3 -1
  201. package/dist/completion.test.d.ts +1 -0
  202. package/dist/completion.test.js +23 -0
  203. package/dist/doctor.js +1 -1
  204. package/dist/electron-apps.d.ts +2 -0
  205. package/dist/electron-apps.js +7 -1
  206. package/dist/errors.js +1 -1
  207. package/dist/execution.js +25 -35
  208. package/dist/explore.js +1 -1
  209. package/dist/launcher.d.ts +4 -0
  210. package/dist/launcher.js +64 -8
  211. package/dist/launcher.test.js +88 -7
  212. package/dist/output.d.ts +2 -0
  213. package/dist/output.js +10 -1
  214. package/dist/output.test.d.ts +0 -3
  215. package/dist/output.test.js +59 -92
  216. package/dist/pipeline/executor.test.js +0 -2
  217. package/dist/pipeline/steps/download.test.js +0 -2
  218. package/dist/registry.d.ts +2 -0
  219. package/dist/serialization.d.ts +1 -0
  220. package/dist/serialization.js +1 -0
  221. package/dist/types.d.ts +9 -2
  222. package/docs/.vitepress/config.mts +4 -0
  223. package/docs/adapters/browser/1688.md +52 -0
  224. package/docs/adapters/browser/36kr.md +2 -1
  225. package/docs/adapters/browser/doubao.md +5 -1
  226. package/docs/adapters/browser/hupu.md +53 -0
  227. package/docs/adapters/browser/sinafinance.md +32 -2
  228. package/docs/adapters/browser/weibo.md +6 -1
  229. package/docs/adapters/browser/wikipedia.md +2 -0
  230. package/docs/adapters/browser/xianyu.md +42 -0
  231. package/docs/adapters/browser/xiaoe.md +44 -0
  232. package/docs/adapters/browser/yuanbao.md +64 -0
  233. package/docs/adapters/index.md +14 -5
  234. package/docs/comparison.md +1 -1
  235. package/docs/developer/ai-workflow.md +2 -2
  236. package/docs/developer/contributing.md +1 -1
  237. package/docs/developer/testing.md +2 -0
  238. package/docs/guide/plugins.md +1 -0
  239. package/docs/guide/troubleshooting.md +11 -0
  240. package/docs/superpowers/specs/2026-04-03-v2ex-autoresearch-design.md +41 -0
  241. package/docs/zh/guide/plugins.md +1 -0
  242. package/extension/dist/background.js +1127 -0
  243. package/extension/src/background.test.ts +39 -0
  244. package/extension/src/background.ts +223 -34
  245. package/extension/src/cdp.ts +194 -4
  246. package/extension/src/protocol.ts +22 -1
  247. package/package.json +3 -2
  248. package/scripts/postinstall.js +1 -1
  249. package/skills/opencli-explorer/SKILL.md +1 -1
  250. package/skills/opencli-oneshot/SKILL.md +2 -2
  251. package/skills/opencli-operate/SKILL.md +120 -27
  252. package/skills/opencli-usage/SKILL.md +31 -20
  253. package/skills/opencli-usage/browser.md +114 -16
  254. package/skills/opencli-usage/public-api.md +32 -3
  255. package/skills/smart-search/SKILL.md +156 -0
  256. package/skills/smart-search/references/sources-ai.md +74 -0
  257. package/skills/smart-search/references/sources-info.md +43 -0
  258. package/skills/smart-search/references/sources-media.md +50 -0
  259. package/skills/smart-search/references/sources-other.md +42 -0
  260. package/skills/smart-search/references/sources-shopping.md +31 -0
  261. package/skills/smart-search/references/sources-social.md +51 -0
  262. package/skills/smart-search/references/sources-tech.md +42 -0
  263. package/skills/smart-search/references/sources-travel.md +20 -0
  264. package/src/browser/base-page.ts +41 -6
  265. package/src/browser/bridge.ts +11 -8
  266. package/src/browser/cdp.ts +1 -8
  267. package/src/browser/daemon-client.ts +11 -1
  268. package/src/browser/dom-helpers.ts +43 -31
  269. package/src/browser/dom-snapshot.ts +23 -1
  270. package/src/browser/page.ts +115 -31
  271. package/src/browser.test.ts +1 -1
  272. package/src/build-manifest.ts +2 -0
  273. package/src/cli.test.ts +133 -0
  274. package/src/cli.ts +73 -11
  275. package/src/clis/1688/item.test.ts +69 -0
  276. package/src/clis/1688/item.ts +282 -0
  277. package/src/clis/1688/search.test.ts +81 -0
  278. package/src/clis/1688/search.ts +402 -0
  279. package/src/clis/1688/shared.test.ts +75 -0
  280. package/src/clis/1688/shared.ts +623 -0
  281. package/src/clis/1688/store.test.ts +69 -0
  282. package/src/clis/1688/store.ts +300 -0
  283. package/src/clis/amazon/bestsellers.test.ts +12 -3
  284. package/src/clis/amazon/bestsellers.ts +6 -178
  285. package/src/clis/amazon/movers-shakers.ts +8 -0
  286. package/src/clis/amazon/new-releases.ts +8 -0
  287. package/src/clis/amazon/rankings.test.ts +47 -0
  288. package/src/clis/amazon/rankings.ts +312 -0
  289. package/src/clis/amazon/shared.test.ts +16 -0
  290. package/src/clis/amazon/shared.ts +134 -12
  291. package/src/clis/bilibili/comments.test.ts +4 -3
  292. package/src/clis/bilibili/comments.ts +2 -2
  293. package/src/clis/bilibili/download.ts +2 -1
  294. package/src/clis/bilibili/subtitle.test.ts +2 -1
  295. package/src/clis/bilibili/subtitle.ts +4 -3
  296. package/src/clis/bilibili/utils.test.ts +21 -0
  297. package/src/clis/bilibili/utils.ts +27 -0
  298. package/src/clis/douban/marks.ts +1 -1
  299. package/src/clis/douban/subject.yaml +50 -19
  300. package/src/clis/doubao/utils.ts +32 -12
  301. package/src/clis/douyin/_shared/browser-fetch.test.ts +0 -1
  302. package/src/clis/douyin/_shared/transcode.test.ts +0 -2
  303. package/src/clis/douyin/draft.test.ts +0 -2
  304. package/src/clis/facebook/search.test.ts +0 -2
  305. package/src/clis/gemini/ask.test.ts +116 -0
  306. package/src/clis/gemini/ask.ts +10 -3
  307. package/src/clis/gemini/reply-state.test.ts +708 -0
  308. package/src/clis/gemini/utils.test.ts +184 -2
  309. package/src/clis/gemini/utils.ts +588 -60
  310. package/src/clis/hupu/detail.ts +126 -0
  311. package/src/clis/hupu/hot.yaml +43 -0
  312. package/src/clis/hupu/like.ts +76 -0
  313. package/src/clis/hupu/reply.ts +76 -0
  314. package/src/clis/hupu/search.ts +95 -0
  315. package/src/clis/hupu/unlike.ts +76 -0
  316. package/src/clis/hupu/utils.ts +381 -0
  317. package/src/clis/instagram/_shared/private-publish.test.ts +827 -0
  318. package/src/clis/instagram/_shared/private-publish.ts +1303 -0
  319. package/src/clis/instagram/_shared/protocol-capture.test.ts +148 -0
  320. package/src/clis/instagram/_shared/protocol-capture.ts +321 -0
  321. package/src/clis/instagram/_shared/runtime-info.ts +91 -0
  322. package/src/clis/instagram/note.test.ts +96 -0
  323. package/src/clis/instagram/note.ts +254 -0
  324. package/src/clis/instagram/post.test.ts +1716 -0
  325. package/src/clis/instagram/post.ts +1620 -0
  326. package/src/clis/instagram/reel.test.ts +191 -0
  327. package/src/clis/instagram/reel.ts +886 -0
  328. package/src/clis/instagram/story.test.ts +191 -0
  329. package/src/clis/instagram/story.ts +151 -0
  330. package/src/clis/sinafinance/stock-rank.ts +68 -0
  331. package/src/clis/substack/utils.test.ts +0 -2
  332. package/src/clis/twitter/post.test.ts +157 -0
  333. package/src/clis/twitter/post.ts +82 -48
  334. package/src/clis/twitter/reply.test.ts +177 -0
  335. package/src/clis/twitter/reply.ts +285 -39
  336. package/src/clis/xianyu/chat.test.ts +20 -0
  337. package/src/clis/xianyu/chat.ts +175 -0
  338. package/src/clis/xianyu/item.test.ts +67 -0
  339. package/src/clis/xianyu/item.ts +172 -0
  340. package/src/clis/xianyu/search.test.ts +22 -0
  341. package/src/clis/xianyu/search.ts +151 -0
  342. package/src/clis/xianyu/utils.ts +9 -0
  343. package/src/clis/xiaoe/catalog.yaml +129 -0
  344. package/src/clis/xiaoe/content.yaml +43 -0
  345. package/src/clis/xiaoe/courses.yaml +73 -0
  346. package/src/clis/xiaoe/detail.yaml +39 -0
  347. package/src/clis/xiaoe/play-url.yaml +124 -0
  348. package/src/clis/xiaohongshu/comments.test.ts +0 -2
  349. package/src/clis/xiaohongshu/creator-note-detail.test.ts +0 -2
  350. package/src/clis/xiaohongshu/creator-notes.test.ts +0 -2
  351. package/src/clis/xiaohongshu/download.test.ts +0 -2
  352. package/src/clis/xiaohongshu/note.test.ts +0 -2
  353. package/src/clis/xiaohongshu/publish.test.ts +0 -2
  354. package/src/clis/xiaohongshu/search.test.ts +59 -48
  355. package/src/clis/xiaohongshu/search.ts +31 -21
  356. package/src/clis/yuanbao/ask.test.ts +156 -0
  357. package/src/clis/yuanbao/ask.ts +522 -0
  358. package/src/clis/yuanbao/new.test.ts +36 -0
  359. package/src/clis/yuanbao/new.ts +81 -0
  360. package/src/clis/yuanbao/shared.ts +57 -0
  361. package/src/clis/zhihu/question.test.ts +42 -17
  362. package/src/clis/zhihu/question.ts +31 -26
  363. package/src/commanderAdapter.test.ts +51 -0
  364. package/src/commanderAdapter.ts +8 -4
  365. package/src/completion.test.ts +30 -0
  366. package/src/completion.ts +3 -1
  367. package/src/doctor.ts +1 -1
  368. package/src/electron-apps.ts +9 -1
  369. package/src/errors.ts +1 -1
  370. package/src/execution.ts +26 -30
  371. package/src/explore.ts +1 -1
  372. package/src/launcher.test.ts +121 -7
  373. package/src/launcher.ts +87 -9
  374. package/src/output.test.ts +50 -90
  375. package/src/output.ts +10 -1
  376. package/src/pipeline/executor.test.ts +0 -2
  377. package/src/pipeline/steps/download.test.ts +0 -2
  378. package/src/registry.ts +2 -0
  379. package/src/serialization.ts +2 -0
  380. package/src/types.ts +9 -2
  381. package/tests/e2e/browser-auth.test.ts +9 -0
  382. package/CLI-EXPLORER.md +0 -724
  383. package/CLI-ONESHOT.md +0 -216
  384. package/SKILL.md +0 -59
@@ -0,0 +1,402 @@
1
+ import { CommandExecutionError, EmptyResultError } from '../../errors.js';
2
+ import { cli, Strategy } from '../../registry.js';
3
+ import type { IPage } from '../../types.js';
4
+ import {
5
+ FACTORY_BADGE_PATTERNS,
6
+ SERVICE_BADGE_PATTERNS,
7
+ assertAuthenticatedState,
8
+ buildProvenance,
9
+ buildSearchUrl,
10
+ canonicalizeItemUrl,
11
+ canonicalizeSellerUrl,
12
+ cleanText,
13
+ extractBadges,
14
+ extractLocation,
15
+ extractMemberId,
16
+ extractOfferId,
17
+ extractShopId,
18
+ gotoAndReadState,
19
+ parseMoqText,
20
+ parsePriceText,
21
+ SEARCH_LIMIT_DEFAULT,
22
+ SEARCH_LIMIT_MAX,
23
+ parseSearchLimit,
24
+ uniqueNonEmpty,
25
+ } from './shared.js';
26
+
27
+ interface SearchPayload {
28
+ href?: string;
29
+ title?: string;
30
+ bodyText?: string;
31
+ next_url?: string;
32
+ candidates?: Array<{
33
+ item_url?: string;
34
+ title?: string;
35
+ container_text?: string;
36
+ desc_rows?: string[];
37
+ price_text?: string | null;
38
+ sales_text?: string | null;
39
+ hover_price_text?: string | null;
40
+ moq_text?: string | null;
41
+ tag_items?: string[];
42
+ hover_items?: string[];
43
+ seller_name?: string | null;
44
+ seller_url?: string | null;
45
+ }>;
46
+ }
47
+
48
+ interface SearchRow {
49
+ rank: number;
50
+ offer_id: string | null;
51
+ member_id: string | null;
52
+ shop_id: string | null;
53
+ title: string | null;
54
+ item_url: string | null;
55
+ seller_name: string | null;
56
+ seller_url: string | null;
57
+ price_text: string | null;
58
+ price_min: number | null;
59
+ price_max: number | null;
60
+ currency: string | null;
61
+ moq_text: string | null;
62
+ moq_value: number | null;
63
+ location: string | null;
64
+ badges: string[];
65
+ sales_text: string | null;
66
+ return_rate_text: string | null;
67
+ source_url: string;
68
+ fetched_at: string;
69
+ strategy: string;
70
+ }
71
+
72
+ const SEARCH_ITEM_URL_PATTERNS = [
73
+ 'detail.1688.com/offer/',
74
+ 'detail.m.1688.com/page/index.html?offerId=',
75
+ ];
76
+ const MAX_SEARCH_PAGES = 12;
77
+
78
+ function normalizeSearchCandidate(
79
+ candidate: NonNullable<SearchPayload['candidates']>[number],
80
+ sourceUrl: string,
81
+ ): SearchRow {
82
+ const canonicalItemUrl = canonicalizeItemUrl(cleanText(candidate.item_url));
83
+ const containerText = cleanText(candidate.container_text);
84
+ const priceText = firstNonEmpty([
85
+ normalizeInlineText(candidate.price_text),
86
+ normalizeInlineText(extractPriceText(candidate.hover_price_text)),
87
+ ]);
88
+ const priceRange = parsePriceText(priceText || containerText);
89
+ const moq = parseMoqText(firstNonEmpty([
90
+ normalizeInlineText(candidate.moq_text),
91
+ normalizeInlineText(extractMoqText(containerText)),
92
+ ]));
93
+ const canonicalSellerUrl = canonicalizeSellerUrl(cleanText(candidate.seller_url));
94
+ const evidenceText = uniqueNonEmpty([
95
+ containerText,
96
+ ...(candidate.desc_rows ?? []),
97
+ ...(candidate.tag_items ?? []),
98
+ ...(candidate.hover_items ?? []),
99
+ ]).join('\n');
100
+ const badges = extractBadges(evidenceText, [...FACTORY_BADGE_PATTERNS, ...SERVICE_BADGE_PATTERNS]);
101
+ const salesText = firstNonEmpty([
102
+ extractSalesText(candidate.sales_text),
103
+ extractSalesText(containerText),
104
+ ]);
105
+ const returnRateText = extractReturnRateText([...(candidate.tag_items ?? []), ...(candidate.hover_items ?? [])]);
106
+ const provenance = buildProvenance(sourceUrl);
107
+
108
+ return {
109
+ rank: 0,
110
+ offer_id: extractOfferId(canonicalItemUrl ?? '') ?? null,
111
+ member_id: extractMemberId(canonicalSellerUrl ?? '') ?? null,
112
+ shop_id: extractShopId(canonicalSellerUrl ?? '') ?? null,
113
+ title: cleanText(candidate.title) || firstWord(containerText) || null,
114
+ item_url: canonicalItemUrl,
115
+ seller_name: cleanText(candidate.seller_name) || null,
116
+ seller_url: canonicalSellerUrl,
117
+ price_text: priceRange.price_text || null,
118
+ price_min: priceRange.price_min,
119
+ price_max: priceRange.price_max,
120
+ currency: priceRange.currency,
121
+ moq_text: moq.moq_text || null,
122
+ moq_value: moq.moq_value,
123
+ location: extractLocation(containerText),
124
+ badges,
125
+ sales_text: salesText || null,
126
+ return_rate_text: returnRateText,
127
+ source_url: provenance.source_url,
128
+ fetched_at: provenance.fetched_at,
129
+ strategy: provenance.strategy,
130
+ };
131
+ }
132
+
133
+ function extractMoqText(text: string | null | undefined): string {
134
+ const normalized = normalizeInlineText(text);
135
+ return normalized.match(/\d+(?:\.\d+)?\s*(件|个|套|箱|包|双|台|把|只)\s*起批/i)?.[0]
136
+ ?? normalized.match(/≥\s*\d+(?:\.\d+)?\s*(件|个|套|箱|包|双|台|把|只)?/i)?.[0]
137
+ ?? normalized.match(/\d+(?:\.\d+)?\s*(?:~|-|至|到)\s*\d+(?:\.\d+)?\s*(件|个|套|箱|包|双|台|把|只)/i)?.[0]
138
+ ?? '';
139
+ }
140
+
141
+ function extractPriceText(text: string | null | undefined): string {
142
+ const normalized = normalizeInlineText(text);
143
+ return normalized.match(/[¥$€]\s*\d+(?:\.\d+)?/)?.[0] ?? '';
144
+ }
145
+
146
+ function extractSalesText(text: string | null | undefined): string {
147
+ const normalized = normalizeInlineText(text);
148
+ if (!normalized) return '';
149
+ if (/^\d+(?:\.\d+)?\+?\s*(件|套|个|单)$/.test(normalized)) {
150
+ return normalized;
151
+ }
152
+ const match = normalized.match(/(?:已售|销量|售)\s*\d+(?:\.\d+)?\+?\s*(件|套|个|单)?/);
153
+ return match ? cleanText(match[0]) : '';
154
+ }
155
+
156
+ function firstWord(text: string): string {
157
+ return text.split(/\s+/).find(Boolean) ?? '';
158
+ }
159
+
160
+ function firstNonEmpty(values: Array<string | null | undefined>): string {
161
+ return values.map((value) => cleanText(value)).find(Boolean) ?? '';
162
+ }
163
+
164
+ function normalizeInlineText(text: string | null | undefined): string {
165
+ return cleanText(text)
166
+ .replace(/([¥$€])\s+(?=\d)/g, '$1')
167
+ .replace(/(\d)\s*\.\s*(\d)/g, '$1.$2')
168
+ .replace(/\s*([~-])\s*/g, '$1')
169
+ .trim();
170
+ }
171
+
172
+ function extractReturnRateText(values: string[]): string | null {
173
+ return uniqueNonEmpty(values.map((value) => normalizeInlineText(value)))
174
+ .find((value) => /^回头率\s*\d+(?:\.\d+)?%$/.test(value))
175
+ ?? null;
176
+ }
177
+
178
+ function buildDedupeKey(row: Pick<SearchRow, 'offer_id' | 'item_url'>): string | null {
179
+ if (row.offer_id) return `offer:${row.offer_id}`;
180
+ if (row.item_url) return `url:${row.item_url}`;
181
+ return null;
182
+ }
183
+
184
+ async function readSearchPayload(page: IPage, url: string): Promise<SearchPayload> {
185
+ const state = await gotoAndReadState(page, url, 2500, 'search');
186
+ assertAuthenticatedState(state, 'search');
187
+
188
+ const payload = await page.evaluate(`
189
+ (() => {
190
+ const normalizeText = (value) => (value || '').replace(/\\s+/g, ' ').trim();
191
+ const normalizeUrl = (href) => {
192
+ if (!href) return '';
193
+ try {
194
+ return new URL(href, window.location.href).toString();
195
+ } catch {
196
+ return '';
197
+ }
198
+ };
199
+ const isItemHref = (href) => ${JSON.stringify(SEARCH_ITEM_URL_PATTERNS)}
200
+ .some((pattern) => (href || '').includes(pattern));
201
+ const uniqueTexts = (values) => [...new Set(values.map((value) => normalizeText(value)).filter(Boolean))];
202
+ const collectTexts = (root, selector) => uniqueTexts(
203
+ Array.from(root.querySelectorAll(selector)).map((node) => node.innerText || node.textContent || ''),
204
+ );
205
+ const firstText = (root, selectors) => {
206
+ for (const selector of selectors) {
207
+ const node = root.querySelector(selector);
208
+ const value = normalizeText(node ? node.innerText || node.textContent || '' : '');
209
+ if (value) return value;
210
+ }
211
+ return '';
212
+ };
213
+ const findMoqText = (values, priceText) => {
214
+ const moqPattern = /(≥\\s*\\d+(?:\\.\\d+)?\\s*(件|个|套|箱|包|双|台|把|只)?)|(\\d+(?:\\.\\d+)?\\s*(?:~|-|至|到)\\s*\\d+(?:\\.\\d+)?\\s*(件|个|套|箱|包|双|台|把|只))|(\\d+(?:\\.\\d+)?\\s*(件|个|套|箱|包|双|台|把|只)\\s*起批)/i;
215
+ return values.find((value) => moqPattern.test(value))
216
+ || normalizeText(priceText).match(moqPattern)?.[0]
217
+ || '';
218
+ };
219
+ const isSellerHref = (href) => {
220
+ if (!href) return false;
221
+ try {
222
+ const url = new URL(href, window.location.href);
223
+ const host = url.hostname || '';
224
+ if (!host.endsWith('.1688.com')) return false;
225
+ if (
226
+ host === 's.1688.com'
227
+ || host === 'r.1688.com'
228
+ || host === 'air.1688.com'
229
+ || host === 'detail.1688.com'
230
+ || host === 'detail.m.1688.com'
231
+ || host === 'dj.1688.com'
232
+ ) {
233
+ return false;
234
+ }
235
+ return true;
236
+ } catch {
237
+ return false;
238
+ }
239
+ };
240
+ const pickContainer = (anchor) => {
241
+ let node = anchor;
242
+ while (node && node !== document.body) {
243
+ const text = normalizeText(node.innerText || node.textContent || '');
244
+ if (text.length >= 40 && text.length <= 2000) {
245
+ return node;
246
+ }
247
+ node = node.parentElement;
248
+ }
249
+ return anchor;
250
+ };
251
+ const collectCandidates = () => {
252
+ const anchors = Array.from(document.querySelectorAll('a')).filter((anchor) => isItemHref(anchor.href || ''));
253
+ const seen = new Set();
254
+ const items = [];
255
+ for (const anchor of anchors) {
256
+ const href = anchor.href || '';
257
+ if (!href || seen.has(href)) continue;
258
+ seen.add(href);
259
+
260
+ const container = pickContainer(anchor);
261
+ const tagItems = collectTexts(container, '.offer-tag-row .offer-desc-item');
262
+ const hoverItems = collectTexts(container, '.offer-hover-wrapper .offer-desc-item');
263
+ const sellerAnchor = Array.from(container.querySelectorAll('a'))
264
+ .find((link) => isSellerHref(link.href || ''));
265
+ const hoverPriceText = firstText(container, [
266
+ '.offer-hover-wrapper .hover-price-item',
267
+ '.offer-hover-wrapper .price-item',
268
+ ]);
269
+
270
+ items.push({
271
+ item_url: href,
272
+ title: firstText(container, ['.offer-title-row .title-text', '.offer-title-row'])
273
+ || normalizeText(anchor.innerText || anchor.textContent || ''),
274
+ container_text: normalizeText(container.innerText || container.textContent || ''),
275
+ desc_rows: collectTexts(container, '.offer-desc-row'),
276
+ price_text: firstText(container, ['.offer-price-row .price-item']),
277
+ sales_text: firstText(container, ['.offer-price-row .col-desc_after', '.offer-desc-row .col-desc_after']),
278
+ hover_price_text: hoverPriceText,
279
+ moq_text: findMoqText(hoverItems, hoverPriceText),
280
+ tag_items: tagItems,
281
+ hover_items: hoverItems,
282
+ seller_name: sellerAnchor ? normalizeText(sellerAnchor.innerText || sellerAnchor.textContent || '') : null,
283
+ seller_url: sellerAnchor ? sellerAnchor.href : null,
284
+ });
285
+ }
286
+ return items;
287
+ };
288
+ const findNextUrl = () => {
289
+ const selectors = [
290
+ 'a.fui-next:not(.disabled)',
291
+ 'a.next-pagination-item:not(.disabled)',
292
+ 'a[rel="next"]:not(.disabled)',
293
+ 'a[data-role="next"]:not(.disabled)',
294
+ ];
295
+ for (const selector of selectors) {
296
+ const node = document.querySelector(selector);
297
+ if (!node) continue;
298
+ const href = normalizeUrl(node.getAttribute('href') || node.href || '');
299
+ if (href) return href;
300
+ }
301
+ const textBased = Array.from(document.querySelectorAll('a'))
302
+ .find((node) => /下一页|next/i.test(normalizeText(node.textContent || '')));
303
+ if (!textBased) return '';
304
+ return normalizeUrl(textBased.getAttribute('href') || textBased.href || '');
305
+ };
306
+
307
+ return {
308
+ href: window.location.href,
309
+ title: document.title || '',
310
+ bodyText: document.body ? document.body.innerText || '' : '',
311
+ next_url: findNextUrl(),
312
+ candidates: collectCandidates(),
313
+ };
314
+ })()
315
+ `) as SearchPayload;
316
+
317
+ if (!payload || typeof payload !== 'object') {
318
+ throw new CommandExecutionError(
319
+ '1688 search page did not return a readable payload',
320
+ 'Open the same query in Chrome and verify the page is fully loaded before retrying.',
321
+ );
322
+ }
323
+
324
+ return payload;
325
+ }
326
+
327
+ async function collectSearchRows(page: IPage, query: string, limit: number): Promise<SearchRow[]> {
328
+ const rowsByKey = new Map<string, SearchRow>();
329
+ const seenPages = new Set<string>();
330
+ let nextUrl = buildSearchUrl(query);
331
+ let pageCount = 0;
332
+
333
+ while (nextUrl && rowsByKey.size < limit && pageCount < MAX_SEARCH_PAGES) {
334
+ if (seenPages.has(nextUrl)) break;
335
+ seenPages.add(nextUrl);
336
+ pageCount += 1;
337
+
338
+ const payload = await readSearchPayload(page, nextUrl);
339
+ const sourceUrl = cleanText(payload.href) || nextUrl;
340
+ const candidates = Array.isArray(payload.candidates) ? payload.candidates : [];
341
+
342
+ for (const candidate of candidates) {
343
+ const row = normalizeSearchCandidate(candidate, sourceUrl);
344
+ const dedupeKey = buildDedupeKey(row);
345
+ if (!dedupeKey || rowsByKey.has(dedupeKey)) continue;
346
+ rowsByKey.set(dedupeKey, row);
347
+ if (rowsByKey.size >= limit) break;
348
+ }
349
+
350
+ const candidateNextUrl = cleanText(payload.next_url);
351
+ if (!candidateNextUrl || candidateNextUrl === sourceUrl) break;
352
+ nextUrl = candidateNextUrl;
353
+ }
354
+
355
+ if (rowsByKey.size === 0) {
356
+ throw new EmptyResultError(
357
+ '1688 search',
358
+ 'No visible results were extracted. Retry with a different query or open the same search page in Chrome first.',
359
+ );
360
+ }
361
+
362
+ return [...rowsByKey.values()]
363
+ .slice(0, limit)
364
+ .map((row, index) => ({ ...row, rank: index + 1 }));
365
+ }
366
+
367
+ cli({
368
+ site: '1688',
369
+ name: 'search',
370
+ description: '1688 商品搜索(结果候选、卖家链接、价格/MOQ/销量文本)',
371
+ domain: 'www.1688.com',
372
+ strategy: Strategy.COOKIE,
373
+ navigateBefore: false,
374
+ args: [
375
+ {
376
+ name: 'query',
377
+ required: true,
378
+ positional: true,
379
+ help: '搜索关键词,如 "置物架"',
380
+ },
381
+ {
382
+ name: 'limit',
383
+ type: 'int',
384
+ default: SEARCH_LIMIT_DEFAULT,
385
+ help: `结果数量上限(默认 ${SEARCH_LIMIT_DEFAULT},最大 ${SEARCH_LIMIT_MAX})`,
386
+ },
387
+ ],
388
+ columns: ['rank', 'title', 'price_text', 'moq_text', 'seller_name', 'location'],
389
+ func: async (page, kwargs) => {
390
+ const query = String(kwargs.query ?? '');
391
+ const limit = parseSearchLimit(kwargs.limit);
392
+ return collectSearchRows(page, query, limit);
393
+ },
394
+ });
395
+
396
+ export const __test__ = {
397
+ normalizeSearchCandidate,
398
+ extractMoqText,
399
+ extractSalesText,
400
+ firstWord,
401
+ buildDedupeKey,
402
+ };
@@ -0,0 +1,75 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { __test__ } from './shared.js';
3
+
4
+ describe('1688 shared helpers', () => {
5
+ it('builds encoded search URLs and validates limit', () => {
6
+ expect(__test__.buildSearchUrl('置物架')).toBe(
7
+ 'https://s.1688.com/selloffer/offer_search.htm?charset=utf8&keywords=%E7%BD%AE%E7%89%A9%E6%9E%B6',
8
+ );
9
+ expect(() => __test__.buildSearchUrl(' ')).toThrowError(/cannot be empty/i);
10
+
11
+ expect(__test__.parseSearchLimit(3)).toBe(3);
12
+ expect(__test__.parseSearchLimit('1000')).toBe(__test__.SEARCH_LIMIT_MAX);
13
+ expect(() => __test__.parseSearchLimit('0')).toThrowError(/positive integer/i);
14
+ });
15
+
16
+ it('extracts IDs and canonicalizes urls', () => {
17
+ expect(__test__.extractOfferId('887904326744')).toBe('887904326744');
18
+ expect(__test__.extractOfferId('https://detail.1688.com/offer/887904326744.html')).toBe('887904326744');
19
+ expect(__test__.extractMemberId('https://winport.m.1688.com/page/index.html?memberId=b2b-1641351767')).toBe('b2b-1641351767');
20
+ expect(__test__.extractMemberId('b2b-22154705262941f196')).toBe('b2b-22154705262941f196');
21
+ expect(__test__.resolveStoreUrl('b2b-22154705262941f196')).toBe(
22
+ 'https://winport.m.1688.com/page/index.html?memberId=b2b-22154705262941f196',
23
+ );
24
+ expect(__test__.canonicalizeStoreUrl('https://yinuoweierfushi.1688.com/page/index.html?spm=foo')).toBe(
25
+ 'https://yinuoweierfushi.1688.com',
26
+ );
27
+ expect(__test__.canonicalizeItemUrl('http://detail.m.1688.com/page/index.html?offerId=910933345396&spm=x')).toBe(
28
+ 'https://detail.1688.com/offer/910933345396.html',
29
+ );
30
+ expect(__test__.canonicalizeSellerUrl('https://yinuoweierfushi.1688.com/page/contactinfo.html?tracelog=1')).toBe(
31
+ 'https://yinuoweierfushi.1688.com',
32
+ );
33
+ expect(__test__.extractShopId('https://yinuoweierfushi.1688.com/page/index.html')).toBe('yinuoweierfushi');
34
+ });
35
+
36
+ it('parses price ranges and moq text', () => {
37
+ expect(__test__.parsePriceText('¥96.00-98.00')).toEqual({
38
+ price_text: '¥96.00-98.00',
39
+ price_min: 96,
40
+ price_max: 98,
41
+ currency: 'CNY',
42
+ });
43
+
44
+ expect(__test__.parsePriceText('¥ 14 .28')).toEqual({
45
+ price_text: '¥14.28',
46
+ price_min: 14.28,
47
+ price_max: 14.28,
48
+ currency: 'CNY',
49
+ });
50
+
51
+ expect(__test__.parseMoqText('3套起批')).toEqual({
52
+ moq_text: '3套起批',
53
+ moq_value: 3,
54
+ });
55
+
56
+ expect(__test__.parseMoqText('2~999个')).toEqual({
57
+ moq_text: '2~999个',
58
+ moq_value: 2,
59
+ });
60
+ });
61
+
62
+ it('detects captcha and login states', () => {
63
+ expect(__test__.extractLocation('山东青岛 送至 江苏苏州')).toBe('山东青岛');
64
+ expect(__test__.isCaptchaState({
65
+ href: 'https://s.1688.com/_____tmd_____/punish',
66
+ title: '验证码拦截',
67
+ body_text: '请拖动下方滑块完成验证',
68
+ })).toBe(true);
69
+ expect(__test__.isLoginState({
70
+ href: 'https://login.taobao.com/member/login.jhtml',
71
+ title: '账号登录',
72
+ body_text: '请登录后继续',
73
+ })).toBe(true);
74
+ });
75
+ });