@jackwener/opencli 1.1.1 → 1.2.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/CONTRIBUTING.md +39 -1
- package/README.md +9 -10
- package/README.zh-CN.md +39 -17
- package/SKILL.md +10 -5
- package/dist/browser/cdp.d.ts +4 -4
- package/dist/browser/cdp.js +39 -16
- package/dist/browser/daemon-client.d.ts +2 -1
- package/dist/browser/dom-helpers.js +38 -7
- package/dist/browser/dom-snapshot.d.ts +86 -0
- package/dist/browser/dom-snapshot.js +729 -0
- package/dist/browser/dom-snapshot.test.d.ts +11 -0
- package/dist/browser/dom-snapshot.test.js +212 -0
- package/dist/browser/index.d.ts +2 -0
- package/dist/browser/index.js +1 -0
- package/dist/browser/page.d.ts +14 -24
- package/dist/browser/page.js +37 -4
- package/dist/build-manifest.d.ts +11 -4
- package/dist/build-manifest.js +59 -21
- package/dist/build-manifest.test.js +58 -2
- package/dist/cli-manifest.json +3856 -1509
- package/dist/cli.js +66 -0
- package/dist/clis/barchart/greeks.js +1 -1
- package/dist/clis/barchart/options.js +1 -1
- package/dist/clis/barchart/quote.js +1 -1
- package/dist/clis/bilibili/download.js +1 -1
- package/dist/clis/bilibili/following.js +1 -1
- package/dist/clis/bilibili/subtitle.js +1 -1
- package/dist/clis/bilibili/user-videos.js +1 -1
- package/dist/clis/boss/batchgreet.js +10 -97
- package/dist/clis/boss/chatlist.js +8 -25
- package/dist/clis/boss/chatmsg.js +11 -42
- package/dist/clis/boss/common.d.ts +92 -0
- package/dist/clis/boss/common.js +223 -0
- package/dist/clis/boss/detail.js +7 -49
- package/dist/clis/boss/exchange.js +13 -79
- package/dist/clis/boss/greet.js +18 -145
- package/dist/clis/boss/invite.js +26 -121
- package/dist/clis/boss/joblist.js +6 -31
- package/dist/clis/boss/mark.js +12 -85
- package/dist/clis/boss/recommend.js +10 -49
- package/dist/clis/boss/resume.js +18 -118
- package/dist/clis/boss/search.js +12 -60
- package/dist/clis/boss/send.js +17 -151
- package/dist/clis/boss/stats.js +18 -69
- package/dist/clis/coupang/add-to-cart.js +1 -1
- package/dist/clis/devto/tag.yaml +34 -0
- package/dist/clis/devto/top.yaml +29 -0
- package/dist/clis/devto/user.yaml +33 -0
- package/dist/clis/douban/book-hot.d.ts +1 -0
- package/dist/clis/douban/book-hot.js +14 -0
- package/dist/clis/douban/marks.d.ts +1 -0
- package/dist/clis/douban/marks.js +115 -0
- package/dist/clis/douban/movie-hot.d.ts +1 -0
- package/dist/clis/douban/movie-hot.js +14 -0
- package/dist/clis/douban/reviews.d.ts +1 -0
- package/dist/clis/douban/reviews.js +106 -0
- package/dist/clis/douban/search.d.ts +1 -0
- package/dist/clis/douban/search.js +16 -0
- package/dist/clis/douban/shared.d.ts +4 -0
- package/dist/clis/douban/shared.js +155 -0
- package/dist/clis/douban/subject.yaml +76 -0
- package/dist/clis/douban/top250.yaml +70 -0
- package/dist/clis/douban/utils.d.ts +35 -0
- package/dist/clis/douban/utils.js +48 -0
- package/dist/clis/facebook/add-friend.yaml +43 -0
- package/dist/clis/facebook/events.yaml +44 -0
- package/dist/clis/facebook/feed.yaml +63 -0
- package/dist/clis/facebook/friends.yaml +42 -0
- package/dist/clis/facebook/groups.yaml +50 -0
- package/dist/clis/facebook/join-group.yaml +44 -0
- package/dist/clis/facebook/memories.yaml +39 -0
- package/dist/clis/facebook/notifications.yaml +40 -0
- package/dist/clis/facebook/profile.yaml +37 -0
- package/dist/clis/facebook/search.yaml +46 -0
- package/dist/clis/google/news.d.ts +5 -0
- package/dist/clis/google/news.js +58 -0
- package/dist/clis/google/search.d.ts +10 -0
- package/dist/clis/google/search.js +127 -0
- package/dist/clis/google/suggest.d.ts +5 -0
- package/dist/clis/google/suggest.js +34 -0
- package/dist/clis/google/trends.d.ts +5 -0
- package/dist/clis/google/trends.js +38 -0
- package/dist/clis/google/utils.d.ts +9 -0
- package/dist/clis/google/utils.js +23 -0
- package/dist/clis/google/utils.test.d.ts +1 -0
- package/dist/clis/google/utils.test.js +75 -0
- package/dist/clis/grok/ask.d.ts +14 -0
- package/dist/clis/grok/ask.js +257 -65
- package/dist/clis/grok/ask.test.d.ts +1 -0
- package/dist/clis/grok/ask.test.js +36 -0
- package/dist/clis/instagram/comment.yaml +52 -0
- package/dist/clis/instagram/explore.yaml +43 -0
- package/dist/clis/instagram/follow.yaml +41 -0
- package/dist/clis/instagram/followers.yaml +51 -0
- package/dist/clis/instagram/following.yaml +51 -0
- package/dist/clis/instagram/like.yaml +46 -0
- package/dist/clis/instagram/profile.yaml +42 -0
- package/dist/clis/instagram/save.yaml +46 -0
- package/dist/clis/instagram/saved.yaml +40 -0
- package/dist/clis/instagram/search.yaml +43 -0
- package/dist/clis/instagram/unfollow.yaml +38 -0
- package/dist/clis/instagram/unlike.yaml +46 -0
- package/dist/clis/instagram/unsave.yaml +46 -0
- package/dist/clis/instagram/user.yaml +54 -0
- package/dist/clis/jike/repost.js +1 -1
- package/dist/clis/jimeng/generate.yaml +1 -0
- package/dist/clis/linux-do/category.yaml +1 -0
- package/dist/clis/lobsters/active.yaml +29 -0
- package/dist/clis/lobsters/hot.yaml +29 -0
- package/dist/clis/lobsters/newest.yaml +29 -0
- package/dist/clis/lobsters/tag.yaml +34 -0
- package/dist/clis/medium/feed.d.ts +1 -0
- package/dist/clis/medium/feed.js +15 -0
- package/dist/clis/medium/search.d.ts +1 -0
- package/dist/clis/medium/search.js +15 -0
- package/dist/clis/medium/shared.d.ts +5 -0
- package/dist/clis/medium/shared.js +78 -0
- package/dist/clis/medium/user.d.ts +1 -0
- package/dist/clis/medium/user.js +15 -0
- package/dist/clis/reddit/comment.js +1 -1
- package/dist/clis/reddit/read.js +1 -1
- package/dist/clis/reddit/save.js +1 -1
- package/dist/clis/reddit/subreddit.yaml +1 -0
- package/dist/clis/reddit/subscribe.js +1 -1
- package/dist/clis/reddit/upvote.js +1 -1
- package/dist/clis/sinablog/article.d.ts +1 -0
- package/dist/clis/sinablog/article.js +14 -0
- package/dist/clis/sinablog/hot.d.ts +1 -0
- package/dist/clis/sinablog/hot.js +14 -0
- package/dist/clis/sinablog/search.d.ts +1 -0
- package/dist/clis/sinablog/search.js +51 -0
- package/dist/clis/sinablog/shared.d.ts +7 -0
- package/dist/clis/sinablog/shared.js +187 -0
- package/dist/clis/sinablog/user.d.ts +1 -0
- package/dist/clis/sinablog/user.js +15 -0
- package/dist/clis/substack/feed.d.ts +1 -0
- package/dist/clis/substack/feed.js +15 -0
- package/dist/clis/substack/publication.d.ts +1 -0
- package/dist/clis/substack/publication.js +15 -0
- package/dist/clis/substack/search.d.ts +1 -0
- package/dist/clis/substack/search.js +77 -0
- package/dist/clis/substack/shared.d.ts +4 -0
- package/dist/clis/substack/shared.js +129 -0
- package/dist/clis/tiktok/comment.yaml +66 -0
- package/dist/clis/tiktok/explore.yaml +39 -0
- package/dist/clis/tiktok/follow.yaml +39 -0
- package/dist/clis/tiktok/following.yaml +46 -0
- package/dist/clis/tiktok/friends.yaml +47 -0
- package/dist/clis/tiktok/like.yaml +38 -0
- package/dist/clis/tiktok/live.yaml +51 -0
- package/dist/clis/tiktok/notifications.yaml +52 -0
- package/dist/clis/tiktok/profile.yaml +45 -0
- package/dist/clis/tiktok/save.yaml +34 -0
- package/dist/clis/tiktok/search.yaml +46 -0
- package/dist/clis/tiktok/unfollow.yaml +44 -0
- package/dist/clis/tiktok/unlike.yaml +38 -0
- package/dist/clis/tiktok/unsave.yaml +36 -0
- package/dist/clis/tiktok/user.yaml +44 -0
- package/dist/clis/twitter/download.d.ts +1 -1
- package/dist/clis/twitter/download.js +3 -3
- package/dist/clis/twitter/followers.js +1 -1
- package/dist/clis/twitter/following.js +1 -1
- package/dist/clis/twitter/thread.js +1 -1
- package/dist/clis/twitter/timeline.d.ts +23 -0
- package/dist/clis/twitter/timeline.js +42 -14
- package/dist/clis/twitter/timeline.test.d.ts +1 -0
- package/dist/clis/twitter/timeline.test.js +102 -0
- package/dist/clis/wikipedia/random.d.ts +1 -0
- package/dist/clis/wikipedia/random.js +19 -0
- package/dist/clis/wikipedia/search.js +3 -3
- package/dist/clis/wikipedia/summary.js +4 -9
- package/dist/clis/wikipedia/trending.d.ts +1 -0
- package/dist/clis/wikipedia/trending.js +35 -0
- package/dist/clis/wikipedia/utils.d.ts +28 -0
- package/dist/clis/wikipedia/utils.js +13 -0
- package/dist/clis/xiaohongshu/creator-note-detail.js +1 -1
- package/dist/clis/xiaohongshu/creator-note-detail.test.js +2 -0
- package/dist/clis/xiaohongshu/creator-notes.test.js +2 -0
- package/dist/clis/xiaohongshu/download.js +1 -1
- package/dist/clis/xueqiu/earnings-date.yaml +69 -0
- package/dist/clis/xueqiu/search.yaml +2 -1
- package/dist/clis/xueqiu/stock.yaml +2 -0
- package/dist/clis/yahoo-finance/quote.js +1 -1
- package/dist/commanderAdapter.js +13 -7
- package/dist/discovery.d.ts +8 -0
- package/dist/discovery.js +105 -19
- package/dist/doctor.js +3 -1
- package/dist/doctor.test.js +46 -2
- package/dist/engine.test.d.ts +0 -3
- package/dist/engine.test.js +74 -6
- package/dist/execution.d.ts +4 -2
- package/dist/execution.js +31 -7
- package/dist/explore.d.ts +76 -3
- package/dist/explore.js +11 -4
- package/dist/generate.d.ts +41 -2
- package/dist/generate.js +5 -4
- package/dist/main.js +2 -1
- package/dist/pipeline/executor.d.ts +2 -2
- package/dist/pipeline/executor.js +2 -2
- package/dist/pipeline/executor.test.js +33 -6
- package/dist/pipeline/registry.d.ts +1 -1
- package/dist/pipeline/steps/browser.d.ts +7 -7
- package/dist/pipeline/steps/browser.js +15 -7
- package/dist/pipeline/steps/fetch.d.ts +1 -1
- package/dist/pipeline/steps/fetch.js +11 -7
- package/dist/pipeline/steps/transform.d.ts +6 -5
- package/dist/pipeline/steps/transform.js +30 -9
- package/dist/pipeline/template.d.ts +6 -6
- package/dist/pipeline/template.js +43 -5
- package/dist/pipeline/template.test.js +18 -0
- package/dist/pipeline/transform.test.js +11 -0
- package/dist/plugin.d.ts +31 -0
- package/dist/plugin.js +216 -0
- package/dist/plugin.test.d.ts +4 -0
- package/dist/plugin.test.js +76 -0
- package/dist/registry-api.d.ts +11 -0
- package/dist/registry-api.js +9 -0
- package/dist/registry.d.ts +11 -0
- package/dist/registry.js +6 -1
- package/dist/synthesize.d.ts +94 -4
- package/dist/synthesize.js +5 -4
- package/dist/types.d.ts +39 -26
- package/dist/validate.js +8 -2
- package/docs/.vitepress/config.mts +6 -4
- package/docs/adapters/browser/barchart.md +6 -5
- package/docs/adapters/browser/bilibili.md +9 -0
- package/docs/adapters/browser/devto.md +35 -0
- package/docs/adapters/browser/douban.md +38 -0
- package/docs/adapters/browser/facebook.md +36 -0
- package/docs/adapters/browser/google.md +62 -0
- package/docs/adapters/browser/grok.md +26 -8
- package/docs/adapters/browser/instagram.md +46 -0
- package/docs/adapters/browser/lobsters.md +32 -0
- package/docs/adapters/browser/medium.md +32 -0
- package/docs/adapters/browser/reddit.md +9 -0
- package/docs/adapters/browser/sinablog.md +36 -0
- package/docs/adapters/browser/substack.md +38 -0
- package/docs/adapters/browser/tiktok.md +68 -0
- package/docs/adapters/browser/wikipedia.md +11 -2
- package/docs/adapters/browser/xueqiu.md +10 -0
- package/docs/adapters/browser/yahoo-finance.md +6 -5
- package/docs/adapters/desktop/antigravity.md +6 -0
- package/docs/adapters/desktop/chatgpt.md +2 -1
- package/docs/adapters/desktop/codex.md +5 -1
- package/docs/adapters/desktop/cursor.md +4 -0
- package/docs/adapters/desktop/discord.md +7 -7
- package/docs/adapters/index.md +1 -4
- package/docs/guide/getting-started.md +1 -0
- package/docs/guide/plugins.md +153 -0
- package/docs/zh/guide/plugins.md +107 -0
- package/extension/src/background.ts +18 -11
- package/package.json +10 -5
- package/scripts/clean-dist.cjs +13 -0
- package/src/browser/cdp.ts +71 -31
- package/src/browser/daemon-client.ts +2 -1
- package/src/browser/dom-helpers.ts +38 -7
- package/src/browser/dom-snapshot.test.ts +249 -0
- package/src/browser/dom-snapshot.ts +770 -0
- package/src/browser/index.ts +2 -0
- package/src/browser/page.ts +50 -19
- package/src/build-manifest.test.ts +70 -2
- package/src/build-manifest.ts +94 -26
- package/src/cli.ts +71 -2
- package/src/clis/barchart/greeks.ts +1 -1
- package/src/clis/barchart/options.ts +1 -1
- package/src/clis/barchart/quote.ts +1 -1
- package/src/clis/bilibili/download.ts +1 -1
- package/src/clis/bilibili/following.ts +1 -1
- package/src/clis/bilibili/subtitle.ts +1 -1
- package/src/clis/bilibili/user-videos.ts +1 -1
- package/src/clis/boss/batchgreet.ts +14 -106
- package/src/clis/boss/chatlist.ts +12 -26
- package/src/clis/boss/chatmsg.ts +16 -40
- package/src/clis/boss/common.ts +287 -0
- package/src/clis/boss/detail.ts +8 -54
- package/src/clis/boss/exchange.ts +15 -89
- package/src/clis/boss/greet.ts +23 -160
- package/src/clis/boss/invite.ts +36 -133
- package/src/clis/boss/joblist.ts +7 -36
- package/src/clis/boss/mark.ts +13 -94
- package/src/clis/boss/recommend.ts +12 -57
- package/src/clis/boss/resume.ts +19 -124
- package/src/clis/boss/search.ts +13 -66
- package/src/clis/boss/send.ts +21 -161
- package/src/clis/boss/stats.ts +19 -74
- package/src/clis/coupang/add-to-cart.ts +1 -1
- package/src/clis/devto/tag.yaml +34 -0
- package/src/clis/devto/top.yaml +29 -0
- package/src/clis/devto/user.yaml +33 -0
- package/src/clis/douban/book-hot.ts +15 -0
- package/src/clis/douban/marks.ts +135 -0
- package/src/clis/douban/movie-hot.ts +15 -0
- package/src/clis/douban/reviews.ts +127 -0
- package/src/clis/douban/search.ts +17 -0
- package/src/clis/douban/shared.ts +165 -0
- package/src/clis/douban/subject.yaml +76 -0
- package/src/clis/douban/top250.yaml +70 -0
- package/src/clis/douban/utils.ts +81 -0
- package/src/clis/facebook/add-friend.yaml +43 -0
- package/src/clis/facebook/events.yaml +44 -0
- package/src/clis/facebook/feed.yaml +63 -0
- package/src/clis/facebook/friends.yaml +42 -0
- package/src/clis/facebook/groups.yaml +50 -0
- package/src/clis/facebook/join-group.yaml +44 -0
- package/src/clis/facebook/memories.yaml +39 -0
- package/src/clis/facebook/notifications.yaml +40 -0
- package/src/clis/facebook/profile.yaml +37 -0
- package/src/clis/facebook/search.yaml +46 -0
- package/src/clis/google/news.ts +66 -0
- package/src/clis/google/search.ts +133 -0
- package/src/clis/google/suggest.ts +40 -0
- package/src/clis/google/trends.ts +44 -0
- package/src/clis/google/utils.test.ts +82 -0
- package/src/clis/google/utils.ts +24 -0
- package/src/clis/grok/ask.test.ts +53 -0
- package/src/clis/grok/ask.ts +300 -69
- package/src/clis/instagram/comment.yaml +52 -0
- package/src/clis/instagram/explore.yaml +43 -0
- package/src/clis/instagram/follow.yaml +41 -0
- package/src/clis/instagram/followers.yaml +51 -0
- package/src/clis/instagram/following.yaml +51 -0
- package/src/clis/instagram/like.yaml +46 -0
- package/src/clis/instagram/profile.yaml +42 -0
- package/src/clis/instagram/save.yaml +46 -0
- package/src/clis/instagram/saved.yaml +40 -0
- package/src/clis/instagram/search.yaml +43 -0
- package/src/clis/instagram/unfollow.yaml +38 -0
- package/src/clis/instagram/unlike.yaml +46 -0
- package/src/clis/instagram/unsave.yaml +46 -0
- package/src/clis/instagram/user.yaml +54 -0
- package/src/clis/jike/repost.ts +1 -1
- package/src/clis/jimeng/generate.yaml +1 -0
- package/src/clis/linux-do/category.yaml +1 -0
- package/src/clis/lobsters/active.yaml +29 -0
- package/src/clis/lobsters/hot.yaml +29 -0
- package/src/clis/lobsters/newest.yaml +29 -0
- package/src/clis/lobsters/tag.yaml +34 -0
- package/src/clis/medium/feed.ts +16 -0
- package/src/clis/medium/search.ts +16 -0
- package/src/clis/medium/shared.ts +83 -0
- package/src/clis/medium/user.ts +16 -0
- package/src/clis/reddit/comment.ts +1 -1
- package/src/clis/reddit/read.ts +1 -1
- package/src/clis/reddit/save.ts +1 -1
- package/src/clis/reddit/subreddit.yaml +1 -0
- package/src/clis/reddit/subscribe.ts +1 -1
- package/src/clis/reddit/upvote.ts +1 -1
- package/src/clis/sinablog/article.ts +15 -0
- package/src/clis/sinablog/hot.ts +15 -0
- package/src/clis/sinablog/search.ts +56 -0
- package/src/clis/sinablog/shared.ts +198 -0
- package/src/clis/sinablog/user.ts +16 -0
- package/src/clis/substack/feed.ts +16 -0
- package/src/clis/substack/publication.ts +16 -0
- package/src/clis/substack/search.ts +91 -0
- package/src/clis/substack/shared.ts +132 -0
- package/src/clis/tiktok/comment.yaml +66 -0
- package/src/clis/tiktok/explore.yaml +39 -0
- package/src/clis/tiktok/follow.yaml +39 -0
- package/src/clis/tiktok/following.yaml +46 -0
- package/src/clis/tiktok/friends.yaml +47 -0
- package/src/clis/tiktok/like.yaml +38 -0
- package/src/clis/tiktok/live.yaml +51 -0
- package/src/clis/tiktok/notifications.yaml +52 -0
- package/src/clis/tiktok/profile.yaml +45 -0
- package/src/clis/tiktok/save.yaml +34 -0
- package/src/clis/tiktok/search.yaml +46 -0
- package/src/clis/tiktok/unfollow.yaml +44 -0
- package/src/clis/tiktok/unlike.yaml +38 -0
- package/src/clis/tiktok/unsave.yaml +36 -0
- package/src/clis/tiktok/user.yaml +44 -0
- package/src/clis/twitter/download.ts +3 -3
- package/src/clis/twitter/followers.ts +1 -1
- package/src/clis/twitter/following.ts +1 -1
- package/src/clis/twitter/thread.ts +1 -1
- package/src/clis/twitter/timeline.test.ts +109 -0
- package/src/clis/twitter/timeline.ts +59 -19
- package/src/clis/wikipedia/random.ts +19 -0
- package/src/clis/wikipedia/search.ts +10 -4
- package/src/clis/wikipedia/summary.ts +4 -9
- package/src/clis/wikipedia/trending.ts +41 -0
- package/src/clis/wikipedia/utils.ts +31 -0
- package/src/clis/xiaohongshu/creator-note-detail.test.ts +2 -0
- package/src/clis/xiaohongshu/creator-note-detail.ts +1 -1
- package/src/clis/xiaohongshu/creator-notes.test.ts +2 -0
- package/src/clis/xiaohongshu/download.ts +1 -1
- package/src/clis/xueqiu/earnings-date.yaml +69 -0
- package/src/clis/xueqiu/search.yaml +2 -1
- package/src/clis/xueqiu/stock.yaml +2 -0
- package/src/clis/yahoo-finance/quote.ts +1 -1
- package/src/commanderAdapter.ts +17 -10
- package/src/discovery.ts +134 -24
- package/src/doctor.test.ts +59 -2
- package/src/doctor.ts +4 -2
- package/src/engine.test.ts +79 -6
- package/src/execution.ts +42 -16
- package/src/explore.ts +77 -9
- package/src/generate.ts +58 -9
- package/src/main.ts +2 -1
- package/src/pipeline/executor.test.ts +35 -6
- package/src/pipeline/executor.ts +11 -7
- package/src/pipeline/registry.ts +3 -3
- package/src/pipeline/steps/browser.ts +24 -15
- package/src/pipeline/steps/fetch.ts +18 -13
- package/src/pipeline/steps/transform.ts +40 -15
- package/src/pipeline/template.test.ts +18 -0
- package/src/pipeline/template.ts +86 -13
- package/src/pipeline/transform.test.ts +15 -2
- package/src/plugin.test.ts +86 -0
- package/src/plugin.ts +254 -0
- package/src/registry-api.ts +12 -0
- package/src/registry.ts +19 -1
- package/src/synthesize.ts +102 -21
- package/src/types.ts +44 -12
- package/src/validate.ts +19 -4
- package/tests/e2e/browser-public.test.ts +11 -0
- package/tests/e2e/public-commands.test.ts +64 -0
- package/dist/clis/feishu/new.d.ts +0 -1
- package/dist/clis/feishu/new.js +0 -27
- package/dist/clis/feishu/read.d.ts +0 -1
- package/dist/clis/feishu/read.js +0 -40
- package/dist/clis/feishu/search.d.ts +0 -1
- package/dist/clis/feishu/search.js +0 -30
- package/dist/clis/feishu/send.d.ts +0 -1
- package/dist/clis/feishu/send.js +0 -39
- package/dist/clis/feishu/status.d.ts +0 -1
- package/dist/clis/feishu/status.js +0 -28
- package/dist/clis/neteasemusic/like.d.ts +0 -1
- package/dist/clis/neteasemusic/like.js +0 -25
- package/dist/clis/neteasemusic/lyrics.d.ts +0 -1
- package/dist/clis/neteasemusic/lyrics.js +0 -47
- package/dist/clis/neteasemusic/next.d.ts +0 -1
- package/dist/clis/neteasemusic/next.js +0 -26
- package/dist/clis/neteasemusic/play.d.ts +0 -1
- package/dist/clis/neteasemusic/play.js +0 -26
- package/dist/clis/neteasemusic/playing.d.ts +0 -1
- package/dist/clis/neteasemusic/playing.js +0 -59
- package/dist/clis/neteasemusic/playlist.d.ts +0 -1
- package/dist/clis/neteasemusic/playlist.js +0 -46
- package/dist/clis/neteasemusic/prev.d.ts +0 -1
- package/dist/clis/neteasemusic/prev.js +0 -25
- package/dist/clis/neteasemusic/search.d.ts +0 -1
- package/dist/clis/neteasemusic/search.js +0 -52
- package/dist/clis/neteasemusic/status.d.ts +0 -1
- package/dist/clis/neteasemusic/status.js +0 -16
- package/dist/clis/neteasemusic/volume.d.ts +0 -1
- package/dist/clis/neteasemusic/volume.js +0 -54
- package/dist/clis/wechat/chats.d.ts +0 -1
- package/dist/clis/wechat/chats.js +0 -28
- package/dist/clis/wechat/contacts.d.ts +0 -1
- package/dist/clis/wechat/contacts.js +0 -28
- package/dist/clis/wechat/read.d.ts +0 -1
- package/dist/clis/wechat/read.js +0 -58
- package/dist/clis/wechat/search.d.ts +0 -1
- package/dist/clis/wechat/search.js +0 -31
- package/dist/clis/wechat/send.d.ts +0 -1
- package/dist/clis/wechat/send.js +0 -42
- package/dist/clis/wechat/status.d.ts +0 -1
- package/dist/clis/wechat/status.js +0 -29
- package/dist/pipeline.d.ts +0 -7
- package/dist/pipeline.js +0 -7
- package/docs/adapters/browser/github.md +0 -26
- package/docs/adapters/desktop/feishu.md +0 -20
- package/docs/adapters/desktop/neteasemusic.md +0 -31
- package/docs/adapters/desktop/wechat.md +0 -28
- package/src/clis/antigravity/README.md +0 -5
- package/src/clis/antigravity/README.zh-CN.md +0 -51
- package/src/clis/chaoxing/README.md +0 -14
- package/src/clis/chaoxing/README.zh-CN.md +0 -35
- package/src/clis/chatgpt/README.md +0 -5
- package/src/clis/chatgpt/README.zh-CN.md +0 -44
- package/src/clis/chatwise/README.md +0 -5
- package/src/clis/chatwise/README.zh-CN.md +0 -38
- package/src/clis/codex/README.md +0 -5
- package/src/clis/codex/README.zh-CN.md +0 -33
- package/src/clis/cursor/README.md +0 -5
- package/src/clis/cursor/README.zh-CN.md +0 -33
- package/src/clis/discord-app/README.md +0 -5
- package/src/clis/discord-app/README.zh-CN.md +0 -28
- package/src/clis/feishu/README.md +0 -5
- package/src/clis/feishu/README.zh-CN.md +0 -20
- package/src/clis/feishu/new.ts +0 -32
- package/src/clis/feishu/read.ts +0 -48
- package/src/clis/feishu/search.ts +0 -35
- package/src/clis/feishu/send.ts +0 -46
- package/src/clis/feishu/status.ts +0 -34
- package/src/clis/neteasemusic/README.md +0 -5
- package/src/clis/neteasemusic/README.zh-CN.md +0 -31
- package/src/clis/neteasemusic/like.ts +0 -28
- package/src/clis/neteasemusic/lyrics.ts +0 -53
- package/src/clis/neteasemusic/next.ts +0 -30
- package/src/clis/neteasemusic/play.ts +0 -30
- package/src/clis/neteasemusic/playing.ts +0 -62
- package/src/clis/neteasemusic/playlist.ts +0 -51
- package/src/clis/neteasemusic/prev.ts +0 -29
- package/src/clis/neteasemusic/search.ts +0 -58
- package/src/clis/neteasemusic/status.ts +0 -18
- package/src/clis/neteasemusic/volume.ts +0 -61
- package/src/clis/notion/README.md +0 -5
- package/src/clis/notion/README.zh-CN.md +0 -29
- package/src/clis/wechat/README.md +0 -5
- package/src/clis/wechat/README.zh-CN.md +0 -28
- package/src/clis/wechat/chats.ts +0 -33
- package/src/clis/wechat/contacts.ts +0 -33
- package/src/clis/wechat/read.ts +0 -72
- package/src/clis/wechat/search.ts +0 -36
- package/src/clis/wechat/send.ts +0 -49
- package/src/clis/wechat/status.ts +0 -35
- package/src/pipeline.ts +0 -8
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
site: tiktok
|
|
2
|
+
name: unlike
|
|
3
|
+
description: Unlike a TikTok video
|
|
4
|
+
domain: www.tiktok.com
|
|
5
|
+
|
|
6
|
+
args:
|
|
7
|
+
url:
|
|
8
|
+
type: str
|
|
9
|
+
required: true
|
|
10
|
+
positional: true
|
|
11
|
+
description: TikTok video URL
|
|
12
|
+
|
|
13
|
+
pipeline:
|
|
14
|
+
- navigate:
|
|
15
|
+
url: ${{ args.url }}
|
|
16
|
+
settleMs: 6000
|
|
17
|
+
|
|
18
|
+
- evaluate: |
|
|
19
|
+
(async () => {
|
|
20
|
+
const url = ${{ args.url | json }};
|
|
21
|
+
const btn = document.querySelector('[data-e2e="like-icon"]');
|
|
22
|
+
if (!btn) throw new Error('Like button not found - make sure you are logged in');
|
|
23
|
+
const container = btn.closest('button') || btn.closest('[role="button"]') || btn;
|
|
24
|
+
const aria = (container.getAttribute('aria-label') || '').toLowerCase();
|
|
25
|
+
const color = window.getComputedStyle(btn).color;
|
|
26
|
+
const isLiked = aria.includes('unlike') || aria.includes('取消点赞') ||
|
|
27
|
+
(color && (color.includes('255, 65') || color.includes('fe2c55')));
|
|
28
|
+
if (!isLiked) {
|
|
29
|
+
const count = document.querySelector('[data-e2e="like-count"]');
|
|
30
|
+
return [{ status: 'Not liked', likes: count ? count.textContent.trim() : '-', url: url }];
|
|
31
|
+
}
|
|
32
|
+
container.click();
|
|
33
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
34
|
+
const count = document.querySelector('[data-e2e="like-count"]');
|
|
35
|
+
return [{ status: 'Unliked', likes: count ? count.textContent.trim() : '-', url: url }];
|
|
36
|
+
})()
|
|
37
|
+
|
|
38
|
+
columns: [status, likes, url]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
site: tiktok
|
|
2
|
+
name: unsave
|
|
3
|
+
description: Remove a TikTok video from Favorites
|
|
4
|
+
domain: www.tiktok.com
|
|
5
|
+
|
|
6
|
+
args:
|
|
7
|
+
url:
|
|
8
|
+
type: str
|
|
9
|
+
required: true
|
|
10
|
+
positional: true
|
|
11
|
+
description: TikTok video URL
|
|
12
|
+
|
|
13
|
+
pipeline:
|
|
14
|
+
- navigate:
|
|
15
|
+
url: ${{ args.url }}
|
|
16
|
+
settleMs: 6000
|
|
17
|
+
|
|
18
|
+
- evaluate: |
|
|
19
|
+
(async () => {
|
|
20
|
+
const url = ${{ args.url | json }};
|
|
21
|
+
const btn = document.querySelector('[data-e2e="bookmark-icon"]') ||
|
|
22
|
+
document.querySelector('[data-e2e="collect-icon"]');
|
|
23
|
+
if (!btn) throw new Error('Favorites button not found - make sure you are logged in');
|
|
24
|
+
const container = btn.closest('button') || btn.closest('[role="button"]') || btn;
|
|
25
|
+
const aria = (container.getAttribute('aria-label') || '').toLowerCase();
|
|
26
|
+
if (aria.includes('add to favorites') || aria.includes('收藏')) {
|
|
27
|
+
if (!aria.includes('remove') && !aria.includes('取消')) {
|
|
28
|
+
return [{ status: 'Not in Favorites', url: url }];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
container.click();
|
|
32
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
33
|
+
return [{ status: 'Removed from Favorites', url: url }];
|
|
34
|
+
})()
|
|
35
|
+
|
|
36
|
+
columns: [status, url]
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
site: tiktok
|
|
2
|
+
name: user
|
|
3
|
+
description: Get recent videos from a TikTok user
|
|
4
|
+
domain: www.tiktok.com
|
|
5
|
+
|
|
6
|
+
args:
|
|
7
|
+
username:
|
|
8
|
+
type: str
|
|
9
|
+
required: true
|
|
10
|
+
positional: true
|
|
11
|
+
description: TikTok username (without @)
|
|
12
|
+
limit:
|
|
13
|
+
type: int
|
|
14
|
+
default: 10
|
|
15
|
+
description: Number of videos
|
|
16
|
+
|
|
17
|
+
pipeline:
|
|
18
|
+
- navigate:
|
|
19
|
+
url: https://www.tiktok.com/@${{ args.username }}
|
|
20
|
+
settleMs: 6000
|
|
21
|
+
|
|
22
|
+
- evaluate: |
|
|
23
|
+
(() => {
|
|
24
|
+
const limit = ${{ args.limit }};
|
|
25
|
+
const username = ${{ args.username | json }};
|
|
26
|
+
const links = Array.from(document.querySelectorAll('a[href*="/video/"]'));
|
|
27
|
+
const seen = {};
|
|
28
|
+
const results = [];
|
|
29
|
+
for (const a of links) {
|
|
30
|
+
const href = a.href;
|
|
31
|
+
if (seen[href]) continue;
|
|
32
|
+
seen[href] = true;
|
|
33
|
+
results.push({
|
|
34
|
+
index: results.length + 1,
|
|
35
|
+
views: a.textContent.trim() || '-',
|
|
36
|
+
url: href,
|
|
37
|
+
});
|
|
38
|
+
if (results.length >= limit) break;
|
|
39
|
+
}
|
|
40
|
+
if (results.length === 0) throw new Error('No videos found for @' + username);
|
|
41
|
+
return results;
|
|
42
|
+
})()
|
|
43
|
+
|
|
44
|
+
columns: [index, views, url]
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Twitter/X download — download images and videos from tweets.
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
|
-
* opencli twitter download
|
|
5
|
+
* opencli twitter download elonmusk --limit 10 --output ./twitter
|
|
6
6
|
* opencli twitter download --tweet-url https://x.com/xxx/status/123 --output ./twitter
|
|
7
7
|
*/
|
|
8
8
|
|
|
@@ -27,7 +27,7 @@ cli({
|
|
|
27
27
|
domain: 'x.com',
|
|
28
28
|
strategy: Strategy.COOKIE,
|
|
29
29
|
args: [
|
|
30
|
-
{ name: 'username', help: 'Twitter username (downloads from media tab)' },
|
|
30
|
+
{ name: 'username', positional: true, help: 'Twitter username (downloads from media tab)' },
|
|
31
31
|
{ name: 'tweet-url', help: 'Single tweet URL to download' },
|
|
32
32
|
{ name: 'limit', type: 'int', default: 10, help: 'Number of tweets to scan' },
|
|
33
33
|
{ name: 'output', default: './twitter-downloads', help: 'Output directory' },
|
|
@@ -44,7 +44,7 @@ cli({
|
|
|
44
44
|
index: 0,
|
|
45
45
|
type: '-',
|
|
46
46
|
status: 'failed',
|
|
47
|
-
size: 'Must provide
|
|
47
|
+
size: 'Must provide a username or --tweet-url',
|
|
48
48
|
}];
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -8,7 +8,7 @@ cli({
|
|
|
8
8
|
strategy: Strategy.INTERCEPT,
|
|
9
9
|
browser: true,
|
|
10
10
|
args: [
|
|
11
|
-
{ name: 'user', type: 'string', required: false },
|
|
11
|
+
{ name: 'user', positional: true, type: 'string', required: false },
|
|
12
12
|
{ name: 'limit', type: 'int', default: 50 },
|
|
13
13
|
],
|
|
14
14
|
columns: ['screen_name', 'name', 'bio', 'followers'],
|
|
@@ -8,7 +8,7 @@ cli({
|
|
|
8
8
|
strategy: Strategy.INTERCEPT,
|
|
9
9
|
browser: true,
|
|
10
10
|
args: [
|
|
11
|
-
{ name: 'user', type: 'string', required: false },
|
|
11
|
+
{ name: 'user', positional: true, type: 'string', required: false },
|
|
12
12
|
{ name: 'limit', type: 'int', default: 50 },
|
|
13
13
|
],
|
|
14
14
|
columns: ['screen_name', 'name', 'bio', 'followers'],
|
|
@@ -122,7 +122,7 @@ cli({
|
|
|
122
122
|
strategy: Strategy.COOKIE,
|
|
123
123
|
browser: true,
|
|
124
124
|
args: [
|
|
125
|
-
{ name: 'tweet-id', type: 'string', required: true },
|
|
125
|
+
{ name: 'tweet-id', positional: true, type: 'string', required: true },
|
|
126
126
|
{ name: 'limit', type: 'int', default: 50 },
|
|
127
127
|
],
|
|
128
128
|
columns: ['id', 'author', 'text', 'likes', 'retweets', 'url'],
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { __test__ } from './timeline.js';
|
|
3
|
+
|
|
4
|
+
describe('twitter timeline helpers', () => {
|
|
5
|
+
it('builds for-you variables with withCommunity', () => {
|
|
6
|
+
expect(__test__.buildTimelineVariables('for-you', 20)).toEqual({
|
|
7
|
+
count: 20,
|
|
8
|
+
includePromotedContent: false,
|
|
9
|
+
latestControlAvailable: true,
|
|
10
|
+
requestContext: 'launch',
|
|
11
|
+
withCommunity: true,
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('builds following variables with seenTweetIds instead of withCommunity', () => {
|
|
16
|
+
expect(__test__.buildTimelineVariables('following', 20, 'cursor-1')).toEqual({
|
|
17
|
+
count: 20,
|
|
18
|
+
includePromotedContent: false,
|
|
19
|
+
latestControlAvailable: true,
|
|
20
|
+
requestContext: 'launch',
|
|
21
|
+
seenTweetIds: [],
|
|
22
|
+
cursor: 'cursor-1',
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('encodes variables into timeline url', () => {
|
|
27
|
+
const url = __test__.buildHomeTimelineUrl('query123', 'HomeLatestTimeline', {
|
|
28
|
+
count: 20,
|
|
29
|
+
seenTweetIds: [],
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
expect(url).toContain('/i/api/graphql/query123/HomeLatestTimeline');
|
|
33
|
+
expect(url).toContain('variables=');
|
|
34
|
+
expect(url).toContain('features=');
|
|
35
|
+
expect(decodeURIComponent(url)).toContain('"seenTweetIds":[]');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('parses tweets and bottom cursor from home timeline payload', () => {
|
|
39
|
+
const payload = {
|
|
40
|
+
data: {
|
|
41
|
+
home: {
|
|
42
|
+
home_timeline_urt: {
|
|
43
|
+
instructions: [
|
|
44
|
+
{
|
|
45
|
+
entries: [
|
|
46
|
+
{
|
|
47
|
+
entryId: 'tweet-1',
|
|
48
|
+
content: {
|
|
49
|
+
itemContent: {
|
|
50
|
+
tweet_results: {
|
|
51
|
+
result: {
|
|
52
|
+
rest_id: '1',
|
|
53
|
+
legacy: {
|
|
54
|
+
full_text: 'hello',
|
|
55
|
+
favorite_count: 3,
|
|
56
|
+
retweet_count: 2,
|
|
57
|
+
reply_count: 1,
|
|
58
|
+
created_at: 'now',
|
|
59
|
+
},
|
|
60
|
+
core: {
|
|
61
|
+
user_results: {
|
|
62
|
+
result: {
|
|
63
|
+
legacy: {
|
|
64
|
+
screen_name: 'alice',
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
views: {
|
|
70
|
+
count: '9',
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
entryId: 'cursor-bottom-1',
|
|
79
|
+
content: {
|
|
80
|
+
entryType: 'TimelineTimelineCursor',
|
|
81
|
+
cursorType: 'Bottom',
|
|
82
|
+
value: 'cursor-next',
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const result = __test__.parseHomeTimeline(payload, new Set());
|
|
94
|
+
|
|
95
|
+
expect(result.nextCursor).toBe('cursor-next');
|
|
96
|
+
expect(result.tweets).toHaveLength(1);
|
|
97
|
+
expect(result.tweets[0]).toMatchObject({
|
|
98
|
+
id: '1',
|
|
99
|
+
author: 'alice',
|
|
100
|
+
text: 'hello',
|
|
101
|
+
likes: 3,
|
|
102
|
+
retweets: 2,
|
|
103
|
+
replies: 1,
|
|
104
|
+
views: 9,
|
|
105
|
+
created_at: 'now',
|
|
106
|
+
url: 'https://x.com/alice/status/1',
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -2,8 +2,24 @@ import { cli, Strategy } from '../../registry.js';
|
|
|
2
2
|
|
|
3
3
|
// ── Twitter GraphQL constants ──────────────────────────────────────────
|
|
4
4
|
|
|
5
|
-
const BEARER_TOKEN =
|
|
5
|
+
const BEARER_TOKEN =
|
|
6
|
+
'AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA';
|
|
6
7
|
const HOME_TIMELINE_QUERY_ID = 'c-CzHF1LboFilMpsx4ZCrQ';
|
|
8
|
+
const HOME_LATEST_TIMELINE_QUERY_ID = 'BKB7oi212Fi7kQtCBGE4zA';
|
|
9
|
+
|
|
10
|
+
type TimelineType = 'for-you' | 'following';
|
|
11
|
+
|
|
12
|
+
interface TimelineEndpointConfig {
|
|
13
|
+
endpoint: string;
|
|
14
|
+
method: 'GET' | 'POST';
|
|
15
|
+
fallbackQueryId: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Endpoint config: for-you uses GET HomeTimeline, following uses POST HomeLatestTimeline
|
|
19
|
+
const TIMELINE_ENDPOINTS: Record<TimelineType, TimelineEndpointConfig> = {
|
|
20
|
+
'for-you': { endpoint: 'HomeTimeline', method: 'GET', fallbackQueryId: HOME_TIMELINE_QUERY_ID },
|
|
21
|
+
following: { endpoint: 'HomeLatestTimeline', method: 'POST', fallbackQueryId: HOME_LATEST_TIMELINE_QUERY_ID },
|
|
22
|
+
};
|
|
7
23
|
|
|
8
24
|
const FEATURES = {
|
|
9
25
|
rweb_video_screen_enabled: false,
|
|
@@ -53,19 +69,26 @@ interface TimelineTweet {
|
|
|
53
69
|
url: string;
|
|
54
70
|
}
|
|
55
71
|
|
|
56
|
-
function
|
|
57
|
-
const vars: Record<string,
|
|
72
|
+
function buildTimelineVariables(type: TimelineType, count: number, cursor?: string | null): Record<string, unknown> {
|
|
73
|
+
const vars: Record<string, unknown> = {
|
|
58
74
|
count,
|
|
59
75
|
includePromotedContent: false,
|
|
60
76
|
latestControlAvailable: true,
|
|
61
77
|
requestContext: 'launch',
|
|
62
|
-
withCommunity: true,
|
|
63
78
|
};
|
|
79
|
+
if (type === 'for-you') vars.withCommunity = true;
|
|
80
|
+
if (type === 'following') vars.seenTweetIds = [];
|
|
64
81
|
if (cursor) vars.cursor = cursor;
|
|
82
|
+
return vars;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function buildHomeTimelineUrl(queryId: string, endpoint: string, vars: Record<string, unknown>): string {
|
|
65
86
|
|
|
66
|
-
return
|
|
67
|
-
|
|
68
|
-
|
|
87
|
+
return (
|
|
88
|
+
`/i/api/graphql/${queryId}/${endpoint}` +
|
|
89
|
+
`?variables=${encodeURIComponent(JSON.stringify(vars))}` +
|
|
90
|
+
`&features=${encodeURIComponent(JSON.stringify(FEATURES))}`
|
|
91
|
+
);
|
|
69
92
|
}
|
|
70
93
|
|
|
71
94
|
function extractTweet(result: any, seen: Set<string>): TimelineTweet | null {
|
|
@@ -97,8 +120,8 @@ function parseHomeTimeline(data: any, seen: Set<string>): { tweets: TimelineTwee
|
|
|
97
120
|
const tweets: TimelineTweet[] = [];
|
|
98
121
|
let nextCursor: string | null = null;
|
|
99
122
|
|
|
100
|
-
|
|
101
|
-
|
|
123
|
+
// Both HomeTimeline and HomeLatestTimeline share the same response envelope
|
|
124
|
+
const instructions = data?.data?.home?.home_timeline_urt?.instructions || [];
|
|
102
125
|
|
|
103
126
|
for (const inst of instructions) {
|
|
104
127
|
for (const entry of inst.entries || []) {
|
|
@@ -144,16 +167,24 @@ function parseHomeTimeline(data: any, seen: Set<string>): { tweets: TimelineTwee
|
|
|
144
167
|
cli({
|
|
145
168
|
site: 'twitter',
|
|
146
169
|
name: 'timeline',
|
|
147
|
-
description: 'Fetch Twitter
|
|
170
|
+
description: 'Fetch Twitter timeline (for-you or following)',
|
|
148
171
|
domain: 'x.com',
|
|
149
172
|
strategy: Strategy.COOKIE,
|
|
150
173
|
browser: true,
|
|
151
174
|
args: [
|
|
175
|
+
{
|
|
176
|
+
name: 'type',
|
|
177
|
+
default: 'for-you',
|
|
178
|
+
choices: ['for-you', 'following'],
|
|
179
|
+
help: 'Timeline type: for-you (algorithmic) or following (chronological)',
|
|
180
|
+
},
|
|
152
181
|
{ name: 'limit', type: 'int', default: 20 },
|
|
153
182
|
],
|
|
154
183
|
columns: ['id', 'author', 'text', 'likes', 'retweets', 'replies', 'views', 'created_at', 'url'],
|
|
155
184
|
func: async (page, kwargs) => {
|
|
156
185
|
const limit = kwargs.limit || 20;
|
|
186
|
+
const timelineType: TimelineType = kwargs.type === 'following' ? 'following' : 'for-you';
|
|
187
|
+
const { endpoint, method, fallbackQueryId } = TIMELINE_ENDPOINTS[timelineType];
|
|
157
188
|
|
|
158
189
|
// Navigate to x.com for cookie context
|
|
159
190
|
await page.goto('https://x.com');
|
|
@@ -165,22 +196,24 @@ cli({
|
|
|
165
196
|
}`);
|
|
166
197
|
if (!ct0) throw new Error('Not logged into x.com (no ct0 cookie)');
|
|
167
198
|
|
|
168
|
-
// Dynamically resolve queryId
|
|
169
|
-
const
|
|
199
|
+
// Dynamically resolve queryId for the selected endpoint
|
|
200
|
+
const resolved = await page.evaluate(`async () => {
|
|
170
201
|
try {
|
|
171
202
|
const ghResp = await fetch('https://raw.githubusercontent.com/fa0311/twitter-openapi/refs/heads/main/src/config/placeholder.json');
|
|
172
203
|
if (ghResp.ok) {
|
|
173
204
|
const data = await ghResp.json();
|
|
174
|
-
const entry = data['
|
|
205
|
+
const entry = data['${endpoint}'];
|
|
175
206
|
if (entry && entry.queryId) return entry.queryId;
|
|
176
207
|
}
|
|
177
208
|
} catch {}
|
|
178
209
|
return null;
|
|
179
|
-
}`)
|
|
210
|
+
}`);
|
|
211
|
+
// Validate queryId format to prevent injection from untrusted upstream
|
|
212
|
+
const queryId = typeof resolved === 'string' && /^[A-Za-z0-9_-]+$/.test(resolved) ? resolved : fallbackQueryId;
|
|
180
213
|
|
|
181
214
|
// Build auth headers
|
|
182
215
|
const headers = JSON.stringify({
|
|
183
|
-
|
|
216
|
+
Authorization: `Bearer ${decodeURIComponent(BEARER_TOKEN)}`,
|
|
184
217
|
'X-Csrf-Token': ct0,
|
|
185
218
|
'X-Twitter-Auth-Type': 'OAuth2Session',
|
|
186
219
|
'X-Twitter-Active-User': 'yes',
|
|
@@ -193,16 +226,17 @@ cli({
|
|
|
193
226
|
|
|
194
227
|
for (let i = 0; i < 5 && allTweets.length < limit; i++) {
|
|
195
228
|
const fetchCount = Math.min(40, limit - allTweets.length + 5); // over-fetch slightly for promoted filtering
|
|
196
|
-
const
|
|
197
|
-
|
|
229
|
+
const variables = buildTimelineVariables(timelineType, fetchCount, cursor);
|
|
230
|
+
const apiUrl = buildHomeTimelineUrl(queryId, endpoint, variables);
|
|
198
231
|
|
|
199
232
|
const data = await page.evaluate(`async () => {
|
|
200
|
-
const r = await fetch("${apiUrl}", { headers: ${headers}, credentials: 'include' });
|
|
233
|
+
const r = await fetch("${apiUrl}", { method: "${method}", headers: ${headers}, credentials: 'include' });
|
|
201
234
|
return r.ok ? await r.json() : { error: r.status };
|
|
202
235
|
}`);
|
|
203
236
|
|
|
204
237
|
if (data?.error) {
|
|
205
|
-
if (allTweets.length === 0)
|
|
238
|
+
if (allTweets.length === 0)
|
|
239
|
+
throw new Error(`HTTP ${data.error}: Failed to fetch timeline. queryId may have expired.`);
|
|
206
240
|
break;
|
|
207
241
|
}
|
|
208
242
|
|
|
@@ -216,3 +250,9 @@ cli({
|
|
|
216
250
|
return allTweets.slice(0, limit);
|
|
217
251
|
},
|
|
218
252
|
});
|
|
253
|
+
|
|
254
|
+
export const __test__ = {
|
|
255
|
+
buildTimelineVariables,
|
|
256
|
+
buildHomeTimelineUrl,
|
|
257
|
+
parseHomeTimeline,
|
|
258
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { CliError } from '../../errors.js';
|
|
2
|
+
import { cli, Strategy } from '../../registry.js';
|
|
3
|
+
import { type WikiSummary, formatSummaryRow, wikiFetch } from './utils.js';
|
|
4
|
+
|
|
5
|
+
cli({
|
|
6
|
+
site: 'wikipedia',
|
|
7
|
+
name: 'random',
|
|
8
|
+
description: 'Get a random Wikipedia article',
|
|
9
|
+
strategy: Strategy.PUBLIC,
|
|
10
|
+
browser: false,
|
|
11
|
+
args: [{ name: 'lang', default: 'en', help: 'Language code (e.g. en, zh, ja)' }],
|
|
12
|
+
columns: ['title', 'description', 'extract', 'url'],
|
|
13
|
+
func: async (_page, args) => {
|
|
14
|
+
const lang = args.lang || 'en';
|
|
15
|
+
const data = (await wikiFetch(lang, '/api/rest_v1/page/random/summary')) as WikiSummary;
|
|
16
|
+
if (!data?.title) throw new CliError('NOT_FOUND', 'No random article returned', 'Try again');
|
|
17
|
+
return [formatSummaryRow(data, lang)];
|
|
18
|
+
},
|
|
19
|
+
});
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { cli, Strategy } from '../../registry.js';
|
|
2
1
|
import { CliError } from '../../errors.js';
|
|
2
|
+
import { cli, Strategy } from '../../registry.js';
|
|
3
3
|
import { wikiFetch } from './utils.js';
|
|
4
4
|
|
|
5
|
-
interface WikiSearchResult {
|
|
5
|
+
interface WikiSearchResult {
|
|
6
|
+
title: string;
|
|
7
|
+
snippet: string;
|
|
8
|
+
}
|
|
6
9
|
|
|
7
10
|
cli({
|
|
8
11
|
site: 'wikipedia',
|
|
@@ -19,8 +22,11 @@ cli({
|
|
|
19
22
|
func: async (_page, args) => {
|
|
20
23
|
const limit = Math.max(1, Math.min(Number(args.limit), 50));
|
|
21
24
|
const lang = args.lang || 'en';
|
|
22
|
-
const q = encodeURIComponent(args.
|
|
23
|
-
const data = await wikiFetch(
|
|
25
|
+
const q = encodeURIComponent(args.query);
|
|
26
|
+
const data = (await wikiFetch(
|
|
27
|
+
lang,
|
|
28
|
+
`/w/api.php?action=query&list=search&srsearch=${q}&srlimit=${limit}&format=json&utf8=1`,
|
|
29
|
+
)) as { query?: { search?: WikiSearchResult[] } };
|
|
24
30
|
const results = data?.query?.search;
|
|
25
31
|
if (!results?.length) throw new CliError('NOT_FOUND', 'No articles found', 'Try a different keyword');
|
|
26
32
|
return results.map((r) => ({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { cli, Strategy } from '../../registry.js';
|
|
2
1
|
import { CliError } from '../../errors.js';
|
|
3
|
-
import {
|
|
2
|
+
import { cli, Strategy } from '../../registry.js';
|
|
3
|
+
import { type WikiSummary, formatSummaryRow, wikiFetch } from './utils.js';
|
|
4
4
|
|
|
5
5
|
cli({
|
|
6
6
|
site: 'wikipedia',
|
|
@@ -16,13 +16,8 @@ cli({
|
|
|
16
16
|
func: async (_page, args) => {
|
|
17
17
|
const lang = args.lang || 'en';
|
|
18
18
|
const title = encodeURIComponent(args.title.replace(/ /g, '_'));
|
|
19
|
-
const data = await wikiFetch(lang, `/api/rest_v1/page/summary/${title}`) as
|
|
19
|
+
const data = (await wikiFetch(lang, `/api/rest_v1/page/summary/${title}`)) as WikiSummary;
|
|
20
20
|
if (!data?.title) throw new CliError('NOT_FOUND', `Article "${args.title}" not found`, 'Try searching first: opencli wikipedia search <keyword>');
|
|
21
|
-
return [
|
|
22
|
-
title: data.title,
|
|
23
|
-
description: data.description ?? '-',
|
|
24
|
-
extract: (data.extract ?? '').slice(0, 300),
|
|
25
|
-
url: data.content_urls?.desktop?.page ?? `https://${lang}.wikipedia.org/wiki/${title}`,
|
|
26
|
-
}];
|
|
21
|
+
return [formatSummaryRow(data, lang)];
|
|
27
22
|
},
|
|
28
23
|
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { CliError } from '../../errors.js';
|
|
2
|
+
import { cli, Strategy } from '../../registry.js';
|
|
3
|
+
import { DESC_MAX_LEN, type WikiMostReadArticle, wikiFetch } from './utils.js';
|
|
4
|
+
|
|
5
|
+
cli({
|
|
6
|
+
site: 'wikipedia',
|
|
7
|
+
name: 'trending',
|
|
8
|
+
description: 'Most-read Wikipedia articles (yesterday)',
|
|
9
|
+
strategy: Strategy.PUBLIC,
|
|
10
|
+
browser: false,
|
|
11
|
+
args: [
|
|
12
|
+
{ name: 'limit', type: 'int', default: 10, help: 'Max results' },
|
|
13
|
+
{ name: 'lang', default: 'en', help: 'Language code (e.g. en, zh, ja)' },
|
|
14
|
+
],
|
|
15
|
+
columns: ['rank', 'title', 'description', 'views'],
|
|
16
|
+
func: async (_page, args) => {
|
|
17
|
+
const lang = args.lang || 'en';
|
|
18
|
+
const limit = Math.max(1, Math.min(Number(args.limit), 50));
|
|
19
|
+
|
|
20
|
+
// Use yesterday's UTC date — Wikipedia API expects UTC and yesterday
|
|
21
|
+
// guarantees data availability (today's aggregation may be incomplete).
|
|
22
|
+
const d = new Date(Date.now() - 86_400_000);
|
|
23
|
+
const yyyy = d.getUTCFullYear();
|
|
24
|
+
const mm = String(d.getUTCMonth() + 1).padStart(2, '0');
|
|
25
|
+
const dd = String(d.getUTCDate()).padStart(2, '0');
|
|
26
|
+
|
|
27
|
+
const data = (await wikiFetch(lang, `/api/rest_v1/feed/featured/${yyyy}/${mm}/${dd}`)) as {
|
|
28
|
+
mostread?: { articles?: WikiMostReadArticle[] };
|
|
29
|
+
};
|
|
30
|
+
const articles = data?.mostread?.articles;
|
|
31
|
+
if (!articles?.length)
|
|
32
|
+
throw new CliError('NOT_FOUND', 'No trending articles available', 'Try a different language with --lang');
|
|
33
|
+
|
|
34
|
+
return articles.slice(0, limit).map((a, i) => ({
|
|
35
|
+
rank: i + 1,
|
|
36
|
+
title: a.title ?? '-',
|
|
37
|
+
description: (a.description ?? '-').slice(0, DESC_MAX_LEN),
|
|
38
|
+
views: a.views ?? 0,
|
|
39
|
+
}));
|
|
40
|
+
},
|
|
41
|
+
});
|
|
@@ -8,6 +8,27 @@
|
|
|
8
8
|
|
|
9
9
|
import { CliError } from '../../errors.js';
|
|
10
10
|
|
|
11
|
+
/** Maximum character length for article extract fields. */
|
|
12
|
+
export const EXTRACT_MAX_LEN = 300;
|
|
13
|
+
|
|
14
|
+
/** Maximum character length for short description fields. */
|
|
15
|
+
export const DESC_MAX_LEN = 80;
|
|
16
|
+
|
|
17
|
+
/** Response shape shared by /page/summary and /page/random/summary endpoints. */
|
|
18
|
+
export interface WikiSummary {
|
|
19
|
+
title?: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
extract?: string;
|
|
22
|
+
content_urls?: { desktop?: { page?: string } };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Article entry returned by the /feed/featured most-read endpoint. */
|
|
26
|
+
export interface WikiMostReadArticle {
|
|
27
|
+
title?: string;
|
|
28
|
+
description?: string;
|
|
29
|
+
views?: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
11
32
|
export async function wikiFetch(lang: string, path: string): Promise<unknown> {
|
|
12
33
|
const url = `https://${lang}.wikipedia.org${path}`;
|
|
13
34
|
const resp = await fetch(url, {
|
|
@@ -18,3 +39,13 @@ export async function wikiFetch(lang: string, path: string): Promise<unknown> {
|
|
|
18
39
|
}
|
|
19
40
|
return resp.json();
|
|
20
41
|
}
|
|
42
|
+
|
|
43
|
+
/** Map a WikiSummary API response to the standard output row. */
|
|
44
|
+
export function formatSummaryRow(data: WikiSummary, lang: string) {
|
|
45
|
+
return {
|
|
46
|
+
title: data.title!,
|
|
47
|
+
description: data.description ?? '-',
|
|
48
|
+
extract: (data.extract ?? '').slice(0, EXTRACT_MAX_LEN),
|
|
49
|
+
url: data.content_urls?.desktop?.page ?? `https://${lang}.wikipedia.org`,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -18,6 +18,8 @@ function createPageMock(evaluateResult: any): IPage {
|
|
|
18
18
|
click: vi.fn().mockResolvedValue(undefined),
|
|
19
19
|
typeText: vi.fn().mockResolvedValue(undefined),
|
|
20
20
|
pressKey: vi.fn().mockResolvedValue(undefined),
|
|
21
|
+
scrollTo: vi.fn().mockResolvedValue(undefined),
|
|
22
|
+
getFormState: vi.fn().mockResolvedValue({ forms: [], orphanFields: [] }),
|
|
21
23
|
wait: vi.fn().mockResolvedValue(undefined),
|
|
22
24
|
tabs: vi.fn().mockResolvedValue([]),
|
|
23
25
|
closeTab: vi.fn().mockResolvedValue(undefined),
|
|
@@ -430,7 +430,7 @@ cli({
|
|
|
430
430
|
strategy: Strategy.COOKIE,
|
|
431
431
|
browser: true,
|
|
432
432
|
args: [
|
|
433
|
-
{ name: 'note-id', type: 'string', required: true, help: 'Note ID (from creator-notes or note-detail page URL)' },
|
|
433
|
+
{ name: 'note-id', positional: true, type: 'string', required: true, help: 'Note ID (from creator-notes or note-detail page URL)' },
|
|
434
434
|
],
|
|
435
435
|
columns: ['section', 'metric', 'value', 'extra'],
|
|
436
436
|
func: async (page, kwargs) => {
|
|
@@ -22,6 +22,8 @@ function createPageMock(evaluateResult: any, interceptedRequests: any[] = []): I
|
|
|
22
22
|
click: vi.fn().mockResolvedValue(undefined),
|
|
23
23
|
typeText: vi.fn().mockResolvedValue(undefined),
|
|
24
24
|
pressKey: vi.fn().mockResolvedValue(undefined),
|
|
25
|
+
scrollTo: vi.fn().mockResolvedValue(undefined),
|
|
26
|
+
getFormState: vi.fn().mockResolvedValue({ forms: [], orphanFields: [] }),
|
|
25
27
|
wait: vi.fn().mockResolvedValue(undefined),
|
|
26
28
|
tabs: vi.fn().mockResolvedValue([]),
|
|
27
29
|
closeTab: vi.fn().mockResolvedValue(undefined),
|
|
@@ -22,7 +22,7 @@ cli({
|
|
|
22
22
|
domain: 'www.xiaohongshu.com',
|
|
23
23
|
strategy: Strategy.COOKIE,
|
|
24
24
|
args: [
|
|
25
|
-
{ name: 'note-id', required: true, help: 'Note ID (from URL)' },
|
|
25
|
+
{ name: 'note-id', positional: true, required: true, help: 'Note ID (from URL)' },
|
|
26
26
|
{ name: 'output', default: './xiaohongshu-downloads', help: 'Output directory' },
|
|
27
27
|
],
|
|
28
28
|
columns: ['index', 'type', 'status', 'size'],
|