@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
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TOS (ByteDance Object Storage) multipart uploader with resume support.
|
|
3
|
+
*
|
|
4
|
+
* Uses AWS Signature V4 (HMAC-SHA256) with STS2 temporary credentials.
|
|
5
|
+
* For the init multipart upload call, the pre-computed auth from TosUploadInfo is used.
|
|
6
|
+
* For PUT part uploads and the final complete call, AWS4 is computed from STS2 credentials.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as crypto from 'node:crypto';
|
|
10
|
+
import * as fs from 'node:fs';
|
|
11
|
+
import * as os from 'node:os';
|
|
12
|
+
import * as path from 'node:path';
|
|
13
|
+
import { CommandExecutionError } from '../../../errors.js';
|
|
14
|
+
import type { Sts2Credentials, TosUploadInfo } from './types.js';
|
|
15
|
+
|
|
16
|
+
export interface TosUploadOptions {
|
|
17
|
+
filePath: string;
|
|
18
|
+
uploadInfo: TosUploadInfo;
|
|
19
|
+
credentials: Sts2Credentials;
|
|
20
|
+
onProgress?: (uploaded: number, total: number) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface ResumePart {
|
|
24
|
+
partNumber: number;
|
|
25
|
+
etag: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface ResumeState {
|
|
29
|
+
uploadId: string;
|
|
30
|
+
fileSize: number;
|
|
31
|
+
parts: ResumePart[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const PART_SIZE = 5 * 1024 * 1024; // 5 MB minimum per TOS/S3 spec
|
|
35
|
+
const RESUME_DIR = path.join(os.homedir(), '.opencli', 'douyin-resume');
|
|
36
|
+
|
|
37
|
+
// ── Resume file helpers ──────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
function getResumeFilePath(filePath: string): string {
|
|
40
|
+
const hash = crypto.createHash('sha256').update(filePath).digest('hex');
|
|
41
|
+
return path.join(RESUME_DIR, `${hash}.json`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function loadResumeState(resumePath: string, fileSize: number): ResumeState | null {
|
|
45
|
+
try {
|
|
46
|
+
const raw = fs.readFileSync(resumePath, 'utf8');
|
|
47
|
+
const state = JSON.parse(raw) as ResumeState;
|
|
48
|
+
if (state.fileSize === fileSize && state.uploadId && Array.isArray(state.parts)) {
|
|
49
|
+
return state;
|
|
50
|
+
}
|
|
51
|
+
} catch {
|
|
52
|
+
// no valid resume state
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function saveResumeState(resumePath: string, state: ResumeState): void {
|
|
58
|
+
fs.mkdirSync(path.dirname(resumePath), { recursive: true });
|
|
59
|
+
fs.writeFileSync(resumePath, JSON.stringify(state, null, 2), 'utf8');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function deleteResumeState(resumePath: string): void {
|
|
63
|
+
try {
|
|
64
|
+
fs.unlinkSync(resumePath);
|
|
65
|
+
} catch {
|
|
66
|
+
// ignore if not found
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ── AWS Signature V4 ─────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
function hmacSha256(key: Buffer | string, data: string): Buffer {
|
|
73
|
+
return crypto.createHmac('sha256', key).update(data, 'utf8').digest();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function sha256Hex(data: Buffer | string): string {
|
|
77
|
+
const hash = crypto.createHash('sha256');
|
|
78
|
+
if (typeof data === 'string') {
|
|
79
|
+
hash.update(data, 'utf8');
|
|
80
|
+
} else {
|
|
81
|
+
hash.update(data);
|
|
82
|
+
}
|
|
83
|
+
return hash.digest('hex');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function extractRegionFromHost(host: string): string {
|
|
87
|
+
// e.g. "tos-cn-i-alisg.volces.com" → "cn-i-alisg"
|
|
88
|
+
// e.g. "tos-cn-beijing.ivolces.com" → "cn-beijing"
|
|
89
|
+
const match = host.match(/^tos-([^.]+)\./);
|
|
90
|
+
if (match) return match[1];
|
|
91
|
+
return 'cn-north-1'; // fallback
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
interface SignedHeaders {
|
|
95
|
+
[key: string]: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Compute AWS Signature V4 headers for a TOS request.
|
|
100
|
+
* Returns a Record of all headers to include (including Authorization, x-amz-date, etc.)
|
|
101
|
+
*/
|
|
102
|
+
function computeAws4Headers(opts: {
|
|
103
|
+
method: string;
|
|
104
|
+
url: string;
|
|
105
|
+
headers: Record<string, string>;
|
|
106
|
+
body: Buffer | string;
|
|
107
|
+
credentials: Sts2Credentials;
|
|
108
|
+
service: string;
|
|
109
|
+
region: string;
|
|
110
|
+
datetime: string; // YYYYMMDDTHHmmssZ
|
|
111
|
+
}): SignedHeaders {
|
|
112
|
+
const { method, url, credentials, service, region, datetime } = opts;
|
|
113
|
+
const date = datetime.slice(0, 8); // YYYYMMDD
|
|
114
|
+
|
|
115
|
+
const parsedUrl = new URL(url);
|
|
116
|
+
const canonicalUri = parsedUrl.pathname || '/';
|
|
117
|
+
// Canonical query string: sort by name, encode
|
|
118
|
+
const queryParams = [...parsedUrl.searchParams.entries()]
|
|
119
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
120
|
+
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
|
|
121
|
+
.join('&');
|
|
122
|
+
|
|
123
|
+
const bodyHash = sha256Hex(opts.body);
|
|
124
|
+
|
|
125
|
+
// Merge in required headers and compute canonical headers
|
|
126
|
+
const allHeaders: Record<string, string> = {
|
|
127
|
+
...opts.headers,
|
|
128
|
+
host: parsedUrl.host,
|
|
129
|
+
'x-amz-content-sha256': bodyHash,
|
|
130
|
+
'x-amz-date': datetime,
|
|
131
|
+
'x-amz-security-token': credentials.session_token,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const sortedHeaderKeys = Object.keys(allHeaders).sort((a, b) =>
|
|
135
|
+
a.toLowerCase().localeCompare(b.toLowerCase())
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const canonicalHeaders = sortedHeaderKeys
|
|
139
|
+
.map(k => `${k.toLowerCase()}:${allHeaders[k].trim()}`)
|
|
140
|
+
.join('\n') + '\n';
|
|
141
|
+
|
|
142
|
+
const signedHeadersList = sortedHeaderKeys.map(k => k.toLowerCase()).join(';');
|
|
143
|
+
|
|
144
|
+
const canonicalRequest = [
|
|
145
|
+
method.toUpperCase(),
|
|
146
|
+
canonicalUri,
|
|
147
|
+
queryParams,
|
|
148
|
+
canonicalHeaders,
|
|
149
|
+
signedHeadersList,
|
|
150
|
+
bodyHash,
|
|
151
|
+
].join('\n');
|
|
152
|
+
|
|
153
|
+
const credentialScope = `${date}/${region}/${service}/aws4_request`;
|
|
154
|
+
const stringToSign = [
|
|
155
|
+
'AWS4-HMAC-SHA256',
|
|
156
|
+
datetime,
|
|
157
|
+
credentialScope,
|
|
158
|
+
sha256Hex(canonicalRequest),
|
|
159
|
+
].join('\n');
|
|
160
|
+
|
|
161
|
+
// Signing key chain
|
|
162
|
+
const kDate = hmacSha256(`AWS4${credentials.secret_access_key}`, date);
|
|
163
|
+
const kRegion = hmacSha256(kDate, region);
|
|
164
|
+
const kService = hmacSha256(kRegion, service);
|
|
165
|
+
const kSigning = hmacSha256(kService, 'aws4_request');
|
|
166
|
+
const signature = hmacSha256(kSigning, stringToSign).toString('hex');
|
|
167
|
+
|
|
168
|
+
const authorization = `AWS4-HMAC-SHA256 Credential=${credentials.access_key_id}/${credentialScope}, SignedHeaders=${signedHeadersList}, Signature=${signature}`;
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
...allHeaders,
|
|
172
|
+
Authorization: authorization,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ── HTTP helpers ─────────────────────────────────────────────────────────────
|
|
177
|
+
|
|
178
|
+
async function tosRequest(opts: {
|
|
179
|
+
method: string;
|
|
180
|
+
url: string;
|
|
181
|
+
headers: Record<string, string>;
|
|
182
|
+
body?: Buffer | string;
|
|
183
|
+
}): Promise<{ status: number; headers: Record<string, string>; body: string }> {
|
|
184
|
+
const { method, url, headers, body } = opts;
|
|
185
|
+
const fetchBody: BodyInit | null =
|
|
186
|
+
body == null ? null
|
|
187
|
+
: typeof body === 'string' ? body
|
|
188
|
+
: (body as unknown as Uint8Array<ArrayBuffer>);
|
|
189
|
+
const res = await fetch(url, {
|
|
190
|
+
method,
|
|
191
|
+
headers,
|
|
192
|
+
body: fetchBody,
|
|
193
|
+
});
|
|
194
|
+
const responseBody = await res.text();
|
|
195
|
+
const responseHeaders: Record<string, string> = {};
|
|
196
|
+
res.headers.forEach((value, key) => {
|
|
197
|
+
responseHeaders[key.toLowerCase()] = value;
|
|
198
|
+
});
|
|
199
|
+
return { status: res.status, headers: responseHeaders, body: responseBody };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function nowDatetime(): string {
|
|
203
|
+
return new Date().toISOString().replace(/[-:]/g, '').replace(/\.\d+Z$/, 'Z');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ── Phase 1: Init multipart upload ───────────────────────────────────────────
|
|
207
|
+
|
|
208
|
+
async function initMultipartUpload(
|
|
209
|
+
tosUrl: string,
|
|
210
|
+
auth: string,
|
|
211
|
+
credentials: Sts2Credentials,
|
|
212
|
+
): Promise<string> {
|
|
213
|
+
const initUrl = `${tosUrl}?uploads`;
|
|
214
|
+
const datetime = nowDatetime();
|
|
215
|
+
|
|
216
|
+
// Use the pre-computed auth for INIT, as it comes from ApplyVideoUpload
|
|
217
|
+
const headers: Record<string, string> = {
|
|
218
|
+
Authorization: auth,
|
|
219
|
+
'x-amz-date': datetime,
|
|
220
|
+
'x-amz-security-token': credentials.session_token,
|
|
221
|
+
'content-type': 'application/octet-stream',
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const res = await tosRequest({ method: 'POST', url: initUrl, headers });
|
|
225
|
+
|
|
226
|
+
if (res.status !== 200) {
|
|
227
|
+
throw new CommandExecutionError(
|
|
228
|
+
`TOS init multipart upload failed with status ${res.status}: ${res.body}`,
|
|
229
|
+
'Check that TOS credentials are valid and not expired.',
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Parse UploadId from XML: <UploadId>...</UploadId>
|
|
234
|
+
const match = res.body.match(/<UploadId>([^<]+)<\/UploadId>/);
|
|
235
|
+
if (!match) {
|
|
236
|
+
throw new CommandExecutionError(
|
|
237
|
+
`TOS init response missing UploadId: ${res.body}`,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
return match[1];
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ── Phase 2: Upload a single part ────────────────────────────────────────────
|
|
244
|
+
|
|
245
|
+
async function uploadPart(
|
|
246
|
+
tosUrl: string,
|
|
247
|
+
partNumber: number,
|
|
248
|
+
uploadId: string,
|
|
249
|
+
data: Buffer,
|
|
250
|
+
credentials: Sts2Credentials,
|
|
251
|
+
region: string,
|
|
252
|
+
): Promise<string> {
|
|
253
|
+
const parsedUrl = new URL(tosUrl);
|
|
254
|
+
parsedUrl.searchParams.set('partNumber', String(partNumber));
|
|
255
|
+
parsedUrl.searchParams.set('uploadId', uploadId);
|
|
256
|
+
const url = parsedUrl.toString();
|
|
257
|
+
|
|
258
|
+
const datetime = nowDatetime();
|
|
259
|
+
const headers = computeAws4Headers({
|
|
260
|
+
method: 'PUT',
|
|
261
|
+
url,
|
|
262
|
+
headers: { 'content-type': 'application/octet-stream' },
|
|
263
|
+
body: data,
|
|
264
|
+
credentials,
|
|
265
|
+
service: 'tos',
|
|
266
|
+
region,
|
|
267
|
+
datetime,
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
const res = await tosRequest({ method: 'PUT', url, headers, body: data });
|
|
271
|
+
|
|
272
|
+
if (res.status !== 200) {
|
|
273
|
+
throw new CommandExecutionError(
|
|
274
|
+
`TOS upload part ${partNumber} failed with status ${res.status}: ${res.body}`,
|
|
275
|
+
'Check that STS2 credentials are valid and not expired.',
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const etag = res.headers['etag'];
|
|
280
|
+
if (!etag) {
|
|
281
|
+
throw new CommandExecutionError(
|
|
282
|
+
`TOS upload part ${partNumber} response missing ETag header`,
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
return etag;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ── Phase 3: Complete multipart upload ───────────────────────────────────────
|
|
289
|
+
|
|
290
|
+
async function completeMultipartUpload(
|
|
291
|
+
tosUrl: string,
|
|
292
|
+
uploadId: string,
|
|
293
|
+
parts: ResumePart[],
|
|
294
|
+
credentials: Sts2Credentials,
|
|
295
|
+
region: string,
|
|
296
|
+
): Promise<void> {
|
|
297
|
+
const parsedUrl = new URL(tosUrl);
|
|
298
|
+
parsedUrl.searchParams.set('uploadId', uploadId);
|
|
299
|
+
const url = parsedUrl.toString();
|
|
300
|
+
|
|
301
|
+
const xmlBody =
|
|
302
|
+
'<CompleteMultipartUpload>' +
|
|
303
|
+
parts
|
|
304
|
+
.sort((a, b) => a.partNumber - b.partNumber)
|
|
305
|
+
.map(p => `<Part><PartNumber>${p.partNumber}</PartNumber><ETag>${p.etag}</ETag></Part>`)
|
|
306
|
+
.join('') +
|
|
307
|
+
'</CompleteMultipartUpload>';
|
|
308
|
+
|
|
309
|
+
const datetime = nowDatetime();
|
|
310
|
+
const headers = computeAws4Headers({
|
|
311
|
+
method: 'POST',
|
|
312
|
+
url,
|
|
313
|
+
headers: { 'content-type': 'application/xml' },
|
|
314
|
+
body: xmlBody,
|
|
315
|
+
credentials,
|
|
316
|
+
service: 'tos',
|
|
317
|
+
region,
|
|
318
|
+
datetime,
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
const res = await tosRequest({
|
|
322
|
+
method: 'POST',
|
|
323
|
+
url,
|
|
324
|
+
headers,
|
|
325
|
+
body: xmlBody,
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
if (res.status !== 200) {
|
|
329
|
+
throw new CommandExecutionError(
|
|
330
|
+
`TOS complete multipart upload failed with status ${res.status}: ${res.body}`,
|
|
331
|
+
'Check that all parts were uploaded successfully.',
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// ── Testing seams ────────────────────────────────────────────────────────────
|
|
337
|
+
|
|
338
|
+
// Allow tests to override the readSync behaviour (e.g. to simulate short reads)
|
|
339
|
+
// without fighting ESM live-binding limitations.
|
|
340
|
+
type ReadSyncFn = (fd: number, buffer: Buffer, offset: number, length: number, position: number) => number;
|
|
341
|
+
let _readSyncOverride: ReadSyncFn | null = null;
|
|
342
|
+
|
|
343
|
+
/** @internal — for testing only */
|
|
344
|
+
export function setReadSyncOverride(fn: ReadSyncFn | null): void {
|
|
345
|
+
_readSyncOverride = fn;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// ── Public API ───────────────────────────────────────────────────────────────
|
|
349
|
+
|
|
350
|
+
export async function tosUpload(options: TosUploadOptions): Promise<void> {
|
|
351
|
+
const { filePath, uploadInfo, credentials, onProgress } = options;
|
|
352
|
+
|
|
353
|
+
// Validate file exists
|
|
354
|
+
if (!fs.existsSync(filePath)) {
|
|
355
|
+
throw new CommandExecutionError(
|
|
356
|
+
`Video file not found: ${filePath}`,
|
|
357
|
+
'Ensure the file path is correct and accessible.',
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const { size: fileSize } = fs.statSync(filePath);
|
|
362
|
+
if (fileSize === 0) {
|
|
363
|
+
throw new CommandExecutionError(`Video file is empty: ${filePath}`);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const { tos_upload_url: tosUrl, auth } = uploadInfo;
|
|
367
|
+
const parsedTosUrl = new URL(tosUrl);
|
|
368
|
+
const region = extractRegionFromHost(parsedTosUrl.host);
|
|
369
|
+
|
|
370
|
+
const resumePath = getResumeFilePath(filePath);
|
|
371
|
+
let resumeState = loadResumeState(resumePath, fileSize);
|
|
372
|
+
|
|
373
|
+
let uploadId: string;
|
|
374
|
+
let completedParts: ResumePart[];
|
|
375
|
+
|
|
376
|
+
if (resumeState) {
|
|
377
|
+
// Resume from previous state
|
|
378
|
+
uploadId = resumeState.uploadId;
|
|
379
|
+
completedParts = resumeState.parts;
|
|
380
|
+
} else {
|
|
381
|
+
// Start fresh
|
|
382
|
+
uploadId = await initMultipartUpload(tosUrl, auth, credentials);
|
|
383
|
+
completedParts = [];
|
|
384
|
+
saveResumeState(resumePath, { uploadId, fileSize, parts: completedParts });
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Determine which parts are already done
|
|
388
|
+
const completedPartNumbers = new Set(completedParts.map(p => p.partNumber));
|
|
389
|
+
|
|
390
|
+
// Calculate total parts
|
|
391
|
+
const totalParts = Math.ceil(fileSize / PART_SIZE);
|
|
392
|
+
|
|
393
|
+
// Track uploaded bytes for progress
|
|
394
|
+
let uploadedBytes = completedParts.length * PART_SIZE;
|
|
395
|
+
if (onProgress) onProgress(Math.min(uploadedBytes, fileSize), fileSize);
|
|
396
|
+
|
|
397
|
+
const fd = fs.openSync(filePath, 'r');
|
|
398
|
+
try {
|
|
399
|
+
for (let partNumber = 1; partNumber <= totalParts; partNumber++) {
|
|
400
|
+
if (completedPartNumbers.has(partNumber)) {
|
|
401
|
+
continue; // already uploaded
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const offset = (partNumber - 1) * PART_SIZE;
|
|
405
|
+
const chunkSize = Math.min(PART_SIZE, fileSize - offset);
|
|
406
|
+
const buffer = Buffer.allocUnsafe(chunkSize);
|
|
407
|
+
const readFn = _readSyncOverride ?? fs.readSync;
|
|
408
|
+
const bytesRead = readFn(fd, buffer, 0, chunkSize, offset);
|
|
409
|
+
if (bytesRead !== chunkSize) {
|
|
410
|
+
throw new CommandExecutionError(
|
|
411
|
+
`Short read on part ${partNumber}: expected ${chunkSize} bytes, got ${bytesRead}`,
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const etag = await uploadPart(tosUrl, partNumber, uploadId, buffer, credentials, region);
|
|
416
|
+
|
|
417
|
+
completedParts.push({ partNumber, etag });
|
|
418
|
+
saveResumeState(resumePath, { uploadId, fileSize, parts: completedParts });
|
|
419
|
+
|
|
420
|
+
uploadedBytes = Math.min(offset + chunkSize, fileSize);
|
|
421
|
+
if (onProgress) onProgress(uploadedBytes, fileSize);
|
|
422
|
+
}
|
|
423
|
+
} finally {
|
|
424
|
+
fs.closeSync(fd);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
await completeMultipartUpload(tosUrl, uploadId, completedParts, credentials, region);
|
|
428
|
+
|
|
429
|
+
deleteResumeState(resumePath);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// ── Internal exports for testing ─────────────────────────────────────────────
|
|
433
|
+
|
|
434
|
+
export {
|
|
435
|
+
PART_SIZE,
|
|
436
|
+
RESUME_DIR,
|
|
437
|
+
extractRegionFromHost,
|
|
438
|
+
getResumeFilePath,
|
|
439
|
+
loadResumeState,
|
|
440
|
+
saveResumeState,
|
|
441
|
+
deleteResumeState,
|
|
442
|
+
computeAws4Headers,
|
|
443
|
+
};
|
|
444
|
+
export type { ResumeState, ResumePart };
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import type { IPage } from '../../../types.js';
|
|
3
|
+
import { TimeoutError } from '../../../errors.js';
|
|
4
|
+
import { pollTranscodeWithFetch } from './transcode.js';
|
|
5
|
+
import type { TranscodeResult } from './types.js';
|
|
6
|
+
|
|
7
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
function makePage(): IPage {
|
|
10
|
+
return {
|
|
11
|
+
goto: vi.fn(),
|
|
12
|
+
evaluate: vi.fn(),
|
|
13
|
+
getCookies: vi.fn(),
|
|
14
|
+
snapshot: vi.fn(),
|
|
15
|
+
click: vi.fn(),
|
|
16
|
+
typeText: vi.fn(),
|
|
17
|
+
pressKey: vi.fn(),
|
|
18
|
+
scrollTo: vi.fn(),
|
|
19
|
+
getFormState: vi.fn(),
|
|
20
|
+
wait: vi.fn(),
|
|
21
|
+
tabs: vi.fn(),
|
|
22
|
+
closeTab: vi.fn(),
|
|
23
|
+
newTab: vi.fn(),
|
|
24
|
+
selectTab: vi.fn(),
|
|
25
|
+
networkRequests: vi.fn(),
|
|
26
|
+
consoleMessages: vi.fn(),
|
|
27
|
+
scroll: vi.fn(),
|
|
28
|
+
autoScroll: vi.fn(),
|
|
29
|
+
installInterceptor: vi.fn(),
|
|
30
|
+
getInterceptedRequests: vi.fn(),
|
|
31
|
+
screenshot: vi.fn(),
|
|
32
|
+
} as unknown as IPage;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const COMPLETE_RESULT: TranscodeResult & { encode: number } = {
|
|
36
|
+
encode: 2,
|
|
37
|
+
duration: 30,
|
|
38
|
+
fps: 30,
|
|
39
|
+
height: 1920,
|
|
40
|
+
width: 1080,
|
|
41
|
+
poster_uri: 'tos-cn-i-alisg.volces.com/poster/abc',
|
|
42
|
+
poster_url: 'https://p3-creator.douyinpic.com/poster/abc',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// ── Tests ─────────────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
describe('pollTranscodeWithFetch', () => {
|
|
48
|
+
it('returns TranscodeResult immediately when first response has encode=2', async () => {
|
|
49
|
+
const fetchFn = vi.fn().mockResolvedValue(COMPLETE_RESULT);
|
|
50
|
+
const page = makePage();
|
|
51
|
+
|
|
52
|
+
const result = await pollTranscodeWithFetch(fetchFn, page, 'vid_123');
|
|
53
|
+
|
|
54
|
+
expect(result).toEqual(COMPLETE_RESULT);
|
|
55
|
+
expect(fetchFn).toHaveBeenCalledOnce();
|
|
56
|
+
const [, method, url] = fetchFn.mock.calls[0] as [IPage, string, string];
|
|
57
|
+
expect(method).toBe('GET');
|
|
58
|
+
expect(url).toContain('video_id=vid_123');
|
|
59
|
+
expect(url).toContain('aid=1128');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('polls multiple times until encode=2 is received', async () => {
|
|
63
|
+
const pending = { encode: 1, duration: 0, fps: 0, height: 0, width: 0, poster_uri: '', poster_url: '' };
|
|
64
|
+
const fetchFn = vi
|
|
65
|
+
.fn()
|
|
66
|
+
.mockResolvedValueOnce(pending)
|
|
67
|
+
.mockResolvedValueOnce(pending)
|
|
68
|
+
.mockResolvedValueOnce(COMPLETE_RESULT);
|
|
69
|
+
const page = makePage();
|
|
70
|
+
|
|
71
|
+
// Use a large timeoutMs so it doesn't expire, but override POLL_INTERVAL via
|
|
72
|
+
// a short timeout knowing we'll get 3 calls quickly with mocked promises.
|
|
73
|
+
// Since pollTranscodeWithFetch uses setTimeout for 3s between polls, we need
|
|
74
|
+
// to mock timers to keep tests fast.
|
|
75
|
+
vi.useFakeTimers();
|
|
76
|
+
|
|
77
|
+
const resultPromise = pollTranscodeWithFetch(fetchFn, page, 'vid_456', 60_000);
|
|
78
|
+
|
|
79
|
+
// Advance timers for each pending poll cycle
|
|
80
|
+
await vi.runAllTimersAsync();
|
|
81
|
+
|
|
82
|
+
const result = await resultPromise;
|
|
83
|
+
expect(result).toEqual(COMPLETE_RESULT);
|
|
84
|
+
expect(fetchFn).toHaveBeenCalledTimes(3);
|
|
85
|
+
|
|
86
|
+
vi.useRealTimers();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('throws TimeoutError when encode never becomes 2 within timeoutMs', async () => {
|
|
90
|
+
const pending = { encode: 1, duration: 0, fps: 0, height: 0, width: 0, poster_uri: '', poster_url: '' };
|
|
91
|
+
const fetchFn = vi.fn().mockResolvedValue(pending);
|
|
92
|
+
const page = makePage();
|
|
93
|
+
|
|
94
|
+
vi.useFakeTimers();
|
|
95
|
+
|
|
96
|
+
const resultPromise = pollTranscodeWithFetch(fetchFn, page, 'vid_789', 5_000);
|
|
97
|
+
// Suppress unhandled-rejection so vitest doesn't flag it
|
|
98
|
+
resultPromise.catch(() => undefined);
|
|
99
|
+
|
|
100
|
+
// Advance time past the 5s timeout
|
|
101
|
+
await vi.runAllTimersAsync();
|
|
102
|
+
|
|
103
|
+
await expect(resultPromise).rejects.toBeInstanceOf(TimeoutError);
|
|
104
|
+
|
|
105
|
+
vi.useRealTimers();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('URL encodes video_id in the request URL', async () => {
|
|
109
|
+
const fetchFn = vi.fn().mockResolvedValue(COMPLETE_RESULT);
|
|
110
|
+
const page = makePage();
|
|
111
|
+
|
|
112
|
+
await pollTranscodeWithFetch(fetchFn, page, 'vid with spaces');
|
|
113
|
+
|
|
114
|
+
const [, , url] = fetchFn.mock.calls[0] as [IPage, string, string];
|
|
115
|
+
expect(url).toContain('video_id=vid%20with%20spaces');
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcode poller for Douyin video processing.
|
|
3
|
+
*
|
|
4
|
+
* After a video is uploaded via TOS and the "confirm upload" API is called,
|
|
5
|
+
* Douyin transcodes the video asynchronously. This module polls the transcode
|
|
6
|
+
* status endpoint until encode=2 (complete) or a timeout is reached.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { IPage } from '../../../types.js';
|
|
10
|
+
import { TimeoutError } from '../../../errors.js';
|
|
11
|
+
import { browserFetch } from './browser-fetch.js';
|
|
12
|
+
import type { TranscodeResult } from './types.js';
|
|
13
|
+
|
|
14
|
+
const POLL_INTERVAL_MS = 3_000;
|
|
15
|
+
const DEFAULT_TIMEOUT_MS = 300_000;
|
|
16
|
+
|
|
17
|
+
const TRANSCODE_URL_BASE =
|
|
18
|
+
'https://creator.douyin.com/web/api/media/video/transend/';
|
|
19
|
+
|
|
20
|
+
type BrowserFetchFn = (
|
|
21
|
+
page: IPage,
|
|
22
|
+
method: 'GET' | 'POST',
|
|
23
|
+
url: string,
|
|
24
|
+
) => Promise<unknown>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Lower-level poll function that accepts an injected fetch function.
|
|
28
|
+
* Exported for testability.
|
|
29
|
+
*/
|
|
30
|
+
export async function pollTranscodeWithFetch(
|
|
31
|
+
fetchFn: BrowserFetchFn,
|
|
32
|
+
page: IPage,
|
|
33
|
+
videoId: string,
|
|
34
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
35
|
+
): Promise<TranscodeResult> {
|
|
36
|
+
const url = `${TRANSCODE_URL_BASE}?video_id=${encodeURIComponent(videoId)}&aid=1128`;
|
|
37
|
+
const deadline = Date.now() + timeoutMs;
|
|
38
|
+
|
|
39
|
+
while (Date.now() < deadline) {
|
|
40
|
+
const result = (await fetchFn(page, 'GET', url)) as {
|
|
41
|
+
encode: number;
|
|
42
|
+
} & TranscodeResult;
|
|
43
|
+
|
|
44
|
+
if (result.encode === 2) {
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Wait before next poll, but don't exceed the deadline
|
|
49
|
+
const remaining = deadline - Date.now();
|
|
50
|
+
if (remaining <= 0) break;
|
|
51
|
+
await new Promise<void>(resolve =>
|
|
52
|
+
setTimeout(resolve, Math.min(POLL_INTERVAL_MS, remaining)),
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
throw new TimeoutError(
|
|
57
|
+
`Douyin transcode for video ${videoId}`,
|
|
58
|
+
Math.round(timeoutMs / 1000),
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Poll Douyin's transcode status endpoint until the video is fully transcoded
|
|
64
|
+
* (encode=2) or the timeout expires.
|
|
65
|
+
*
|
|
66
|
+
* @param page - Browser page for making credentialed API calls
|
|
67
|
+
* @param videoId - The video_id returned from the confirm upload step
|
|
68
|
+
* @param timeoutMs - Maximum wait time in ms (default: 300 000 = 5 minutes)
|
|
69
|
+
* @returns TranscodeResult including duration, fps, dimensions, and poster info
|
|
70
|
+
* @throws TimeoutError if transcode does not complete within timeoutMs
|
|
71
|
+
*/
|
|
72
|
+
export async function pollTranscode(
|
|
73
|
+
page: IPage,
|
|
74
|
+
videoId: string,
|
|
75
|
+
timeoutMs?: number,
|
|
76
|
+
): Promise<TranscodeResult> {
|
|
77
|
+
return pollTranscodeWithFetch(browserFetch, page, videoId, timeoutMs);
|
|
78
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface Sts2Credentials {
|
|
2
|
+
access_key_id: string;
|
|
3
|
+
secret_access_key: string;
|
|
4
|
+
session_token: string;
|
|
5
|
+
expired_time: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface TosUploadInfo {
|
|
9
|
+
tos_upload_url: string;
|
|
10
|
+
/** Pre-computed Authorization header value returned by ApplyVideoUpload (StoreInfos[0].Auth) */
|
|
11
|
+
auth: string;
|
|
12
|
+
video_id: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface TranscodeResult {
|
|
16
|
+
encode: number;
|
|
17
|
+
duration: number;
|
|
18
|
+
fps: number;
|
|
19
|
+
height: number;
|
|
20
|
+
width: number;
|
|
21
|
+
poster_uri: string;
|
|
22
|
+
poster_url: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface PublishResult {
|
|
26
|
+
aweme_id: string;
|
|
27
|
+
url: string;
|
|
28
|
+
publish_time: number;
|
|
29
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { getRegistry } from '../../registry.js';
|
|
3
|
+
import './activities.js';
|
|
4
|
+
|
|
5
|
+
describe('douyin activities registration', () => {
|
|
6
|
+
it('registers the activities command', () => {
|
|
7
|
+
const registry = getRegistry();
|
|
8
|
+
const cmd = [...registry.values()].find(c => c.site === 'douyin' && c.name === 'activities');
|
|
9
|
+
expect(cmd).toBeDefined();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('has expected columns', () => {
|
|
13
|
+
const registry = getRegistry();
|
|
14
|
+
const cmd = [...registry.values()].find(c => c.site === 'douyin' && c.name === 'activities');
|
|
15
|
+
expect(cmd?.columns).toContain('activity_id');
|
|
16
|
+
expect(cmd?.columns).toContain('title');
|
|
17
|
+
expect(cmd?.columns).toContain('end_time');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('uses COOKIE strategy', () => {
|
|
21
|
+
const registry = getRegistry();
|
|
22
|
+
const cmd = [...registry.values()].find(c => c.site === 'douyin' && c.name === 'activities');
|
|
23
|
+
expect(cmd?.strategy).toBe('cookie');
|
|
24
|
+
});
|
|
25
|
+
});
|