@jackwener/opencli 1.7.11 → 1.7.12

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 (730) hide show
  1. package/README.zh-CN.md +2 -1
  2. package/cli-manifest.json +1417 -24
  3. package/clis/1688/assets.js +1 -0
  4. package/clis/1688/download.js +1 -0
  5. package/clis/1688/item.js +1 -0
  6. package/clis/1688/search.js +2 -1
  7. package/clis/1688/store.js +1 -0
  8. package/clis/36kr/article.js +1 -0
  9. package/clis/36kr/hot.js +1 -0
  10. package/clis/36kr/news.js +1 -0
  11. package/clis/36kr/search.js +1 -0
  12. package/clis/51job/company.js +1 -0
  13. package/clis/51job/detail.js +1 -0
  14. package/clis/51job/hot.js +1 -0
  15. package/clis/51job/search.js +1 -0
  16. package/clis/amazon/bestsellers.js +1 -0
  17. package/clis/amazon/discussion.js +1 -0
  18. package/clis/amazon/movers-shakers.js +1 -0
  19. package/clis/amazon/new-releases.js +1 -0
  20. package/clis/amazon/offer.js +1 -0
  21. package/clis/amazon/product.js +1 -0
  22. package/clis/amazon/rankings.js +1 -0
  23. package/clis/amazon/search.js +1 -0
  24. package/clis/antigravity/dump.js +1 -0
  25. package/clis/antigravity/extract-code.js +1 -0
  26. package/clis/antigravity/model.js +1 -0
  27. package/clis/antigravity/new.js +1 -0
  28. package/clis/antigravity/read.js +1 -0
  29. package/clis/antigravity/send.js +1 -0
  30. package/clis/antigravity/status.js +1 -0
  31. package/clis/antigravity/watch.js +1 -0
  32. package/clis/apple-podcasts/episodes.js +1 -0
  33. package/clis/apple-podcasts/search.js +1 -0
  34. package/clis/apple-podcasts/top.js +1 -0
  35. package/clis/arxiv/arxiv.test.js +112 -0
  36. package/clis/arxiv/paper.js +4 -3
  37. package/clis/arxiv/recent.js +33 -0
  38. package/clis/arxiv/search.js +19 -7
  39. package/clis/arxiv/utils.js +68 -5
  40. package/clis/baidu-scholar/search.js +1 -0
  41. package/clis/band/bands.js +1 -0
  42. package/clis/band/mentions.js +1 -0
  43. package/clis/band/post.js +1 -0
  44. package/clis/band/posts.js +1 -0
  45. package/clis/barchart/flow.js +1 -0
  46. package/clis/barchart/greeks.js +1 -0
  47. package/clis/barchart/options.js +1 -0
  48. package/clis/barchart/quote.js +1 -0
  49. package/clis/bbc/news.js +1 -0
  50. package/clis/bilibili/comments.js +1 -0
  51. package/clis/bilibili/download.js +1 -0
  52. package/clis/bilibili/dynamic.js +1 -0
  53. package/clis/bilibili/favorite.js +1 -0
  54. package/clis/bilibili/feed.js +2 -0
  55. package/clis/bilibili/following.js +1 -0
  56. package/clis/bilibili/history.js +1 -0
  57. package/clis/bilibili/hot.js +6 -1
  58. package/clis/bilibili/hot.test.js +17 -0
  59. package/clis/bilibili/me.js +1 -1
  60. package/clis/bilibili/ranking.js +1 -0
  61. package/clis/bilibili/search.js +1 -1
  62. package/clis/bilibili/subtitle.js +1 -0
  63. package/clis/bilibili/user-videos.js +1 -0
  64. package/clis/bilibili/video.js +1 -0
  65. package/clis/binance/asks.js +1 -0
  66. package/clis/binance/depth.js +1 -0
  67. package/clis/binance/gainers.js +1 -0
  68. package/clis/binance/klines.js +1 -0
  69. package/clis/binance/losers.js +1 -0
  70. package/clis/binance/pairs.js +1 -0
  71. package/clis/binance/price.js +1 -0
  72. package/clis/binance/prices.js +1 -0
  73. package/clis/binance/ticker.js +1 -0
  74. package/clis/binance/top.js +1 -0
  75. package/clis/binance/trades.js +1 -0
  76. package/clis/bloomberg/businessweek.js +1 -0
  77. package/clis/bloomberg/economics.js +1 -0
  78. package/clis/bloomberg/feeds.js +1 -0
  79. package/clis/bloomberg/industries.js +1 -0
  80. package/clis/bloomberg/main.js +1 -0
  81. package/clis/bloomberg/markets.js +1 -0
  82. package/clis/bloomberg/news.js +1 -0
  83. package/clis/bloomberg/opinions.js +1 -0
  84. package/clis/bloomberg/politics.js +1 -0
  85. package/clis/bloomberg/tech.js +1 -0
  86. package/clis/bluesky/feeds.js +1 -0
  87. package/clis/bluesky/followers.js +1 -0
  88. package/clis/bluesky/following.js +1 -0
  89. package/clis/bluesky/profile.js +1 -0
  90. package/clis/bluesky/search.js +1 -0
  91. package/clis/bluesky/starter-packs.js +1 -0
  92. package/clis/bluesky/thread.js +1 -0
  93. package/clis/bluesky/trending.js +1 -0
  94. package/clis/bluesky/user.js +3 -1
  95. package/clis/boss/batchgreet.js +1 -0
  96. package/clis/boss/chatlist.js +1 -0
  97. package/clis/boss/chatmsg.js +1 -0
  98. package/clis/boss/detail.js +1 -0
  99. package/clis/boss/exchange.js +1 -0
  100. package/clis/boss/greet.js +1 -0
  101. package/clis/boss/invite.js +1 -0
  102. package/clis/boss/joblist.js +1 -0
  103. package/clis/boss/mark.js +1 -0
  104. package/clis/boss/recommend.js +1 -0
  105. package/clis/boss/resume.js +1 -0
  106. package/clis/boss/search.js +1 -0
  107. package/clis/boss/send.js +1 -0
  108. package/clis/boss/stats.js +1 -0
  109. package/clis/chaoxing/assignments.js +1 -0
  110. package/clis/chaoxing/exams.js +1 -0
  111. package/clis/chatgpt/image.js +1 -0
  112. package/clis/chatgpt-app/ask.js +1 -0
  113. package/clis/chatgpt-app/model.js +1 -0
  114. package/clis/chatgpt-app/new.js +1 -0
  115. package/clis/chatgpt-app/read.js +1 -0
  116. package/clis/chatgpt-app/send.js +1 -0
  117. package/clis/chatgpt-app/status.js +1 -0
  118. package/clis/chatwise/ask.js +1 -0
  119. package/clis/chatwise/export.js +1 -0
  120. package/clis/chatwise/history.js +1 -0
  121. package/clis/chatwise/model.js +1 -0
  122. package/clis/chatwise/read.js +1 -0
  123. package/clis/chatwise/send.js +1 -0
  124. package/clis/claude/ask.js +1 -0
  125. package/clis/claude/detail.js +1 -0
  126. package/clis/claude/history.js +1 -0
  127. package/clis/claude/new.js +1 -0
  128. package/clis/claude/read.js +1 -0
  129. package/clis/claude/send.js +1 -0
  130. package/clis/claude/status.js +1 -0
  131. package/clis/cnki/search.js +1 -0
  132. package/clis/codex/ask.js +1 -0
  133. package/clis/codex/export.js +1 -0
  134. package/clis/codex/extract-diff.js +1 -0
  135. package/clis/codex/history.js +1 -0
  136. package/clis/codex/model.js +1 -0
  137. package/clis/codex/read.js +1 -0
  138. package/clis/codex/send.js +1 -0
  139. package/clis/coupang/add-to-cart.js +1 -0
  140. package/clis/coupang/search.js +1 -0
  141. package/clis/ctrip/search.js +1 -0
  142. package/clis/cursor/ask.js +1 -0
  143. package/clis/cursor/composer.js +1 -0
  144. package/clis/cursor/export.js +1 -0
  145. package/clis/cursor/extract-code.js +1 -0
  146. package/clis/cursor/history.js +1 -0
  147. package/clis/cursor/model.js +1 -0
  148. package/clis/cursor/read.js +1 -0
  149. package/clis/cursor/send.js +1 -0
  150. package/clis/dblp/dblp.test.js +397 -0
  151. package/clis/dblp/paper.js +40 -0
  152. package/clis/dblp/search.js +45 -0
  153. package/clis/dblp/utils.js +290 -0
  154. package/clis/deepseek/ask.js +1 -0
  155. package/clis/deepseek/history.js +1 -0
  156. package/clis/deepseek/new.js +1 -0
  157. package/clis/deepseek/read.js +1 -0
  158. package/clis/deepseek/status.js +1 -0
  159. package/clis/devto/devto.test.js +236 -0
  160. package/clis/devto/read.js +103 -0
  161. package/clis/devto/tag.js +5 -1
  162. package/clis/devto/top.js +5 -1
  163. package/clis/devto/user.js +5 -1
  164. package/clis/dianping/__fixtures__/search.html +168 -0
  165. package/clis/dianping/__fixtures__/shop.html +6 -0
  166. package/clis/dianping/dianping.test.js +424 -0
  167. package/clis/dianping/search.js +154 -0
  168. package/clis/dianping/shop.js +173 -0
  169. package/clis/dianping/utils.js +157 -0
  170. package/clis/dictionary/examples.js +1 -0
  171. package/clis/dictionary/search.js +1 -0
  172. package/clis/dictionary/synonyms.js +1 -0
  173. package/clis/discord-app/channels.js +1 -0
  174. package/clis/discord-app/delete.js +1 -0
  175. package/clis/discord-app/members.js +1 -0
  176. package/clis/discord-app/read.js +1 -0
  177. package/clis/discord-app/search.js +1 -0
  178. package/clis/discord-app/send.js +1 -0
  179. package/clis/discord-app/servers.js +1 -0
  180. package/clis/discord-app/status.js +1 -0
  181. package/clis/douban/book-hot.js +1 -0
  182. package/clis/douban/download.js +1 -0
  183. package/clis/douban/marks.js +1 -0
  184. package/clis/douban/movie-hot.js +2 -1
  185. package/clis/douban/movie-hot.test.js +14 -0
  186. package/clis/douban/photos.js +2 -1
  187. package/clis/douban/reviews.js +1 -0
  188. package/clis/douban/search.js +1 -0
  189. package/clis/douban/subject.js +1 -0
  190. package/clis/douban/top250.js +1 -0
  191. package/clis/douban/utils.js +11 -13
  192. package/clis/douban/utils.test.js +79 -0
  193. package/clis/doubao/ask.js +1 -0
  194. package/clis/doubao/detail.js +1 -0
  195. package/clis/doubao/history.js +1 -0
  196. package/clis/doubao/meeting-summary.js +1 -0
  197. package/clis/doubao/meeting-transcript.js +1 -0
  198. package/clis/doubao/new.js +1 -0
  199. package/clis/doubao/read.js +1 -0
  200. package/clis/doubao/send.js +1 -0
  201. package/clis/doubao/status.js +1 -0
  202. package/clis/doubao-app/ask.js +1 -0
  203. package/clis/doubao-app/dump.js +1 -0
  204. package/clis/doubao-app/new.js +1 -0
  205. package/clis/doubao-app/read.js +1 -0
  206. package/clis/doubao-app/screenshot.js +1 -0
  207. package/clis/doubao-app/send.js +1 -0
  208. package/clis/doubao-app/status.js +1 -0
  209. package/clis/douyin/activities.js +1 -0
  210. package/clis/douyin/collections.js +1 -0
  211. package/clis/douyin/delete.js +1 -0
  212. package/clis/douyin/draft.js +1 -0
  213. package/clis/douyin/drafts.js +1 -0
  214. package/clis/douyin/hashtag.js +1 -0
  215. package/clis/douyin/location.js +1 -0
  216. package/clis/douyin/profile.js +1 -0
  217. package/clis/douyin/publish.js +1 -0
  218. package/clis/douyin/stats.js +1 -0
  219. package/clis/douyin/update.js +1 -0
  220. package/clis/douyin/user-videos.js +1 -0
  221. package/clis/douyin/videos.js +1 -0
  222. package/clis/eastmoney/announcement.js +1 -0
  223. package/clis/eastmoney/convertible.js +1 -0
  224. package/clis/eastmoney/etf.js +1 -0
  225. package/clis/eastmoney/holders.js +1 -0
  226. package/clis/eastmoney/hot-rank.js +1 -0
  227. package/clis/eastmoney/index-board.js +1 -0
  228. package/clis/eastmoney/kline.js +1 -0
  229. package/clis/eastmoney/kuaixun.js +1 -0
  230. package/clis/eastmoney/longhu.js +1 -0
  231. package/clis/eastmoney/money-flow.js +1 -0
  232. package/clis/eastmoney/northbound.js +1 -0
  233. package/clis/eastmoney/quote.js +1 -0
  234. package/clis/eastmoney/rank.js +1 -0
  235. package/clis/eastmoney/sectors.js +1 -0
  236. package/clis/facebook/add-friend.js +1 -0
  237. package/clis/facebook/events.js +1 -0
  238. package/clis/facebook/feed.js +1 -0
  239. package/clis/facebook/friends.js +1 -0
  240. package/clis/facebook/groups.js +1 -0
  241. package/clis/facebook/join-group.js +1 -0
  242. package/clis/facebook/marketplace-inbox.js +1 -0
  243. package/clis/facebook/marketplace-listings.js +1 -0
  244. package/clis/facebook/memories.js +1 -0
  245. package/clis/facebook/notifications.js +1 -0
  246. package/clis/facebook/profile.js +1 -0
  247. package/clis/facebook/search.js +1 -0
  248. package/clis/gemini/ask.js +1 -0
  249. package/clis/gemini/deep-research-result.js +1 -0
  250. package/clis/gemini/deep-research.js +1 -0
  251. package/clis/gemini/image.js +1 -0
  252. package/clis/gemini/new.js +1 -0
  253. package/clis/gitee/search.js +1 -0
  254. package/clis/gitee/trending.js +1 -0
  255. package/clis/gitee/user.js +1 -0
  256. package/clis/google/news.js +1 -0
  257. package/clis/google/search.js +1 -0
  258. package/clis/google/suggest.js +1 -0
  259. package/clis/google/trends.js +1 -0
  260. package/clis/google-scholar/cite.js +1 -0
  261. package/clis/google-scholar/profile.js +1 -0
  262. package/clis/google-scholar/search.js +1 -0
  263. package/clis/gov-law/recent.js +1 -0
  264. package/clis/gov-law/search.js +1 -0
  265. package/clis/gov-policy/recent.js +1 -0
  266. package/clis/gov-policy/search.js +1 -0
  267. package/clis/grok/ask.js +1 -0
  268. package/clis/grok/image.ts +1 -0
  269. package/clis/hackernews/ask.js +3 -1
  270. package/clis/hackernews/best.js +3 -1
  271. package/clis/hackernews/hackernews.test.js +132 -0
  272. package/clis/hackernews/jobs.js +3 -1
  273. package/clis/hackernews/new.js +3 -1
  274. package/clis/hackernews/read.js +188 -0
  275. package/clis/hackernews/search.js +3 -1
  276. package/clis/hackernews/show.js +3 -1
  277. package/clis/hackernews/top.js +3 -1
  278. package/clis/hackernews/user.js +1 -0
  279. package/clis/hf/top.js +1 -0
  280. package/clis/hupu/detail.js +1 -0
  281. package/clis/hupu/hot.js +3 -1
  282. package/clis/hupu/like.js +1 -0
  283. package/clis/hupu/mentions.js +2 -1
  284. package/clis/hupu/reply.js +1 -0
  285. package/clis/hupu/search.js +3 -1
  286. package/clis/hupu/unlike.js +1 -0
  287. package/clis/imdb/person.js +1 -0
  288. package/clis/imdb/reviews.js +1 -0
  289. package/clis/imdb/search.js +1 -0
  290. package/clis/imdb/title.js +1 -0
  291. package/clis/imdb/top.js +1 -0
  292. package/clis/imdb/trending.js +1 -0
  293. package/clis/indeed/indeed.test.js +375 -0
  294. package/clis/indeed/job.js +86 -0
  295. package/clis/indeed/search.js +110 -0
  296. package/clis/indeed/utils.js +152 -0
  297. package/clis/instagram/collection-create.js +1 -0
  298. package/clis/instagram/collection-delete.js +1 -0
  299. package/clis/instagram/comment.js +1 -0
  300. package/clis/instagram/download.js +1 -0
  301. package/clis/instagram/explore.js +1 -0
  302. package/clis/instagram/follow.js +1 -0
  303. package/clis/instagram/followers.js +1 -0
  304. package/clis/instagram/following.js +1 -0
  305. package/clis/instagram/like.js +1 -0
  306. package/clis/instagram/note.js +1 -0
  307. package/clis/instagram/post.js +1 -0
  308. package/clis/instagram/profile.js +1 -0
  309. package/clis/instagram/reel.js +1 -0
  310. package/clis/instagram/save.js +1 -0
  311. package/clis/instagram/saved.js +1 -0
  312. package/clis/instagram/search.js +1 -0
  313. package/clis/instagram/story.js +1 -0
  314. package/clis/instagram/unfollow.js +1 -0
  315. package/clis/instagram/unlike.js +1 -0
  316. package/clis/instagram/unsave.js +1 -0
  317. package/clis/instagram/user.js +1 -0
  318. package/clis/jd/add-cart.js +1 -0
  319. package/clis/jd/cart.js +1 -0
  320. package/clis/jd/detail.js +1 -0
  321. package/clis/jd/item.js +1 -0
  322. package/clis/jd/reviews.js +1 -0
  323. package/clis/jd/search.js +1 -0
  324. package/clis/jianyu/detail.js +1 -0
  325. package/clis/jianyu/search.js +1 -0
  326. package/clis/jike/comment.js +1 -0
  327. package/clis/jike/create.js +1 -0
  328. package/clis/jike/feed.js +3 -1
  329. package/clis/jike/like.js +1 -0
  330. package/clis/jike/notifications.js +1 -0
  331. package/clis/jike/post.js +1 -0
  332. package/clis/jike/repost.js +1 -0
  333. package/clis/jike/search.js +3 -1
  334. package/clis/jike/topic.js +1 -0
  335. package/clis/jike/user.js +3 -1
  336. package/clis/jimeng/generate.js +1 -0
  337. package/clis/jimeng/history.js +1 -0
  338. package/clis/jimeng/new.js +1 -0
  339. package/clis/jimeng/workspaces.js +1 -0
  340. package/clis/ke/chengjiao.js +1 -0
  341. package/clis/ke/ershoufang.js +1 -0
  342. package/clis/ke/xiaoqu.js +1 -0
  343. package/clis/ke/zufang.js +1 -0
  344. package/clis/lesswrong/comments.js +1 -0
  345. package/clis/lesswrong/curated.js +1 -0
  346. package/clis/lesswrong/frontpage.js +1 -0
  347. package/clis/lesswrong/new.js +1 -0
  348. package/clis/lesswrong/read.js +1 -0
  349. package/clis/lesswrong/sequences.js +1 -0
  350. package/clis/lesswrong/shortform.js +1 -0
  351. package/clis/lesswrong/tag.js +1 -0
  352. package/clis/lesswrong/tags.js +1 -0
  353. package/clis/lesswrong/top-month.js +1 -0
  354. package/clis/lesswrong/top-week.js +1 -0
  355. package/clis/lesswrong/top-year.js +1 -0
  356. package/clis/lesswrong/top.js +1 -0
  357. package/clis/lesswrong/user-posts.js +1 -0
  358. package/clis/lesswrong/user.js +1 -0
  359. package/clis/linkedin/search.js +1 -0
  360. package/clis/linkedin/timeline.js +1 -0
  361. package/clis/linux-do/categories.js +1 -0
  362. package/clis/linux-do/category.js +1 -0
  363. package/clis/linux-do/feed.js +1 -0
  364. package/clis/linux-do/hot.js +1 -0
  365. package/clis/linux-do/latest.js +1 -0
  366. package/clis/linux-do/search.js +1 -0
  367. package/clis/linux-do/tags.js +2 -1
  368. package/clis/linux-do/topic-content.js +1 -0
  369. package/clis/linux-do/topic.js +1 -0
  370. package/clis/linux-do/user-posts.js +1 -0
  371. package/clis/linux-do/user-topics.js +1 -0
  372. package/clis/lobsters/active.js +4 -1
  373. package/clis/lobsters/hot.js +4 -1
  374. package/clis/lobsters/lobsters.test.js +169 -0
  375. package/clis/lobsters/newest.js +4 -1
  376. package/clis/lobsters/read.js +196 -0
  377. package/clis/lobsters/tag.js +4 -1
  378. package/clis/maimai/search-talents.js +1 -0
  379. package/clis/medium/feed.js +1 -0
  380. package/clis/medium/search.js +1 -0
  381. package/clis/medium/user.js +1 -0
  382. package/clis/mubu/doc.js +1 -0
  383. package/clis/mubu/docs.js +1 -0
  384. package/clis/mubu/notes.js +1 -0
  385. package/clis/mubu/recent.js +1 -0
  386. package/clis/mubu/search.js +1 -0
  387. package/clis/notebooklm/current.js +1 -0
  388. package/clis/notebooklm/get.js +1 -0
  389. package/clis/notebooklm/history.js +1 -0
  390. package/clis/notebooklm/list.js +1 -0
  391. package/clis/notebooklm/note-list.js +1 -0
  392. package/clis/notebooklm/notes-get.js +1 -0
  393. package/clis/notebooklm/open.js +1 -0
  394. package/clis/notebooklm/source-fulltext.js +1 -0
  395. package/clis/notebooklm/source-get.js +1 -0
  396. package/clis/notebooklm/source-guide.js +1 -0
  397. package/clis/notebooklm/source-list.js +1 -0
  398. package/clis/notebooklm/status.js +1 -0
  399. package/clis/notebooklm/summary.js +1 -0
  400. package/clis/notion/export.js +1 -0
  401. package/clis/notion/favorites.js +1 -0
  402. package/clis/notion/new.js +1 -0
  403. package/clis/notion/read.js +1 -0
  404. package/clis/notion/search.js +1 -0
  405. package/clis/notion/sidebar.js +1 -0
  406. package/clis/notion/status.js +1 -0
  407. package/clis/notion/write.js +1 -0
  408. package/clis/nowcoder/companies.js +1 -0
  409. package/clis/nowcoder/creators.js +1 -0
  410. package/clis/nowcoder/detail.js +1 -0
  411. package/clis/nowcoder/experience.js +1 -0
  412. package/clis/nowcoder/hot.js +1 -0
  413. package/clis/nowcoder/jobs.js +1 -0
  414. package/clis/nowcoder/notifications.js +1 -0
  415. package/clis/nowcoder/papers.js +1 -0
  416. package/clis/nowcoder/practice.js +1 -0
  417. package/clis/nowcoder/recommend.js +1 -0
  418. package/clis/nowcoder/referral.js +1 -0
  419. package/clis/nowcoder/salary.js +1 -0
  420. package/clis/nowcoder/search.js +1 -0
  421. package/clis/nowcoder/suggest.js +1 -0
  422. package/clis/nowcoder/topics.js +1 -0
  423. package/clis/nowcoder/trending.js +1 -0
  424. package/clis/ones/login.js +1 -0
  425. package/clis/ones/logout.js +1 -0
  426. package/clis/ones/me.js +1 -0
  427. package/clis/ones/my-tasks.js +1 -0
  428. package/clis/ones/task.js +1 -0
  429. package/clis/ones/tasks.js +1 -0
  430. package/clis/ones/token-info.js +1 -0
  431. package/clis/ones/worklog.js +1 -0
  432. package/clis/openreview/openreview.test.js +345 -0
  433. package/clis/openreview/paper.js +43 -0
  434. package/clis/openreview/reviews.js +131 -0
  435. package/clis/openreview/search.js +46 -0
  436. package/clis/openreview/utils.js +158 -0
  437. package/clis/openreview/venue.js +63 -0
  438. package/clis/paperreview/feedback.js +1 -0
  439. package/clis/paperreview/review.js +1 -0
  440. package/clis/paperreview/submit.js +1 -0
  441. package/clis/pixiv/detail.js +1 -0
  442. package/clis/pixiv/download.js +1 -0
  443. package/clis/pixiv/illusts.js +2 -1
  444. package/clis/pixiv/ranking.js +2 -1
  445. package/clis/pixiv/search.js +2 -1
  446. package/clis/pixiv/user.js +2 -0
  447. package/clis/powerchina/search.js +1 -0
  448. package/clis/producthunt/browse.js +1 -0
  449. package/clis/producthunt/hot.js +1 -0
  450. package/clis/producthunt/posts.js +1 -0
  451. package/clis/producthunt/today.js +1 -0
  452. package/clis/quark/ls.js +1 -0
  453. package/clis/quark/mkdir.js +1 -0
  454. package/clis/quark/mv.js +1 -0
  455. package/clis/quark/rename.js +1 -0
  456. package/clis/quark/rm.js +1 -0
  457. package/clis/quark/save.js +1 -0
  458. package/clis/quark/share-tree.js +1 -0
  459. package/clis/reddit/comment.js +1 -0
  460. package/clis/reddit/frontpage.js +1 -0
  461. package/clis/reddit/hot.js +6 -1
  462. package/clis/reddit/hot.test.js +18 -0
  463. package/clis/reddit/popular.js +1 -0
  464. package/clis/reddit/read.js +1 -0
  465. package/clis/reddit/save.js +1 -0
  466. package/clis/reddit/saved.js +1 -0
  467. package/clis/reddit/search.js +1 -0
  468. package/clis/reddit/subreddit.js +1 -0
  469. package/clis/reddit/subscribe.js +1 -0
  470. package/clis/reddit/upvote.js +1 -0
  471. package/clis/reddit/upvoted.js +1 -0
  472. package/clis/reddit/user-comments.js +1 -0
  473. package/clis/reddit/user-posts.js +1 -0
  474. package/clis/reddit/user.js +1 -0
  475. package/clis/reuters/search.js +1 -0
  476. package/clis/sinablog/article.js +1 -0
  477. package/clis/sinablog/hot.js +1 -0
  478. package/clis/sinablog/search.js +1 -0
  479. package/clis/sinablog/user.js +1 -0
  480. package/clis/sinafinance/news.js +1 -0
  481. package/clis/sinafinance/rolling-news.js +1 -0
  482. package/clis/sinafinance/stock-rank.js +1 -0
  483. package/clis/sinafinance/stock.js +1 -0
  484. package/clis/smzdm/search.js +1 -0
  485. package/clis/spotify/spotify.js +11 -0
  486. package/clis/stackoverflow/bounties.js +11 -3
  487. package/clis/stackoverflow/hot.js +10 -2
  488. package/clis/stackoverflow/read.js +314 -0
  489. package/clis/stackoverflow/search.js +10 -2
  490. package/clis/stackoverflow/stackoverflow.test.js +346 -0
  491. package/clis/stackoverflow/unanswered.js +9 -2
  492. package/clis/steam/top-sellers.js +1 -0
  493. package/clis/substack/feed.js +1 -0
  494. package/clis/substack/publication.js +1 -0
  495. package/clis/substack/search.js +1 -0
  496. package/clis/taobao/add-cart.js +1 -0
  497. package/clis/taobao/cart.js +1 -0
  498. package/clis/taobao/detail.js +1 -0
  499. package/clis/taobao/reviews.js +1 -0
  500. package/clis/taobao/search.js +1 -0
  501. package/clis/tdx/hot-rank.js +1 -0
  502. package/clis/ths/hot-rank.js +1 -0
  503. package/clis/tieba/hot.js +2 -1
  504. package/clis/tieba/posts.js +1 -0
  505. package/clis/tieba/read.js +1 -0
  506. package/clis/tieba/search.js +2 -1
  507. package/clis/tiktok/comment.js +1 -0
  508. package/clis/tiktok/explore.js +1 -0
  509. package/clis/tiktok/follow.js +1 -0
  510. package/clis/tiktok/following.js +1 -0
  511. package/clis/tiktok/friends.js +1 -0
  512. package/clis/tiktok/like.js +1 -0
  513. package/clis/tiktok/live.js +1 -0
  514. package/clis/tiktok/notifications.js +1 -0
  515. package/clis/tiktok/profile.js +1 -0
  516. package/clis/tiktok/save.js +1 -0
  517. package/clis/tiktok/search.js +1 -0
  518. package/clis/tiktok/unfollow.js +1 -0
  519. package/clis/tiktok/unlike.js +1 -0
  520. package/clis/tiktok/unsave.js +1 -0
  521. package/clis/tiktok/user.js +1 -0
  522. package/clis/toutiao/articles.js +1 -0
  523. package/clis/twitter/accept.js +1 -0
  524. package/clis/twitter/article.js +1 -0
  525. package/clis/twitter/block.js +1 -0
  526. package/clis/twitter/bookmark.js +1 -0
  527. package/clis/twitter/bookmarks.js +2 -1
  528. package/clis/twitter/delete.js +1 -0
  529. package/clis/twitter/download.js +1 -0
  530. package/clis/twitter/follow.js +1 -0
  531. package/clis/twitter/followers.js +1 -0
  532. package/clis/twitter/following.js +1 -0
  533. package/clis/twitter/hide-reply.js +1 -0
  534. package/clis/twitter/like.js +1 -0
  535. package/clis/twitter/likes.js +2 -1
  536. package/clis/twitter/list-add.js +1 -0
  537. package/clis/twitter/list-remove.js +1 -0
  538. package/clis/twitter/list-tweets.js +1 -0
  539. package/clis/twitter/lists.js +1 -0
  540. package/clis/twitter/notifications.js +1 -0
  541. package/clis/twitter/post.js +1 -0
  542. package/clis/twitter/profile.js +1 -0
  543. package/clis/twitter/reply-dm.js +1 -0
  544. package/clis/twitter/reply.js +1 -0
  545. package/clis/twitter/search.js +1 -0
  546. package/clis/twitter/thread.js +1 -0
  547. package/clis/twitter/timeline.js +1 -0
  548. package/clis/twitter/trending.js +11 -12
  549. package/clis/twitter/trending.test.js +15 -0
  550. package/clis/twitter/tweets.js +2 -1
  551. package/clis/twitter/tweets.test.js +2 -2
  552. package/clis/twitter/unblock.js +1 -0
  553. package/clis/twitter/unbookmark.js +1 -0
  554. package/clis/twitter/unfollow.js +1 -0
  555. package/clis/uiverse/code.js +1 -0
  556. package/clis/uiverse/preview.js +1 -0
  557. package/clis/v2ex/daily.js +1 -0
  558. package/clis/v2ex/hot.js +1 -0
  559. package/clis/v2ex/latest.js +1 -0
  560. package/clis/v2ex/me.js +1 -0
  561. package/clis/v2ex/member.js +1 -0
  562. package/clis/v2ex/node.js +1 -0
  563. package/clis/v2ex/nodes.js +1 -0
  564. package/clis/v2ex/notifications.js +1 -0
  565. package/clis/v2ex/replies.js +1 -0
  566. package/clis/v2ex/topic.js +1 -0
  567. package/clis/v2ex/user.js +1 -0
  568. package/clis/wanfang/search.js +1 -0
  569. package/clis/web/read.js +1 -0
  570. package/clis/weibo/comments.js +1 -0
  571. package/clis/weibo/favorites.js +1 -0
  572. package/clis/weibo/feed.js +3 -1
  573. package/clis/weibo/hot.js +1 -0
  574. package/clis/weibo/me.js +1 -0
  575. package/clis/weibo/post.js +1 -0
  576. package/clis/weibo/publish.js +1 -0
  577. package/clis/weibo/search.js +8 -2
  578. package/clis/weibo/user.js +1 -0
  579. package/clis/weixin/create-draft.js +1 -0
  580. package/clis/weixin/download.js +1 -0
  581. package/clis/weixin/drafts.js +1 -0
  582. package/clis/weread/ai-outline.js +1 -0
  583. package/clis/weread/book.js +1 -0
  584. package/clis/weread/highlights.js +1 -0
  585. package/clis/weread/notebooks.js +1 -0
  586. package/clis/weread/notes.js +1 -0
  587. package/clis/weread/ranking.js +1 -0
  588. package/clis/weread/search.js +1 -0
  589. package/clis/weread/shelf.js +1 -0
  590. package/clis/wikipedia/random.js +1 -0
  591. package/clis/wikipedia/search.js +1 -0
  592. package/clis/wikipedia/summary.js +1 -0
  593. package/clis/wikipedia/trending.js +1 -0
  594. package/clis/xianyu/chat.js +1 -0
  595. package/clis/xianyu/item.js +1 -0
  596. package/clis/xianyu/search.js +1 -0
  597. package/clis/xiaoe/catalog.js +2 -1
  598. package/clis/xiaoe/content.js +1 -0
  599. package/clis/xiaoe/courses.js +1 -0
  600. package/clis/xiaoe/detail.js +1 -0
  601. package/clis/xiaoe/play-url.js +1 -0
  602. package/clis/xiaohongshu/comments.js +1 -0
  603. package/clis/xiaohongshu/creator-note-detail.js +1 -0
  604. package/clis/xiaohongshu/creator-notes-summary.js +1 -0
  605. package/clis/xiaohongshu/creator-notes.js +1 -0
  606. package/clis/xiaohongshu/creator-profile.js +1 -0
  607. package/clis/xiaohongshu/creator-stats.js +1 -0
  608. package/clis/xiaohongshu/download.js +1 -0
  609. package/clis/xiaohongshu/feed.js +2 -1
  610. package/clis/xiaohongshu/note.js +1 -0
  611. package/clis/xiaohongshu/notifications.js +1 -0
  612. package/clis/xiaohongshu/publish.js +1 -0
  613. package/clis/xiaohongshu/search.js +1 -0
  614. package/clis/xiaohongshu/user.js +1 -0
  615. package/clis/xiaoyuzhou/download.js +1 -0
  616. package/clis/xiaoyuzhou/episode.js +1 -0
  617. package/clis/xiaoyuzhou/podcast-episodes.js +1 -0
  618. package/clis/xiaoyuzhou/podcast.js +1 -0
  619. package/clis/xiaoyuzhou/transcript.js +1 -0
  620. package/clis/xueqiu/comments.js +1 -0
  621. package/clis/xueqiu/earnings-date.js +1 -0
  622. package/clis/xueqiu/feed.js +1 -0
  623. package/clis/xueqiu/fund-holdings.js +1 -0
  624. package/clis/xueqiu/fund-snapshot.js +1 -0
  625. package/clis/xueqiu/groups.js +1 -0
  626. package/clis/xueqiu/hot-stock.js +1 -0
  627. package/clis/xueqiu/hot.js +1 -0
  628. package/clis/xueqiu/kline.js +1 -0
  629. package/clis/xueqiu/search.js +1 -0
  630. package/clis/xueqiu/stock.js +1 -0
  631. package/clis/xueqiu/watchlist.js +1 -0
  632. package/clis/yahoo-finance/quote.js +1 -0
  633. package/clis/yollomi/background.js +1 -0
  634. package/clis/yollomi/edit.js +1 -0
  635. package/clis/yollomi/face-swap.js +1 -0
  636. package/clis/yollomi/generate.js +1 -0
  637. package/clis/yollomi/models.js +1 -0
  638. package/clis/yollomi/object-remover.js +1 -0
  639. package/clis/yollomi/remove-bg.js +1 -0
  640. package/clis/yollomi/restore.js +1 -0
  641. package/clis/yollomi/try-on.js +1 -0
  642. package/clis/yollomi/upload.js +1 -0
  643. package/clis/yollomi/upscale.js +1 -0
  644. package/clis/yollomi/video.js +1 -0
  645. package/clis/youtube/channel.js +1 -0
  646. package/clis/youtube/comments.js +1 -0
  647. package/clis/youtube/feed.js +8 -7
  648. package/clis/youtube/feed.test.js +131 -0
  649. package/clis/youtube/history.js +1 -0
  650. package/clis/youtube/like.js +1 -0
  651. package/clis/youtube/playlist.js +1 -0
  652. package/clis/youtube/search.js +1 -0
  653. package/clis/youtube/subscribe.js +1 -0
  654. package/clis/youtube/subscriptions.js +1 -0
  655. package/clis/youtube/transcript.js +1 -0
  656. package/clis/youtube/unlike.js +1 -0
  657. package/clis/youtube/unsubscribe.js +1 -0
  658. package/clis/youtube/video.js +1 -0
  659. package/clis/youtube/watch-later.js +1 -0
  660. package/clis/yuanbao/ask.js +1 -0
  661. package/clis/yuanbao/new.js +1 -0
  662. package/clis/zhihu/answer.js +1 -0
  663. package/clis/zhihu/collection.js +1 -0
  664. package/clis/zhihu/collections.js +1 -0
  665. package/clis/zhihu/comment.js +1 -0
  666. package/clis/zhihu/download.js +1 -0
  667. package/clis/zhihu/favorite.js +1 -0
  668. package/clis/zhihu/follow.js +1 -0
  669. package/clis/zhihu/hot.js +1 -0
  670. package/clis/zhihu/like.js +1 -0
  671. package/clis/zhihu/question.js +1 -0
  672. package/clis/zhihu/search.js +1 -0
  673. package/clis/zlibrary/info.js +1 -0
  674. package/clis/zlibrary/search.js +1 -0
  675. package/clis/zsxq/dynamics.js +1 -0
  676. package/clis/zsxq/groups.js +1 -0
  677. package/clis/zsxq/search.js +1 -0
  678. package/clis/zsxq/topic.js +1 -0
  679. package/clis/zsxq/topics.js +1 -0
  680. package/dist/src/adapter-source.test.js +1 -1
  681. package/dist/src/browser/analyze.test.js +2 -0
  682. package/dist/src/browser/base-page.d.ts +9 -0
  683. package/dist/src/browser/base-page.js +72 -3
  684. package/dist/src/browser/base-page.test.js +42 -0
  685. package/dist/src/browser/cdp.js +6 -0
  686. package/dist/src/browser/cdp.test.js +3 -0
  687. package/dist/src/browser/page.d.ts +1 -0
  688. package/dist/src/browser/page.js +6 -0
  689. package/dist/src/browser/page.test.js +17 -0
  690. package/dist/src/browser/target-resolver.d.ts +10 -3
  691. package/dist/src/browser/target-resolver.js +16 -10
  692. package/dist/src/browser/verify-fixture.d.ts +6 -1
  693. package/dist/src/browser/verify-fixture.js +87 -0
  694. package/dist/src/browser/verify-fixture.test.js +44 -1
  695. package/dist/src/build-manifest.js +3 -0
  696. package/dist/src/build-manifest.test.js +18 -12
  697. package/dist/src/capabilityRouting.test.js +1 -1
  698. package/dist/src/cli.js +141 -5
  699. package/dist/src/cli.test.js +179 -0
  700. package/dist/src/commanderAdapter.d.ts +1 -1
  701. package/dist/src/commanderAdapter.js +17 -7
  702. package/dist/src/commanderAdapter.test.js +7 -6
  703. package/dist/src/convention-audit.d.ts +50 -0
  704. package/dist/src/convention-audit.js +546 -0
  705. package/dist/src/convention-audit.test.d.ts +1 -0
  706. package/dist/src/convention-audit.test.js +226 -0
  707. package/dist/src/discovery.js +2 -0
  708. package/dist/src/doctor.js +0 -1
  709. package/dist/src/doctor.test.js +1 -1
  710. package/dist/src/engine.test.js +10 -10
  711. package/dist/src/execution.js +1 -1
  712. package/dist/src/execution.test.js +9 -9
  713. package/dist/src/help.d.ts +15 -0
  714. package/dist/src/help.js +149 -0
  715. package/dist/src/main.js +13 -7
  716. package/dist/src/manifest-types.d.ts +2 -0
  717. package/dist/src/plugin.test.js +26 -26
  718. package/dist/src/registry.d.ts +5 -0
  719. package/dist/src/registry.js +8 -0
  720. package/dist/src/registry.test.js +27 -20
  721. package/dist/src/serialization.d.ts +4 -0
  722. package/dist/src/serialization.js +27 -1
  723. package/dist/src/serialization.test.js +24 -2
  724. package/dist/src/types.d.ts +2 -0
  725. package/package.json +4 -1
  726. package/scripts/check-listing-id-pairing.mjs +193 -0
  727. package/scripts/check-silent-column-drop.mjs +105 -0
  728. package/scripts/check-typed-error-lint.mjs +118 -0
  729. package/scripts/silent-column-drop-baseline.json +962 -0
  730. package/scripts/typed-error-lint-baseline.json +1586 -0
