@zenalexa/unicli 0.220.1 → 0.221.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (471) hide show
  1. package/AGENTS.md +28 -6
  2. package/README.md +8 -8
  3. package/README.zh-CN.md +8 -8
  4. package/dist/adapters/bilibili/comments.js +66 -4
  5. package/dist/adapters/bilibili/comments.js.map +1 -1
  6. package/dist/adapters/bilibili/compat.js +2 -2
  7. package/dist/adapters/bilibili/compat.js.map +1 -1
  8. package/dist/adapters/bilibili/download.js +4 -4
  9. package/dist/adapters/bilibili/download.js.map +1 -1
  10. package/dist/adapters/bilibili/wbi.d.ts.map +1 -1
  11. package/dist/adapters/bilibili/wbi.js +3 -3
  12. package/dist/adapters/bilibili/wbi.js.map +1 -1
  13. package/dist/adapters/cipo/_shared.d.ts +21 -0
  14. package/dist/adapters/cipo/_shared.d.ts.map +1 -0
  15. package/dist/adapters/cipo/_shared.js +67 -0
  16. package/dist/adapters/cipo/_shared.js.map +1 -0
  17. package/dist/adapters/cipo/get.d.ts +19 -0
  18. package/dist/adapters/cipo/get.d.ts.map +1 -0
  19. package/dist/adapters/cipo/get.js +140 -0
  20. package/dist/adapters/cipo/get.js.map +1 -0
  21. package/dist/adapters/cipo/legal-status.d.ts +19 -0
  22. package/dist/adapters/cipo/legal-status.d.ts.map +1 -0
  23. package/dist/adapters/cipo/legal-status.js +111 -0
  24. package/dist/adapters/cipo/legal-status.js.map +1 -0
  25. package/dist/adapters/cipo/search.d.ts +20 -0
  26. package/dist/adapters/cipo/search.d.ts.map +1 -0
  27. package/dist/adapters/cipo/search.js +148 -0
  28. package/dist/adapters/cipo/search.js.map +1 -0
  29. package/dist/adapters/cnipa/_shared.d.ts +47 -0
  30. package/dist/adapters/cnipa/_shared.d.ts.map +1 -0
  31. package/dist/adapters/cnipa/_shared.js +97 -0
  32. package/dist/adapters/cnipa/_shared.js.map +1 -0
  33. package/dist/adapters/cnipa/get.d.ts +19 -0
  34. package/dist/adapters/cnipa/get.d.ts.map +1 -0
  35. package/dist/adapters/cnipa/get.js +149 -0
  36. package/dist/adapters/cnipa/get.js.map +1 -0
  37. package/dist/adapters/cnipa/legal-status.d.ts +19 -0
  38. package/dist/adapters/cnipa/legal-status.d.ts.map +1 -0
  39. package/dist/adapters/cnipa/legal-status.js +119 -0
  40. package/dist/adapters/cnipa/legal-status.js.map +1 -0
  41. package/dist/adapters/cnipa/search.d.ts +21 -0
  42. package/dist/adapters/cnipa/search.d.ts.map +1 -0
  43. package/dist/adapters/cnipa/search.js +170 -0
  44. package/dist/adapters/cnipa/search.js.map +1 -0
  45. package/dist/adapters/espacenet/_shared.d.ts +21 -0
  46. package/dist/adapters/espacenet/_shared.d.ts.map +1 -0
  47. package/dist/adapters/espacenet/_shared.js +67 -0
  48. package/dist/adapters/espacenet/_shared.js.map +1 -0
  49. package/dist/adapters/espacenet/family.d.ts +19 -0
  50. package/dist/adapters/espacenet/family.d.ts.map +1 -0
  51. package/dist/adapters/espacenet/family.js +118 -0
  52. package/dist/adapters/espacenet/family.js.map +1 -0
  53. package/dist/adapters/espacenet/get.d.ts +19 -0
  54. package/dist/adapters/espacenet/get.d.ts.map +1 -0
  55. package/dist/adapters/espacenet/get.js +130 -0
  56. package/dist/adapters/espacenet/get.js.map +1 -0
  57. package/dist/adapters/espacenet/legal-status.d.ts +19 -0
  58. package/dist/adapters/espacenet/legal-status.d.ts.map +1 -0
  59. package/dist/adapters/espacenet/legal-status.js +110 -0
  60. package/dist/adapters/espacenet/legal-status.js.map +1 -0
  61. package/dist/adapters/espacenet/search.d.ts +20 -0
  62. package/dist/adapters/espacenet/search.d.ts.map +1 -0
  63. package/dist/adapters/espacenet/search.js +165 -0
  64. package/dist/adapters/espacenet/search.js.map +1 -0
  65. package/dist/adapters/facebook/subtitles.d.ts +9 -0
  66. package/dist/adapters/facebook/subtitles.d.ts.map +1 -0
  67. package/dist/adapters/facebook/subtitles.js +42 -0
  68. package/dist/adapters/facebook/subtitles.js.map +1 -0
  69. package/dist/adapters/fips/_shared.d.ts +21 -0
  70. package/dist/adapters/fips/_shared.d.ts.map +1 -0
  71. package/dist/adapters/fips/_shared.js +77 -0
  72. package/dist/adapters/fips/_shared.js.map +1 -0
  73. package/dist/adapters/fips/get.d.ts +19 -0
  74. package/dist/adapters/fips/get.d.ts.map +1 -0
  75. package/dist/adapters/fips/get.js +139 -0
  76. package/dist/adapters/fips/get.js.map +1 -0
  77. package/dist/adapters/fips/search.d.ts +20 -0
  78. package/dist/adapters/fips/search.d.ts.map +1 -0
  79. package/dist/adapters/fips/search.js +148 -0
  80. package/dist/adapters/fips/search.js.map +1 -0
  81. package/dist/adapters/freepatentsonline-web/_shared.d.ts +72 -0
  82. package/dist/adapters/freepatentsonline-web/_shared.d.ts.map +1 -0
  83. package/dist/adapters/freepatentsonline-web/_shared.js +216 -0
  84. package/dist/adapters/freepatentsonline-web/_shared.js.map +1 -0
  85. package/dist/adapters/freepatentsonline-web/get.d.ts +21 -0
  86. package/dist/adapters/freepatentsonline-web/get.d.ts.map +1 -0
  87. package/dist/adapters/freepatentsonline-web/get.js +127 -0
  88. package/dist/adapters/freepatentsonline-web/get.js.map +1 -0
  89. package/dist/adapters/freepatentsonline-web/search.d.ts +22 -0
  90. package/dist/adapters/freepatentsonline-web/search.d.ts.map +1 -0
  91. package/dist/adapters/freepatentsonline-web/search.js +149 -0
  92. package/dist/adapters/freepatentsonline-web/search.js.map +1 -0
  93. package/dist/adapters/google-patents-web/_shared.d.ts +110 -0
  94. package/dist/adapters/google-patents-web/_shared.d.ts.map +1 -0
  95. package/dist/adapters/google-patents-web/_shared.js +164 -0
  96. package/dist/adapters/google-patents-web/_shared.js.map +1 -0
  97. package/dist/adapters/google-patents-web/get.d.ts +36 -0
  98. package/dist/adapters/google-patents-web/get.d.ts.map +1 -0
  99. package/dist/adapters/google-patents-web/get.js +187 -0
  100. package/dist/adapters/google-patents-web/get.js.map +1 -0
  101. package/dist/adapters/google-patents-web/search.d.ts +23 -0
  102. package/dist/adapters/google-patents-web/search.d.ts.map +1 -0
  103. package/dist/adapters/google-patents-web/search.js +169 -0
  104. package/dist/adapters/google-patents-web/search.js.map +1 -0
  105. package/dist/adapters/inpi-br/_shared.d.ts +21 -0
  106. package/dist/adapters/inpi-br/_shared.d.ts.map +1 -0
  107. package/dist/adapters/inpi-br/_shared.js +67 -0
  108. package/dist/adapters/inpi-br/_shared.js.map +1 -0
  109. package/dist/adapters/inpi-br/get.d.ts +19 -0
  110. package/dist/adapters/inpi-br/get.d.ts.map +1 -0
  111. package/dist/adapters/inpi-br/get.js +142 -0
  112. package/dist/adapters/inpi-br/get.js.map +1 -0
  113. package/dist/adapters/inpi-br/search.d.ts +20 -0
  114. package/dist/adapters/inpi-br/search.d.ts.map +1 -0
  115. package/dist/adapters/inpi-br/search.js +154 -0
  116. package/dist/adapters/inpi-br/search.js.map +1 -0
  117. package/dist/adapters/instagram/subtitles.d.ts +9 -0
  118. package/dist/adapters/instagram/subtitles.d.ts.map +1 -0
  119. package/dist/adapters/instagram/subtitles.js +42 -0
  120. package/dist/adapters/instagram/subtitles.js.map +1 -0
  121. package/dist/adapters/mastodon/statuses.d.ts +40 -0
  122. package/dist/adapters/mastodon/statuses.d.ts.map +1 -0
  123. package/dist/adapters/mastodon/statuses.js +153 -0
  124. package/dist/adapters/mastodon/statuses.js.map +1 -0
  125. package/dist/adapters/reddit/comments.d.ts +9 -0
  126. package/dist/adapters/reddit/comments.d.ts.map +1 -0
  127. package/dist/adapters/reddit/comments.js +124 -0
  128. package/dist/adapters/reddit/comments.js.map +1 -0
  129. package/dist/adapters/threads/post.d.ts +32 -0
  130. package/dist/adapters/threads/post.d.ts.map +1 -0
  131. package/dist/adapters/threads/post.js +287 -0
  132. package/dist/adapters/threads/post.js.map +1 -0
  133. package/dist/adapters/tiktok/subtitles.d.ts +9 -0
  134. package/dist/adapters/tiktok/subtitles.d.ts.map +1 -0
  135. package/dist/adapters/tiktok/subtitles.js +42 -0
  136. package/dist/adapters/tiktok/subtitles.js.map +1 -0
  137. package/dist/adapters/twitter/accept.js +2 -2
  138. package/dist/adapters/twitter/accept.js.map +1 -1
  139. package/dist/adapters/twitter/browser-fallback.d.ts +26 -0
  140. package/dist/adapters/twitter/browser-fallback.d.ts.map +1 -0
  141. package/dist/adapters/twitter/browser-fallback.js +93 -0
  142. package/dist/adapters/twitter/browser-fallback.js.map +1 -0
  143. package/dist/adapters/twitter/browser-state.d.ts +11 -0
  144. package/dist/adapters/twitter/browser-state.d.ts.map +1 -0
  145. package/dist/adapters/twitter/browser-state.js +46 -0
  146. package/dist/adapters/twitter/browser-state.js.map +1 -0
  147. package/dist/adapters/twitter/client.d.ts.map +1 -1
  148. package/dist/adapters/twitter/client.js +36 -13
  149. package/dist/adapters/twitter/client.js.map +1 -1
  150. package/dist/adapters/twitter/reply-dm.js +2 -2
  151. package/dist/adapters/twitter/reply-dm.js.map +1 -1
  152. package/dist/adapters/twitter/reply.js +1 -0
  153. package/dist/adapters/twitter/reply.js.map +1 -1
  154. package/dist/adapters/twitter/search.js +11 -18
  155. package/dist/adapters/twitter/search.js.map +1 -1
  156. package/dist/adapters/twitter/thread.d.ts +14 -0
  157. package/dist/adapters/twitter/thread.d.ts.map +1 -1
  158. package/dist/adapters/twitter/thread.js +28 -2
  159. package/dist/adapters/twitter/thread.js.map +1 -1
  160. package/dist/adapters/twitter/trending.js +13 -59
  161. package/dist/adapters/twitter/trending.js.map +1 -1
  162. package/dist/adapters/xiaohongshu/browser-state.d.ts +19 -0
  163. package/dist/adapters/xiaohongshu/browser-state.d.ts.map +1 -0
  164. package/dist/adapters/xiaohongshu/browser-state.js +67 -0
  165. package/dist/adapters/xiaohongshu/browser-state.js.map +1 -0
  166. package/dist/adapters/xiaohongshu/comments.js +28 -5
  167. package/dist/adapters/xiaohongshu/comments.js.map +1 -1
  168. package/dist/adapters/xiaohongshu/download.js +49 -11
  169. package/dist/adapters/xiaohongshu/download.js.map +1 -1
  170. package/dist/adapters/xiaohongshu/search.d.ts.map +1 -1
  171. package/dist/adapters/xiaohongshu/search.js +11 -5
  172. package/dist/adapters/xiaohongshu/search.js.map +1 -1
  173. package/dist/adapters/xiaohongshu/trending.d.ts +9 -0
  174. package/dist/adapters/xiaohongshu/trending.d.ts.map +1 -0
  175. package/dist/adapters/xiaohongshu/trending.js +94 -0
  176. package/dist/adapters/xiaohongshu/trending.js.map +1 -0
  177. package/dist/adapters/youtube/comments.d.ts +80 -0
  178. package/dist/adapters/youtube/comments.d.ts.map +1 -1
  179. package/dist/adapters/youtube/comments.js +108 -12
  180. package/dist/adapters/youtube/comments.js.map +1 -1
  181. package/dist/adapters/youtube/subtitles.d.ts +9 -0
  182. package/dist/adapters/youtube/subtitles.d.ts.map +1 -0
  183. package/dist/adapters/youtube/subtitles.js +42 -0
  184. package/dist/adapters/youtube/subtitles.js.map +1 -0
  185. package/dist/adapters/yt-dlp/subtitles.d.ts +9 -0
  186. package/dist/adapters/yt-dlp/subtitles.d.ts.map +1 -0
  187. package/dist/adapters/yt-dlp/subtitles.js +41 -0
  188. package/dist/adapters/yt-dlp/subtitles.js.map +1 -0
  189. package/dist/adapters/zhihu/answer-detail.d.ts +39 -0
  190. package/dist/adapters/zhihu/answer-detail.d.ts.map +1 -0
  191. package/dist/adapters/zhihu/answer-detail.js +204 -0
  192. package/dist/adapters/zhihu/answer-detail.js.map +1 -0
  193. package/dist/adapters/zhihu/comment.d.ts +9 -0
  194. package/dist/adapters/zhihu/comment.d.ts.map +1 -0
  195. package/dist/adapters/zhihu/comment.js +149 -0
  196. package/dist/adapters/zhihu/comment.js.map +1 -0
  197. package/dist/adapters/zhihu/recommend.d.ts +36 -0
  198. package/dist/adapters/zhihu/recommend.d.ts.map +1 -0
  199. package/dist/adapters/zhihu/recommend.js +151 -0
  200. package/dist/adapters/zhihu/recommend.js.map +1 -0
  201. package/dist/browser/bridge.d.ts.map +1 -1
  202. package/dist/browser/bridge.js +14 -3
  203. package/dist/browser/bridge.js.map +1 -1
  204. package/dist/browser/daemon-client.d.ts +6 -0
  205. package/dist/browser/daemon-client.d.ts.map +1 -1
  206. package/dist/browser/daemon-client.js +75 -15
  207. package/dist/browser/daemon-client.js.map +1 -1
  208. package/dist/browser/daemon.js +39 -15
  209. package/dist/browser/daemon.js.map +1 -1
  210. package/dist/browser/protocol.d.ts +1 -0
  211. package/dist/browser/protocol.d.ts.map +1 -1
  212. package/dist/browser/protocol.js +1 -0
  213. package/dist/browser/protocol.js.map +1 -1
  214. package/dist/cli.d.ts.map +1 -1
  215. package/dist/cli.js +6 -0
  216. package/dist/cli.js.map +1 -1
  217. package/dist/commands/approvals.d.ts.map +1 -1
  218. package/dist/commands/approvals.js +1 -37
  219. package/dist/commands/approvals.js.map +1 -1
  220. package/dist/commands/browser/index.d.ts.map +1 -1
  221. package/dist/commands/browser/index.js +7 -2
  222. package/dist/commands/browser/index.js.map +1 -1
  223. package/dist/commands/daemon.d.ts.map +1 -1
  224. package/dist/commands/daemon.js +7 -3
  225. package/dist/commands/daemon.js.map +1 -1
  226. package/dist/commands/dispatch.d.ts.map +1 -1
  227. package/dist/commands/dispatch.js +27 -3
  228. package/dist/commands/dispatch.js.map +1 -1
  229. package/dist/commands/patent-doctor.d.ts +48 -0
  230. package/dist/commands/patent-doctor.d.ts.map +1 -0
  231. package/dist/commands/patent-doctor.js +109 -0
  232. package/dist/commands/patent-doctor.js.map +1 -0
  233. package/dist/commands/patent.d.ts +78 -0
  234. package/dist/commands/patent.d.ts.map +1 -0
  235. package/dist/commands/patent.js +919 -0
  236. package/dist/commands/patent.js.map +1 -0
  237. package/dist/commands/social.d.ts +19 -0
  238. package/dist/commands/social.d.ts.map +1 -0
  239. package/dist/commands/social.js +236 -0
  240. package/dist/commands/social.js.map +1 -0
  241. package/dist/core/registry.d.ts +1 -1
  242. package/dist/core/registry.d.ts.map +1 -1
  243. package/dist/core/registry.js +11 -2
  244. package/dist/core/registry.js.map +1 -1
  245. package/dist/discovery/loader.d.ts.map +1 -1
  246. package/dist/discovery/loader.js +4 -0
  247. package/dist/discovery/loader.js.map +1 -1
  248. package/dist/engine/approval-presenter.d.ts +10 -0
  249. package/dist/engine/approval-presenter.d.ts.map +1 -0
  250. package/dist/engine/approval-presenter.js +45 -0
  251. package/dist/engine/approval-presenter.js.map +1 -0
  252. package/dist/engine/approval-store.d.ts +4 -0
  253. package/dist/engine/approval-store.d.ts.map +1 -1
  254. package/dist/engine/approval-store.js +85 -11
  255. package/dist/engine/approval-store.js.map +1 -1
  256. package/dist/engine/auth/oauth2-cc.d.ts +67 -0
  257. package/dist/engine/auth/oauth2-cc.d.ts.map +1 -0
  258. package/dist/engine/auth/oauth2-cc.js +120 -0
  259. package/dist/engine/auth/oauth2-cc.js.map +1 -0
  260. package/dist/engine/cookies.d.ts +10 -0
  261. package/dist/engine/cookies.d.ts.map +1 -1
  262. package/dist/engine/cookies.js +64 -0
  263. package/dist/engine/cookies.js.map +1 -1
  264. package/dist/engine/download.d.ts +5 -0
  265. package/dist/engine/download.d.ts.map +1 -1
  266. package/dist/engine/download.js +11 -4
  267. package/dist/engine/download.js.map +1 -1
  268. package/dist/engine/executor.d.ts +1 -0
  269. package/dist/engine/executor.d.ts.map +1 -1
  270. package/dist/engine/executor.js +25 -0
  271. package/dist/engine/executor.js.map +1 -1
  272. package/dist/engine/framework.d.ts +5 -5
  273. package/dist/engine/framework.js +5 -5
  274. package/dist/engine/harden.d.ts +1 -1
  275. package/dist/engine/harden.js +1 -1
  276. package/dist/engine/kernel/stages.d.ts.map +1 -1
  277. package/dist/engine/kernel/stages.js +2 -1
  278. package/dist/engine/kernel/stages.js.map +1 -1
  279. package/dist/engine/normalizer/patent-envelope.d.ts +61 -0
  280. package/dist/engine/normalizer/patent-envelope.d.ts.map +1 -0
  281. package/dist/engine/normalizer/patent-envelope.js +132 -0
  282. package/dist/engine/normalizer/patent-envelope.js.map +1 -0
  283. package/dist/engine/research.d.ts +5 -7
  284. package/dist/engine/research.d.ts.map +1 -1
  285. package/dist/engine/research.js +6 -9
  286. package/dist/engine/research.js.map +1 -1
  287. package/dist/engine/steps/browser-helpers.d.ts +2 -2
  288. package/dist/engine/steps/browser-helpers.d.ts.map +1 -1
  289. package/dist/engine/steps/browser-helpers.js +39 -16
  290. package/dist/engine/steps/browser-helpers.js.map +1 -1
  291. package/dist/engine/steps/download.d.ts +1 -0
  292. package/dist/engine/steps/download.d.ts.map +1 -1
  293. package/dist/engine/steps/download.js +3 -1
  294. package/dist/engine/steps/download.js.map +1 -1
  295. package/dist/engine/steps/index.d.ts +2 -0
  296. package/dist/engine/steps/index.d.ts.map +1 -1
  297. package/dist/engine/steps/index.js +2 -0
  298. package/dist/engine/steps/index.js.map +1 -1
  299. package/dist/engine/steps/oauth2-token.d.ts +41 -0
  300. package/dist/engine/steps/oauth2-token.d.ts.map +1 -0
  301. package/dist/engine/steps/oauth2-token.js +115 -0
  302. package/dist/engine/steps/oauth2-token.js.map +1 -0
  303. package/dist/engine/steps/select-xml.d.ts +34 -0
  304. package/dist/engine/steps/select-xml.d.ts.map +1 -0
  305. package/dist/engine/steps/select-xml.js +222 -0
  306. package/dist/engine/steps/select-xml.js.map +1 -0
  307. package/dist/engine/template.d.ts.map +1 -1
  308. package/dist/engine/template.js +7 -0
  309. package/dist/engine/template.js.map +1 -1
  310. package/dist/engine/transport/mcp-browser.d.ts +128 -0
  311. package/dist/engine/transport/mcp-browser.d.ts.map +1 -0
  312. package/dist/engine/transport/mcp-browser.js +120 -0
  313. package/dist/engine/transport/mcp-browser.js.map +1 -0
  314. package/dist/fast-path/handlers/approvals.d.ts +11 -0
  315. package/dist/fast-path/handlers/approvals.d.ts.map +1 -0
  316. package/dist/fast-path/handlers/approvals.js +136 -0
  317. package/dist/fast-path/handlers/approvals.js.map +1 -0
  318. package/dist/fast-path/manifest.d.ts +1 -0
  319. package/dist/fast-path/manifest.d.ts.map +1 -1
  320. package/dist/fast-path/manifest.js.map +1 -1
  321. package/dist/fast-path.d.ts.map +1 -1
  322. package/dist/fast-path.js +3 -0
  323. package/dist/fast-path.js.map +1 -1
  324. package/dist/index.d.ts +23 -0
  325. package/dist/index.d.ts.map +1 -0
  326. package/dist/index.js +38 -0
  327. package/dist/index.js.map +1 -0
  328. package/dist/manifest-compact.txt +3 -3
  329. package/dist/manifest-search.json +1 -1
  330. package/dist/manifest.json +2239 -176
  331. package/dist/output/auth-guidance.d.ts +14 -0
  332. package/dist/output/auth-guidance.d.ts.map +1 -0
  333. package/dist/output/auth-guidance.js +50 -0
  334. package/dist/output/auth-guidance.js.map +1 -0
  335. package/dist/output/error-map.d.ts +1 -1
  336. package/dist/output/error-map.d.ts.map +1 -1
  337. package/dist/output/error-map.js +28 -4
  338. package/dist/output/error-map.js.map +1 -1
  339. package/dist/output/next-actions.d.ts.map +1 -1
  340. package/dist/output/next-actions.js +19 -3
  341. package/dist/output/next-actions.js.map +1 -1
  342. package/dist/registry.d.ts +18 -1
  343. package/dist/registry.d.ts.map +1 -1
  344. package/dist/registry.js +5 -0
  345. package/dist/registry.js.map +1 -1
  346. package/dist/social/browser-errors.d.ts +13 -0
  347. package/dist/social/browser-errors.d.ts.map +1 -0
  348. package/dist/social/browser-errors.js +36 -0
  349. package/dist/social/browser-errors.js.map +1 -0
  350. package/dist/social/capabilities.d.ts +29 -0
  351. package/dist/social/capabilities.d.ts.map +1 -0
  352. package/dist/social/capabilities.js +448 -0
  353. package/dist/social/capabilities.js.map +1 -0
  354. package/dist/social/comments.d.ts +26 -0
  355. package/dist/social/comments.d.ts.map +1 -0
  356. package/dist/social/comments.js +97 -0
  357. package/dist/social/comments.js.map +1 -0
  358. package/dist/social/video-text.d.ts +27 -0
  359. package/dist/social/video-text.d.ts.map +1 -0
  360. package/dist/social/video-text.js +140 -0
  361. package/dist/social/video-text.js.map +1 -0
  362. package/dist/types/patent.d.ts +160 -0
  363. package/dist/types/patent.d.ts.map +1 -0
  364. package/dist/types/patent.js +16 -0
  365. package/dist/types/patent.js.map +1 -0
  366. package/dist/types.d.ts +12 -0
  367. package/dist/types.d.ts.map +1 -1
  368. package/dist/types.js.map +1 -1
  369. package/package.json +9 -4
  370. package/server.json +3 -3
  371. package/skills/unicli/SKILL.md +1 -1
  372. package/skills/unicli-claude-code/SKILL.md +1 -1
  373. package/skills/unicli-hermes/SKILL.md +1 -1
  374. package/src/adapters/bilibili/comments-tree.test.ts +41 -0
  375. package/src/adapters/bilibili/comments.ts +78 -4
  376. package/src/adapters/bilibili/compat.ts +5 -2
  377. package/src/adapters/bilibili/download.ts +7 -4
  378. package/src/adapters/bilibili/wbi.ts +6 -3
  379. package/src/adapters/brave/search.yaml +53 -0
  380. package/src/adapters/cipo/_shared.ts +98 -0
  381. package/src/adapters/cipo/get.ts +188 -0
  382. package/src/adapters/cipo/legal-status.ts +148 -0
  383. package/src/adapters/cipo/search.ts +195 -0
  384. package/src/adapters/cnipa/_shared.ts +138 -0
  385. package/src/adapters/cnipa/get.ts +199 -0
  386. package/src/adapters/cnipa/legal-status.ts +162 -0
  387. package/src/adapters/cnipa/search.ts +229 -0
  388. package/src/adapters/dpma/get.yaml +67 -0
  389. package/src/adapters/dpma/search.yaml +77 -0
  390. package/src/adapters/duckduckgo/search.yaml +54 -0
  391. package/src/adapters/duckduckgo/suggest.yaml +52 -0
  392. package/src/adapters/epo/family.yaml +69 -0
  393. package/src/adapters/epo/get.yaml +74 -0
  394. package/src/adapters/epo/legal-status.yaml +63 -0
  395. package/src/adapters/epo/search.yaml +84 -0
  396. package/src/adapters/espacenet/_shared.ts +98 -0
  397. package/src/adapters/espacenet/family.ts +161 -0
  398. package/src/adapters/espacenet/get.ts +185 -0
  399. package/src/adapters/espacenet/legal-status.ts +151 -0
  400. package/src/adapters/espacenet/search.ts +229 -0
  401. package/src/adapters/facebook/subtitles.ts +44 -0
  402. package/src/adapters/fips/_shared.ts +109 -0
  403. package/src/adapters/fips/get.ts +186 -0
  404. package/src/adapters/fips/search.ts +195 -0
  405. package/src/adapters/freepatentsonline-web/_shared.ts +273 -0
  406. package/src/adapters/freepatentsonline-web/get.ts +144 -0
  407. package/src/adapters/freepatentsonline-web/search.ts +170 -0
  408. package/src/adapters/google-patents-bq/prior-art.yaml +80 -0
  409. package/src/adapters/google-patents-bq/search.yaml +97 -0
  410. package/src/adapters/google-patents-web/_shared.ts +242 -0
  411. package/src/adapters/google-patents-web/get.ts +224 -0
  412. package/src/adapters/google-patents-web/search.ts +196 -0
  413. package/src/adapters/inpi-br/_shared.ts +98 -0
  414. package/src/adapters/inpi-br/get.ts +193 -0
  415. package/src/adapters/inpi-br/search.ts +206 -0
  416. package/src/adapters/inpi-fr/get.yaml +62 -0
  417. package/src/adapters/inpi-fr/search.yaml +74 -0
  418. package/src/adapters/instagram/subtitles.ts +44 -0
  419. package/src/adapters/ipaustralia/get.yaml +67 -0
  420. package/src/adapters/ipaustralia/search.yaml +74 -0
  421. package/src/adapters/jpo/get.yaml +63 -0
  422. package/src/adapters/jpo/search.yaml +76 -0
  423. package/src/adapters/kipris/get.yaml +69 -0
  424. package/src/adapters/kipris/legal-status.yaml +58 -0
  425. package/src/adapters/kipris/search.yaml +79 -0
  426. package/src/adapters/lens/get.yaml +64 -0
  427. package/src/adapters/lens/search.yaml +82 -0
  428. package/src/adapters/mastodon/statuses.test.ts +82 -0
  429. package/src/adapters/mastodon/statuses.ts +208 -0
  430. package/src/adapters/patsnap/get.yaml +65 -0
  431. package/src/adapters/patsnap/search.yaml +77 -0
  432. package/src/adapters/pqai/prior-art.yaml +59 -0
  433. package/src/adapters/pqai/search.yaml +60 -0
  434. package/src/adapters/reddit/comments-tree.test.ts +79 -0
  435. package/src/adapters/reddit/comments.ts +159 -0
  436. package/src/adapters/threads/post.test.ts +64 -0
  437. package/src/adapters/threads/post.ts +366 -0
  438. package/src/adapters/threads/user.yaml +73 -0
  439. package/src/adapters/tiktok/subtitles.ts +44 -0
  440. package/src/adapters/twitter/accept.ts +5 -2
  441. package/src/adapters/twitter/browser-fallback.ts +138 -0
  442. package/src/adapters/twitter/browser-state.ts +74 -0
  443. package/src/adapters/twitter/client.ts +51 -21
  444. package/src/adapters/twitter/reply-dm.ts +5 -2
  445. package/src/adapters/twitter/reply.ts +1 -0
  446. package/src/adapters/twitter/search.ts +12 -38
  447. package/src/adapters/twitter/thread.test.ts +43 -0
  448. package/src/adapters/twitter/thread.ts +44 -2
  449. package/src/adapters/twitter/trending.ts +14 -95
  450. package/src/adapters/ukipo/info.yaml +43 -0
  451. package/src/adapters/uspto/get.yaml +67 -0
  452. package/src/adapters/uspto/legal-status.yaml +58 -0
  453. package/src/adapters/uspto/search.yaml +88 -0
  454. package/src/adapters/wipo-patentscope/info.yaml +43 -0
  455. package/src/adapters/xiaohongshu/browser-state.ts +95 -0
  456. package/src/adapters/xiaohongshu/comments.ts +29 -6
  457. package/src/adapters/xiaohongshu/download.ts +60 -11
  458. package/src/adapters/xiaohongshu/search.ts +18 -6
  459. package/src/adapters/xiaohongshu/trending.ts +112 -0
  460. package/src/adapters/yahoo/search.yaml +52 -0
  461. package/src/adapters/youtube/comments-microformat.test.ts +35 -0
  462. package/src/adapters/youtube/comments-tree.test.ts +74 -0
  463. package/src/adapters/youtube/comments.ts +166 -12
  464. package/src/adapters/youtube/subtitles.ts +44 -0
  465. package/src/adapters/yt-dlp/subtitles.ts +43 -0
  466. package/src/adapters/zhihu/answer-detail.test.ts +83 -0
  467. package/src/adapters/zhihu/answer-detail.ts +275 -0
  468. package/src/adapters/zhihu/comment-tree.test.ts +57 -0
  469. package/src/adapters/zhihu/comment.ts +186 -0
  470. package/src/adapters/zhihu/recommend.test.ts +65 -0
  471. package/src/adapters/zhihu/recommend.ts +207 -0
