@jackwener/opencli 1.7.12 → 1.7.14

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 (419) hide show
  1. package/README.md +8 -7
  2. package/README.zh-CN.md +9 -8
  3. package/cli-manifest.json +12128 -6665
  4. package/clis/1point3acres/digest.js +35 -0
  5. package/clis/1point3acres/forum.js +51 -0
  6. package/clis/1point3acres/forums.js +44 -0
  7. package/clis/1point3acres/hot.js +35 -0
  8. package/clis/1point3acres/latest.js +35 -0
  9. package/clis/1point3acres/notifications.js +64 -0
  10. package/clis/1point3acres/search.js +71 -0
  11. package/clis/1point3acres/thread.js +117 -0
  12. package/clis/1point3acres/user.js +77 -0
  13. package/clis/1point3acres/utils.js +247 -0
  14. package/clis/_shared/desktop-commands.js +4 -0
  15. package/clis/aibase/news.js +110 -0
  16. package/clis/aibase/news.test.js +59 -0
  17. package/clis/amazon/discussion.test.js +1 -28
  18. package/clis/antigravity/watch.js +3 -2
  19. package/clis/arxiv/author.js +44 -0
  20. package/clis/baidu-scholar/search.js +0 -1
  21. package/clis/bbc/topic.js +57 -0
  22. package/clis/bbc/utils.js +79 -0
  23. package/clis/chaoxing/assignments.js +1 -1
  24. package/clis/chaoxing/exams.js +1 -1
  25. package/clis/chatgpt/ask.js +57 -0
  26. package/clis/chatgpt/commands.test.js +45 -0
  27. package/clis/chatgpt/detail.js +46 -0
  28. package/clis/chatgpt/history.js +39 -0
  29. package/clis/chatgpt/image.js +12 -11
  30. package/clis/chatgpt/image.test.js +23 -0
  31. package/clis/chatgpt/new.js +25 -0
  32. package/clis/chatgpt/read.js +43 -0
  33. package/clis/chatgpt/send.js +46 -0
  34. package/clis/chatgpt/status.js +29 -0
  35. package/clis/chatgpt/utils.js +294 -4
  36. package/clis/chatgpt/utils.test.js +13 -0
  37. package/clis/chatgpt-app/ask.js +6 -3
  38. package/clis/chatwise/ask.js +16 -43
  39. package/clis/chatwise/composer.test.js +186 -0
  40. package/clis/chatwise/send.js +2 -24
  41. package/clis/chatwise/utils.js +143 -0
  42. package/clis/claude/ask.js +1 -1
  43. package/clis/claude/detail.js +1 -0
  44. package/clis/claude/history.js +1 -0
  45. package/clis/claude/new.js +1 -0
  46. package/clis/claude/read.js +1 -0
  47. package/clis/claude/send.js +1 -0
  48. package/clis/claude/status.js +1 -0
  49. package/clis/codex/ask.js +15 -9
  50. package/clis/codex/history.js +16 -33
  51. package/clis/codex/projects.js +28 -0
  52. package/clis/codex/read.js +10 -4
  53. package/clis/codex/send.js +10 -3
  54. package/clis/codex/sidebar.js +356 -0
  55. package/clis/codex/sidebar.test.js +329 -0
  56. package/clis/coingecko/categories.js +75 -0
  57. package/clis/coingecko/coin.js +107 -0
  58. package/clis/coingecko/coingecko.test.js +109 -0
  59. package/clis/coingecko/derivatives.js +84 -0
  60. package/clis/coingecko/exchanges.js +74 -0
  61. package/clis/coingecko/global.js +71 -0
  62. package/clis/coingecko/top.js +64 -0
  63. package/clis/coingecko/trending.js +55 -0
  64. package/clis/coupang/add-to-cart.js +21 -13
  65. package/clis/coupang/coupang.test.js +159 -0
  66. package/clis/coupang/product.js +257 -0
  67. package/clis/coupang/search.js +38 -16
  68. package/clis/coupang/utils.js +55 -1
  69. package/clis/crates/crate.js +62 -0
  70. package/clis/crates/search.js +44 -0
  71. package/clis/crates/utils.js +72 -0
  72. package/clis/ctrip/ctrip.test.js +234 -0
  73. package/clis/ctrip/hotel-suggest.js +45 -0
  74. package/clis/ctrip/search.js +22 -68
  75. package/clis/ctrip/utils.js +175 -0
  76. package/clis/cursor/ask.js +6 -3
  77. package/clis/dblp/author.js +133 -0
  78. package/clis/dblp/venue.js +64 -0
  79. package/clis/deepseek/ask.js +12 -7
  80. package/clis/deepseek/ask.test.js +13 -13
  81. package/clis/deepseek/detail.js +38 -0
  82. package/clis/deepseek/detail.test.js +81 -0
  83. package/clis/deepseek/history.js +1 -0
  84. package/clis/deepseek/new.js +1 -0
  85. package/clis/deepseek/read.js +1 -0
  86. package/clis/deepseek/send.js +140 -0
  87. package/clis/deepseek/send.test.js +107 -0
  88. package/clis/deepseek/status.js +1 -0
  89. package/clis/deepseek/utils.js +66 -0
  90. package/clis/deepseek/utils.test.js +107 -1
  91. package/clis/defillama/defillama.test.js +99 -0
  92. package/clis/defillama/protocol.js +84 -0
  93. package/clis/defillama/protocols.js +55 -0
  94. package/clis/defillama/utils.js +99 -0
  95. package/clis/devto/latest.js +74 -0
  96. package/clis/dockerhub/image.js +52 -0
  97. package/clis/dockerhub/search.js +47 -0
  98. package/clis/dockerhub/utils.js +100 -0
  99. package/clis/doubao/ask.js +7 -3
  100. package/clis/doubao/detail.js +1 -0
  101. package/clis/doubao/history.js +1 -0
  102. package/clis/doubao/meeting-summary.js +1 -0
  103. package/clis/doubao/meeting-transcript.js +1 -0
  104. package/clis/doubao/new.js +1 -0
  105. package/clis/doubao/read.js +1 -0
  106. package/clis/doubao/send.js +1 -0
  107. package/clis/doubao/status.js +1 -0
  108. package/clis/douyin/draft.test.js +1 -30
  109. package/clis/endoflife/endoflife.test.js +51 -0
  110. package/clis/endoflife/product.js +55 -0
  111. package/clis/endoflife/utils.js +89 -0
  112. package/clis/facebook/__fixtures__/notifications-page.html +13 -0
  113. package/clis/facebook/notifications.js +326 -30
  114. package/clis/facebook/notifications.test.js +458 -0
  115. package/clis/flathub/app.js +71 -0
  116. package/clis/flathub/flathub.test.js +90 -0
  117. package/clis/flathub/search.js +80 -0
  118. package/clis/flathub/utils.js +114 -0
  119. package/clis/gemini/ask.js +7 -3
  120. package/clis/gemini/ask.test.js +2 -2
  121. package/clis/gemini/deep-research-result.js +6 -2
  122. package/clis/gemini/deep-research-result.test.js +15 -14
  123. package/clis/gemini/deep-research.js +8 -4
  124. package/clis/gemini/deep-research.test.js +15 -18
  125. package/clis/gemini/image.js +7 -2
  126. package/clis/gemini/new.js +1 -0
  127. package/clis/gemini/utils.js +0 -4
  128. package/clis/google-scholar/cite.js +0 -1
  129. package/clis/google-scholar/profile.js +0 -1
  130. package/clis/google-scholar/search.js +0 -1
  131. package/clis/goproxy/goproxy.test.js +103 -0
  132. package/clis/goproxy/module.js +47 -0
  133. package/clis/goproxy/utils.js +165 -0
  134. package/clis/goproxy/versions.js +59 -0
  135. package/clis/gov-law/recent.js +0 -1
  136. package/clis/gov-law/search.js +0 -1
  137. package/clis/gov-policy/__fixtures__/recent.html +16 -0
  138. package/clis/gov-policy/__fixtures__/search.html +41 -0
  139. package/clis/gov-policy/gov-policy.test.js +224 -0
  140. package/clis/gov-policy/recent.js +66 -24
  141. package/clis/gov-policy/search.js +65 -23
  142. package/clis/gov-policy/utils.js +54 -0
  143. package/clis/grok/ask.js +49 -265
  144. package/clis/grok/ask.test.js +21 -46
  145. package/clis/grok/detail.js +60 -0
  146. package/clis/grok/history.js +48 -0
  147. package/clis/grok/{image.ts → image.js} +56 -70
  148. package/clis/grok/image.test.ts +20 -0
  149. package/clis/grok/new.js +20 -0
  150. package/clis/grok/read.js +39 -0
  151. package/clis/grok/send.js +50 -0
  152. package/clis/grok/status.js +41 -0
  153. package/clis/grok/utils.js +326 -0
  154. package/clis/grok/utils.test.js +103 -0
  155. package/clis/hf/datasets.js +88 -0
  156. package/clis/hf/hf.test.js +16 -0
  157. package/clis/hf/models.js +91 -0
  158. package/clis/hf/paper.js +79 -0
  159. package/clis/hf/spaces.js +101 -0
  160. package/clis/hf/top.js +1 -0
  161. package/clis/homebrew/cask.js +39 -0
  162. package/clis/homebrew/formula.js +41 -0
  163. package/clis/homebrew/popular.js +54 -0
  164. package/clis/homebrew/utils.js +100 -0
  165. package/clis/hupu/__fixtures__/hot-home.html +64 -0
  166. package/clis/hupu/detail.js +0 -1
  167. package/clis/hupu/hot.js +156 -35
  168. package/clis/hupu/hot.test.js +224 -0
  169. package/clis/hupu/search.js +0 -1
  170. package/clis/instagram/note.js +1 -1
  171. package/clis/instagram/note.test.js +1 -29
  172. package/clis/instagram/post.js +1 -1
  173. package/clis/instagram/post.test.js +1 -1
  174. package/clis/instagram/reel.js +1 -1
  175. package/clis/instagram/story.js +1 -1
  176. package/clis/instagram/story.test.js +1 -34
  177. package/clis/jd/commands.test.js +1 -24
  178. package/clis/lichess/lichess.test.js +85 -0
  179. package/clis/lichess/top.js +46 -0
  180. package/clis/lichess/user.js +91 -0
  181. package/clis/lichess/utils.js +97 -0
  182. package/clis/linkedin/search.js +107 -10
  183. package/clis/linkedin/search.test.js +222 -0
  184. package/clis/linux-do/feed.js +2 -5
  185. package/clis/linux-do/feed.test.js +35 -0
  186. package/clis/lobsters/domain.js +92 -0
  187. package/clis/maven/artifact.js +49 -0
  188. package/clis/maven/search.js +51 -0
  189. package/clis/maven/utils.js +110 -0
  190. package/clis/mdn/search.js +97 -0
  191. package/clis/medium/tag.js +135 -0
  192. package/clis/npm/downloads.js +59 -0
  193. package/clis/npm/package.js +70 -0
  194. package/clis/npm/search.js +49 -0
  195. package/clis/npm/utils.js +76 -0
  196. package/clis/nuget/nuget.test.js +111 -0
  197. package/clis/nuget/package.js +101 -0
  198. package/clis/nuget/search.js +69 -0
  199. package/clis/nuget/utils.js +87 -0
  200. package/clis/nvd/cve.js +121 -0
  201. package/clis/oeis/oeis.test.js +88 -0
  202. package/clis/oeis/search.js +63 -0
  203. package/clis/oeis/sequence.js +71 -0
  204. package/clis/oeis/utils.js +88 -0
  205. package/clis/openalex/search.js +69 -0
  206. package/clis/openalex/utils.js +160 -0
  207. package/clis/openalex/work.js +65 -0
  208. package/clis/openfda/drug-label.js +74 -0
  209. package/clis/openfda/food-recall.js +65 -0
  210. package/clis/openfda/openfda.test.js +114 -0
  211. package/clis/openfda/utils.js +67 -0
  212. package/clis/osv/osv.test.js +97 -0
  213. package/clis/osv/query.js +72 -0
  214. package/clis/osv/utils.js +169 -0
  215. package/clis/osv/vulnerability.js +54 -0
  216. package/clis/packagist/package.js +49 -0
  217. package/clis/packagist/search.js +43 -0
  218. package/clis/packagist/utils.js +113 -0
  219. package/clis/paperreview/feedback.js +1 -1
  220. package/clis/paperreview/review.js +1 -1
  221. package/clis/paperreview/submit.js +1 -1
  222. package/clis/pixiv/download.test.js +1 -1
  223. package/clis/pixiv/illusts.test.js +1 -1
  224. package/clis/pixiv/search.test.js +1 -1
  225. package/clis/pubmed/article.js +50 -0
  226. package/clis/pubmed/author.js +64 -0
  227. package/clis/pubmed/citations.js +36 -0
  228. package/clis/pubmed/pubmed.test.js +276 -0
  229. package/clis/pubmed/related.js +45 -0
  230. package/clis/pubmed/search.js +75 -0
  231. package/clis/pubmed/utils.js +309 -0
  232. package/clis/pypi/downloads.js +66 -0
  233. package/clis/pypi/package.js +79 -0
  234. package/clis/pypi/utils.js +55 -0
  235. package/clis/quark/mv.js +1 -1
  236. package/clis/quark/save.js +1 -1
  237. package/clis/qwen/ask.js +85 -0
  238. package/clis/qwen/detail.js +62 -0
  239. package/clis/qwen/history.js +61 -0
  240. package/clis/qwen/image.js +179 -0
  241. package/clis/qwen/new.js +23 -0
  242. package/clis/qwen/read.js +41 -0
  243. package/clis/qwen/send.js +55 -0
  244. package/clis/qwen/status.js +37 -0
  245. package/clis/qwen/utils.js +409 -0
  246. package/clis/qwen/utils.test.js +45 -0
  247. package/clis/rest-countries/country.js +65 -0
  248. package/clis/rest-countries/region.js +64 -0
  249. package/clis/rest-countries/rest-countries.test.js +83 -0
  250. package/clis/rest-countries/utils.js +126 -0
  251. package/clis/reuters/article-detail.js +53 -0
  252. package/clis/reuters/reuters.test.js +299 -0
  253. package/clis/reuters/search.js +45 -34
  254. package/clis/reuters/utils.js +159 -0
  255. package/clis/rfc/rfc.js +52 -0
  256. package/clis/rfc/rfc.test.js +74 -0
  257. package/clis/rfc/utils.js +72 -0
  258. package/clis/rubygems/gem.js +42 -0
  259. package/clis/rubygems/search.js +47 -0
  260. package/clis/rubygems/utils.js +86 -0
  261. package/clis/stackoverflow/related.js +66 -0
  262. package/clis/stackoverflow/stackoverflow.test.js +58 -0
  263. package/clis/stackoverflow/tag.js +60 -0
  264. package/clis/stackoverflow/user.js +50 -0
  265. package/clis/stackoverflow/utils.js +118 -0
  266. package/clis/steam/app.js +67 -0
  267. package/clis/steam/search.js +58 -0
  268. package/clis/steam/steam.test.js +46 -0
  269. package/clis/steam/utils.js +107 -0
  270. package/clis/taobao/commands.test.js +1 -24
  271. package/clis/test-utils.js +61 -0
  272. package/clis/tieba/hot.js +0 -1
  273. package/clis/tiktok/comment.js +128 -41
  274. package/clis/tiktok/creator-videos.js +270 -0
  275. package/clis/tiktok/creator-videos.test.js +113 -0
  276. package/clis/tiktok/explore.js +137 -29
  277. package/clis/tiktok/follow.js +115 -33
  278. package/clis/tiktok/following.js +157 -36
  279. package/clis/tiktok/friends.js +139 -37
  280. package/clis/tiktok/live.js +137 -41
  281. package/clis/tiktok/notifications.js +141 -38
  282. package/clis/tiktok/refactor.test.js +389 -0
  283. package/clis/tiktok/unfollow.js +124 -38
  284. package/clis/tiktok/user.js +203 -29
  285. package/clis/tiktok/utils.js +505 -0
  286. package/clis/tiktok/write-refactor.test.js +370 -0
  287. package/clis/toutiao/articles.js +36 -62
  288. package/clis/toutiao/hot.js +63 -0
  289. package/clis/toutiao/toutiao.test.js +378 -0
  290. package/clis/toutiao/utils.js +161 -0
  291. package/clis/tvmaze/search.js +61 -0
  292. package/clis/tvmaze/show.js +60 -0
  293. package/clis/tvmaze/tvmaze.test.js +93 -0
  294. package/clis/tvmaze/utils.js +110 -0
  295. package/clis/twitter/accept.js +1 -1
  296. package/clis/twitter/followers.js +134 -69
  297. package/clis/twitter/quote.js +139 -0
  298. package/clis/twitter/quote.test.js +106 -0
  299. package/clis/twitter/reply-dm.js +1 -1
  300. package/clis/twitter/reply.test.js +1 -29
  301. package/clis/twitter/retweet.js +99 -0
  302. package/clis/twitter/retweet.test.js +69 -0
  303. package/clis/twitter/shared.js +38 -0
  304. package/clis/twitter/shared.test.js +28 -1
  305. package/clis/twitter/unlike.js +87 -0
  306. package/clis/twitter/unlike.test.js +72 -0
  307. package/clis/twitter/unretweet.js +99 -0
  308. package/clis/twitter/unretweet.test.js +69 -0
  309. package/clis/uisdc/news.js +105 -0
  310. package/clis/uisdc/news.test.js +66 -0
  311. package/clis/wanfang/search.js +0 -1
  312. package/clis/web/read.js +47 -17
  313. package/clis/web/read.test.js +101 -1
  314. package/clis/weixin/create-draft.js +1 -1
  315. package/clis/weixin/drafts.js +1 -1
  316. package/clis/weixin/drafts.test.js +5 -1
  317. package/clis/weixin/search.js +157 -0
  318. package/clis/weixin/search.test.js +227 -0
  319. package/clis/wikidata/entity.js +60 -0
  320. package/clis/wikidata/search.js +50 -0
  321. package/clis/wikidata/utils.js +117 -0
  322. package/clis/wikidata/wikidata.test.js +83 -0
  323. package/clis/wikipedia/page.js +95 -0
  324. package/clis/wttr/current.js +63 -0
  325. package/clis/wttr/forecast.js +71 -0
  326. package/clis/wttr/utils.js +50 -0
  327. package/clis/wttr/wttr.test.js +84 -0
  328. package/clis/xianyu/chat.js +16 -4
  329. package/clis/xianyu/chat.test.js +64 -0
  330. package/clis/xianyu/publish.js +485 -0
  331. package/clis/xianyu/publish.test.js +220 -0
  332. package/clis/xiaoe/catalog.js +105 -40
  333. package/clis/xiaoe/content.js +164 -29
  334. package/clis/xiaoe/courses.js +86 -29
  335. package/clis/xiaoe/xiaoe.test.js +486 -0
  336. package/clis/xiaohongshu/creator-notes-summary.js +1 -1
  337. package/clis/xiaohongshu/publish.js +16 -3
  338. package/clis/xiaohongshu/publish.test.js +46 -1
  339. package/clis/youtube/transcript.js +13 -19
  340. package/clis/youtube/transcript.test.js +17 -0
  341. package/clis/yuanbao/ask.js +17 -66
  342. package/clis/yuanbao/ask.test.js +5 -5
  343. package/clis/yuanbao/detail.js +65 -0
  344. package/clis/yuanbao/history.js +51 -0
  345. package/clis/yuanbao/new.js +1 -0
  346. package/clis/yuanbao/read.js +38 -0
  347. package/clis/yuanbao/send.js +57 -0
  348. package/clis/yuanbao/shared.js +297 -5
  349. package/clis/yuanbao/shared.test.js +80 -0
  350. package/clis/yuanbao/status.js +44 -0
  351. package/clis/zlibrary/commands.test.js +1 -11
  352. package/dist/src/browser/base-page.d.ts +9 -0
  353. package/dist/src/browser/base-page.js +44 -1
  354. package/dist/src/browser/base-page.test.js +66 -0
  355. package/dist/src/browser/bridge.js +47 -45
  356. package/dist/src/browser/cdp.d.ts +1 -0
  357. package/dist/src/browser/cdp.js +51 -9
  358. package/dist/src/browser/daemon-client.d.ts +4 -0
  359. package/dist/src/browser/errors.js +1 -1
  360. package/dist/src/browser/page.d.ts +1 -1
  361. package/dist/src/browser/page.js +3 -1
  362. package/dist/src/browser/page.test.js +29 -0
  363. package/dist/src/browser/target-errors.d.ts +2 -1
  364. package/dist/src/browser/target-errors.js +1 -0
  365. package/dist/src/browser/target-resolver.d.ts +25 -0
  366. package/dist/src/browser/target-resolver.js +43 -0
  367. package/dist/src/browser.test.js +18 -0
  368. package/dist/src/build-manifest.js +9 -4
  369. package/dist/src/build-manifest.test.js +2 -8
  370. package/dist/src/capabilityRouting.d.ts +16 -1
  371. package/dist/src/capabilityRouting.js +24 -1
  372. package/dist/src/capabilityRouting.test.js +19 -1
  373. package/dist/src/cli.js +76 -11
  374. package/dist/src/cli.test.js +241 -1
  375. package/dist/src/commanderAdapter.js +23 -9
  376. package/dist/src/commanderAdapter.test.js +0 -1
  377. package/dist/src/discovery.js +2 -5
  378. package/dist/src/errors.js +1 -1
  379. package/dist/src/execution.d.ts +1 -1
  380. package/dist/src/execution.js +111 -27
  381. package/dist/src/execution.test.js +326 -17
  382. package/dist/src/help.d.ts +27 -2
  383. package/dist/src/help.js +196 -23
  384. package/dist/src/help.test.d.ts +1 -0
  385. package/dist/src/help.test.js +54 -0
  386. package/dist/src/main.js +14 -1
  387. package/dist/src/manifest-types.d.ts +5 -3
  388. package/dist/src/pipeline/executor.js +1 -1
  389. package/dist/src/pipeline/executor.test.js +8 -0
  390. package/dist/src/pipeline/registry.d.ts +9 -0
  391. package/dist/src/pipeline/registry.js +13 -1
  392. package/dist/src/pipeline/steps/browser.d.ts +1 -0
  393. package/dist/src/pipeline/steps/browser.js +10 -0
  394. package/dist/src/pipeline/steps/download.test.js +1 -0
  395. package/dist/src/registry-api.d.ts +1 -1
  396. package/dist/src/registry.d.ts +12 -11
  397. package/dist/src/registry.js +16 -6
  398. package/dist/src/registry.test.js +2 -2
  399. package/dist/src/runtime.d.ts +2 -1
  400. package/dist/src/runtime.js +1 -1
  401. package/dist/src/serialization.d.ts +2 -2
  402. package/dist/src/serialization.js +4 -6
  403. package/dist/src/serialization.test.js +17 -0
  404. package/dist/src/types.d.ts +17 -0
  405. package/dist/src/validate.js +15 -11
  406. package/dist/src/validate.test.d.ts +9 -0
  407. package/dist/src/validate.test.js +90 -0
  408. package/package.json +1 -1
  409. package/scripts/fetch-adapters.js +1 -1
  410. package/scripts/typed-error-lint-baseline.json +5 -77
  411. package/clis/ctrip/search.test.js +0 -64
  412. package/clis/gov-policy/commands.test.js +0 -27
  413. package/clis/linux-do/category.js +0 -37
  414. package/clis/linux-do/hot.js +0 -26
  415. package/clis/linux-do/latest.js +0 -19
  416. package/clis/pixiv/test-utils.js +0 -23
  417. package/clis/toutiao/articles.test.js +0 -30
  418. package/dist/src/analysis.d.ts +0 -40
  419. package/dist/src/analysis.js +0 -172
