@jackwener/opencli 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/actions/setup-chrome/action.yml +5 -4
- package/.github/workflows/build-extension.yml +2 -6
- package/.github/workflows/ci.yml +37 -3
- package/.github/workflows/e2e-headed.yml +16 -3
- package/CHANGELOG.md +23 -0
- package/PRIVACY.md +57 -0
- package/README.md +36 -7
- package/README.zh-CN.md +13 -6
- package/SKILL.md +103 -2
- package/dist/browser/cdp.d.ts +2 -1
- package/dist/browser/discover.d.ts +4 -1
- package/dist/browser/discover.js +6 -2
- package/dist/browser/errors.d.ts +2 -2
- package/dist/browser/errors.js +4 -12
- package/dist/browser/mcp.d.ts +2 -1
- package/dist/build-manifest.d.ts +2 -0
- package/dist/build-manifest.js +39 -14
- package/dist/build-manifest.test.js +21 -0
- package/dist/capabilityRouting.d.ts +2 -0
- package/dist/capabilityRouting.js +2 -1
- package/dist/cli-manifest.json +1838 -151
- package/dist/cli.js +34 -3
- package/dist/clis/36kr/article.d.ts +1 -0
- package/dist/clis/36kr/article.js +62 -0
- package/dist/clis/36kr/hot.d.ts +3 -0
- package/dist/clis/36kr/hot.js +80 -0
- package/dist/clis/36kr/hot.test.d.ts +1 -0
- package/dist/clis/36kr/hot.test.js +15 -0
- package/dist/clis/36kr/news.d.ts +1 -0
- package/dist/clis/36kr/news.js +51 -0
- package/dist/clis/36kr/news.test.d.ts +1 -0
- package/dist/clis/36kr/news.test.js +85 -0
- package/dist/clis/36kr/search.d.ts +1 -0
- package/dist/clis/36kr/search.js +72 -0
- package/dist/clis/apple-podcasts/search.js +2 -1
- package/dist/clis/arxiv/search.js +2 -2
- package/dist/clis/bbc/news.js +0 -1
- package/dist/clis/bilibili/comments.d.ts +5 -0
- package/dist/clis/bilibili/comments.js +40 -0
- package/dist/clis/bilibili/comments.test.d.ts +1 -0
- package/dist/clis/bilibili/comments.test.js +82 -0
- package/dist/clis/chatgpt/ask.js +29 -14
- package/dist/clis/chatgpt/ax.d.ts +6 -0
- package/dist/clis/chatgpt/ax.js +172 -1
- package/dist/clis/chatgpt/model.d.ts +1 -0
- package/dist/clis/chatgpt/model.js +24 -0
- package/dist/clis/chatgpt/send.js +12 -3
- package/dist/clis/ctrip/search.js +0 -1
- package/dist/clis/douban/download.d.ts +1 -0
- package/dist/clis/douban/download.js +67 -0
- package/dist/clis/douban/download.test.d.ts +1 -0
- package/dist/clis/douban/download.test.js +170 -0
- package/dist/clis/douban/photos.d.ts +1 -0
- package/dist/clis/douban/photos.js +34 -0
- package/dist/clis/douban/utils.d.ts +25 -0
- package/dist/clis/douban/utils.js +190 -1
- package/dist/clis/douban/utils.test.d.ts +1 -0
- package/dist/clis/douban/utils.test.js +64 -0
- 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/hackernews/search.yaml +1 -1
- package/dist/clis/imdb/person.d.ts +1 -0
- package/dist/clis/imdb/person.js +203 -0
- package/dist/clis/imdb/reviews.d.ts +1 -0
- package/dist/clis/imdb/reviews.js +88 -0
- package/dist/clis/imdb/search.d.ts +1 -0
- package/dist/clis/imdb/search.js +161 -0
- package/dist/clis/imdb/title.d.ts +1 -0
- package/dist/clis/imdb/title.js +93 -0
- package/dist/clis/imdb/top.d.ts +1 -0
- package/dist/clis/imdb/top.js +53 -0
- package/dist/clis/imdb/trending.d.ts +1 -0
- package/dist/clis/imdb/trending.js +52 -0
- package/dist/clis/imdb/utils.d.ts +46 -0
- package/dist/clis/imdb/utils.js +285 -0
- package/dist/clis/imdb/utils.test.d.ts +1 -0
- package/dist/clis/imdb/utils.test.js +88 -0
- package/dist/clis/instagram/search.yaml +2 -1
- package/dist/clis/jd/item.d.ts +4 -0
- package/dist/clis/jd/item.js +16 -15
- package/dist/clis/jd/item.test.js +16 -1
- package/dist/clis/linux-do/categories.yaml +38 -9
- package/dist/clis/linux-do/category.d.ts +1 -0
- package/dist/clis/linux-do/category.js +36 -0
- package/dist/clis/linux-do/feed.d.ts +45 -0
- package/dist/clis/linux-do/feed.js +397 -0
- package/dist/clis/linux-do/feed.test.d.ts +1 -0
- package/dist/clis/linux-do/feed.test.js +118 -0
- package/dist/clis/linux-do/hot.d.ts +1 -0
- package/dist/clis/linux-do/hot.js +25 -0
- package/dist/clis/linux-do/latest.d.ts +1 -0
- package/dist/clis/linux-do/latest.js +18 -0
- package/dist/clis/linux-do/search.yaml +3 -1
- package/dist/clis/linux-do/tags.yaml +41 -0
- package/dist/clis/linux-do/topic.yaml +41 -3
- package/dist/clis/linux-do/user-posts.yaml +67 -0
- package/dist/clis/linux-do/user-topics.yaml +54 -0
- package/dist/clis/medium/search.js +1 -1
- package/dist/clis/paperreview/commands.test.d.ts +3 -0
- package/dist/clis/paperreview/commands.test.js +243 -0
- package/dist/clis/paperreview/feedback.d.ts +1 -0
- package/dist/clis/paperreview/feedback.js +52 -0
- package/dist/clis/paperreview/review.d.ts +1 -0
- package/dist/clis/paperreview/review.js +37 -0
- package/dist/clis/paperreview/submit.d.ts +1 -0
- package/dist/clis/paperreview/submit.js +85 -0
- package/dist/clis/paperreview/utils.d.ts +46 -0
- package/dist/clis/paperreview/utils.js +197 -0
- package/dist/clis/paperreview/utils.test.d.ts +1 -0
- package/dist/clis/paperreview/utils.test.js +49 -0
- package/dist/clis/producthunt/browse.d.ts +1 -0
- package/dist/clis/producthunt/browse.js +99 -0
- package/dist/clis/producthunt/hot.d.ts +1 -0
- package/dist/clis/producthunt/hot.js +110 -0
- package/dist/clis/producthunt/posts.d.ts +1 -0
- package/dist/clis/producthunt/posts.js +28 -0
- package/dist/clis/producthunt/today.d.ts +1 -0
- package/dist/clis/producthunt/today.js +35 -0
- package/dist/clis/producthunt/utils.d.ts +29 -0
- package/dist/clis/producthunt/utils.js +99 -0
- package/dist/clis/producthunt/utils.test.d.ts +1 -0
- package/dist/clis/producthunt/utils.test.js +64 -0
- package/dist/clis/reuters/search.js +0 -1
- package/dist/clis/twitter/article.js +4 -28
- package/dist/clis/twitter/likes.d.ts +24 -0
- package/dist/clis/twitter/likes.js +217 -0
- package/dist/clis/twitter/likes.test.d.ts +1 -0
- package/dist/clis/twitter/likes.test.js +85 -0
- package/dist/clis/twitter/profile.js +4 -28
- package/dist/clis/twitter/search.js +7 -4
- package/dist/clis/twitter/search.test.js +56 -2
- package/dist/clis/twitter/shared.d.ts +6 -0
- package/dist/clis/twitter/shared.js +35 -0
- package/dist/clis/twitter/timeline.js +2 -13
- 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/weixin/download.d.ts +17 -0
- package/dist/clis/weixin/download.js +88 -20
- package/dist/clis/weread/book.js +2 -2
- package/dist/clis/weread/commands.test.d.ts +3 -0
- package/dist/clis/weread/commands.test.js +43 -0
- package/dist/clis/weread/highlights.js +2 -2
- package/dist/clis/weread/notebooks.js +2 -2
- package/dist/clis/weread/notes.js +3 -3
- package/dist/clis/weread/search.js +3 -2
- package/dist/clis/weread/shelf.js +2 -2
- package/dist/clis/weread/utils.d.ts +4 -4
- package/dist/clis/weread/utils.js +32 -14
- package/dist/clis/weread/utils.test.js +1 -28
- package/dist/clis/xiaohongshu/comments.d.ts +5 -0
- package/dist/clis/xiaohongshu/comments.js +74 -0
- package/dist/clis/xiaohongshu/comments.test.d.ts +1 -0
- package/dist/clis/xiaohongshu/comments.test.js +79 -0
- package/dist/clis/xiaohongshu/publish.js +114 -18
- package/dist/clis/xiaohongshu/publish.test.d.ts +1 -0
- package/dist/clis/xiaohongshu/publish.test.js +119 -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/zhihu/search.yaml +2 -1
- package/dist/commanderAdapter.d.ts +1 -0
- package/dist/commanderAdapter.js +176 -29
- package/dist/commanderAdapter.test.d.ts +1 -0
- package/dist/commanderAdapter.test.js +62 -0
- package/dist/daemon.js +17 -1
- package/dist/discovery.js +8 -14
- package/dist/doctor.d.ts +1 -0
- package/dist/doctor.js +9 -2
- package/dist/download/index.js +63 -51
- package/dist/download/index.test.js +17 -4
- package/dist/errors.d.ts +3 -1
- package/dist/errors.js +15 -32
- package/dist/execution.d.ts +1 -3
- package/dist/execution.js +21 -1
- package/dist/external-clis.yaml +0 -17
- package/dist/hooks.js +2 -0
- package/dist/main.js +5 -0
- package/dist/output.js +5 -1
- package/dist/pipeline/executor.js +3 -4
- package/dist/plugin-manifest.d.ts +70 -0
- package/dist/plugin-manifest.js +160 -0
- package/dist/plugin-manifest.test.d.ts +4 -0
- package/dist/plugin-manifest.test.js +179 -0
- package/dist/plugin.d.ts +38 -5
- package/dist/plugin.js +267 -33
- package/dist/plugin.test.js +220 -3
- package/dist/registry.d.ts +4 -0
- package/dist/registry.js +2 -0
- package/dist/runtime-detect.d.ts +21 -0
- package/dist/runtime-detect.js +32 -0
- package/dist/runtime-detect.test.d.ts +1 -0
- package/dist/runtime-detect.test.js +27 -0
- package/dist/runtime.js +1 -1
- package/dist/serialization.d.ts +2 -0
- package/dist/serialization.js +6 -0
- package/dist/types.d.ts +1 -0
- package/dist/update-check.d.ts +22 -0
- package/dist/update-check.js +112 -0
- package/dist/weixin-download.test.d.ts +1 -0
- package/dist/weixin-download.test.js +30 -0
- package/dist/weread-private-api-regression.test.d.ts +1 -0
- package/dist/weread-private-api-regression.test.js +122 -0
- 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 +3 -0
- package/dist/yaml-schema.js +18 -1
- package/docs/.vitepress/config.mts +17 -0
- package/docs/adapters/browser/36kr.md +47 -0
- package/docs/adapters/browser/douban.md +14 -0
- package/docs/adapters/browser/douyin.md +75 -0
- package/docs/adapters/browser/imdb.md +47 -0
- package/docs/adapters/browser/jd.md +2 -2
- package/docs/adapters/browser/linux-do.md +181 -20
- package/docs/adapters/browser/paperreview.md +43 -0
- package/docs/adapters/browser/producthunt.md +49 -0
- package/docs/adapters/browser/twitter.md +6 -0
- package/docs/adapters/desktop/chatgpt.md +5 -0
- package/docs/adapters/index.md +12 -3
- package/docs/advanced/download.md +4 -0
- package/docs/advanced/rate-limiter-plugin.md +99 -0
- package/docs/guide/electron-app-cli.md +200 -0
- package/docs/guide/getting-started.md +1 -0
- package/docs/guide/plugins.md +87 -0
- package/docs/zh/guide/electron-app-cli.md +188 -0
- package/docs/zh/guide/getting-started.md +1 -0
- package/docs/zh/guide/plugins.md +65 -0
- package/extension/dist/background.js +508 -518
- package/extension/manifest.json +6 -2
- package/extension/package.json +2 -1
- package/extension/popup.html +84 -0
- package/extension/popup.js +25 -0
- package/extension/scripts/package-release.mjs +179 -0
- package/extension/src/background.ts +22 -1
- package/package.json +4 -1
- package/scripts/postinstall.js +10 -0
- package/src/browser/cdp.ts +2 -1
- package/src/browser/discover.ts +8 -3
- package/src/browser/errors.ts +13 -14
- package/src/browser/mcp.ts +2 -1
- package/src/build-manifest.test.ts +23 -0
- package/src/build-manifest.ts +40 -15
- package/src/capabilityRouting.ts +2 -1
- package/src/cli.ts +35 -3
- package/src/clis/36kr/article.ts +69 -0
- package/src/clis/36kr/hot.test.ts +19 -0
- package/src/clis/36kr/hot.ts +100 -0
- package/src/clis/36kr/news.test.ts +90 -0
- package/src/clis/36kr/news.ts +54 -0
- package/src/clis/36kr/search.ts +78 -0
- package/src/clis/apple-podcasts/search.ts +2 -1
- package/src/clis/arxiv/search.ts +2 -2
- package/src/clis/bbc/news.ts +0 -1
- package/src/clis/bilibili/comments.test.ts +102 -0
- package/src/clis/bilibili/comments.ts +44 -0
- package/src/clis/chatgpt/ask.ts +28 -14
- package/src/clis/chatgpt/ax.ts +180 -1
- package/src/clis/chatgpt/model.ts +27 -0
- package/src/clis/chatgpt/send.ts +16 -6
- package/src/clis/ctrip/search.ts +0 -1
- package/src/clis/douban/download.test.ts +196 -0
- package/src/clis/douban/download.ts +78 -0
- package/src/clis/douban/photos.ts +36 -0
- package/src/clis/douban/utils.test.ts +97 -0
- package/src/clis/douban/utils.ts +232 -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/hackernews/search.yaml +1 -1
- package/src/clis/imdb/person.ts +232 -0
- package/src/clis/imdb/reviews.ts +111 -0
- package/src/clis/imdb/search.ts +179 -0
- package/src/clis/imdb/title.ts +121 -0
- package/src/clis/imdb/top.ts +67 -0
- package/src/clis/imdb/trending.ts +66 -0
- package/src/clis/imdb/utils.test.ts +117 -0
- package/src/clis/imdb/utils.ts +305 -0
- package/src/clis/instagram/search.yaml +2 -1
- package/src/clis/jd/item.test.ts +18 -1
- package/src/clis/jd/item.ts +18 -15
- package/src/clis/linux-do/categories.yaml +38 -9
- package/src/clis/linux-do/category.ts +37 -0
- package/src/clis/linux-do/feed.test.ts +132 -0
- package/src/clis/linux-do/feed.ts +501 -0
- package/src/clis/linux-do/hot.ts +26 -0
- package/src/clis/linux-do/latest.ts +19 -0
- package/src/clis/linux-do/search.yaml +3 -1
- package/src/clis/linux-do/tags.yaml +41 -0
- package/src/clis/linux-do/topic.yaml +41 -3
- package/src/clis/linux-do/user-posts.yaml +67 -0
- package/src/clis/linux-do/user-topics.yaml +54 -0
- package/src/clis/medium/search.ts +1 -1
- package/src/clis/paperreview/commands.test.ts +283 -0
- package/src/clis/paperreview/feedback.ts +64 -0
- package/src/clis/paperreview/review.ts +47 -0
- package/src/clis/paperreview/submit.ts +119 -0
- package/src/clis/paperreview/utils.test.ts +68 -0
- package/src/clis/paperreview/utils.ts +276 -0
- package/src/clis/producthunt/browse.ts +109 -0
- package/src/clis/producthunt/hot.ts +127 -0
- package/src/clis/producthunt/posts.ts +29 -0
- package/src/clis/producthunt/today.ts +37 -0
- package/src/clis/producthunt/utils.test.ts +72 -0
- package/src/clis/producthunt/utils.ts +122 -0
- package/src/clis/reuters/search.ts +0 -1
- package/src/clis/twitter/article.ts +5 -28
- package/src/clis/twitter/likes.test.ts +91 -0
- package/src/clis/twitter/likes.ts +256 -0
- package/src/clis/twitter/profile.ts +5 -28
- package/src/clis/twitter/search.test.ts +71 -2
- package/src/clis/twitter/search.ts +8 -4
- package/src/clis/twitter/shared.ts +45 -0
- package/src/clis/twitter/timeline.ts +2 -13
- 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/weixin/download.ts +114 -20
- package/src/clis/weread/book.ts +2 -2
- package/src/clis/weread/commands.test.ts +57 -0
- package/src/clis/weread/highlights.ts +2 -2
- package/src/clis/weread/notebooks.ts +2 -2
- package/src/clis/weread/notes.ts +3 -3
- package/src/clis/weread/search.ts +3 -2
- package/src/clis/weread/shelf.ts +2 -2
- package/src/clis/weread/utils.test.ts +1 -32
- package/src/clis/weread/utils.ts +41 -16
- package/src/clis/xiaohongshu/comments.test.ts +96 -0
- package/src/clis/xiaohongshu/comments.ts +81 -0
- package/src/clis/xiaohongshu/publish.test.ts +137 -0
- package/src/clis/xiaohongshu/publish.ts +129 -18
- 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/zhihu/search.yaml +2 -1
- package/src/commanderAdapter.test.ts +78 -0
- package/src/commanderAdapter.ts +188 -24
- package/src/daemon.ts +19 -1
- package/src/discovery.ts +8 -15
- package/src/doctor.ts +13 -2
- package/src/download/index.test.ts +14 -4
- package/src/download/index.ts +67 -55
- package/src/errors.ts +25 -66
- package/src/execution.ts +28 -3
- package/src/external-clis.yaml +0 -17
- package/src/hooks.ts +1 -0
- package/src/main.ts +6 -0
- package/src/output.ts +3 -1
- package/src/pipeline/executor.ts +4 -6
- package/src/plugin-manifest.test.ts +223 -0
- package/src/plugin-manifest.ts +206 -0
- package/src/plugin.test.ts +246 -2
- package/src/plugin.ts +338 -36
- package/src/registry.ts +6 -1
- package/src/runtime-detect.test.ts +30 -0
- package/src/runtime-detect.ts +36 -0
- package/src/runtime.ts +1 -1
- package/src/serialization.ts +4 -0
- package/src/types.ts +1 -0
- package/src/update-check.ts +114 -0
- package/src/weixin-download.test.ts +64 -0
- package/src/weread-private-api-regression.test.ts +150 -0
- package/src/weread-search-regression.test.ts +44 -0
- package/src/yaml-schema.ts +20 -0
- package/tests/e2e/browser-auth.test.ts +13 -9
- package/tests/e2e/browser-public-extended.test.ts +162 -0
- package/tests/e2e/browser-public.test.ts +55 -136
- package/tests/e2e/helpers.ts +2 -1
- package/tests/e2e/public-commands.test.ts +37 -3
- package/tests/smoke/api-health.test.ts +1 -1
- package/vitest.config.ts +34 -17
- package/dist/clis/linux-do/category.yaml +0 -51
- package/dist/clis/linux-do/hot.yaml +0 -50
- package/dist/clis/linux-do/latest.yaml +0 -40
- package/src/clis/linux-do/category.yaml +0 -51
- package/src/clis/linux-do/hot.yaml +0 -50
- package/src/clis/linux-do/latest.yaml +0 -40
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import { PART_SIZE, computeAws4Headers, deleteResumeState, extractRegionFromHost, getResumeFilePath, loadResumeState, saveResumeState, } from './tos-upload.js';
|
|
6
|
+
// ── extractRegionFromHost ────────────────────────────────────────────────────
|
|
7
|
+
describe('extractRegionFromHost', () => {
|
|
8
|
+
it('extracts region from standard TOS host', () => {
|
|
9
|
+
expect(extractRegionFromHost('tos-cn-i-alisg.volces.com')).toBe('cn-i-alisg');
|
|
10
|
+
});
|
|
11
|
+
it('extracts region from beijing TOS host', () => {
|
|
12
|
+
expect(extractRegionFromHost('tos-cn-beijing.ivolces.com')).toBe('cn-beijing');
|
|
13
|
+
});
|
|
14
|
+
it('falls back to cn-north-1 for unknown host', () => {
|
|
15
|
+
expect(extractRegionFromHost('unknown.example.com')).toBe('cn-north-1');
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
// ── Part chunking ────────────────────────────────────────────────────────────
|
|
19
|
+
describe('PART_SIZE and part chunking logic', () => {
|
|
20
|
+
it('PART_SIZE is exactly 5 MB', () => {
|
|
21
|
+
expect(PART_SIZE).toBe(5 * 1024 * 1024);
|
|
22
|
+
});
|
|
23
|
+
it('single file smaller than PART_SIZE fits in 1 part', () => {
|
|
24
|
+
const fileSize = 1 * 1024 * 1024; // 1 MB
|
|
25
|
+
const totalParts = Math.ceil(fileSize / PART_SIZE);
|
|
26
|
+
expect(totalParts).toBe(1);
|
|
27
|
+
const lastPartSize = fileSize - (totalParts - 1) * PART_SIZE;
|
|
28
|
+
expect(lastPartSize).toBe(fileSize);
|
|
29
|
+
});
|
|
30
|
+
it('exactly 5 MB file produces 1 part', () => {
|
|
31
|
+
const fileSize = PART_SIZE;
|
|
32
|
+
expect(Math.ceil(fileSize / PART_SIZE)).toBe(1);
|
|
33
|
+
});
|
|
34
|
+
it('5 MB + 1 byte produces 2 parts', () => {
|
|
35
|
+
const fileSize = PART_SIZE + 1;
|
|
36
|
+
expect(Math.ceil(fileSize / PART_SIZE)).toBe(2);
|
|
37
|
+
});
|
|
38
|
+
it('100 MB file produces 20 parts of 5 MB each', () => {
|
|
39
|
+
const fileSize = 100 * 1024 * 1024;
|
|
40
|
+
const totalParts = Math.ceil(fileSize / PART_SIZE);
|
|
41
|
+
expect(totalParts).toBe(20);
|
|
42
|
+
// Each part is exactly PART_SIZE
|
|
43
|
+
for (let i = 1; i <= totalParts; i++) {
|
|
44
|
+
const offset = (i - 1) * PART_SIZE;
|
|
45
|
+
const chunkSize = Math.min(PART_SIZE, fileSize - offset);
|
|
46
|
+
expect(chunkSize).toBe(PART_SIZE);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
it('101 MB file produces 21 parts, last part is 1 MB', () => {
|
|
50
|
+
const fileSize = 101 * 1024 * 1024;
|
|
51
|
+
const totalParts = Math.ceil(fileSize / PART_SIZE);
|
|
52
|
+
expect(totalParts).toBe(21);
|
|
53
|
+
const lastOffset = (totalParts - 1) * PART_SIZE;
|
|
54
|
+
const lastPartSize = fileSize - lastOffset;
|
|
55
|
+
expect(lastPartSize).toBe(1 * 1024 * 1024);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
// ── Resume file serialization/deserialization ─────────────────────────────────
|
|
59
|
+
describe('resume state read/write', () => {
|
|
60
|
+
let tmpDir;
|
|
61
|
+
let resumePath;
|
|
62
|
+
beforeEach(() => {
|
|
63
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tos-upload-test-'));
|
|
64
|
+
resumePath = path.join(tmpDir, 'resume.json');
|
|
65
|
+
});
|
|
66
|
+
afterEach(() => {
|
|
67
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
68
|
+
});
|
|
69
|
+
it('saves and loads resume state correctly', () => {
|
|
70
|
+
const state = {
|
|
71
|
+
uploadId: 'test-upload-id-123',
|
|
72
|
+
fileSize: 12345678,
|
|
73
|
+
parts: [
|
|
74
|
+
{ partNumber: 1, etag: '"abc123"' },
|
|
75
|
+
{ partNumber: 2, etag: '"def456"' },
|
|
76
|
+
],
|
|
77
|
+
};
|
|
78
|
+
saveResumeState(resumePath, state);
|
|
79
|
+
const loaded = loadResumeState(resumePath, 12345678);
|
|
80
|
+
expect(loaded).not.toBeNull();
|
|
81
|
+
expect(loaded.uploadId).toBe('test-upload-id-123');
|
|
82
|
+
expect(loaded.fileSize).toBe(12345678);
|
|
83
|
+
expect(loaded.parts).toHaveLength(2);
|
|
84
|
+
expect(loaded.parts[0]).toEqual({ partNumber: 1, etag: '"abc123"' });
|
|
85
|
+
expect(loaded.parts[1]).toEqual({ partNumber: 2, etag: '"def456"' });
|
|
86
|
+
});
|
|
87
|
+
it('returns null when file does not exist', () => {
|
|
88
|
+
const result = loadResumeState('/nonexistent/path/resume.json', 12345678);
|
|
89
|
+
expect(result).toBeNull();
|
|
90
|
+
});
|
|
91
|
+
it('returns null when fileSize does not match', () => {
|
|
92
|
+
const state = {
|
|
93
|
+
uploadId: 'upload-id',
|
|
94
|
+
fileSize: 100,
|
|
95
|
+
parts: [],
|
|
96
|
+
};
|
|
97
|
+
saveResumeState(resumePath, state);
|
|
98
|
+
// Different file size — should not resume
|
|
99
|
+
const result = loadResumeState(resumePath, 999);
|
|
100
|
+
expect(result).toBeNull();
|
|
101
|
+
});
|
|
102
|
+
it('returns null when JSON is malformed', () => {
|
|
103
|
+
fs.writeFileSync(resumePath, 'not-valid-json', 'utf8');
|
|
104
|
+
const result = loadResumeState(resumePath, 100);
|
|
105
|
+
expect(result).toBeNull();
|
|
106
|
+
});
|
|
107
|
+
it('returns null when uploadId is missing', () => {
|
|
108
|
+
const broken = { fileSize: 100, parts: [] };
|
|
109
|
+
fs.writeFileSync(resumePath, JSON.stringify(broken), 'utf8');
|
|
110
|
+
const result = loadResumeState(resumePath, 100);
|
|
111
|
+
expect(result).toBeNull();
|
|
112
|
+
});
|
|
113
|
+
it('deletes resume file without throwing when file exists', () => {
|
|
114
|
+
fs.writeFileSync(resumePath, '{}', 'utf8');
|
|
115
|
+
expect(() => deleteResumeState(resumePath)).not.toThrow();
|
|
116
|
+
expect(fs.existsSync(resumePath)).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
it('deleteResumeState does not throw when file does not exist', () => {
|
|
119
|
+
expect(() => deleteResumeState('/nonexistent/path/resume.json')).not.toThrow();
|
|
120
|
+
});
|
|
121
|
+
it('saveResumeState creates parent directories if missing', () => {
|
|
122
|
+
const nestedPath = path.join(tmpDir, 'nested', 'deep', 'resume.json');
|
|
123
|
+
const state = { uploadId: 'x', fileSize: 0, parts: [] };
|
|
124
|
+
expect(() => saveResumeState(nestedPath, state)).not.toThrow();
|
|
125
|
+
expect(fs.existsSync(nestedPath)).toBe(true);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
// ── getResumeFilePath ────────────────────────────────────────────────────────
|
|
129
|
+
describe('getResumeFilePath', () => {
|
|
130
|
+
it('returns a path inside ~/.opencli/douyin-resume/', () => {
|
|
131
|
+
const result = getResumeFilePath('/some/video/file.mp4');
|
|
132
|
+
expect(result).toContain('douyin-resume');
|
|
133
|
+
expect(result).toMatch(/\.json$/);
|
|
134
|
+
});
|
|
135
|
+
it('produces same path for same input', () => {
|
|
136
|
+
const a = getResumeFilePath('/video.mp4');
|
|
137
|
+
const b = getResumeFilePath('/video.mp4');
|
|
138
|
+
expect(a).toBe(b);
|
|
139
|
+
});
|
|
140
|
+
it('produces different paths for different inputs', () => {
|
|
141
|
+
const a = getResumeFilePath('/video1.mp4');
|
|
142
|
+
const b = getResumeFilePath('/video2.mp4');
|
|
143
|
+
expect(a).not.toBe(b);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
// ── computeAws4Headers ───────────────────────────────────────────────────────
|
|
147
|
+
describe('computeAws4Headers', () => {
|
|
148
|
+
const mockCredentials = {
|
|
149
|
+
access_key_id: 'AKIAIOSFODNN7EXAMPLE',
|
|
150
|
+
secret_access_key: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
|
|
151
|
+
session_token: 'FQoGZXIvYXdzEJr//////////test-session-token',
|
|
152
|
+
expired_time: Date.now() / 1000 + 3600,
|
|
153
|
+
};
|
|
154
|
+
it('returns Authorization header', () => {
|
|
155
|
+
const headers = computeAws4Headers({
|
|
156
|
+
method: 'PUT',
|
|
157
|
+
url: 'https://tos-cn-i-alisg.volces.com/bucket/object?partNumber=1&uploadId=abc',
|
|
158
|
+
headers: { 'content-type': 'application/octet-stream' },
|
|
159
|
+
body: Buffer.from('hello'),
|
|
160
|
+
credentials: mockCredentials,
|
|
161
|
+
service: 'tos',
|
|
162
|
+
region: 'cn-i-alisg',
|
|
163
|
+
datetime: '20260325T120000Z',
|
|
164
|
+
});
|
|
165
|
+
expect(headers['Authorization']).toMatch(/^AWS4-HMAC-SHA256 Credential=/);
|
|
166
|
+
expect(headers['Authorization']).toContain('AKIAIOSFODNN7EXAMPLE/20260325/cn-i-alisg/tos/aws4_request');
|
|
167
|
+
expect(headers['Authorization']).toContain('SignedHeaders=');
|
|
168
|
+
expect(headers['Authorization']).toContain('Signature=');
|
|
169
|
+
});
|
|
170
|
+
it('includes x-amz-date header', () => {
|
|
171
|
+
const headers = computeAws4Headers({
|
|
172
|
+
method: 'PUT',
|
|
173
|
+
url: 'https://tos-cn-i-alisg.volces.com/bucket/object?partNumber=1&uploadId=abc',
|
|
174
|
+
headers: {},
|
|
175
|
+
body: Buffer.alloc(0),
|
|
176
|
+
credentials: mockCredentials,
|
|
177
|
+
service: 'tos',
|
|
178
|
+
region: 'cn-i-alisg',
|
|
179
|
+
datetime: '20260325T120000Z',
|
|
180
|
+
});
|
|
181
|
+
expect(headers['x-amz-date']).toBe('20260325T120000Z');
|
|
182
|
+
});
|
|
183
|
+
it('includes x-amz-security-token with session token', () => {
|
|
184
|
+
const headers = computeAws4Headers({
|
|
185
|
+
method: 'PUT',
|
|
186
|
+
url: 'https://tos-cn-i-alisg.volces.com/bucket/object',
|
|
187
|
+
headers: {},
|
|
188
|
+
body: '',
|
|
189
|
+
credentials: mockCredentials,
|
|
190
|
+
service: 'tos',
|
|
191
|
+
region: 'cn-i-alisg',
|
|
192
|
+
datetime: '20260325T120000Z',
|
|
193
|
+
});
|
|
194
|
+
expect(headers['x-amz-security-token']).toBe(mockCredentials.session_token);
|
|
195
|
+
});
|
|
196
|
+
it('signed headers list is sorted', () => {
|
|
197
|
+
const headers = computeAws4Headers({
|
|
198
|
+
method: 'POST',
|
|
199
|
+
url: 'https://tos-cn-i-alisg.volces.com/bucket/object?uploadId=abc',
|
|
200
|
+
headers: { 'content-type': 'application/xml' },
|
|
201
|
+
body: '<xml/>',
|
|
202
|
+
credentials: mockCredentials,
|
|
203
|
+
service: 'tos',
|
|
204
|
+
region: 'cn-i-alisg',
|
|
205
|
+
datetime: '20260325T120000Z',
|
|
206
|
+
});
|
|
207
|
+
const authHeader = headers['Authorization'];
|
|
208
|
+
const signedHeadersMatch = authHeader.match(/SignedHeaders=([^,]+)/);
|
|
209
|
+
expect(signedHeadersMatch).not.toBeNull();
|
|
210
|
+
const signedHeadersList = signedHeadersMatch[1].split(';');
|
|
211
|
+
const sorted = [...signedHeadersList].sort((a, b) => a.localeCompare(b));
|
|
212
|
+
expect(signedHeadersList).toEqual(sorted);
|
|
213
|
+
});
|
|
214
|
+
it('produces deterministic signature for same inputs', () => {
|
|
215
|
+
const opts = {
|
|
216
|
+
method: 'PUT',
|
|
217
|
+
url: 'https://tos-cn-i-alisg.volces.com/bucket/key?partNumber=1&uploadId=xyz',
|
|
218
|
+
headers: { 'content-type': 'application/octet-stream' },
|
|
219
|
+
body: Buffer.from('test-data'),
|
|
220
|
+
credentials: mockCredentials,
|
|
221
|
+
service: 'tos',
|
|
222
|
+
region: 'cn-i-alisg',
|
|
223
|
+
datetime: '20260325T120000Z',
|
|
224
|
+
};
|
|
225
|
+
const h1 = computeAws4Headers(opts);
|
|
226
|
+
const h2 = computeAws4Headers(opts);
|
|
227
|
+
expect(h1['Authorization']).toBe(h2['Authorization']);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
import type { IPage } from '../../../types.js';
|
|
9
|
+
import type { TranscodeResult } from './types.js';
|
|
10
|
+
type BrowserFetchFn = (page: IPage, method: 'GET' | 'POST', url: string) => Promise<unknown>;
|
|
11
|
+
/**
|
|
12
|
+
* Lower-level poll function that accepts an injected fetch function.
|
|
13
|
+
* Exported for testability.
|
|
14
|
+
*/
|
|
15
|
+
export declare function pollTranscodeWithFetch(fetchFn: BrowserFetchFn, page: IPage, videoId: string, timeoutMs?: number): Promise<TranscodeResult>;
|
|
16
|
+
/**
|
|
17
|
+
* Poll Douyin's transcode status endpoint until the video is fully transcoded
|
|
18
|
+
* (encode=2) or the timeout expires.
|
|
19
|
+
*
|
|
20
|
+
* @param page - Browser page for making credentialed API calls
|
|
21
|
+
* @param videoId - The video_id returned from the confirm upload step
|
|
22
|
+
* @param timeoutMs - Maximum wait time in ms (default: 300 000 = 5 minutes)
|
|
23
|
+
* @returns TranscodeResult including duration, fps, dimensions, and poster info
|
|
24
|
+
* @throws TimeoutError if transcode does not complete within timeoutMs
|
|
25
|
+
*/
|
|
26
|
+
export declare function pollTranscode(page: IPage, videoId: string, timeoutMs?: number): Promise<TranscodeResult>;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
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
|
+
import { TimeoutError } from '../../../errors.js';
|
|
9
|
+
import { browserFetch } from './browser-fetch.js';
|
|
10
|
+
const POLL_INTERVAL_MS = 3_000;
|
|
11
|
+
const DEFAULT_TIMEOUT_MS = 300_000;
|
|
12
|
+
const TRANSCODE_URL_BASE = 'https://creator.douyin.com/web/api/media/video/transend/';
|
|
13
|
+
/**
|
|
14
|
+
* Lower-level poll function that accepts an injected fetch function.
|
|
15
|
+
* Exported for testability.
|
|
16
|
+
*/
|
|
17
|
+
export async function pollTranscodeWithFetch(fetchFn, page, videoId, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
18
|
+
const url = `${TRANSCODE_URL_BASE}?video_id=${encodeURIComponent(videoId)}&aid=1128`;
|
|
19
|
+
const deadline = Date.now() + timeoutMs;
|
|
20
|
+
while (Date.now() < deadline) {
|
|
21
|
+
const result = (await fetchFn(page, 'GET', url));
|
|
22
|
+
if (result.encode === 2) {
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
// Wait before next poll, but don't exceed the deadline
|
|
26
|
+
const remaining = deadline - Date.now();
|
|
27
|
+
if (remaining <= 0)
|
|
28
|
+
break;
|
|
29
|
+
await new Promise(resolve => setTimeout(resolve, Math.min(POLL_INTERVAL_MS, remaining)));
|
|
30
|
+
}
|
|
31
|
+
throw new TimeoutError(`Douyin transcode for video ${videoId}`, Math.round(timeoutMs / 1000));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Poll Douyin's transcode status endpoint until the video is fully transcoded
|
|
35
|
+
* (encode=2) or the timeout expires.
|
|
36
|
+
*
|
|
37
|
+
* @param page - Browser page for making credentialed API calls
|
|
38
|
+
* @param videoId - The video_id returned from the confirm upload step
|
|
39
|
+
* @param timeoutMs - Maximum wait time in ms (default: 300 000 = 5 minutes)
|
|
40
|
+
* @returns TranscodeResult including duration, fps, dimensions, and poster info
|
|
41
|
+
* @throws TimeoutError if transcode does not complete within timeoutMs
|
|
42
|
+
*/
|
|
43
|
+
export async function pollTranscode(page, videoId, timeoutMs) {
|
|
44
|
+
return pollTranscodeWithFetch(browserFetch, page, videoId, timeoutMs);
|
|
45
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { TimeoutError } from '../../../errors.js';
|
|
3
|
+
import { pollTranscodeWithFetch } from './transcode.js';
|
|
4
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
5
|
+
function makePage() {
|
|
6
|
+
return {
|
|
7
|
+
goto: vi.fn(),
|
|
8
|
+
evaluate: vi.fn(),
|
|
9
|
+
getCookies: vi.fn(),
|
|
10
|
+
snapshot: vi.fn(),
|
|
11
|
+
click: vi.fn(),
|
|
12
|
+
typeText: vi.fn(),
|
|
13
|
+
pressKey: vi.fn(),
|
|
14
|
+
scrollTo: vi.fn(),
|
|
15
|
+
getFormState: vi.fn(),
|
|
16
|
+
wait: vi.fn(),
|
|
17
|
+
tabs: vi.fn(),
|
|
18
|
+
closeTab: vi.fn(),
|
|
19
|
+
newTab: vi.fn(),
|
|
20
|
+
selectTab: vi.fn(),
|
|
21
|
+
networkRequests: vi.fn(),
|
|
22
|
+
consoleMessages: vi.fn(),
|
|
23
|
+
scroll: vi.fn(),
|
|
24
|
+
autoScroll: vi.fn(),
|
|
25
|
+
installInterceptor: vi.fn(),
|
|
26
|
+
getInterceptedRequests: vi.fn(),
|
|
27
|
+
screenshot: vi.fn(),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const COMPLETE_RESULT = {
|
|
31
|
+
encode: 2,
|
|
32
|
+
duration: 30,
|
|
33
|
+
fps: 30,
|
|
34
|
+
height: 1920,
|
|
35
|
+
width: 1080,
|
|
36
|
+
poster_uri: 'tos-cn-i-alisg.volces.com/poster/abc',
|
|
37
|
+
poster_url: 'https://p3-creator.douyinpic.com/poster/abc',
|
|
38
|
+
};
|
|
39
|
+
// ── Tests ─────────────────────────────────────────────────────────────────────
|
|
40
|
+
describe('pollTranscodeWithFetch', () => {
|
|
41
|
+
it('returns TranscodeResult immediately when first response has encode=2', async () => {
|
|
42
|
+
const fetchFn = vi.fn().mockResolvedValue(COMPLETE_RESULT);
|
|
43
|
+
const page = makePage();
|
|
44
|
+
const result = await pollTranscodeWithFetch(fetchFn, page, 'vid_123');
|
|
45
|
+
expect(result).toEqual(COMPLETE_RESULT);
|
|
46
|
+
expect(fetchFn).toHaveBeenCalledOnce();
|
|
47
|
+
const [, method, url] = fetchFn.mock.calls[0];
|
|
48
|
+
expect(method).toBe('GET');
|
|
49
|
+
expect(url).toContain('video_id=vid_123');
|
|
50
|
+
expect(url).toContain('aid=1128');
|
|
51
|
+
});
|
|
52
|
+
it('polls multiple times until encode=2 is received', async () => {
|
|
53
|
+
const pending = { encode: 1, duration: 0, fps: 0, height: 0, width: 0, poster_uri: '', poster_url: '' };
|
|
54
|
+
const fetchFn = vi
|
|
55
|
+
.fn()
|
|
56
|
+
.mockResolvedValueOnce(pending)
|
|
57
|
+
.mockResolvedValueOnce(pending)
|
|
58
|
+
.mockResolvedValueOnce(COMPLETE_RESULT);
|
|
59
|
+
const page = makePage();
|
|
60
|
+
// Use a large timeoutMs so it doesn't expire, but override POLL_INTERVAL via
|
|
61
|
+
// a short timeout knowing we'll get 3 calls quickly with mocked promises.
|
|
62
|
+
// Since pollTranscodeWithFetch uses setTimeout for 3s between polls, we need
|
|
63
|
+
// to mock timers to keep tests fast.
|
|
64
|
+
vi.useFakeTimers();
|
|
65
|
+
const resultPromise = pollTranscodeWithFetch(fetchFn, page, 'vid_456', 60_000);
|
|
66
|
+
// Advance timers for each pending poll cycle
|
|
67
|
+
await vi.runAllTimersAsync();
|
|
68
|
+
const result = await resultPromise;
|
|
69
|
+
expect(result).toEqual(COMPLETE_RESULT);
|
|
70
|
+
expect(fetchFn).toHaveBeenCalledTimes(3);
|
|
71
|
+
vi.useRealTimers();
|
|
72
|
+
});
|
|
73
|
+
it('throws TimeoutError when encode never becomes 2 within timeoutMs', async () => {
|
|
74
|
+
const pending = { encode: 1, duration: 0, fps: 0, height: 0, width: 0, poster_uri: '', poster_url: '' };
|
|
75
|
+
const fetchFn = vi.fn().mockResolvedValue(pending);
|
|
76
|
+
const page = makePage();
|
|
77
|
+
vi.useFakeTimers();
|
|
78
|
+
const resultPromise = pollTranscodeWithFetch(fetchFn, page, 'vid_789', 5_000);
|
|
79
|
+
// Suppress unhandled-rejection so vitest doesn't flag it
|
|
80
|
+
resultPromise.catch(() => undefined);
|
|
81
|
+
// Advance time past the 5s timeout
|
|
82
|
+
await vi.runAllTimersAsync();
|
|
83
|
+
await expect(resultPromise).rejects.toBeInstanceOf(TimeoutError);
|
|
84
|
+
vi.useRealTimers();
|
|
85
|
+
});
|
|
86
|
+
it('URL encodes video_id in the request URL', async () => {
|
|
87
|
+
const fetchFn = vi.fn().mockResolvedValue(COMPLETE_RESULT);
|
|
88
|
+
const page = makePage();
|
|
89
|
+
await pollTranscodeWithFetch(fetchFn, page, 'vid with spaces');
|
|
90
|
+
const [, , url] = fetchFn.mock.calls[0];
|
|
91
|
+
expect(url).toContain('video_id=vid%20with%20spaces');
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface Sts2Credentials {
|
|
2
|
+
access_key_id: string;
|
|
3
|
+
secret_access_key: string;
|
|
4
|
+
session_token: string;
|
|
5
|
+
expired_time: number;
|
|
6
|
+
}
|
|
7
|
+
export interface TosUploadInfo {
|
|
8
|
+
tos_upload_url: string;
|
|
9
|
+
/** Pre-computed Authorization header value returned by ApplyVideoUpload (StoreInfos[0].Auth) */
|
|
10
|
+
auth: string;
|
|
11
|
+
video_id: string;
|
|
12
|
+
}
|
|
13
|
+
export interface TranscodeResult {
|
|
14
|
+
encode: number;
|
|
15
|
+
duration: number;
|
|
16
|
+
fps: number;
|
|
17
|
+
height: number;
|
|
18
|
+
width: number;
|
|
19
|
+
poster_uri: string;
|
|
20
|
+
poster_url: string;
|
|
21
|
+
}
|
|
22
|
+
export interface PublishResult {
|
|
23
|
+
aweme_id: string;
|
|
24
|
+
url: string;
|
|
25
|
+
publish_time: number;
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { cli, Strategy } from '../../registry.js';
|
|
2
|
+
import { browserFetch } from './_shared/browser-fetch.js';
|
|
3
|
+
cli({
|
|
4
|
+
site: 'douyin',
|
|
5
|
+
name: 'activities',
|
|
6
|
+
description: '官方活动列表',
|
|
7
|
+
domain: 'creator.douyin.com',
|
|
8
|
+
strategy: Strategy.COOKIE,
|
|
9
|
+
args: [],
|
|
10
|
+
columns: ['activity_id', 'title', 'end_time'],
|
|
11
|
+
func: async (page, _kwargs) => {
|
|
12
|
+
const url = 'https://creator.douyin.com/web/api/media/activity/get/?aid=1128';
|
|
13
|
+
const res = await browserFetch(page, 'GET', url);
|
|
14
|
+
return (res.activity_list ?? []).map(a => ({
|
|
15
|
+
activity_id: a.activity_id,
|
|
16
|
+
title: a.title,
|
|
17
|
+
end_time: new Date(a.end_time * 1000).toLocaleString('zh-CN', { timeZone: 'Asia/Tokyo' }),
|
|
18
|
+
}));
|
|
19
|
+
},
|
|
20
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import './activities.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { getRegistry } from '../../registry.js';
|
|
3
|
+
import './activities.js';
|
|
4
|
+
describe('douyin activities registration', () => {
|
|
5
|
+
it('registers the activities command', () => {
|
|
6
|
+
const registry = getRegistry();
|
|
7
|
+
const cmd = [...registry.values()].find(c => c.site === 'douyin' && c.name === 'activities');
|
|
8
|
+
expect(cmd).toBeDefined();
|
|
9
|
+
});
|
|
10
|
+
it('has expected columns', () => {
|
|
11
|
+
const registry = getRegistry();
|
|
12
|
+
const cmd = [...registry.values()].find(c => c.site === 'douyin' && c.name === 'activities');
|
|
13
|
+
expect(cmd?.columns).toContain('activity_id');
|
|
14
|
+
expect(cmd?.columns).toContain('title');
|
|
15
|
+
expect(cmd?.columns).toContain('end_time');
|
|
16
|
+
});
|
|
17
|
+
it('uses COOKIE strategy', () => {
|
|
18
|
+
const registry = getRegistry();
|
|
19
|
+
const cmd = [...registry.values()].find(c => c.site === 'douyin' && c.name === 'activities');
|
|
20
|
+
expect(cmd?.strategy).toBe('cookie');
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { cli, Strategy } from '../../registry.js';
|
|
2
|
+
import { browserFetch } from './_shared/browser-fetch.js';
|
|
3
|
+
cli({
|
|
4
|
+
site: 'douyin',
|
|
5
|
+
name: 'collections',
|
|
6
|
+
description: '合集列表',
|
|
7
|
+
domain: 'creator.douyin.com',
|
|
8
|
+
strategy: Strategy.COOKIE,
|
|
9
|
+
args: [
|
|
10
|
+
{ name: 'limit', type: 'int', default: 20 },
|
|
11
|
+
],
|
|
12
|
+
columns: ['mix_id', 'name', 'item_count'],
|
|
13
|
+
func: async (page, kwargs) => {
|
|
14
|
+
const url = `https://creator.douyin.com/web/api/mix/list/?aid=1128&count=${kwargs.limit}`;
|
|
15
|
+
const res = await browserFetch(page, 'GET', url);
|
|
16
|
+
return (res.mix_list ?? []).map(m => ({
|
|
17
|
+
mix_id: m.mix_id,
|
|
18
|
+
name: m.mix_name,
|
|
19
|
+
item_count: m.item_count,
|
|
20
|
+
}));
|
|
21
|
+
},
|
|
22
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import './collections.js';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { getRegistry } from '../../registry.js';
|
|
3
|
+
import './collections.js';
|
|
4
|
+
describe('douyin collections registration', () => {
|
|
5
|
+
it('registers the collections command', () => {
|
|
6
|
+
const registry = getRegistry();
|
|
7
|
+
const cmd = [...registry.values()].find(c => c.site === 'douyin' && c.name === 'collections');
|
|
8
|
+
expect(cmd).toBeDefined();
|
|
9
|
+
expect(cmd?.args.some(a => a.name === 'limit')).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
it('has expected columns', () => {
|
|
12
|
+
const registry = getRegistry();
|
|
13
|
+
const cmd = [...registry.values()].find(c => c.site === 'douyin' && c.name === 'collections');
|
|
14
|
+
expect(cmd?.columns).toContain('mix_id');
|
|
15
|
+
expect(cmd?.columns).toContain('name');
|
|
16
|
+
expect(cmd?.columns).toContain('item_count');
|
|
17
|
+
});
|
|
18
|
+
it('uses COOKIE strategy', () => {
|
|
19
|
+
const registry = getRegistry();
|
|
20
|
+
const cmd = [...registry.values()].find(c => c.site === 'douyin' && c.name === 'collections');
|
|
21
|
+
expect(cmd?.strategy).toBe('cookie');
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { cli, Strategy } from '../../registry.js';
|
|
2
|
+
import { browserFetch } from './_shared/browser-fetch.js';
|
|
3
|
+
cli({
|
|
4
|
+
site: 'douyin',
|
|
5
|
+
name: 'delete',
|
|
6
|
+
description: '删除作品',
|
|
7
|
+
domain: 'creator.douyin.com',
|
|
8
|
+
strategy: Strategy.COOKIE,
|
|
9
|
+
args: [
|
|
10
|
+
{ name: 'aweme_id', required: true, positional: true, help: '作品 ID' },
|
|
11
|
+
],
|
|
12
|
+
columns: ['status'],
|
|
13
|
+
func: async (page, kwargs) => {
|
|
14
|
+
const url = 'https://creator.douyin.com/web/api/media/aweme/delete/?aid=1128';
|
|
15
|
+
await browserFetch(page, 'POST', url, { body: { aweme_id: kwargs.aweme_id } });
|
|
16
|
+
return [{ status: `✅ 已删除 ${kwargs.aweme_id}` }];
|
|
17
|
+
},
|
|
18
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import './delete.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { getRegistry } from '../../registry.js';
|
|
3
|
+
import './delete.js';
|
|
4
|
+
describe('douyin delete registration', () => {
|
|
5
|
+
it('registers the delete command', () => {
|
|
6
|
+
const registry = getRegistry();
|
|
7
|
+
const values = [...registry.values()];
|
|
8
|
+
const cmd = values.find(c => c.site === 'douyin' && c.name === 'delete');
|
|
9
|
+
expect(cmd).toBeDefined();
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Douyin draft — 6-phase pipeline for saving video as draft.
|
|
3
|
+
*
|
|
4
|
+
* Phases:
|
|
5
|
+
* 1. STS2 credentials
|
|
6
|
+
* 2. Apply TOS upload URL
|
|
7
|
+
* 3. TOS multipart upload
|
|
8
|
+
* 4. Cover upload (optional, via ImageX)
|
|
9
|
+
* 5. Enable video
|
|
10
|
+
* 6. Poll transcode
|
|
11
|
+
* 7. (skipped — no safety check for drafts)
|
|
12
|
+
* 8. create_v2 with is_draft: 1
|
|
13
|
+
*/
|
|
14
|
+
export {};
|