@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,309 @@
1
+ import { CommandExecutionError, EmptyResultError } from '../../errors.js';
2
+ import { cli, Strategy } from '../../registry.js';
3
+ import { FACTORY_BADGE_PATTERNS, SERVICE_BADGE_PATTERNS, assertAuthenticatedState, buildProvenance, buildSearchUrl, canonicalizeItemUrl, canonicalizeSellerUrl, cleanText, extractBadges, extractLocation, extractMemberId, extractOfferId, extractShopId, gotoAndReadState, parseMoqText, parsePriceText, SEARCH_LIMIT_DEFAULT, SEARCH_LIMIT_MAX, parseSearchLimit, uniqueNonEmpty, } from './shared.js';
4
+ const SEARCH_ITEM_URL_PATTERNS = [
5
+ 'detail.1688.com/offer/',
6
+ 'detail.m.1688.com/page/index.html?offerId=',
7
+ ];
8
+ const MAX_SEARCH_PAGES = 12;
9
+ function normalizeSearchCandidate(candidate, sourceUrl) {
10
+ const canonicalItemUrl = canonicalizeItemUrl(cleanText(candidate.item_url));
11
+ const containerText = cleanText(candidate.container_text);
12
+ const priceText = firstNonEmpty([
13
+ normalizeInlineText(candidate.price_text),
14
+ normalizeInlineText(extractPriceText(candidate.hover_price_text)),
15
+ ]);
16
+ const priceRange = parsePriceText(priceText || containerText);
17
+ const moq = parseMoqText(firstNonEmpty([
18
+ normalizeInlineText(candidate.moq_text),
19
+ normalizeInlineText(extractMoqText(containerText)),
20
+ ]));
21
+ const canonicalSellerUrl = canonicalizeSellerUrl(cleanText(candidate.seller_url));
22
+ const evidenceText = uniqueNonEmpty([
23
+ containerText,
24
+ ...(candidate.desc_rows ?? []),
25
+ ...(candidate.tag_items ?? []),
26
+ ...(candidate.hover_items ?? []),
27
+ ]).join('\n');
28
+ const badges = extractBadges(evidenceText, [...FACTORY_BADGE_PATTERNS, ...SERVICE_BADGE_PATTERNS]);
29
+ const salesText = firstNonEmpty([
30
+ extractSalesText(candidate.sales_text),
31
+ extractSalesText(containerText),
32
+ ]);
33
+ const returnRateText = extractReturnRateText([...(candidate.tag_items ?? []), ...(candidate.hover_items ?? [])]);
34
+ const provenance = buildProvenance(sourceUrl);
35
+ return {
36
+ rank: 0,
37
+ offer_id: extractOfferId(canonicalItemUrl ?? '') ?? null,
38
+ member_id: extractMemberId(canonicalSellerUrl ?? '') ?? null,
39
+ shop_id: extractShopId(canonicalSellerUrl ?? '') ?? null,
40
+ title: cleanText(candidate.title) || firstWord(containerText) || null,
41
+ item_url: canonicalItemUrl,
42
+ seller_name: cleanText(candidate.seller_name) || null,
43
+ seller_url: canonicalSellerUrl,
44
+ price_text: priceRange.price_text || null,
45
+ price_min: priceRange.price_min,
46
+ price_max: priceRange.price_max,
47
+ currency: priceRange.currency,
48
+ moq_text: moq.moq_text || null,
49
+ moq_value: moq.moq_value,
50
+ location: extractLocation(containerText),
51
+ badges,
52
+ sales_text: salesText || null,
53
+ return_rate_text: returnRateText,
54
+ source_url: provenance.source_url,
55
+ fetched_at: provenance.fetched_at,
56
+ strategy: provenance.strategy,
57
+ };
58
+ }
59
+ function extractMoqText(text) {
60
+ const normalized = normalizeInlineText(text);
61
+ return normalized.match(/\d+(?:\.\d+)?\s*(件|个|套|箱|包|双|台|把|只)\s*起批/i)?.[0]
62
+ ?? normalized.match(/≥\s*\d+(?:\.\d+)?\s*(件|个|套|箱|包|双|台|把|只)?/i)?.[0]
63
+ ?? normalized.match(/\d+(?:\.\d+)?\s*(?:~|-|至|到)\s*\d+(?:\.\d+)?\s*(件|个|套|箱|包|双|台|把|只)/i)?.[0]
64
+ ?? '';
65
+ }
66
+ function extractPriceText(text) {
67
+ const normalized = normalizeInlineText(text);
68
+ return normalized.match(/[¥$€]\s*\d+(?:\.\d+)?/)?.[0] ?? '';
69
+ }
70
+ function extractSalesText(text) {
71
+ const normalized = normalizeInlineText(text);
72
+ if (!normalized)
73
+ return '';
74
+ if (/^\d+(?:\.\d+)?\+?\s*(件|套|个|单)$/.test(normalized)) {
75
+ return normalized;
76
+ }
77
+ const match = normalized.match(/(?:已售|销量|售)\s*\d+(?:\.\d+)?\+?\s*(件|套|个|单)?/);
78
+ return match ? cleanText(match[0]) : '';
79
+ }
80
+ function firstWord(text) {
81
+ return text.split(/\s+/).find(Boolean) ?? '';
82
+ }
83
+ function firstNonEmpty(values) {
84
+ return values.map((value) => cleanText(value)).find(Boolean) ?? '';
85
+ }
86
+ function normalizeInlineText(text) {
87
+ return cleanText(text)
88
+ .replace(/([¥$€])\s+(?=\d)/g, '$1')
89
+ .replace(/(\d)\s*\.\s*(\d)/g, '$1.$2')
90
+ .replace(/\s*([~-])\s*/g, '$1')
91
+ .trim();
92
+ }
93
+ function extractReturnRateText(values) {
94
+ return uniqueNonEmpty(values.map((value) => normalizeInlineText(value)))
95
+ .find((value) => /^回头率\s*\d+(?:\.\d+)?%$/.test(value))
96
+ ?? null;
97
+ }
98
+ function buildDedupeKey(row) {
99
+ if (row.offer_id)
100
+ return `offer:${row.offer_id}`;
101
+ if (row.item_url)
102
+ return `url:${row.item_url}`;
103
+ return null;
104
+ }
105
+ async function readSearchPayload(page, url) {
106
+ const state = await gotoAndReadState(page, url, 2500, 'search');
107
+ assertAuthenticatedState(state, 'search');
108
+ const payload = await page.evaluate(`
109
+ (() => {
110
+ const normalizeText = (value) => (value || '').replace(/\\s+/g, ' ').trim();
111
+ const normalizeUrl = (href) => {
112
+ if (!href) return '';
113
+ try {
114
+ return new URL(href, window.location.href).toString();
115
+ } catch {
116
+ return '';
117
+ }
118
+ };
119
+ const isItemHref = (href) => ${JSON.stringify(SEARCH_ITEM_URL_PATTERNS)}
120
+ .some((pattern) => (href || '').includes(pattern));
121
+ const uniqueTexts = (values) => [...new Set(values.map((value) => normalizeText(value)).filter(Boolean))];
122
+ const collectTexts = (root, selector) => uniqueTexts(
123
+ Array.from(root.querySelectorAll(selector)).map((node) => node.innerText || node.textContent || ''),
124
+ );
125
+ const firstText = (root, selectors) => {
126
+ for (const selector of selectors) {
127
+ const node = root.querySelector(selector);
128
+ const value = normalizeText(node ? node.innerText || node.textContent || '' : '');
129
+ if (value) return value;
130
+ }
131
+ return '';
132
+ };
133
+ const findMoqText = (values, priceText) => {
134
+ const moqPattern = /(≥\\s*\\d+(?:\\.\\d+)?\\s*(件|个|套|箱|包|双|台|把|只)?)|(\\d+(?:\\.\\d+)?\\s*(?:~|-|至|到)\\s*\\d+(?:\\.\\d+)?\\s*(件|个|套|箱|包|双|台|把|只))|(\\d+(?:\\.\\d+)?\\s*(件|个|套|箱|包|双|台|把|只)\\s*起批)/i;
135
+ return values.find((value) => moqPattern.test(value))
136
+ || normalizeText(priceText).match(moqPattern)?.[0]
137
+ || '';
138
+ };
139
+ const isSellerHref = (href) => {
140
+ if (!href) return false;
141
+ try {
142
+ const url = new URL(href, window.location.href);
143
+ const host = url.hostname || '';
144
+ if (!host.endsWith('.1688.com')) return false;
145
+ if (
146
+ host === 's.1688.com'
147
+ || host === 'r.1688.com'
148
+ || host === 'air.1688.com'
149
+ || host === 'detail.1688.com'
150
+ || host === 'detail.m.1688.com'
151
+ || host === 'dj.1688.com'
152
+ ) {
153
+ return false;
154
+ }
155
+ return true;
156
+ } catch {
157
+ return false;
158
+ }
159
+ };
160
+ const pickContainer = (anchor) => {
161
+ let node = anchor;
162
+ while (node && node !== document.body) {
163
+ const text = normalizeText(node.innerText || node.textContent || '');
164
+ if (text.length >= 40 && text.length <= 2000) {
165
+ return node;
166
+ }
167
+ node = node.parentElement;
168
+ }
169
+ return anchor;
170
+ };
171
+ const collectCandidates = () => {
172
+ const anchors = Array.from(document.querySelectorAll('a')).filter((anchor) => isItemHref(anchor.href || ''));
173
+ const seen = new Set();
174
+ const items = [];
175
+ for (const anchor of anchors) {
176
+ const href = anchor.href || '';
177
+ if (!href || seen.has(href)) continue;
178
+ seen.add(href);
179
+
180
+ const container = pickContainer(anchor);
181
+ const tagItems = collectTexts(container, '.offer-tag-row .offer-desc-item');
182
+ const hoverItems = collectTexts(container, '.offer-hover-wrapper .offer-desc-item');
183
+ const sellerAnchor = Array.from(container.querySelectorAll('a'))
184
+ .find((link) => isSellerHref(link.href || ''));
185
+ const hoverPriceText = firstText(container, [
186
+ '.offer-hover-wrapper .hover-price-item',
187
+ '.offer-hover-wrapper .price-item',
188
+ ]);
189
+
190
+ items.push({
191
+ item_url: href,
192
+ title: firstText(container, ['.offer-title-row .title-text', '.offer-title-row'])
193
+ || normalizeText(anchor.innerText || anchor.textContent || ''),
194
+ container_text: normalizeText(container.innerText || container.textContent || ''),
195
+ desc_rows: collectTexts(container, '.offer-desc-row'),
196
+ price_text: firstText(container, ['.offer-price-row .price-item']),
197
+ sales_text: firstText(container, ['.offer-price-row .col-desc_after', '.offer-desc-row .col-desc_after']),
198
+ hover_price_text: hoverPriceText,
199
+ moq_text: findMoqText(hoverItems, hoverPriceText),
200
+ tag_items: tagItems,
201
+ hover_items: hoverItems,
202
+ seller_name: sellerAnchor ? normalizeText(sellerAnchor.innerText || sellerAnchor.textContent || '') : null,
203
+ seller_url: sellerAnchor ? sellerAnchor.href : null,
204
+ });
205
+ }
206
+ return items;
207
+ };
208
+ const findNextUrl = () => {
209
+ const selectors = [
210
+ 'a.fui-next:not(.disabled)',
211
+ 'a.next-pagination-item:not(.disabled)',
212
+ 'a[rel="next"]:not(.disabled)',
213
+ 'a[data-role="next"]:not(.disabled)',
214
+ ];
215
+ for (const selector of selectors) {
216
+ const node = document.querySelector(selector);
217
+ if (!node) continue;
218
+ const href = normalizeUrl(node.getAttribute('href') || node.href || '');
219
+ if (href) return href;
220
+ }
221
+ const textBased = Array.from(document.querySelectorAll('a'))
222
+ .find((node) => /下一页|next/i.test(normalizeText(node.textContent || '')));
223
+ if (!textBased) return '';
224
+ return normalizeUrl(textBased.getAttribute('href') || textBased.href || '');
225
+ };
226
+
227
+ return {
228
+ href: window.location.href,
229
+ title: document.title || '',
230
+ bodyText: document.body ? document.body.innerText || '' : '',
231
+ next_url: findNextUrl(),
232
+ candidates: collectCandidates(),
233
+ };
234
+ })()
235
+ `);
236
+ if (!payload || typeof payload !== 'object') {
237
+ throw new CommandExecutionError('1688 search page did not return a readable payload', 'Open the same query in Chrome and verify the page is fully loaded before retrying.');
238
+ }
239
+ return payload;
240
+ }
241
+ async function collectSearchRows(page, query, limit) {
242
+ const rowsByKey = new Map();
243
+ const seenPages = new Set();
244
+ let nextUrl = buildSearchUrl(query);
245
+ let pageCount = 0;
246
+ while (nextUrl && rowsByKey.size < limit && pageCount < MAX_SEARCH_PAGES) {
247
+ if (seenPages.has(nextUrl))
248
+ break;
249
+ seenPages.add(nextUrl);
250
+ pageCount += 1;
251
+ const payload = await readSearchPayload(page, nextUrl);
252
+ const sourceUrl = cleanText(payload.href) || nextUrl;
253
+ const candidates = Array.isArray(payload.candidates) ? payload.candidates : [];
254
+ for (const candidate of candidates) {
255
+ const row = normalizeSearchCandidate(candidate, sourceUrl);
256
+ const dedupeKey = buildDedupeKey(row);
257
+ if (!dedupeKey || rowsByKey.has(dedupeKey))
258
+ continue;
259
+ rowsByKey.set(dedupeKey, row);
260
+ if (rowsByKey.size >= limit)
261
+ break;
262
+ }
263
+ const candidateNextUrl = cleanText(payload.next_url);
264
+ if (!candidateNextUrl || candidateNextUrl === sourceUrl)
265
+ break;
266
+ nextUrl = candidateNextUrl;
267
+ }
268
+ if (rowsByKey.size === 0) {
269
+ throw new EmptyResultError('1688 search', 'No visible results were extracted. Retry with a different query or open the same search page in Chrome first.');
270
+ }
271
+ return [...rowsByKey.values()]
272
+ .slice(0, limit)
273
+ .map((row, index) => ({ ...row, rank: index + 1 }));
274
+ }
275
+ cli({
276
+ site: '1688',
277
+ name: 'search',
278
+ description: '1688 商品搜索(结果候选、卖家链接、价格/MOQ/销量文本)',
279
+ domain: 'www.1688.com',
280
+ strategy: Strategy.COOKIE,
281
+ navigateBefore: false,
282
+ args: [
283
+ {
284
+ name: 'query',
285
+ required: true,
286
+ positional: true,
287
+ help: '搜索关键词,如 "置物架"',
288
+ },
289
+ {
290
+ name: 'limit',
291
+ type: 'int',
292
+ default: SEARCH_LIMIT_DEFAULT,
293
+ help: `结果数量上限(默认 ${SEARCH_LIMIT_DEFAULT},最大 ${SEARCH_LIMIT_MAX})`,
294
+ },
295
+ ],
296
+ columns: ['rank', 'title', 'price_text', 'moq_text', 'seller_name', 'location'],
297
+ func: async (page, kwargs) => {
298
+ const query = String(kwargs.query ?? '');
299
+ const limit = parseSearchLimit(kwargs.limit);
300
+ return collectSearchRows(page, query, limit);
301
+ },
302
+ });
303
+ export const __test__ = {
304
+ normalizeSearchCandidate,
305
+ extractMoqText,
306
+ extractSalesText,
307
+ firstWord,
308
+ buildDedupeKey,
309
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,75 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { __test__ } from './search.js';
3
+ describe('1688 search normalization', () => {
4
+ it('normalizes search candidates into structured result rows', () => {
5
+ const result = __test__.normalizeSearchCandidate({
6
+ item_url: 'https://detail.1688.com/offer/887904326744.html',
7
+ title: '宿舍置物架桌面加高架',
8
+ container_text: '宿舍置物架桌面加高架 ¥56.00 2套起批 山东青岛 已售300+套',
9
+ price_text: '¥ 56 .00',
10
+ sales_text: '300+套',
11
+ moq_text: '2套起批',
12
+ tag_items: ['退货包运费', '回头率52%'],
13
+ hover_items: ['验厂报告'],
14
+ seller_name: '青岛沁澜衣品服装有限公司',
15
+ seller_url: 'https://yinuoweierfushi.1688.com/page/index.html?spm=a123',
16
+ }, 'https://s.1688.com/selloffer/offer_search.htm?charset=utf8&keywords=置物架');
17
+ expect(result.rank).toBe(0);
18
+ expect(result.offer_id).toBe('887904326744');
19
+ expect(result.shop_id).toBe('yinuoweierfushi');
20
+ expect(result.item_url).toBe('https://detail.1688.com/offer/887904326744.html');
21
+ expect(result.seller_url).toBe('https://yinuoweierfushi.1688.com');
22
+ expect(result.price_text).toBe('¥56.00');
23
+ expect(result.price_min).toBe(56);
24
+ expect(result.price_max).toBe(56);
25
+ expect(result.moq_value).toBe(2);
26
+ expect(result.location).toBe('山东青岛');
27
+ expect(result.sales_text).toBe('300+套');
28
+ expect(result.badges).toEqual(expect.arrayContaining(['退货包运费', '验厂报告']));
29
+ expect(result.return_rate_text).toBe('回头率52%');
30
+ });
31
+ it('does not use hover_price_text as MOQ source', () => {
32
+ const result = __test__.normalizeSearchCandidate({
33
+ item_url: 'https://detail.1688.com/offer/887904326744.html',
34
+ title: 'test',
35
+ container_text: 'test ¥56.00',
36
+ price_text: '¥ 56 .00',
37
+ hover_price_text: '¥56.00 3件起批',
38
+ moq_text: null,
39
+ }, 'https://s.1688.com/selloffer/offer_search.htm?charset=utf8&keywords=test');
40
+ // hover_price_text should not be used for MOQ extraction
41
+ expect(result.moq_text).toBeNull();
42
+ expect(result.moq_value).toBeNull();
43
+ });
44
+ it('extracts offer id from mobile detail search links', () => {
45
+ const result = __test__.normalizeSearchCandidate({
46
+ item_url: 'http://detail.m.1688.com/page/index.html?offerId=910933345396&sortType=&pageId=',
47
+ title: '',
48
+ container_text: '桌面书桌办公室工位收纳展示新中式博古架多层茶具厨房摆放置物架 ¥24.3 已售20+件',
49
+ price_text: '¥ 14 .28',
50
+ sales_text: '1500+件',
51
+ moq_text: '≥2个',
52
+ seller_name: '泰商国际贸易(宁阳)有限公司',
53
+ seller_url: 'http://tsgjmy.1688.com/',
54
+ }, 'https://s.1688.com/selloffer/offer_search.htm?charset=utf8&keywords=桌面置物架');
55
+ expect(result.offer_id).toBe('910933345396');
56
+ expect(result.shop_id).toBe('tsgjmy');
57
+ expect(result.item_url).toBe('https://detail.1688.com/offer/910933345396.html');
58
+ expect(result.title).toContain('桌面书桌办公室工位收纳展示');
59
+ expect(result.price_text).toBe('¥14.28');
60
+ expect(result.sales_text).toBe('1500+件');
61
+ expect(result.moq_text).toBe('≥2个');
62
+ expect(result.moq_value).toBe(2);
63
+ });
64
+ it('prefers offer id and falls back to item url for dedupe key', () => {
65
+ expect(__test__.buildDedupeKey({
66
+ offer_id: '123456',
67
+ item_url: 'https://detail.1688.com/offer/123456.html',
68
+ })).toBe('offer:123456');
69
+ expect(__test__.buildDedupeKey({
70
+ offer_id: null,
71
+ item_url: 'https://detail.1688.com/offer/123456.html',
72
+ })).toBe('url:https://detail.1688.com/offer/123456.html');
73
+ expect(__test__.buildDedupeKey({ offer_id: null, item_url: null })).toBeNull();
74
+ });
75
+ });
@@ -0,0 +1,112 @@
1
+ import type { IPage } from '../../types.js';
2
+ export declare const SITE = "1688";
3
+ export declare const HOME_URL = "https://www.1688.com/";
4
+ export declare const SEARCH_URL_PREFIX = "https://s.1688.com/selloffer/offer_search.htm?charset=utf8&keywords=";
5
+ export declare const DETAIL_URL_PREFIX = "https://detail.1688.com/offer/";
6
+ export declare const STORE_MOBILE_URL_PREFIX = "https://winport.m.1688.com/page/index.html?memberId=";
7
+ export declare const STRATEGY = "cookie";
8
+ export declare const SEARCH_LIMIT_DEFAULT = 20;
9
+ export declare const SEARCH_LIMIT_MAX = 100;
10
+ export declare const FACTORY_BADGE_PATTERNS: string[];
11
+ export declare const SERVICE_BADGE_PATTERNS: string[];
12
+ export interface ProvenanceFields {
13
+ source_url: string;
14
+ fetched_at: string;
15
+ strategy: string;
16
+ }
17
+ export interface PageState {
18
+ href: string;
19
+ title: string;
20
+ body_text: string;
21
+ }
22
+ export interface PriceRange {
23
+ price_text: string;
24
+ price_min: number | null;
25
+ price_max: number | null;
26
+ currency: string | null;
27
+ }
28
+ export interface MoqValue {
29
+ moq_text: string;
30
+ moq_value: number | null;
31
+ }
32
+ export interface PriceTier {
33
+ quantity_text: string;
34
+ quantity_min: number | null;
35
+ price_text: string;
36
+ price: number | null;
37
+ currency: string | null;
38
+ }
39
+ export interface SearchCandidate {
40
+ item_url: string;
41
+ title: string;
42
+ container_text: string;
43
+ seller_name: string | null;
44
+ seller_url: string | null;
45
+ }
46
+ export declare function cleanText(value: unknown): string;
47
+ export declare function cleanMultilineText(value: unknown): string;
48
+ export declare function uniqueNonEmpty(values: Array<string | null | undefined>): string[];
49
+ export declare function parseSearchLimit(input: unknown): number;
50
+ export declare function buildSearchUrl(query: string): string;
51
+ export declare function buildDetailUrl(input: string): string;
52
+ export declare function resolveStoreUrl(input: string): string;
53
+ export declare function canonicalizeStoreUrl(input: string): string;
54
+ export declare function canonicalizeItemUrl(input: string): string | null;
55
+ export declare function canonicalizeSellerUrl(input: string): string | null;
56
+ export declare function extractOfferId(input: string): string | null;
57
+ export declare function extractMemberId(input: string): string | null;
58
+ export declare function extractShopId(input: string): string | null;
59
+ export declare function buildProvenance(sourceUrl: string): ProvenanceFields;
60
+ export declare function parsePriceText(text: string): PriceRange;
61
+ export declare function normalizePriceTiers(rawTiers: Array<{
62
+ beginAmount?: unknown;
63
+ price?: unknown;
64
+ }>, unit: string | null): PriceTier[];
65
+ export declare function parseMoqText(text: string): MoqValue;
66
+ export declare function extractLocation(text: string): string | null;
67
+ export declare function extractAddress(text: string): string | null;
68
+ export declare function extractMetric(text: string, label: string): string | null;
69
+ export declare function extractYearsOnPlatform(text: string): string | null;
70
+ export declare function extractMainBusiness(text: string): string | null;
71
+ export declare function extractBadges(text: string, candidates: string[]): string[];
72
+ export declare function guessTopCategories(text: string): string[];
73
+ export declare function isCaptchaState(state: Partial<PageState>): boolean;
74
+ export declare function isLoginState(state: Partial<PageState>): boolean;
75
+ export declare function buildCaptchaHint(action: string): string;
76
+ export declare function readPageState(page: IPage): Promise<PageState>;
77
+ export declare function gotoAndReadState(page: IPage, url: string, settleMs?: number, action?: string): Promise<PageState>;
78
+ export declare function ensure1688Session(page: IPage): Promise<void>;
79
+ export declare function assertAuthenticatedState(state: PageState, action: string): void;
80
+ export declare function assertNotCaptcha(state: PageState, action: string): void;
81
+ export declare function toNumber(value: unknown): number | null;
82
+ export declare function limitCandidates<T>(values: T[], limit: number): T[];
83
+ export declare const __test__: {
84
+ SEARCH_LIMIT_DEFAULT: number;
85
+ SEARCH_LIMIT_MAX: number;
86
+ parseSearchLimit: typeof parseSearchLimit;
87
+ buildSearchUrl: typeof buildSearchUrl;
88
+ buildDetailUrl: typeof buildDetailUrl;
89
+ resolveStoreUrl: typeof resolveStoreUrl;
90
+ canonicalizeStoreUrl: typeof canonicalizeStoreUrl;
91
+ canonicalizeItemUrl: typeof canonicalizeItemUrl;
92
+ canonicalizeSellerUrl: typeof canonicalizeSellerUrl;
93
+ extractOfferId: typeof extractOfferId;
94
+ extractMemberId: typeof extractMemberId;
95
+ extractShopId: typeof extractShopId;
96
+ parsePriceText: typeof parsePriceText;
97
+ normalizePriceTiers: typeof normalizePriceTiers;
98
+ parseMoqText: typeof parseMoqText;
99
+ extractLocation: typeof extractLocation;
100
+ extractAddress: typeof extractAddress;
101
+ extractMetric: typeof extractMetric;
102
+ extractYearsOnPlatform: typeof extractYearsOnPlatform;
103
+ extractMainBusiness: typeof extractMainBusiness;
104
+ extractBadges: typeof extractBadges;
105
+ guessTopCategories: typeof guessTopCategories;
106
+ isCaptchaState: typeof isCaptchaState;
107
+ isLoginState: typeof isLoginState;
108
+ cleanText: typeof cleanText;
109
+ cleanMultilineText: typeof cleanMultilineText;
110
+ uniqueNonEmpty: typeof uniqueNonEmpty;
111
+ limitCandidates: typeof limitCandidates;
112
+ };