@jackwener/opencli 1.5.6 → 1.5.8
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/CHANGELOG.md +34 -0
- package/README.md +4 -2
- package/README.zh-CN.md +4 -1
- package/SKILL.md +879 -0
- package/dist/browser/cdp.d.ts +1 -0
- package/dist/browser/cdp.js +30 -27
- package/dist/browser/daemon-client.d.ts +7 -1
- package/dist/browser/daemon-client.js +3 -0
- package/dist/browser/dom-helpers.js +1 -0
- package/dist/browser/dom-helpers.test.js +14 -1
- package/dist/browser/mcp.js +18 -13
- package/dist/browser/page.js +22 -2
- package/dist/browser/page.test.d.ts +1 -0
- package/dist/browser/page.test.js +44 -0
- package/dist/browser/stealth.js +198 -0
- package/dist/browser/stealth.test.d.ts +1 -0
- package/dist/browser/stealth.test.js +134 -0
- package/dist/browser.test.js +1 -1
- package/dist/build-manifest.d.ts +1 -0
- package/dist/build-manifest.js +5 -1
- package/dist/build-manifest.test.js +2 -0
- package/dist/cli-manifest.json +544 -137
- package/dist/cli.js +20 -3
- package/dist/clis/antigravity/serve.d.ts +1 -1
- package/dist/clis/antigravity/serve.js +5 -8
- package/dist/clis/bilibili/subtitle.js +4 -0
- package/dist/clis/bilibili/subtitle.test.d.ts +1 -0
- package/dist/clis/bilibili/subtitle.test.js +48 -0
- package/dist/clis/chatwise/ask.js +0 -2
- package/dist/clis/chatwise/export.js +0 -2
- package/dist/clis/chatwise/history.js +0 -2
- package/dist/clis/chatwise/model.js +0 -2
- package/dist/clis/chatwise/new.js +1 -2
- package/dist/clis/chatwise/read.js +0 -2
- package/dist/clis/chatwise/screenshot.js +1 -2
- package/dist/clis/chatwise/send.js +0 -2
- package/dist/clis/chatwise/status.js +1 -2
- package/dist/clis/ctrip/search.d.ts +13 -0
- package/dist/clis/ctrip/search.js +73 -48
- package/dist/clis/ctrip/search.test.d.ts +1 -0
- package/dist/clis/ctrip/search.test.js +64 -0
- package/dist/clis/douyin/_shared/sts2.js +8 -2
- package/dist/clis/douyin/_shared/sts2.test.d.ts +1 -0
- package/dist/clis/douyin/_shared/sts2.test.js +27 -0
- package/dist/clis/douyin/activities.js +4 -2
- package/dist/clis/douyin/activities.test.js +34 -1
- package/dist/clis/douyin/collections.js +1 -1
- package/dist/clis/douyin/collections.test.js +24 -2
- package/dist/clis/douyin/draft.d.ts +8 -11
- package/dist/clis/douyin/draft.js +302 -185
- package/dist/clis/douyin/draft.test.d.ts +1 -1
- package/dist/clis/douyin/draft.test.js +357 -2
- package/dist/clis/douyin/hashtag.js +9 -2
- package/dist/clis/douyin/hashtag.test.js +35 -2
- package/dist/clis/douyin/profile.js +1 -1
- package/dist/clis/douyin/profile.test.js +36 -1
- package/dist/clis/douyin/videos.js +22 -5
- package/dist/clis/douyin/videos.test.js +45 -2
- package/dist/clis/facebook/search.test.d.ts +5 -0
- package/dist/clis/facebook/search.test.js +60 -0
- package/dist/clis/facebook/search.yaml +4 -3
- package/dist/clis/instagram/download.d.ts +16 -0
- package/dist/clis/instagram/download.js +225 -0
- package/dist/clis/instagram/download.test.d.ts +1 -0
- package/dist/clis/instagram/download.test.js +118 -0
- package/dist/clis/notebooklm/bind-current.d.ts +1 -0
- package/dist/clis/notebooklm/bind-current.js +29 -0
- package/dist/clis/notebooklm/bind-current.test.d.ts +1 -0
- package/dist/clis/notebooklm/bind-current.test.js +35 -0
- package/dist/clis/notebooklm/binding.test.d.ts +1 -0
- package/dist/clis/notebooklm/binding.test.js +44 -0
- package/dist/clis/notebooklm/compat.test.d.ts +3 -0
- package/dist/clis/notebooklm/compat.test.js +16 -0
- package/dist/clis/notebooklm/current.d.ts +1 -0
- package/dist/clis/notebooklm/current.js +28 -0
- package/dist/clis/notebooklm/get.d.ts +1 -0
- package/dist/clis/notebooklm/get.js +37 -0
- package/dist/clis/notebooklm/history.d.ts +1 -0
- package/dist/clis/notebooklm/history.js +25 -0
- package/dist/clis/notebooklm/history.test.d.ts +1 -0
- package/dist/clis/notebooklm/history.test.js +58 -0
- package/dist/clis/notebooklm/list.d.ts +1 -0
- package/dist/clis/notebooklm/list.js +35 -0
- package/dist/clis/notebooklm/note-list.d.ts +1 -0
- package/dist/clis/notebooklm/note-list.js +28 -0
- package/dist/clis/notebooklm/note-list.test.d.ts +1 -0
- package/dist/clis/notebooklm/note-list.test.js +56 -0
- package/dist/clis/notebooklm/notes-get.d.ts +1 -0
- package/dist/clis/notebooklm/notes-get.js +47 -0
- package/dist/clis/notebooklm/notes-get.test.d.ts +1 -0
- package/dist/clis/notebooklm/notes-get.test.js +72 -0
- package/dist/clis/notebooklm/rpc.d.ts +36 -0
- package/dist/clis/notebooklm/rpc.js +189 -0
- package/dist/clis/notebooklm/rpc.test.d.ts +1 -0
- package/dist/clis/notebooklm/rpc.test.js +105 -0
- package/dist/clis/notebooklm/shared.d.ts +87 -0
- package/dist/clis/notebooklm/shared.js +3 -0
- package/dist/clis/notebooklm/source-fulltext.d.ts +1 -0
- package/dist/clis/notebooklm/source-fulltext.js +44 -0
- package/dist/clis/notebooklm/source-fulltext.test.d.ts +1 -0
- package/dist/clis/notebooklm/source-fulltext.test.js +106 -0
- package/dist/clis/notebooklm/source-get.d.ts +1 -0
- package/dist/clis/notebooklm/source-get.js +40 -0
- package/dist/clis/notebooklm/source-get.test.d.ts +1 -0
- package/dist/clis/notebooklm/source-get.test.js +84 -0
- package/dist/clis/notebooklm/source-guide.d.ts +1 -0
- package/dist/clis/notebooklm/source-guide.js +44 -0
- package/dist/clis/notebooklm/source-guide.test.d.ts +1 -0
- package/dist/clis/notebooklm/source-guide.test.js +104 -0
- package/dist/clis/notebooklm/source-list.d.ts +1 -0
- package/dist/clis/notebooklm/source-list.js +30 -0
- package/dist/clis/notebooklm/status.d.ts +1 -0
- package/dist/clis/notebooklm/status.js +31 -0
- package/dist/clis/notebooklm/summary.d.ts +1 -0
- package/dist/clis/notebooklm/summary.js +30 -0
- package/dist/clis/notebooklm/summary.test.d.ts +1 -0
- package/dist/clis/notebooklm/summary.test.js +78 -0
- package/dist/clis/notebooklm/utils.d.ts +37 -0
- package/dist/clis/notebooklm/utils.js +739 -0
- package/dist/clis/notebooklm/utils.test.d.ts +1 -0
- package/dist/clis/notebooklm/utils.test.js +390 -0
- package/dist/clis/substack/utils.d.ts +4 -0
- package/dist/clis/substack/utils.js +8 -2
- package/dist/clis/substack/utils.test.d.ts +1 -0
- package/dist/clis/substack/utils.test.js +46 -0
- package/dist/clis/v2ex/hot.yaml +4 -1
- package/dist/clis/v2ex/latest.yaml +4 -1
- package/dist/clis/v2ex/topic.yaml +6 -1
- package/dist/clis/weixin/download.d.ts +9 -0
- package/dist/clis/weixin/download.js +76 -6
- package/dist/clis/weread/book.js +108 -2
- package/dist/clis/weread/commands.test.js +262 -152
- package/dist/clis/weread/utils.d.ts +10 -0
- package/dist/clis/weread/utils.js +27 -7
- package/dist/clis/xiaohongshu/comments.d.ts +3 -0
- package/dist/clis/xiaohongshu/comments.js +76 -17
- package/dist/clis/xiaohongshu/comments.test.js +70 -9
- package/dist/clis/xiaohongshu/download.d.ts +4 -1
- package/dist/clis/xiaohongshu/download.js +83 -22
- package/dist/clis/xiaohongshu/download.test.d.ts +1 -0
- package/dist/clis/xiaohongshu/download.test.js +75 -0
- package/dist/clis/xiaohongshu/note-helpers.d.ts +12 -0
- package/dist/clis/xiaohongshu/note-helpers.js +23 -0
- package/dist/clis/xiaohongshu/note.d.ts +7 -0
- package/dist/clis/xiaohongshu/note.js +76 -0
- package/dist/clis/xiaohongshu/note.test.d.ts +1 -0
- package/dist/clis/xiaohongshu/note.test.js +136 -0
- package/dist/clis/xiaohongshu/search.js +9 -0
- package/dist/clis/xiaohongshu/search.test.js +10 -4
- package/dist/clis/youtube/search.js +57 -17
- package/dist/clis/zhihu/question.js +19 -17
- package/dist/clis/zhihu/question.test.d.ts +1 -0
- package/dist/clis/zhihu/question.test.js +54 -0
- package/dist/commanderAdapter.js +9 -0
- package/dist/commanderAdapter.test.js +25 -0
- package/dist/commands/daemon.d.ts +9 -0
- package/dist/commands/daemon.js +124 -0
- package/dist/commands/daemon.test.d.ts +1 -0
- package/dist/commands/daemon.test.js +185 -0
- package/dist/completion.js +3 -1
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +2 -0
- package/dist/daemon.d.ts +1 -1
- package/dist/daemon.js +25 -14
- package/dist/daemon.test.d.ts +1 -0
- package/dist/daemon.test.js +65 -0
- package/dist/discovery.d.ts +9 -0
- package/dist/discovery.js +47 -2
- package/dist/electron-apps.d.ts +29 -0
- package/dist/electron-apps.js +65 -0
- package/dist/electron-apps.test.d.ts +1 -0
- package/dist/electron-apps.test.js +43 -0
- package/dist/engine.test.js +41 -9
- package/dist/execution.js +20 -16
- package/dist/extension-manifest-regression.test.js +1 -0
- package/dist/idle-manager.d.ts +19 -0
- package/dist/idle-manager.js +54 -0
- package/dist/launcher.d.ts +36 -0
- package/dist/launcher.js +152 -0
- package/dist/launcher.test.d.ts +1 -0
- package/dist/launcher.test.js +57 -0
- package/dist/main.js +3 -3
- package/dist/registry.d.ts +1 -0
- package/dist/registry.js +31 -3
- package/dist/registry.test.js +13 -0
- package/dist/runtime.d.ts +5 -3
- package/dist/runtime.js +12 -5
- package/dist/serialization.d.ts +1 -0
- package/dist/serialization.js +3 -0
- package/dist/serialization.test.js +17 -1
- package/dist/tui.d.ts +7 -0
- package/dist/tui.js +52 -0
- package/dist/tui.test.d.ts +1 -0
- package/dist/tui.test.js +19 -0
- package/dist/weixin-download.test.js +14 -0
- package/docs/.vitepress/config.mts +1 -0
- package/docs/adapters/browser/notebooklm.md +69 -0
- package/docs/adapters/browser/xiaohongshu.md +19 -10
- package/docs/adapters/index.md +67 -66
- package/docs/guide/browser-bridge.md +12 -0
- package/docs/guide/troubleshooting.md +9 -4
- package/docs/superpowers/plans/2026-03-31-daemon-lifecycle-redesign.md +857 -0
- package/docs/superpowers/specs/2026-03-31-daemon-lifecycle-redesign.md +208 -0
- package/docs/zh/guide/browser-bridge.md +12 -0
- package/extension/dist/background.js +250 -11
- package/extension/manifest.json +2 -1
- package/extension/src/background.test.ts +202 -2
- package/extension/src/background.ts +175 -10
- package/extension/src/cdp.test.ts +75 -0
- package/extension/src/cdp.ts +89 -3
- package/extension/src/protocol.ts +7 -5
- package/package.json +1 -1
- package/src/browser/cdp.ts +24 -17
- package/src/browser/daemon-client.ts +7 -1
- package/src/browser/dom-helpers.test.ts +15 -1
- package/src/browser/dom-helpers.ts +1 -0
- package/src/browser/mcp.ts +18 -13
- package/src/browser/page.test.ts +58 -0
- package/src/browser/page.ts +18 -2
- package/src/browser/stealth.test.ts +153 -0
- package/src/browser/stealth.ts +198 -0
- package/src/browser.test.ts +1 -1
- package/src/build-manifest.test.ts +2 -0
- package/src/build-manifest.ts +6 -1
- package/src/cli.ts +21 -3
- package/src/clis/antigravity/SKILL.md +3 -12
- package/src/clis/antigravity/serve.ts +5 -10
- package/src/clis/bilibili/subtitle.test.ts +60 -0
- package/src/clis/bilibili/subtitle.ts +4 -0
- package/src/clis/chatwise/ask.ts +0 -2
- package/src/clis/chatwise/export.ts +0 -2
- package/src/clis/chatwise/history.ts +0 -2
- package/src/clis/chatwise/model.ts +0 -2
- package/src/clis/chatwise/new.ts +1 -2
- package/src/clis/chatwise/read.ts +0 -2
- package/src/clis/chatwise/screenshot.ts +1 -2
- package/src/clis/chatwise/send.ts +0 -2
- package/src/clis/chatwise/status.ts +1 -2
- package/src/clis/ctrip/search.test.ts +73 -0
- package/src/clis/ctrip/search.ts +97 -47
- package/src/clis/douyin/_shared/sts2.test.ts +31 -0
- package/src/clis/douyin/_shared/sts2.ts +11 -3
- package/src/clis/douyin/activities.test.ts +41 -1
- package/src/clis/douyin/activities.ts +12 -3
- package/src/clis/douyin/collections.test.ts +35 -2
- package/src/clis/douyin/collections.ts +1 -1
- package/src/clis/douyin/draft.test.ts +444 -2
- package/src/clis/douyin/draft.ts +382 -218
- package/src/clis/douyin/hashtag.test.ts +42 -2
- package/src/clis/douyin/hashtag.ts +11 -3
- package/src/clis/douyin/profile.test.ts +43 -1
- package/src/clis/douyin/profile.ts +9 -2
- package/src/clis/douyin/videos.test.ts +52 -2
- package/src/clis/douyin/videos.ts +49 -15
- package/src/clis/facebook/search.test.ts +70 -0
- package/src/clis/facebook/search.yaml +4 -3
- package/src/clis/instagram/download.test.ts +159 -0
- package/src/clis/instagram/download.ts +286 -0
- package/src/clis/notebooklm/bind-current.test.ts +43 -0
- package/src/clis/notebooklm/bind-current.ts +36 -0
- package/src/clis/notebooklm/binding.test.ts +53 -0
- package/src/clis/notebooklm/compat.test.ts +19 -0
- package/src/clis/notebooklm/current.ts +38 -0
- package/src/clis/notebooklm/get.ts +53 -0
- package/src/clis/notebooklm/history.test.ts +70 -0
- package/src/clis/notebooklm/history.ts +36 -0
- package/src/clis/notebooklm/list.ts +40 -0
- package/src/clis/notebooklm/note-list.test.ts +64 -0
- package/src/clis/notebooklm/note-list.ts +42 -0
- package/src/clis/notebooklm/notes-get.test.ts +88 -0
- package/src/clis/notebooklm/notes-get.ts +67 -0
- package/src/clis/notebooklm/rpc.test.ts +126 -0
- package/src/clis/notebooklm/rpc.ts +286 -0
- package/src/clis/notebooklm/shared.ts +98 -0
- package/src/clis/notebooklm/source-fulltext.test.ts +123 -0
- package/src/clis/notebooklm/source-fulltext.ts +69 -0
- package/src/clis/notebooklm/source-get.test.ts +100 -0
- package/src/clis/notebooklm/source-get.ts +60 -0
- package/src/clis/notebooklm/source-guide.test.ts +121 -0
- package/src/clis/notebooklm/source-guide.ts +69 -0
- package/src/clis/notebooklm/source-list.ts +45 -0
- package/src/clis/notebooklm/status.ts +34 -0
- package/src/clis/notebooklm/summary.test.ts +94 -0
- package/src/clis/notebooklm/summary.ts +45 -0
- package/src/clis/notebooklm/utils.test.ts +446 -0
- package/src/clis/notebooklm/utils.ts +893 -0
- package/src/clis/substack/utils.test.ts +54 -0
- package/src/clis/substack/utils.ts +10 -2
- package/src/clis/v2ex/hot.yaml +4 -1
- package/src/clis/v2ex/latest.yaml +4 -1
- package/src/clis/v2ex/topic.yaml +6 -1
- package/src/clis/weixin/download.ts +95 -6
- package/src/clis/weread/book.ts +142 -2
- package/src/clis/weread/commands.test.ts +314 -154
- package/src/clis/weread/utils.ts +33 -4
- package/src/clis/xiaohongshu/comments.test.ts +85 -9
- package/src/clis/xiaohongshu/comments.ts +76 -17
- package/src/clis/xiaohongshu/download.test.ts +96 -0
- package/src/clis/xiaohongshu/download.ts +83 -22
- package/src/clis/xiaohongshu/note-helpers.ts +25 -0
- package/src/clis/xiaohongshu/note.test.ts +164 -0
- package/src/clis/xiaohongshu/note.ts +86 -0
- package/src/clis/xiaohongshu/search.test.ts +11 -4
- package/src/clis/xiaohongshu/search.ts +13 -0
- package/src/clis/youtube/search.ts +57 -17
- package/src/clis/zhihu/question.test.ts +71 -0
- package/src/clis/zhihu/question.ts +27 -15
- package/src/commanderAdapter.test.ts +30 -0
- package/src/commanderAdapter.ts +7 -0
- package/src/commands/daemon.test.ts +238 -0
- package/src/commands/daemon.ts +135 -0
- package/src/completion.ts +2 -1
- package/src/constants.ts +3 -0
- package/src/daemon.test.ts +88 -0
- package/src/daemon.ts +26 -14
- package/src/discovery.ts +52 -2
- package/src/electron-apps.test.ts +50 -0
- package/src/electron-apps.ts +89 -0
- package/src/engine.test.ts +45 -9
- package/src/execution.ts +24 -19
- package/src/extension-manifest-regression.test.ts +1 -0
- package/src/idle-manager.ts +60 -0
- package/src/launcher.test.ts +67 -0
- package/src/launcher.ts +185 -0
- package/src/main.ts +3 -2
- package/src/registry.test.ts +15 -0
- package/src/registry.ts +32 -3
- package/src/runtime.ts +13 -7
- package/src/serialization.test.ts +19 -1
- package/src/serialization.ts +2 -0
- package/src/tui.test.ts +23 -0
- package/src/tui.ts +65 -0
- package/src/weixin-download.test.ts +27 -0
- package/tests/e2e/browser-public-extended.test.ts +6 -2
- package/chatwise-opencli.ps1 +0 -82
- package/dist/clis/chatwise/shared.d.ts +0 -2
- package/dist/clis/chatwise/shared.js +0 -6
- package/src/clis/chatwise/shared.ts +0 -8
package/dist/cli.js
CHANGED
|
@@ -15,6 +15,7 @@ import { printCompletionScript } from './completion.js';
|
|
|
15
15
|
import { loadExternalClis, executeExternalCli, installExternalCli, registerExternalCli, isBinaryInstalled } from './external.js';
|
|
16
16
|
import { registerAllCommands } from './commanderAdapter.js';
|
|
17
17
|
import { EXIT_CODES, getErrorMessage } from './errors.js';
|
|
18
|
+
import { daemonStatus, daemonStop, daemonRestart } from './commands/daemon.js';
|
|
18
19
|
export function runCli(BUILTIN_CLIS, USER_CLIS) {
|
|
19
20
|
const program = new Command();
|
|
20
21
|
// enablePositionalOptions: prevents parent from consuming flags meant for subcommands;
|
|
@@ -32,7 +33,7 @@ export function runCli(BUILTIN_CLIS, USER_CLIS) {
|
|
|
32
33
|
.option('--json', 'JSON output (deprecated)')
|
|
33
34
|
.action((opts) => {
|
|
34
35
|
const registry = getRegistry();
|
|
35
|
-
const commands = [...registry.values()].sort((a, b) => fullName(a).localeCompare(fullName(b)));
|
|
36
|
+
const commands = [...new Set(registry.values())].sort((a, b) => fullName(a).localeCompare(fullName(b)));
|
|
36
37
|
const fmt = opts.json && opts.format === 'table' ? 'json' : opts.format;
|
|
37
38
|
const isStructured = fmt === 'json' || fmt === 'yaml';
|
|
38
39
|
if (fmt !== 'table') {
|
|
@@ -42,6 +43,7 @@ export function runCli(BUILTIN_CLIS, USER_CLIS) {
|
|
|
42
43
|
command: fullName(c),
|
|
43
44
|
site: c.site,
|
|
44
45
|
name: c.name,
|
|
46
|
+
aliases: c.aliases?.join(', ') ?? '',
|
|
45
47
|
description: c.description,
|
|
46
48
|
strategy: strategyLabel(c),
|
|
47
49
|
browser: !!c.browser,
|
|
@@ -49,7 +51,7 @@ export function runCli(BUILTIN_CLIS, USER_CLIS) {
|
|
|
49
51
|
}));
|
|
50
52
|
renderOutput(rows, {
|
|
51
53
|
fmt,
|
|
52
|
-
columns: ['command', 'site', 'name', 'description', 'strategy', 'browser', 'args',
|
|
54
|
+
columns: ['command', 'site', 'name', 'aliases', 'description', 'strategy', 'browser', 'args',
|
|
53
55
|
...(isStructured ? ['columns', 'domain'] : [])],
|
|
54
56
|
title: 'opencli/list',
|
|
55
57
|
source: 'opencli list',
|
|
@@ -73,7 +75,8 @@ export function runCli(BUILTIN_CLIS, USER_CLIS) {
|
|
|
73
75
|
const tag = label === 'public'
|
|
74
76
|
? chalk.green('[public]')
|
|
75
77
|
: chalk.yellow(`[${label}]`);
|
|
76
|
-
|
|
78
|
+
const aliases = cmd.aliases?.length ? chalk.dim(` (aliases: ${cmd.aliases.join(', ')})`) : '';
|
|
79
|
+
console.log(` ${cmd.name} ${tag}${aliases}${cmd.description ? chalk.dim(` — ${cmd.description}`) : ''}`);
|
|
77
80
|
}
|
|
78
81
|
console.log();
|
|
79
82
|
}
|
|
@@ -411,6 +414,20 @@ export function runCli(BUILTIN_CLIS, USER_CLIS) {
|
|
|
411
414
|
process.exitCode = EXIT_CODES.GENERIC_ERROR;
|
|
412
415
|
}
|
|
413
416
|
});
|
|
417
|
+
// ── Built-in: daemon ──────────────────────────────────────────────────────
|
|
418
|
+
const daemonCmd = program.command('daemon').description('Manage the opencli daemon');
|
|
419
|
+
daemonCmd
|
|
420
|
+
.command('status')
|
|
421
|
+
.description('Show daemon status')
|
|
422
|
+
.action(async () => { await daemonStatus(); });
|
|
423
|
+
daemonCmd
|
|
424
|
+
.command('stop')
|
|
425
|
+
.description('Stop the daemon')
|
|
426
|
+
.action(async () => { await daemonStop(); });
|
|
427
|
+
daemonCmd
|
|
428
|
+
.command('restart')
|
|
429
|
+
.description('Restart the daemon')
|
|
430
|
+
.action(async () => { await daemonRestart(); });
|
|
414
431
|
// ── External CLIs ─────────────────────────────────────────────────────────
|
|
415
432
|
const externalClis = loadExternalClis();
|
|
416
433
|
program
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* and returns it in Anthropic format.
|
|
7
7
|
*
|
|
8
8
|
* Usage:
|
|
9
|
-
*
|
|
9
|
+
* opencli antigravity serve --port 8082
|
|
10
10
|
* ANTHROPIC_BASE_URL=http://localhost:8082 claude
|
|
11
11
|
*/
|
|
12
12
|
export declare function startServe(opts?: {
|
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
* and returns it in Anthropic format.
|
|
7
7
|
*
|
|
8
8
|
* Usage:
|
|
9
|
-
*
|
|
9
|
+
* opencli antigravity serve --port 8082
|
|
10
10
|
* ANTHROPIC_BASE_URL=http://localhost:8082 claude
|
|
11
11
|
*/
|
|
12
12
|
import { createServer } from 'node:http';
|
|
13
13
|
import { CDPBridge } from '../../browser/cdp.js';
|
|
14
|
+
import { resolveElectronEndpoint } from '../../launcher.js';
|
|
14
15
|
import { EXIT_CODES, getErrorMessage } from '../../errors.js';
|
|
15
16
|
// ─── Helpers ─────────────────────────────────────────────────────────
|
|
16
17
|
function generateMsgId() {
|
|
@@ -365,11 +366,7 @@ export async function startServe(opts = {}) {
|
|
|
365
366
|
page = null;
|
|
366
367
|
}
|
|
367
368
|
}
|
|
368
|
-
const endpoint =
|
|
369
|
-
if (!endpoint) {
|
|
370
|
-
throw new Error('OPENCLI_CDP_ENDPOINT is not set.\n' +
|
|
371
|
-
'Usage: OPENCLI_CDP_ENDPOINT=http://127.0.0.1:9224 opencli antigravity serve');
|
|
372
|
-
}
|
|
369
|
+
const endpoint = await resolveElectronEndpoint('antigravity');
|
|
373
370
|
// Note: Antigravity chat panel lives inside editor windows, not in Launchpad.
|
|
374
371
|
// If multiple editor windows are open, set OPENCLI_CDP_TARGET to the window title.
|
|
375
372
|
if (process.env.OPENCLI_CDP_TARGET) {
|
|
@@ -386,7 +383,7 @@ export async function startServe(opts = {}) {
|
|
|
386
383
|
console.error(`[serve] Connecting via CDP (target pattern: "${process.env.OPENCLI_CDP_TARGET}")...`);
|
|
387
384
|
cdp = new CDPBridge();
|
|
388
385
|
try {
|
|
389
|
-
page = await cdp.connect({ timeout: 15_000 });
|
|
386
|
+
page = await cdp.connect({ timeout: 15_000, cdpEndpoint: endpoint });
|
|
390
387
|
}
|
|
391
388
|
catch (err) {
|
|
392
389
|
cdp = null;
|
|
@@ -396,7 +393,7 @@ export async function startServe(opts = {}) {
|
|
|
396
393
|
throw new Error(isRefused
|
|
397
394
|
? `Cannot connect to Antigravity at ${endpoint}.\n` +
|
|
398
395
|
' 1. Make sure Antigravity is running\n' +
|
|
399
|
-
' 2. Launch with: --remote-debugging-port=
|
|
396
|
+
' 2. Launch with: --remote-debugging-port=9234'
|
|
400
397
|
: `CDP connection failed: ${errMsg}`);
|
|
401
398
|
}
|
|
402
399
|
console.error('[serve] ✅ CDP connected.');
|
|
@@ -33,8 +33,12 @@ cli({
|
|
|
33
33
|
if (payload.code !== 0) {
|
|
34
34
|
throw new CommandExecutionError(`获取视频播放信息失败: ${payload.message} (${payload.code})`);
|
|
35
35
|
}
|
|
36
|
+
const needLoginSubtitle = payload.data?.need_login_subtitle === true;
|
|
36
37
|
const subtitles = payload.data?.subtitle?.subtitles || [];
|
|
37
38
|
if (subtitles.length === 0) {
|
|
39
|
+
if (needLoginSubtitle) {
|
|
40
|
+
throw new AuthRequiredError('bilibili.com', 'Bilibili subtitles are hidden behind login for this video. Please log in to bilibili.com in Chrome and retry.');
|
|
41
|
+
}
|
|
38
42
|
throw new EmptyResultError('bilibili subtitle', '此视频没有发现外挂或智能字幕。');
|
|
39
43
|
}
|
|
40
44
|
// 4. 选择目标字幕语言
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import './subtitle.js';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { AuthRequiredError, EmptyResultError } from '../../errors.js';
|
|
3
|
+
const { mockApiGet } = vi.hoisted(() => ({
|
|
4
|
+
mockApiGet: vi.fn(),
|
|
5
|
+
}));
|
|
6
|
+
vi.mock('./utils.js', () => ({
|
|
7
|
+
apiGet: mockApiGet,
|
|
8
|
+
}));
|
|
9
|
+
import { getRegistry } from '../../registry.js';
|
|
10
|
+
import './subtitle.js';
|
|
11
|
+
describe('bilibili subtitle', () => {
|
|
12
|
+
const command = getRegistry().get('bilibili/subtitle');
|
|
13
|
+
const page = {
|
|
14
|
+
goto: vi.fn().mockResolvedValue(undefined),
|
|
15
|
+
evaluate: vi.fn(),
|
|
16
|
+
};
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
mockApiGet.mockReset();
|
|
19
|
+
page.goto.mockClear();
|
|
20
|
+
page.evaluate.mockReset();
|
|
21
|
+
});
|
|
22
|
+
it('throws AuthRequiredError when bilibili hides subtitles behind login', async () => {
|
|
23
|
+
page.evaluate.mockResolvedValueOnce(123456);
|
|
24
|
+
mockApiGet.mockResolvedValueOnce({
|
|
25
|
+
code: 0,
|
|
26
|
+
data: {
|
|
27
|
+
need_login_subtitle: true,
|
|
28
|
+
subtitle: {
|
|
29
|
+
subtitles: [],
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
await expect(command.func(page, { bvid: 'BV1GbXPBeEZm' })).rejects.toSatisfy((err) => err instanceof AuthRequiredError && /login|登录/i.test(err.message));
|
|
34
|
+
});
|
|
35
|
+
it('throws EmptyResultError when a video truly has no subtitles', async () => {
|
|
36
|
+
page.evaluate.mockResolvedValueOnce(123456);
|
|
37
|
+
mockApiGet.mockResolvedValueOnce({
|
|
38
|
+
code: 0,
|
|
39
|
+
data: {
|
|
40
|
+
need_login_subtitle: false,
|
|
41
|
+
subtitle: {
|
|
42
|
+
subtitles: [],
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
await expect(command.func(page, { bvid: 'BV1GbXPBeEZm' })).rejects.toThrow(EmptyResultError);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { cli, Strategy } from '../../registry.js';
|
|
2
2
|
import { SelectorError } from '../../errors.js';
|
|
3
|
-
import { chatwiseRequiredEnv } from './shared.js';
|
|
4
3
|
export const askCommand = cli({
|
|
5
4
|
site: 'chatwise',
|
|
6
5
|
name: 'ask',
|
|
@@ -8,7 +7,6 @@ export const askCommand = cli({
|
|
|
8
7
|
domain: 'localhost',
|
|
9
8
|
strategy: Strategy.UI,
|
|
10
9
|
browser: true,
|
|
11
|
-
requiredEnv: chatwiseRequiredEnv,
|
|
12
10
|
args: [
|
|
13
11
|
{ name: 'text', required: true, positional: true, help: 'Prompt to send' },
|
|
14
12
|
{ name: 'timeout', required: false, help: 'Max seconds to wait (default: 30)', default: '30' },
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
import { cli, Strategy } from '../../registry.js';
|
|
3
|
-
import { chatwiseRequiredEnv } from './shared.js';
|
|
4
3
|
export const exportCommand = cli({
|
|
5
4
|
site: 'chatwise',
|
|
6
5
|
name: 'export',
|
|
@@ -8,7 +7,6 @@ export const exportCommand = cli({
|
|
|
8
7
|
domain: 'localhost',
|
|
9
8
|
strategy: Strategy.UI,
|
|
10
9
|
browser: true,
|
|
11
|
-
requiredEnv: chatwiseRequiredEnv,
|
|
12
10
|
args: [
|
|
13
11
|
{ name: 'output', required: false, help: 'Output file (default: /tmp/chatwise-export.md)' },
|
|
14
12
|
],
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { cli, Strategy } from '../../registry.js';
|
|
2
|
-
import { chatwiseRequiredEnv } from './shared.js';
|
|
3
2
|
export const historyCommand = cli({
|
|
4
3
|
site: 'chatwise',
|
|
5
4
|
name: 'history',
|
|
@@ -7,7 +6,6 @@ export const historyCommand = cli({
|
|
|
7
6
|
domain: 'localhost',
|
|
8
7
|
strategy: Strategy.UI,
|
|
9
8
|
browser: true,
|
|
10
|
-
requiredEnv: chatwiseRequiredEnv,
|
|
11
9
|
args: [],
|
|
12
10
|
columns: ['Index', 'Title'],
|
|
13
11
|
func: async (page) => {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { cli, Strategy } from '../../registry.js';
|
|
2
2
|
import { SelectorError } from '../../errors.js';
|
|
3
|
-
import { chatwiseRequiredEnv } from './shared.js';
|
|
4
3
|
export const modelCommand = cli({
|
|
5
4
|
site: 'chatwise',
|
|
6
5
|
name: 'model',
|
|
@@ -8,7 +7,6 @@ export const modelCommand = cli({
|
|
|
8
7
|
domain: 'localhost',
|
|
9
8
|
strategy: Strategy.UI,
|
|
10
9
|
browser: true,
|
|
11
|
-
requiredEnv: chatwiseRequiredEnv,
|
|
12
10
|
args: [
|
|
13
11
|
{ name: 'model-name', required: false, positional: true, help: 'Model to switch to (e.g. gpt-4, claude-3)' },
|
|
14
12
|
],
|
|
@@ -1,3 +1,2 @@
|
|
|
1
1
|
import { makeNewCommand } from '../_shared/desktop-commands.js';
|
|
2
|
-
|
|
3
|
-
export const newCommand = makeNewCommand('chatwise', 'ChatWise conversation', { requiredEnv: chatwiseRequiredEnv });
|
|
2
|
+
export const newCommand = makeNewCommand('chatwise', 'ChatWise conversation');
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { cli, Strategy } from '../../registry.js';
|
|
2
|
-
import { chatwiseRequiredEnv } from './shared.js';
|
|
3
2
|
export const readCommand = cli({
|
|
4
3
|
site: 'chatwise',
|
|
5
4
|
name: 'read',
|
|
@@ -7,7 +6,6 @@ export const readCommand = cli({
|
|
|
7
6
|
domain: 'localhost',
|
|
8
7
|
strategy: Strategy.UI,
|
|
9
8
|
browser: true,
|
|
10
|
-
requiredEnv: chatwiseRequiredEnv,
|
|
11
9
|
args: [],
|
|
12
10
|
columns: ['Content'],
|
|
13
11
|
func: async (page) => {
|
|
@@ -1,3 +1,2 @@
|
|
|
1
1
|
import { makeScreenshotCommand } from '../_shared/desktop-commands.js';
|
|
2
|
-
|
|
3
|
-
export const screenshotCommand = makeScreenshotCommand('chatwise', 'ChatWise', { requiredEnv: chatwiseRequiredEnv });
|
|
2
|
+
export const screenshotCommand = makeScreenshotCommand('chatwise', 'ChatWise');
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { cli, Strategy } from '../../registry.js';
|
|
2
2
|
import { SelectorError } from '../../errors.js';
|
|
3
|
-
import { chatwiseRequiredEnv } from './shared.js';
|
|
4
3
|
export const sendCommand = cli({
|
|
5
4
|
site: 'chatwise',
|
|
6
5
|
name: 'send',
|
|
@@ -8,7 +7,6 @@ export const sendCommand = cli({
|
|
|
8
7
|
domain: 'localhost',
|
|
9
8
|
strategy: Strategy.UI,
|
|
10
9
|
browser: true,
|
|
11
|
-
requiredEnv: chatwiseRequiredEnv,
|
|
12
10
|
args: [{ name: 'text', required: true, positional: true, help: 'Message to send' }],
|
|
13
11
|
columns: ['Status', 'InjectedText'],
|
|
14
12
|
func: async (page, kwargs) => {
|
|
@@ -1,3 +1,2 @@
|
|
|
1
1
|
import { makeStatusCommand } from '../_shared/desktop-commands.js';
|
|
2
|
-
|
|
3
|
-
export const statusCommand = makeStatusCommand('chatwise', 'ChatWise Desktop', { requiredEnv: chatwiseRequiredEnv });
|
|
2
|
+
export const statusCommand = makeStatusCommand('chatwise', 'ChatWise Desktop');
|
|
@@ -1 +1,14 @@
|
|
|
1
|
+
declare function clampLimit(raw: unknown, fallback?: number): number;
|
|
2
|
+
declare function mapSearchResults(results: unknown[], limit: number): {
|
|
3
|
+
rank: number;
|
|
4
|
+
name: string;
|
|
5
|
+
type: string;
|
|
6
|
+
score: string | number;
|
|
7
|
+
price: string | number;
|
|
8
|
+
url: string;
|
|
9
|
+
}[];
|
|
10
|
+
export declare const __test__: {
|
|
11
|
+
clampLimit: typeof clampLimit;
|
|
12
|
+
mapSearchResults: typeof mapSearchResults;
|
|
13
|
+
};
|
|
1
14
|
export {};
|
|
@@ -1,61 +1,86 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 携程旅行搜索 —
|
|
2
|
+
* 携程旅行搜索 — public destination and hotel suggestion lookup.
|
|
3
3
|
*/
|
|
4
|
+
import { ArgumentError, CliError, EmptyResultError } from '../../errors.js';
|
|
4
5
|
import { cli, Strategy } from '../../registry.js';
|
|
6
|
+
function clampLimit(raw, fallback = 15) {
|
|
7
|
+
const parsed = Number(raw);
|
|
8
|
+
if (!Number.isFinite(parsed))
|
|
9
|
+
return fallback;
|
|
10
|
+
return Math.max(1, Math.min(Math.floor(parsed), 50));
|
|
11
|
+
}
|
|
12
|
+
function mapSearchResults(results, limit) {
|
|
13
|
+
return results
|
|
14
|
+
.filter((item) => !!item && typeof item === 'object')
|
|
15
|
+
.slice(0, limit)
|
|
16
|
+
.map((item, index) => ({
|
|
17
|
+
rank: index + 1,
|
|
18
|
+
name: String(item.displayName || item.word || item.cityName || '').replace(/\s+/g, ' ').trim(),
|
|
19
|
+
type: String(item.displayType || item.type || '').replace(/\s+/g, ' ').trim(),
|
|
20
|
+
score: item.commentScore ?? item.cStar ?? '',
|
|
21
|
+
price: item.price ?? item.minPrice ?? '',
|
|
22
|
+
url: '',
|
|
23
|
+
}))
|
|
24
|
+
.filter((item) => item.name);
|
|
25
|
+
}
|
|
5
26
|
cli({
|
|
6
27
|
site: 'ctrip',
|
|
7
28
|
name: 'search',
|
|
8
|
-
description: '
|
|
9
|
-
|
|
10
|
-
|
|
29
|
+
description: '搜索携程目的地、景区和酒店联想结果',
|
|
30
|
+
strategy: Strategy.PUBLIC,
|
|
31
|
+
browser: false,
|
|
11
32
|
args: [
|
|
12
33
|
{ name: 'query', required: true, positional: true, help: 'Search keyword (city or attraction)' },
|
|
13
34
|
{ name: 'limit', type: 'int', default: 15, help: 'Number of results' },
|
|
14
35
|
],
|
|
15
36
|
columns: ['rank', 'name', 'type', 'score', 'price', 'url'],
|
|
16
|
-
func: async (
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return
|
|
37
|
+
func: async (_page, kwargs) => {
|
|
38
|
+
const query = String(kwargs.query || '').trim();
|
|
39
|
+
if (!query) {
|
|
40
|
+
throw new ArgumentError('Search keyword cannot be empty');
|
|
41
|
+
}
|
|
42
|
+
const limit = clampLimit(kwargs.limit);
|
|
43
|
+
const response = await fetch('https://m.ctrip.com/restapi/soa2/21881/json/gaHotelSearchEngine', {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: {
|
|
46
|
+
'content-type': 'application/json',
|
|
47
|
+
},
|
|
48
|
+
body: JSON.stringify({
|
|
49
|
+
keyword: query,
|
|
50
|
+
searchType: 'D',
|
|
51
|
+
platform: 'online',
|
|
52
|
+
pageID: '102001',
|
|
53
|
+
head: {
|
|
54
|
+
Locale: 'zh-CN',
|
|
55
|
+
LocaleController: 'zh_cn',
|
|
56
|
+
Currency: 'CNY',
|
|
57
|
+
PageId: '102001',
|
|
58
|
+
clientID: 'opencli-ctrip-search',
|
|
59
|
+
group: 'ctrip',
|
|
60
|
+
Frontend: {
|
|
61
|
+
sessionID: 1,
|
|
62
|
+
pvid: 1,
|
|
63
|
+
},
|
|
64
|
+
HotelExtension: {
|
|
65
|
+
group: 'CTRIP',
|
|
66
|
+
WebpSupport: false,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
}),
|
|
70
|
+
});
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
throw new CliError('FETCH_ERROR', `ctrip search failed with status ${response.status}`, 'Retry the command or verify ctrip.com is reachable');
|
|
73
|
+
}
|
|
74
|
+
const payload = await response.json();
|
|
75
|
+
const rawResults = Array.isArray(payload?.Response?.searchResults) ? payload.Response.searchResults : [];
|
|
76
|
+
const results = mapSearchResults(rawResults, limit);
|
|
77
|
+
if (!results.length) {
|
|
78
|
+
throw new EmptyResultError('ctrip search', 'Try a destination, scenic spot, or hotel keyword such as "苏州" or "朱家尖"');
|
|
79
|
+
}
|
|
80
|
+
return results;
|
|
60
81
|
},
|
|
61
82
|
});
|
|
83
|
+
export const __test__ = {
|
|
84
|
+
clampLimit,
|
|
85
|
+
mapSearchResults,
|
|
86
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import './search.js';
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { getRegistry } from '../../registry.js';
|
|
3
|
+
import './search.js';
|
|
4
|
+
describe('ctrip search', () => {
|
|
5
|
+
const command = getRegistry().get('ctrip/search');
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
vi.unstubAllGlobals();
|
|
8
|
+
});
|
|
9
|
+
it('maps live endpoint results into ranked rows', async () => {
|
|
10
|
+
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(JSON.stringify({
|
|
11
|
+
Response: {
|
|
12
|
+
searchResults: [
|
|
13
|
+
{
|
|
14
|
+
displayName: '苏州, 江苏, 中国',
|
|
15
|
+
displayType: '城市',
|
|
16
|
+
commentScore: 0,
|
|
17
|
+
price: '',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
word: '姑苏区',
|
|
21
|
+
type: '行政区',
|
|
22
|
+
cStar: 4.8,
|
|
23
|
+
minPrice: 320,
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
}), { status: 200 })));
|
|
28
|
+
const result = await command.func(null, { query: '苏州', limit: 3 });
|
|
29
|
+
expect(result).toEqual([
|
|
30
|
+
{
|
|
31
|
+
rank: 1,
|
|
32
|
+
name: '苏州, 江苏, 中国',
|
|
33
|
+
type: '城市',
|
|
34
|
+
score: 0,
|
|
35
|
+
price: '',
|
|
36
|
+
url: '',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
rank: 2,
|
|
40
|
+
name: '姑苏区',
|
|
41
|
+
type: '行政区',
|
|
42
|
+
score: 4.8,
|
|
43
|
+
price: 320,
|
|
44
|
+
url: '',
|
|
45
|
+
},
|
|
46
|
+
]);
|
|
47
|
+
});
|
|
48
|
+
it('rejects empty queries', async () => {
|
|
49
|
+
await expect(command.func(null, { query: ' ', limit: 3 })).rejects.toThrow('Search keyword cannot be empty');
|
|
50
|
+
});
|
|
51
|
+
it('surfaces fetch failures as CliError', async () => {
|
|
52
|
+
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response('{}', { status: 503 })));
|
|
53
|
+
await expect(command.func(null, { query: '苏州', limit: 3 })).rejects.toMatchObject({
|
|
54
|
+
code: 'FETCH_ERROR',
|
|
55
|
+
message: 'ctrip search failed with status 503',
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
it('surfaces empty results as EmptyResultError', async () => {
|
|
59
|
+
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(JSON.stringify({
|
|
60
|
+
Response: { searchResults: [] },
|
|
61
|
+
}), { status: 200 })));
|
|
62
|
+
await expect(command.func(null, { query: '苏州', limit: 3 })).rejects.toThrow('ctrip search returned no data');
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -8,8 +8,14 @@ const STS2_URL = 'https://creator.douyin.com/aweme/mid/video/sts2/?scene=web&aid
|
|
|
8
8
|
export async function getSts2Credentials(page) {
|
|
9
9
|
const js = `fetch(${JSON.stringify(STS2_URL)}, { credentials: 'include' }).then(r => r.json())`;
|
|
10
10
|
const res = await page.evaluate(js);
|
|
11
|
-
|
|
11
|
+
const credentials = (typeof res === 'object' &&
|
|
12
|
+
res !== null &&
|
|
13
|
+
'data' in res &&
|
|
14
|
+
res.data)
|
|
15
|
+
? res.data
|
|
16
|
+
: res;
|
|
17
|
+
if (!credentials?.access_key_id) {
|
|
12
18
|
throw new AuthRequiredError('creator.douyin.com', 'STS2 credentials missing');
|
|
13
19
|
}
|
|
14
|
-
return
|
|
20
|
+
return credentials;
|
|
15
21
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { AuthRequiredError } from '../../../errors.js';
|
|
3
|
+
import { getSts2Credentials } from './sts2.js';
|
|
4
|
+
describe('douyin sts2 credentials', () => {
|
|
5
|
+
it('accepts top-level credential fields returned by creator center', async () => {
|
|
6
|
+
const page = {
|
|
7
|
+
evaluate: async () => ({
|
|
8
|
+
access_key_id: 'ak',
|
|
9
|
+
secret_access_key: 'sk',
|
|
10
|
+
session_token: 'token',
|
|
11
|
+
expired_time: 1_234_567_890,
|
|
12
|
+
}),
|
|
13
|
+
};
|
|
14
|
+
await expect(getSts2Credentials(page)).resolves.toEqual({
|
|
15
|
+
access_key_id: 'ak',
|
|
16
|
+
secret_access_key: 'sk',
|
|
17
|
+
session_token: 'token',
|
|
18
|
+
expired_time: 1_234_567_890,
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
it('still rejects responses without credential fields', async () => {
|
|
22
|
+
const page = {
|
|
23
|
+
evaluate: async () => ({ status_code: 8 }),
|
|
24
|
+
};
|
|
25
|
+
await expect(getSts2Credentials(page)).rejects.toBeInstanceOf(AuthRequiredError);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -13,8 +13,10 @@ cli({
|
|
|
13
13
|
const res = await browserFetch(page, 'GET', url);
|
|
14
14
|
return (res.activity_list ?? []).map(a => ({
|
|
15
15
|
activity_id: a.activity_id,
|
|
16
|
-
title: a.title,
|
|
17
|
-
end_time:
|
|
16
|
+
title: a.title ?? a.activity_name ?? '',
|
|
17
|
+
end_time: typeof a.end_time === 'number'
|
|
18
|
+
? new Date(a.end_time * 1000).toLocaleString('zh-CN', { timeZone: 'Asia/Tokyo' })
|
|
19
|
+
: (a.show_end_time ?? ''),
|
|
18
20
|
}));
|
|
19
21
|
},
|
|
20
22
|
});
|
|
@@ -1,7 +1,16 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
const { browserFetchMock } = vi.hoisted(() => ({
|
|
3
|
+
browserFetchMock: vi.fn(),
|
|
4
|
+
}));
|
|
5
|
+
vi.mock('./_shared/browser-fetch.js', () => ({
|
|
6
|
+
browserFetch: browserFetchMock,
|
|
7
|
+
}));
|
|
2
8
|
import { getRegistry } from '../../registry.js';
|
|
3
9
|
import './activities.js';
|
|
4
10
|
describe('douyin activities registration', () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
browserFetchMock.mockReset();
|
|
13
|
+
});
|
|
5
14
|
it('registers the activities command', () => {
|
|
6
15
|
const registry = getRegistry();
|
|
7
16
|
const cmd = [...registry.values()].find(c => c.site === 'douyin' && c.name === 'activities');
|
|
@@ -19,4 +28,28 @@ describe('douyin activities registration', () => {
|
|
|
19
28
|
const cmd = [...registry.values()].find(c => c.site === 'douyin' && c.name === 'activities');
|
|
20
29
|
expect(cmd?.strategy).toBe('cookie');
|
|
21
30
|
});
|
|
31
|
+
it('maps the current activity payload shape returned by creator center', async () => {
|
|
32
|
+
const registry = getRegistry();
|
|
33
|
+
const cmd = [...registry.values()].find(c => c.site === 'douyin' && c.name === 'activities');
|
|
34
|
+
expect(cmd?.func).toBeDefined();
|
|
35
|
+
if (!cmd?.func)
|
|
36
|
+
throw new Error('douyin activities command not registered');
|
|
37
|
+
browserFetchMock.mockResolvedValueOnce({
|
|
38
|
+
activity_list: [
|
|
39
|
+
{
|
|
40
|
+
activity_id: '200',
|
|
41
|
+
activity_name: '超会玩派对',
|
|
42
|
+
show_end_time: '2026.05.31',
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
});
|
|
46
|
+
const rows = await cmd.func({}, {});
|
|
47
|
+
expect(rows).toEqual([
|
|
48
|
+
{
|
|
49
|
+
activity_id: '200',
|
|
50
|
+
title: '超会玩派对',
|
|
51
|
+
end_time: '2026.05.31',
|
|
52
|
+
},
|
|
53
|
+
]);
|
|
54
|
+
});
|
|
22
55
|
});
|
|
@@ -11,7 +11,7 @@ cli({
|
|
|
11
11
|
],
|
|
12
12
|
columns: ['mix_id', 'name', 'item_count'],
|
|
13
13
|
func: async (page, kwargs) => {
|
|
14
|
-
const url = `https://creator.douyin.com/web/api/mix/list/?
|
|
14
|
+
const url = `https://creator.douyin.com/web/api/mix/list/?status=0,1,2,3,6&count=${kwargs.limit}&cursor=0&should_query_new_mix=1&device_platform=web&aid=1128`;
|
|
15
15
|
const res = await browserFetch(page, 'GET', url);
|
|
16
16
|
return (res.mix_list ?? []).map(m => ({
|
|
17
17
|
mix_id: m.mix_id,
|