@@ -0,0 +1,173 @@
1
+ /**
2
+ * dianping shop — read shop detail by shop ID (the alphanumeric handle in
3
+ * `https://www.dianping.com/shop/<shop_id>`).
4
+ *
5
+ * Returns a key/value sheet so the table view stays readable when fields
6
+ * are absent (phone is hidden on PC web — only shows in app — so the row
7
+ * surfaces it as `null` rather than fabricating).
8
+ *
9
+ * The DOM extractor is defined as a plain top-level function and injected
10
+ * into `page.evaluate` via `.toString()`, so the same code is exercised by
11
+ * a JSDOM-against-frozen-fixture unit test (see dianping.test.js). Mocked
12
+ * `page.evaluate` tests can't catch in-browser bugs like the original
13
+ * full-width `【】` vs ASCII `[]` mismatch — fixture tests can.
14
+ */
15
+
16
+ import { cli, Strategy } from '@jackwener/opencli/registry';
17
+ import {
18
+ SHOP_COLUMNS,
19
+ detectAuthOrPageFailure,
20
+ normalizeShopId,
21
+ parsePrice,
22
+ parseReviewCount,
23
+ wrapDianpingStep,
24
+ } from './utils.js';
25
+
26
+ /**
27
+ * Pure DOM extractor for the dianping shop page.
28
+ *
29
+ * Uses bare `document` / `location` so it runs identically in:
30
+ * - the live browser (injected via `${extractShopFields.toString()}`)
31
+ * - JSDOM unit tests (which swap `globalThis.document` / `globalThis.location`)
32
+ *
33
+ * Returns either `{ ok: true, ...fields }` on success or
34
+ * `{ ok: false, sample, url }` so the caller can classify the failure.
35
+ */
36
+ export function extractShopFields() {
37
+ const head = document.querySelector('.shop-head');
38
+ if (!head) {
39
+ const body = document.body;
40
+ const sampleText = (body && (body.innerText || body.textContent)) || '';
41
+ return {
42
+ ok: false,
43
+ sample: sampleText.slice(0, 800),
44
+ url: location.href,
45
+ };
46
+ }
47
+ const headText = head.textContent.trim().replace(/\s+/g, ' ');
48
+ // Shop name: dianping puts it as "【芈重山老火锅(五道口店)】" at
49
+ // the head of document.title (full-width 【】, not ASCII []).
50
+ // Try selectors first, then fall back to title parsing.
51
+ const titleEl = document.querySelector('.shop-name, .shop-head h2, .shop-head h1');
52
+ let name = (titleEl && titleEl.textContent && titleEl.textContent.trim()) || '';
53
+ if (!name) {
54
+ const t = document.title || '';
55
+ const m = t.match(/【([^】]+)】/);
56
+ if (m) name = m[1].trim();
57
+ }
58
+ const ratingEl = document.querySelector('.star-score');
59
+ const ratingText = (ratingEl && ratingEl.textContent && ratingEl.textContent.trim()) || '';
60
+ const features = Array.from(document.querySelectorAll('.shop-feature'))
61
+ .map((f) => f.textContent.trim())
62
+ .filter(Boolean);
63
+ const addressEl = document.querySelector('.desc-info');
64
+ const address = (addressEl && addressEl.textContent && addressEl.textContent.trim()) || '';
65
+ const subwayMatch = headText.match(/距(?:地铁)?[^\s]+?步行\d+m/);
66
+ const subway = subwayMatch ? subwayMatch[0] : '';
67
+
68
+ // Reviews: prefer .reviews / .review-num selector — its text is
69
+ // "21241条" cleanly. Whitespace-collapsed headText fuses the
70
+ // rating "4.8" with review digits ("4.821241条"), so a
71
+ // head-wide /\d+条/ regex captures "4.821241" and rounds to 5.
72
+ const reviewEl = document.querySelector('.reviews, .review-num, .reviewCount, .reviewCountSentence');
73
+ let reviewsRaw = (reviewEl && reviewEl.textContent && reviewEl.textContent.trim()) || '';
74
+ if (!reviewsRaw) {
75
+ const titleReviewEl = document.querySelector('.review-title');
76
+ const titleText = titleReviewEl && titleReviewEl.textContent;
77
+ const titleM = titleText && titleText.match(/评价\(([\d.,万]+)\)/);
78
+ if (titleM) reviewsRaw = titleM[1];
79
+ }
80
+
81
+ // Shop-head text holds price + cuisine + district + rank.
82
+ const priceMatch = headText.match(/[¥¥]\s*\d+(?:\.\d+)?/);
83
+
84
+ // Try to read score breakdown ("口味:4.8 环境:4.8 服务:4.8 食材:4.9").
85
+ const breakdown = {};
86
+ const breakKeys = ['口味', '环境', '服务', '食材'];
87
+ for (const key of breakKeys) {
88
+ const m = headText.match(new RegExp(key + '[::]\\s*([0-9.]+)'));
89
+ if (m) breakdown[key] = Number(m[1]);
90
+ }
91
+
92
+ // Hours: "营业中 11:00-次日02:00" / "今日休息".
93
+ const hoursMatch = headText.match(/营业中[^\s]*\d{1,2}:\d{2}-(?:次日)?\d{1,2}:\d{2}|今日休息|暂停营业/);
94
+
95
+ // Rank line: "海淀区 重庆火锅 口味榜 · 第1名".
96
+ const rankMatch = headText.match(/[^\s]+?(?:口味|人气|环境|服务)榜\s*[·•]\s*第\d+名/);
97
+
98
+ return {
99
+ ok: true,
100
+ name,
101
+ rating: ratingText,
102
+ reviewsRaw,
103
+ priceRaw: (priceMatch && priceMatch[0]) || '',
104
+ breakdown,
105
+ features,
106
+ address,
107
+ subway,
108
+ hours: (hoursMatch && hoursMatch[0]) || '',
109
+ rank: (rankMatch && rankMatch[0]) || '',
110
+ url: location.href,
111
+ };
112
+ }
113
+
114
+ cli({
115
+ site: 'dianping',
116
+ name: 'shop',
117
+ access: 'read',
118
+ aliases: ['detail'],
119
+ description: '大众点评店铺详情(按 shop_id)',
120
+ domain: 'www.dianping.com',
121
+ strategy: Strategy.COOKIE,
122
+ args: [
123
+ { name: 'shop_id', required: true, positional: true, help: '店铺 ID(来自 search 的 shop_id 列,或 https://www.dianping.com/shop/<id> URL 段)' },
124
+ ],
125
+ columns: SHOP_COLUMNS,
126
+ func: async (page, kwargs) => {
127
+ const shopId = normalizeShopId(kwargs.shop_id);
128
+
129
+ const url = `https://www.dianping.com/shop/${shopId}`;
130
+ await wrapDianpingStep(`shop ${shopId} navigation`, async () => {
131
+ await page.goto(url);
132
+ await page.wait(3);
133
+ });
134
+
135
+ const data = await wrapDianpingStep(
136
+ `shop ${shopId} extraction`,
137
+ () => page.evaluate(`(${extractShopFields.toString()})()`),
138
+ );
139
+
140
+ if (!data || !data.ok) {
141
+ detectAuthOrPageFailure(
142
+ { text: String(data?.sample || ''), url: String(data?.url || url) },
143
+ `shop ${shopId}`,
144
+ { emptyPatterns: [/商户不存在|店铺不存在|店铺已关闭|页面不存在|404|已下线|没有找到相关商户/i] },
145
+ );
146
+ }
147
+
148
+ const rating = data.rating ? Number(data.rating) : null;
149
+ const reviews = parseReviewCount(data.reviewsRaw);
150
+ const price = parsePrice(data.priceRaw);
151
+ const breakdown = data.breakdown || {};
152
+
153
+ const fields = [
154
+ ['shop_id', shopId],
155
+ ['name', data.name || ''],
156
+ ['rating', Number.isFinite(rating) ? rating : null],
157
+ ['reviews', reviews],
158
+ ['price', price],
159
+ ['rank', data.rank || ''],
160
+ ['taste', Number.isFinite(breakdown['口味']) ? breakdown['口味'] : null],
161
+ ['environment', Number.isFinite(breakdown['环境']) ? breakdown['环境'] : null],
162
+ ['service', Number.isFinite(breakdown['服务']) ? breakdown['服务'] : null],
163
+ ['ingredients', Number.isFinite(breakdown['食材']) ? breakdown['食材'] : null],
164
+ ['hours', data.hours || ''],
165
+ ['address', data.address || ''],
166
+ ['subway', data.subway || ''],
167
+ ['features', (data.features || []).join(', ')],
168
+ ['url', data.url || url],
169
+ ];
170
+
171
+ return fields.map(([field, value]) => ({ field, value }));
172
+ },
173
+ });
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Shared helpers for dianping (大众点评) adapters.
3
+ *
4
+ * Mobile m.dianping.com is intentionally crippled to push users to the
5
+ * native app — body renders as 2 chars when probed from desktop UA.
6
+ * www.dianping.com (PC site) returns the full search HTML server-side
7
+ * and does NOT require JS hydration, so adapters target it directly.
8
+ */
9
+
10
+ import {
11
+ ArgumentError,
12
+ AuthRequiredError,
13
+ CommandExecutionError,
14
+ EmptyResultError,
15
+ } from '@jackwener/opencli/errors';
16
+
17
+ /**
18
+ * Common Chinese cities → dianping cityId.
19
+ * Source: dianping.com /citylist URL structure (cityId is the integer in
20
+ * the search path: /search/keyword/{cityId}/0_keyword).
21
+ */
22
+ export const CITY_ID = {
23
+ beijing: 2, '北京': 2,
24
+ shanghai: 1, '上海': 1,
25
+ guangzhou: 4, '广州': 4,
26
+ shenzhen: 7, '深圳': 7,
27
+ hangzhou: 3, '杭州': 3,
28
+ chengdu: 8, '成都': 8,
29
+ chongqing: 9, '重庆': 9,
30
+ nanjing: 5, '南京': 5,
31
+ suzhou: 6, '苏州': 6,
32
+ xian: 17, '西安': 17,
33
+ wuhan: 16, '武汉': 16,
34
+ tianjin: 10, '天津': 10,
35
+ qingdao: 21, '青岛': 21,
36
+ changsha: 344, '长沙': 344,
37
+ dalian: 19, '大连': 19,
38
+ shenyang: 18, '沈阳': 18,
39
+ kunming: 25, '昆明': 25,
40
+ fuzhou: 110, '福州': 110,
41
+ xiamen: 14, '厦门': 14,
42
+ hefei: 26, '合肥': 26,
43
+ };
44
+
45
+ export const SEARCH_COLUMNS = ['rank', 'shop_id', 'name', 'rating', 'reviews', 'price', 'cuisine', 'district', 'url'];
46
+ export const SHOP_COLUMNS = ['field', 'value'];
47
+
48
+ /**
49
+ * Resolve a city argument (name or id) to a numeric cityId.
50
+ * Returns null when the cookie's default city should be used.
51
+ */
52
+ export function resolveCityId(cityArg) {
53
+ if (cityArg == null || cityArg === '') return null;
54
+ const raw = String(cityArg).trim().toLowerCase();
55
+ if (/^\d+$/.test(raw)) return Number(raw);
56
+ const id = CITY_ID[raw];
57
+ if (!id) {
58
+ const names = Object.keys(CITY_ID).filter((k) => /^[a-z]+$/.test(k)).join(', ');
59
+ throw new ArgumentError(
60
+ 'city',
61
+ `unknown city '${cityArg}'. pass a numeric cityId or one of: ${names}`,
62
+ );
63
+ }
64
+ return id;
65
+ }
66
+
67
+ export function requireSearchLimit(value) {
68
+ const raw = value == null || value === '' ? 15 : value;
69
+ const limit = typeof raw === 'number' ? raw : Number(String(raw).trim());
70
+ if (!Number.isInteger(limit) || limit < 1 || limit > 15) {
71
+ throw new ArgumentError('limit must be an integer between 1 and 15 (dianping single page)');
72
+ }
73
+ return limit;
74
+ }
75
+
76
+ export function normalizeShopId(rawInput) {
77
+ const raw = String(rawInput || '').trim();
78
+ if (!raw) throw new ArgumentError('shop_id must be a non-empty string');
79
+
80
+ const idMatch = raw.match(/\/shop\/([^?#/]+)/);
81
+ const shopId = idMatch ? idMatch[1] : raw;
82
+ if (!/^[A-Za-z0-9_-]+$/.test(shopId)) {
83
+ throw new ArgumentError(`'${raw}' does not look like a dianping shop id`);
84
+ }
85
+ return shopId;
86
+ }
87
+
88
+ export function wrapDianpingStep(label, fn) {
89
+ return Promise.resolve()
90
+ .then(fn)
91
+ .catch((err) => {
92
+ if (err?.code) throw err;
93
+ const message = err?.message || String(err);
94
+ throw new CommandExecutionError(`dianping ${label} failed: ${message}`);
95
+ });
96
+ }
97
+
98
+ /**
99
+ * Throw the right typed error for a dianping page that didn't render data.
100
+ * The site short-circuits HTML when bot/login checks trip — typically
101
+ * redirects to verify.meituan.com (Yoda icon-tap captcha) or to a login
102
+ * page when the cookie is missing.
103
+ */
104
+ export function detectAuthOrPageFailure({ text = '', url = '' }, contextHint, { emptyPatterns = [] } = {}) {
105
+ const signal = `${url} ${text}`;
106
+ if (/verify\.meituan\.com|verifyimg|身份核实|请依次点击|美团安全验证|Yoda/i.test(signal)) {
107
+ throw new AuthRequiredError(
108
+ 'dianping.com',
109
+ `dianping ${contextHint} blocked by captcha — open ${url || 'www.dianping.com'} manually in this profile and solve the captcha, then retry`,
110
+ );
111
+ }
112
+ if (/login\.dianping\.com|account\.dianping\.com|请先登录|未登录|请登录/.test(signal)) {
113
+ throw new AuthRequiredError(
114
+ 'dianping.com',
115
+ `dianping ${contextHint} requires login — sign in to dianping.com in this profile, then retry`,
116
+ );
117
+ }
118
+ if (emptyPatterns.some((pattern) => pattern.test(signal))) {
119
+ throw new EmptyResultError(`dianping ${contextHint}`);
120
+ }
121
+ const sample = text ? `; sample: ${String(text).slice(0, 160)}` : '';
122
+ throw new CommandExecutionError(
123
+ `dianping ${contextHint} did not render expected data${sample}`,
124
+ 'This usually means dianping changed its HTML, returned an unexpected error page, or the browser profile hit an unrecognized anti-bot state.',
125
+ );
126
+ }
127
+
128
+ /**
129
+ * Parse "21231" / "1.2万" review-count strings into integers.
130
+ * Returns null when the input has no parseable digits.
131
+ */
132
+ export function parseReviewCount(raw) {
133
+ if (raw == null) return null;
134
+ const s = String(raw).trim();
135
+ if (!s) return null;
136
+ const wanMatch = s.match(/^([\d.]+)\s*万/);
137
+ if (wanMatch) {
138
+ const n = Number(wanMatch[1]);
139
+ return Number.isFinite(n) ? Math.round(n * 10000) : null;
140
+ }
141
+ const plainMatch = s.match(/(\d+(?:\.\d+)?)/);
142
+ if (!plainMatch) return null;
143
+ const n = Number(plainMatch[1]);
144
+ return Number.isFinite(n) ? Math.round(n) : null;
145
+ }
146
+
147
+ /**
148
+ * Parse "¥109" / "¥109" / "人均¥109" into a numeric yuan value.
149
+ * Returns null when no price is present (some shops omit price entirely).
150
+ */
151
+ export function parsePrice(raw) {
152
+ if (!raw) return null;
153
+ const m = String(raw).match(/[¥¥]\s*(\d+(?:\.\d+)?)/);
154
+ if (!m) return null;
155
+ const n = Number(m[1]);
156
+ return Number.isFinite(n) ? n : null;
157
+ }
@@ -2,6 +2,7 @@ import { cli, Strategy } from '@jackwener/opencli/registry';
2
2
  cli({
3
3
  site: 'dictionary',
4
4
  name: 'examples',
5
+ access: 'read',
5
6
  description: 'Read real-world example sentences utilizing the word',
6
7
  domain: 'api.dictionaryapi.dev',
7
8
  strategy: Strategy.PUBLIC,
@@ -2,6 +2,7 @@ import { cli, Strategy } from '@jackwener/opencli/registry';
2
2
  cli({
3
3
  site: 'dictionary',
4
4
  name: 'search',
5
+ access: 'read',
5
6
  description: 'Search the Free Dictionary API for definitions, parts of speech, and pronunciations.',
6
7
  domain: 'api.dictionaryapi.dev',
7
8
  strategy: Strategy.PUBLIC,
@@ -2,6 +2,7 @@ import { cli, Strategy } from '@jackwener/opencli/registry';
2
2
  cli({
3
3
  site: 'dictionary',
4
4
  name: 'synonyms',
5
+ access: 'read',
5
6
  description: 'Find synonyms for a specific word',
6
7
  domain: 'api.dictionaryapi.dev',
7
8
  strategy: Strategy.PUBLIC,
@@ -2,6 +2,7 @@ import { cli, Strategy } from '@jackwener/opencli/registry';
2
2
  export const channelsCommand = cli({
3
3
  site: 'discord-app',
4
4
  name: 'channels',
5
+ access: 'read',
5
6
  description: 'List channels in the current Discord server',
6
7
  domain: 'localhost',
7
8
  strategy: Strategy.UI,
@@ -73,6 +73,7 @@ function buildDeleteScript(messageId) {
73
73
  cli({
74
74
  site: 'discord-app',
75
75
  name: 'delete',
76
+ access: 'write',
76
77
  description: 'Delete a message by its ID in the active Discord channel',
77
78
  domain: 'localhost',
78
79
  strategy: Strategy.UI,
@@ -2,6 +2,7 @@ import { cli, Strategy } from '@jackwener/opencli/registry';
2
2
  export const membersCommand = cli({
3
3
  site: 'discord-app',
4
4
  name: 'members',
5
+ access: 'read',
5
6
  description: 'List online members in the current Discord channel',
6
7
  domain: 'localhost',
7
8
  strategy: Strategy.UI,
@@ -2,6 +2,7 @@ import { cli, Strategy } from '@jackwener/opencli/registry';
2
2
  export const readCommand = cli({
3
3
  site: 'discord-app',
4
4
  name: 'read',
5
+ access: 'read',
5
6
  description: 'Read recent messages from the active Discord channel',
6
7
  domain: 'localhost',
7
8
  strategy: Strategy.UI,
@@ -2,6 +2,7 @@ import { cli, Strategy } from '@jackwener/opencli/registry';
2
2
  export const searchCommand = cli({
3
3
  site: 'discord-app',
4
4
  name: 'search',
5
+ access: 'read',
5
6
  description: 'Search messages in the current Discord server/channel (Cmd+F)',
6
7
  domain: 'localhost',
7
8
  strategy: Strategy.UI,
@@ -2,6 +2,7 @@ import { cli, Strategy } from '@jackwener/opencli/registry';
2
2
  export const sendCommand = cli({
3
3
  site: 'discord-app',
4
4
  name: 'send',
5
+ access: 'write',
5
6
  description: 'Send a message in the active Discord channel',
6
7
  domain: 'localhost',
7
8
  strategy: Strategy.UI,
@@ -2,6 +2,7 @@ import { cli, Strategy } from '@jackwener/opencli/registry';
2
2
  export const serversCommand = cli({
3
3
  site: 'discord-app',
4
4
  name: 'servers',
5
+ access: 'read',
5
6
  description: 'List all Discord servers (guilds) in the sidebar',
6
7
  domain: 'localhost',
7
8
  strategy: Strategy.UI,
@@ -2,6 +2,7 @@ import { cli, Strategy } from '@jackwener/opencli/registry';
2
2
  export const statusCommand = cli({
3
3
  site: 'discord-app',
4
4
  name: 'status',
5
+ access: 'read',
5
6
  description: 'Check active CDP connection to Discord Desktop',
6
7
  domain: 'localhost',
7
8
  strategy: Strategy.UI,
@@ -3,6 +3,7 @@ import { loadDoubanBookHot } from './utils.js';
3
3
  cli({
4
4
  site: 'douban',
5
5
  name: 'book-hot',
6
+ access: 'read',
6
7
  description: '豆瓣图书热门榜单',
7
8
  domain: 'book.douban.com',
8
9
  strategy: Strategy.COOKIE,
@@ -13,6 +13,7 @@ function buildDoubanPhotoFilename(subjectId, photo) {
13
13
  cli({
14
14
  site: 'douban',
15
15
  name: 'download',
16
+ access: 'read',
16
17
  description: '下载电影海报/剧照图片',
17
18
  domain: 'movie.douban.com',
18
19
  strategy: Strategy.COOKIE,
@@ -3,6 +3,7 @@ import { getSelfUid } from './utils.js';
3
3
  cli({
4
4
  site: 'douban',
5
5
  name: 'marks',
6
+ access: 'read',
6
7
  description: '导出个人观影标记',
7
8
  domain: 'movie.douban.com',
8
9
  strategy: Strategy.COOKIE,
@@ -3,12 +3,13 @@ import { loadDoubanMovieHot } from './utils.js';
3
3
  cli({
4
4
  site: 'douban',
5
5
  name: 'movie-hot',
6
+ access: 'read',
6
7
  description: '豆瓣电影热门榜单',
7
8
  domain: 'movie.douban.com',
8
9
  strategy: Strategy.COOKIE,
9
10
  args: [
10
11
  { name: 'limit', type: 'int', default: 20, help: '返回的电影数量' },
11
12
  ],
12
- columns: ['rank', 'title', 'rating', 'quote', 'director', 'year', 'region', 'url'],
13
+ columns: ['rank', 'id', 'title', 'rating', 'votes', 'year', 'url'],
13
14
  func: async (page, args) => loadDoubanMovieHot(page, Number(args.limit) || 20),
14
15
  });
@@ -0,0 +1,14 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { getRegistry } from '@jackwener/opencli/registry';
3
+ import './movie-hot.js';
4
+
5
+ describe('douban movie-hot command', () => {
6
+ it('exposes only fields available from the chart page', () => {
7
+ const command = getRegistry().get('douban/movie-hot');
8
+
9
+ expect(command?.columns).toEqual(['rank', 'id', 'title', 'rating', 'votes', 'year', 'url']);
10
+ expect(command?.columns).not.toContain('director');
11
+ expect(command?.columns).not.toContain('region');
12
+ expect(command?.columns).not.toContain('quote');
13
+ });
14
+ });
@@ -3,6 +3,7 @@ import { loadDoubanSubjectPhotos, normalizeDoubanSubjectId } from './utils.js';
3
3
  cli({
4
4
  site: 'douban',
5
5
  name: 'photos',
6
+ access: 'read',
6
7
  description: '获取电影海报/剧照图片列表',
7
8
  domain: 'movie.douban.com',
8
9
  strategy: Strategy.COOKIE,
@@ -11,7 +12,7 @@ cli({
11
12
  { name: 'type', default: 'Rb', help: '豆瓣 photos 的 type 参数,默认 Rb(海报)' },
12
13
  { name: 'limit', type: 'int', default: 120, help: '最多返回多少张图片' },
13
14
  ],
14
- columns: ['index', 'title', 'image_url', 'detail_url'],
15
+ columns: ['index', 'photo_id', 'subject_id', 'title', 'image_url', 'detail_url'],
15
16
  func: async (page, kwargs) => {
16
17
  const subjectId = normalizeDoubanSubjectId(String(kwargs.id || ''));
17
18
  const data = await loadDoubanSubjectPhotos(page, subjectId, {
@@ -3,6 +3,7 @@ import { getSelfUid } from './utils.js';
3
3
  cli({
4
4
  site: 'douban',
5
5
  name: 'reviews',
6
+ access: 'read',
6
7
  description: '导出个人影评',
7
8
  domain: 'movie.douban.com',
8
9
  strategy: Strategy.COOKIE,
@@ -3,6 +3,7 @@ import { searchDouban } from './utils.js';
3
3
  cli({
4
4
  site: 'douban',
5
5
  name: 'search',
6
+ access: 'read',
6
7
  description: '搜索豆瓣电影、图书或音乐',
7
8
  domain: 'search.douban.com',
8
9
  strategy: Strategy.COOKIE,
@@ -4,6 +4,7 @@ import { loadDoubanSubjectDetail } from './utils.js';
4
4
  cli({
5
5
  site: 'douban',
6
6
  name: 'subject',
7
+ access: 'read',
7
8
  description: '获取豆瓣条目详情',
8
9
  domain: 'movie.douban.com',
9
10
  strategy: Strategy.COOKIE,
@@ -2,6 +2,7 @@ import { cli, Strategy } from '@jackwener/opencli/registry';
2
2
  cli({
3
3
  site: 'douban',
4
4
  name: 'top250',
5
+ access: 'read',
5
6
  description: '豆瓣电影 Top250',
6
7
  domain: 'movie.douban.com',
7
8
  strategy: Strategy.COOKIE,
@@ -502,26 +502,20 @@ export async function loadDoubanMovieHot(page, limit) {
502
502
  let url = titleEl?.getAttribute('href') || '';
503
503
  if (!title || !url) continue;
504
504
  if (!url.startsWith('http')) url = 'https://movie.douban.com' + url;
505
+ const id = url.match(/subject\\/(\\d+)/)?.[1] || '';
505
506
 
506
507
  const info = normalize(el.querySelector('.pl2 p')?.textContent);
507
- const infoParts = info.split('/').map((part) => part.trim()).filter(Boolean);
508
- const releaseIndex = (() => {
509
- for (let i = infoParts.length - 1; i >= 0; i -= 1) {
510
- if (/\\d{4}-\\d{2}-\\d{2}|\\d{4}\\/\\d{2}\\/\\d{2}/.test(infoParts[i])) return i;
511
- }
512
- return -1;
513
- })();
514
- const directorPart = releaseIndex >= 1 ? infoParts[releaseIndex - 1] : '';
515
- const regionPart = releaseIndex >= 2 ? infoParts[releaseIndex - 2] : '';
516
508
  const yearMatch = info.match(/\\b(19|20)\\d{2}\\b/);
509
+ const votesText = normalize(el.querySelector('.star .pl')?.textContent);
510
+ const votes = parseInt(votesText.replace(/[^0-9]/g, ''), 10) || 0;
511
+
517
512
  results.push({
518
513
  rank: results.length + 1,
514
+ id,
519
515
  title,
520
516
  rating: parseFloat(normalize(el.querySelector('.rating_nums')?.textContent)) || 0,
521
- quote: normalize(el.querySelector('.inq')?.textContent),
522
- director: directorPart.replace(/^导演:\\s*/, ''),
517
+ votes,
523
518
  year: yearMatch?.[0] || '',
524
- region: regionPart,
525
519
  url,
526
520
  cover: el.querySelector('img')?.getAttribute('src') || '',
527
521
  });
@@ -530,7 +524,11 @@ export async function loadDoubanMovieHot(page, limit) {
530
524
  return results;
531
525
  })()
532
526
  `);
533
- return Array.isArray(data) ? data : [];
527
+ const results = Array.isArray(data) ? data : [];
528
+ if (!results.length) {
529
+ throw new EmptyResultError('douban movie-hot', 'No movie chart rows were parsed from movie.douban.com/chart.');
530
+ }
531
+ return results;
534
532
  }
535
533
  export function inferDoubanSearchResultType(searchType, item = {}) {
536
534
  const fallbackType = String(searchType || '').trim() || 'movie';