@sovovs/bycli 1.8.0 → 2.0.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/cli-manifest.json +0 -61
- package/dist/src/adapter-shadow.js +2 -2
- package/dist/src/browser/analyze.d.ts +10 -99
- package/dist/src/browser/analyze.js +10 -226
- package/dist/src/browser/cdp.js +64 -7
- package/dist/src/browser/navigation-guard.d.ts +78 -0
- package/dist/src/browser/navigation-guard.js +142 -0
- package/dist/src/browser/navigation-guard.test.d.ts +1 -0
- package/dist/src/browser/url-policy.d.ts +89 -0
- package/dist/src/browser/url-policy.js +311 -0
- package/dist/src/browser/url-policy.test.d.ts +1 -0
- package/dist/src/browser/verify-fixture.js +2 -2
- package/dist/src/cli.js +10 -13
- package/dist/src/config-paths.d.ts +19 -0
- package/dist/src/config-paths.js +31 -0
- package/dist/src/daemon.js +240 -0
- package/dist/src/discovery.d.ts +5 -3
- package/dist/src/discovery.js +8 -6
- package/dist/src/execution.js +8 -3
- package/dist/src/main.js +18 -2
- package/dist/src/plugin.js +11 -8
- package/dist/src/recorder/highlevel/analyze.d.ts +29 -0
- package/dist/src/recorder/highlevel/analyze.js +91 -0
- package/dist/src/recorder/highlevel/init.d.ts +125 -0
- package/dist/src/recorder/highlevel/init.js +279 -0
- package/dist/src/recorder/highlevel/init.test.d.ts +1 -0
- package/dist/src/recorder/highlevel/verify.d.ts +73 -0
- package/dist/src/recorder/highlevel/verify.js +74 -0
- package/dist/src/recorder/http/analyze-runner.d.ts +28 -0
- package/dist/src/recorder/http/analyze-runner.js +78 -0
- package/dist/src/recorder/http/analyze-runner.test.d.ts +1 -0
- package/dist/src/recorder/http/handlers.d.ts +51 -0
- package/dist/src/recorder/http/handlers.js +219 -0
- package/dist/src/recorder/http/wrapper-config.d.ts +20 -0
- package/dist/src/recorder/http/wrapper-config.js +82 -0
- package/dist/src/recorder/http/wrapper-envelope.d.ts +31 -0
- package/dist/src/recorder/http/wrapper-envelope.js +51 -0
- package/dist/src/recorder/http/wrapper-gates.d.ts +16 -0
- package/dist/src/recorder/http/wrapper-gates.js +28 -0
- package/dist/src/recorder/http/wrapper-metrics.d.ts +1 -0
- package/dist/src/recorder/http/wrapper-metrics.js +5 -0
- package/dist/src/recorder/http/wrapper-registry.d.ts +50 -0
- package/dist/src/recorder/http/wrapper-registry.js +98 -0
- package/dist/src/recorder/http/wrapper-server.d.ts +59 -0
- package/dist/src/recorder/http/wrapper-server.js +240 -0
- package/dist/src/recorder/http/wrapper.test.d.ts +1 -0
- package/dist/src/recorder/observability/logger.d.ts +5 -0
- package/dist/src/recorder/observability/logger.js +13 -0
- package/dist/src/recorder/runner/config.d.ts +34 -0
- package/dist/src/recorder/runner/config.js +84 -0
- package/dist/src/recorder/runner/config.test.d.ts +1 -0
- package/dist/src/recorder/runner/reap.d.ts +79 -0
- package/dist/src/recorder/runner/reap.js +230 -0
- package/dist/src/recorder/runner/reap.test.d.ts +1 -0
- package/dist/src/recorder/runner/runner-port.d.ts +95 -0
- package/dist/src/recorder/runner/runner-port.js +518 -0
- package/dist/src/recorder/runner/runner-port.test.d.ts +1 -0
- package/dist/src/recorder/runner/session-keys.d.ts +28 -0
- package/dist/src/recorder/runner/session-keys.js +72 -0
- package/dist/src/recorder/runner/session-keys.test.d.ts +1 -0
- package/dist/src/recorder/runner/verify-runner-main.d.ts +95 -0
- package/dist/src/recorder/runner/verify-runner-main.js +275 -0
- package/dist/src/recorder/runner/verify-runner-main.test.d.ts +1 -0
- package/package.json +8 -5
- package/scripts/m1-daemon-navigate-drive.mjs +138 -0
- package/scripts/m1-extension-spike.mjs +237 -0
- package/scripts/m1-live-spike.mjs +156 -0
- package/scripts/m6b-connect-back-spike.sh +31 -0
- package/scripts/recorder.sh +186 -0
- package/clis/12306/utils.test.js +0 -331
- package/clis/1688/assets.test.js +0 -39
- package/clis/1688/download.test.js +0 -31
- package/clis/1688/item.test.js +0 -67
- package/clis/1688/search.test.js +0 -75
- package/clis/1688/shared.test.js +0 -57
- package/clis/1688/store.test.js +0 -62
- package/clis/36kr/article.test.js +0 -46
- package/clis/36kr/hot.test.js +0 -15
- package/clis/36kr/news.test.js +0 -85
- package/clis/51job/utils.test.js +0 -69
- package/clis/_atlassian/shared.test.js +0 -170
- package/clis/aibase/news.test.js +0 -59
- package/clis/amazon/bestsellers.test.js +0 -29
- package/clis/amazon/discussion.test.js +0 -124
- package/clis/amazon/offer.test.js +0 -29
- package/clis/amazon/product.test.js +0 -24
- package/clis/amazon/rankings.test.js +0 -41
- package/clis/amazon/search.test.js +0 -22
- package/clis/amazon/shared.test.js +0 -44
- package/clis/apple-podcasts/commands.test.js +0 -119
- package/clis/apple-podcasts/utils.test.js +0 -57
- package/clis/arxiv/arxiv.test.js +0 -112
- package/clis/baidu-scholar/search.test.js +0 -23
- package/clis/barchart/greeks.test.js +0 -138
- package/clis/bilibili/comment.test.js +0 -153
- package/clis/bilibili/comments.test.js +0 -142
- package/clis/bilibili/dynamic.test.js +0 -68
- package/clis/bilibili/hot.test.js +0 -17
- package/clis/bilibili/subtitle.test.js +0 -196
- package/clis/bilibili/summary.test.js +0 -210
- package/clis/bilibili/utils.test.js +0 -61
- package/clis/bilibili/video.test.js +0 -132
- package/clis/binance/commands.test.js +0 -70
- package/clis/bloomberg/utils.test.js +0 -129
- package/clis/booking/booking.test.js +0 -356
- package/clis/boss/chatlist.test.js +0 -211
- package/clis/boss/chatmsg.test.js +0 -230
- package/clis/boss/search.test.js +0 -78
- package/clis/boss/utils.test.js +0 -34
- package/clis/brave/search.test.js +0 -76
- package/clis/chaoxing/utils.test.js +0 -45
- package/clis/chatgpt/commands.test.js +0 -45
- package/clis/chatgpt/envelope.test.js +0 -108
- package/clis/chatgpt/image.test.js +0 -188
- package/clis/chatgpt/utils.test.js +0 -330
- package/clis/chatgpt-app/ax.test.js +0 -35
- package/clis/chatwise/composer.test.js +0 -186
- package/clis/chess/analyze.test.js +0 -79
- package/clis/chess/game.test.js +0 -178
- package/clis/chess/games.test.js +0 -164
- package/clis/chess/stats.test.js +0 -79
- package/clis/chess/utils.test.js +0 -230
- package/clis/claude/ask.test.js +0 -338
- package/clis/claude/commands.test.js +0 -118
- package/clis/claude/utils.test.js +0 -148
- package/clis/cnki/search.test.js +0 -18
- package/clis/codex/sidebar.test.js +0 -329
- package/clis/coingecko/coingecko.test.js +0 -109
- package/clis/confluence/commands.test.js +0 -195
- package/clis/coupang/coupang.test.js +0 -159
- package/clis/coupang/utils.test.js +0 -62
- package/clis/ctrip/ctrip.test.js +0 -719
- package/clis/dblp/dblp.test.js +0 -397
- package/clis/deepseek/ask.test.js +0 -312
- package/clis/deepseek/detail.test.js +0 -81
- package/clis/deepseek/send.test.js +0 -107
- package/clis/deepseek/utils.test.js +0 -370
- package/clis/defillama/defillama.test.js +0 -99
- package/clis/devto/devto.test.js +0 -236
- package/clis/dianping/dianping.test.js +0 -578
- package/clis/douban/download.test.js +0 -170
- package/clis/douban/movie-hot.test.js +0 -14
- package/clis/douban/search.test.js +0 -11
- package/clis/douban/subject.test.js +0 -11
- package/clis/douban/utils.test.js +0 -438
- package/clis/doubao/detail.test.js +0 -42
- package/clis/doubao/history.test.js +0 -37
- package/clis/doubao/utils.test.js +0 -344
- package/clis/douyin/_shared/browser-fetch.test.js +0 -64
- package/clis/douyin/_shared/creation-id.test.js +0 -22
- package/clis/douyin/_shared/imagex-upload.test.js +0 -87
- package/clis/douyin/_shared/sts2.test.js +0 -27
- package/clis/douyin/_shared/text-extra.test.js +0 -37
- package/clis/douyin/_shared/timing.test.js +0 -28
- package/clis/douyin/_shared/tos-upload-short-read.test.js +0 -83
- package/clis/douyin/_shared/tos-upload.test.js +0 -229
- package/clis/douyin/_shared/transcode.test.js +0 -91
- package/clis/douyin/_shared/vod-upload.test.js +0 -38
- package/clis/douyin/activities.test.js +0 -55
- package/clis/douyin/collections.test.js +0 -45
- package/clis/douyin/delete.test.js +0 -100
- package/clis/douyin/draft.test.js +0 -335
- package/clis/douyin/drafts.test.js +0 -11
- package/clis/douyin/hashtag.test.js +0 -171
- package/clis/douyin/location.test.js +0 -23
- package/clis/douyin/profile.test.js +0 -46
- package/clis/douyin/publish-upload-id.test.js +0 -170
- package/clis/douyin/publish.test.js +0 -38
- package/clis/douyin/stats.test.js +0 -22
- package/clis/douyin/update.test.js +0 -11
- package/clis/douyin/user-videos.test.js +0 -151
- package/clis/douyin/videos.test.js +0 -54
- package/clis/duckduckgo/search.test.js +0 -128
- package/clis/duckduckgo/suggest.test.js +0 -66
- package/clis/eastmoney/hot-rank.test.js +0 -59
- package/clis/endoflife/endoflife.test.js +0 -51
- package/clis/facebook/feed.test.js +0 -169
- package/clis/facebook/marketplace.test.js +0 -91
- package/clis/facebook/notifications.test.js +0 -458
- package/clis/facebook/search.test.js +0 -55
- package/clis/flathub/flathub.test.js +0 -90
- package/clis/flomo/memos.test.js +0 -144
- package/clis/gemini/ask.test.js +0 -100
- package/clis/gemini/deep-research-result.test.js +0 -155
- package/clis/gemini/deep-research.test.js +0 -181
- package/clis/gemini/reply-state.test.js +0 -671
- package/clis/gemini/utils.test.js +0 -198
- package/clis/geogebra/geogebra.test.js +0 -175
- package/clis/gitee/search.test.js +0 -65
- package/clis/gitee/user.test.js +0 -63
- package/clis/google/utils.test.js +0 -75
- package/clis/google-scholar/cite.test.js +0 -47
- package/clis/google-scholar/profile.test.js +0 -49
- package/clis/google-scholar/search.test.js +0 -71
- package/clis/goproxy/goproxy.test.js +0 -103
- package/clis/gov-law/commands.test.js +0 -39
- package/clis/gov-policy/gov-policy.test.js +0 -224
- package/clis/grok/ask.test.js +0 -29
- package/clis/grok/utils.test.js +0 -103
- package/clis/hackernews/hackernews.test.js +0 -132
- package/clis/hf/hf.test.js +0 -16
- package/clis/hupu/hot.test.js +0 -224
- package/clis/imdb/utils.test.js +0 -88
- package/clis/indeed/indeed.test.js +0 -375
- package/clis/instagram/_shared/private-publish.test.js +0 -705
- package/clis/instagram/_shared/protocol-capture.test.js +0 -114
- package/clis/instagram/download.test.js +0 -118
- package/clis/instagram/note.test.js +0 -53
- package/clis/instagram/post.test.js +0 -1647
- package/clis/instagram/reel.test.js +0 -167
- package/clis/instagram/story.test.js +0 -134
- package/clis/jd/commands.test.js +0 -41
- package/clis/jd/item.test.js +0 -354
- package/clis/jianyu/search.test.js +0 -153
- package/clis/jianyu/shared/procurement-contract.test.js +0 -72
- package/clis/jianyu/shared/procurement-detail.test.js +0 -84
- package/clis/jike/read.test.js +0 -86
- package/clis/jira/commands.test.js +0 -287
- package/clis/juejin/search.js +0 -123
- package/clis/juejin/search.test.js +0 -98
- package/clis/lesswrong/frontpage.test.js +0 -37
- package/clis/lichess/lichess.test.js +0 -85
- package/clis/linkedin/connect.test.js +0 -213
- package/clis/linkedin/inbox.test.js +0 -152
- package/clis/linkedin/job-detail.test.js +0 -38
- package/clis/linkedin/jobs-preferences.test.js +0 -43
- package/clis/linkedin/people-search.test.js +0 -216
- package/clis/linkedin/post-analytics.test.js +0 -40
- package/clis/linkedin/posts.test.js +0 -40
- package/clis/linkedin/profile-analytics.test.js +0 -67
- package/clis/linkedin/profile-experience.test.js +0 -152
- package/clis/linkedin/profile-projects.test.js +0 -111
- package/clis/linkedin/profile-read.test.js +0 -77
- package/clis/linkedin/safe-send.test.js +0 -204
- package/clis/linkedin/salesnav-inbox.test.js +0 -113
- package/clis/linkedin/salesnav-message.test.js +0 -172
- package/clis/linkedin/salesnav-search.test.js +0 -76
- package/clis/linkedin/salesnav-thread.test.js +0 -79
- package/clis/linkedin/search.test.js +0 -222
- package/clis/linkedin/sent-invitations.test.js +0 -62
- package/clis/linkedin/services-read.test.js +0 -105
- package/clis/linkedin/thread-snapshot.test.js +0 -89
- package/clis/linkedin/timeline.test.js +0 -81
- package/clis/linkedin-learning/course.test.js +0 -114
- package/clis/linkedin-learning/search.test.js +0 -144
- package/clis/linkedin-learning/trending.test.js +0 -123
- package/clis/linux-do/feed.test.js +0 -153
- package/clis/linux-do/topic-content.test.js +0 -59
- package/clis/lobsters/lobsters.test.js +0 -169
- package/clis/notebooklm/add-source.test.js +0 -97
- package/clis/notebooklm/compat.test.js +0 -16
- package/clis/notebooklm/create.test.js +0 -58
- package/clis/notebooklm/generate-audio.test.js +0 -63
- package/clis/notebooklm/generate-slides.test.js +0 -75
- package/clis/notebooklm/history.test.js +0 -58
- package/clis/notebooklm/note-list.test.js +0 -56
- package/clis/notebooklm/notes-get.test.js +0 -72
- package/clis/notebooklm/open.test.js +0 -63
- package/clis/notebooklm/rpc.test.js +0 -131
- package/clis/notebooklm/source-fulltext.test.js +0 -106
- package/clis/notebooklm/source-get.test.js +0 -84
- package/clis/notebooklm/source-guide.test.js +0 -104
- package/clis/notebooklm/summary.test.js +0 -78
- package/clis/notebooklm/utils.test.js +0 -449
- package/clis/notebooklm/write-note.test.js +0 -70
- package/clis/nuget/nuget.test.js +0 -111
- package/clis/oeis/oeis.test.js +0 -88
- package/clis/ones/task-helpers.test.js +0 -12
- package/clis/ones/worklog.test.js +0 -20
- package/clis/openfda/openfda.test.js +0 -114
- package/clis/openreview/openreview.test.js +0 -427
- package/clis/osv/osv.test.js +0 -97
- package/clis/paperreview/commands.test.js +0 -243
- package/clis/paperreview/utils.test.js +0 -49
- package/clis/pixiv/download.test.js +0 -87
- package/clis/pixiv/illusts.test.js +0 -99
- package/clis/pixiv/search.test.js +0 -83
- package/clis/powerchina/search.test.js +0 -93
- package/clis/producthunt/utils.test.js +0 -64
- package/clis/pubmed/pubmed.test.js +0 -276
- package/clis/quark/utils.test.js +0 -58
- package/clis/qwen/utils.test.js +0 -45
- package/clis/reddit/extract-media.test.js +0 -149
- package/clis/reddit/frontpage.test.js +0 -34
- package/clis/reddit/home.test.js +0 -170
- package/clis/reddit/hot.test.js +0 -32
- package/clis/reddit/popular.test.js +0 -26
- package/clis/reddit/read.test.js +0 -335
- package/clis/reddit/reply.test.js +0 -89
- package/clis/reddit/search.test.js +0 -26
- package/clis/reddit/subreddit-info.test.js +0 -163
- package/clis/reddit/subreddit.test.js +0 -31
- package/clis/reddit/subscribed.test.js +0 -168
- package/clis/reddit/whoami.test.js +0 -105
- package/clis/rednote/navigation.test.js +0 -26
- package/clis/rednote/rednote.test.js +0 -222
- package/clis/rest-countries/rest-countries.test.js +0 -83
- package/clis/reuters/reuters.test.js +0 -299
- package/clis/rfc/rfc.test.js +0 -74
- package/clis/sinafinance/stock.test.js +0 -59
- package/clis/spotify/utils.test.js +0 -67
- package/clis/stackoverflow/stackoverflow.test.js +0 -404
- package/clis/steam/steam.test.js +0 -46
- package/clis/substack/utils.test.js +0 -44
- package/clis/suno/commands.test.js +0 -188
- package/clis/suno/download.test.js +0 -151
- package/clis/suno/generate.test.js +0 -252
- package/clis/suno/utils.test.js +0 -329
- package/clis/taobao/commands.test.js +0 -41
- package/clis/tdx/hot-rank.test.js +0 -59
- package/clis/ths/hot-rank.test.js +0 -64
- package/clis/tieba/commands.test.js +0 -79
- package/clis/tieba/utils.test.js +0 -290
- package/clis/tiktok/creator-videos.test.js +0 -113
- package/clis/tiktok/refactor.test.js +0 -389
- package/clis/tiktok/write-refactor.test.js +0 -370
- package/clis/toutiao/toutiao.test.js +0 -378
- package/clis/tvmaze/tvmaze.test.js +0 -93
- package/clis/twitter/bookmark-folder.test.js +0 -395
- package/clis/twitter/bookmark-folders.test.js +0 -152
- package/clis/twitter/bookmark.test.js +0 -74
- package/clis/twitter/bookmarks.test.js +0 -205
- package/clis/twitter/delete.test.js +0 -85
- package/clis/twitter/device-follow.test.js +0 -287
- package/clis/twitter/download.test.js +0 -457
- package/clis/twitter/followers.test.js +0 -62
- package/clis/twitter/following.test.js +0 -338
- package/clis/twitter/hide-reply.test.js +0 -76
- package/clis/twitter/like.test.js +0 -73
- package/clis/twitter/likes.test.js +0 -220
- package/clis/twitter/list-add.test.js +0 -133
- package/clis/twitter/list-create.test.js +0 -169
- package/clis/twitter/list-remove.test.js +0 -110
- package/clis/twitter/list-tweets.test.js +0 -196
- package/clis/twitter/lists.test.js +0 -253
- package/clis/twitter/post.test.js +0 -258
- package/clis/twitter/profile.test.js +0 -39
- package/clis/twitter/quote.test.js +0 -194
- package/clis/twitter/reply.test.js +0 -237
- package/clis/twitter/retweet.test.js +0 -73
- package/clis/twitter/search.test.js +0 -349
- package/clis/twitter/shared.test.js +0 -794
- package/clis/twitter/thread.test.js +0 -58
- package/clis/twitter/timeline.test.js +0 -104
- package/clis/twitter/trending.test.js +0 -15
- package/clis/twitter/tweets.test.js +0 -362
- package/clis/twitter/unbookmark.test.js +0 -73
- package/clis/twitter/unlike.test.js +0 -75
- package/clis/twitter/unretweet.test.js +0 -73
- package/clis/twitter/utils.test.js +0 -169
- package/clis/uisdc/news.test.js +0 -66
- package/clis/uiverse/_shared.test.js +0 -62
- package/clis/uiverse/navigation.test.js +0 -12
- package/clis/upwork/upwork.test.js +0 -566
- package/clis/wanfang/search.test.js +0 -23
- package/clis/web/read.test.js +0 -392
- package/clis/weibo/delete.test.js +0 -94
- package/clis/weibo/envelope.test.js +0 -85
- package/clis/weibo/favorites.test.js +0 -114
- package/clis/weibo/publish.test.js +0 -192
- package/clis/weibo/user-posts.test.js +0 -92
- package/clis/weibo/utils.test.js +0 -36
- package/clis/weixin/drafts.test.js +0 -69
- package/clis/weixin/search.test.js +0 -227
- package/clis/weread/ai-outline.test.js +0 -83
- package/clis/weread/book-search.test.js +0 -242
- package/clis/weread/commands.test.js +0 -398
- package/clis/weread/private-api-regression.test.js +0 -369
- package/clis/weread/search-regression.test.js +0 -494
- package/clis/weread/utils.test.js +0 -128
- package/clis/weread-official/commands.test.js +0 -385
- package/clis/weread-official/utils.test.js +0 -242
- package/clis/wikidata/wikidata.test.js +0 -83
- package/clis/wikipedia/trending.test.js +0 -57
- package/clis/wttr/wttr.test.js +0 -84
- package/clis/xianyu/chat.test.js +0 -84
- package/clis/xianyu/im.test.js +0 -253
- package/clis/xianyu/item.test.js +0 -56
- package/clis/xianyu/publish.test.js +0 -220
- package/clis/xianyu/search.test.js +0 -17
- package/clis/xiaoe/xiaoe.test.js +0 -486
- package/clis/xiaohongshu/comments.test.js +0 -258
- package/clis/xiaohongshu/creator-note-detail.test.js +0 -451
- package/clis/xiaohongshu/creator-notes-summary.test.js +0 -94
- package/clis/xiaohongshu/creator-notes.test.js +0 -281
- package/clis/xiaohongshu/creator-stats.test.js +0 -24
- package/clis/xiaohongshu/delete-note.test.js +0 -172
- package/clis/xiaohongshu/download.test.js +0 -316
- package/clis/xiaohongshu/navigation.test.js +0 -34
- package/clis/xiaohongshu/note.test.js +0 -249
- package/clis/xiaohongshu/publish.test.js +0 -608
- package/clis/xiaohongshu/search.test.js +0 -325
- package/clis/xiaohongshu/user-helpers.test.js +0 -160
- package/clis/xiaoyuzhou/auth.test.js +0 -124
- package/clis/xiaoyuzhou/download.test.js +0 -135
- package/clis/xiaoyuzhou/podcast-episodes.test.js +0 -78
- package/clis/xiaoyuzhou/transcript.test.js +0 -195
- package/clis/xiaoyuzhou/utils.test.js +0 -39
- package/clis/xueqiu/comments.test.js +0 -696
- package/clis/xueqiu/danjuan-utils.test.js +0 -41
- package/clis/xueqiu/utils.test.js +0 -26
- package/clis/yahoo/search.test.js +0 -94
- package/clis/youdao/note.test.js +0 -99
- package/clis/youtube/channel.test.js +0 -59
- package/clis/youtube/feed.test.js +0 -131
- package/clis/youtube/transcript-group.test.js +0 -99
- package/clis/youtube/transcript.test.js +0 -296
- package/clis/youtube/utils.test.js +0 -84
- package/clis/yuanbao/ask.test.js +0 -124
- package/clis/yuanbao/new.test.js +0 -30
- package/clis/yuanbao/shared.test.js +0 -80
- package/clis/zhihu/answer-comments.test.js +0 -287
- package/clis/zhihu/answer-detail.test.js +0 -338
- package/clis/zhihu/answer.test.js +0 -54
- package/clis/zhihu/collection.test.js +0 -337
- package/clis/zhihu/collections.test.js +0 -182
- package/clis/zhihu/comment.test.js +0 -50
- package/clis/zhihu/download.test.js +0 -12
- package/clis/zhihu/favorite.test.js +0 -59
- package/clis/zhihu/follow.test.js +0 -42
- package/clis/zhihu/like.test.js +0 -41
- package/clis/zhihu/question.test.js +0 -251
- package/clis/zhihu/recommend.test.js +0 -143
- package/clis/zhihu/search.test.js +0 -198
- package/clis/zhihu/target.test.js +0 -77
- package/clis/zhihu/text.test.js +0 -24
- package/clis/zhihu/write-shared.test.js +0 -176
- package/clis/zlibrary/commands.test.js +0 -65
- package/clis/zsxq/search.test.js +0 -24
- package/clis/zsxq/topic.test.js +0 -30
- package/clis/zsxq/topics.test.js +0 -24
- package/clis/zsxq/utils.test.js +0 -31
- package/dist/src/adapter-shadow.test.js +0 -49
- package/dist/src/adapter-source.test.js +0 -29
- package/dist/src/browser/analyze.test.js +0 -166
- package/dist/src/browser/article-extract.e2e.test.js +0 -105
- package/dist/src/browser/article-extract.test.js +0 -94
- package/dist/src/browser/ax-snapshot.test.js +0 -91
- package/dist/src/browser/base-page.test.js +0 -758
- package/dist/src/browser/cdp-click-fixture.test.js +0 -87
- package/dist/src/browser/cdp.test.js +0 -79
- package/dist/src/browser/compound.test.js +0 -175
- package/dist/src/browser/daemon-client.test.js +0 -199
- package/dist/src/browser/dom-helpers.test.js +0 -106
- package/dist/src/browser/dom-snapshot.test.js +0 -313
- package/dist/src/browser/errors.test.js +0 -60
- package/dist/src/browser/extract.test.js +0 -129
- package/dist/src/browser/find.test.js +0 -180
- package/dist/src/browser/html-tree.test.js +0 -181
- package/dist/src/browser/network-cache.test.js +0 -58
- package/dist/src/browser/network-key.test.js +0 -49
- package/dist/src/browser/page.test.js +0 -378
- package/dist/src/browser/shape-filter.test.js +0 -101
- package/dist/src/browser/shape.test.js +0 -82
- package/dist/src/browser/stealth.test.js +0 -134
- package/dist/src/browser/target-errors.test.js +0 -94
- package/dist/src/browser/target-resolver.test.js +0 -118
- package/dist/src/browser/utils.test.js +0 -29
- package/dist/src/browser/verify-fixture.test.js +0 -219
- package/dist/src/browser.test.js +0 -232
- package/dist/src/build-manifest.test.js +0 -341
- package/dist/src/capabilityRouting.test.js +0 -63
- package/dist/src/cli-argv-preprocess.test.js +0 -209
- package/dist/src/cli.test.js +0 -2803
- package/dist/src/commanderAdapter.test.js +0 -352
- package/dist/src/commands/daemon.test.js +0 -219
- package/dist/src/completion.test.js +0 -24
- package/dist/src/convention-audit.test.js +0 -247
- package/dist/src/daemon.test.js +0 -60
- package/dist/src/doctor.test.js +0 -263
- package/dist/src/download/article-download.test.js +0 -235
- package/dist/src/download/index.test.js +0 -140
- package/dist/src/download/media-download.test.js +0 -112
- package/dist/src/download/progress.test.js +0 -36
- package/dist/src/electron-apps.test.js +0 -49
- package/dist/src/engine.test.js +0 -312
- package/dist/src/errors.test.js +0 -109
- package/dist/src/execution.test.js +0 -623
- package/dist/src/extension-manifest-regression.test.js +0 -12
- package/dist/src/external.test.js +0 -146
- package/dist/src/help.test.js +0 -58
- package/dist/src/hooks.test.js +0 -92
- package/dist/src/interceptor.test.js +0 -81
- package/dist/src/launcher.test.js +0 -138
- package/dist/src/node-network.test.js +0 -55
- package/dist/src/observation/artifact.test.js +0 -121
- package/dist/src/observation/manager.test.js +0 -13
- package/dist/src/observation/redaction.test.js +0 -32
- package/dist/src/observation/retention.test.js +0 -118
- package/dist/src/observation/ring-buffer.test.js +0 -22
- package/dist/src/output.test.js +0 -53
- package/dist/src/package-exports.test.js +0 -112
- package/dist/src/pipeline/executor.test.js +0 -182
- package/dist/src/pipeline/steps/download.test.js +0 -102
- package/dist/src/pipeline/steps/fetch.test.js +0 -117
- package/dist/src/pipeline/template.test.js +0 -175
- package/dist/src/pipeline/transform.test.js +0 -133
- package/dist/src/plugin-manifest.test.js +0 -179
- package/dist/src/plugin-scaffold.test.js +0 -83
- package/dist/src/plugin.test.js +0 -1324
- package/dist/src/registry.test.js +0 -209
- package/dist/src/runtime-detect.test.js +0 -39
- package/dist/src/serialization.test.js +0 -78
- package/dist/src/snapshotFormatter.test.js +0 -521
- package/dist/src/tui.test.js +0 -19
- package/dist/src/update-check.test.js +0 -116
- package/dist/src/utils.test.js +0 -155
- package/dist/src/validate.test.js +0 -90
- package/dist/src/weixin-download.test.js +0 -45
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
-
import { ArgumentError, CommandExecutionError } from '@sovovs/bycli/errors';
|
|
4
|
-
import { getRegistry } from '@sovovs/bycli/registry';
|
|
5
|
-
|
|
6
|
-
const mocks = vi.hoisted(() => ({
|
|
7
|
-
browserFetch: vi.fn(),
|
|
8
|
-
}));
|
|
9
|
-
|
|
10
|
-
vi.mock('./_shared/browser-fetch.js', () => ({ browserFetch: mocks.browserFetch }));
|
|
11
|
-
|
|
12
|
-
import './delete.js';
|
|
13
|
-
|
|
14
|
-
function makePage({ evaluateResult, listBefore = [], listAfter = [] } = {}) {
|
|
15
|
-
let listCalls = 0;
|
|
16
|
-
mocks.browserFetch.mockImplementation(async (_page, method, url) => {
|
|
17
|
-
if (method === 'GET' && String(url).includes('/work_list?')) {
|
|
18
|
-
listCalls += 1;
|
|
19
|
-
return { aweme_list: listCalls === 1 ? listBefore : listAfter };
|
|
20
|
-
}
|
|
21
|
-
return { status_code: 0 };
|
|
22
|
-
});
|
|
23
|
-
return {
|
|
24
|
-
goto: vi.fn().mockResolvedValue(undefined),
|
|
25
|
-
evaluate: vi.fn().mockResolvedValue(evaluateResult ?? { ok: false, reason: 'not_found' }),
|
|
26
|
-
wait: vi.fn().mockResolvedValue(undefined),
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
describe('douyin delete registration', () => {
|
|
31
|
-
const command = getRegistry().get('douyin/delete');
|
|
32
|
-
|
|
33
|
-
beforeEach(() => {
|
|
34
|
-
vi.clearAllMocks();
|
|
35
|
-
vi.useFakeTimers();
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
afterEach(() => {
|
|
39
|
-
vi.useRealTimers();
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('registers the delete command', () => {
|
|
43
|
-
const registry = getRegistry();
|
|
44
|
-
const values = [...registry.values()];
|
|
45
|
-
const cmd = values.find(c => c.site === 'douyin' && c.name === 'delete');
|
|
46
|
-
expect(cmd).toBeDefined();
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('uses work_list id/index matching instead of title matching for fallback deletion', () => {
|
|
50
|
-
const source = readFileSync(new URL('./delete.js', import.meta.url), 'utf8');
|
|
51
|
-
expect(source).toContain('target_not_unique');
|
|
52
|
-
expect(source).toContain("String(entry.aweme_id || '') === targetId");
|
|
53
|
-
expect(source).toContain('cards[target.index]');
|
|
54
|
-
expect(source).not.toContain('text.includes(target.title)');
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('validates aweme_id before navigation', async () => {
|
|
58
|
-
const page = makePage();
|
|
59
|
-
await expect(command.func(page, { aweme_id: '' })).rejects.toBeInstanceOf(ArgumentError);
|
|
60
|
-
await expect(command.func(page, { aweme_id: 'abc' })).rejects.toBeInstanceOf(ArgumentError);
|
|
61
|
-
expect(page.goto).not.toHaveBeenCalled();
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('does not treat a missing work as successful delete', async () => {
|
|
65
|
-
const page = makePage({ listBefore: [], listAfter: [] });
|
|
66
|
-
const promise = command.func(page, { aweme_id: '123' });
|
|
67
|
-
const assertion = expect(promise).rejects.toBeInstanceOf(CommandExecutionError);
|
|
68
|
-
await vi.advanceTimersByTimeAsync(7000);
|
|
69
|
-
await assertion;
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('unwraps Browser Bridge envelopes around creator manage delete results', async () => {
|
|
73
|
-
const page = makePage({ evaluateResult: { session: 'site:douyin:test', data: { ok: true, aweme_id: '123' } } });
|
|
74
|
-
const promise = command.func(page, { aweme_id: '123' });
|
|
75
|
-
const assertion = expect(promise).resolves.toEqual([{ status: '✅ 已通过后台管理删除 123' }]);
|
|
76
|
-
await vi.advanceTimersByTimeAsync(7000);
|
|
77
|
-
await assertion;
|
|
78
|
-
expect(mocks.browserFetch).not.toHaveBeenCalled();
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('throws typed on malformed creator manage delete result', async () => {
|
|
82
|
-
const page = makePage({ evaluateResult: 'bad-shape' });
|
|
83
|
-
const promise = command.func(page, { aweme_id: '123' });
|
|
84
|
-
const assertion = expect(promise).rejects.toBeInstanceOf(CommandExecutionError);
|
|
85
|
-
await vi.advanceTimersByTimeAsync(7000);
|
|
86
|
-
await assertion;
|
|
87
|
-
expect(mocks.browserFetch).not.toHaveBeenCalled();
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('returns success only after fallback delete postcondition removes the target', async () => {
|
|
91
|
-
const page = makePage({
|
|
92
|
-
listBefore: [{ aweme_id: '123' }],
|
|
93
|
-
listAfter: [],
|
|
94
|
-
});
|
|
95
|
-
const promise = command.func(page, { aweme_id: '123' });
|
|
96
|
-
const assertion = expect(promise).resolves.toEqual([{ status: '✅ 已删除 123' }]);
|
|
97
|
-
await vi.advanceTimersByTimeAsync(8000);
|
|
98
|
-
await assertion;
|
|
99
|
-
});
|
|
100
|
-
});
|
|
@@ -1,335 +0,0 @@
|
|
|
1
|
-
import * as fs from 'node:fs';
|
|
2
|
-
import * as os from 'node:os';
|
|
3
|
-
import * as path from 'node:path';
|
|
4
|
-
import { afterAll, describe, expect, it, vi } from 'vitest';
|
|
5
|
-
import { wrapForEval } from '@sovovs/bycli/browser/utils';
|
|
6
|
-
import { getRegistry } from '@sovovs/bycli/registry';
|
|
7
|
-
import { buildCoverCheckPanelTextJs } from './draft.js';
|
|
8
|
-
import { createPageMock } from '../test-utils.js';
|
|
9
|
-
// ─── Shared test helpers ────────────────────────────────────────────
|
|
10
|
-
const tempDirs = [];
|
|
11
|
-
function createTempVideo(name = 'demo.mp4') {
|
|
12
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'bycli-douyin-draft-'));
|
|
13
|
-
tempDirs.push(dir);
|
|
14
|
-
const filePath = path.join(dir, name);
|
|
15
|
-
fs.writeFileSync(filePath, Buffer.from([0, 0, 0, 20, 102, 116, 121, 112]));
|
|
16
|
-
return filePath;
|
|
17
|
-
}
|
|
18
|
-
function createTempCover(videoPath, name = 'cover.jpg') {
|
|
19
|
-
const filePath = path.join(path.dirname(videoPath), name);
|
|
20
|
-
fs.writeFileSync(filePath, Buffer.from([0xff, 0xd8, 0xff, 0xd9]));
|
|
21
|
-
return filePath;
|
|
22
|
-
}
|
|
23
|
-
function getDraftCommand() {
|
|
24
|
-
const registry = getRegistry();
|
|
25
|
-
const cmd = [...registry.values()].find(c => c.site === 'douyin' && c.name === 'draft');
|
|
26
|
-
if (!cmd?.func)
|
|
27
|
-
throw new Error('douyin draft command not registered');
|
|
28
|
-
return cmd;
|
|
29
|
-
}
|
|
30
|
-
afterAll(() => {
|
|
31
|
-
for (const dir of tempDirs) {
|
|
32
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
function createFakeTree(text, children = []) {
|
|
36
|
-
const node = {
|
|
37
|
-
textContent: text,
|
|
38
|
-
parentElement: null,
|
|
39
|
-
querySelectorAll: () => [],
|
|
40
|
-
};
|
|
41
|
-
node.querySelectorAll = () => {
|
|
42
|
-
const descendants = [];
|
|
43
|
-
for (const child of children) {
|
|
44
|
-
descendants.push(child, ...child.querySelectorAll('*'));
|
|
45
|
-
}
|
|
46
|
-
return descendants;
|
|
47
|
-
};
|
|
48
|
-
for (const child of children) {
|
|
49
|
-
child.parentElement = node;
|
|
50
|
-
}
|
|
51
|
-
return node;
|
|
52
|
-
}
|
|
53
|
-
describe('douyin draft registration', () => {
|
|
54
|
-
it('registers the draft command', () => {
|
|
55
|
-
const registry = getRegistry();
|
|
56
|
-
const values = [...registry.values()];
|
|
57
|
-
const cmd = values.find(c => c.site === 'douyin' && c.name === 'draft');
|
|
58
|
-
expect(cmd).toBeDefined();
|
|
59
|
-
});
|
|
60
|
-
it('extracts the higher quick-check panel instead of stopping at a header-only ancestor', () => {
|
|
61
|
-
const marker = createFakeTree('快速检测');
|
|
62
|
-
const state = createFakeTree('重新检测');
|
|
63
|
-
const header = createFakeTree('快速检测', [marker]);
|
|
64
|
-
const status = createFakeTree('重新检测', [state]);
|
|
65
|
-
const panel = createFakeTree('快速检测重新检测', [header, status]);
|
|
66
|
-
const body = createFakeTree('body', [panel]);
|
|
67
|
-
const g = globalThis;
|
|
68
|
-
const originalDocument = g.document;
|
|
69
|
-
g.document = {
|
|
70
|
-
body,
|
|
71
|
-
querySelectorAll: () => [marker, state],
|
|
72
|
-
};
|
|
73
|
-
try {
|
|
74
|
-
expect(eval(buildCoverCheckPanelTextJs())()).toBe('快速检测重新检测');
|
|
75
|
-
}
|
|
76
|
-
finally {
|
|
77
|
-
g.document = originalDocument;
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
it('returns empty when only header text exists and no exact quick-check state node is present', () => {
|
|
81
|
-
const marker = createFakeTree('快速检测');
|
|
82
|
-
const note = createFakeTree('检测说明');
|
|
83
|
-
const header = createFakeTree('快速检测检测说明', [marker, note]);
|
|
84
|
-
const body = createFakeTree('body', [header]);
|
|
85
|
-
const g = globalThis;
|
|
86
|
-
const originalDocument = g.document;
|
|
87
|
-
g.document = {
|
|
88
|
-
body,
|
|
89
|
-
querySelectorAll: () => [marker, note],
|
|
90
|
-
};
|
|
91
|
-
try {
|
|
92
|
-
expect(eval(buildCoverCheckPanelTextJs())()).toBe('');
|
|
93
|
-
}
|
|
94
|
-
finally {
|
|
95
|
-
g.document = originalDocument;
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
it('extracts the quick-check panel when busy state is rendered as a single 封面检测中 node', () => {
|
|
99
|
-
const marker = createFakeTree('快速检测');
|
|
100
|
-
const busy = createFakeTree('封面检测中');
|
|
101
|
-
const header = createFakeTree('快速检测', [marker]);
|
|
102
|
-
const status = createFakeTree('封面检测中', [busy]);
|
|
103
|
-
const panel = createFakeTree('快速检测封面检测中', [header, status]);
|
|
104
|
-
const body = createFakeTree('body', [panel]);
|
|
105
|
-
const g = globalThis;
|
|
106
|
-
const originalDocument = g.document;
|
|
107
|
-
g.document = {
|
|
108
|
-
body,
|
|
109
|
-
querySelectorAll: () => [marker, busy],
|
|
110
|
-
};
|
|
111
|
-
try {
|
|
112
|
-
expect(eval(buildCoverCheckPanelTextJs())()).toBe('快速检测封面检测中');
|
|
113
|
-
}
|
|
114
|
-
finally {
|
|
115
|
-
g.document = originalDocument;
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
it('uploads through the official creator draft page and saves the draft session', async () => {
|
|
119
|
-
const cmd = getDraftCommand();
|
|
120
|
-
const videoPath = createTempVideo('demo.mp4');
|
|
121
|
-
const page = createPageMock([
|
|
122
|
-
undefined,
|
|
123
|
-
{ href: 'https://creator.douyin.com/creator-micro/content/post/video?enter_from=publish_page', ready: true, bodyText: '' },
|
|
124
|
-
undefined,
|
|
125
|
-
true,
|
|
126
|
-
true,
|
|
127
|
-
true,
|
|
128
|
-
{ ok: true, text: '暂存离开', creationId: 'creation-001' },
|
|
129
|
-
{
|
|
130
|
-
href: 'https://creator.douyin.com/creator-micro/content/upload?enter_from=publish',
|
|
131
|
-
bodyText: '你还有上次未发布的视频,是否继续编辑?继续编辑放弃',
|
|
132
|
-
},
|
|
133
|
-
]);
|
|
134
|
-
const rows = await cmd.func(page, {
|
|
135
|
-
video: videoPath,
|
|
136
|
-
title: '最小修复验证',
|
|
137
|
-
caption: 'bycli draft e2e',
|
|
138
|
-
cover: '',
|
|
139
|
-
visibility: 'friends',
|
|
140
|
-
});
|
|
141
|
-
expect(page.goto).toHaveBeenCalledWith('https://creator.douyin.com/creator-micro/content/upload');
|
|
142
|
-
expect(page.wait).toHaveBeenCalledWith({
|
|
143
|
-
selector: 'input[type="file"]',
|
|
144
|
-
timeout: 20,
|
|
145
|
-
});
|
|
146
|
-
expect(page.setFileInput).toHaveBeenCalledWith([videoPath], 'input[type="file"]');
|
|
147
|
-
const evaluateCalls = page.evaluate.mock.calls.map((args) => String(args[0]));
|
|
148
|
-
expect(evaluateCalls.some((code) => code.includes('填写作品标题'))).toBe(true);
|
|
149
|
-
expect(evaluateCalls.some((code) => code.includes('好友可见'))).toBe(true);
|
|
150
|
-
expect(evaluateCalls.some((code) => code.includes('暂存离开'))).toBe(true);
|
|
151
|
-
expect(rows).toEqual([
|
|
152
|
-
{
|
|
153
|
-
status: '✅ 草稿已保存,可在创作中心继续编辑',
|
|
154
|
-
draft_id: 'creation-001',
|
|
155
|
-
},
|
|
156
|
-
]);
|
|
157
|
-
});
|
|
158
|
-
it('waits for the composer when upload processing is slower than the first few polls', async () => {
|
|
159
|
-
const cmd = getDraftCommand();
|
|
160
|
-
const videoPath = createTempVideo('slow.mp4');
|
|
161
|
-
const page = createPageMock([
|
|
162
|
-
undefined,
|
|
163
|
-
{ href: 'https://creator.douyin.com/creator-micro/content/upload', ready: false, bodyText: '上传中 42%' },
|
|
164
|
-
{ href: 'https://creator.douyin.com/creator-micro/content/upload', ready: false, bodyText: '转码中' },
|
|
165
|
-
{ href: 'https://creator.douyin.com/creator-micro/content/post/video?enter_from=publish_page', ready: true, bodyText: '' },
|
|
166
|
-
undefined,
|
|
167
|
-
true,
|
|
168
|
-
true,
|
|
169
|
-
{ ok: true, text: '暂存离开', creationId: 'creation-slow' },
|
|
170
|
-
{
|
|
171
|
-
href: 'https://creator.douyin.com/creator-micro/content/upload?enter_from=publish',
|
|
172
|
-
bodyText: '你还有上次未发布的视频,是否继续编辑?继续编辑放弃',
|
|
173
|
-
},
|
|
174
|
-
]);
|
|
175
|
-
const rows = await cmd.func(page, {
|
|
176
|
-
video: videoPath,
|
|
177
|
-
title: '慢上传验证',
|
|
178
|
-
caption: '',
|
|
179
|
-
cover: '',
|
|
180
|
-
visibility: 'public',
|
|
181
|
-
});
|
|
182
|
-
expect(rows).toEqual([
|
|
183
|
-
{
|
|
184
|
-
status: '✅ 草稿已保存,可在创作中心继续编辑',
|
|
185
|
-
draft_id: 'creation-slow',
|
|
186
|
-
},
|
|
187
|
-
]);
|
|
188
|
-
expect(page.wait).toHaveBeenCalledWith({ time: 0.5 });
|
|
189
|
-
const shortWaitCalls = page.wait.mock.calls.filter(([arg]) => JSON.stringify(arg) === JSON.stringify({ time: 0.5 }));
|
|
190
|
-
expect(shortWaitCalls).toHaveLength(2);
|
|
191
|
-
});
|
|
192
|
-
it('fails fast when the save action does not expose a draft creation id', async () => {
|
|
193
|
-
const cmd = getDraftCommand();
|
|
194
|
-
const videoPath = createTempVideo('missing-id.mp4');
|
|
195
|
-
const page = createPageMock([
|
|
196
|
-
undefined,
|
|
197
|
-
{ href: 'https://creator.douyin.com/creator-micro/content/post/video?enter_from=publish_page', ready: true, bodyText: '' },
|
|
198
|
-
undefined,
|
|
199
|
-
true,
|
|
200
|
-
true,
|
|
201
|
-
{ ok: true, text: '暂存离开', creationId: '' },
|
|
202
|
-
]);
|
|
203
|
-
await expect(cmd.func(page, {
|
|
204
|
-
video: videoPath,
|
|
205
|
-
title: '缺失 creation id',
|
|
206
|
-
caption: '',
|
|
207
|
-
cover: '',
|
|
208
|
-
visibility: 'public',
|
|
209
|
-
})).rejects.toThrow('点击草稿按钮失败: creation-id-missing');
|
|
210
|
-
});
|
|
211
|
-
it('uses the dedicated cover upload input when a custom cover is provided', async () => {
|
|
212
|
-
const cmd = getDraftCommand();
|
|
213
|
-
const videoPath = createTempVideo('demo.mp4');
|
|
214
|
-
const coverPath = createTempCover(videoPath);
|
|
215
|
-
const page = createPageMock([
|
|
216
|
-
undefined,
|
|
217
|
-
{ href: 'https://creator.douyin.com/creator-micro/content/post/video?enter_from=publish_page', ready: true, bodyText: '' },
|
|
218
|
-
undefined,
|
|
219
|
-
1,
|
|
220
|
-
{ ok: false, reason: 'cover-input-pending' },
|
|
221
|
-
{ ok: true, selector: '[data-bycli-cover-input="1"]' },
|
|
222
|
-
'快速检测检测中',
|
|
223
|
-
'快速检测重新检测',
|
|
224
|
-
true,
|
|
225
|
-
true,
|
|
226
|
-
{ ok: true, text: '暂存离开', creationId: 'creation-002' },
|
|
227
|
-
{
|
|
228
|
-
href: 'https://creator.douyin.com/creator-micro/content/upload?enter_from=publish',
|
|
229
|
-
bodyText: '你还有上次未发布的视频,是否继续编辑?继续编辑放弃',
|
|
230
|
-
},
|
|
231
|
-
]);
|
|
232
|
-
const rows = await cmd.func(page, {
|
|
233
|
-
video: videoPath,
|
|
234
|
-
title: '封面上传验证',
|
|
235
|
-
caption: '',
|
|
236
|
-
cover: coverPath,
|
|
237
|
-
visibility: 'public',
|
|
238
|
-
});
|
|
239
|
-
expect(page.setFileInput).toHaveBeenNthCalledWith(1, [videoPath], 'input[type="file"]');
|
|
240
|
-
expect(page.setFileInput).toHaveBeenNthCalledWith(2, [coverPath], '[data-bycli-cover-input="1"]');
|
|
241
|
-
const shortWaitCalls = page.wait.mock.calls.filter(([arg]) => JSON.stringify(arg) === JSON.stringify({ time: 0.5 }));
|
|
242
|
-
expect(shortWaitCalls).toHaveLength(2);
|
|
243
|
-
const evaluateCalls = page.evaluate.mock.calls.map((args) => String(args[0]));
|
|
244
|
-
expect(evaluateCalls.some((code) => code.includes('上传新封面'))).toBe(true);
|
|
245
|
-
expect(evaluateCalls.some((code) => code.includes("text.includes('快速检测检测')"))).toBe(false);
|
|
246
|
-
expect(() => {
|
|
247
|
-
for (const code of evaluateCalls) {
|
|
248
|
-
new Function(wrapForEval(code));
|
|
249
|
-
}
|
|
250
|
-
}).not.toThrow();
|
|
251
|
-
expect(rows).toEqual([
|
|
252
|
-
{
|
|
253
|
-
status: '✅ 草稿已保存,可在创作中心继续编辑',
|
|
254
|
-
draft_id: 'creation-002',
|
|
255
|
-
},
|
|
256
|
-
]);
|
|
257
|
-
});
|
|
258
|
-
it('waits for a late cover-section update before treating the custom cover as ready', async () => {
|
|
259
|
-
const cmd = getDraftCommand();
|
|
260
|
-
const videoPath = createTempVideo('cover-race.mp4');
|
|
261
|
-
const coverPath = createTempCover(videoPath, 'cover-race.jpg');
|
|
262
|
-
const page = createPageMock([
|
|
263
|
-
undefined,
|
|
264
|
-
{ href: 'https://creator.douyin.com/creator-micro/content/post/video?enter_from=publish_page', ready: true, bodyText: '' },
|
|
265
|
-
undefined,
|
|
266
|
-
1,
|
|
267
|
-
{ ok: true, selector: '[data-bycli-cover-input="1"]' },
|
|
268
|
-
'快速检测重新检测',
|
|
269
|
-
'快速检测重新检测',
|
|
270
|
-
'快速检测重新检测',
|
|
271
|
-
'快速检测检测中',
|
|
272
|
-
'快速检测横/竖双封面缺失',
|
|
273
|
-
true,
|
|
274
|
-
true,
|
|
275
|
-
{ ok: true, text: '暂存离开', creationId: 'creation-cover-race' },
|
|
276
|
-
{
|
|
277
|
-
href: 'https://creator.douyin.com/creator-micro/content/upload?enter_from=publish',
|
|
278
|
-
bodyText: '你还有上次未发布的视频,是否继续编辑?继续编辑放弃',
|
|
279
|
-
},
|
|
280
|
-
]);
|
|
281
|
-
const rows = await cmd.func(page, {
|
|
282
|
-
video: videoPath,
|
|
283
|
-
title: '封面竞态验证',
|
|
284
|
-
caption: '',
|
|
285
|
-
cover: coverPath,
|
|
286
|
-
visibility: 'public',
|
|
287
|
-
});
|
|
288
|
-
expect(rows).toEqual([
|
|
289
|
-
{
|
|
290
|
-
status: '✅ 草稿已保存,可在创作中心继续编辑',
|
|
291
|
-
draft_id: 'creation-cover-race',
|
|
292
|
-
},
|
|
293
|
-
]);
|
|
294
|
-
const shortWaitCalls = page.wait.mock.calls.filter(([arg]) => JSON.stringify(arg) === JSON.stringify({ time: 0.5 }));
|
|
295
|
-
expect(shortWaitCalls).toHaveLength(4);
|
|
296
|
-
});
|
|
297
|
-
it('accepts the same ready label after cover busy state when the quick-check panel actually transitioned', async () => {
|
|
298
|
-
const cmd = getDraftCommand();
|
|
299
|
-
const videoPath = createTempVideo('cover-same-ready.mp4');
|
|
300
|
-
const coverPath = createTempCover(videoPath, 'cover-same-ready.jpg');
|
|
301
|
-
const page = createPageMock([
|
|
302
|
-
undefined,
|
|
303
|
-
{ href: 'https://creator.douyin.com/creator-micro/content/post/video?enter_from=publish_page', ready: true, bodyText: '' },
|
|
304
|
-
undefined,
|
|
305
|
-
1,
|
|
306
|
-
{ ok: true, selector: '[data-bycli-cover-input="1"]' },
|
|
307
|
-
'快速检测重新检测',
|
|
308
|
-
'快速检测重新检测',
|
|
309
|
-
'快速检测检测中',
|
|
310
|
-
'快速检测重新检测',
|
|
311
|
-
true,
|
|
312
|
-
true,
|
|
313
|
-
{ ok: true, text: '暂存离开', creationId: 'creation-cover-same-ready' },
|
|
314
|
-
{
|
|
315
|
-
href: 'https://creator.douyin.com/creator-micro/content/upload?enter_from=publish',
|
|
316
|
-
bodyText: '你还有上次未发布的视频,是否继续编辑?继续编辑放弃',
|
|
317
|
-
},
|
|
318
|
-
]);
|
|
319
|
-
const rows = await cmd.func(page, {
|
|
320
|
-
video: videoPath,
|
|
321
|
-
title: '封面同文案验证',
|
|
322
|
-
caption: '',
|
|
323
|
-
cover: coverPath,
|
|
324
|
-
visibility: 'public',
|
|
325
|
-
});
|
|
326
|
-
expect(rows).toEqual([
|
|
327
|
-
{
|
|
328
|
-
status: '✅ 草稿已保存,可在创作中心继续编辑',
|
|
329
|
-
draft_id: 'creation-cover-same-ready',
|
|
330
|
-
},
|
|
331
|
-
]);
|
|
332
|
-
const shortWaitCalls = page.wait.mock.calls.filter(([arg]) => JSON.stringify(arg) === JSON.stringify({ time: 0.5 }));
|
|
333
|
-
expect(shortWaitCalls).toHaveLength(3);
|
|
334
|
-
});
|
|
335
|
-
});
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { getRegistry } from '@sovovs/bycli/registry';
|
|
3
|
-
import './drafts.js';
|
|
4
|
-
describe('douyin drafts registration', () => {
|
|
5
|
-
it('registers the drafts command', () => {
|
|
6
|
-
const registry = getRegistry();
|
|
7
|
-
const values = [...registry.values()];
|
|
8
|
-
const cmd = values.find(c => c.site === 'douyin' && c.name === 'drafts');
|
|
9
|
-
expect(cmd).toBeDefined();
|
|
10
|
-
});
|
|
11
|
-
});
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { ArgumentError, CommandExecutionError } from '@sovovs/bycli/errors';
|
|
3
|
-
const { browserFetchMock } = vi.hoisted(() => ({
|
|
4
|
-
browserFetchMock: vi.fn(),
|
|
5
|
-
}));
|
|
6
|
-
vi.mock('./_shared/browser-fetch.js', () => ({
|
|
7
|
-
browserFetch: browserFetchMock,
|
|
8
|
-
}));
|
|
9
|
-
import { getRegistry } from '@sovovs/bycli/registry';
|
|
10
|
-
import './hashtag.js';
|
|
11
|
-
describe('douyin hashtag', () => {
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
browserFetchMock.mockReset();
|
|
14
|
-
});
|
|
15
|
-
it('registers the hashtag command', () => {
|
|
16
|
-
const registry = getRegistry();
|
|
17
|
-
const cmd = [...registry.values()].find(c => c.site === 'douyin' && c.name === 'hashtag');
|
|
18
|
-
expect(cmd).toBeDefined();
|
|
19
|
-
expect(cmd?.args.some(a => a.name === 'action')).toBe(true);
|
|
20
|
-
});
|
|
21
|
-
it('has all expected args', () => {
|
|
22
|
-
const registry = getRegistry();
|
|
23
|
-
const cmd = [...registry.values()].find(c => c.site === 'douyin' && c.name === 'hashtag');
|
|
24
|
-
const argNames = cmd?.args.map(a => a.name) ?? [];
|
|
25
|
-
expect(argNames).toContain('action');
|
|
26
|
-
expect(argNames).toContain('keyword');
|
|
27
|
-
expect(argNames).toContain('cover');
|
|
28
|
-
expect(argNames).toContain('limit');
|
|
29
|
-
});
|
|
30
|
-
it('uses COOKIE strategy', () => {
|
|
31
|
-
const registry = getRegistry();
|
|
32
|
-
const cmd = [...registry.values()].find(c => c.site === 'douyin' && c.name === 'hashtag');
|
|
33
|
-
expect(cmd?.strategy).toBe('cookie');
|
|
34
|
-
});
|
|
35
|
-
it('registers action-specific validation so missing args fail before browser pre-navigation', () => {
|
|
36
|
-
const registry = getRegistry();
|
|
37
|
-
const cmd = [...registry.values()].find((c) => c.site === 'douyin' && c.name === 'hashtag');
|
|
38
|
-
expect(cmd?.validateArgs).toBeTypeOf('function');
|
|
39
|
-
expect(() => cmd.validateArgs({ action: 'search', keyword: '', cover: '', limit: 10 }))
|
|
40
|
-
.toThrow(ArgumentError);
|
|
41
|
-
expect(() => cmd.validateArgs({ action: 'suggest', keyword: '速效救心丸', cover: '', limit: 10 }))
|
|
42
|
-
.toThrow(ArgumentError);
|
|
43
|
-
expect(() => cmd.validateArgs({ action: 'hot', keyword: '', cover: '', limit: 10 }))
|
|
44
|
-
.not.toThrow();
|
|
45
|
-
expect(browserFetchMock).not.toHaveBeenCalled();
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('search throws ArgumentError when --keyword is missing or blank (#1689)', async () => {
|
|
49
|
-
const registry = getRegistry();
|
|
50
|
-
const cmd = [...registry.values()].find((c) => c.site === 'douyin' && c.name === 'hashtag');
|
|
51
|
-
await expect(cmd.func({}, { action: 'search', keyword: '', cover: '', limit: 10 }))
|
|
52
|
-
.rejects.toBeInstanceOf(ArgumentError);
|
|
53
|
-
await expect(cmd.func({}, { action: 'search', keyword: ' ', cover: '', limit: 10 }))
|
|
54
|
-
.rejects.toBeInstanceOf(ArgumentError);
|
|
55
|
-
expect(browserFetchMock).not.toHaveBeenCalled();
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('suggest throws ArgumentError when --cover is missing (#1689 root cause)', async () => {
|
|
59
|
-
const registry = getRegistry();
|
|
60
|
-
const cmd = [...registry.values()].find((c) => c.site === 'douyin' && c.name === 'hashtag');
|
|
61
|
-
await expect(cmd.func({}, { action: 'suggest', keyword: '速效救心丸', cover: '', limit: 10 }))
|
|
62
|
-
.rejects.toMatchObject({ code: 'ARGUMENT', message: expect.stringContaining('--cover') });
|
|
63
|
-
await expect(cmd.func({}, { action: 'suggest', keyword: '', cover: ' ', limit: 10 }))
|
|
64
|
-
.rejects.toBeInstanceOf(ArgumentError);
|
|
65
|
-
expect(browserFetchMock).not.toHaveBeenCalled();
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('hot accepts empty --keyword (it is optional for hot)', async () => {
|
|
69
|
-
const registry = getRegistry();
|
|
70
|
-
const cmd = [...registry.values()].find((c) => c.site === 'douyin' && c.name === 'hashtag');
|
|
71
|
-
browserFetchMock.mockResolvedValueOnce({ hotspot_list: [{ sentence: '热点1', hot_value: 100, sentence_id: 'h1' }] });
|
|
72
|
-
const rows = await cmd.func({}, { action: 'hot', keyword: '', cover: '', limit: 5 });
|
|
73
|
-
expect(rows[0]).toEqual({ name: '热点1', id: 'h1', view_count: 100 });
|
|
74
|
-
const url = browserFetchMock.mock.calls[0][2];
|
|
75
|
-
expect(url).not.toContain('keyword=');
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('search threads --keyword + count into the challenge/search URL', async () => {
|
|
79
|
-
const registry = getRegistry();
|
|
80
|
-
const cmd = [...registry.values()].find((c) => c.site === 'douyin' && c.name === 'hashtag');
|
|
81
|
-
browserFetchMock.mockResolvedValueOnce({
|
|
82
|
-
challenge_list: [{ challenge_info: { cha_name: '美食', cid: '123', view_count: 5000 } }],
|
|
83
|
-
});
|
|
84
|
-
const rows = await cmd.func({}, { action: 'search', keyword: '美食', cover: '', limit: 10 });
|
|
85
|
-
expect(rows).toEqual([{ name: '美食', id: '123', view_count: 5000 }]);
|
|
86
|
-
const url = browserFetchMock.mock.calls[0][2];
|
|
87
|
-
expect(url).toContain('challenge/search');
|
|
88
|
-
expect(url).toContain('keyword=' + encodeURIComponent('美食'));
|
|
89
|
-
expect(url).toContain('count=10');
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('suggest threads --cover into the hashtag/rec URL on success', async () => {
|
|
93
|
-
const registry = getRegistry();
|
|
94
|
-
const cmd = [...registry.values()].find((c) => c.site === 'douyin' && c.name === 'hashtag');
|
|
95
|
-
browserFetchMock.mockResolvedValueOnce({
|
|
96
|
-
hashtag_list: [{ name: '推荐话题', id: 'h99', view_count: 1234 }],
|
|
97
|
-
});
|
|
98
|
-
const rows = await cmd.func({}, { action: 'suggest', keyword: '', cover: 'tos-cn-i-cover/abc', limit: 10 });
|
|
99
|
-
expect(rows).toEqual([{ name: '推荐话题', id: 'h99', view_count: 1234 }]);
|
|
100
|
-
const url = browserFetchMock.mock.calls[0][2];
|
|
101
|
-
expect(url).toContain('hashtag/rec');
|
|
102
|
-
expect(url).toContain('cover_uri=' + encodeURIComponent('tos-cn-i-cover/abc'));
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it('search throws CommandExecutionError when API returns a non-object payload', async () => {
|
|
106
|
-
const registry = getRegistry();
|
|
107
|
-
const cmd = [...registry.values()].find((c) => c.site === 'douyin' && c.name === 'hashtag');
|
|
108
|
-
browserFetchMock.mockResolvedValueOnce(null);
|
|
109
|
-
await expect(cmd.func({}, { action: 'search', keyword: '美食', cover: '', limit: 10 }))
|
|
110
|
-
.rejects.toBeInstanceOf(CommandExecutionError);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('search throws CommandExecutionError when challenge_list has wrong shape', async () => {
|
|
114
|
-
const registry = getRegistry();
|
|
115
|
-
const cmd = [...registry.values()].find((c) => c.site === 'douyin' && c.name === 'hashtag');
|
|
116
|
-
browserFetchMock.mockResolvedValueOnce({ challenge_list: 'not-an-array' });
|
|
117
|
-
await expect(cmd.func({}, { action: 'search', keyword: '美食', cover: '', limit: 10 }))
|
|
118
|
-
.rejects.toBeInstanceOf(CommandExecutionError);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it('search throws CommandExecutionError when challenges return but none parse', async () => {
|
|
122
|
-
const registry = getRegistry();
|
|
123
|
-
const cmd = [...registry.values()].find((c) => c.site === 'douyin' && c.name === 'hashtag');
|
|
124
|
-
browserFetchMock.mockResolvedValueOnce({
|
|
125
|
-
challenge_list: [{ challenge_info: null }, { other_field: 1 }],
|
|
126
|
-
});
|
|
127
|
-
await expect(cmd.func({}, { action: 'search', keyword: '美食', cover: '', limit: 10 }))
|
|
128
|
-
.rejects.toBeInstanceOf(CommandExecutionError);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it('suggest throws CommandExecutionError when hashtag_list has wrong shape', async () => {
|
|
132
|
-
const registry = getRegistry();
|
|
133
|
-
const cmd = [...registry.values()].find((c) => c.site === 'douyin' && c.name === 'hashtag');
|
|
134
|
-
browserFetchMock.mockResolvedValueOnce({ hashtag_list: 'oops' });
|
|
135
|
-
await expect(cmd.func({}, { action: 'suggest', keyword: '', cover: 'tos-cn-i-cover/x', limit: 10 }))
|
|
136
|
-
.rejects.toBeInstanceOf(CommandExecutionError);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it('hot throws CommandExecutionError when hotspot_list has wrong shape', async () => {
|
|
140
|
-
const registry = getRegistry();
|
|
141
|
-
const cmd = [...registry.values()].find((c) => c.site === 'douyin' && c.name === 'hashtag');
|
|
142
|
-
browserFetchMock.mockResolvedValueOnce({ hotspot_list: { malformed: true } });
|
|
143
|
-
await expect(cmd.func({}, { action: 'hot', keyword: '', cover: '', limit: 5 }))
|
|
144
|
-
.rejects.toBeInstanceOf(CommandExecutionError);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it('parses the current hotspot recommendation shape', async () => {
|
|
148
|
-
const registry = getRegistry();
|
|
149
|
-
const command = [...registry.values()].find((cmd) => cmd.site === 'douyin' && cmd.name === 'hashtag');
|
|
150
|
-
expect(command?.func).toBeDefined();
|
|
151
|
-
if (!command?.func)
|
|
152
|
-
throw new Error('douyin hashtag command not registered');
|
|
153
|
-
browserFetchMock.mockResolvedValueOnce({
|
|
154
|
-
all_sentences: [
|
|
155
|
-
{
|
|
156
|
-
word: '在公园花海里大晒一场',
|
|
157
|
-
hot_value: 12141172,
|
|
158
|
-
sentence_id: '2448416',
|
|
159
|
-
},
|
|
160
|
-
],
|
|
161
|
-
});
|
|
162
|
-
const rows = await command.func({}, { action: 'hot', keyword: '', limit: 5 });
|
|
163
|
-
expect(rows).toEqual([
|
|
164
|
-
{
|
|
165
|
-
name: '在公园花海里大晒一场',
|
|
166
|
-
id: '2448416',
|
|
167
|
-
view_count: 12141172,
|
|
168
|
-
},
|
|
169
|
-
]);
|
|
170
|
-
});
|
|
171
|
-
});
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { getRegistry } from '@sovovs/bycli/registry';
|
|
3
|
-
import './location.js';
|
|
4
|
-
describe('douyin location registration', () => {
|
|
5
|
-
it('registers the location command', () => {
|
|
6
|
-
const registry = getRegistry();
|
|
7
|
-
const cmd = [...registry.values()].find(c => c.site === 'douyin' && c.name === 'location');
|
|
8
|
-
expect(cmd).toBeDefined();
|
|
9
|
-
expect(cmd?.args.some(a => a.name === 'query')).toBe(true);
|
|
10
|
-
});
|
|
11
|
-
it('has all expected args', () => {
|
|
12
|
-
const registry = getRegistry();
|
|
13
|
-
const cmd = [...registry.values()].find(c => c.site === 'douyin' && c.name === 'location');
|
|
14
|
-
const argNames = cmd?.args.map(a => a.name) ?? [];
|
|
15
|
-
expect(argNames).toContain('query');
|
|
16
|
-
expect(argNames).toContain('limit');
|
|
17
|
-
});
|
|
18
|
-
it('uses COOKIE strategy', () => {
|
|
19
|
-
const registry = getRegistry();
|
|
20
|
-
const cmd = [...registry.values()].find(c => c.site === 'douyin' && c.name === 'location');
|
|
21
|
-
expect(cmd?.strategy).toBe('cookie');
|
|
22
|
-
});
|
|
23
|
-
});
|