@@ -0,0 +1,35 @@
1
+ /**
2
+ * 一亩三分地 精华帖 — Discuz guide=digest view.
3
+ */
4
+ import { cli, Strategy } from '@jackwener/opencli/registry';
5
+ import { fetchHtml, parseThreadList, normalizeLimit, BASE } from './utils.js';
6
+
7
+ cli({
8
+ site: '1point3acres',
9
+ name: 'digest',
10
+ access: 'read',
11
+ description: '一亩三分地 精华帖(编辑推荐 / 加精)',
12
+ domain: 'www.1point3acres.com',
13
+ strategy: Strategy.PUBLIC,
14
+ browser: false,
15
+ args: [
16
+ { name: 'limit', type: 'int', default: 20, help: '返回条数(默认 20,最多 50)' },
17
+ ],
18
+ columns: ['rank', 'tid', 'title', 'forum', 'author', 'replies', 'views', 'lastReplyTime', 'url'],
19
+ func: async (args) => {
20
+ const limit = normalizeLimit(args.limit, 20, 50);
21
+ const html = await fetchHtml(`${BASE}/forum.php?mod=guide&view=digest`);
22
+ const items = parseThreadList(html);
23
+ return items.slice(0, limit).map((t, i) => ({
24
+ rank: i + 1,
25
+ tid: t.tid,
26
+ title: t.title,
27
+ forum: t.forum,
28
+ author: t.author,
29
+ replies: t.replies,
30
+ views: t.views,
31
+ lastReplyTime: t.lastReplyTime,
32
+ url: t.url,
33
+ }));
34
+ },
35
+ });
@@ -0,0 +1,51 @@
1
+ /**
2
+ * 一亩三分地 版块帖子列表 — /bbs/forum-<fid>-<page>.html
3
+ */
4
+ import { cli, Strategy } from '@jackwener/opencli/registry';
5
+ import { ArgumentError } from '@jackwener/opencli/errors';
6
+ import { fetchHtml, parseThreadList, parseThreadRows, normalizeLimit, BASE } from './utils.js';
7
+
8
+ cli({
9
+ site: '1point3acres',
10
+ name: 'forum',
11
+ access: 'read',
12
+ description: '浏览一亩三分地某个版块的帖子列表(按 fid)',
13
+ domain: 'www.1point3acres.com',
14
+ strategy: Strategy.PUBLIC,
15
+ browser: false,
16
+ args: [
17
+ { name: 'fid', required: true, positional: true, help: '版块 ID,例如 145(海外面经)、198(海外职位内推)、27(研究生申请)' },
18
+ { name: 'page', type: 'int', default: 1, help: '页码(默认 1)' },
19
+ { name: 'limit', type: 'int', default: 20, help: '返回条数(默认 20,最多 50)' },
20
+ ],
21
+ columns: ['rank', 'tid', 'kind', 'title', 'author', 'replies', 'views', 'lastReplyTime', 'url'],
22
+ func: async (args) => {
23
+ const fid = String(args.fid || '').trim();
24
+ if (!/^\d+$/.test(fid)) {
25
+ throw new ArgumentError('fid must be a numeric forum id', 'e.g. 145 for 海外面经');
26
+ }
27
+ const pageNum = Number(args.page ?? 1);
28
+ if (!Number.isInteger(pageNum) || pageNum <= 0) {
29
+ throw new ArgumentError('page must be a positive integer');
30
+ }
31
+ const limit = normalizeLimit(args.limit, 20, 50);
32
+ const html = await fetchHtml(`${BASE}/forum-${fid}-${pageNum}.html`);
33
+ const rows = parseThreadRows(html);
34
+ if (rows.length === 0) {
35
+ // Forum may be sub-category-only — surface gracefully as empty with hint.
36
+ return [];
37
+ }
38
+ const items = parseThreadList(html);
39
+ return items.slice(0, limit).map((t, i) => ({
40
+ rank: i + 1,
41
+ tid: t.tid,
42
+ kind: t.kind === 'stickthread' ? '置顶' : '普通',
43
+ title: t.title,
44
+ author: t.author,
45
+ replies: t.replies,
46
+ views: t.views,
47
+ lastReplyTime: t.lastReplyTime,
48
+ url: t.url,
49
+ }));
50
+ },
51
+ });
@@ -0,0 +1,44 @@
1
+ /**
2
+ * 一亩三分地 所有版块清单 — parsed from /bbs/forum.php
3
+ *
4
+ * Each forum card has:
5
+ * <a href="forum-<fid>-1.html" ... class="... overflow-hidden whitespace-nowrap hidden desktop:block">版块名</a>
6
+ * and an adjacent description element. We dedupe by fid and return name + url.
7
+ */
8
+ import { cli, Strategy } from '@jackwener/opencli/registry';
9
+ import { fetchHtml, decodeEntities, BASE } from './utils.js';
10
+
11
+ cli({
12
+ site: '1point3acres',
13
+ name: 'forums',
14
+ access: 'read',
15
+ description: '一亩三分地 所有版块(fid + 版块名)',
16
+ domain: 'www.1point3acres.com',
17
+ strategy: Strategy.PUBLIC,
18
+ browser: false,
19
+ args: [
20
+ { name: 'filter', type: 'string', default: '', help: '按版块名关键字过滤(子串匹配,中英文)' },
21
+ ],
22
+ columns: ['fid', 'name', 'url'],
23
+ func: async (args) => {
24
+ const html = await fetchHtml(`${BASE}/forum.php`);
25
+ const seen = new Map();
26
+ const re = /<a href="forum-(\d+)-1\.html"[^>]*class="[^"]*overflow-hidden[^"]*"[^>]*>\s*([^<]+?)\s*<\/a>/g;
27
+ let m;
28
+ while ((m = re.exec(html))) {
29
+ const fid = m[1];
30
+ let name = decodeEntities(m[2].trim());
31
+ // Some subforum labels are wrapped in brackets — unwrap for display parity.
32
+ name = name.replace(/^\[(.+)\]$/, '$1').trim();
33
+ if (!name || seen.has(fid)) continue;
34
+ seen.set(fid, name);
35
+ }
36
+ const filter = String(args.filter || '').toLowerCase().trim();
37
+ const out = [];
38
+ for (const [fid, name] of seen) {
39
+ if (filter && !name.toLowerCase().includes(filter)) continue;
40
+ out.push({ fid, name, url: `${BASE}/forum-${fid}-1.html` });
41
+ }
42
+ return out;
43
+ },
44
+ });
@@ -0,0 +1,35 @@
1
+ /**
2
+ * 一亩三分地 热门帖子 — Discuz guide=hot view.
3
+ */
4
+ import { cli, Strategy } from '@jackwener/opencli/registry';
5
+ import { fetchHtml, parseThreadList, normalizeLimit, BASE } from './utils.js';
6
+
7
+ cli({
8
+ site: '1point3acres',
9
+ name: 'hot',
10
+ access: 'read',
11
+ description: '一亩三分地 今日热门帖子(按热度排序,约 50 条)',
12
+ domain: 'www.1point3acres.com',
13
+ strategy: Strategy.PUBLIC,
14
+ browser: false,
15
+ args: [
16
+ { name: 'limit', type: 'int', default: 20, help: '返回条数(默认 20,最多 50)' },
17
+ ],
18
+ columns: ['rank', 'tid', 'title', 'forum', 'author', 'replies', 'views', 'lastReplyTime', 'url'],
19
+ func: async (args) => {
20
+ const limit = normalizeLimit(args.limit, 20, 50);
21
+ const html = await fetchHtml(`${BASE}/forum.php?mod=guide&view=hot`);
22
+ const items = parseThreadList(html);
23
+ return items.slice(0, limit).map((t, i) => ({
24
+ rank: i + 1,
25
+ tid: t.tid,
26
+ title: t.title,
27
+ forum: t.forum,
28
+ author: t.author,
29
+ replies: t.replies,
30
+ views: t.views,
31
+ lastReplyTime: t.lastReplyTime,
32
+ url: t.url,
33
+ }));
34
+ },
35
+ });
@@ -0,0 +1,35 @@
1
+ /**
2
+ * 一亩三分地 最新帖子 — Discuz guide=new view.
3
+ */
4
+ import { cli, Strategy } from '@jackwener/opencli/registry';
5
+ import { fetchHtml, parseThreadList, normalizeLimit, BASE } from './utils.js';
6
+
7
+ cli({
8
+ site: '1point3acres',
9
+ name: 'latest',
10
+ access: 'read',
11
+ description: '一亩三分地 最新发帖(按发帖时间倒序)',
12
+ domain: 'www.1point3acres.com',
13
+ strategy: Strategy.PUBLIC,
14
+ browser: false,
15
+ args: [
16
+ { name: 'limit', type: 'int', default: 20, help: '返回条数(默认 20,最多 50)' },
17
+ ],
18
+ columns: ['rank', 'tid', 'title', 'forum', 'author', 'replies', 'views', 'postTime', 'url'],
19
+ func: async (args) => {
20
+ const limit = normalizeLimit(args.limit, 20, 50);
21
+ const html = await fetchHtml(`${BASE}/forum.php?mod=guide&view=new`);
22
+ const items = parseThreadList(html);
23
+ return items.slice(0, limit).map((t, i) => ({
24
+ rank: i + 1,
25
+ tid: t.tid,
26
+ title: t.title,
27
+ forum: t.forum,
28
+ author: t.author,
29
+ replies: t.replies,
30
+ views: t.views,
31
+ postTime: t.postTime,
32
+ url: t.url,
33
+ }));
34
+ },
35
+ });
@@ -0,0 +1,64 @@
1
+ /**
2
+ * 一亩三分地 我的通知 — 坛友互动 / 点评 / @我 等
3
+ *
4
+ * /bbs/home.php?mod=space&do=notice&view=interactive needs login cookie.
5
+ */
6
+ import { cli, Strategy } from '@jackwener/opencli/registry';
7
+ import { AuthRequiredError, EmptyResultError } from '@jackwener/opencli/errors';
8
+ import { fetchHtml, decodeEntities, getCookie, stripHtml, truncate, normalizePositiveInteger, BASE } from './utils.js';
9
+
10
+ cli({
11
+ site: '1point3acres',
12
+ name: 'notifications',
13
+ access: 'read',
14
+ description: '一亩三分地 站内通知(互动 / 点评 / @ 我;需要登录)',
15
+ domain: 'www.1point3acres.com',
16
+ strategy: Strategy.COOKIE,
17
+ browser: true,
18
+ navigateBefore: false,
19
+ args: [
20
+ { name: 'kind', type: 'string', default: 'mypost',
21
+ help: '通知类型:mypost(我的帖子) / interactive(互动) / system(系统) / app(应用)' },
22
+ { name: 'limit', type: 'int', default: 20, help: '返回条数' },
23
+ ],
24
+ columns: ['index', 'from', 'summary', 'time', 'threadUrl'],
25
+ func: async (page, args) => {
26
+ const kind = String(args.kind || 'mypost').trim();
27
+ const cookie = await getCookie(page);
28
+ const url = `${BASE}/home.php?mod=space&do=notice&view=${encodeURIComponent(kind)}`;
29
+ const html = await fetchHtml(url, { cookie, headers: { Referer: `${BASE}/` } });
30
+
31
+ if (/<title>提示信息/.test(html) && /请登录/.test(html)) {
32
+ throw new AuthRequiredError('www.1point3acres.com', '请先登录一亩三分地');
33
+ }
34
+
35
+ // "No notifications" is a real empty result, not a synthetic data row.
36
+ if (/暂时没有提醒内容/.test(html)) {
37
+ throw new EmptyResultError('1point3acres notifications', '暂时没有提醒内容');
38
+ }
39
+
40
+ const rows = [];
41
+ const limit = normalizePositiveInteger(args.limit, 20, 'limit');
42
+
43
+ // Pattern 1: standard Discuz <dl class="cl">…</dl> block per notice.
44
+ const dlRe = /<dl class="[^"]*cl[^"]*"[^>]*>([\s\S]*?)<\/dl>/g;
45
+ let m;
46
+ let i = 0;
47
+ while ((m = dlRe.exec(html)) && rows.length < limit) {
48
+ const block = m[1];
49
+ const from = decodeEntities((block.match(/<dt>([\s\S]*?)<\/dt>/) || [, ''])[1])
50
+ .replace(/<[^>]+>/g, '').trim();
51
+ const summaryRaw = (block.match(/<dd class="ntc_body">([\s\S]*?)<\/dd>/) ||
52
+ block.match(/<dd>([\s\S]*?)<\/dd>/) || [, ''])[1];
53
+ const summary = truncate(stripHtml(summaryRaw), 200);
54
+ const time = ((block.match(/<dd class="[^"]*xg1[^"]*"[^>]*>([\s\S]*?)<\/dd>/) || [, ''])[1] || '')
55
+ .replace(/<[^>]+>/g, '').trim();
56
+ const linkMatch = summaryRaw.match(/href="([^"]*thread-\d+[^"]*)"/);
57
+ const threadUrl = linkMatch ? (linkMatch[1].startsWith('http') ? linkMatch[1] : `${BASE}/${linkMatch[1]}`) : '';
58
+ i += 1;
59
+ if (!from && !summary) continue;
60
+ rows.push({ index: i, from, summary, time, threadUrl });
61
+ }
62
+ return rows;
63
+ },
64
+ });
@@ -0,0 +1,71 @@
1
+ /**
2
+ * 一亩三分地 站内搜索 — /bbs/search.php?mod=forum
3
+ *
4
+ * Guests get a "请登录" alert page, so this command needs the live browser
5
+ * session's cookie. Discuz routes search through a 302 redirect to
6
+ * search.php?searchid=<ID>. Node fetch follows redirects automatically as
7
+ * long as we pass the session cookie along.
8
+ */
9
+ import { cli, Strategy } from '@jackwener/opencli/registry';
10
+ import { ArgumentError, EmptyResultError } from '@jackwener/opencli/errors';
11
+ import { fetchHtml, parseSearchList, assertNotGuestAlert, getCookie, decodeEntities, normalizeLimit, BASE } from './utils.js';
12
+
13
+ cli({
14
+ site: '1point3acres',
15
+ name: 'search',
16
+ access: 'read',
17
+ description: '一亩三分地 站内关键字搜索(需要登录)',
18
+ domain: 'www.1point3acres.com',
19
+ strategy: Strategy.COOKIE,
20
+ browser: true,
21
+ navigateBefore: false,
22
+ args: [
23
+ { name: 'query', required: true, positional: true, help: '搜索关键字' },
24
+ { name: 'limit', type: 'int', default: 20, help: '返回条数(默认 20,最多 50)' },
25
+ { name: 'fid', type: 'string', default: '', help: '限定版块 ID(可选)' },
26
+ ],
27
+ columns: ['rank', 'tid', 'title', 'forum', 'author', 'replies', 'views', 'postTime', 'url'],
28
+ func: async (page, args) => {
29
+ const query = String(args.query || '').trim();
30
+ if (!query) throw new ArgumentError('query 不能为空');
31
+ const limit = normalizeLimit(args.limit, 20, 50);
32
+ const fid = String(args.fid || '').trim();
33
+
34
+ const cookie = await getCookie(page);
35
+ const qs = new URLSearchParams({
36
+ mod: 'forum',
37
+ srchtxt: query,
38
+ searchsubmit: 'yes',
39
+ ...(fid ? { srchfid: fid } : {}),
40
+ });
41
+ const url = `${BASE}/search.php?${qs.toString()}`;
42
+
43
+ // Node fetch with the session cookie — Discuz's 302 to search.php?searchid=…
44
+ // is followed by default.
45
+ const html = await fetchHtml(url, {
46
+ cookie,
47
+ headers: { Referer: `${BASE}/` },
48
+ });
49
+ assertNotGuestAlert(html);
50
+
51
+ const items = parseSearchList(html);
52
+ if (items.length === 0) {
53
+ const hint = html.match(/<p>([^<]*?抱歉[^<]*?)<\/p>/);
54
+ if (hint) {
55
+ throw new EmptyResultError('1point3acres search', decodeEntities(hint[1].trim()));
56
+ }
57
+ throw new EmptyResultError('1point3acres search', `No results for "${query}"`);
58
+ }
59
+ return items.slice(0, limit).map((t, i) => ({
60
+ rank: i + 1,
61
+ tid: t.tid,
62
+ title: t.title,
63
+ forum: t.forum,
64
+ author: t.author,
65
+ replies: t.replies,
66
+ views: t.views,
67
+ postTime: t.postTime,
68
+ url: t.url,
69
+ }));
70
+ },
71
+ });
@@ -0,0 +1,117 @@
1
+ /**
2
+ * 一亩三分地 帖子详情 — /bbs/thread-<tid>-<page>-1.html
3
+ *
4
+ * Returns one row per post on the requested page. First row (floor=1) is the
5
+ * main post; the rest are replies. Columns are shaped so `--limit 1` gives
6
+ * just the main post, and larger limits walk down the thread.
7
+ */
8
+ import { cli, Strategy } from '@jackwener/opencli/registry';
9
+ import { ArgumentError, EmptyResultError } from '@jackwener/opencli/errors';
10
+ import { fetchHtml, decodeEntities, stripHtml, truncate, normalizePositiveInteger, BASE } from './utils.js';
11
+
12
+ function extract(html, regex, group = 1) {
13
+ const m = html.match(regex);
14
+ return m ? m[group] : '';
15
+ }
16
+
17
+ cli({
18
+ site: '1point3acres',
19
+ name: 'thread',
20
+ access: 'read',
21
+ description: '一亩三分地 帖子详情 + 楼层(主楼 + 回复)',
22
+ domain: 'www.1point3acres.com',
23
+ strategy: Strategy.PUBLIC,
24
+ browser: false,
25
+ args: [
26
+ { name: 'tid', required: true, positional: true, help: '帖子 ID(数字,见 `hot`/`latest` 返回的 tid)' },
27
+ { name: 'page', type: 'int', default: 1, help: '楼层分页页码(默认 1)' },
28
+ { name: 'limit', type: 'int', default: 10, help: '返回楼层条数(默认 10,含主楼)' },
29
+ { name: 'contentLimit', type: 'int', default: 400, help: '每楼正文截断长度(默认 400 字符,最少 50)' },
30
+ ],
31
+ columns: ['floor', 'pid', 'author', 'postTime', 'content', 'url'],
32
+ func: async (args) => {
33
+ const tid = String(args.tid || '').trim();
34
+ if (!/^\d+$/.test(tid)) {
35
+ throw new ArgumentError('tid must be a numeric thread id');
36
+ }
37
+ const page = normalizePositiveInteger(args.page, 1, 'page');
38
+ const limit = normalizePositiveInteger(args.limit, 10, 'limit');
39
+ const contentLimit = normalizePositiveInteger(args.contentLimit, 400, 'contentLimit', { min: 50 });
40
+
41
+ const url = `${BASE}/thread-${tid}-${page}-1.html`;
42
+ const html = await fetchHtml(url);
43
+
44
+ // Sanity: real thread page will contain postlist + at least one post div.
45
+ if (!/id="postlist"/.test(html) && !/id="post_\d+"/.test(html)) {
46
+ throw new EmptyResultError('1point3acres thread', `帖子 ${tid} 不存在或被删除`);
47
+ }
48
+
49
+ // Split posts: each post block is bounded by <div id="post_<PID>">…</div> next post or postlist end.
50
+ // NOTE: intermediate objects intentionally use postId/body/offset (not pid/html/start) to
51
+ // avoid being mistaken for row-shaped objects by the silent-column-drop audit.
52
+ const postBlocks = [];
53
+ const re = /<div id="post_(\d+)"[^>]*>/g;
54
+ const offsets = [];
55
+ let m;
56
+ while ((m = re.exec(html))) offsets.push({ postId: m[1], offset: m.index });
57
+ for (let i = 0; i < offsets.length; i++) {
58
+ const segStart = offsets[i].offset;
59
+ const segEnd = i + 1 < offsets.length ? offsets[i + 1].offset : html.length;
60
+ postBlocks.push({ postId: offsets[i].postId, body: html.slice(segStart, segEnd) });
61
+ }
62
+
63
+ const rows = [];
64
+ for (let i = 0; i < postBlocks.length && rows.length < limit; i++) {
65
+ const { postId: pid, body: block } = postBlocks[i];
66
+ // Discuz authi block holds the author link + post time metadata.
67
+ const authiMatch = block.match(/<div class="authi"[\s\S]*?<\/div>/);
68
+ const authiBlock = authiMatch ? authiMatch[0] : '';
69
+ const authorCandidates = [
70
+ /<a [^>]*class="[^"]*\bxi2\b[^"]*"[^>]*>\s*([^<]+?)\s*<\/a>/,
71
+ /<a [^>]*href="space-uid-\d+\.html"[^>]*>\s*([^<]+?)\s*<\/a>/,
72
+ /<a [^>]*class="[^"]*\bxw1\b[^"]*"[^>]*>\s*([^<]+?)\s*<\/a>/,
73
+ ];
74
+ let author = '';
75
+ for (const re of authorCandidates) {
76
+ const v = decodeEntities(extract(authiBlock || block, re));
77
+ if (v && !/匿名卡|变色卡|关贴卡/.test(v)) { author = v; break; }
78
+ }
79
+ // Time: prefer <span title="YYYY-MM-DD HH:MM:SS"> (per-post, precise).
80
+ // <meta itemprop="datePublished"> is the *thread* publish time on this site — avoid.
81
+ const postTime = extract(authiBlock, /<span title="([^"]+)">/) ||
82
+ extract(block, /id="authorposton\d+"[^>]*>\s*<span title="([^"]+)">/) ||
83
+ extract(block, /id="authorposton\d+"[^>]*>\s*([^<]+?)\s*</) ||
84
+ extract(block, /<meta itemprop="datePublished" content="([^"]+)"/);
85
+ // Floor: first post on page 1 is the 楼主, subsequent posts carry <em>N#</em>.
86
+ const floorEm = extract(block, /<em>(\d+)<\/em>\s*#?\s*<\/a>/) ||
87
+ extract(block, /id="postnum\d+"[^>]*>\s*<em>(\d+)<\/em>/);
88
+ const isMainPost = page === 1 && i === 0;
89
+ const floor = floorEm ? Number(floorEm) : (isMainPost ? 1 : (page - 1) * 10 + i + 1);
90
+ const contentMatch = block.match(/id="postmessage_\d+"[^>]*>([\s\S]*?)<\/td>/);
91
+ const content = truncate(stripHtml(contentMatch ? contentMatch[1] : ''), contentLimit);
92
+ rows.push({
93
+ floor,
94
+ pid,
95
+ author,
96
+ postTime: postTime.trim(),
97
+ content,
98
+ url: `${BASE}/forum.php?mod=redirect&goto=findpost&ptid=${tid}&pid=${pid}`,
99
+ });
100
+ }
101
+
102
+ // Attach the thread title + forum name as a leading synthetic row only when rows exist
103
+ // and only for page 1, so agents get the title without needing a separate call.
104
+ if (page === 1 && rows.length > 0) {
105
+ const title = decodeEntities(
106
+ extract(html, /<span id="thread_subject">([^<]+)<\/span>/).trim() ||
107
+ extract(html, /<title>([^<]+?)\s*[-|]/).trim()
108
+ );
109
+ rows[0].content = title ? `【${title}】\n${rows[0].content}` : rows[0].content;
110
+ }
111
+
112
+ if (!rows.length) {
113
+ throw new EmptyResultError('1point3acres thread', `帖子 ${tid} 第 ${page} 页没有可读取楼层`);
114
+ }
115
+ return rows;
116
+ },
117
+ });
@@ -0,0 +1,77 @@
1
+ /**
2
+ * 一亩三分地 用户资料 — /bbs/space-uid-<uid>.html or /bbs/space-username-<name>.html
3
+ *
4
+ * Guest-visible fields: username, uid, user group, register/last-access times,
5
+ * post/thread/digest counts, credits, rice (大米 — site currency), profile URL.
6
+ * Users can be queried by numeric uid or by username (both routes are public).
7
+ */
8
+ import { cli, Strategy } from '@jackwener/opencli/registry';
9
+ import { ArgumentError, EmptyResultError } from '@jackwener/opencli/errors';
10
+ import { fetchHtml, decodeEntities, BASE } from './utils.js';
11
+
12
+ cli({
13
+ site: '1point3acres',
14
+ name: 'user',
15
+ access: 'read',
16
+ description: '一亩三分地 用户空间(用户组 / 积分 / 大米 / 帖子数 等)',
17
+ domain: 'www.1point3acres.com',
18
+ strategy: Strategy.PUBLIC,
19
+ browser: false,
20
+ args: [
21
+ { name: 'who', required: true, positional: true, help: '用户名或 uid(纯数字按 uid 查,否则按用户名)' },
22
+ ],
23
+ columns: [
24
+ 'uid', 'username', 'group', 'credits', 'rice',
25
+ 'posts', 'threads', 'digests', 'registerTime', 'lastAccess', 'profileUrl',
26
+ ],
27
+ func: async (args) => {
28
+ const who = String(args.who || '').trim();
29
+ if (!who) throw new ArgumentError('who 不能为空', '传用户名或数字 uid');
30
+ const url = /^\d+$/.test(who)
31
+ ? `${BASE}/space-uid-${who}.html`
32
+ : `${BASE}/space-username-${encodeURIComponent(who)}.html`;
33
+
34
+ const html = await fetchHtml(url);
35
+ if (/<title>提示信息/.test(html) && /(没有找到|不存在)/.test(html)) {
36
+ throw new EmptyResultError('1point3acres user', `用户 "${who}" 不存在`);
37
+ }
38
+
39
+ const pick = (re) => {
40
+ const m = html.match(re);
41
+ return m ? decodeEntities(m[1].trim()) : '';
42
+ };
43
+ // <li>KEY: VAL</li> — tolerant of optional <span>, colons fullwidth/半角, 颗/根/粒 suffixes.
44
+ const pickLi = (label) => {
45
+ const re = new RegExp(`<li>\\s*${label}[::\\s]*(?:<[^>]+>)?\\s*([^<]+?)\\s*(?:<|$)`);
46
+ const m = html.match(re);
47
+ return m ? decodeEntities(m[1].trim()) : '';
48
+ };
49
+
50
+ const username =
51
+ pick(/<p class="mtm[^"]*"[^>]*>\s*<a [^>]*>([^<]+?)<\/a>/) ||
52
+ pick(/<title>([^<]+?)的个人资料/);
53
+ const uid = pick(/uid=(\d+)/) || pick(/space-uid-(\d+)\.html/);
54
+ const group = pickLi('用户组');
55
+ const credits = pickLi('积分');
56
+ const rice = pickLi('大米');
57
+ const posts = pickLi('帖子数');
58
+ const threads = pickLi('主题数');
59
+ const digests = pickLi('精华数');
60
+ const registerTime = pickLi('注册时间');
61
+ const lastAccess = pickLi('最后访问');
62
+
63
+ return [{
64
+ uid,
65
+ username,
66
+ group,
67
+ credits,
68
+ rice,
69
+ posts,
70
+ threads,
71
+ digests,
72
+ registerTime,
73
+ lastAccess,
74
+ profileUrl: uid ? `${BASE}/space-uid-${uid}.html` : url,
75
+ }];
76
+ },
77
+ });