@zenalexa/unicli 0.219.0 → 0.220.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +9 -9
- package/README.md +55 -249
- package/README.zh-CN.md +55 -249
- package/dist/adapters/1point3acres/forum.d.ts +58 -0
- package/dist/adapters/1point3acres/forum.d.ts.map +1 -0
- package/dist/adapters/1point3acres/forum.js +708 -0
- package/dist/adapters/1point3acres/forum.js.map +1 -0
- package/dist/adapters/aibase/news.d.ts +21 -0
- package/dist/adapters/aibase/news.d.ts.map +1 -0
- package/dist/adapters/aibase/news.js +96 -0
- package/dist/adapters/aibase/news.js.map +1 -0
- package/dist/adapters/arxiv/papers.d.ts +27 -0
- package/dist/adapters/arxiv/papers.d.ts.map +1 -0
- package/dist/adapters/arxiv/papers.js +193 -0
- package/dist/adapters/arxiv/papers.js.map +1 -0
- package/dist/adapters/bbc/topic.d.ts +24 -0
- package/dist/adapters/bbc/topic.d.ts.map +1 -0
- package/dist/adapters/bbc/topic.js +122 -0
- package/dist/adapters/bbc/topic.js.map +1 -0
- package/dist/adapters/chatgpt/web.d.ts +24 -0
- package/dist/adapters/chatgpt/web.d.ts.map +1 -0
- package/dist/adapters/chatgpt/web.js +242 -0
- package/dist/adapters/chatgpt/web.js.map +1 -0
- package/dist/adapters/claude/web.d.ts +24 -0
- package/dist/adapters/claude/web.d.ts.map +1 -0
- package/dist/adapters/claude/web.js +575 -0
- package/dist/adapters/claude/web.js.map +1 -0
- package/dist/adapters/codex/projects.d.ts +27 -0
- package/dist/adapters/codex/projects.d.ts.map +1 -0
- package/dist/adapters/codex/projects.js +147 -0
- package/dist/adapters/codex/projects.js.map +1 -0
- package/dist/adapters/coingecko/markets.d.ts +19 -0
- package/dist/adapters/coingecko/markets.d.ts.map +1 -0
- package/dist/adapters/coingecko/markets.js +474 -0
- package/dist/adapters/coingecko/markets.js.map +1 -0
- package/dist/adapters/coupang/product.d.ts +27 -0
- package/dist/adapters/coupang/product.d.ts.map +1 -0
- package/dist/adapters/coupang/product.js +211 -0
- package/dist/adapters/coupang/product.js.map +1 -0
- package/dist/adapters/crates/registry.d.ts +44 -0
- package/dist/adapters/crates/registry.d.ts.map +1 -0
- package/dist/adapters/crates/registry.js +186 -0
- package/dist/adapters/crates/registry.js.map +1 -0
- package/dist/adapters/ctrip/travel.d.ts +83 -0
- package/dist/adapters/ctrip/travel.d.ts.map +1 -0
- package/dist/adapters/ctrip/travel.js +630 -0
- package/dist/adapters/ctrip/travel.js.map +1 -0
- package/dist/adapters/dblp/publications.d.ts +41 -0
- package/dist/adapters/dblp/publications.d.ts.map +1 -0
- package/dist/adapters/dblp/publications.js +409 -0
- package/dist/adapters/dblp/publications.js.map +1 -0
- package/dist/adapters/deepseek/web.d.ts +1 -1
- package/dist/adapters/deepseek/web.d.ts.map +1 -1
- package/dist/adapters/deepseek/web.js +66 -1
- package/dist/adapters/deepseek/web.js.map +1 -1
- package/dist/adapters/defillama/protocols.d.ts +13 -0
- package/dist/adapters/defillama/protocols.d.ts.map +1 -0
- package/dist/adapters/defillama/protocols.js +218 -0
- package/dist/adapters/defillama/protocols.js.map +1 -0
- package/dist/adapters/devto/read.d.ts +26 -0
- package/dist/adapters/devto/read.d.ts.map +1 -0
- package/dist/adapters/devto/read.js +110 -0
- package/dist/adapters/devto/read.js.map +1 -0
- package/dist/adapters/dianping/shop.d.ts +38 -0
- package/dist/adapters/dianping/shop.d.ts.map +1 -0
- package/dist/adapters/dianping/shop.js +194 -0
- package/dist/adapters/dianping/shop.js.map +1 -0
- package/dist/adapters/dockerhub/registry.d.ts +36 -0
- package/dist/adapters/dockerhub/registry.d.ts.map +1 -0
- package/dist/adapters/dockerhub/registry.js +172 -0
- package/dist/adapters/dockerhub/registry.js.map +1 -0
- package/dist/adapters/endoflife/product.d.ts +11 -0
- package/dist/adapters/endoflife/product.d.ts.map +1 -0
- package/dist/adapters/endoflife/product.js +113 -0
- package/dist/adapters/endoflife/product.js.map +1 -0
- package/dist/adapters/facebook/marketplace-extra.d.ts +9 -0
- package/dist/adapters/facebook/marketplace-extra.d.ts.map +1 -0
- package/dist/adapters/facebook/marketplace-extra.js +170 -0
- package/dist/adapters/facebook/marketplace-extra.js.map +1 -0
- package/dist/adapters/flathub/apps.d.ts +17 -0
- package/dist/adapters/flathub/apps.d.ts.map +1 -0
- package/dist/adapters/flathub/apps.js +220 -0
- package/dist/adapters/flathub/apps.js.map +1 -0
- package/dist/adapters/goproxy/module.d.ts +24 -0
- package/dist/adapters/goproxy/module.d.ts.map +1 -0
- package/dist/adapters/goproxy/module.js +221 -0
- package/dist/adapters/goproxy/module.js.map +1 -0
- package/dist/adapters/grok/web.d.ts +29 -0
- package/dist/adapters/grok/web.d.ts.map +1 -0
- package/dist/adapters/grok/web.js +553 -0
- package/dist/adapters/grok/web.js.map +1 -0
- package/dist/adapters/hackernews/read.d.ts +31 -0
- package/dist/adapters/hackernews/read.d.ts.map +1 -0
- package/dist/adapters/hackernews/read.js +201 -0
- package/dist/adapters/hackernews/read.js.map +1 -0
- package/dist/adapters/hf/paper.d.ts +22 -0
- package/dist/adapters/hf/paper.d.ts.map +1 -0
- package/dist/adapters/hf/paper.js +112 -0
- package/dist/adapters/hf/paper.js.map +1 -0
- package/dist/adapters/homebrew/packages.d.ts +52 -0
- package/dist/adapters/homebrew/packages.d.ts.map +1 -0
- package/dist/adapters/homebrew/packages.js +240 -0
- package/dist/adapters/homebrew/packages.js.map +1 -0
- package/dist/adapters/indeed/jobs.d.ts +38 -0
- package/dist/adapters/indeed/jobs.d.ts.map +1 -0
- package/dist/adapters/indeed/jobs.js +300 -0
- package/dist/adapters/indeed/jobs.js.map +1 -0
- package/dist/adapters/instagram/collections.d.ts +9 -0
- package/dist/adapters/instagram/collections.d.ts.map +1 -0
- package/dist/adapters/instagram/collections.js +174 -0
- package/dist/adapters/instagram/collections.js.map +1 -0
- package/dist/adapters/lichess/players.d.ts +46 -0
- package/dist/adapters/lichess/players.d.ts.map +1 -0
- package/dist/adapters/lichess/players.js +221 -0
- package/dist/adapters/lichess/players.js.map +1 -0
- package/dist/adapters/lobsters/read-domain.d.ts +35 -0
- package/dist/adapters/lobsters/read-domain.d.ts.map +1 -0
- package/dist/adapters/lobsters/read-domain.js +306 -0
- package/dist/adapters/lobsters/read-domain.js.map +1 -0
- package/dist/adapters/maven/artifact.d.ts +30 -0
- package/dist/adapters/maven/artifact.d.ts.map +1 -0
- package/dist/adapters/maven/artifact.js +121 -0
- package/dist/adapters/maven/artifact.js.map +1 -0
- package/dist/adapters/mdn/search.d.ts +11 -0
- package/dist/adapters/mdn/search.d.ts.map +1 -0
- package/dist/adapters/mdn/search.js +115 -0
- package/dist/adapters/mdn/search.js.map +1 -0
- package/dist/adapters/medium/tag.d.ts +15 -0
- package/dist/adapters/medium/tag.d.ts.map +1 -0
- package/dist/adapters/medium/tag.js +148 -0
- package/dist/adapters/medium/tag.js.map +1 -0
- package/dist/adapters/npm/package.d.ts +32 -0
- package/dist/adapters/npm/package.d.ts.map +1 -0
- package/dist/adapters/npm/package.js +141 -0
- package/dist/adapters/npm/package.js.map +1 -0
- package/dist/adapters/nuget/package.d.ts +34 -0
- package/dist/adapters/nuget/package.d.ts.map +1 -0
- package/dist/adapters/nuget/package.js +135 -0
- package/dist/adapters/nuget/package.js.map +1 -0
- package/dist/adapters/nvd/cve.d.ts +42 -0
- package/dist/adapters/nvd/cve.d.ts.map +1 -0
- package/dist/adapters/nvd/cve.js +132 -0
- package/dist/adapters/nvd/cve.js.map +1 -0
- package/dist/adapters/oeis/sequences.d.ts +14 -0
- package/dist/adapters/oeis/sequences.d.ts.map +1 -0
- package/dist/adapters/oeis/sequences.js +219 -0
- package/dist/adapters/oeis/sequences.js.map +1 -0
- package/dist/adapters/openalex/works.d.ts +43 -0
- package/dist/adapters/openalex/works.d.ts.map +1 -0
- package/dist/adapters/openalex/works.js +267 -0
- package/dist/adapters/openalex/works.js.map +1 -0
- package/dist/adapters/openfda/records.d.ts +18 -0
- package/dist/adapters/openfda/records.d.ts.map +1 -0
- package/dist/adapters/openfda/records.js +209 -0
- package/dist/adapters/openfda/records.js.map +1 -0
- package/dist/adapters/openreview/papers.d.ts +34 -0
- package/dist/adapters/openreview/papers.d.ts.map +1 -0
- package/dist/adapters/openreview/papers.js +463 -0
- package/dist/adapters/openreview/papers.js.map +1 -0
- package/dist/adapters/osv/security.d.ts +36 -0
- package/dist/adapters/osv/security.d.ts.map +1 -0
- package/dist/adapters/osv/security.js +247 -0
- package/dist/adapters/osv/security.js.map +1 -0
- package/dist/adapters/packagist/package.d.ts +31 -0
- package/dist/adapters/packagist/package.d.ts.map +1 -0
- package/dist/adapters/packagist/package.js +108 -0
- package/dist/adapters/packagist/package.js.map +1 -0
- package/dist/adapters/pubmed/articles.d.ts +31 -0
- package/dist/adapters/pubmed/articles.d.ts.map +1 -0
- package/dist/adapters/pubmed/articles.js +385 -0
- package/dist/adapters/pubmed/articles.js.map +1 -0
- package/dist/adapters/pypi/package.d.ts +38 -0
- package/dist/adapters/pypi/package.d.ts.map +1 -0
- package/dist/adapters/pypi/package.js +235 -0
- package/dist/adapters/pypi/package.js.map +1 -0
- package/dist/adapters/qwen/web.d.ts +26 -0
- package/dist/adapters/qwen/web.d.ts.map +1 -0
- package/dist/adapters/qwen/web.js +672 -0
- package/dist/adapters/qwen/web.js.map +1 -0
- package/dist/adapters/reddit/account.d.ts +12 -0
- package/dist/adapters/reddit/account.d.ts.map +1 -0
- package/dist/adapters/reddit/account.js +409 -0
- package/dist/adapters/reddit/account.js.map +1 -0
- package/dist/adapters/rednote/web.d.ts +30 -0
- package/dist/adapters/rednote/web.d.ts.map +1 -0
- package/dist/adapters/rednote/web.js +858 -0
- package/dist/adapters/rednote/web.js.map +1 -0
- package/dist/adapters/rest-countries/countries.d.ts +14 -0
- package/dist/adapters/rest-countries/countries.d.ts.map +1 -0
- package/dist/adapters/rest-countries/countries.js +231 -0
- package/dist/adapters/rest-countries/countries.js.map +1 -0
- package/dist/adapters/reuters/article-detail.d.ts +37 -0
- package/dist/adapters/reuters/article-detail.d.ts.map +1 -0
- package/dist/adapters/reuters/article-detail.js +139 -0
- package/dist/adapters/reuters/article-detail.js.map +1 -0
- package/dist/adapters/rfc/rfc.d.ts +11 -0
- package/dist/adapters/rfc/rfc.d.ts.map +1 -0
- package/dist/adapters/rfc/rfc.js +121 -0
- package/dist/adapters/rfc/rfc.js.map +1 -0
- package/dist/adapters/rubygems/gem.d.ts +26 -0
- package/dist/adapters/rubygems/gem.d.ts.map +1 -0
- package/dist/adapters/rubygems/gem.js +96 -0
- package/dist/adapters/rubygems/gem.js.map +1 -0
- package/dist/adapters/stackoverflow/questions.d.ts +79 -0
- package/dist/adapters/stackoverflow/questions.d.ts.map +1 -0
- package/dist/adapters/stackoverflow/questions.js +504 -0
- package/dist/adapters/stackoverflow/questions.js.map +1 -0
- package/dist/adapters/steam/app.d.ts +39 -0
- package/dist/adapters/steam/app.d.ts.map +1 -0
- package/dist/adapters/steam/app.js +165 -0
- package/dist/adapters/steam/app.js.map +1 -0
- package/dist/adapters/tiktok/creator-videos.d.ts +52 -0
- package/dist/adapters/tiktok/creator-videos.d.ts.map +1 -0
- package/dist/adapters/tiktok/creator-videos.js +267 -0
- package/dist/adapters/tiktok/creator-videos.js.map +1 -0
- package/dist/adapters/tvmaze/shows.d.ts +13 -0
- package/dist/adapters/tvmaze/shows.d.ts.map +1 -0
- package/dist/adapters/tvmaze/shows.js +240 -0
- package/dist/adapters/tvmaze/shows.js.map +1 -0
- package/dist/adapters/twitter/bookmark-folders.d.ts +33 -0
- package/dist/adapters/twitter/bookmark-folders.d.ts.map +1 -0
- package/dist/adapters/twitter/bookmark-folders.js +290 -0
- package/dist/adapters/twitter/bookmark-folders.js.map +1 -0
- package/dist/adapters/twitter/quote.d.ts +18 -0
- package/dist/adapters/twitter/quote.d.ts.map +1 -0
- package/dist/adapters/twitter/quote.js +285 -0
- package/dist/adapters/twitter/quote.js.map +1 -0
- package/dist/adapters/twitter/tweet-actions.d.ts +12 -0
- package/dist/adapters/twitter/tweet-actions.d.ts.map +1 -0
- package/dist/adapters/twitter/tweet-actions.js +145 -0
- package/dist/adapters/twitter/tweet-actions.js.map +1 -0
- package/dist/adapters/twitter/tweet-url.d.ts +15 -0
- package/dist/adapters/twitter/tweet-url.d.ts.map +1 -0
- package/dist/adapters/twitter/tweet-url.js +57 -0
- package/dist/adapters/twitter/tweet-url.js.map +1 -0
- package/dist/adapters/uisdc/news.d.ts +22 -0
- package/dist/adapters/uisdc/news.d.ts.map +1 -0
- package/dist/adapters/uisdc/news.js +91 -0
- package/dist/adapters/uisdc/news.js.map +1 -0
- package/dist/adapters/weibo/favorites-publish.d.ts +28 -0
- package/dist/adapters/weibo/favorites-publish.d.ts.map +1 -0
- package/dist/adapters/weibo/favorites-publish.js +356 -0
- package/dist/adapters/weibo/favorites-publish.js.map +1 -0
- package/dist/adapters/wikidata/entities.d.ts +15 -0
- package/dist/adapters/wikidata/entities.d.ts.map +1 -0
- package/dist/adapters/wikidata/entities.js +219 -0
- package/dist/adapters/wikidata/entities.js.map +1 -0
- package/dist/adapters/wikipedia/page.d.ts +21 -0
- package/dist/adapters/wikipedia/page.d.ts.map +1 -0
- package/dist/adapters/wikipedia/page.js +116 -0
- package/dist/adapters/wikipedia/page.js.map +1 -0
- package/dist/adapters/wttr/weather.d.ts +12 -0
- package/dist/adapters/wttr/weather.d.ts.map +1 -0
- package/dist/adapters/wttr/weather.js +207 -0
- package/dist/adapters/wttr/weather.js.map +1 -0
- package/dist/adapters/xianyu/publish.d.ts +31 -0
- package/dist/adapters/xianyu/publish.d.ts.map +1 -0
- package/dist/adapters/xianyu/publish.js +349 -0
- package/dist/adapters/xianyu/publish.js.map +1 -0
- package/dist/adapters/xiaohongshu/user-helpers.d.ts +2 -2
- package/dist/adapters/xiaohongshu/user-helpers.d.ts.map +1 -1
- package/dist/adapters/xiaohongshu/user-helpers.js +5 -4
- package/dist/adapters/xiaohongshu/user-helpers.js.map +1 -1
- package/dist/adapters/yuanbao/web.d.ts +27 -0
- package/dist/adapters/yuanbao/web.d.ts.map +1 -0
- package/dist/adapters/yuanbao/web.js +365 -0
- package/dist/adapters/yuanbao/web.js.map +1 -0
- package/dist/adapters/zhihu/collection.d.ts +33 -0
- package/dist/adapters/zhihu/collection.d.ts.map +1 -0
- package/dist/adapters/zhihu/collection.js +185 -0
- package/dist/adapters/zhihu/collection.js.map +1 -0
- package/dist/adapters/zlibrary/web.d.ts +19 -0
- package/dist/adapters/zlibrary/web.d.ts.map +1 -0
- package/dist/adapters/zlibrary/web.js +153 -0
- package/dist/adapters/zlibrary/web.js.map +1 -0
- package/dist/browser/daemon-client.js +2 -2
- package/dist/browser/daemon-client.js.map +1 -1
- package/dist/discovery/macos-dynamic.d.ts.map +1 -1
- package/dist/discovery/macos-dynamic.js +17 -3
- package/dist/discovery/macos-dynamic.js.map +1 -1
- package/dist/manifest-compact.txt +10 -10
- package/dist/manifest-search.json +1 -1
- package/dist/manifest.json +5130 -112
- package/dist/transport/refs.d.ts +5 -3
- package/dist/transport/refs.d.ts.map +1 -1
- package/dist/transport/refs.js +8 -1
- package/dist/transport/refs.js.map +1 -1
- package/dist/transport/sidecar-binary.d.ts +7 -0
- package/dist/transport/sidecar-binary.d.ts.map +1 -1
- package/dist/transport/sidecar-binary.js +28 -8
- package/dist/transport/sidecar-binary.js.map +1 -1
- package/package.json +4 -3
- package/server.json +3 -3
- package/skills/unicli/SKILL.md +1 -1
- package/skills/unicli-claude-code/SKILL.md +1 -1
- package/skills/unicli-hermes/SKILL.md +1 -1
- package/src/adapters/1point3acres/forum.test.ts +300 -0
- package/src/adapters/1point3acres/forum.ts +852 -0
- package/src/adapters/aibase/news.test.ts +42 -0
- package/src/adapters/aibase/news.ts +118 -0
- package/src/adapters/arxiv/papers.test.ts +59 -0
- package/src/adapters/arxiv/papers.ts +226 -0
- package/src/adapters/bbc/topic.test.ts +52 -0
- package/src/adapters/bbc/topic.ts +149 -0
- package/src/adapters/chatgpt/web.test.ts +121 -0
- package/src/adapters/chatgpt/web.ts +286 -0
- package/src/adapters/claude/web.test.ts +206 -0
- package/src/adapters/claude/web.ts +684 -0
- package/src/adapters/codex/projects.test.ts +77 -0
- package/src/adapters/codex/projects.ts +178 -0
- package/src/adapters/coingecko/markets.test.ts +156 -0
- package/src/adapters/coingecko/markets.ts +574 -0
- package/src/adapters/coupang/product.test.ts +111 -0
- package/src/adapters/coupang/product.ts +256 -0
- package/src/adapters/crates/registry.test.ts +89 -0
- package/src/adapters/crates/registry.ts +247 -0
- package/src/adapters/ctrip/travel.test.ts +359 -0
- package/src/adapters/ctrip/travel.ts +792 -0
- package/src/adapters/dblp/publications.test.ts +123 -0
- package/src/adapters/dblp/publications.ts +494 -0
- package/src/adapters/deepseek/web.test.ts +69 -0
- package/src/adapters/deepseek/web.ts +78 -1
- package/src/adapters/defillama/protocols.test.ts +75 -0
- package/src/adapters/defillama/protocols.ts +253 -0
- package/src/adapters/devto/read.test.ts +49 -0
- package/src/adapters/devto/read.ts +145 -0
- package/src/adapters/dianping/shop.test.ts +134 -0
- package/src/adapters/dianping/shop.ts +261 -0
- package/src/adapters/dockerhub/registry.test.ts +97 -0
- package/src/adapters/dockerhub/registry.ts +223 -0
- package/src/adapters/endoflife/product.test.ts +50 -0
- package/src/adapters/endoflife/product.ts +128 -0
- package/src/adapters/facebook/marketplace-extra.test.ts +132 -0
- package/src/adapters/facebook/marketplace-extra.ts +213 -0
- package/src/adapters/flathub/apps.test.ts +85 -0
- package/src/adapters/flathub/apps.ts +254 -0
- package/src/adapters/goproxy/module.test.ts +72 -0
- package/src/adapters/goproxy/module.ts +258 -0
- package/src/adapters/grok/web.test.ts +181 -0
- package/src/adapters/grok/web.ts +640 -0
- package/src/adapters/hackernews/read.test.ts +68 -0
- package/src/adapters/hackernews/read.ts +265 -0
- package/src/adapters/hf/paper.test.ts +48 -0
- package/src/adapters/hf/paper.ts +138 -0
- package/src/adapters/homebrew/packages.test.ts +109 -0
- package/src/adapters/homebrew/packages.ts +304 -0
- package/src/adapters/indeed/jobs.test.ts +230 -0
- package/src/adapters/indeed/jobs.ts +375 -0
- package/src/adapters/instagram/collections.test.ts +94 -0
- package/src/adapters/instagram/collections.ts +206 -0
- package/src/adapters/lichess/players.test.ts +99 -0
- package/src/adapters/lichess/players.ts +277 -0
- package/src/adapters/lobsters/read-domain.test.ts +121 -0
- package/src/adapters/lobsters/read-domain.ts +400 -0
- package/src/adapters/maven/artifact.test.ts +67 -0
- package/src/adapters/maven/artifact.ts +155 -0
- package/src/adapters/mdn/search.test.ts +39 -0
- package/src/adapters/mdn/search.ts +133 -0
- package/src/adapters/medium/tag.test.ts +64 -0
- package/src/adapters/medium/tag.ts +164 -0
- package/src/adapters/npm/package.test.ts +53 -0
- package/src/adapters/npm/package.ts +177 -0
- package/src/adapters/nuget/package.test.ts +102 -0
- package/src/adapters/nuget/package.ts +193 -0
- package/src/adapters/nvd/cve.test.ts +66 -0
- package/src/adapters/nvd/cve.ts +182 -0
- package/src/adapters/oeis/sequences.test.ts +71 -0
- package/src/adapters/oeis/sequences.ts +234 -0
- package/src/adapters/openalex/works.test.ts +99 -0
- package/src/adapters/openalex/works.ts +319 -0
- package/src/adapters/openfda/records.test.ts +90 -0
- package/src/adapters/openfda/records.ts +239 -0
- package/src/adapters/openreview/papers.test.ts +139 -0
- package/src/adapters/openreview/papers.ts +560 -0
- package/src/adapters/osv/security.test.ts +91 -0
- package/src/adapters/osv/security.ts +298 -0
- package/src/adapters/packagist/package.test.ts +62 -0
- package/src/adapters/packagist/package.ts +146 -0
- package/src/adapters/pubmed/articles.test.ts +96 -0
- package/src/adapters/pubmed/articles.ts +497 -0
- package/src/adapters/pypi/package.test.ts +131 -0
- package/src/adapters/pypi/package.ts +297 -0
- package/src/adapters/qwen/web.test.ts +176 -0
- package/src/adapters/qwen/web.ts +758 -0
- package/src/adapters/reddit/account.test.ts +56 -0
- package/src/adapters/reddit/account.ts +493 -0
- package/src/adapters/rednote/web.test.ts +354 -0
- package/src/adapters/rednote/web.ts +968 -0
- package/src/adapters/rest-countries/countries.test.ts +80 -0
- package/src/adapters/rest-countries/countries.ts +271 -0
- package/src/adapters/reuters/article-detail.test.ts +65 -0
- package/src/adapters/reuters/article-detail.ts +186 -0
- package/src/adapters/rfc/rfc.test.ts +37 -0
- package/src/adapters/rfc/rfc.ts +133 -0
- package/src/adapters/rubygems/gem.test.ts +43 -0
- package/src/adapters/rubygems/gem.ts +126 -0
- package/src/adapters/stackoverflow/questions.test.ts +207 -0
- package/src/adapters/stackoverflow/questions.ts +765 -0
- package/src/adapters/steam/app.test.ts +68 -0
- package/src/adapters/steam/app.ts +218 -0
- package/src/adapters/tiktok/creator-videos.test.ts +158 -0
- package/src/adapters/tiktok/creator-videos.ts +370 -0
- package/src/adapters/tvmaze/shows.test.ts +93 -0
- package/src/adapters/tvmaze/shows.ts +271 -0
- package/src/adapters/twitter/bookmark-folders.test.ts +164 -0
- package/src/adapters/twitter/bookmark-folders.ts +366 -0
- package/src/adapters/twitter/quote.test.ts +157 -0
- package/src/adapters/twitter/quote.ts +332 -0
- package/src/adapters/twitter/tweet-actions.test.ts +51 -0
- package/src/adapters/twitter/tweet-actions.ts +187 -0
- package/src/adapters/twitter/tweet-url.ts +65 -0
- package/src/adapters/uisdc/news.test.ts +46 -0
- package/src/adapters/uisdc/news.ts +111 -0
- package/src/adapters/weibo/favorites-publish.test.ts +177 -0
- package/src/adapters/weibo/favorites-publish.ts +426 -0
- package/src/adapters/wikidata/entities.test.ts +103 -0
- package/src/adapters/wikidata/entities.ts +253 -0
- package/src/adapters/wikipedia/page.test.ts +49 -0
- package/src/adapters/wikipedia/page.ts +158 -0
- package/src/adapters/wttr/weather.test.ts +99 -0
- package/src/adapters/wttr/weather.ts +239 -0
- package/src/adapters/xianyu/publish.test.ts +210 -0
- package/src/adapters/xianyu/publish.ts +420 -0
- package/src/adapters/xiaohongshu/user-helpers.ts +5 -2
- package/src/adapters/yuanbao/web.test.ts +144 -0
- package/src/adapters/yuanbao/web.ts +435 -0
- package/src/adapters/zhihu/collection.test.ts +96 -0
- package/src/adapters/zhihu/collection.ts +239 -0
- package/src/adapters/zlibrary/web.test.ts +104 -0
- package/src/adapters/zlibrary/web.ts +192 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
normalizeRedditCommentFullname,
|
|
4
|
+
parseRedditHomeLimit,
|
|
5
|
+
parseSubredditName,
|
|
6
|
+
requireReplyText,
|
|
7
|
+
} from "./account.js";
|
|
8
|
+
|
|
9
|
+
describe("reddit account and thread helpers", () => {
|
|
10
|
+
it("validates home feed limits", () => {
|
|
11
|
+
expect(parseRedditHomeLimit(undefined)).toBe(25);
|
|
12
|
+
expect(parseRedditHomeLimit("7")).toBe(7);
|
|
13
|
+
expect(() => parseRedditHomeLimit("0")).toThrow("limit must be an integer");
|
|
14
|
+
expect(() => parseRedditHomeLimit("101")).toThrow(
|
|
15
|
+
"limit must be an integer",
|
|
16
|
+
);
|
|
17
|
+
expect(() => parseRedditHomeLimit("1.5")).toThrow(
|
|
18
|
+
"limit must be an integer",
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("normalizes subreddit names", () => {
|
|
23
|
+
expect(parseSubredditName("python")).toBe("python");
|
|
24
|
+
expect(parseSubredditName("r/MachineLearning")).toBe("MachineLearning");
|
|
25
|
+
expect(parseSubredditName("/r/typescript")).toBe("typescript");
|
|
26
|
+
expect(() => parseSubredditName("")).toThrow("Subreddit name is required");
|
|
27
|
+
expect(() => parseSubredditName("1bad")).toThrow("Invalid subreddit name");
|
|
28
|
+
expect(() => parseSubredditName("ab")).toThrow("Invalid subreddit name");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("normalizes Reddit comment targets to t1 fullnames", () => {
|
|
32
|
+
expect(normalizeRedditCommentFullname("okf3s7u")).toBe("t1_okf3s7u");
|
|
33
|
+
expect(normalizeRedditCommentFullname("T1_OKF3S7U")).toBe("t1_okf3s7u");
|
|
34
|
+
expect(
|
|
35
|
+
normalizeRedditCommentFullname(
|
|
36
|
+
"https://www.reddit.com/r/programming/comments/abc123/title/okf3s7u/",
|
|
37
|
+
),
|
|
38
|
+
).toBe("t1_okf3s7u");
|
|
39
|
+
expect(() => normalizeRedditCommentFullname("t3_abc123")).toThrow(
|
|
40
|
+
"Comment ID must be a Reddit comment id",
|
|
41
|
+
);
|
|
42
|
+
expect(() =>
|
|
43
|
+
normalizeRedditCommentFullname(
|
|
44
|
+
"https://www.reddit.com/r/programming/comments/abc123/title/okf3s7u/extra",
|
|
45
|
+
),
|
|
46
|
+
).toThrow("Comment URL must end at the target comment id");
|
|
47
|
+
expect(() =>
|
|
48
|
+
normalizeRedditCommentFullname("https://example.com/x"),
|
|
49
|
+
).toThrow("Comment URL must be an https reddit.com URL");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("requires non-empty reply text", () => {
|
|
53
|
+
expect(requireReplyText(" hello ")).toBe(" hello ");
|
|
54
|
+
expect(() => requireReplyText(" ")).toThrow("Reply text is required");
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @owner src/adapters/reddit/account.ts
|
|
3
|
+
* @does Register Reddit authenticated account, home feed, subreddit metadata, and reply commands.
|
|
4
|
+
* @needs Browser-authenticated reddit.com session, Reddit JSON endpoints, registry TypeScript adapter loader.
|
|
5
|
+
* @feeds surface coverage ledger, Reddit authenticated command surface, agent-readable command output.
|
|
6
|
+
* @breaks Reddit auth/session drift, malformed JSON envelopes, or weak ID validation can post to the wrong target or hide auth failures.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { cli, Strategy } from "../../registry.js";
|
|
10
|
+
import type { AdapterArg, IPage } from "../../types.js";
|
|
11
|
+
import { redditChildren, redditJson } from "./browser-utils.js";
|
|
12
|
+
|
|
13
|
+
const REDDIT_HOME_MAX_LIMIT = 100;
|
|
14
|
+
const SUBREDDIT_NAME_RE = /^[A-Za-z][A-Za-z0-9_]{2,20}$/;
|
|
15
|
+
const REDDIT_COMMENT_ID_RE = /^[a-z0-9]+$/i;
|
|
16
|
+
|
|
17
|
+
const LIMIT_ARG: AdapterArg = {
|
|
18
|
+
name: "limit",
|
|
19
|
+
type: "int",
|
|
20
|
+
default: 25,
|
|
21
|
+
description: `Number of posts (1-${REDDIT_HOME_MAX_LIMIT})`,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
interface RedditBrowserResult {
|
|
25
|
+
kind?: string;
|
|
26
|
+
detail?: string;
|
|
27
|
+
httpStatus?: number;
|
|
28
|
+
where?: string;
|
|
29
|
+
identity?: Record<string, unknown>;
|
|
30
|
+
createdName?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function failRedditResult(command: string, result: RedditBrowserResult): never {
|
|
34
|
+
if (result.kind === "auth") {
|
|
35
|
+
throw new Error(`Authentication required for reddit.com: ${result.detail}`);
|
|
36
|
+
}
|
|
37
|
+
if (result.kind === "http") {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`Reddit ${command} failed: HTTP ${result.httpStatus} from ${result.where}`,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
if (result.kind === "reddit-error") {
|
|
43
|
+
throw new Error(`Reddit rejected ${command}: ${result.detail}`);
|
|
44
|
+
}
|
|
45
|
+
if (result.kind === "postcondition" || result.kind === "malformed") {
|
|
46
|
+
throw new Error(
|
|
47
|
+
String(result.detail ?? `Reddit ${command} response failed validation`),
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
if (result.kind === "exception") {
|
|
51
|
+
throw new Error(`Reddit ${command} failed: ${result.detail}`);
|
|
52
|
+
}
|
|
53
|
+
throw new Error(
|
|
54
|
+
`Unexpected Reddit ${command} result: ${JSON.stringify(result)}`,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function parseRedditHomeLimit(raw: unknown): number {
|
|
59
|
+
if (raw === undefined || raw === null || raw === "") return 25;
|
|
60
|
+
const n = Number(raw);
|
|
61
|
+
if (
|
|
62
|
+
!Number.isFinite(n) ||
|
|
63
|
+
!Number.isInteger(n) ||
|
|
64
|
+
n < 1 ||
|
|
65
|
+
n > REDDIT_HOME_MAX_LIMIT
|
|
66
|
+
) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`limit must be an integer in [1, ${REDDIT_HOME_MAX_LIMIT}]. Got: ${String(raw)}`,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
return n;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function parseSubredditName(raw: unknown): string {
|
|
75
|
+
let name = String(raw ?? "").trim();
|
|
76
|
+
if (!name) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
"Subreddit name is required. Pass a name like `python` or `r/python`.",
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
if (name.startsWith("/r/")) name = name.slice(3);
|
|
82
|
+
else if (name.startsWith("r/")) name = name.slice(2);
|
|
83
|
+
if (!SUBREDDIT_NAME_RE.test(name)) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
"Invalid subreddit name. Names are 3-21 characters, start with a letter, and contain only letters, digits, and underscores.",
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
return name;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function normalizeBareCommentId(value: unknown): string {
|
|
92
|
+
const commentId = String(value ?? "").trim();
|
|
93
|
+
if (!REDDIT_COMMENT_ID_RE.test(commentId)) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
"Comment ID must be a Reddit comment id, t1_ fullname, or reddit.com comment URL.",
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
return commentId.toLowerCase();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function normalizeRedditCommentFullname(value: unknown): string {
|
|
102
|
+
const raw = String(value ?? "").trim();
|
|
103
|
+
if (!raw) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
"Comment ID is required. Use a bare comment id, t1_ fullname, or full Reddit comment URL.",
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const fullname = raw.match(/^t1_([a-z0-9]+)$/i);
|
|
110
|
+
if (fullname) return `t1_${normalizeBareCommentId(fullname[1])}`;
|
|
111
|
+
|
|
112
|
+
if (/^https?:\/\//i.test(raw)) {
|
|
113
|
+
let parsed: URL;
|
|
114
|
+
try {
|
|
115
|
+
parsed = new URL(raw);
|
|
116
|
+
} catch {
|
|
117
|
+
throw new Error(`Invalid Reddit comment URL: ${raw}`);
|
|
118
|
+
}
|
|
119
|
+
const host = parsed.hostname.toLowerCase();
|
|
120
|
+
if (
|
|
121
|
+
parsed.protocol !== "https:" ||
|
|
122
|
+
(host !== "reddit.com" && !host.endsWith(".reddit.com"))
|
|
123
|
+
) {
|
|
124
|
+
throw new Error("Comment URL must be an https reddit.com URL.");
|
|
125
|
+
}
|
|
126
|
+
const parts = parsed.pathname.split("/").filter(Boolean);
|
|
127
|
+
const commentsIndex = parts.indexOf("comments");
|
|
128
|
+
const commentIndex = commentsIndex + 3;
|
|
129
|
+
if (commentsIndex < 0 || parts.length <= commentIndex) {
|
|
130
|
+
throw new Error("Comment URL must include the target comment id.");
|
|
131
|
+
}
|
|
132
|
+
if (parts.length !== commentIndex + 1) {
|
|
133
|
+
throw new Error("Comment URL must end at the target comment id.");
|
|
134
|
+
}
|
|
135
|
+
return `t1_${normalizeBareCommentId(parts[commentIndex])}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (raw.includes("/") || raw.startsWith("t3_")) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
"Comment ID must be a Reddit comment id, t1_ fullname, or reddit.com comment URL.",
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return `t1_${normalizeBareCommentId(raw)}`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function requireReplyText(value: unknown): string {
|
|
148
|
+
const text = String(value ?? "");
|
|
149
|
+
if (!text.trim()) {
|
|
150
|
+
throw new Error(
|
|
151
|
+
"Reply text is required. Pass non-empty text to post as the Reddit reply.",
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
return text;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function requireRedditIdentity(
|
|
158
|
+
page: IPage,
|
|
159
|
+
): Promise<Record<string, unknown>> {
|
|
160
|
+
await page.goto("https://www.reddit.com", { settleMs: 500 });
|
|
161
|
+
const result = (await page.evaluate(`(async () => {
|
|
162
|
+
try {
|
|
163
|
+
const res = await fetch('/api/me.json?raw_json=1', { credentials: 'include' });
|
|
164
|
+
if (res.status === 401 || res.status === 403) {
|
|
165
|
+
return { kind: 'auth', detail: 'Reddit /api/me.json returned HTTP ' + res.status };
|
|
166
|
+
}
|
|
167
|
+
if (!res.ok) return { kind: 'http', httpStatus: res.status, where: '/api/me.json' };
|
|
168
|
+
const body = await res.json();
|
|
169
|
+
const identity = body?.data;
|
|
170
|
+
if (!identity?.name) {
|
|
171
|
+
return { kind: 'auth', detail: 'Not logged in to reddit.com (no identity in /api/me.json)' };
|
|
172
|
+
}
|
|
173
|
+
return { kind: 'ok', identity };
|
|
174
|
+
} catch (err) {
|
|
175
|
+
return { kind: 'exception', detail: String(err && err.message || err) };
|
|
176
|
+
}
|
|
177
|
+
})()`)) as RedditBrowserResult;
|
|
178
|
+
|
|
179
|
+
if (result.kind !== "ok" || !result.identity)
|
|
180
|
+
failRedditResult("identity", result);
|
|
181
|
+
return result.identity;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function identityRows(
|
|
185
|
+
identity: Record<string, unknown>,
|
|
186
|
+
): Array<Record<string, unknown>> {
|
|
187
|
+
const created =
|
|
188
|
+
typeof identity.created_utc === "number"
|
|
189
|
+
? new Date(identity.created_utc * 1000).toISOString().split("T")[0]
|
|
190
|
+
: null;
|
|
191
|
+
const linkKarma =
|
|
192
|
+
typeof identity.link_karma === "number" ? identity.link_karma : null;
|
|
193
|
+
const commentKarma =
|
|
194
|
+
typeof identity.comment_karma === "number" ? identity.comment_karma : null;
|
|
195
|
+
const totalKarma =
|
|
196
|
+
typeof identity.total_karma === "number"
|
|
197
|
+
? identity.total_karma
|
|
198
|
+
: linkKarma != null && commentKarma != null
|
|
199
|
+
? linkKarma + commentKarma
|
|
200
|
+
: null;
|
|
201
|
+
const inboxCount =
|
|
202
|
+
typeof identity.inbox_count === "number" ? identity.inbox_count : null;
|
|
203
|
+
return [
|
|
204
|
+
{
|
|
205
|
+
field: "Username",
|
|
206
|
+
value: identity.name ? `u/${String(identity.name)}` : null,
|
|
207
|
+
},
|
|
208
|
+
{ field: "ID", value: identity.id ? `t2_${String(identity.id)}` : null },
|
|
209
|
+
{
|
|
210
|
+
field: "Post Karma",
|
|
211
|
+
value: linkKarma != null ? String(linkKarma) : null,
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
field: "Comment Karma",
|
|
215
|
+
value: commentKarma != null ? String(commentKarma) : null,
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
field: "Total Karma",
|
|
219
|
+
value: totalKarma != null ? String(totalKarma) : null,
|
|
220
|
+
},
|
|
221
|
+
{ field: "Account Created", value: created },
|
|
222
|
+
{ field: "Gold", value: identity.is_gold ? "Yes" : "No" },
|
|
223
|
+
{ field: "Mod", value: identity.is_mod ? "Yes" : "No" },
|
|
224
|
+
{
|
|
225
|
+
field: "Verified Email",
|
|
226
|
+
value: identity.has_verified_email ? "Yes" : "No",
|
|
227
|
+
},
|
|
228
|
+
{ field: "Has Mail", value: identity.has_mail ? "Yes" : "No" },
|
|
229
|
+
{
|
|
230
|
+
field: "Inbox Count",
|
|
231
|
+
value: inboxCount != null ? String(inboxCount) : null,
|
|
232
|
+
},
|
|
233
|
+
];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function homeRows(
|
|
237
|
+
children: Array<Record<string, unknown>>,
|
|
238
|
+
limit: number,
|
|
239
|
+
): Array<Record<string, unknown>> {
|
|
240
|
+
return children.slice(0, limit).flatMap((child, index) => {
|
|
241
|
+
const data = (child.data ?? {}) as Record<string, unknown>;
|
|
242
|
+
const postId = typeof data.id === "string" ? data.id : "";
|
|
243
|
+
if (!postId) return [];
|
|
244
|
+
const permalink = String(data.permalink ?? "");
|
|
245
|
+
return [
|
|
246
|
+
{
|
|
247
|
+
rank: index + 1,
|
|
248
|
+
title: typeof data.title === "string" ? data.title : null,
|
|
249
|
+
subreddit:
|
|
250
|
+
typeof data.subreddit_name_prefixed === "string"
|
|
251
|
+
? data.subreddit_name_prefixed
|
|
252
|
+
: null,
|
|
253
|
+
score: typeof data.score === "number" ? data.score : null,
|
|
254
|
+
comments:
|
|
255
|
+
typeof data.num_comments === "number" ? data.num_comments : null,
|
|
256
|
+
postId,
|
|
257
|
+
author: typeof data.author === "string" ? data.author : null,
|
|
258
|
+
url: permalink ? `https://www.reddit.com${permalink}` : null,
|
|
259
|
+
},
|
|
260
|
+
];
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function subredditRows(
|
|
265
|
+
info: Record<string, unknown>,
|
|
266
|
+
): Array<Record<string, unknown>> {
|
|
267
|
+
const created =
|
|
268
|
+
typeof info.created_utc === "number"
|
|
269
|
+
? new Date(info.created_utc * 1000).toISOString().split("T")[0]
|
|
270
|
+
: null;
|
|
271
|
+
const subscribers =
|
|
272
|
+
typeof info.subscribers === "number" ? info.subscribers : null;
|
|
273
|
+
const activeNow =
|
|
274
|
+
typeof info.active_user_count === "number"
|
|
275
|
+
? info.active_user_count
|
|
276
|
+
: typeof info.accounts_active === "number"
|
|
277
|
+
? info.accounts_active
|
|
278
|
+
: null;
|
|
279
|
+
const displayName = String(info.display_name ?? "");
|
|
280
|
+
return [
|
|
281
|
+
{
|
|
282
|
+
field: "Name",
|
|
283
|
+
value: info.display_name_prefixed
|
|
284
|
+
? String(info.display_name_prefixed)
|
|
285
|
+
: `r/${displayName}`,
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
field: "Title",
|
|
289
|
+
value: typeof info.title === "string" ? info.title : null,
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
field: "Subscribers",
|
|
293
|
+
value: subscribers != null ? String(subscribers) : null,
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
field: "Active Now",
|
|
297
|
+
value: activeNow != null ? String(activeNow) : null,
|
|
298
|
+
},
|
|
299
|
+
{ field: "NSFW", value: info.over18 ? "Yes" : "No" },
|
|
300
|
+
{
|
|
301
|
+
field: "Type",
|
|
302
|
+
value:
|
|
303
|
+
typeof info.subreddit_type === "string" ? info.subreddit_type : null,
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
field: "Description",
|
|
307
|
+
value:
|
|
308
|
+
typeof info.public_description === "string" &&
|
|
309
|
+
info.public_description.trim()
|
|
310
|
+
? info.public_description.trim()
|
|
311
|
+
: null,
|
|
312
|
+
},
|
|
313
|
+
{ field: "Created", value: created },
|
|
314
|
+
{
|
|
315
|
+
field: "URL",
|
|
316
|
+
value: info.url ? `https://www.reddit.com${String(info.url)}` : null,
|
|
317
|
+
},
|
|
318
|
+
];
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
cli({
|
|
322
|
+
site: "reddit",
|
|
323
|
+
name: "whoami",
|
|
324
|
+
description: "Show the currently logged-in Reddit user",
|
|
325
|
+
domain: "www.reddit.com",
|
|
326
|
+
strategy: Strategy.COOKIE,
|
|
327
|
+
browser: true,
|
|
328
|
+
args: [],
|
|
329
|
+
columns: ["field", "value"],
|
|
330
|
+
func: async (page) =>
|
|
331
|
+
identityRows(await requireRedditIdentity(page as IPage)),
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
cli({
|
|
335
|
+
site: "reddit",
|
|
336
|
+
name: "home",
|
|
337
|
+
description: "Reddit personalized home feed",
|
|
338
|
+
domain: "www.reddit.com",
|
|
339
|
+
strategy: Strategy.COOKIE,
|
|
340
|
+
browser: true,
|
|
341
|
+
args: [LIMIT_ARG],
|
|
342
|
+
columns: [
|
|
343
|
+
"rank",
|
|
344
|
+
"title",
|
|
345
|
+
"subreddit",
|
|
346
|
+
"score",
|
|
347
|
+
"comments",
|
|
348
|
+
"postId",
|
|
349
|
+
"author",
|
|
350
|
+
"url",
|
|
351
|
+
],
|
|
352
|
+
func: async (page, kwargs) => {
|
|
353
|
+
const limit = parseRedditHomeLimit(kwargs.limit);
|
|
354
|
+
await requireRedditIdentity(page as IPage);
|
|
355
|
+
const data = await redditJson(page as IPage, "/best.json", { limit });
|
|
356
|
+
const children = redditChildren(data);
|
|
357
|
+
const rows = homeRows(children, limit);
|
|
358
|
+
if (rows.length === 0) {
|
|
359
|
+
throw new Error(
|
|
360
|
+
children.length > 0
|
|
361
|
+
? "Reddit home feed entries were missing required post id anchors."
|
|
362
|
+
: "Reddit returned no posts in the personalized home feed.",
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
return rows;
|
|
366
|
+
},
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
cli({
|
|
370
|
+
site: "reddit",
|
|
371
|
+
name: "subreddit-info",
|
|
372
|
+
description: "Show metadata for a Reddit subreddit",
|
|
373
|
+
domain: "www.reddit.com",
|
|
374
|
+
strategy: Strategy.COOKIE,
|
|
375
|
+
browser: true,
|
|
376
|
+
args: [
|
|
377
|
+
{
|
|
378
|
+
name: "name",
|
|
379
|
+
type: "str",
|
|
380
|
+
required: true,
|
|
381
|
+
positional: true,
|
|
382
|
+
description: "Subreddit name without r/",
|
|
383
|
+
},
|
|
384
|
+
],
|
|
385
|
+
columns: ["field", "value"],
|
|
386
|
+
func: async (page, kwargs) => {
|
|
387
|
+
const sub = parseSubredditName(kwargs.name);
|
|
388
|
+
const data = (await redditJson(
|
|
389
|
+
page as IPage,
|
|
390
|
+
`/r/${encodeURIComponent(sub)}/about.json`,
|
|
391
|
+
{},
|
|
392
|
+
)) as {
|
|
393
|
+
error?: unknown;
|
|
394
|
+
reason?: unknown;
|
|
395
|
+
data?: Record<string, unknown>;
|
|
396
|
+
};
|
|
397
|
+
if (data.error) {
|
|
398
|
+
throw new Error(
|
|
399
|
+
`Subreddit r/${sub} is ${String(data.reason || "unavailable")}.`,
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
const info = data.data;
|
|
403
|
+
if (!info?.display_name) {
|
|
404
|
+
throw new Error(`Reddit returned malformed subreddit info for r/${sub}.`);
|
|
405
|
+
}
|
|
406
|
+
return subredditRows(info);
|
|
407
|
+
},
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
cli({
|
|
411
|
+
site: "reddit",
|
|
412
|
+
name: "reply",
|
|
413
|
+
description: "Reply to a Reddit comment",
|
|
414
|
+
domain: "www.reddit.com",
|
|
415
|
+
strategy: Strategy.COOKIE,
|
|
416
|
+
browser: true,
|
|
417
|
+
args: [
|
|
418
|
+
{
|
|
419
|
+
name: "comment-id",
|
|
420
|
+
type: "str",
|
|
421
|
+
required: true,
|
|
422
|
+
positional: true,
|
|
423
|
+
description: "Comment ID, t1_ fullname, or Reddit comment URL",
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
name: "text",
|
|
427
|
+
type: "str",
|
|
428
|
+
required: true,
|
|
429
|
+
positional: true,
|
|
430
|
+
description: "Reply text",
|
|
431
|
+
},
|
|
432
|
+
],
|
|
433
|
+
columns: ["status", "message"],
|
|
434
|
+
func: async (page, kwargs) => {
|
|
435
|
+
const fullname = normalizeRedditCommentFullname(kwargs["comment-id"]);
|
|
436
|
+
const text = requireReplyText(kwargs.text);
|
|
437
|
+
await requireRedditIdentity(page as IPage);
|
|
438
|
+
const result = (await (page as IPage).evaluate(`(async () => {
|
|
439
|
+
try {
|
|
440
|
+
const fullname = ${JSON.stringify(fullname)};
|
|
441
|
+
const text = ${JSON.stringify(text)};
|
|
442
|
+
const meRes = await fetch('/api/me.json?raw_json=1', { credentials: 'include' });
|
|
443
|
+
if (meRes.status === 401 || meRes.status === 403) {
|
|
444
|
+
return { kind: 'auth', detail: 'Reddit /api/me.json returned HTTP ' + meRes.status };
|
|
445
|
+
}
|
|
446
|
+
if (!meRes.ok) return { kind: 'http', httpStatus: meRes.status, where: '/api/me.json' };
|
|
447
|
+
const me = await meRes.json();
|
|
448
|
+
const modhash = me?.data?.modhash || '';
|
|
449
|
+
if (!me?.data?.name) {
|
|
450
|
+
return { kind: 'auth', detail: 'Not logged in to reddit.com (no identity in /api/me.json)' };
|
|
451
|
+
}
|
|
452
|
+
const res = await fetch('/api/comment', {
|
|
453
|
+
method: 'POST',
|
|
454
|
+
credentials: 'include',
|
|
455
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
456
|
+
body: 'parent=' + encodeURIComponent(fullname)
|
|
457
|
+
+ '&text=' + encodeURIComponent(text)
|
|
458
|
+
+ '&api_type=json'
|
|
459
|
+
+ (modhash ? '&uh=' + encodeURIComponent(modhash) : ''),
|
|
460
|
+
});
|
|
461
|
+
if (res.status === 401 || res.status === 403) {
|
|
462
|
+
return { kind: 'auth', detail: 'Reddit /api/comment returned HTTP ' + res.status };
|
|
463
|
+
}
|
|
464
|
+
if (!res.ok) return { kind: 'http', httpStatus: res.status, where: '/api/comment' };
|
|
465
|
+
const data = await res.json();
|
|
466
|
+
const errors = data?.json?.errors;
|
|
467
|
+
if (Array.isArray(errors) && errors.length > 0) {
|
|
468
|
+
return { kind: 'reddit-error', detail: errors.map((entry) => entry.join(': ')).join('; ') };
|
|
469
|
+
}
|
|
470
|
+
const things = data?.json?.data?.things;
|
|
471
|
+
const created = Array.isArray(things)
|
|
472
|
+
? things.find((thing) => thing?.kind === 't1' || String(thing?.data?.name || '').startsWith('t1_'))
|
|
473
|
+
: null;
|
|
474
|
+
const createdName = created?.data?.name || (created?.data?.id ? 't1_' + created.data.id : '');
|
|
475
|
+
if (!createdName) {
|
|
476
|
+
return { kind: 'postcondition', detail: 'Reddit comment response did not include a created reply id' };
|
|
477
|
+
}
|
|
478
|
+
return { kind: 'ok', createdName };
|
|
479
|
+
} catch (err) {
|
|
480
|
+
return { kind: 'exception', detail: String(err && err.message || err) };
|
|
481
|
+
}
|
|
482
|
+
})()`)) as RedditBrowserResult;
|
|
483
|
+
|
|
484
|
+
if (result.kind !== "ok" || !result.createdName)
|
|
485
|
+
failRedditResult("reply", result);
|
|
486
|
+
return [
|
|
487
|
+
{
|
|
488
|
+
status: "success",
|
|
489
|
+
message: `Reply posted on ${fullname} as ${result.createdName}`,
|
|
490
|
+
},
|
|
491
|
+
];
|
|
492
|
+
},
|
|
493
|
+
});
|