@zenalexa/unicli 0.219.0 → 0.220.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (601) hide show
  1. package/AGENTS.md +15 -24
  2. package/README.md +62 -250
  3. package/README.zh-CN.md +62 -250
  4. package/dist/adapters/1point3acres/forum.d.ts +58 -0
  5. package/dist/adapters/1point3acres/forum.d.ts.map +1 -0
  6. package/dist/adapters/1point3acres/forum.js +708 -0
  7. package/dist/adapters/1point3acres/forum.js.map +1 -0
  8. package/dist/adapters/aibase/news.d.ts +21 -0
  9. package/dist/adapters/aibase/news.d.ts.map +1 -0
  10. package/dist/adapters/aibase/news.js +96 -0
  11. package/dist/adapters/aibase/news.js.map +1 -0
  12. package/dist/adapters/anilist/web.d.ts +11 -0
  13. package/dist/adapters/anilist/web.d.ts.map +1 -0
  14. package/dist/adapters/anilist/web.js +284 -0
  15. package/dist/adapters/anilist/web.js.map +1 -0
  16. package/dist/adapters/arxiv/papers.d.ts +27 -0
  17. package/dist/adapters/arxiv/papers.d.ts.map +1 -0
  18. package/dist/adapters/arxiv/papers.js +193 -0
  19. package/dist/adapters/arxiv/papers.js.map +1 -0
  20. package/dist/adapters/bangumi/web.d.ts +14 -0
  21. package/dist/adapters/bangumi/web.d.ts.map +1 -0
  22. package/dist/adapters/bangumi/web.js +257 -0
  23. package/dist/adapters/bangumi/web.js.map +1 -0
  24. package/dist/adapters/bbc/topic.d.ts +24 -0
  25. package/dist/adapters/bbc/topic.d.ts.map +1 -0
  26. package/dist/adapters/bbc/topic.js +122 -0
  27. package/dist/adapters/bbc/topic.js.map +1 -0
  28. package/dist/adapters/chatgpt/web.d.ts +24 -0
  29. package/dist/adapters/chatgpt/web.d.ts.map +1 -0
  30. package/dist/adapters/chatgpt/web.js +242 -0
  31. package/dist/adapters/chatgpt/web.js.map +1 -0
  32. package/dist/adapters/claude/web.d.ts +24 -0
  33. package/dist/adapters/claude/web.d.ts.map +1 -0
  34. package/dist/adapters/claude/web.js +575 -0
  35. package/dist/adapters/claude/web.js.map +1 -0
  36. package/dist/adapters/codex/projects.d.ts +27 -0
  37. package/dist/adapters/codex/projects.d.ts.map +1 -0
  38. package/dist/adapters/codex/projects.js +147 -0
  39. package/dist/adapters/codex/projects.js.map +1 -0
  40. package/dist/adapters/coingecko/markets.d.ts +19 -0
  41. package/dist/adapters/coingecko/markets.d.ts.map +1 -0
  42. package/dist/adapters/coingecko/markets.js +474 -0
  43. package/dist/adapters/coingecko/markets.js.map +1 -0
  44. package/dist/adapters/coupang/product.d.ts +27 -0
  45. package/dist/adapters/coupang/product.d.ts.map +1 -0
  46. package/dist/adapters/coupang/product.js +211 -0
  47. package/dist/adapters/coupang/product.js.map +1 -0
  48. package/dist/adapters/crates/registry.d.ts +44 -0
  49. package/dist/adapters/crates/registry.d.ts.map +1 -0
  50. package/dist/adapters/crates/registry.js +186 -0
  51. package/dist/adapters/crates/registry.js.map +1 -0
  52. package/dist/adapters/ctrip/travel.d.ts +83 -0
  53. package/dist/adapters/ctrip/travel.d.ts.map +1 -0
  54. package/dist/adapters/ctrip/travel.js +630 -0
  55. package/dist/adapters/ctrip/travel.js.map +1 -0
  56. package/dist/adapters/dblp/publications.d.ts +41 -0
  57. package/dist/adapters/dblp/publications.d.ts.map +1 -0
  58. package/dist/adapters/dblp/publications.js +409 -0
  59. package/dist/adapters/dblp/publications.js.map +1 -0
  60. package/dist/adapters/deepseek/web.d.ts +1 -1
  61. package/dist/adapters/deepseek/web.d.ts.map +1 -1
  62. package/dist/adapters/deepseek/web.js +66 -1
  63. package/dist/adapters/deepseek/web.js.map +1 -1
  64. package/dist/adapters/defillama/protocols.d.ts +13 -0
  65. package/dist/adapters/defillama/protocols.d.ts.map +1 -0
  66. package/dist/adapters/defillama/protocols.js +218 -0
  67. package/dist/adapters/defillama/protocols.js.map +1 -0
  68. package/dist/adapters/devto/read.d.ts +26 -0
  69. package/dist/adapters/devto/read.d.ts.map +1 -0
  70. package/dist/adapters/devto/read.js +110 -0
  71. package/dist/adapters/devto/read.js.map +1 -0
  72. package/dist/adapters/dianping/shop.d.ts +38 -0
  73. package/dist/adapters/dianping/shop.d.ts.map +1 -0
  74. package/dist/adapters/dianping/shop.js +194 -0
  75. package/dist/adapters/dianping/shop.js.map +1 -0
  76. package/dist/adapters/dlsite/web.d.ts +31 -0
  77. package/dist/adapters/dlsite/web.d.ts.map +1 -0
  78. package/dist/adapters/dlsite/web.js +455 -0
  79. package/dist/adapters/dlsite/web.js.map +1 -0
  80. package/dist/adapters/dockerhub/registry.d.ts +36 -0
  81. package/dist/adapters/dockerhub/registry.d.ts.map +1 -0
  82. package/dist/adapters/dockerhub/registry.js +172 -0
  83. package/dist/adapters/dockerhub/registry.js.map +1 -0
  84. package/dist/adapters/ehentai/web.d.ts +66 -0
  85. package/dist/adapters/ehentai/web.d.ts.map +1 -0
  86. package/dist/adapters/ehentai/web.js +608 -0
  87. package/dist/adapters/ehentai/web.js.map +1 -0
  88. package/dist/adapters/endoflife/product.d.ts +11 -0
  89. package/dist/adapters/endoflife/product.d.ts.map +1 -0
  90. package/dist/adapters/endoflife/product.js +113 -0
  91. package/dist/adapters/endoflife/product.js.map +1 -0
  92. package/dist/adapters/facebook/marketplace-extra.d.ts +9 -0
  93. package/dist/adapters/facebook/marketplace-extra.d.ts.map +1 -0
  94. package/dist/adapters/facebook/marketplace-extra.js +170 -0
  95. package/dist/adapters/facebook/marketplace-extra.js.map +1 -0
  96. package/dist/adapters/flathub/apps.d.ts +17 -0
  97. package/dist/adapters/flathub/apps.d.ts.map +1 -0
  98. package/dist/adapters/flathub/apps.js +220 -0
  99. package/dist/adapters/flathub/apps.js.map +1 -0
  100. package/dist/adapters/goproxy/module.d.ts +24 -0
  101. package/dist/adapters/goproxy/module.d.ts.map +1 -0
  102. package/dist/adapters/goproxy/module.js +221 -0
  103. package/dist/adapters/goproxy/module.js.map +1 -0
  104. package/dist/adapters/grok/web.d.ts +29 -0
  105. package/dist/adapters/grok/web.d.ts.map +1 -0
  106. package/dist/adapters/grok/web.js +553 -0
  107. package/dist/adapters/grok/web.js.map +1 -0
  108. package/dist/adapters/hackernews/read.d.ts +31 -0
  109. package/dist/adapters/hackernews/read.d.ts.map +1 -0
  110. package/dist/adapters/hackernews/read.js +201 -0
  111. package/dist/adapters/hackernews/read.js.map +1 -0
  112. package/dist/adapters/hf/paper.d.ts +22 -0
  113. package/dist/adapters/hf/paper.d.ts.map +1 -0
  114. package/dist/adapters/hf/paper.js +112 -0
  115. package/dist/adapters/hf/paper.js.map +1 -0
  116. package/dist/adapters/homebrew/packages.d.ts +52 -0
  117. package/dist/adapters/homebrew/packages.d.ts.map +1 -0
  118. package/dist/adapters/homebrew/packages.js +240 -0
  119. package/dist/adapters/homebrew/packages.js.map +1 -0
  120. package/dist/adapters/indeed/jobs.d.ts +38 -0
  121. package/dist/adapters/indeed/jobs.d.ts.map +1 -0
  122. package/dist/adapters/indeed/jobs.js +300 -0
  123. package/dist/adapters/indeed/jobs.js.map +1 -0
  124. package/dist/adapters/instagram/collections.d.ts +9 -0
  125. package/dist/adapters/instagram/collections.d.ts.map +1 -0
  126. package/dist/adapters/instagram/collections.js +174 -0
  127. package/dist/adapters/instagram/collections.js.map +1 -0
  128. package/dist/adapters/jikan/web.d.ts +9 -0
  129. package/dist/adapters/jikan/web.d.ts.map +1 -0
  130. package/dist/adapters/jikan/web.js +154 -0
  131. package/dist/adapters/jikan/web.js.map +1 -0
  132. package/dist/adapters/kitsu/web.d.ts +9 -0
  133. package/dist/adapters/kitsu/web.d.ts.map +1 -0
  134. package/dist/adapters/kitsu/web.js +97 -0
  135. package/dist/adapters/kitsu/web.js.map +1 -0
  136. package/dist/adapters/lichess/players.d.ts +46 -0
  137. package/dist/adapters/lichess/players.d.ts.map +1 -0
  138. package/dist/adapters/lichess/players.js +221 -0
  139. package/dist/adapters/lichess/players.js.map +1 -0
  140. package/dist/adapters/lobsters/read-domain.d.ts +35 -0
  141. package/dist/adapters/lobsters/read-domain.d.ts.map +1 -0
  142. package/dist/adapters/lobsters/read-domain.js +306 -0
  143. package/dist/adapters/lobsters/read-domain.js.map +1 -0
  144. package/dist/adapters/mangadex/web.d.ts +10 -0
  145. package/dist/adapters/mangadex/web.d.ts.map +1 -0
  146. package/dist/adapters/mangadex/web.js +188 -0
  147. package/dist/adapters/mangadex/web.js.map +1 -0
  148. package/dist/adapters/maven/artifact.d.ts +30 -0
  149. package/dist/adapters/maven/artifact.d.ts.map +1 -0
  150. package/dist/adapters/maven/artifact.js +121 -0
  151. package/dist/adapters/maven/artifact.js.map +1 -0
  152. package/dist/adapters/mdn/search.d.ts +11 -0
  153. package/dist/adapters/mdn/search.d.ts.map +1 -0
  154. package/dist/adapters/mdn/search.js +115 -0
  155. package/dist/adapters/mdn/search.js.map +1 -0
  156. package/dist/adapters/medium/tag.d.ts +15 -0
  157. package/dist/adapters/medium/tag.d.ts.map +1 -0
  158. package/dist/adapters/medium/tag.js +148 -0
  159. package/dist/adapters/medium/tag.js.map +1 -0
  160. package/dist/adapters/moegirl/web.d.ts +23 -0
  161. package/dist/adapters/moegirl/web.d.ts.map +1 -0
  162. package/dist/adapters/moegirl/web.js +269 -0
  163. package/dist/adapters/moegirl/web.js.map +1 -0
  164. package/dist/adapters/npm/package.d.ts +32 -0
  165. package/dist/adapters/npm/package.d.ts.map +1 -0
  166. package/dist/adapters/npm/package.js +141 -0
  167. package/dist/adapters/npm/package.js.map +1 -0
  168. package/dist/adapters/nuget/package.d.ts +34 -0
  169. package/dist/adapters/nuget/package.d.ts.map +1 -0
  170. package/dist/adapters/nuget/package.js +135 -0
  171. package/dist/adapters/nuget/package.js.map +1 -0
  172. package/dist/adapters/nvd/cve.d.ts +42 -0
  173. package/dist/adapters/nvd/cve.d.ts.map +1 -0
  174. package/dist/adapters/nvd/cve.js +132 -0
  175. package/dist/adapters/nvd/cve.js.map +1 -0
  176. package/dist/adapters/oeis/sequences.d.ts +14 -0
  177. package/dist/adapters/oeis/sequences.d.ts.map +1 -0
  178. package/dist/adapters/oeis/sequences.js +219 -0
  179. package/dist/adapters/oeis/sequences.js.map +1 -0
  180. package/dist/adapters/openalex/works.d.ts +43 -0
  181. package/dist/adapters/openalex/works.d.ts.map +1 -0
  182. package/dist/adapters/openalex/works.js +267 -0
  183. package/dist/adapters/openalex/works.js.map +1 -0
  184. package/dist/adapters/openfda/records.d.ts +18 -0
  185. package/dist/adapters/openfda/records.d.ts.map +1 -0
  186. package/dist/adapters/openfda/records.js +209 -0
  187. package/dist/adapters/openfda/records.js.map +1 -0
  188. package/dist/adapters/openreview/papers.d.ts +34 -0
  189. package/dist/adapters/openreview/papers.d.ts.map +1 -0
  190. package/dist/adapters/openreview/papers.js +463 -0
  191. package/dist/adapters/openreview/papers.js.map +1 -0
  192. package/dist/adapters/osv/security.d.ts +36 -0
  193. package/dist/adapters/osv/security.d.ts.map +1 -0
  194. package/dist/adapters/osv/security.js +247 -0
  195. package/dist/adapters/osv/security.js.map +1 -0
  196. package/dist/adapters/packagist/package.d.ts +31 -0
  197. package/dist/adapters/packagist/package.d.ts.map +1 -0
  198. package/dist/adapters/packagist/package.js +108 -0
  199. package/dist/adapters/packagist/package.js.map +1 -0
  200. package/dist/adapters/pubmed/articles.d.ts +31 -0
  201. package/dist/adapters/pubmed/articles.d.ts.map +1 -0
  202. package/dist/adapters/pubmed/articles.js +385 -0
  203. package/dist/adapters/pubmed/articles.js.map +1 -0
  204. package/dist/adapters/pypi/package.d.ts +38 -0
  205. package/dist/adapters/pypi/package.d.ts.map +1 -0
  206. package/dist/adapters/pypi/package.js +235 -0
  207. package/dist/adapters/pypi/package.js.map +1 -0
  208. package/dist/adapters/qwen/web.d.ts +26 -0
  209. package/dist/adapters/qwen/web.d.ts.map +1 -0
  210. package/dist/adapters/qwen/web.js +672 -0
  211. package/dist/adapters/qwen/web.js.map +1 -0
  212. package/dist/adapters/reddit/account.d.ts +12 -0
  213. package/dist/adapters/reddit/account.d.ts.map +1 -0
  214. package/dist/adapters/reddit/account.js +409 -0
  215. package/dist/adapters/reddit/account.js.map +1 -0
  216. package/dist/adapters/rednote/web.d.ts +30 -0
  217. package/dist/adapters/rednote/web.d.ts.map +1 -0
  218. package/dist/adapters/rednote/web.js +858 -0
  219. package/dist/adapters/rednote/web.js.map +1 -0
  220. package/dist/adapters/rest-countries/countries.d.ts +14 -0
  221. package/dist/adapters/rest-countries/countries.d.ts.map +1 -0
  222. package/dist/adapters/rest-countries/countries.js +231 -0
  223. package/dist/adapters/rest-countries/countries.js.map +1 -0
  224. package/dist/adapters/reuters/article-detail.d.ts +37 -0
  225. package/dist/adapters/reuters/article-detail.d.ts.map +1 -0
  226. package/dist/adapters/reuters/article-detail.js +139 -0
  227. package/dist/adapters/reuters/article-detail.js.map +1 -0
  228. package/dist/adapters/rfc/rfc.d.ts +11 -0
  229. package/dist/adapters/rfc/rfc.d.ts.map +1 -0
  230. package/dist/adapters/rfc/rfc.js +121 -0
  231. package/dist/adapters/rfc/rfc.js.map +1 -0
  232. package/dist/adapters/rubygems/gem.d.ts +26 -0
  233. package/dist/adapters/rubygems/gem.d.ts.map +1 -0
  234. package/dist/adapters/rubygems/gem.js +96 -0
  235. package/dist/adapters/rubygems/gem.js.map +1 -0
  236. package/dist/adapters/safebooru/web.d.ts +10 -0
  237. package/dist/adapters/safebooru/web.d.ts.map +1 -0
  238. package/dist/adapters/safebooru/web.js +120 -0
  239. package/dist/adapters/safebooru/web.js.map +1 -0
  240. package/dist/adapters/stackoverflow/questions.d.ts +79 -0
  241. package/dist/adapters/stackoverflow/questions.d.ts.map +1 -0
  242. package/dist/adapters/stackoverflow/questions.js +504 -0
  243. package/dist/adapters/stackoverflow/questions.js.map +1 -0
  244. package/dist/adapters/steam/app.d.ts +39 -0
  245. package/dist/adapters/steam/app.d.ts.map +1 -0
  246. package/dist/adapters/steam/app.js +165 -0
  247. package/dist/adapters/steam/app.js.map +1 -0
  248. package/dist/adapters/tiktok/creator-videos.d.ts +52 -0
  249. package/dist/adapters/tiktok/creator-videos.d.ts.map +1 -0
  250. package/dist/adapters/tiktok/creator-videos.js +267 -0
  251. package/dist/adapters/tiktok/creator-videos.js.map +1 -0
  252. package/dist/adapters/tvmaze/shows.d.ts +13 -0
  253. package/dist/adapters/tvmaze/shows.d.ts.map +1 -0
  254. package/dist/adapters/tvmaze/shows.js +240 -0
  255. package/dist/adapters/tvmaze/shows.js.map +1 -0
  256. package/dist/adapters/twitter/bookmark-folders.d.ts +33 -0
  257. package/dist/adapters/twitter/bookmark-folders.d.ts.map +1 -0
  258. package/dist/adapters/twitter/bookmark-folders.js +290 -0
  259. package/dist/adapters/twitter/bookmark-folders.js.map +1 -0
  260. package/dist/adapters/twitter/quote.d.ts +18 -0
  261. package/dist/adapters/twitter/quote.d.ts.map +1 -0
  262. package/dist/adapters/twitter/quote.js +285 -0
  263. package/dist/adapters/twitter/quote.js.map +1 -0
  264. package/dist/adapters/twitter/tweet-actions.d.ts +12 -0
  265. package/dist/adapters/twitter/tweet-actions.d.ts.map +1 -0
  266. package/dist/adapters/twitter/tweet-actions.js +145 -0
  267. package/dist/adapters/twitter/tweet-actions.js.map +1 -0
  268. package/dist/adapters/twitter/tweet-url.d.ts +15 -0
  269. package/dist/adapters/twitter/tweet-url.d.ts.map +1 -0
  270. package/dist/adapters/twitter/tweet-url.js +57 -0
  271. package/dist/adapters/twitter/tweet-url.js.map +1 -0
  272. package/dist/adapters/uisdc/news.d.ts +22 -0
  273. package/dist/adapters/uisdc/news.d.ts.map +1 -0
  274. package/dist/adapters/uisdc/news.js +91 -0
  275. package/dist/adapters/uisdc/news.js.map +1 -0
  276. package/dist/adapters/vndb/web.d.ts +10 -0
  277. package/dist/adapters/vndb/web.d.ts.map +1 -0
  278. package/dist/adapters/vndb/web.js +321 -0
  279. package/dist/adapters/vndb/web.js.map +1 -0
  280. package/dist/adapters/weibo/favorites-publish.d.ts +28 -0
  281. package/dist/adapters/weibo/favorites-publish.d.ts.map +1 -0
  282. package/dist/adapters/weibo/favorites-publish.js +356 -0
  283. package/dist/adapters/weibo/favorites-publish.js.map +1 -0
  284. package/dist/adapters/wikidata/entities.d.ts +15 -0
  285. package/dist/adapters/wikidata/entities.d.ts.map +1 -0
  286. package/dist/adapters/wikidata/entities.js +219 -0
  287. package/dist/adapters/wikidata/entities.js.map +1 -0
  288. package/dist/adapters/wikipedia/page.d.ts +21 -0
  289. package/dist/adapters/wikipedia/page.d.ts.map +1 -0
  290. package/dist/adapters/wikipedia/page.js +116 -0
  291. package/dist/adapters/wikipedia/page.js.map +1 -0
  292. package/dist/adapters/wttr/weather.d.ts +12 -0
  293. package/dist/adapters/wttr/weather.d.ts.map +1 -0
  294. package/dist/adapters/wttr/weather.js +207 -0
  295. package/dist/adapters/wttr/weather.js.map +1 -0
  296. package/dist/adapters/xianyu/publish.d.ts +31 -0
  297. package/dist/adapters/xianyu/publish.d.ts.map +1 -0
  298. package/dist/adapters/xianyu/publish.js +349 -0
  299. package/dist/adapters/xianyu/publish.js.map +1 -0
  300. package/dist/adapters/xiaohongshu/user-helpers.d.ts +2 -2
  301. package/dist/adapters/xiaohongshu/user-helpers.d.ts.map +1 -1
  302. package/dist/adapters/xiaohongshu/user-helpers.js +5 -4
  303. package/dist/adapters/xiaohongshu/user-helpers.js.map +1 -1
  304. package/dist/adapters/yuanbao/web.d.ts +27 -0
  305. package/dist/adapters/yuanbao/web.d.ts.map +1 -0
  306. package/dist/adapters/yuanbao/web.js +365 -0
  307. package/dist/adapters/yuanbao/web.js.map +1 -0
  308. package/dist/adapters/zhihu/collection.d.ts +33 -0
  309. package/dist/adapters/zhihu/collection.d.ts.map +1 -0
  310. package/dist/adapters/zhihu/collection.js +185 -0
  311. package/dist/adapters/zhihu/collection.js.map +1 -0
  312. package/dist/adapters/zlibrary/web.d.ts +19 -0
  313. package/dist/adapters/zlibrary/web.d.ts.map +1 -0
  314. package/dist/adapters/zlibrary/web.js +153 -0
  315. package/dist/adapters/zlibrary/web.js.map +1 -0
  316. package/dist/agents/codex-pack.d.ts +62 -0
  317. package/dist/agents/codex-pack.d.ts.map +1 -0
  318. package/dist/agents/codex-pack.js +163 -0
  319. package/dist/agents/codex-pack.js.map +1 -0
  320. package/dist/browser/daemon-client.js +2 -2
  321. package/dist/browser/daemon-client.js.map +1 -1
  322. package/dist/commands/agents.d.ts.map +1 -1
  323. package/dist/commands/agents.js +6 -43
  324. package/dist/commands/agents.js.map +1 -1
  325. package/dist/commands/browser/adapter.d.ts.map +1 -1
  326. package/dist/commands/browser/adapter.js +17 -3
  327. package/dist/commands/browser/adapter.js.map +1 -1
  328. package/dist/commands/describe.d.ts.map +1 -1
  329. package/dist/commands/describe.js +6 -7
  330. package/dist/commands/describe.js.map +1 -1
  331. package/dist/commands/dispatch.d.ts +1 -1
  332. package/dist/commands/dispatch.d.ts.map +1 -1
  333. package/dist/commands/dispatch.js +4 -2
  334. package/dist/commands/dispatch.js.map +1 -1
  335. package/dist/commands/mcp.d.ts +1 -1
  336. package/dist/commands/mcp.d.ts.map +1 -1
  337. package/dist/commands/mcp.js +10 -5
  338. package/dist/commands/mcp.js.map +1 -1
  339. package/dist/core/command-contract-lint.d.ts +10 -0
  340. package/dist/core/command-contract-lint.d.ts.map +1 -0
  341. package/dist/core/command-contract-lint.js +41 -0
  342. package/dist/core/command-contract-lint.js.map +1 -0
  343. package/dist/core/command-contract.d.ts +100 -0
  344. package/dist/core/command-contract.d.ts.map +1 -0
  345. package/dist/core/command-contract.js +174 -0
  346. package/dist/core/command-contract.js.map +1 -0
  347. package/dist/core/index.d.ts +2 -0
  348. package/dist/core/index.d.ts.map +1 -1
  349. package/dist/core/index.js +2 -0
  350. package/dist/core/index.js.map +1 -1
  351. package/dist/discovery/aliases.d.ts +2 -2
  352. package/dist/discovery/aliases.d.ts.map +1 -1
  353. package/dist/discovery/aliases.js +464 -6
  354. package/dist/discovery/aliases.js.map +1 -1
  355. package/dist/discovery/macos-dynamic.d.ts.map +1 -1
  356. package/dist/discovery/macos-dynamic.js +17 -3
  357. package/dist/discovery/macos-dynamic.js.map +1 -1
  358. package/dist/discovery/search.d.ts.map +1 -1
  359. package/dist/discovery/search.js +147 -2
  360. package/dist/discovery/search.js.map +1 -1
  361. package/dist/engine/args.d.ts.map +1 -1
  362. package/dist/engine/args.js +18 -1
  363. package/dist/engine/args.js.map +1 -1
  364. package/dist/engine/artifact-validation.d.ts +29 -0
  365. package/dist/engine/artifact-validation.d.ts.map +1 -0
  366. package/dist/engine/artifact-validation.js +211 -0
  367. package/dist/engine/artifact-validation.js.map +1 -0
  368. package/dist/engine/browser/diagnostics.d.ts +38 -0
  369. package/dist/engine/browser/diagnostics.d.ts.map +1 -0
  370. package/dist/engine/browser/diagnostics.js +40 -0
  371. package/dist/engine/browser/diagnostics.js.map +1 -0
  372. package/dist/engine/invoke.d.ts +1 -0
  373. package/dist/engine/invoke.d.ts.map +1 -1
  374. package/dist/engine/invoke.js +1 -0
  375. package/dist/engine/invoke.js.map +1 -1
  376. package/dist/engine/kernel/errors.d.ts +11 -0
  377. package/dist/engine/kernel/errors.d.ts.map +1 -0
  378. package/dist/engine/kernel/errors.js +15 -0
  379. package/dist/engine/kernel/errors.js.map +1 -0
  380. package/dist/engine/kernel/execute.d.ts +7 -18
  381. package/dist/engine/kernel/execute.d.ts.map +1 -1
  382. package/dist/engine/kernel/execute.js +25 -410
  383. package/dist/engine/kernel/execute.js.map +1 -1
  384. package/dist/engine/kernel/stages.d.ts +44 -0
  385. package/dist/engine/kernel/stages.d.ts.map +1 -0
  386. package/dist/engine/kernel/stages.js +428 -0
  387. package/dist/engine/kernel/stages.js.map +1 -0
  388. package/dist/engine/kernel/types.d.ts +21 -1
  389. package/dist/engine/kernel/types.d.ts.map +1 -1
  390. package/dist/engine/steps/download.d.ts +1 -0
  391. package/dist/engine/steps/download.d.ts.map +1 -1
  392. package/dist/engine/steps/download.js +10 -6
  393. package/dist/engine/steps/download.js.map +1 -1
  394. package/dist/fast-path/render.js +1 -1
  395. package/dist/fast-path/render.js.map +1 -1
  396. package/dist/manifest-compact.txt +10 -10
  397. package/dist/manifest-search.json +1 -1
  398. package/dist/manifest.json +8421 -332
  399. package/dist/mcp/handler.d.ts.map +1 -1
  400. package/dist/mcp/handler.js +11 -1
  401. package/dist/mcp/handler.js.map +1 -1
  402. package/dist/mcp/server.d.ts +1 -1
  403. package/dist/mcp/server.js +1 -1
  404. package/dist/mcp/tools.d.ts.map +1 -1
  405. package/dist/mcp/tools.js +18 -10
  406. package/dist/mcp/tools.js.map +1 -1
  407. package/dist/output/error-map.d.ts.map +1 -1
  408. package/dist/output/error-map.js +1 -1
  409. package/dist/output/error-map.js.map +1 -1
  410. package/dist/registry.d.ts.map +1 -1
  411. package/dist/registry.js +2 -1
  412. package/dist/registry.js.map +1 -1
  413. package/dist/transport/refs.d.ts +5 -3
  414. package/dist/transport/refs.d.ts.map +1 -1
  415. package/dist/transport/refs.js +8 -1
  416. package/dist/transport/refs.js.map +1 -1
  417. package/dist/transport/sidecar-binary.d.ts +7 -0
  418. package/dist/transport/sidecar-binary.d.ts.map +1 -1
  419. package/dist/transport/sidecar-binary.js +28 -8
  420. package/dist/transport/sidecar-binary.js.map +1 -1
  421. package/package.json +4 -3
  422. package/server.json +3 -3
  423. package/skills/unicli/SKILL.md +1 -1
  424. package/skills/unicli-claude-code/SKILL.md +1 -1
  425. package/skills/unicli-hermes/SKILL.md +1 -1
  426. package/src/adapters/1point3acres/forum.test.ts +300 -0
  427. package/src/adapters/1point3acres/forum.ts +852 -0
  428. package/src/adapters/aibase/news.test.ts +42 -0
  429. package/src/adapters/aibase/news.ts +118 -0
  430. package/src/adapters/anilist/web.test.ts +93 -0
  431. package/src/adapters/anilist/web.ts +341 -0
  432. package/src/adapters/arxiv/download.yaml +53 -0
  433. package/src/adapters/arxiv/papers.test.ts +59 -0
  434. package/src/adapters/arxiv/papers.ts +226 -0
  435. package/src/adapters/bangumi/web.test.ts +109 -0
  436. package/src/adapters/bangumi/web.ts +295 -0
  437. package/src/adapters/bbc/topic.test.ts +52 -0
  438. package/src/adapters/bbc/topic.ts +149 -0
  439. package/src/adapters/chatgpt/web.test.ts +121 -0
  440. package/src/adapters/chatgpt/web.ts +286 -0
  441. package/src/adapters/claude/web.test.ts +206 -0
  442. package/src/adapters/claude/web.ts +684 -0
  443. package/src/adapters/codex/projects.test.ts +77 -0
  444. package/src/adapters/codex/projects.ts +178 -0
  445. package/src/adapters/coingecko/markets.test.ts +156 -0
  446. package/src/adapters/coingecko/markets.ts +574 -0
  447. package/src/adapters/coupang/product.test.ts +111 -0
  448. package/src/adapters/coupang/product.ts +256 -0
  449. package/src/adapters/crates/registry.test.ts +89 -0
  450. package/src/adapters/crates/registry.ts +247 -0
  451. package/src/adapters/ctrip/travel.test.ts +359 -0
  452. package/src/adapters/ctrip/travel.ts +792 -0
  453. package/src/adapters/danbooru/artists.yaml +44 -0
  454. package/src/adapters/danbooru/comments.yaml +45 -0
  455. package/src/adapters/danbooru/detail.yaml +78 -0
  456. package/src/adapters/danbooru/download.yaml +51 -0
  457. package/src/adapters/danbooru/pools.yaml +56 -0
  458. package/src/adapters/danbooru/search.yaml +69 -0
  459. package/src/adapters/danbooru/tags.yaml +42 -0
  460. package/src/adapters/danbooru/wiki.yaml +44 -0
  461. package/src/adapters/dblp/publications.test.ts +123 -0
  462. package/src/adapters/dblp/publications.ts +494 -0
  463. package/src/adapters/deepseek/web.test.ts +69 -0
  464. package/src/adapters/deepseek/web.ts +78 -1
  465. package/src/adapters/defillama/protocols.test.ts +75 -0
  466. package/src/adapters/defillama/protocols.ts +253 -0
  467. package/src/adapters/devto/read.test.ts +49 -0
  468. package/src/adapters/devto/read.ts +145 -0
  469. package/src/adapters/dianping/shop.test.ts +134 -0
  470. package/src/adapters/dianping/shop.ts +261 -0
  471. package/src/adapters/dlsite/web.test.ts +132 -0
  472. package/src/adapters/dlsite/web.ts +557 -0
  473. package/src/adapters/dockerhub/registry.test.ts +97 -0
  474. package/src/adapters/dockerhub/registry.ts +223 -0
  475. package/src/adapters/ehentai/web.test.ts +157 -0
  476. package/src/adapters/ehentai/web.ts +750 -0
  477. package/src/adapters/endoflife/product.test.ts +50 -0
  478. package/src/adapters/endoflife/product.ts +128 -0
  479. package/src/adapters/facebook/marketplace-extra.test.ts +132 -0
  480. package/src/adapters/facebook/marketplace-extra.ts +213 -0
  481. package/src/adapters/flathub/apps.test.ts +85 -0
  482. package/src/adapters/flathub/apps.ts +254 -0
  483. package/src/adapters/goproxy/module.test.ts +72 -0
  484. package/src/adapters/goproxy/module.ts +258 -0
  485. package/src/adapters/grok/web.test.ts +181 -0
  486. package/src/adapters/grok/web.ts +640 -0
  487. package/src/adapters/hackernews/read.test.ts +68 -0
  488. package/src/adapters/hackernews/read.ts +265 -0
  489. package/src/adapters/hf/paper.test.ts +48 -0
  490. package/src/adapters/hf/paper.ts +138 -0
  491. package/src/adapters/homebrew/packages.test.ts +109 -0
  492. package/src/adapters/homebrew/packages.ts +304 -0
  493. package/src/adapters/indeed/jobs.test.ts +230 -0
  494. package/src/adapters/indeed/jobs.ts +375 -0
  495. package/src/adapters/instagram/collections.test.ts +94 -0
  496. package/src/adapters/instagram/collections.ts +206 -0
  497. package/src/adapters/jikan/web.test.ts +50 -0
  498. package/src/adapters/jikan/web.ts +177 -0
  499. package/src/adapters/kitsu/web.test.ts +29 -0
  500. package/src/adapters/kitsu/web.ts +109 -0
  501. package/src/adapters/konachan/detail.yaml +62 -0
  502. package/src/adapters/konachan/download.yaml +55 -0
  503. package/src/adapters/konachan/search.yaml +65 -0
  504. package/src/adapters/konachan/tags.yaml +40 -0
  505. package/src/adapters/lichess/players.test.ts +99 -0
  506. package/src/adapters/lichess/players.ts +277 -0
  507. package/src/adapters/lobsters/read-domain.test.ts +121 -0
  508. package/src/adapters/lobsters/read-domain.ts +400 -0
  509. package/src/adapters/mangadex/web.test.ts +46 -0
  510. package/src/adapters/mangadex/web.ts +210 -0
  511. package/src/adapters/maven/artifact.test.ts +67 -0
  512. package/src/adapters/maven/artifact.ts +155 -0
  513. package/src/adapters/mdn/search.test.ts +39 -0
  514. package/src/adapters/mdn/search.ts +133 -0
  515. package/src/adapters/medium/tag.test.ts +64 -0
  516. package/src/adapters/medium/tag.ts +164 -0
  517. package/src/adapters/moegirl/web.test.ts +87 -0
  518. package/src/adapters/moegirl/web.ts +343 -0
  519. package/src/adapters/npm/package.test.ts +53 -0
  520. package/src/adapters/npm/package.ts +177 -0
  521. package/src/adapters/nuget/package.test.ts +102 -0
  522. package/src/adapters/nuget/package.ts +193 -0
  523. package/src/adapters/nvd/cve.test.ts +66 -0
  524. package/src/adapters/nvd/cve.ts +182 -0
  525. package/src/adapters/oeis/sequences.test.ts +71 -0
  526. package/src/adapters/oeis/sequences.ts +234 -0
  527. package/src/adapters/openalex/works.test.ts +99 -0
  528. package/src/adapters/openalex/works.ts +319 -0
  529. package/src/adapters/openfda/records.test.ts +90 -0
  530. package/src/adapters/openfda/records.ts +239 -0
  531. package/src/adapters/openreview/papers.test.ts +139 -0
  532. package/src/adapters/openreview/papers.ts +560 -0
  533. package/src/adapters/osv/security.test.ts +91 -0
  534. package/src/adapters/osv/security.ts +298 -0
  535. package/src/adapters/packagist/package.test.ts +62 -0
  536. package/src/adapters/packagist/package.ts +146 -0
  537. package/src/adapters/pdf/read.yaml +49 -0
  538. package/src/adapters/pixiv/download.yaml +15 -2
  539. package/src/adapters/pubmed/articles.test.ts +96 -0
  540. package/src/adapters/pubmed/articles.ts +497 -0
  541. package/src/adapters/pypi/package.test.ts +131 -0
  542. package/src/adapters/pypi/package.ts +297 -0
  543. package/src/adapters/qwen/web.test.ts +176 -0
  544. package/src/adapters/qwen/web.ts +758 -0
  545. package/src/adapters/reddit/account.test.ts +56 -0
  546. package/src/adapters/reddit/account.ts +493 -0
  547. package/src/adapters/rednote/web.test.ts +354 -0
  548. package/src/adapters/rednote/web.ts +968 -0
  549. package/src/adapters/rest-countries/countries.test.ts +80 -0
  550. package/src/adapters/rest-countries/countries.ts +271 -0
  551. package/src/adapters/reuters/article-detail.test.ts +65 -0
  552. package/src/adapters/reuters/article-detail.ts +186 -0
  553. package/src/adapters/rfc/rfc.test.ts +37 -0
  554. package/src/adapters/rfc/rfc.ts +133 -0
  555. package/src/adapters/rubygems/gem.test.ts +43 -0
  556. package/src/adapters/rubygems/gem.ts +126 -0
  557. package/src/adapters/safebooru/detail.yaml +63 -0
  558. package/src/adapters/safebooru/download.yaml +58 -0
  559. package/src/adapters/safebooru/search.yaml +69 -0
  560. package/src/adapters/safebooru/web.test.ts +60 -0
  561. package/src/adapters/safebooru/web.ts +130 -0
  562. package/src/adapters/stackoverflow/questions.test.ts +207 -0
  563. package/src/adapters/stackoverflow/questions.ts +765 -0
  564. package/src/adapters/steam/app.test.ts +68 -0
  565. package/src/adapters/steam/app.ts +218 -0
  566. package/src/adapters/tiktok/creator-videos.test.ts +158 -0
  567. package/src/adapters/tiktok/creator-videos.ts +370 -0
  568. package/src/adapters/tvmaze/shows.test.ts +93 -0
  569. package/src/adapters/tvmaze/shows.ts +271 -0
  570. package/src/adapters/twitter/bookmark-folders.test.ts +164 -0
  571. package/src/adapters/twitter/bookmark-folders.ts +366 -0
  572. package/src/adapters/twitter/quote.test.ts +157 -0
  573. package/src/adapters/twitter/quote.ts +332 -0
  574. package/src/adapters/twitter/tweet-actions.test.ts +51 -0
  575. package/src/adapters/twitter/tweet-actions.ts +187 -0
  576. package/src/adapters/twitter/tweet-url.ts +65 -0
  577. package/src/adapters/uisdc/news.test.ts +46 -0
  578. package/src/adapters/uisdc/news.ts +111 -0
  579. package/src/adapters/vndb/web.test.ts +86 -0
  580. package/src/adapters/vndb/web.ts +393 -0
  581. package/src/adapters/weibo/favorites-publish.test.ts +177 -0
  582. package/src/adapters/weibo/favorites-publish.ts +426 -0
  583. package/src/adapters/wikidata/entities.test.ts +103 -0
  584. package/src/adapters/wikidata/entities.ts +253 -0
  585. package/src/adapters/wikipedia/page.test.ts +49 -0
  586. package/src/adapters/wikipedia/page.ts +158 -0
  587. package/src/adapters/wttr/weather.test.ts +99 -0
  588. package/src/adapters/wttr/weather.ts +239 -0
  589. package/src/adapters/xianyu/publish.test.ts +210 -0
  590. package/src/adapters/xianyu/publish.ts +420 -0
  591. package/src/adapters/xiaohongshu/user-helpers.ts +5 -2
  592. package/src/adapters/yandere/detail.yaml +61 -0
  593. package/src/adapters/yandere/download.yaml +56 -0
  594. package/src/adapters/yandere/search.yaml +67 -0
  595. package/src/adapters/yandere/tags.yaml +41 -0
  596. package/src/adapters/yuanbao/web.test.ts +144 -0
  597. package/src/adapters/yuanbao/web.ts +435 -0
  598. package/src/adapters/zhihu/collection.test.ts +96 -0
  599. package/src/adapters/zhihu/collection.ts +239 -0
  600. package/src/adapters/zlibrary/web.test.ts +104 -0
  601. package/src/adapters/zlibrary/web.ts +192 -0