@@ -7,16 +7,56 @@
7
7
 
8
8
  import { cli, Strategy } from "../../registry.js";
9
9
  import type { IPage } from "../../types.js";
10
- import { loadCookies, formatCookieHeader } from "../../engine/cookies.js";
10
+ import {
11
+ loadCookiesWithCDP,
12
+ formatCookieHeader,
13
+ } from "../../engine/cookies.js";
14
+ import { mkdir, writeFile } from "node:fs/promises";
15
+ import { join } from "node:path";
16
+ import { USER_AGENT } from "../../constants.js";
11
17
  import { parseNoteId, buildNoteUrl } from "./note-helpers.js";
12
18
 
13
19
  /** Build authenticated headers for Xiaohongshu downloads. */
14
- function buildCookieHeader(): string {
15
- const cookies = loadCookies("xiaohongshu");
20
+ async function buildCookieHeader(): Promise<string> {
21
+ const cookies = await loadCookiesWithCDP(
22
+ "xiaohongshu",
23
+ "www.xiaohongshu.com",
24
+ );
16
25
  if (cookies) return formatCookieHeader(cookies);
17
26
  return "";
18
27
  }
19
28
 
29
+ function extensionForMedia(type: string, url: string): string {
30
+ try {
31
+ const pathname = new URL(url).pathname;
32
+ const match = /\.[a-zA-Z0-9]{2,5}$/.exec(pathname);
33
+ if (match) return match[0].toLowerCase();
34
+ } catch {
35
+ return type === "video" ? ".mp4" : ".jpg";
36
+ }
37
+ return type === "video" ? ".mp4" : ".jpg";
38
+ }
39
+
40
+ async function downloadMedia(
41
+ url: string,
42
+ path: string,
43
+ cookieHeader: string,
44
+ ): Promise<void> {
45
+ const headers: Record<string, string> = {
46
+ "User-Agent": USER_AGENT,
47
+ Referer: "https://www.xiaohongshu.com/",
48
+ };
49
+ if (cookieHeader) headers.Cookie = cookieHeader;
50
+ const response = await fetch(url, { headers });
51
+ if (!response.ok) {
52
+ throw new Error(
53
+ `Xiaohongshu media download failed: HTTP ${response.status}`,
54
+ );
55
+ }
56
+ const bytes = Buffer.from(await response.arrayBuffer());
57
+ await writeFile(path, bytes);
58
+ }
59
+
20
60
  cli({
21
61
  site: "xiaohongshu",
22
62
  name: "download",
@@ -37,11 +77,12 @@ cli({
37
77
  description: "Output directory",
38
78
  },
39
79
  ],
40
- columns: ["index", "type", "url"],
80
+ columns: ["index", "type", "path", "url"],
41
81
  func: async (page, kwargs) => {
42
82
  const p = page as IPage;
43
83
  const rawInput = String(kwargs["note-id"]);
44
84
  const noteId = parseNoteId(rawInput);
85
+ const outputDir = String(kwargs.output ?? "./xiaohongshu-downloads");
45
86
 
46
87
  await p.goto(buildNoteUrl(rawInput));
47
88
  await p.wait(3);
@@ -158,13 +199,21 @@ cli({
158
199
  return [{ index: 0, type: "-", url: "No media found" }];
159
200
  }
160
201
 
161
- // Cookie header available for authenticated downloads when media pipeline is wired
162
- void buildCookieHeader();
202
+ await mkdir(outputDir, { recursive: true });
203
+ const cookieHeader = await buildCookieHeader();
163
204
 
164
- return data.media.map((m: { type: string; url: string }, idx: number) => ({
165
- index: idx + 1,
166
- type: m.type,
167
- url: m.url,
168
- }));
205
+ const rows = [];
206
+ for (const [idx, media] of data.media.entries()) {
207
+ const ext = extensionForMedia(media.type, media.url);
208
+ const path = join(outputDir, `${noteId}-${idx + 1}${ext}`);
209
+ await downloadMedia(media.url, path, cookieHeader);
210
+ rows.push({
211
+ index: idx + 1,
212
+ type: media.type,
213
+ path,
214
+ url: media.url,
215
+ });
216
+ }
217
+ return rows;
169
218
  },
170
219
  });
@@ -7,6 +7,11 @@
7
7
 
8
8
  import { cli, Strategy } from "../../registry.js";
9
9
  import type { IPage } from "../../types.js";
10
+ import {
11
+ socialAuthError,
12
+ socialEmptyError,
13
+ } from "../../social/browser-errors.js";
14
+ import { assertXhsReadable } from "./browser-state.js";
10
15
 
11
16
  /**
12
17
  * Wait for search results or login wall using MutationObserver (max 5 s).
@@ -52,6 +57,7 @@ cli({
52
57
  domain: "www.xiaohongshu.com",
53
58
  strategy: Strategy.COOKIE,
54
59
  browser: true,
60
+ browserSession: "user",
55
61
  args: [
56
62
  {
57
63
  name: "query",
@@ -70,19 +76,19 @@ cli({
70
76
  func: async (page, kwargs) => {
71
77
  const p = page as IPage;
72
78
  const keyword = encodeURIComponent(String(kwargs.query));
79
+ const limit = Number(kwargs.limit) || 20;
73
80
  await p.goto(
74
81
  `https://www.xiaohongshu.com/search_result?keyword=${keyword}&source=web_search_result_notes`,
82
+ { settleMs: 2500 },
75
83
  );
76
84
 
77
85
  const waitResult = await p.evaluate(WAIT_FOR_CONTENT_JS);
86
+ await assertXhsReadable(p, "search");
78
87
 
79
88
  if (waitResult === "login_wall") {
80
- throw new Error(
81
- "Xiaohongshu search results are blocked behind a login wall",
82
- );
89
+ throw socialAuthError("xiaohongshu", "search");
83
90
  }
84
91
 
85
- // Scroll a couple of times to load more results
86
92
  await p.autoScroll({ maxScrolls: 2, delay: 1500 });
87
93
 
88
94
  const payload = await p.evaluate(`
@@ -121,11 +127,10 @@ cli({
121
127
  })()
122
128
  `);
123
129
 
124
- const limit = Number(kwargs.limit) || 20;
125
130
  const data: Record<string, unknown>[] = Array.isArray(payload)
126
131
  ? (payload as Record<string, unknown>[])
127
132
  : [];
128
- return data
133
+ const rows = data
129
134
  .filter((item) => item.title)
130
135
  .slice(0, limit)
131
136
  .map((item, i) => ({
@@ -133,5 +138,12 @@ cli({
133
138
  ...item,
134
139
  published_at: noteIdToDate(String(item.url ?? "")),
135
140
  }));
141
+ if (rows.length > 0) return rows;
142
+
143
+ throw socialEmptyError(
144
+ "xiaohongshu",
145
+ "search",
146
+ `Xiaohongshu search loaded no parseable notes for "${String(kwargs.query)}".`,
147
+ );
136
148
  },
137
149
  });
@@ -0,0 +1,112 @@
1
+ /**
2
+ * @owner Xiaohongshu trend extraction.
3
+ * @does Reads trending searches, falling back to visible hot notes in Chrome.
4
+ * @needs A logged-in Xiaohongshu browser session or valid local cookies.
5
+ * @feeds Agents needing current XHS trend context without brittle API-only failure.
6
+ * @breaks XHS endpoint and DOM drift can reduce rows; command returns only observed public UI data.
7
+ */
8
+
9
+ import { cli, Strategy } from "../../registry.js";
10
+ import type { IPage } from "../../types.js";
11
+ import { socialEmptyError } from "../../social/browser-errors.js";
12
+ import { assertXhsReadable, fetchXhsFeedItems } from "./browser-state.js";
13
+
14
+ interface TrendRow {
15
+ rank: number;
16
+ keyword: string;
17
+ score: string;
18
+ url: string;
19
+ }
20
+
21
+ async function fetchHotList(page: IPage, limit: number): Promise<TrendRow[]> {
22
+ const raw = await page.evaluate(`
23
+ (async () => {
24
+ const limit = ${limit};
25
+ const resp = await fetch('/api/sns/v1/search/hot_list', {
26
+ credentials: 'include',
27
+ headers: { 'Content-Type': 'application/json' }
28
+ });
29
+ if (!resp.ok) return { ok: false, status: resp.status };
30
+ const data = await resp.json();
31
+ const items = data?.data?.items || data?.data || [];
32
+ return {
33
+ ok: true,
34
+ rows: items.slice(0, limit).map((item, i) => {
35
+ const keyword = item.title || item.name || item.word || '';
36
+ return {
37
+ rank: i + 1,
38
+ keyword,
39
+ score: String(item.score || item.hot_value || ''),
40
+ url: 'https://www.xiaohongshu.com/search_result?keyword=' + encodeURIComponent(keyword),
41
+ };
42
+ }).filter((item) => item.keyword)
43
+ };
44
+ })()
45
+ `);
46
+ const result = raw as { ok?: boolean; rows?: TrendRow[] };
47
+ return result.ok && Array.isArray(result.rows) ? result.rows : [];
48
+ }
49
+
50
+ async function fetchHotFeed(page: IPage, limit: number): Promise<TrendRow[]> {
51
+ const items = await fetchXhsFeedItems(page);
52
+ const rows = items
53
+ .map((item) => item as Record<string, unknown>)
54
+ .map((item) => {
55
+ const note = item.note_card as Record<string, unknown> | undefined;
56
+ const interact = note?.interact_info as
57
+ | Record<string, unknown>
58
+ | undefined;
59
+ return {
60
+ title: String(note?.display_title ?? ""),
61
+ likes: String(interact?.liked_count ?? ""),
62
+ id: String(item.id ?? ""),
63
+ };
64
+ })
65
+ .filter((item) => item.title && item.id)
66
+ .sort((a, b) => Number(b.likes || 0) - Number(a.likes || 0))
67
+ .slice(0, limit);
68
+ return rows.map((item, i) => ({
69
+ rank: i + 1,
70
+ keyword: item.title,
71
+ score: item.likes,
72
+ url: `https://www.xiaohongshu.com/explore/${item.id}`,
73
+ }));
74
+ }
75
+
76
+ cli({
77
+ site: "xiaohongshu",
78
+ name: "trending",
79
+ description: "Xiaohongshu trending searches and visible hot notes",
80
+ domain: "www.xiaohongshu.com",
81
+ strategy: Strategy.COOKIE,
82
+ browser: true,
83
+ browserSession: "user",
84
+ args: [
85
+ {
86
+ name: "limit",
87
+ type: "int",
88
+ default: 20,
89
+ description: "Number of trending topics",
90
+ },
91
+ ],
92
+ columns: ["rank", "keyword", "score", "url"],
93
+ func: async (page, kwargs) => {
94
+ const p = page as IPage;
95
+ const limit = Number(kwargs.limit) || 20;
96
+ await p.goto("https://www.xiaohongshu.com/explore", { settleMs: 2500 });
97
+ await p.wait(2);
98
+ await assertXhsReadable(p, "trending");
99
+
100
+ const hotList = await fetchHotList(p, limit);
101
+ if (hotList.length > 0) return hotList;
102
+
103
+ const hotFeed = await fetchHotFeed(p, limit);
104
+ if (hotFeed.length > 0) return hotFeed;
105
+
106
+ throw socialEmptyError(
107
+ "xiaohongshu",
108
+ "trending",
109
+ "Xiaohongshu explore loaded no parseable hot-search or hot-note rows.",
110
+ );
111
+ },
112
+ });
@@ -0,0 +1,52 @@
1
+ # Yahoo Search — keyless HTML scrape.
2
+ #
3
+ # verification_status: keyless-best-effort
4
+ #
5
+ # Yahoo Search at https://search.yahoo.com/search returns SSR HTML
6
+ # with a result list. No token, no API key, no JS gate on first paint.
7
+ # Yahoo's anti-bot does throttle rapid loops; this adapter is sized for
8
+ # interactive use, not high-volume crawling.
9
+ site: yahoo
10
+ name: search
11
+ description: Search the public web via Yahoo Search (no API key needed)
12
+ type: web-api
13
+ domain: search.yahoo.com
14
+ strategy: public
15
+ lint_listing_detail: skip
16
+
17
+ args:
18
+ query:
19
+ type: str
20
+ required: true
21
+ positional: true
22
+ description: Free-text web search query
23
+ limit:
24
+ type: int
25
+ default: 10
26
+ description: Maximum results returned (Yahoo paginates at ~10 per page)
27
+
28
+ pipeline:
29
+ - fetch_text:
30
+ url: "https://search.yahoo.com/search?p=${{ args.query | url_encode }}"
31
+ headers:
32
+ User-Agent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
33
+ Accept: "text/html,application/xhtml+xml"
34
+
35
+ - extract:
36
+ selector: "div.algo"
37
+ fields:
38
+ title: "h3.title"
39
+ url: "h3.title a@href"
40
+ snippet: "div.compText"
41
+
42
+ - limit: ${{ args.limit }}
43
+
44
+ columns: [title, url, snippet]
45
+
46
+ # schema-v2 metadata
47
+ capabilities: ["http.fetch"]
48
+ minimum_capability: http.fetch
49
+ trust: public
50
+ confidentiality: public
51
+ quarantine: false
52
+ schema_version: v2
@@ -0,0 +1,35 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { extractYouTubeCommentRows } from "./comments.js";
4
+
5
+ describe("youtube microformat comments fallback", () => {
6
+ it("extracts public schema comments when thread renderers are absent", () => {
7
+ const extracted = extractYouTubeCommentRows({
8
+ microformat: {
9
+ microformatDataRenderer: {
10
+ videoDetails: {
11
+ comments: [
12
+ {
13
+ text: "Public comment",
14
+ dateCreated: "2026-05-14T00:00:00Z",
15
+ upvoteCount: "42",
16
+ author: { alternateName: "@alice", name: "Alice" },
17
+ },
18
+ ],
19
+ },
20
+ },
21
+ },
22
+ });
23
+
24
+ expect(extracted.rows).toEqual([
25
+ expect.objectContaining({
26
+ id: "microformat:1",
27
+ author: "@alice",
28
+ text: "Public comment",
29
+ likes: 42,
30
+ replies: 0,
31
+ created: "2026-05-14T00:00:00Z",
32
+ }),
33
+ ]);
34
+ });
35
+ });
@@ -0,0 +1,74 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { normalizeCommentRows } from "../../social/comments.js";
4
+ import { extractYouTubeCommentRows } from "./comments.js";
5
+
6
+ describe("youtube comment tree extraction", () => {
7
+ it("keeps inline reply rows under their parent comment", () => {
8
+ const extracted = extractYouTubeCommentRows({
9
+ onResponseReceivedEndpoints: [
10
+ {
11
+ appendContinuationItemsAction: {
12
+ continuationItems: [
13
+ {
14
+ itemSectionRenderer: {
15
+ contents: [
16
+ {
17
+ commentThreadRenderer: {
18
+ comment: {
19
+ commentRenderer: {
20
+ commentId: "root",
21
+ authorText: { simpleText: "Alice" },
22
+ contentText: { runs: [{ text: "Root" }] },
23
+ voteCount: { simpleText: "5" },
24
+ replyCount: 1,
25
+ },
26
+ },
27
+ replies: {
28
+ commentRepliesRenderer: {
29
+ contents: [
30
+ {
31
+ commentRenderer: {
32
+ commentId: "child",
33
+ authorText: { simpleText: "Bob" },
34
+ contentText: { runs: [{ text: "Child" }] },
35
+ voteCount: { simpleText: "2" },
36
+ },
37
+ },
38
+ ],
39
+ },
40
+ },
41
+ },
42
+ },
43
+ ],
44
+ },
45
+ },
46
+ ],
47
+ },
48
+ },
49
+ ],
50
+ });
51
+
52
+ const rows = normalizeCommentRows(extracted.rows, {
53
+ platform: "youtube",
54
+ contentId: "video",
55
+ });
56
+
57
+ expect(rows).toEqual([
58
+ expect.objectContaining({
59
+ comment_id: "root",
60
+ parent_id: "",
61
+ depth: 0,
62
+ path: "0001",
63
+ text: "Root",
64
+ }),
65
+ expect.objectContaining({
66
+ comment_id: "child",
67
+ parent_id: "root",
68
+ depth: 1,
69
+ path: "0001.0001",
70
+ text: "Child",
71
+ }),
72
+ ]);
73
+ });
74
+ });
@@ -6,14 +6,28 @@
6
6
  */
7
7
 
8
8
  import { cli, Strategy } from "../../registry.js";
9
+ import { normalizeCommentRows } from "../../social/comments.js";
9
10
  import { innertubeFetch } from "./innertube.js";
10
11
 
11
12
  interface CommentRenderer {
12
13
  commentRenderer?: {
14
+ commentId?: string;
13
15
  authorText?: { simpleText?: string };
14
16
  contentText?: { runs?: Array<{ text: string }> };
15
17
  voteCount?: { simpleText?: string };
16
18
  replyCount?: number;
19
+ publishedTimeText?: { runs?: Array<{ text: string }>; simpleText?: string };
20
+ };
21
+ }
22
+
23
+ interface ReplyContinuationItem {
24
+ commentRenderer?: CommentRenderer["commentRenderer"];
25
+ continuationItemRenderer?: {
26
+ continuationEndpoint?: {
27
+ continuationCommand?: {
28
+ token?: string;
29
+ };
30
+ };
17
31
  };
18
32
  }
19
33
 
@@ -22,6 +36,11 @@ interface CommentSection {
22
36
  contents?: Array<{
23
37
  commentThreadRenderer?: {
24
38
  comment?: CommentRenderer;
39
+ replies?: {
40
+ commentRepliesRenderer?: {
41
+ contents?: ReplyContinuationItem[];
42
+ };
43
+ };
25
44
  };
26
45
  }>;
27
46
  };
@@ -38,11 +57,70 @@ interface NextEndpoint {
38
57
 
39
58
  interface NextResponse {
40
59
  onResponseReceivedEndpoints?: NextEndpoint[];
60
+ microformat?: {
61
+ microformatDataRenderer?: {
62
+ videoDetails?: {
63
+ comments?: Array<{
64
+ text?: string;
65
+ dateCreated?: string;
66
+ upvoteCount?: string | number;
67
+ author?: {
68
+ name?: string;
69
+ alternateName?: string;
70
+ };
71
+ }>;
72
+ };
73
+ };
74
+ };
41
75
  }
42
76
 
43
- function extractComments(data: NextResponse): Array<Record<string, unknown>> {
77
+ function rowFromComment(
78
+ c: NonNullable<CommentRenderer["commentRenderer"]>,
79
+ parentId = "",
80
+ ): Record<string, unknown> {
81
+ return {
82
+ id: c.commentId ?? "",
83
+ parent_id: parentId,
84
+ author: c.authorText?.simpleText ?? "",
85
+ text: c.contentText?.runs?.map((r) => r.text).join("") ?? "",
86
+ likes: c.voteCount?.simpleText ?? "0",
87
+ replies: c.replyCount ?? 0,
88
+ created:
89
+ c.publishedTimeText?.simpleText ??
90
+ c.publishedTimeText?.runs?.map((r) => r.text).join("") ??
91
+ "",
92
+ };
93
+ }
94
+
95
+ function extractReplyRows(
96
+ items: ReplyContinuationItem[],
97
+ parentId: string,
98
+ ): {
99
+ rows: Array<Record<string, unknown>>;
100
+ continuationTokens: string[];
101
+ } {
102
+ const rows: Array<Record<string, unknown>> = [];
103
+ const continuationTokens: string[] = [];
104
+ for (const item of items) {
105
+ if (item.commentRenderer) {
106
+ rows.push(rowFromComment(item.commentRenderer, parentId));
107
+ }
108
+ const token =
109
+ item.continuationItemRenderer?.continuationEndpoint?.continuationCommand
110
+ ?.token;
111
+ if (token) continuationTokens.push(token);
112
+ }
113
+ return { rows, continuationTokens };
114
+ }
115
+
116
+ export function extractYouTubeCommentRows(data: NextResponse): {
117
+ rows: Array<Record<string, unknown>>;
118
+ replyContinuationTokens: Array<{ parentId: string; token: string }>;
119
+ } {
44
120
  const endpoints = data.onResponseReceivedEndpoints ?? [];
45
- const results: Array<Record<string, unknown>> = [];
121
+ const rows: Array<Record<string, unknown>> = [];
122
+ const replyContinuationTokens: Array<{ parentId: string; token: string }> =
123
+ [];
46
124
 
47
125
  for (const ep of endpoints) {
48
126
  const items =
@@ -53,19 +131,60 @@ function extractComments(data: NextResponse): Array<Record<string, unknown>> {
53
131
  for (const section of items) {
54
132
  const contents = section.itemSectionRenderer?.contents ?? [];
55
133
  for (const thread of contents) {
56
- const c = thread.commentThreadRenderer?.comment?.commentRenderer;
134
+ const renderer = thread.commentThreadRenderer;
135
+ const c = renderer?.comment?.commentRenderer;
57
136
  if (!c) continue;
58
- results.push({
59
- author: c.authorText?.simpleText ?? "",
60
- text: c.contentText?.runs?.map((r) => r.text).join("") ?? "",
61
- likes: c.voteCount?.simpleText ?? "0",
62
- replies: c.replyCount ?? 0,
63
- });
137
+ const rootRow = rowFromComment(c);
138
+ const parentId = String(rootRow.id ?? "");
139
+ rows.push(rootRow);
140
+ const replies = renderer?.replies?.commentRepliesRenderer?.contents;
141
+ if (!replies || !parentId) continue;
142
+ const extracted = extractReplyRows(replies, parentId);
143
+ rows.push(...extracted.rows);
144
+ for (const token of extracted.continuationTokens) {
145
+ replyContinuationTokens.push({ parentId, token });
146
+ }
64
147
  }
65
148
  }
66
149
  }
67
150
 
68
- return results;
151
+ if (rows.length === 0) {
152
+ const comments =
153
+ data.microformat?.microformatDataRenderer?.videoDetails?.comments ?? [];
154
+ for (const [index, comment] of comments.entries()) {
155
+ rows.push({
156
+ id: `microformat:${index + 1}`,
157
+ author: comment.author?.alternateName ?? comment.author?.name ?? "",
158
+ text: comment.text ?? "",
159
+ likes:
160
+ typeof comment.upvoteCount === "number"
161
+ ? comment.upvoteCount
162
+ : Number(comment.upvoteCount ?? 0),
163
+ replies: 0,
164
+ created: comment.dateCreated ?? "",
165
+ });
166
+ }
167
+ }
168
+
169
+ return { rows, replyContinuationTokens };
170
+ }
171
+
172
+ function extractContinuationReplyRows(
173
+ data: NextResponse,
174
+ parentId: string,
175
+ ): Array<Record<string, unknown>> {
176
+ const rows: Array<Record<string, unknown>> = [];
177
+ for (const ep of data.onResponseReceivedEndpoints ?? []) {
178
+ const items =
179
+ ep.reloadContinuationItemsCommand?.continuationItems ??
180
+ ep.appendContinuationItemsAction?.continuationItems ??
181
+ [];
182
+ for (const item of items as unknown[]) {
183
+ const renderer = (item as ReplyContinuationItem).commentRenderer;
184
+ if (renderer) rows.push(rowFromComment(renderer, parentId));
185
+ }
186
+ }
187
+ return rows;
69
188
  }
70
189
 
71
190
  cli({
@@ -88,14 +207,49 @@ cli({
88
207
  default: 20,
89
208
  description: "Max comments to return",
90
209
  },
210
+ {
211
+ name: "with-replies",
212
+ type: "bool",
213
+ default: false,
214
+ description: "Fetch available nested replies",
215
+ },
216
+ ],
217
+ columns: [
218
+ "platform",
219
+ "content_id",
220
+ "comment_id",
221
+ "parent_id",
222
+ "depth",
223
+ "path",
224
+ "author",
225
+ "text",
226
+ "likes",
227
+ "replies",
228
+ "created",
91
229
  ],
92
- columns: ["author", "text", "likes", "replies"],
93
230
  async func(_page, kwargs) {
94
231
  const videoId = kwargs.videoId as string;
95
232
  const limit = (kwargs.limit as number) ?? 20;
233
+ const withReplies = Boolean(kwargs["with-replies"]);
96
234
 
97
235
  const data = (await innertubeFetch("next", { videoId })) as NextResponse;
236
+ const extracted = extractYouTubeCommentRows(data);
237
+ const rows = extracted.rows.slice(0, limit);
238
+
239
+ if (withReplies) {
240
+ for (const continuation of extracted.replyContinuationTokens) {
241
+ const replyData = (await innertubeFetch("next", {
242
+ continuation: continuation.token,
243
+ })) as NextResponse;
244
+ rows.push(
245
+ ...extractContinuationReplyRows(replyData, continuation.parentId),
246
+ );
247
+ }
248
+ }
98
249
 
99
- return extractComments(data).slice(0, limit);
250
+ return normalizeCommentRows(rows, {
251
+ platform: "youtube",
252
+ contentId: videoId,
253
+ });
100
254
  },
101
255
  });