@jackwener/opencli 1.3.3 → 1.4.1
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/.github/actions/setup-chrome/action.yml +5 -4
- package/.github/pull_request_template.md +3 -1
- package/.github/workflows/build-extension.yml +7 -1
- package/.github/workflows/ci.yml +46 -6
- package/.github/workflows/docs.yml +1 -1
- package/.github/workflows/e2e-headed.yml +36 -3
- package/.github/workflows/release.yml +1 -1
- package/.github/workflows/security.yml +0 -3
- package/CHANGELOG.md +78 -0
- package/CONTRIBUTING.md +6 -3
- package/PRIVACY.md +57 -0
- package/README.md +31 -4
- package/README.zh-CN.md +31 -4
- package/SKILL.md +107 -2
- package/TESTING.md +1 -0
- package/chatwise-opencli.ps1 +82 -0
- package/dist/analysis.d.ts +38 -0
- package/dist/analysis.js +166 -0
- package/dist/browser/cdp.d.ts +0 -4
- package/dist/browser/cdp.js +53 -41
- package/dist/browser/cdp.test.d.ts +1 -0
- package/dist/browser/cdp.test.js +52 -0
- package/dist/browser/dom-snapshot.d.ts +2 -2
- package/dist/browser/dom-snapshot.js +54 -1
- package/dist/browser/dom-snapshot.test.js +36 -0
- package/dist/browser/index.d.ts +2 -2
- package/dist/browser/index.js +1 -1
- package/dist/browser/mcp.d.ts +0 -2
- package/dist/browser/mcp.js +2 -3
- package/dist/browser/page.d.ts +4 -3
- package/dist/browser/page.js +34 -37
- package/dist/browser/stealth.d.ts +0 -2
- package/dist/browser/stealth.js +24 -9
- package/dist/browser.test.js +2 -2
- package/dist/build-manifest.js +15 -9
- package/dist/build-manifest.test.js +12 -0
- package/dist/cascade.js +4 -2
- package/dist/cli-manifest.json +1325 -256
- package/dist/cli.js +57 -29
- package/dist/clis/_shared/desktop-commands.d.ts +22 -0
- package/dist/clis/_shared/desktop-commands.js +108 -0
- package/dist/clis/antigravity/serve.js +5 -2
- package/dist/clis/apple-podcasts/search.js +2 -1
- package/dist/clis/arxiv/search.js +3 -3
- package/dist/clis/bbc/news.js +0 -1
- package/dist/clis/bilibili/dynamic.test.d.ts +1 -0
- package/dist/clis/bilibili/dynamic.test.js +68 -0
- package/dist/clis/bilibili/favorite.js +4 -2
- package/dist/clis/bilibili/following.js +3 -2
- package/dist/clis/bilibili/subtitle.js +8 -7
- package/dist/clis/bilibili/utils.js +2 -2
- package/dist/clis/boss/batchgreet.js +1 -1
- package/dist/clis/boss/chatlist.js +1 -1
- package/dist/clis/boss/chatmsg.js +1 -1
- package/dist/clis/boss/detail.js +1 -1
- package/dist/clis/boss/exchange.js +1 -1
- package/dist/clis/boss/greet.js +1 -1
- package/dist/clis/boss/invite.js +1 -1
- package/dist/clis/boss/joblist.js +1 -1
- package/dist/clis/boss/mark.js +4 -3
- package/dist/clis/boss/recommend.js +1 -1
- package/dist/clis/boss/resume.js +1 -1
- package/dist/clis/boss/search.js +1 -1
- package/dist/clis/boss/send.js +5 -4
- package/dist/clis/boss/stats.js +1 -1
- package/dist/clis/chatgpt/ask.js +4 -0
- package/dist/clis/chatgpt/new.js +5 -1
- package/dist/clis/chatgpt/read.js +5 -1
- package/dist/clis/chatgpt/send.js +2 -1
- package/dist/clis/chatgpt/status.js +5 -1
- package/dist/clis/chatwise/ask.js +8 -2
- package/dist/clis/chatwise/export.js +2 -0
- package/dist/clis/chatwise/history.js +2 -0
- package/dist/clis/chatwise/model.js +8 -3
- package/dist/clis/chatwise/new.js +3 -18
- package/dist/clis/chatwise/read.js +2 -0
- package/dist/clis/chatwise/screenshot.js +3 -27
- package/dist/clis/chatwise/send.js +8 -2
- package/dist/clis/chatwise/shared.d.ts +2 -0
- package/dist/clis/chatwise/shared.js +6 -0
- package/dist/clis/chatwise/status.js +3 -22
- package/dist/clis/codex/ask.js +6 -2
- package/dist/clis/codex/dump.js +2 -25
- package/dist/clis/codex/new.js +2 -25
- package/dist/clis/codex/screenshot.js +2 -27
- package/dist/clis/codex/send.js +6 -4
- package/dist/clis/codex/status.js +2 -22
- package/dist/clis/ctrip/search.js +0 -1
- package/dist/clis/cursor/ask.js +2 -1
- package/dist/clis/cursor/composer.js +2 -1
- package/dist/clis/cursor/dump.js +2 -25
- package/dist/clis/cursor/new.js +2 -18
- package/dist/clis/cursor/read.js +2 -1
- package/dist/clis/cursor/screenshot.js +1 -30
- package/dist/clis/cursor/send.js +2 -1
- package/dist/clis/cursor/status.js +2 -21
- package/dist/clis/dictionary/examples.yaml +25 -0
- package/dist/clis/dictionary/search.yaml +27 -0
- package/dist/clis/dictionary/synonyms.yaml +25 -0
- package/dist/clis/douban/book-hot.js +1 -1
- package/dist/clis/douban/movie-hot.js +1 -1
- package/dist/clis/douban/search.js +1 -1
- package/dist/clis/douban/utils.d.ts +4 -1
- package/dist/clis/douban/utils.js +156 -1
- package/dist/clis/doubao/ask.js +1 -1
- package/dist/clis/doubao/new.js +1 -1
- package/dist/clis/doubao/read.js +1 -1
- package/dist/clis/doubao/send.js +1 -1
- package/dist/clis/doubao/status.js +1 -1
- package/dist/clis/doubao-app/ask.js +1 -1
- package/dist/clis/doubao-app/new.js +1 -1
- package/dist/clis/doubao-app/read.js +1 -1
- package/dist/clis/doubao-app/send.js +1 -1
- package/dist/clis/douyin/_shared/browser-fetch.d.ts +10 -0
- package/dist/clis/douyin/_shared/browser-fetch.js +30 -0
- package/dist/clis/douyin/_shared/browser-fetch.test.d.ts +1 -0
- package/dist/clis/douyin/_shared/browser-fetch.test.js +31 -0
- package/dist/clis/douyin/_shared/creation-id.d.ts +1 -0
- package/dist/clis/douyin/_shared/creation-id.js +5 -0
- package/dist/clis/douyin/_shared/creation-id.test.d.ts +1 -0
- package/dist/clis/douyin/_shared/creation-id.test.js +22 -0
- package/dist/clis/douyin/_shared/imagex-upload.d.ts +20 -0
- package/dist/clis/douyin/_shared/imagex-upload.js +53 -0
- package/dist/clis/douyin/_shared/imagex-upload.test.d.ts +1 -0
- package/dist/clis/douyin/_shared/imagex-upload.test.js +87 -0
- package/dist/clis/douyin/_shared/sts2.d.ts +8 -0
- package/dist/clis/douyin/_shared/sts2.js +15 -0
- package/dist/clis/douyin/_shared/text-extra.d.ts +18 -0
- package/dist/clis/douyin/_shared/text-extra.js +15 -0
- package/dist/clis/douyin/_shared/text-extra.test.d.ts +1 -0
- package/dist/clis/douyin/_shared/text-extra.test.js +37 -0
- package/dist/clis/douyin/_shared/timing.d.ts +2 -0
- package/dist/clis/douyin/_shared/timing.js +22 -0
- package/dist/clis/douyin/_shared/timing.test.d.ts +1 -0
- package/dist/clis/douyin/_shared/timing.test.js +28 -0
- package/dist/clis/douyin/_shared/tos-upload-short-read.test.d.ts +11 -0
- package/dist/clis/douyin/_shared/tos-upload-short-read.test.js +83 -0
- package/dist/clis/douyin/_shared/tos-upload.d.ts +53 -0
- package/dist/clis/douyin/_shared/tos-upload.js +295 -0
- package/dist/clis/douyin/_shared/tos-upload.test.d.ts +1 -0
- package/dist/clis/douyin/_shared/tos-upload.test.js +229 -0
- package/dist/clis/douyin/_shared/transcode.d.ts +27 -0
- package/dist/clis/douyin/_shared/transcode.js +45 -0
- package/dist/clis/douyin/_shared/transcode.test.d.ts +1 -0
- package/dist/clis/douyin/_shared/transcode.test.js +93 -0
- package/dist/clis/douyin/_shared/types.d.ts +26 -0
- package/dist/clis/douyin/_shared/types.js +1 -0
- package/dist/clis/douyin/activities.d.ts +1 -0
- package/dist/clis/douyin/activities.js +20 -0
- package/dist/clis/douyin/activities.test.d.ts +1 -0
- package/dist/clis/douyin/activities.test.js +22 -0
- package/dist/clis/douyin/collections.d.ts +1 -0
- package/dist/clis/douyin/collections.js +22 -0
- package/dist/clis/douyin/collections.test.d.ts +1 -0
- package/dist/clis/douyin/collections.test.js +23 -0
- package/dist/clis/douyin/delete.d.ts +1 -0
- package/dist/clis/douyin/delete.js +18 -0
- package/dist/clis/douyin/delete.test.d.ts +1 -0
- package/dist/clis/douyin/delete.test.js +11 -0
- package/dist/clis/douyin/draft.d.ts +14 -0
- package/dist/clis/douyin/draft.js +237 -0
- package/dist/clis/douyin/draft.test.d.ts +1 -0
- package/dist/clis/douyin/draft.test.js +11 -0
- package/dist/clis/douyin/drafts.d.ts +1 -0
- package/dist/clis/douyin/drafts.js +23 -0
- package/dist/clis/douyin/drafts.test.d.ts +1 -0
- package/dist/clis/douyin/drafts.test.js +11 -0
- package/dist/clis/douyin/hashtag.d.ts +1 -0
- package/dist/clis/douyin/hashtag.js +45 -0
- package/dist/clis/douyin/hashtag.test.d.ts +1 -0
- package/dist/clis/douyin/hashtag.test.js +25 -0
- package/dist/clis/douyin/location.d.ts +1 -0
- package/dist/clis/douyin/location.js +24 -0
- package/dist/clis/douyin/location.test.d.ts +1 -0
- package/dist/clis/douyin/location.test.js +23 -0
- package/dist/clis/douyin/profile.d.ts +1 -0
- package/dist/clis/douyin/profile.js +28 -0
- package/dist/clis/douyin/profile.test.d.ts +1 -0
- package/dist/clis/douyin/profile.test.js +11 -0
- package/dist/clis/douyin/publish.d.ts +14 -0
- package/dist/clis/douyin/publish.js +288 -0
- package/dist/clis/douyin/publish.test.d.ts +1 -0
- package/dist/clis/douyin/publish.test.js +38 -0
- package/dist/clis/douyin/stats.d.ts +1 -0
- package/dist/clis/douyin/stats.js +27 -0
- package/dist/clis/douyin/stats.test.d.ts +1 -0
- package/dist/clis/douyin/stats.test.js +22 -0
- package/dist/clis/douyin/update.d.ts +1 -0
- package/dist/clis/douyin/update.js +31 -0
- package/dist/clis/douyin/update.test.d.ts +1 -0
- package/dist/clis/douyin/update.test.js +11 -0
- package/dist/clis/douyin/videos.d.ts +1 -0
- package/dist/clis/douyin/videos.js +34 -0
- package/dist/clis/douyin/videos.test.d.ts +1 -0
- package/dist/clis/douyin/videos.test.js +11 -0
- package/dist/clis/grok/ask.d.ts +4 -0
- package/dist/clis/grok/ask.js +28 -10
- package/dist/clis/grok/ask.test.js +18 -0
- package/dist/clis/hackernews/search.yaml +1 -1
- package/dist/clis/instagram/search.yaml +2 -1
- package/dist/clis/jd/item.d.ts +1 -0
- package/dist/clis/jd/item.js +96 -0
- package/dist/clis/jd/item.test.d.ts +1 -0
- package/dist/clis/jd/item.test.js +28 -0
- package/dist/clis/jike/feed.js +1 -1
- package/dist/clis/jike/search.js +1 -1
- package/dist/clis/linkedin/search.js +5 -4
- package/dist/clis/linkedin/timeline.d.ts +21 -0
- package/dist/clis/linkedin/timeline.js +503 -0
- package/dist/clis/linkedin/timeline.test.d.ts +1 -0
- package/dist/clis/linkedin/timeline.test.js +81 -0
- package/dist/clis/linux-do/search.yaml +3 -1
- package/dist/clis/medium/feed.js +1 -1
- package/dist/clis/medium/search.js +2 -2
- package/dist/clis/medium/user.js +1 -1
- package/dist/clis/medium/{shared.js → utils.js} +2 -1
- package/dist/clis/pixiv/detail.yaml +49 -0
- package/dist/clis/pixiv/download.d.ts +7 -0
- package/dist/clis/pixiv/download.js +78 -0
- package/dist/clis/pixiv/download.test.d.ts +1 -0
- package/dist/clis/pixiv/download.test.js +87 -0
- package/dist/clis/pixiv/illusts.d.ts +8 -0
- package/dist/clis/pixiv/illusts.js +65 -0
- package/dist/clis/pixiv/illusts.test.d.ts +1 -0
- package/dist/clis/pixiv/illusts.test.js +99 -0
- package/dist/clis/pixiv/ranking.yaml +53 -0
- package/dist/clis/pixiv/search.d.ts +6 -0
- package/dist/clis/pixiv/search.js +43 -0
- package/dist/clis/pixiv/search.test.d.ts +1 -0
- package/dist/clis/pixiv/search.test.js +83 -0
- package/dist/clis/pixiv/test-utils.d.ts +12 -0
- package/dist/clis/pixiv/test-utils.js +23 -0
- package/dist/clis/pixiv/user.yaml +46 -0
- package/dist/clis/pixiv/utils.d.ts +27 -0
- package/dist/clis/pixiv/utils.js +49 -0
- package/dist/clis/reddit/comment.js +2 -1
- package/dist/clis/reddit/read.js +4 -3
- package/dist/clis/reddit/read.test.d.ts +1 -0
- package/dist/clis/reddit/read.test.js +28 -0
- package/dist/clis/reddit/save.js +2 -1
- package/dist/clis/reddit/saved.js +7 -3
- package/dist/clis/reddit/subscribe.js +2 -1
- package/dist/clis/reddit/upvote.js +2 -1
- package/dist/clis/reddit/upvoted.js +7 -3
- package/dist/clis/reuters/search.js +0 -1
- package/dist/clis/sinablog/article.js +1 -1
- package/dist/clis/sinablog/hot.js +1 -1
- package/dist/clis/sinablog/user.js +1 -1
- package/dist/clis/substack/feed.js +1 -1
- package/dist/clis/substack/publication.js +1 -1
- package/dist/clis/substack/search.js +3 -2
- package/dist/clis/substack/{shared.js → utils.js} +3 -2
- package/dist/clis/tiktok/search.yaml +2 -1
- package/dist/clis/twitter/accept.js +2 -1
- package/dist/clis/twitter/article.js +4 -1
- package/dist/clis/twitter/block.js +2 -1
- package/dist/clis/twitter/bookmark.js +2 -1
- package/dist/clis/twitter/bookmarks.js +3 -2
- package/dist/clis/twitter/delete.js +2 -1
- package/dist/clis/twitter/follow.js +2 -1
- package/dist/clis/twitter/followers.js +3 -2
- package/dist/clis/twitter/following.js +3 -2
- package/dist/clis/twitter/hide-reply.js +2 -1
- package/dist/clis/twitter/like.js +2 -1
- package/dist/clis/twitter/notifications.js +2 -1
- package/dist/clis/twitter/post.js +2 -1
- package/dist/clis/twitter/profile.js +5 -2
- package/dist/clis/twitter/reply-dm.js +2 -1
- package/dist/clis/twitter/reply.js +2 -1
- package/dist/clis/twitter/search.js +32 -13
- package/dist/clis/twitter/search.test.d.ts +1 -0
- package/dist/clis/twitter/search.test.js +156 -0
- package/dist/clis/twitter/thread.js +2 -2
- package/dist/clis/twitter/timeline.js +3 -2
- package/dist/clis/twitter/trending.js +3 -2
- package/dist/clis/twitter/unblock.js +2 -1
- package/dist/clis/twitter/unbookmark.js +2 -1
- package/dist/clis/twitter/unfollow.js +2 -1
- package/dist/clis/v2ex/daily.js +3 -2
- package/dist/clis/v2ex/me.js +3 -2
- package/dist/clis/v2ex/notifications.js +4 -4
- package/dist/clis/web/read.d.ts +16 -0
- package/dist/clis/web/read.js +202 -0
- package/dist/clis/weibo/comments.d.ts +1 -0
- package/dist/clis/weibo/comments.js +53 -0
- package/dist/clis/weibo/feed.d.ts +1 -0
- package/dist/clis/weibo/feed.js +56 -0
- package/dist/clis/weibo/hot.js +0 -1
- package/dist/clis/weibo/me.d.ts +1 -0
- package/dist/clis/weibo/me.js +76 -0
- package/dist/clis/weibo/post.d.ts +1 -0
- package/dist/clis/weibo/post.js +75 -0
- package/dist/clis/weibo/user.d.ts +1 -0
- package/dist/clis/weibo/user.js +63 -0
- package/dist/clis/weibo/utils.d.ts +6 -0
- package/dist/clis/weibo/utils.js +30 -0
- package/dist/clis/weread/search.js +3 -2
- package/dist/clis/xueqiu/danjuan-utils.d.ts +55 -0
- package/dist/clis/xueqiu/danjuan-utils.js +126 -0
- package/dist/clis/xueqiu/danjuan-utils.test.d.ts +1 -0
- package/dist/clis/xueqiu/danjuan-utils.test.js +41 -0
- package/dist/clis/xueqiu/fund-holdings.d.ts +1 -0
- package/dist/clis/xueqiu/fund-holdings.js +28 -0
- package/dist/clis/xueqiu/fund-snapshot.d.ts +1 -0
- package/dist/clis/xueqiu/fund-snapshot.js +25 -0
- package/dist/clis/xueqiu/search.yaml +2 -1
- package/dist/clis/yahoo-finance/quote.js +0 -1
- package/dist/clis/youtube/channel.d.ts +1 -0
- package/dist/clis/youtube/channel.js +150 -0
- package/dist/clis/youtube/comments.d.ts +1 -0
- package/dist/clis/youtube/comments.js +95 -0
- package/dist/clis/youtube/search.js +0 -1
- package/dist/clis/youtube/transcript.js +5 -4
- package/dist/clis/youtube/video.js +3 -2
- package/dist/clis/zhihu/search.yaml +2 -1
- package/dist/daemon.js +7 -3
- package/dist/discovery.js +11 -10
- package/dist/doctor.js +2 -1
- package/dist/download/index.d.ts +4 -12
- package/dist/download/index.js +33 -12
- package/dist/download/index.test.js +79 -2
- package/dist/download/media-download.js +4 -2
- package/dist/engine.test.js +76 -4
- package/dist/execution.d.ts +1 -9
- package/dist/execution.js +56 -46
- package/dist/explore.js +12 -111
- package/dist/external-clis.yaml +0 -25
- package/dist/external.js +7 -5
- package/dist/external.test.js +4 -0
- package/dist/generate.d.ts +0 -9
- package/dist/generate.js +4 -20
- package/dist/hooks.d.ts +46 -0
- package/dist/hooks.js +56 -0
- package/dist/hooks.test.d.ts +4 -0
- package/dist/hooks.test.js +92 -0
- package/dist/interceptor.js +70 -23
- package/dist/main.js +2 -0
- package/dist/output.js +12 -6
- package/dist/pipeline/executor.js +1 -1
- package/dist/pipeline/steps/browser.js +1 -3
- package/dist/pipeline/steps/download.js +42 -26
- package/dist/pipeline/steps/download.test.d.ts +1 -0
- package/dist/pipeline/steps/download.test.js +101 -0
- package/dist/pipeline/steps/fetch.js +40 -22
- package/dist/pipeline/steps/fetch.test.d.ts +1 -0
- package/dist/pipeline/steps/fetch.test.js +123 -0
- package/dist/pipeline/steps/transform.js +2 -6
- package/dist/pipeline/template.js +66 -52
- package/dist/pipeline/template.test.js +28 -0
- package/dist/pipeline/transform.test.js +18 -0
- package/dist/plugin.d.ts +40 -1
- package/dist/plugin.js +214 -17
- package/dist/plugin.test.d.ts +1 -1
- package/dist/plugin.test.js +219 -3
- package/dist/record.js +6 -98
- package/dist/registry-api.d.ts +2 -0
- package/dist/registry-api.js +1 -0
- package/dist/registry.d.ts +5 -2
- package/dist/registry.js +1 -2
- package/dist/runtime.d.ts +0 -1
- package/dist/runtime.js +14 -4
- package/dist/snapshotFormatter.d.ts +7 -14
- package/dist/snapshotFormatter.js +38 -78
- package/dist/utils.d.ts +9 -0
- package/dist/utils.js +29 -0
- package/dist/validate.js +3 -5
- package/dist/weread-search-regression.test.d.ts +1 -0
- package/dist/weread-search-regression.test.js +39 -0
- package/dist/yaml-schema.d.ts +26 -0
- package/dist/yaml-schema.js +5 -0
- package/docs/.vitepress/config.mts +16 -0
- package/docs/adapters/browser/dictionary.md +27 -0
- package/docs/adapters/browser/douyin.md +75 -0
- package/docs/adapters/browser/jd.md +27 -0
- package/docs/adapters/browser/linkedin.md +6 -0
- package/docs/adapters/browser/pixiv.md +92 -0
- package/docs/adapters/browser/twitter.md +6 -0
- package/docs/adapters/browser/web.md +30 -0
- package/docs/adapters/browser/xueqiu.md +27 -9
- package/docs/adapters/index.md +9 -2
- package/docs/comparison.md +125 -0
- package/docs/developer/contributing.md +21 -2
- package/docs/developer/testing.md +14 -8
- package/docs/developer/ts-adapter.md +18 -0
- package/docs/developer/yaml-adapter.md +16 -0
- package/docs/guide/plugins.md +10 -0
- package/docs/zh/guide/plugins.md +10 -0
- package/extension/dist/background.js +100 -35
- package/extension/manifest.json +6 -2
- package/extension/package.json +1 -1
- package/extension/popup.html +84 -0
- package/extension/popup.js +25 -0
- package/extension/src/background.test.ts +46 -1
- package/extension/src/background.ts +128 -34
- package/extension/src/cdp.ts +9 -9
- package/package.json +3 -2
- package/scripts/check-doc-coverage.sh +2 -0
- package/src/analysis.ts +170 -0
- package/src/browser/cdp.test.ts +66 -0
- package/src/browser/cdp.ts +59 -44
- package/src/browser/dom-snapshot.test.ts +42 -0
- package/src/browser/dom-snapshot.ts +56 -3
- package/src/browser/index.ts +2 -2
- package/src/browser/mcp.ts +2 -4
- package/src/browser/page.ts +34 -37
- package/src/browser/stealth.ts +24 -10
- package/src/browser.test.ts +2 -2
- package/src/build-manifest.test.ts +14 -0
- package/src/build-manifest.ts +13 -31
- package/src/cascade.ts +5 -3
- package/src/cli.ts +66 -34
- package/src/clis/_shared/desktop-commands.ts +121 -0
- package/src/clis/antigravity/serve.ts +6 -3
- package/src/clis/apple-podcasts/search.ts +2 -1
- package/src/clis/arxiv/search.ts +3 -3
- package/src/clis/bbc/news.ts +0 -1
- package/src/clis/bilibili/dynamic.test.ts +79 -0
- package/src/clis/bilibili/favorite.ts +5 -2
- package/src/clis/bilibili/following.ts +3 -2
- package/src/clis/bilibili/subtitle.ts +8 -7
- package/src/clis/bilibili/utils.ts +2 -2
- package/src/clis/boss/batchgreet.ts +1 -1
- package/src/clis/boss/chatlist.ts +1 -1
- package/src/clis/boss/chatmsg.ts +1 -1
- package/src/clis/boss/detail.ts +1 -1
- package/src/clis/boss/exchange.ts +1 -1
- package/src/clis/boss/greet.ts +1 -1
- package/src/clis/boss/invite.ts +1 -1
- package/src/clis/boss/joblist.ts +1 -1
- package/src/clis/boss/mark.ts +4 -3
- package/src/clis/boss/recommend.ts +1 -1
- package/src/clis/boss/resume.ts +1 -1
- package/src/clis/boss/search.ts +1 -1
- package/src/clis/boss/send.ts +5 -4
- package/src/clis/boss/stats.ts +1 -1
- package/src/clis/chatgpt/ask.ts +5 -0
- package/src/clis/chatgpt/new.ts +7 -2
- package/src/clis/chatgpt/read.ts +7 -2
- package/src/clis/chatgpt/send.ts +3 -2
- package/src/clis/chatgpt/status.ts +6 -1
- package/src/clis/chatwise/ask.ts +7 -2
- package/src/clis/chatwise/export.ts +2 -0
- package/src/clis/chatwise/history.ts +2 -0
- package/src/clis/chatwise/model.ts +7 -3
- package/src/clis/chatwise/new.ts +3 -20
- package/src/clis/chatwise/read.ts +2 -0
- package/src/clis/chatwise/screenshot.ts +3 -32
- package/src/clis/chatwise/send.ts +7 -2
- package/src/clis/chatwise/shared.ts +8 -0
- package/src/clis/chatwise/status.ts +3 -24
- package/src/clis/codex/ask.ts +5 -2
- package/src/clis/codex/dump.ts +2 -27
- package/src/clis/codex/new.ts +2 -28
- package/src/clis/codex/screenshot.ts +2 -32
- package/src/clis/codex/send.ts +5 -4
- package/src/clis/codex/status.ts +2 -24
- package/src/clis/ctrip/search.ts +0 -1
- package/src/clis/cursor/ask.ts +2 -1
- package/src/clis/cursor/composer.ts +2 -1
- package/src/clis/cursor/dump.ts +2 -27
- package/src/clis/cursor/new.ts +2 -20
- package/src/clis/cursor/read.ts +2 -1
- package/src/clis/cursor/screenshot.ts +1 -36
- package/src/clis/cursor/send.ts +2 -1
- package/src/clis/cursor/status.ts +2 -22
- package/src/clis/dictionary/examples.yaml +25 -0
- package/src/clis/dictionary/search.yaml +27 -0
- package/src/clis/dictionary/synonyms.yaml +25 -0
- package/src/clis/douban/book-hot.ts +1 -1
- package/src/clis/douban/movie-hot.ts +1 -1
- package/src/clis/douban/search.ts +1 -1
- package/src/clis/douban/utils.ts +165 -1
- package/src/clis/doubao/ask.ts +1 -1
- package/src/clis/doubao/new.ts +1 -1
- package/src/clis/doubao/read.ts +1 -1
- package/src/clis/doubao/send.ts +1 -1
- package/src/clis/doubao/status.ts +1 -1
- package/src/clis/doubao-app/ask.ts +1 -1
- package/src/clis/doubao-app/new.ts +1 -1
- package/src/clis/doubao-app/read.ts +1 -1
- package/src/clis/doubao-app/send.ts +1 -1
- package/src/clis/douyin/_shared/browser-fetch.test.ts +38 -0
- package/src/clis/douyin/_shared/browser-fetch.ts +45 -0
- package/src/clis/douyin/_shared/creation-id.test.ts +26 -0
- package/src/clis/douyin/_shared/creation-id.ts +8 -0
- package/src/clis/douyin/_shared/imagex-upload.test.ts +113 -0
- package/src/clis/douyin/_shared/imagex-upload.ts +76 -0
- package/src/clis/douyin/_shared/sts2.ts +20 -0
- package/src/clis/douyin/_shared/text-extra.test.ts +42 -0
- package/src/clis/douyin/_shared/text-extra.ts +33 -0
- package/src/clis/douyin/_shared/timing.test.ts +38 -0
- package/src/clis/douyin/_shared/timing.ts +22 -0
- package/src/clis/douyin/_shared/tos-upload-short-read.test.ts +102 -0
- package/src/clis/douyin/_shared/tos-upload.test.ts +281 -0
- package/src/clis/douyin/_shared/tos-upload.ts +444 -0
- package/src/clis/douyin/_shared/transcode.test.ts +117 -0
- package/src/clis/douyin/_shared/transcode.ts +78 -0
- package/src/clis/douyin/_shared/types.ts +29 -0
- package/src/clis/douyin/activities.test.ts +25 -0
- package/src/clis/douyin/activities.ts +23 -0
- package/src/clis/douyin/collections.test.ts +26 -0
- package/src/clis/douyin/collections.ts +25 -0
- package/src/clis/douyin/delete.test.ts +12 -0
- package/src/clis/douyin/delete.ts +20 -0
- package/src/clis/douyin/draft.test.ts +12 -0
- package/src/clis/douyin/draft.ts +282 -0
- package/src/clis/douyin/drafts.test.ts +12 -0
- package/src/clis/douyin/drafts.ts +27 -0
- package/src/clis/douyin/hashtag.test.ts +28 -0
- package/src/clis/douyin/hashtag.ts +56 -0
- package/src/clis/douyin/location.test.ts +26 -0
- package/src/clis/douyin/location.ts +27 -0
- package/src/clis/douyin/profile.test.ts +12 -0
- package/src/clis/douyin/profile.ts +37 -0
- package/src/clis/douyin/publish.test.ts +45 -0
- package/src/clis/douyin/publish.ts +340 -0
- package/src/clis/douyin/stats.test.ts +25 -0
- package/src/clis/douyin/stats.ts +30 -0
- package/src/clis/douyin/update.test.ts +12 -0
- package/src/clis/douyin/update.ts +43 -0
- package/src/clis/douyin/videos.test.ts +12 -0
- package/src/clis/douyin/videos.ts +49 -0
- package/src/clis/grok/ask.test.ts +25 -0
- package/src/clis/grok/ask.ts +25 -12
- package/src/clis/hackernews/search.yaml +1 -1
- package/src/clis/instagram/search.yaml +2 -1
- package/src/clis/jd/item.test.ts +35 -0
- package/src/clis/jd/item.ts +101 -0
- package/src/clis/jike/feed.ts +1 -1
- package/src/clis/jike/search.ts +1 -1
- package/src/clis/linkedin/search.ts +5 -4
- package/src/clis/linkedin/timeline.test.ts +99 -0
- package/src/clis/linkedin/timeline.ts +532 -0
- package/src/clis/linux-do/search.yaml +3 -1
- package/src/clis/medium/feed.ts +1 -1
- package/src/clis/medium/search.ts +2 -2
- package/src/clis/medium/user.ts +1 -1
- package/src/clis/medium/{shared.ts → utils.ts} +2 -1
- package/src/clis/pixiv/detail.yaml +49 -0
- package/src/clis/pixiv/download.test.ts +114 -0
- package/src/clis/pixiv/download.ts +91 -0
- package/src/clis/pixiv/illusts.test.ts +115 -0
- package/src/clis/pixiv/illusts.ts +78 -0
- package/src/clis/pixiv/ranking.yaml +53 -0
- package/src/clis/pixiv/search.test.ts +97 -0
- package/src/clis/pixiv/search.ts +53 -0
- package/src/clis/pixiv/test-utils.ts +29 -0
- package/src/clis/pixiv/user.yaml +46 -0
- package/src/clis/pixiv/utils.ts +62 -0
- package/src/clis/reddit/comment.ts +2 -1
- package/src/clis/reddit/read.test.ts +34 -0
- package/src/clis/reddit/read.ts +4 -3
- package/src/clis/reddit/save.ts +2 -1
- package/src/clis/reddit/saved.ts +6 -2
- package/src/clis/reddit/subscribe.ts +2 -1
- package/src/clis/reddit/upvote.ts +2 -1
- package/src/clis/reddit/upvoted.ts +6 -2
- package/src/clis/reuters/search.ts +0 -1
- package/src/clis/sinablog/article.ts +1 -1
- package/src/clis/sinablog/hot.ts +1 -1
- package/src/clis/sinablog/user.ts +1 -1
- package/src/clis/substack/feed.ts +1 -1
- package/src/clis/substack/publication.ts +1 -1
- package/src/clis/substack/search.ts +3 -2
- package/src/clis/substack/{shared.ts → utils.ts} +3 -2
- package/src/clis/tiktok/search.yaml +2 -1
- package/src/clis/twitter/accept.ts +2 -1
- package/src/clis/twitter/article.ts +3 -1
- package/src/clis/twitter/block.ts +2 -1
- package/src/clis/twitter/bookmark.ts +2 -1
- package/src/clis/twitter/bookmarks.ts +3 -2
- package/src/clis/twitter/delete.ts +2 -1
- package/src/clis/twitter/follow.ts +2 -1
- package/src/clis/twitter/followers.ts +3 -2
- package/src/clis/twitter/following.ts +3 -2
- package/src/clis/twitter/hide-reply.ts +2 -1
- package/src/clis/twitter/like.ts +2 -1
- package/src/clis/twitter/notifications.ts +2 -1
- package/src/clis/twitter/post.ts +2 -1
- package/src/clis/twitter/profile.ts +4 -2
- package/src/clis/twitter/reply-dm.ts +2 -1
- package/src/clis/twitter/reply.ts +2 -1
- package/src/clis/twitter/search.test.ts +180 -0
- package/src/clis/twitter/search.ts +40 -14
- package/src/clis/twitter/thread.ts +2 -2
- package/src/clis/twitter/timeline.ts +3 -2
- package/src/clis/twitter/trending.ts +3 -2
- package/src/clis/twitter/unblock.ts +2 -1
- package/src/clis/twitter/unbookmark.ts +2 -1
- package/src/clis/twitter/unfollow.ts +2 -1
- package/src/clis/v2ex/daily.ts +3 -2
- package/src/clis/v2ex/me.ts +3 -2
- package/src/clis/v2ex/notifications.ts +3 -4
- package/src/clis/web/read.ts +210 -0
- package/src/clis/weibo/comments.ts +54 -0
- package/src/clis/weibo/feed.ts +57 -0
- package/src/clis/weibo/hot.ts +0 -1
- package/src/clis/weibo/me.ts +77 -0
- package/src/clis/weibo/post.ts +77 -0
- package/src/clis/weibo/user.ts +64 -0
- package/src/clis/weibo/utils.ts +32 -0
- package/src/clis/weread/search.ts +3 -2
- package/src/clis/xueqiu/danjuan-utils.test.ts +49 -0
- package/src/clis/xueqiu/danjuan-utils.ts +176 -0
- package/src/clis/xueqiu/fund-holdings.ts +32 -0
- package/src/clis/xueqiu/fund-snapshot.ts +27 -0
- package/src/clis/xueqiu/search.yaml +2 -1
- package/src/clis/yahoo-finance/quote.ts +0 -1
- package/src/clis/youtube/channel.ts +155 -0
- package/src/clis/youtube/comments.ts +97 -0
- package/src/clis/youtube/search.ts +0 -1
- package/src/clis/youtube/transcript.ts +5 -4
- package/src/clis/youtube/video.ts +3 -2
- package/src/clis/zhihu/search.yaml +2 -1
- package/src/daemon.ts +5 -4
- package/src/discovery.ts +12 -34
- package/src/doctor.ts +3 -2
- package/src/download/index.test.ts +93 -2
- package/src/download/index.ts +44 -23
- package/src/download/media-download.ts +5 -3
- package/src/engine.test.ts +84 -3
- package/src/execution.ts +62 -46
- package/src/explore.ts +21 -90
- package/src/external-clis.yaml +0 -25
- package/src/external.test.ts +9 -0
- package/src/external.ts +12 -10
- package/src/generate.ts +4 -41
- package/src/hooks.test.ts +126 -0
- package/src/hooks.ts +90 -0
- package/src/interceptor.ts +73 -23
- package/src/main.ts +2 -0
- package/src/output.ts +14 -6
- package/src/pipeline/executor.ts +1 -1
- package/src/pipeline/steps/browser.ts +1 -3
- package/src/pipeline/steps/download.test.ts +136 -0
- package/src/pipeline/steps/download.ts +47 -34
- package/src/pipeline/steps/fetch.test.ts +179 -0
- package/src/pipeline/steps/fetch.ts +39 -23
- package/src/pipeline/steps/transform.ts +2 -6
- package/src/pipeline/template.test.ts +28 -0
- package/src/pipeline/template.ts +67 -79
- package/src/pipeline/transform.test.ts +20 -0
- package/src/plugin.test.ts +251 -3
- package/src/plugin.ts +265 -21
- package/src/record.ts +12 -84
- package/src/registry-api.ts +2 -0
- package/src/registry.ts +7 -4
- package/src/runtime.ts +14 -4
- package/src/snapshotFormatter.ts +43 -121
- package/src/utils.ts +39 -0
- package/src/validate.ts +3 -5
- package/src/weread-search-regression.test.ts +44 -0
- package/src/yaml-schema.ts +28 -0
- package/tests/e2e/browser-auth.test.ts +25 -0
- package/tests/e2e/browser-public-extended.test.ts +162 -0
- package/tests/e2e/browser-public.test.ts +7 -146
- package/tests/e2e/plugin-management.test.ts +137 -0
- package/tests/e2e/public-commands.test.ts +34 -1
- package/vitest.config.ts +33 -8
- package/.github/workflows/pkg-pr-new.yml +0 -30
- package/dist/clis/douban/shared.d.ts +0 -4
- package/dist/clis/douban/shared.js +0 -155
- package/src/clis/douban/shared.ts +0 -165
- /package/dist/clis/boss/{common.d.ts → utils.d.ts} +0 -0
- /package/dist/clis/boss/{common.js → utils.js} +0 -0
- /package/dist/clis/doubao/{common.d.ts → utils.d.ts} +0 -0
- /package/dist/clis/doubao/{common.js → utils.js} +0 -0
- /package/dist/clis/doubao-app/{common.d.ts → utils.d.ts} +0 -0
- /package/dist/clis/doubao-app/{common.js → utils.js} +0 -0
- /package/dist/clis/jike/{shared.d.ts → utils.d.ts} +0 -0
- /package/dist/clis/jike/{shared.js → utils.js} +0 -0
- /package/dist/clis/medium/{shared.d.ts → utils.d.ts} +0 -0
- /package/dist/clis/sinablog/{shared.d.ts → utils.d.ts} +0 -0
- /package/dist/clis/sinablog/{shared.js → utils.js} +0 -0
- /package/dist/clis/substack/{shared.d.ts → utils.d.ts} +0 -0
- /package/src/clis/boss/{common.ts → utils.ts} +0 -0
- /package/src/clis/doubao/{common.ts → utils.ts} +0 -0
- /package/src/clis/doubao-app/{common.ts → utils.ts} +0 -0
- /package/src/clis/jike/{shared.ts → utils.ts} +0 -0
- /package/src/clis/sinablog/{shared.ts → utils.ts} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* E2E tests for browser commands
|
|
2
|
+
* E2E tests for core browser commands (bilibili, zhihu, v2ex).
|
|
3
3
|
* These use OPENCLI_HEADLESS=1 to launch a headless Chromium.
|
|
4
4
|
*
|
|
5
5
|
* NOTE: Some sites may block headless browsers with bot detection.
|
|
@@ -9,9 +9,6 @@
|
|
|
9
9
|
import { describe, it, expect } from 'vitest';
|
|
10
10
|
import { runCli, parseJsonOutput } from './helpers.js';
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
* Run a browser command — returns parsed data or null on failure.
|
|
14
|
-
*/
|
|
15
12
|
async function tryBrowserCommand(args: string[]): Promise<any[] | null> {
|
|
16
13
|
const { stdout, code } = await runCli(args, { timeout: 60_000 });
|
|
17
14
|
if (code !== 0) return null;
|
|
@@ -23,10 +20,6 @@ async function tryBrowserCommand(args: string[]): Promise<any[] | null> {
|
|
|
23
20
|
}
|
|
24
21
|
}
|
|
25
22
|
|
|
26
|
-
/**
|
|
27
|
-
* Assert browser command returns data OR log a warning if blocked.
|
|
28
|
-
* Empty results (bot detection, geo-blocking) are treated as a warning, not a failure.
|
|
29
|
-
*/
|
|
30
23
|
function expectDataOrSkip(data: any[] | null, label: string) {
|
|
31
24
|
if (data === null || data.length === 0) {
|
|
32
25
|
console.warn(`${label}: skipped — no data returned (likely bot detection or geo-blocking)`);
|
|
@@ -37,47 +30,7 @@ function expectDataOrSkip(data: any[] | null, label: string) {
|
|
|
37
30
|
|
|
38
31
|
describe('browser public-data commands E2E', () => {
|
|
39
32
|
|
|
40
|
-
// ──
|
|
41
|
-
it('bbc news returns headlines', async () => {
|
|
42
|
-
const data = await tryBrowserCommand(['bbc', 'news', '--limit', '3', '-f', 'json']);
|
|
43
|
-
expectDataOrSkip(data, 'bbc news');
|
|
44
|
-
if (data) {
|
|
45
|
-
expect(data[0]).toHaveProperty('title');
|
|
46
|
-
}
|
|
47
|
-
}, 60_000);
|
|
48
|
-
|
|
49
|
-
it('bloomberg news returns article detail when the article page is accessible', async () => {
|
|
50
|
-
const feedResult = await runCli(['bloomberg', 'tech', '--limit', '1', '-f', 'json']);
|
|
51
|
-
if (feedResult.code !== 0) {
|
|
52
|
-
console.warn('bloomberg news: skipped — could not load Bloomberg tech feed');
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const feedItems = parseJsonOutput(feedResult.stdout);
|
|
57
|
-
const link = Array.isArray(feedItems) ? feedItems[0]?.link : null;
|
|
58
|
-
if (!link) {
|
|
59
|
-
console.warn('bloomberg news: skipped — tech feed returned no link');
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const data = await tryBrowserCommand(['bloomberg', 'news', link, '-f', 'json']);
|
|
64
|
-
expectDataOrSkip(data, 'bloomberg news');
|
|
65
|
-
if (data) {
|
|
66
|
-
expect(data[0]).toHaveProperty('title');
|
|
67
|
-
expect(data[0]).toHaveProperty('summary');
|
|
68
|
-
expect(data[0]).toHaveProperty('link');
|
|
69
|
-
expect(data[0]).toHaveProperty('mediaLinks');
|
|
70
|
-
expect(data[0]).toHaveProperty('content');
|
|
71
|
-
}
|
|
72
|
-
}, 60_000);
|
|
73
|
-
|
|
74
|
-
// ── v2ex daily (browser: true) ──
|
|
75
|
-
it('v2ex daily returns topics', async () => {
|
|
76
|
-
const data = await tryBrowserCommand(['v2ex', 'daily', '--limit', '3', '-f', 'json']);
|
|
77
|
-
expectDataOrSkip(data, 'v2ex daily');
|
|
78
|
-
}, 60_000);
|
|
79
|
-
|
|
80
|
-
// ── bilibili (browser: true, cookie strategy) ──
|
|
33
|
+
// ── bilibili ──
|
|
81
34
|
it('bilibili hot returns trending videos', async () => {
|
|
82
35
|
const data = await tryBrowserCommand(['bilibili', 'hot', '--limit', '5', '-f', 'json']);
|
|
83
36
|
expectDataOrSkip(data, 'bilibili hot');
|
|
@@ -96,18 +49,7 @@ describe('browser public-data commands E2E', () => {
|
|
|
96
49
|
expectDataOrSkip(data, 'bilibili search');
|
|
97
50
|
}, 60_000);
|
|
98
51
|
|
|
99
|
-
// ──
|
|
100
|
-
it('weibo hot returns trending topics', async () => {
|
|
101
|
-
const data = await tryBrowserCommand(['weibo', 'hot', '--limit', '5', '-f', 'json']);
|
|
102
|
-
expectDataOrSkip(data, 'weibo hot');
|
|
103
|
-
}, 60_000);
|
|
104
|
-
|
|
105
|
-
it('weibo search returns results', async () => {
|
|
106
|
-
const data = await tryBrowserCommand(['weibo', 'search', 'openai', '--limit', '3', '-f', 'json']);
|
|
107
|
-
expectDataOrSkip(data, 'weibo search');
|
|
108
|
-
}, 60_000);
|
|
109
|
-
|
|
110
|
-
// ── zhihu (browser: true, cookie strategy) ──
|
|
52
|
+
// ── zhihu ──
|
|
111
53
|
it('zhihu hot returns trending questions', async () => {
|
|
112
54
|
const data = await tryBrowserCommand(['zhihu', 'hot', '--limit', '5', '-f', 'json']);
|
|
113
55
|
expectDataOrSkip(data, 'zhihu hot');
|
|
@@ -121,90 +63,9 @@ describe('browser public-data commands E2E', () => {
|
|
|
121
63
|
expectDataOrSkip(data, 'zhihu search');
|
|
122
64
|
}, 60_000);
|
|
123
65
|
|
|
124
|
-
// ──
|
|
125
|
-
it('
|
|
126
|
-
const data = await tryBrowserCommand(['
|
|
127
|
-
expectDataOrSkip(data, '
|
|
128
|
-
}, 60_000);
|
|
129
|
-
|
|
130
|
-
it('reddit frontpage returns posts', async () => {
|
|
131
|
-
const data = await tryBrowserCommand(['reddit', 'frontpage', '--limit', '5', '-f', 'json']);
|
|
132
|
-
expectDataOrSkip(data, 'reddit frontpage');
|
|
133
|
-
}, 60_000);
|
|
134
|
-
|
|
135
|
-
// ── twitter (browser: true) ──
|
|
136
|
-
it('twitter trending returns trends', async () => {
|
|
137
|
-
const data = await tryBrowserCommand(['twitter', 'trending', '--limit', '5', '-f', 'json']);
|
|
138
|
-
expectDataOrSkip(data, 'twitter trending');
|
|
139
|
-
}, 60_000);
|
|
140
|
-
|
|
141
|
-
// ── xueqiu (browser: true, cookie strategy) ──
|
|
142
|
-
it('xueqiu hot returns hot posts', async () => {
|
|
143
|
-
const data = await tryBrowserCommand(['xueqiu', 'hot', '--limit', '5', '-f', 'json']);
|
|
144
|
-
expectDataOrSkip(data, 'xueqiu hot');
|
|
145
|
-
}, 60_000);
|
|
146
|
-
|
|
147
|
-
it('xueqiu hot-stock returns stocks', async () => {
|
|
148
|
-
const data = await tryBrowserCommand(['xueqiu', 'hot-stock', '--limit', '5', '-f', 'json']);
|
|
149
|
-
expectDataOrSkip(data, 'xueqiu hot-stock');
|
|
150
|
-
}, 60_000);
|
|
151
|
-
|
|
152
|
-
// ── reuters (browser: true) ──
|
|
153
|
-
it('reuters search returns articles', async () => {
|
|
154
|
-
const data = await tryBrowserCommand(['reuters', 'search', 'technology', '--limit', '3', '-f', 'json']);
|
|
155
|
-
expectDataOrSkip(data, 'reuters search');
|
|
156
|
-
}, 60_000);
|
|
157
|
-
|
|
158
|
-
// ── youtube (browser: true) ──
|
|
159
|
-
it('youtube search returns videos', async () => {
|
|
160
|
-
const data = await tryBrowserCommand(['youtube', 'search', 'typescript tutorial', '--limit', '3', '-f', 'json']);
|
|
161
|
-
expectDataOrSkip(data, 'youtube search');
|
|
162
|
-
}, 60_000);
|
|
163
|
-
|
|
164
|
-
// ── smzdm (browser: true) ──
|
|
165
|
-
it('smzdm search returns deals', async () => {
|
|
166
|
-
const data = await tryBrowserCommand(['smzdm', 'search', '键盘', '--limit', '3', '-f', 'json']);
|
|
167
|
-
expectDataOrSkip(data, 'smzdm search');
|
|
168
|
-
}, 60_000);
|
|
169
|
-
|
|
170
|
-
// ── boss (browser: true) ──
|
|
171
|
-
it('boss search returns jobs', async () => {
|
|
172
|
-
const data = await tryBrowserCommand(['boss', 'search', 'golang', '--limit', '3', '-f', 'json']);
|
|
173
|
-
expectDataOrSkip(data, 'boss search');
|
|
174
|
-
}, 60_000);
|
|
175
|
-
|
|
176
|
-
// ── ctrip (browser: true) ──
|
|
177
|
-
it('ctrip search returns flights', async () => {
|
|
178
|
-
const data = await tryBrowserCommand(['ctrip', 'search', '-f', 'json']);
|
|
179
|
-
expectDataOrSkip(data, 'ctrip search');
|
|
180
|
-
}, 60_000);
|
|
181
|
-
|
|
182
|
-
// ── coupang (browser: true) ──
|
|
183
|
-
it('coupang search returns products', async () => {
|
|
184
|
-
const data = await tryBrowserCommand(['coupang', 'search', 'laptop', '--limit', '3', '-f', 'json']);
|
|
185
|
-
expectDataOrSkip(data, 'coupang search');
|
|
186
|
-
}, 60_000);
|
|
187
|
-
|
|
188
|
-
// ── xiaohongshu (browser: true) ──
|
|
189
|
-
it('xiaohongshu search returns notes', async () => {
|
|
190
|
-
const data = await tryBrowserCommand(['xiaohongshu', 'search', '美食', '--limit', '3', '-f', 'json']);
|
|
191
|
-
expectDataOrSkip(data, 'xiaohongshu search');
|
|
192
|
-
}, 60_000);
|
|
193
|
-
|
|
194
|
-
// ── google search (browser: true, public strategy) ──
|
|
195
|
-
it('google search returns results', async () => {
|
|
196
|
-
const data = await tryBrowserCommand(['google', 'search', 'typescript', '--limit', '5', '-f', 'json']);
|
|
197
|
-
expectDataOrSkip(data, 'google search');
|
|
198
|
-
if (data) {
|
|
199
|
-
expect(data[0]).toHaveProperty('type');
|
|
200
|
-
expect(data[0]).toHaveProperty('title');
|
|
201
|
-
expect(data[0]).toHaveProperty('url');
|
|
202
|
-
}
|
|
203
|
-
}, 60_000);
|
|
204
|
-
|
|
205
|
-
// ── yahoo-finance (browser: true) ──
|
|
206
|
-
it('yahoo-finance quote returns stock data', async () => {
|
|
207
|
-
const data = await tryBrowserCommand(['yahoo-finance', 'quote', '--symbol', 'AAPL', '-f', 'json']);
|
|
208
|
-
expectDataOrSkip(data, 'yahoo-finance quote');
|
|
66
|
+
// ── v2ex ──
|
|
67
|
+
it('v2ex daily returns topics', async () => {
|
|
68
|
+
const data = await tryBrowserCommand(['v2ex', 'daily', '--limit', '3', '-f', 'json']);
|
|
69
|
+
expectDataOrSkip(data, 'v2ex daily');
|
|
209
70
|
}, 60_000);
|
|
210
71
|
});
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E integration tests for plugin management commands.
|
|
3
|
+
* Uses a real GitHub plugin (opencli-plugin-hot-digest) to verify the full
|
|
4
|
+
* install → list → update → uninstall lifecycle in an isolated HOME.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, afterAll } from 'vitest';
|
|
8
|
+
import * as fs from 'node:fs';
|
|
9
|
+
import * as path from 'node:path';
|
|
10
|
+
import * as os from 'node:os';
|
|
11
|
+
import { runCli, parseJsonOutput } from './helpers.js';
|
|
12
|
+
|
|
13
|
+
const TEST_HOME = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-plugin-e2e-'));
|
|
14
|
+
const OPENCLI_HOME = path.join(TEST_HOME, '.opencli');
|
|
15
|
+
const PLUGINS_DIR = path.join(OPENCLI_HOME, 'plugins');
|
|
16
|
+
const PLUGIN_SOURCE = 'github:ByteYue/opencli-plugin-hot-digest';
|
|
17
|
+
const PLUGIN_NAME = 'hot-digest';
|
|
18
|
+
const PLUGIN_DIR = path.join(PLUGINS_DIR, PLUGIN_NAME);
|
|
19
|
+
const LOCK_FILE = path.join(OPENCLI_HOME, 'plugins.lock.json');
|
|
20
|
+
|
|
21
|
+
function runPluginCli(
|
|
22
|
+
args: string[],
|
|
23
|
+
opts: { timeout?: number; env?: Record<string, string> } = {},
|
|
24
|
+
) {
|
|
25
|
+
return runCli(args, {
|
|
26
|
+
...opts,
|
|
27
|
+
env: {
|
|
28
|
+
HOME: TEST_HOME,
|
|
29
|
+
USERPROFILE: TEST_HOME,
|
|
30
|
+
...opts.env,
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe('plugin management E2E', () => {
|
|
36
|
+
afterAll(() => {
|
|
37
|
+
fs.rmSync(TEST_HOME, { recursive: true, force: true });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// ── plugin list (empty) ──
|
|
41
|
+
it('plugin list shows "No plugins installed" when none exist', async () => {
|
|
42
|
+
const { stdout, code } = await runPluginCli(['plugin', 'list']);
|
|
43
|
+
expect(code).toBe(0);
|
|
44
|
+
expect(stdout).toContain('No plugins installed');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// ── plugin install ──
|
|
48
|
+
it('plugin install clones and sets up a real plugin', async () => {
|
|
49
|
+
const { stdout, code } = await runPluginCli(['plugin', 'install', PLUGIN_SOURCE], {
|
|
50
|
+
timeout: 60_000,
|
|
51
|
+
});
|
|
52
|
+
expect(code).toBe(0);
|
|
53
|
+
expect(stdout).toContain('installed successfully');
|
|
54
|
+
expect(stdout).toContain(PLUGIN_NAME);
|
|
55
|
+
|
|
56
|
+
// Verify the plugin directory was created
|
|
57
|
+
expect(fs.existsSync(PLUGIN_DIR)).toBe(true);
|
|
58
|
+
|
|
59
|
+
// Verify lock file was updated
|
|
60
|
+
const lock = JSON.parse(fs.readFileSync(LOCK_FILE, 'utf-8'));
|
|
61
|
+
expect(lock[PLUGIN_NAME]).toBeDefined();
|
|
62
|
+
expect(lock[PLUGIN_NAME].commitHash).toBeTruthy();
|
|
63
|
+
expect(lock[PLUGIN_NAME].source).toContain('opencli-plugin-hot-digest');
|
|
64
|
+
expect(lock[PLUGIN_NAME].installedAt).toBeTruthy();
|
|
65
|
+
}, 60_000);
|
|
66
|
+
|
|
67
|
+
// ── plugin list (after install) ──
|
|
68
|
+
it('plugin list shows the installed plugin', async () => {
|
|
69
|
+
const { stdout, code } = await runPluginCli(['plugin', 'list']);
|
|
70
|
+
expect(code).toBe(0);
|
|
71
|
+
expect(stdout).toContain(PLUGIN_NAME);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('plugin list -f json returns structured data', async () => {
|
|
75
|
+
const { stdout, code } = await runPluginCli(['plugin', 'list', '-f', 'json']);
|
|
76
|
+
expect(code).toBe(0);
|
|
77
|
+
const data = parseJsonOutput(stdout);
|
|
78
|
+
expect(Array.isArray(data)).toBe(true);
|
|
79
|
+
|
|
80
|
+
const plugin = data.find((p: any) => p.name === PLUGIN_NAME);
|
|
81
|
+
expect(plugin).toBeDefined();
|
|
82
|
+
expect(plugin.name).toBe(PLUGIN_NAME);
|
|
83
|
+
expect(Array.isArray(plugin.commands)).toBe(true);
|
|
84
|
+
expect(plugin.commands.length).toBeGreaterThan(0);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// ── plugin update ──
|
|
88
|
+
it('plugin update succeeds on an installed plugin', async () => {
|
|
89
|
+
const { stdout, code } = await runPluginCli(['plugin', 'update', PLUGIN_NAME], {
|
|
90
|
+
timeout: 30_000,
|
|
91
|
+
});
|
|
92
|
+
expect(code).toBe(0);
|
|
93
|
+
expect(stdout).toContain('updated successfully');
|
|
94
|
+
|
|
95
|
+
// Verify lock file has updatedAt
|
|
96
|
+
const lock = JSON.parse(fs.readFileSync(LOCK_FILE, 'utf-8'));
|
|
97
|
+
expect(lock[PLUGIN_NAME].updatedAt).toBeTruthy();
|
|
98
|
+
}, 30_000);
|
|
99
|
+
|
|
100
|
+
// ── plugin uninstall ──
|
|
101
|
+
it('plugin uninstall removes the plugin', async () => {
|
|
102
|
+
const { stdout, code } = await runPluginCli(['plugin', 'uninstall', PLUGIN_NAME]);
|
|
103
|
+
expect(code).toBe(0);
|
|
104
|
+
expect(stdout).toContain('uninstalled');
|
|
105
|
+
|
|
106
|
+
// Verify directory was removed
|
|
107
|
+
expect(fs.existsSync(PLUGIN_DIR)).toBe(false);
|
|
108
|
+
|
|
109
|
+
// Verify lock entry was removed
|
|
110
|
+
const lock = JSON.parse(fs.readFileSync(LOCK_FILE, 'utf-8'));
|
|
111
|
+
expect(lock[PLUGIN_NAME]).toBeUndefined();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// ── error paths ──
|
|
115
|
+
it('plugin install rejects invalid source', async () => {
|
|
116
|
+
const { stderr, code } = await runPluginCli(['plugin', 'install', 'invalid-source-format']);
|
|
117
|
+
expect(code).toBe(1);
|
|
118
|
+
expect(stderr).toContain('Invalid plugin source');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('plugin uninstall rejects non-existent plugin', async () => {
|
|
122
|
+
const { stderr, code } = await runPluginCli(['plugin', 'uninstall', '__nonexistent_plugin_xyz__']);
|
|
123
|
+
expect(code).toBe(1);
|
|
124
|
+
expect(stderr).toContain('not installed');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('plugin update rejects non-existent plugin', async () => {
|
|
128
|
+
const { stderr, code } = await runPluginCli(['plugin', 'update', '__nonexistent_plugin_xyz__']);
|
|
129
|
+
expect(code).toBe(1);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('plugin update without name or --all shows error', async () => {
|
|
133
|
+
const { stderr, code } = await runPluginCli(['plugin', 'update']);
|
|
134
|
+
expect(code).toBe(1);
|
|
135
|
+
expect(stderr).toContain('specify a plugin name');
|
|
136
|
+
});
|
|
137
|
+
});
|
|
@@ -16,7 +16,8 @@ function isExpectedChineseSiteRestriction(code: number, stderr: string): boolean
|
|
|
16
16
|
|
|
17
17
|
function isExpectedApplePodcastsRestriction(code: number, stderr: string): boolean {
|
|
18
18
|
if (code === 0) return false;
|
|
19
|
-
return /Error \[FETCH_ERROR\]: (Charts API HTTP \d+|Unable to reach Apple Podcasts charts)/.test(stderr)
|
|
19
|
+
return /Error \[FETCH_ERROR\]: (Charts API HTTP \d+|Unable to reach Apple Podcasts charts)/.test(stderr)
|
|
20
|
+
|| stderr === ''; // timeout killed the process before any output
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
function isExpectedGoogleRestriction(code: number, stderr: string): boolean {
|
|
@@ -488,4 +489,36 @@ describe('public commands E2E', () => {
|
|
|
488
489
|
expect(data.length).toBeGreaterThan(0);
|
|
489
490
|
expect(data.every((d: any) => d.type === 'image')).toBe(true);
|
|
490
491
|
}, 30_000);
|
|
492
|
+
|
|
493
|
+
// ── dictionary (public API, browser: false) ──
|
|
494
|
+
it('dictionary search returns word definitions', async () => {
|
|
495
|
+
const { stdout, code } = await runCli(['dictionary', 'search', 'serendipity', '-f', 'json']);
|
|
496
|
+
expect(code).toBe(0);
|
|
497
|
+
const data = parseJsonOutput(stdout);
|
|
498
|
+
expect(Array.isArray(data)).toBe(true);
|
|
499
|
+
expect(data.length).toBeGreaterThanOrEqual(1);
|
|
500
|
+
expect(data[0]).toHaveProperty('word', 'serendipity');
|
|
501
|
+
expect(data[0]).toHaveProperty('phonetic');
|
|
502
|
+
expect(data[0]).toHaveProperty('definition');
|
|
503
|
+
}, 30_000);
|
|
504
|
+
|
|
505
|
+
it('dictionary synonyms returns synonyms', async () => {
|
|
506
|
+
const { stdout, code } = await runCli(['dictionary', 'synonyms', 'serendipity', '-f', 'json']);
|
|
507
|
+
expect(code).toBe(0);
|
|
508
|
+
const data = parseJsonOutput(stdout);
|
|
509
|
+
expect(Array.isArray(data)).toBe(true);
|
|
510
|
+
expect(data.length).toBeGreaterThanOrEqual(1);
|
|
511
|
+
expect(data[0]).toHaveProperty('word', 'serendipity');
|
|
512
|
+
expect(data[0]).toHaveProperty('synonyms');
|
|
513
|
+
}, 30_000);
|
|
514
|
+
|
|
515
|
+
it('dictionary examples returns examples', async () => {
|
|
516
|
+
const { stdout, code } = await runCli(['dictionary', 'examples', 'perfect', '-f', 'json']);
|
|
517
|
+
expect(code).toBe(0);
|
|
518
|
+
const data = parseJsonOutput(stdout);
|
|
519
|
+
expect(Array.isArray(data)).toBe(true);
|
|
520
|
+
expect(data.length).toBeGreaterThanOrEqual(1);
|
|
521
|
+
expect(data[0]).toHaveProperty('word', 'perfect');
|
|
522
|
+
expect(data[0]).toHaveProperty('example');
|
|
523
|
+
}, 30_000);
|
|
491
524
|
});
|
package/vitest.config.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { defineConfig } from 'vitest/config';
|
|
2
2
|
|
|
3
|
+
const includeExtendedE2e = process.env.OPENCLI_E2E === '1';
|
|
4
|
+
|
|
3
5
|
export default defineConfig({
|
|
4
6
|
test: {
|
|
5
7
|
projects: [
|
|
@@ -7,20 +9,43 @@ export default defineConfig({
|
|
|
7
9
|
test: {
|
|
8
10
|
name: 'unit',
|
|
9
11
|
include: ['src/**/*.test.ts'],
|
|
10
|
-
|
|
11
|
-
sequence: {
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
exclude: ['src/clis/**/*.test.ts'],
|
|
13
|
+
sequence: { groupOrder: 0 },
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
test: {
|
|
18
|
+
name: 'adapter',
|
|
19
|
+
include: [
|
|
20
|
+
'src/clis/bilibili/**/*.test.ts',
|
|
21
|
+
'src/clis/zhihu/**/*.test.ts',
|
|
22
|
+
'src/clis/v2ex/**/*.test.ts',
|
|
23
|
+
],
|
|
24
|
+
sequence: { groupOrder: 1 },
|
|
14
25
|
},
|
|
15
26
|
},
|
|
16
27
|
{
|
|
17
28
|
test: {
|
|
18
29
|
name: 'e2e',
|
|
19
|
-
include: [
|
|
30
|
+
include: [
|
|
31
|
+
'tests/e2e/browser-public.test.ts',
|
|
32
|
+
'tests/e2e/public-commands.test.ts',
|
|
33
|
+
'tests/e2e/management.test.ts',
|
|
34
|
+
'tests/e2e/output-formats.test.ts',
|
|
35
|
+
'tests/e2e/plugin-management.test.ts',
|
|
36
|
+
// Extended browser tests (20+ sites) — opt-in only:
|
|
37
|
+
// OPENCLI_E2E=1 npx vitest run
|
|
38
|
+
...(includeExtendedE2e ? ['tests/e2e/browser-public-extended.test.ts', 'tests/e2e/browser-auth.test.ts'] : []),
|
|
39
|
+
],
|
|
20
40
|
maxWorkers: 2,
|
|
21
|
-
sequence: {
|
|
22
|
-
|
|
23
|
-
|
|
41
|
+
sequence: { groupOrder: 2 },
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
test: {
|
|
46
|
+
name: 'smoke',
|
|
47
|
+
include: ['tests/smoke/**/*.test.ts'],
|
|
48
|
+
sequence: { groupOrder: 3 },
|
|
24
49
|
},
|
|
25
50
|
},
|
|
26
51
|
],
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
name: Publish Any Commit
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [main, dev]
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: [main, dev]
|
|
8
|
-
|
|
9
|
-
permissions: {}
|
|
10
|
-
|
|
11
|
-
jobs:
|
|
12
|
-
publish:
|
|
13
|
-
if: ${{ vars.PKG_PR_NEW_ENABLED == 'true' }}
|
|
14
|
-
runs-on: ubuntu-latest
|
|
15
|
-
steps:
|
|
16
|
-
- uses: actions/checkout@v6
|
|
17
|
-
|
|
18
|
-
- uses: actions/setup-node@v6
|
|
19
|
-
with:
|
|
20
|
-
node-version: '22'
|
|
21
|
-
cache: 'npm'
|
|
22
|
-
|
|
23
|
-
- name: Install dependencies
|
|
24
|
-
run: npm ci
|
|
25
|
-
|
|
26
|
-
- name: Build
|
|
27
|
-
run: npm run build
|
|
28
|
-
|
|
29
|
-
- name: Publish to pkg.pr.new
|
|
30
|
-
run: npx pkg-pr-new publish
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { IPage } from '../../types.js';
|
|
2
|
-
export declare function loadDoubanBookHot(page: IPage, limit: number): Promise<any[]>;
|
|
3
|
-
export declare function loadDoubanMovieHot(page: IPage, limit: number): Promise<any[]>;
|
|
4
|
-
export declare function searchDouban(page: IPage, type: string, keyword: string, limit: number): Promise<any[]>;
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
import { CliError } from '../../errors.js';
|
|
2
|
-
function clampLimit(limit) {
|
|
3
|
-
return Math.max(1, Math.min(limit || 20, 50));
|
|
4
|
-
}
|
|
5
|
-
async function ensureDoubanReady(page) {
|
|
6
|
-
const state = await page.evaluate(`
|
|
7
|
-
(() => {
|
|
8
|
-
const title = (document.title || '').trim();
|
|
9
|
-
const href = (location.href || '').trim();
|
|
10
|
-
const blocked = href.includes('sec.douban.com') || /登录跳转/.test(title) || /异常请求/.test(document.body?.innerText || '');
|
|
11
|
-
return { blocked, title, href };
|
|
12
|
-
})()
|
|
13
|
-
`);
|
|
14
|
-
if (state?.blocked) {
|
|
15
|
-
throw new CliError('AUTH_REQUIRED', 'Douban requires a logged-in browser session before these commands can load data.', 'Please sign in to douban.com in the browser that opencli reuses, then rerun the command.');
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
export async function loadDoubanBookHot(page, limit) {
|
|
19
|
-
const safeLimit = clampLimit(limit);
|
|
20
|
-
await page.goto('https://book.douban.com/chart');
|
|
21
|
-
await page.wait(4);
|
|
22
|
-
await ensureDoubanReady(page);
|
|
23
|
-
const data = await page.evaluate(`
|
|
24
|
-
(() => {
|
|
25
|
-
const normalize = (value) => (value || '').replace(/\\s+/g, ' ').trim();
|
|
26
|
-
const books = [];
|
|
27
|
-
for (const el of Array.from(document.querySelectorAll('.media.clearfix'))) {
|
|
28
|
-
try {
|
|
29
|
-
const titleEl = el.querySelector('h2 a[href*="/subject/"]');
|
|
30
|
-
const title = normalize(titleEl?.textContent);
|
|
31
|
-
let url = titleEl?.getAttribute('href') || '';
|
|
32
|
-
if (!title || !url) continue;
|
|
33
|
-
if (!url.startsWith('http')) url = 'https://book.douban.com' + url;
|
|
34
|
-
|
|
35
|
-
const info = normalize(el.querySelector('.subject-abstract, .pl, .pub')?.textContent);
|
|
36
|
-
const infoParts = info.split('/').map((part) => part.trim()).filter(Boolean);
|
|
37
|
-
const ratingText = normalize(el.querySelector('.subject-rating .font-small, .rating_nums, .rating')?.textContent);
|
|
38
|
-
const quote = Array.from(el.querySelectorAll('.subject-tags .tag'))
|
|
39
|
-
.map((node) => normalize(node.textContent))
|
|
40
|
-
.filter(Boolean)
|
|
41
|
-
.join(' / ');
|
|
42
|
-
|
|
43
|
-
books.push({
|
|
44
|
-
rank: parseInt(normalize(el.querySelector('.green-num-box')?.textContent), 10) || books.length + 1,
|
|
45
|
-
title,
|
|
46
|
-
rating: parseFloat(ratingText) || 0,
|
|
47
|
-
quote,
|
|
48
|
-
author: infoParts[0] || '',
|
|
49
|
-
publisher: infoParts.find((part) => /出版社|出版公司|Press/i.test(part)) || infoParts[2] || '',
|
|
50
|
-
year: infoParts.find((part) => /\\d{4}(?:-\\d{1,2})?/.test(part))?.match(/\\d{4}/)?.[0] || '',
|
|
51
|
-
price: infoParts.find((part) => /元|USD|\\$|¥/.test(part)) || '',
|
|
52
|
-
url,
|
|
53
|
-
cover: el.querySelector('img')?.getAttribute('src') || '',
|
|
54
|
-
});
|
|
55
|
-
} catch {}
|
|
56
|
-
}
|
|
57
|
-
return books.slice(0, ${safeLimit});
|
|
58
|
-
})()
|
|
59
|
-
`);
|
|
60
|
-
return Array.isArray(data) ? data : [];
|
|
61
|
-
}
|
|
62
|
-
export async function loadDoubanMovieHot(page, limit) {
|
|
63
|
-
const safeLimit = clampLimit(limit);
|
|
64
|
-
await page.goto('https://movie.douban.com/chart');
|
|
65
|
-
await page.wait(4);
|
|
66
|
-
await ensureDoubanReady(page);
|
|
67
|
-
const data = await page.evaluate(`
|
|
68
|
-
(() => {
|
|
69
|
-
const normalize = (value) => (value || '').replace(/\\s+/g, ' ').trim();
|
|
70
|
-
const results = [];
|
|
71
|
-
for (const el of Array.from(document.querySelectorAll('.item'))) {
|
|
72
|
-
const titleEl = el.querySelector('.pl2 a');
|
|
73
|
-
const title = normalize(titleEl?.textContent);
|
|
74
|
-
let url = titleEl?.getAttribute('href') || '';
|
|
75
|
-
if (!title || !url) continue;
|
|
76
|
-
if (!url.startsWith('http')) url = 'https://movie.douban.com' + url;
|
|
77
|
-
|
|
78
|
-
const info = normalize(el.querySelector('.pl2 p')?.textContent);
|
|
79
|
-
const infoParts = info.split('/').map((part) => part.trim()).filter(Boolean);
|
|
80
|
-
const releaseIndex = (() => {
|
|
81
|
-
for (let i = infoParts.length - 1; i >= 0; i -= 1) {
|
|
82
|
-
if (/\\d{4}-\\d{2}-\\d{2}|\\d{4}\\/\\d{2}\\/\\d{2}/.test(infoParts[i])) return i;
|
|
83
|
-
}
|
|
84
|
-
return -1;
|
|
85
|
-
})();
|
|
86
|
-
const directorPart = releaseIndex >= 1 ? infoParts[releaseIndex - 1] : '';
|
|
87
|
-
const regionPart = releaseIndex >= 2 ? infoParts[releaseIndex - 2] : '';
|
|
88
|
-
const yearMatch = info.match(/\\b(19|20)\\d{2}\\b/);
|
|
89
|
-
results.push({
|
|
90
|
-
rank: results.length + 1,
|
|
91
|
-
title,
|
|
92
|
-
rating: parseFloat(normalize(el.querySelector('.rating_nums')?.textContent)) || 0,
|
|
93
|
-
quote: normalize(el.querySelector('.inq')?.textContent),
|
|
94
|
-
director: directorPart.replace(/^导演:\\s*/, ''),
|
|
95
|
-
year: yearMatch?.[0] || '',
|
|
96
|
-
region: regionPart,
|
|
97
|
-
url,
|
|
98
|
-
cover: el.querySelector('img')?.getAttribute('src') || '',
|
|
99
|
-
});
|
|
100
|
-
if (results.length >= ${safeLimit}) break;
|
|
101
|
-
}
|
|
102
|
-
return results;
|
|
103
|
-
})()
|
|
104
|
-
`);
|
|
105
|
-
return Array.isArray(data) ? data : [];
|
|
106
|
-
}
|
|
107
|
-
export async function searchDouban(page, type, keyword, limit) {
|
|
108
|
-
const safeLimit = clampLimit(limit);
|
|
109
|
-
await page.goto(`https://search.douban.com/${encodeURIComponent(type)}/subject_search?search_text=${encodeURIComponent(keyword)}`);
|
|
110
|
-
await page.wait(2);
|
|
111
|
-
await ensureDoubanReady(page);
|
|
112
|
-
const data = await page.evaluate(`
|
|
113
|
-
(async () => {
|
|
114
|
-
const type = ${JSON.stringify(type)};
|
|
115
|
-
const normalize = (value) => (value || '').replace(/\\s+/g, ' ').trim();
|
|
116
|
-
const seen = new Set();
|
|
117
|
-
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
118
|
-
|
|
119
|
-
for (let i = 0; i < 20; i += 1) {
|
|
120
|
-
if (document.querySelector('.item-root .title-text, .item-root .title a')) break;
|
|
121
|
-
await sleep(300);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const items = Array.from(document.querySelectorAll('.item-root'));
|
|
125
|
-
|
|
126
|
-
const results = [];
|
|
127
|
-
for (const el of items) {
|
|
128
|
-
const titleEl = el.querySelector('.title-text, .title a, a[title]');
|
|
129
|
-
const title = normalize(titleEl?.textContent) || normalize(titleEl?.getAttribute('title'));
|
|
130
|
-
let url = titleEl?.getAttribute('href') || '';
|
|
131
|
-
if (!title || !url) continue;
|
|
132
|
-
if (!url.startsWith('http')) url = 'https://search.douban.com' + url;
|
|
133
|
-
if (!url.includes('/subject/') || seen.has(url)) continue;
|
|
134
|
-
seen.add(url);
|
|
135
|
-
const ratingText = normalize(el.querySelector('.rating_nums')?.textContent);
|
|
136
|
-
const abstract = normalize(
|
|
137
|
-
el.querySelector('.meta.abstract, .meta, .abstract, p')?.textContent,
|
|
138
|
-
);
|
|
139
|
-
results.push({
|
|
140
|
-
rank: results.length + 1,
|
|
141
|
-
id: url.match(/subject\\/(\\d+)/)?.[1] || '',
|
|
142
|
-
type,
|
|
143
|
-
title,
|
|
144
|
-
rating: ratingText.includes('.') ? parseFloat(ratingText) : 0,
|
|
145
|
-
abstract: abstract.slice(0, 100) + (abstract.length > 100 ? '...' : ''),
|
|
146
|
-
url,
|
|
147
|
-
cover: el.querySelector('img')?.getAttribute('src') || '',
|
|
148
|
-
});
|
|
149
|
-
if (results.length >= ${safeLimit}) break;
|
|
150
|
-
}
|
|
151
|
-
return results;
|
|
152
|
-
})()
|
|
153
|
-
`);
|
|
154
|
-
return Array.isArray(data) ? data : [];
|
|
155
|
-
}
|