@@ -0,0 +1,858 @@
1
+ /**
2
+ * @owner src/adapters/rednote/web.ts
3
+ * @does Register agent-facing Rednote read, notification, and media commands.
4
+ * @needs Logged-in or challenge-cleared www.rednote.com browser session plus stable Rednote DOM/Pinia stores.
5
+ * @feeds surface coverage ledger, Rednote research workflows, and media download automation.
6
+ * @breaks Rednote route drift, Pinia store reshaping, note-card DOM drift, CDN auth changes, or risk-control blocks.
7
+ */
8
+ import { join } from "node:path";
9
+ import { formatCookieHeader } from "../../engine/cookies.js";
10
+ import { generateFilename, httpDownload, mapConcurrent, } from "../../engine/download.js";
11
+ import { cli, Strategy } from "../../registry.js";
12
+ import { extractXhsUserNotes, normalizeXhsUserId, } from "../xiaohongshu/user-helpers.js";
13
+ const REDNOTE_HOST = "www.rednote.com";
14
+ const REDNOTE_ORIGIN = `https://${REDNOTE_HOST}`;
15
+ const SIGNED_URL_HINT = "Pass a full rednote.com note URL with xsec_token from search results or user/profile context.";
16
+ export const REDNOTE_NOTE_COLUMNS = ["field", "value"];
17
+ export const REDNOTE_SEARCH_COLUMNS = [
18
+ "rank",
19
+ "title",
20
+ "author",
21
+ "likes",
22
+ "published_at",
23
+ "url",
24
+ "author_url",
25
+ ];
26
+ export const REDNOTE_USER_COLUMNS = ["id", "title", "type", "likes", "url"];
27
+ export const REDNOTE_COMMENT_COLUMNS = [
28
+ "rank",
29
+ "author",
30
+ "text",
31
+ "likes",
32
+ "time",
33
+ "is_reply",
34
+ "reply_to",
35
+ ];
36
+ export const REDNOTE_FEED_COLUMNS = [
37
+ "id",
38
+ "title",
39
+ "author",
40
+ "likes",
41
+ "type",
42
+ "url",
43
+ ];
44
+ export const REDNOTE_NOTIFICATION_COLUMNS = [
45
+ "rank",
46
+ "user",
47
+ "action",
48
+ "content",
49
+ "note",
50
+ "time",
51
+ ];
52
+ export const REDNOTE_DOWNLOAD_COLUMNS = [
53
+ "index",
54
+ "type",
55
+ "status",
56
+ "path",
57
+ "size",
58
+ "url",
59
+ "error",
60
+ ];
61
+ function cleanString(value) {
62
+ return typeof value === "string"
63
+ ? value.trim()
64
+ : value == null
65
+ ? ""
66
+ : String(value).trim();
67
+ }
68
+ export function parseRednoteLimit(raw, fallback, max) {
69
+ const parsed = Number(raw ?? fallback);
70
+ if (!Number.isFinite(parsed) || !Number.isInteger(parsed)) {
71
+ throw new Error(`--limit must be a positive integer, got ${JSON.stringify(raw)}`);
72
+ }
73
+ if (parsed < 1) {
74
+ throw new Error(`--limit must be a positive integer, got ${parsed}`);
75
+ }
76
+ if (max !== undefined && parsed > max) {
77
+ throw new Error(`--limit must be between 1 and ${max}, got ${parsed}`);
78
+ }
79
+ return parsed;
80
+ }
81
+ export function parseRednoteSearchLimit(raw) {
82
+ const parsed = Number(raw ?? 20);
83
+ if (!Number.isFinite(parsed) || !Number.isInteger(parsed)) {
84
+ throw new Error(`--limit must be an integer between 1 and 100, got ${JSON.stringify(raw)}`);
85
+ }
86
+ if (parsed < 1 || parsed > 100) {
87
+ throw new Error(`--limit must be between 1 and 100, got ${parsed}`);
88
+ }
89
+ return parsed;
90
+ }
91
+ export function parseRednoteNotificationType(raw) {
92
+ const type = cleanString(raw ?? "mentions");
93
+ if (!["mentions", "likes", "connections"].includes(type)) {
94
+ throw new Error(`--type must be one of mentions, likes, or connections, got ${JSON.stringify(raw)}`);
95
+ }
96
+ return type;
97
+ }
98
+ function requireRednoteHost(input, commandName) {
99
+ let url;
100
+ try {
101
+ url = new URL(input);
102
+ }
103
+ catch {
104
+ throw new Error(`${commandName}: invalid URL. ${SIGNED_URL_HINT}`);
105
+ }
106
+ if (!url.hostname.endsWith("rednote.com")) {
107
+ throw new Error(`${commandName}: URL must be on rednote.com. ${SIGNED_URL_HINT}`);
108
+ }
109
+ return url;
110
+ }
111
+ export function parseRednoteNoteId(input) {
112
+ const trimmed = cleanString(input);
113
+ if (!trimmed)
114
+ throw new Error("note-id cannot be empty");
115
+ if (/^https?:\/\//i.test(trimmed)) {
116
+ const url = requireRednoteHost(trimmed, "rednote note");
117
+ const match = url.pathname.match(/\/(?:explore|note|search_result)\/([a-zA-Z0-9]+)/);
118
+ if (!match?.[1]) {
119
+ throw new Error(`rednote note: URL does not contain a note id. ${SIGNED_URL_HINT}`);
120
+ }
121
+ return match[1];
122
+ }
123
+ if (/[/?#]/.test(trimmed)) {
124
+ throw new Error(`rednote note: note-id must be a bare id or rednote.com URL. ${SIGNED_URL_HINT}`);
125
+ }
126
+ return trimmed;
127
+ }
128
+ export function buildRednoteNoteUrl(input, commandName = "rednote note") {
129
+ const trimmed = cleanString(input);
130
+ if (/^https?:\/\//i.test(trimmed)) {
131
+ return requireRednoteHost(trimmed, commandName).toString();
132
+ }
133
+ return `${REDNOTE_ORIGIN}/explore/${parseRednoteNoteId(trimmed)}`;
134
+ }
135
+ export function normalizeRednoteUserId(input) {
136
+ const trimmed = cleanString(input);
137
+ if (!trimmed)
138
+ throw new Error("id cannot be empty");
139
+ if (/^https?:\/\//i.test(trimmed)) {
140
+ const url = requireRednoteHost(trimmed, "rednote user");
141
+ const matched = url.pathname.match(/\/user\/profile\/([a-zA-Z0-9]+)/);
142
+ if (!matched?.[1])
143
+ throw new Error("rednote user: URL does not contain a user id");
144
+ return matched[1];
145
+ }
146
+ return normalizeXhsUserId(trimmed);
147
+ }
148
+ export function rednoteNoteIdToDate(url) {
149
+ const match = url.match(/\/(?:search_result|explore|note)\/([0-9a-f]{24})(?=[?#/]|$)/i);
150
+ if (!match)
151
+ return "";
152
+ const timestamp = parseInt(match[1].slice(0, 8), 16);
153
+ if (!timestamp || timestamp < 1_000_000_000 || timestamp > 4_000_000_000)
154
+ return "";
155
+ return new Date((timestamp + 8 * 3600) * 1000).toISOString().slice(0, 10);
156
+ }
157
+ export function buildRednoteSearchWaitScript() {
158
+ return `
159
+ new Promise((resolve) => {
160
+ const hasLoginModal = () => {
161
+ const candidates = document.querySelectorAll('[class*="login-modal"], [class*="LoginModal"], [class*="login-container"], [class*="LoginContainer"], dialog[role="dialog"]');
162
+ for (const el of candidates) {
163
+ if (!(el instanceof HTMLElement)) continue;
164
+ const rect = el.getBoundingClientRect();
165
+ if (rect.width <= 0 || rect.height <= 0) continue;
166
+ const style = getComputedStyle(el);
167
+ if (style.display === 'none' || style.visibility === 'hidden') continue;
168
+ return true;
169
+ }
170
+ return false;
171
+ };
172
+ const detect = () => {
173
+ if (document.querySelector('section.note-item')) return 'content';
174
+ if (/登录后查看搜索结果|请登录/.test(document.body?.innerText || '')) return 'login_wall';
175
+ if (hasLoginModal()) return 'login_wall';
176
+ return null;
177
+ };
178
+ const found = detect();
179
+ if (found) return resolve(found);
180
+ const observer = new MutationObserver(() => {
181
+ const result = detect();
182
+ if (result) { observer.disconnect(); resolve(result); }
183
+ });
184
+ observer.observe(document.body, { childList: true, subtree: true });
185
+ setTimeout(() => { observer.disconnect(); resolve('timeout'); }, 5000);
186
+ })
187
+ `;
188
+ }
189
+ export function buildRednoteScrollUntilScript(limit) {
190
+ return `
191
+ (async () => {
192
+ const target = ${JSON.stringify(limit)};
193
+ let previous = -1;
194
+ let stable = 0;
195
+ for (let i = 0; i < 10; i += 1) {
196
+ const count = document.querySelectorAll('section.note-item').length;
197
+ if (count >= target) return { count, stable: false };
198
+ if (count === previous) stable += 1;
199
+ else stable = 0;
200
+ if (stable >= 2) return { count, stable: true };
201
+ previous = count;
202
+ window.scrollTo(0, document.body.scrollHeight);
203
+ await new Promise((resolve) => setTimeout(resolve, 900));
204
+ }
205
+ return { count: document.querySelectorAll('section.note-item').length, stable: true };
206
+ })()
207
+ `;
208
+ }
209
+ export function buildRednoteSearchExtractScript() {
210
+ return `
211
+ (() => {
212
+ const normalizeUrl = (href) => {
213
+ if (!href) return '';
214
+ if (href.startsWith('http://') || href.startsWith('https://')) return href;
215
+ if (href.startsWith('/')) return '${REDNOTE_ORIGIN}' + href;
216
+ return '';
217
+ };
218
+ const cleanText = (value) => (value || '').replace(/\\s+/g, ' ').trim();
219
+ const results = [];
220
+ const seen = new Set();
221
+ document.querySelectorAll('section.note-item').forEach((el) => {
222
+ if (el.classList.contains('query-note-item')) return;
223
+ const titleEl = el.querySelector('.title, .note-title, a.title, .footer .title span');
224
+ const authorEl = el.querySelector('a.author .name, .name, .author-name, .nick-name, a.author');
225
+ const likesEl = el.querySelector('.count, .like-count, .like-wrapper .count');
226
+ const detailLinkEl = el.querySelector('a.cover.mask') || el.querySelector('a[href*="/search_result/"]') || el.querySelector('a[href*="/explore/"]') || el.querySelector('a[href*="/note/"]');
227
+ const authorLinkEl = el.querySelector('a.author[href*="/user/profile/"], a[href*="/user/profile/"]');
228
+ const url = normalizeUrl(detailLinkEl?.getAttribute('href') || '');
229
+ if (!url || seen.has(url)) return;
230
+ seen.add(url);
231
+ results.push({
232
+ title: cleanText(titleEl?.textContent || ''),
233
+ author: cleanText(authorEl?.textContent || ''),
234
+ likes: cleanText(likesEl?.textContent || '0'),
235
+ url,
236
+ author_url: normalizeUrl(authorLinkEl?.getAttribute('href') || ''),
237
+ });
238
+ });
239
+ return results;
240
+ })()
241
+ `;
242
+ }
243
+ export function buildRednoteNoteExtractScript() {
244
+ return `
245
+ (() => {
246
+ const body = document.body?.innerText || '';
247
+ const loginWall = /登录后查看|请登录|Log in to view/.test(body);
248
+ const notFound = /页面不见了|笔记不存在|无法浏览|not found|unavailable/i.test(body);
249
+ const securityBlock = /访问频繁|安全验证|risk control|Security Verification|Access Denied/i.test(body);
250
+ const clean = (el) => (el?.textContent || '').replace(/\\s+/g, ' ').trim();
251
+ const title = clean(document.querySelector('#detail-title, .title'));
252
+ const desc = clean(document.querySelector('#detail-desc, .desc, .note-text'));
253
+ const author = clean(document.querySelector('.username, .author-wrapper .name, .user-name'));
254
+ const likes = clean(document.querySelector('.like-wrapper .count, [class*="like"] .count'));
255
+ const collects = clean(document.querySelector('.collect-wrapper .count, [class*="collect"] .count'));
256
+ const comments = clean(document.querySelector('.chat-wrapper .count, [class*="comment"] .count'));
257
+ const tags = [];
258
+ document.querySelectorAll('#detail-desc a.tag, #detail-desc a[href*="search_result"], a[href*="search_result?keyword="]').forEach((el) => {
259
+ const text = clean(el);
260
+ if (text) tags.push(text);
261
+ });
262
+ return { loginWall, notFound, securityBlock, title, desc, author, likes, collects, comments, tags };
263
+ })()
264
+ `;
265
+ }
266
+ export function buildRednoteCommentsExtractScript(withReplies) {
267
+ return `
268
+ (async () => {
269
+ const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
270
+ const body = document.body?.innerText || '';
271
+ const loginWall = /登录后查看|请登录|Log in to view/.test(body);
272
+ const securityBlock = /访问频繁|安全验证|risk control|Security Verification|Access Denied/i.test(body);
273
+ const scroller = document.querySelector('.note-scroller') || document.querySelector('.container') || document.scrollingElement;
274
+ if (scroller) {
275
+ for (let i = 0; i < 3; i += 1) {
276
+ scroller.scrollTo(0, scroller.scrollHeight);
277
+ await wait(700);
278
+ }
279
+ }
280
+ const clean = (el) => (el?.textContent || '').replace(/\\s+/g, ' ').trim();
281
+ const parseLikes = (el) => {
282
+ const raw = clean(el);
283
+ return /^\\d+$/.test(raw) ? Number(raw) : 0;
284
+ };
285
+ const expandReplyThreads = async (root) => {
286
+ if (!${JSON.stringify(withReplies)} || !root) return;
287
+ const clickedTexts = new Set();
288
+ for (let round = 0; round < 3; round += 1) {
289
+ const expanders = Array.from(root.querySelectorAll('button, [role="button"], span, div')).filter((el) => {
290
+ if (!(el instanceof HTMLElement)) return false;
291
+ const text = clean(el);
292
+ if (!text || text.length > 24) return false;
293
+ if (!/(展开|更多回复|全部回复|查看.*回复|共\\d+条回复)/.test(text)) return false;
294
+ if (clickedTexts.has(text)) return false;
295
+ return true;
296
+ });
297
+ if (!expanders.length) break;
298
+ for (const el of expanders) {
299
+ const text = clean(el);
300
+ el.click();
301
+ clickedTexts.add(text);
302
+ await wait(300);
303
+ }
304
+ }
305
+ };
306
+ const results = [];
307
+ const parents = document.querySelectorAll('.parent-comment, .comment-item');
308
+ for (const parent of parents) {
309
+ const item = parent.matches?.('.comment-item') ? parent : parent.querySelector('.comment-item');
310
+ if (!item) continue;
311
+ const author = clean(item.querySelector('.author-wrapper .name, .user-name, .name'));
312
+ const text = clean(item.querySelector('.content, .note-text, .comment-content'));
313
+ const likes = parseLikes(item.querySelector('.count, .like-count'));
314
+ const time = clean(item.querySelector('.date, .time'));
315
+ if (!text) continue;
316
+ results.push({ author, text, likes, time, is_reply: false, reply_to: '' });
317
+ if (${JSON.stringify(withReplies)}) {
318
+ await expandReplyThreads(parent);
319
+ parent.querySelectorAll('.reply-container .comment-item-sub, .sub-comment-list .comment-item, .reply-item').forEach((sub) => {
320
+ const sAuthor = clean(sub.querySelector('.name, .user-name'));
321
+ const sText = clean(sub.querySelector('.content, .note-text, .comment-content'));
322
+ const sLikes = parseLikes(sub.querySelector('.count, .like-count'));
323
+ const sTime = clean(sub.querySelector('.date, .time'));
324
+ if (!sText) return;
325
+ results.push({ author: sAuthor, text: sText, likes: sLikes, time: sTime, is_reply: true, reply_to: author });
326
+ });
327
+ }
328
+ }
329
+ return { loginWall, securityBlock, results };
330
+ })()
331
+ `;
332
+ }
333
+ export function buildRednoteFeedReadScript() {
334
+ return `
335
+ (() => {
336
+ let pinia = null;
337
+ const probe = (el) => el?.__vue_app__?.config?.globalProperties?.$pinia ?? null;
338
+ pinia = probe(document.querySelector('#app'));
339
+ if (!pinia) {
340
+ for (const el of document.querySelectorAll('*')) {
341
+ pinia = probe(el);
342
+ if (pinia) break;
343
+ }
344
+ }
345
+ if (!pinia || !pinia._s) return { error: 'no_pinia' };
346
+ const store = pinia._s.get('feed');
347
+ if (!store) return { error: 'no_feed_store' };
348
+ const feeds = store.feeds;
349
+ if (!Array.isArray(feeds)) return { error: 'feeds_not_array' };
350
+ return {
351
+ items: feeds.map((entry) => {
352
+ const card = entry?.noteCard ?? {};
353
+ return {
354
+ id: entry?.id ?? '',
355
+ title: card.displayTitle ?? '',
356
+ type: card.type ?? '',
357
+ author: card.user?.nickName ?? card.user?.nickname ?? '',
358
+ likes: card.interactInfo?.likedCount ?? '',
359
+ };
360
+ }),
361
+ };
362
+ })()
363
+ `;
364
+ }
365
+ export function buildRednoteNotificationsReadScript(type) {
366
+ return `
367
+ (async () => {
368
+ const type = ${JSON.stringify(type)};
369
+ let pinia = null;
370
+ const probe = (el) => el?.__vue_app__?.config?.globalProperties?.$pinia ?? null;
371
+ pinia = probe(document.querySelector('#app'));
372
+ if (!pinia) {
373
+ for (const el of document.querySelectorAll('*')) {
374
+ pinia = probe(el);
375
+ if (pinia) break;
376
+ }
377
+ }
378
+ if (!pinia || !pinia._s) return { error: 'no_pinia' };
379
+ const store = pinia._s.get('notification');
380
+ if (!store) return { error: 'no_notification_store' };
381
+ if (typeof store.getNotification !== 'function') return { error: 'no_getNotification_action' };
382
+ try {
383
+ await store.getNotification(type);
384
+ } catch (e) {
385
+ return { error: 'action_failed', detail: e?.message };
386
+ }
387
+ const readMessages = () => {
388
+ if (Array.isArray(store.activeTabMessageList) && store.activeTabMessageList.length > 0) return store.activeTabMessageList;
389
+ const tab = store.notificationMap?.[type];
390
+ if (Array.isArray(tab) && tab.length > 0) return tab;
391
+ if (Array.isArray(tab?.messages) && tab.messages.length > 0) return tab.messages;
392
+ if (Array.isArray(tab?.messageList) && tab.messageList.length > 0) return tab.messageList;
393
+ return null;
394
+ };
395
+ let messages = null;
396
+ for (let i = 0; i < 16; i += 1) {
397
+ messages = readMessages();
398
+ if (messages) break;
399
+ await new Promise((resolve) => setTimeout(resolve, 500));
400
+ }
401
+ const arr = messages ?? (Array.isArray(store.activeTabMessageList) ? store.activeTabMessageList : []);
402
+ const pick = (item, snake, camel) => item?.[snake] ?? item?.[camel];
403
+ const leafVariants = (leaf) => {
404
+ const camel = leaf.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
405
+ const snake = leaf.replace(/([A-Z])/g, (_, c) => '_' + c.toLowerCase());
406
+ const capCamel = leaf.charAt(0) + leaf.slice(1).replace(/(^|_)([a-z])/g, (_, sep, c) => (sep ? c.toUpperCase() : c));
407
+ return [...new Set([leaf, camel, snake, capCamel])];
408
+ };
409
+ const nested = (item, snake, camel, ...leafCandidates) => {
410
+ const target = pick(item, snake, camel);
411
+ if (!target || typeof target !== 'object') return '';
412
+ for (const candidate of leafCandidates) {
413
+ for (const variant of leafVariants(candidate)) {
414
+ if (target[variant] != null && target[variant] !== '') return target[variant];
415
+ }
416
+ }
417
+ return '';
418
+ };
419
+ return {
420
+ items: arr.map((item) => ({
421
+ user: nested(item, 'user_info', 'userInfo', 'nickname', 'nickName'),
422
+ action: item?.title ?? item?.actionTitle ?? '',
423
+ content: nested(item, 'comment_info', 'commentInfo', 'content'),
424
+ note: nested(item, 'item_info', 'itemInfo', 'content'),
425
+ time: item?.time ?? item?.timestamp ?? '',
426
+ })),
427
+ };
428
+ })()
429
+ `;
430
+ }
431
+ export function buildRednoteMediaExtractScript(noteId) {
432
+ return `
433
+ (() => {
434
+ const result = { noteId: ${JSON.stringify(noteId)}, media: [] };
435
+ const body = document.body?.innerText || '';
436
+ const loginWall = /登录后查看|请登录|Log in to view/.test(body);
437
+ const securityBlock = /访问频繁|安全验证|risk control|Security Verification|Access Denied/i.test(body);
438
+ const seen = new Set();
439
+ const pushMedia = (type, url) => {
440
+ if (!url || typeof url !== 'string') return;
441
+ if (url.startsWith('blob:')) return;
442
+ const normalized = url.replace(/\\\\u002F/g, '/').split('?')[0];
443
+ if (!/^https?:\\/\\//.test(normalized)) return;
444
+ if (!/(rednote|xiaohongshu|xhscdn|sns-video|ci\\.)/i.test(normalized)) return;
445
+ const key = type + ':' + normalized;
446
+ if (seen.has(key)) return;
447
+ seen.add(key);
448
+ result.media.push({ type, url: normalized });
449
+ };
450
+ document.querySelectorAll('img').forEach((img) => {
451
+ const src = img.currentSrc || img.src || img.getAttribute('data-src') || '';
452
+ pushMedia('image', src);
453
+ });
454
+ document.querySelectorAll('video source, video[src]').forEach((video) => {
455
+ const src = video.src || video.getAttribute('src') || '';
456
+ pushMedia('video', src);
457
+ });
458
+ const state = window.__INITIAL_STATE__;
459
+ try {
460
+ const noteData = state?.note?.noteDetailMap ?? state?.note?.note ?? {};
461
+ for (const key of Object.keys(noteData)) {
462
+ const note = noteData[key]?.note ?? noteData[key];
463
+ const imageList = note?.imageList ?? note?.image_list ?? [];
464
+ for (const image of imageList) {
465
+ pushMedia('image', image?.urlDefault ?? image?.urlPre ?? image?.url ?? image?.traceId);
466
+ }
467
+ const video = note?.video;
468
+ if (video) {
469
+ pushMedia('video', video.url ?? video.originVideoKey ?? video.consumer?.originVideoKey);
470
+ const streams = video.media?.stream?.h264 ?? [];
471
+ for (const stream of streams) pushMedia('video', stream.masterUrl ?? stream.backupUrls?.[0]);
472
+ }
473
+ }
474
+ } catch {}
475
+ return { ...result, loginWall, securityBlock };
476
+ })()
477
+ `;
478
+ }
479
+ async function readUserSnapshot(page) {
480
+ return (await page.evaluate(`
481
+ (() => {
482
+ const safeClone = (value) => {
483
+ try {
484
+ return JSON.parse(JSON.stringify(value ?? null));
485
+ } catch {
486
+ return null;
487
+ }
488
+ };
489
+ const userStore = window.__INITIAL_STATE__?.user || {};
490
+ return {
491
+ noteGroups: safeClone(userStore.notes?._value || userStore.notes || []),
492
+ pageData: safeClone(userStore.userPageData?._value || userStore.userPageData || {}),
493
+ };
494
+ })()
495
+ `));
496
+ }
497
+ function noteRows(data, noteId) {
498
+ if (data.loginWall)
499
+ throw new Error("Note content requires login to www.rednote.com");
500
+ if (data.securityBlock) {
501
+ throw new Error(`Rednote security block: the note detail page was blocked by risk control. ${SIGNED_URL_HINT}`);
502
+ }
503
+ if (data.notFound) {
504
+ throw new Error(`Note ${noteId} not found or unavailable - it may have been deleted or restricted`);
505
+ }
506
+ if (!data.title && !data.author) {
507
+ throw new Error("The note page loaded without visible content. The note may be deleted or restricted.");
508
+ }
509
+ const numOrZero = (value) => (/^\d+/.test(value) ? value : "0");
510
+ const rows = [
511
+ { field: "title", value: cleanString(data.title) },
512
+ { field: "author", value: cleanString(data.author) },
513
+ { field: "content", value: cleanString(data.desc) },
514
+ { field: "likes", value: numOrZero(cleanString(data.likes)) },
515
+ { field: "collects", value: numOrZero(cleanString(data.collects)) },
516
+ { field: "comments", value: numOrZero(cleanString(data.comments)) },
517
+ ];
518
+ const tags = Array.isArray(data.tags)
519
+ ? data.tags.map(cleanString).filter(Boolean)
520
+ : [];
521
+ if (tags.length)
522
+ rows.push({ field: "tags", value: tags.join(", ") });
523
+ return rows;
524
+ }
525
+ cli({
526
+ site: "rednote",
527
+ name: "note",
528
+ description: "Read note body and engagement counts from a rednote note",
529
+ domain: REDNOTE_HOST,
530
+ strategy: Strategy.COOKIE,
531
+ browser: true,
532
+ args: [
533
+ {
534
+ name: "note-id",
535
+ required: true,
536
+ positional: true,
537
+ description: "Full rednote note URL with xsec_token",
538
+ },
539
+ ],
540
+ columns: REDNOTE_NOTE_COLUMNS,
541
+ func: async (page, kwargs) => {
542
+ const p = page;
543
+ const raw = cleanString(kwargs["note-id"]);
544
+ const noteId = parseRednoteNoteId(raw);
545
+ await p.goto(buildRednoteNoteUrl(raw, "rednote note"));
546
+ await p.wait(2);
547
+ const data = (await p.evaluate(buildRednoteNoteExtractScript()));
548
+ if (!data || typeof data !== "object")
549
+ throw new Error("rednote/note: unexpected evaluate response");
550
+ return noteRows(data, noteId);
551
+ },
552
+ });
553
+ cli({
554
+ site: "rednote",
555
+ name: "search",
556
+ description: "Search rednote notes",
557
+ domain: REDNOTE_HOST,
558
+ strategy: Strategy.COOKIE,
559
+ browser: true,
560
+ args: [
561
+ {
562
+ name: "query",
563
+ required: true,
564
+ positional: true,
565
+ description: "Search keyword",
566
+ },
567
+ {
568
+ name: "limit",
569
+ type: "int",
570
+ default: 20,
571
+ description: "Number of results",
572
+ },
573
+ ],
574
+ columns: REDNOTE_SEARCH_COLUMNS,
575
+ func: async (page, kwargs) => {
576
+ const p = page;
577
+ const query = cleanString(kwargs.query);
578
+ if (!query)
579
+ throw new Error("query cannot be empty");
580
+ const limit = parseRednoteSearchLimit(kwargs.limit);
581
+ await p.goto(`${REDNOTE_ORIGIN}/search_result?keyword=${encodeURIComponent(query)}&source=web_search_result_notes`);
582
+ const waitResult = await p.evaluate(buildRednoteSearchWaitScript());
583
+ if (waitResult === "login_wall") {
584
+ throw new Error("Rednote search results are blocked behind a login wall");
585
+ }
586
+ await p.evaluate(buildRednoteScrollUntilScript(limit));
587
+ const payload = await p.evaluate(buildRednoteSearchExtractScript());
588
+ const data = Array.isArray(payload) ? payload : [];
589
+ return data
590
+ .filter((item) => cleanString(item.title))
591
+ .slice(0, limit)
592
+ .map((item, index) => ({
593
+ rank: index + 1,
594
+ title: cleanString(item.title),
595
+ author: cleanString(item.author),
596
+ likes: cleanString(item.likes || "0"),
597
+ published_at: rednoteNoteIdToDate(cleanString(item.url)),
598
+ url: cleanString(item.url),
599
+ author_url: cleanString(item.author_url),
600
+ }));
601
+ },
602
+ });
603
+ cli({
604
+ site: "rednote",
605
+ name: "user",
606
+ description: "Get public notes from a rednote user profile",
607
+ domain: REDNOTE_HOST,
608
+ strategy: Strategy.COOKIE,
609
+ browser: true,
610
+ args: [
611
+ {
612
+ name: "id",
613
+ type: "str",
614
+ required: true,
615
+ positional: true,
616
+ description: "User id or profile URL",
617
+ },
618
+ {
619
+ name: "limit",
620
+ type: "int",
621
+ default: 15,
622
+ description: "Number of notes to return",
623
+ },
624
+ ],
625
+ columns: REDNOTE_USER_COLUMNS,
626
+ func: async (page, kwargs) => {
627
+ const p = page;
628
+ const userId = normalizeRednoteUserId(String(kwargs.id));
629
+ const limit = parseRednoteLimit(kwargs.limit, 15);
630
+ await p.goto(`${REDNOTE_ORIGIN}/user/profile/${userId}`);
631
+ let snapshot = await readUserSnapshot(p);
632
+ let results = extractXhsUserNotes(snapshot ?? {}, userId, REDNOTE_HOST);
633
+ let previousCount = results.length;
634
+ for (let i = 0; results.length < limit && i < 4; i += 1) {
635
+ await p.autoScroll({ maxScrolls: 1, delay: 1500 });
636
+ await p.wait(1);
637
+ snapshot = await readUserSnapshot(p);
638
+ const nextResults = extractXhsUserNotes(snapshot ?? {}, userId, REDNOTE_HOST);
639
+ if (nextResults.length <= previousCount)
640
+ break;
641
+ results = nextResults;
642
+ previousCount = nextResults.length;
643
+ }
644
+ if (results.length === 0)
645
+ throw new Error("No public notes found for this rednote user.");
646
+ return results.slice(0, limit);
647
+ },
648
+ });
649
+ cli({
650
+ site: "rednote",
651
+ name: "comments",
652
+ description: "Read comments from a rednote note (supports nested replies)",
653
+ domain: REDNOTE_HOST,
654
+ strategy: Strategy.COOKIE,
655
+ browser: true,
656
+ args: [
657
+ {
658
+ name: "note-id",
659
+ required: true,
660
+ positional: true,
661
+ description: "Full rednote note URL with xsec_token",
662
+ },
663
+ {
664
+ name: "limit",
665
+ type: "int",
666
+ default: 20,
667
+ description: "Number of top-level comments (max 50)",
668
+ },
669
+ {
670
+ name: "with-replies",
671
+ type: "bool",
672
+ default: false,
673
+ description: "Include nested replies",
674
+ },
675
+ ],
676
+ columns: REDNOTE_COMMENT_COLUMNS,
677
+ func: async (page, kwargs) => {
678
+ const p = page;
679
+ const limit = parseRednoteLimit(kwargs.limit, 20, 50);
680
+ const withReplies = Boolean(kwargs["with-replies"]);
681
+ const raw = cleanString(kwargs["note-id"]);
682
+ await p.goto(buildRednoteNoteUrl(raw, "rednote comments"));
683
+ await p.wait(2);
684
+ const data = (await p.evaluate(buildRednoteCommentsExtractScript(withReplies)));
685
+ if (!data || typeof data !== "object")
686
+ throw new Error("rednote/comments: unexpected evaluate response");
687
+ if (data.securityBlock) {
688
+ throw new Error(`Rednote security block: the note detail page was blocked by risk control. ${SIGNED_URL_HINT}`);
689
+ }
690
+ if (data.loginWall)
691
+ throw new Error("Note comments require login to www.rednote.com");
692
+ const all = Array.isArray(data.results)
693
+ ? data.results
694
+ : [];
695
+ if (withReplies) {
696
+ const limited = [];
697
+ let topCount = 0;
698
+ for (const row of all) {
699
+ if (!row.is_reply)
700
+ topCount += 1;
701
+ if (topCount > limit)
702
+ break;
703
+ limited.push(row);
704
+ }
705
+ return limited.map((row, index) => ({ rank: index + 1, ...row }));
706
+ }
707
+ return all
708
+ .slice(0, limit)
709
+ .map((row, index) => ({ rank: index + 1, ...row }));
710
+ },
711
+ });
712
+ cli({
713
+ site: "rednote",
714
+ name: "feed",
715
+ description: "Rednote home feed (reads hydrated Pinia store)",
716
+ domain: REDNOTE_HOST,
717
+ strategy: Strategy.COOKIE,
718
+ browser: true,
719
+ args: [
720
+ {
721
+ name: "limit",
722
+ type: "int",
723
+ default: 20,
724
+ description: "Number of items to return",
725
+ },
726
+ ],
727
+ columns: REDNOTE_FEED_COLUMNS,
728
+ func: async (page, kwargs) => {
729
+ const p = page;
730
+ const limit = parseRednoteLimit(kwargs.limit, 20);
731
+ await p.goto(`${REDNOTE_ORIGIN}/explore`);
732
+ await p.wait(2);
733
+ const data = (await p.evaluate(buildRednoteFeedReadScript()));
734
+ if (!data || typeof data !== "object")
735
+ throw new Error("rednote feed: unexpected evaluate response");
736
+ if (data.error)
737
+ throw new Error(`rednote feed: ${cleanString(data.error)}`);
738
+ const items = Array.isArray(data.items) ? data.items : [];
739
+ const rows = items
740
+ .filter((row) => cleanString(row.id))
741
+ .slice(0, limit)
742
+ .map((row) => ({
743
+ id: cleanString(row.id),
744
+ title: cleanString(row.title),
745
+ author: cleanString(row.author),
746
+ likes: cleanString(row.likes),
747
+ type: cleanString(row.type),
748
+ url: `${REDNOTE_ORIGIN}/explore/${cleanString(row.id)}`,
749
+ }));
750
+ if (rows.length === 0)
751
+ throw new Error("No feed items in the hydrated store.");
752
+ return rows;
753
+ },
754
+ });
755
+ cli({
756
+ site: "rednote",
757
+ name: "notifications",
758
+ description: "Rednote notifications (mentions/likes/connections)",
759
+ domain: REDNOTE_HOST,
760
+ strategy: Strategy.COOKIE,
761
+ browser: true,
762
+ args: [
763
+ {
764
+ name: "type",
765
+ default: "mentions",
766
+ description: "Notification type: mentions, likes, or connections",
767
+ },
768
+ {
769
+ name: "limit",
770
+ type: "int",
771
+ default: 20,
772
+ description: "Number of notifications to return",
773
+ },
774
+ ],
775
+ columns: REDNOTE_NOTIFICATION_COLUMNS,
776
+ func: async (page, kwargs) => {
777
+ const p = page;
778
+ const type = parseRednoteNotificationType(kwargs.type);
779
+ const limit = parseRednoteLimit(kwargs.limit, 20);
780
+ await p.goto(`${REDNOTE_ORIGIN}/notification`);
781
+ await p.wait(2);
782
+ const data = (await p.evaluate(buildRednoteNotificationsReadScript(type)));
783
+ if (!data || typeof data !== "object")
784
+ throw new Error("rednote notifications: unexpected evaluate response");
785
+ if (data.error) {
786
+ const detail = cleanString(data.detail);
787
+ throw new Error(`rednote notifications: ${cleanString(data.error)}${detail ? ` (${detail})` : ""}`);
788
+ }
789
+ const items = Array.isArray(data.items) ? data.items : [];
790
+ return items.slice(0, limit).map((row, index) => ({
791
+ rank: index + 1,
792
+ user: cleanString(row.user),
793
+ action: cleanString(row.action),
794
+ content: cleanString(row.content),
795
+ note: cleanString(row.note),
796
+ time: cleanString(row.time),
797
+ }));
798
+ },
799
+ });
800
+ cli({
801
+ site: "rednote",
802
+ name: "download",
803
+ description: "Download images and videos from a rednote note",
804
+ domain: REDNOTE_HOST,
805
+ strategy: Strategy.COOKIE,
806
+ browser: true,
807
+ args: [
808
+ {
809
+ name: "note-id",
810
+ positional: true,
811
+ required: true,
812
+ description: "Full rednote note URL with xsec_token",
813
+ },
814
+ {
815
+ name: "output",
816
+ default: "./rednote-downloads",
817
+ description: "Output directory",
818
+ },
819
+ ],
820
+ columns: REDNOTE_DOWNLOAD_COLUMNS,
821
+ func: async (page, kwargs) => {
822
+ const p = page;
823
+ const raw = cleanString(kwargs["note-id"]);
824
+ const noteId = parseRednoteNoteId(raw);
825
+ const output = cleanString(kwargs.output || "./rednote-downloads");
826
+ if (!output)
827
+ throw new Error("output cannot be empty");
828
+ await p.goto(buildRednoteNoteUrl(raw, "rednote download"));
829
+ await p.wait(2);
830
+ const extracted = (await p.evaluate(buildRednoteMediaExtractScript(noteId)));
831
+ if (!extracted || typeof extracted !== "object")
832
+ throw new Error("rednote/download: unexpected evaluate response");
833
+ if (extracted.securityBlock) {
834
+ throw new Error(`Rednote security block: the note detail page was blocked by risk control. ${SIGNED_URL_HINT}`);
835
+ }
836
+ if (extracted.loginWall)
837
+ throw new Error("Note media requires login to www.rednote.com");
838
+ const media = Array.isArray(extracted.media) ? extracted.media : [];
839
+ if (media.length === 0)
840
+ throw new Error("No downloadable media found on this rednote note.");
841
+ const cookieHeader = formatCookieHeader(await p.cookies());
842
+ const results = await mapConcurrent(media, 3, async (item, index) => {
843
+ const filename = `${index + 1}-${generateFilename(item.url, index + 1)}`;
844
+ const result = await httpDownload(item.url, join(output, filename), cookieHeader ? { Cookie: cookieHeader } : undefined);
845
+ return {
846
+ index: index + 1,
847
+ type: item.type,
848
+ status: result.status,
849
+ path: result.path ?? "",
850
+ size: result.size ?? 0,
851
+ url: item.url,
852
+ error: result.error ?? "",
853
+ };
854
+ });
855
+ return results;
856
+ },
857
+ });
858
+ //# sourceMappingURL=web.js.map