@jackwener/opencli 1.5.6 → 1.5.7
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 +26 -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/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 +794 -513
- package/extension/src/background.test.ts +202 -2
- package/extension/src/background.ts +174 -10
- package/extension/src/cdp.ts +12 -0
- 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/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
|
@@ -7,17 +7,10 @@ description: How to automate Antigravity using OpenCLI
|
|
|
7
7
|
This skill allows AI agents to control the [Antigravity](https://github.com/chengazhen/Antigravity) desktop app (and any Electron app with CDP enabled) programmatically via OpenCLI.
|
|
8
8
|
|
|
9
9
|
## Requirements
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
/Applications/Antigravity.app/Contents/MacOS/Electron --remote-debugging-port=9224
|
|
13
|
-
\`\`\`
|
|
14
|
-
|
|
15
|
-
The agent must configure the endpoint environment variable locally before invoking standard commands:
|
|
16
|
-
\`\`\`bash
|
|
17
|
-
export OPENCLI_CDP_ENDPOINT="http://127.0.0.1:9224"
|
|
18
|
-
\`\`\`
|
|
10
|
+
opencli automatically detects, launches (with `--remote-debugging-port=9234`), and connects to Antigravity.
|
|
11
|
+
If Antigravity is already running without CDP, opencli will prompt to restart it.
|
|
19
12
|
|
|
20
|
-
If the endpoint exposes multiple inspectable targets,
|
|
13
|
+
If the endpoint exposes multiple inspectable targets, set:
|
|
21
14
|
\`\`\`bash
|
|
22
15
|
export OPENCLI_CDP_TARGET="antigravity"
|
|
23
16
|
\`\`\`
|
|
@@ -33,7 +26,6 @@ export OPENCLI_CDP_TARGET="antigravity"
|
|
|
33
26
|
|
|
34
27
|
### Generating and Saving Code
|
|
35
28
|
\`\`\`bash
|
|
36
|
-
export OPENCLI_CDP_ENDPOINT="http://127.0.0.1:9224"
|
|
37
29
|
opencli antigravity send "Write a python script to fetch HN top stories"
|
|
38
30
|
# wait ~10-15 seconds for output to render
|
|
39
31
|
opencli antigravity extract-code > hn_fetcher.py
|
|
@@ -42,6 +34,5 @@ opencli antigravity extract-code > hn_fetcher.py
|
|
|
42
34
|
### Reading Real-time Logs
|
|
43
35
|
Agents can run long-running streaming watch instances:
|
|
44
36
|
\`\`\`bash
|
|
45
|
-
export OPENCLI_CDP_ENDPOINT="http://127.0.0.1:9224"
|
|
46
37
|
opencli antigravity watch
|
|
47
38
|
\`\`\`
|
|
@@ -6,13 +6,14 @@
|
|
|
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
|
|
|
13
13
|
import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';
|
|
14
14
|
import { CDPBridge } from '../../browser/cdp.js';
|
|
15
15
|
import type { IPage } from '../../types.js';
|
|
16
|
+
import { resolveElectronEndpoint } from '../../launcher.js';
|
|
16
17
|
import { EXIT_CODES, getErrorMessage } from '../../errors.js';
|
|
17
18
|
|
|
18
19
|
// ─── Types ───────────────────────────────────────────────────────────
|
|
@@ -436,13 +437,7 @@ export async function startServe(opts: { port?: number } = {}): Promise<void> {
|
|
|
436
437
|
}
|
|
437
438
|
}
|
|
438
439
|
|
|
439
|
-
const endpoint =
|
|
440
|
-
if (!endpoint) {
|
|
441
|
-
throw new Error(
|
|
442
|
-
'OPENCLI_CDP_ENDPOINT is not set.\n' +
|
|
443
|
-
'Usage: OPENCLI_CDP_ENDPOINT=http://127.0.0.1:9224 opencli antigravity serve'
|
|
444
|
-
);
|
|
445
|
-
}
|
|
440
|
+
const endpoint = await resolveElectronEndpoint('antigravity');
|
|
446
441
|
|
|
447
442
|
// Note: Antigravity chat panel lives inside editor windows, not in Launchpad.
|
|
448
443
|
// If multiple editor windows are open, set OPENCLI_CDP_TARGET to the window title.
|
|
@@ -461,7 +456,7 @@ export async function startServe(opts: { port?: number } = {}): Promise<void> {
|
|
|
461
456
|
console.error(`[serve] Connecting via CDP (target pattern: "${process.env.OPENCLI_CDP_TARGET}")...`);
|
|
462
457
|
cdp = new CDPBridge();
|
|
463
458
|
try {
|
|
464
|
-
page = await cdp.connect({ timeout: 15_000 });
|
|
459
|
+
page = await cdp.connect({ timeout: 15_000, cdpEndpoint: endpoint });
|
|
465
460
|
} catch (err: unknown) {
|
|
466
461
|
cdp = null;
|
|
467
462
|
const errMsg = getErrorMessage(err);
|
|
@@ -471,7 +466,7 @@ export async function startServe(opts: { port?: number } = {}): Promise<void> {
|
|
|
471
466
|
isRefused
|
|
472
467
|
? `Cannot connect to Antigravity at ${endpoint}.\n` +
|
|
473
468
|
' 1. Make sure Antigravity is running\n' +
|
|
474
|
-
' 2. Launch with: --remote-debugging-port=
|
|
469
|
+
' 2. Launch with: --remote-debugging-port=9234'
|
|
475
470
|
: `CDP connection failed: ${errMsg}`
|
|
476
471
|
);
|
|
477
472
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { AuthRequiredError, EmptyResultError } from '../../errors.js';
|
|
4
|
+
|
|
5
|
+
const { mockApiGet } = vi.hoisted(() => ({
|
|
6
|
+
mockApiGet: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
vi.mock('./utils.js', () => ({
|
|
10
|
+
apiGet: mockApiGet,
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
import { getRegistry } from '../../registry.js';
|
|
14
|
+
import './subtitle.js';
|
|
15
|
+
|
|
16
|
+
describe('bilibili subtitle', () => {
|
|
17
|
+
const command = getRegistry().get('bilibili/subtitle');
|
|
18
|
+
const page = {
|
|
19
|
+
goto: vi.fn().mockResolvedValue(undefined),
|
|
20
|
+
evaluate: vi.fn(),
|
|
21
|
+
} as any;
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
mockApiGet.mockReset();
|
|
25
|
+
page.goto.mockClear();
|
|
26
|
+
page.evaluate.mockReset();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('throws AuthRequiredError when bilibili hides subtitles behind login', async () => {
|
|
30
|
+
page.evaluate.mockResolvedValueOnce(123456);
|
|
31
|
+
mockApiGet.mockResolvedValueOnce({
|
|
32
|
+
code: 0,
|
|
33
|
+
data: {
|
|
34
|
+
need_login_subtitle: true,
|
|
35
|
+
subtitle: {
|
|
36
|
+
subtitles: [],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
await expect(command!.func!(page, { bvid: 'BV1GbXPBeEZm' })).rejects.toSatisfy((err: Error) =>
|
|
42
|
+
err instanceof AuthRequiredError && /login|登录/i.test(err.message),
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('throws EmptyResultError when a video truly has no subtitles', async () => {
|
|
47
|
+
page.evaluate.mockResolvedValueOnce(123456);
|
|
48
|
+
mockApiGet.mockResolvedValueOnce({
|
|
49
|
+
code: 0,
|
|
50
|
+
data: {
|
|
51
|
+
need_login_subtitle: false,
|
|
52
|
+
subtitle: {
|
|
53
|
+
subtitles: [],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
await expect(command!.func!(page, { bvid: 'BV1GbXPBeEZm' })).rejects.toThrow(EmptyResultError);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -39,8 +39,12 @@ cli({
|
|
|
39
39
|
throw new CommandExecutionError(`获取视频播放信息失败: ${payload.message} (${payload.code})`);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
const needLoginSubtitle = payload.data?.need_login_subtitle === true;
|
|
42
43
|
const subtitles = payload.data?.subtitle?.subtitles || [];
|
|
43
44
|
if (subtitles.length === 0) {
|
|
45
|
+
if (needLoginSubtitle) {
|
|
46
|
+
throw new AuthRequiredError('bilibili.com', 'Bilibili subtitles are hidden behind login for this video. Please log in to bilibili.com in Chrome and retry.');
|
|
47
|
+
}
|
|
44
48
|
throw new EmptyResultError('bilibili subtitle', '此视频没有发现外挂或智能字幕。');
|
|
45
49
|
}
|
|
46
50
|
|
package/src/clis/chatwise/ask.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { cli, Strategy } from '../../registry.js';
|
|
2
2
|
import { SelectorError } from '../../errors.js';
|
|
3
3
|
import type { IPage } from '../../types.js';
|
|
4
|
-
import { chatwiseRequiredEnv } from './shared.js';
|
|
5
4
|
|
|
6
5
|
export const askCommand = cli({
|
|
7
6
|
site: 'chatwise',
|
|
@@ -10,7 +9,6 @@ export const askCommand = cli({
|
|
|
10
9
|
domain: 'localhost',
|
|
11
10
|
strategy: Strategy.UI,
|
|
12
11
|
browser: true,
|
|
13
|
-
requiredEnv: chatwiseRequiredEnv,
|
|
14
12
|
args: [
|
|
15
13
|
{ name: 'text', required: true, positional: true, help: 'Prompt to send' },
|
|
16
14
|
{ name: 'timeout', required: false, help: 'Max seconds to wait (default: 30)', default: '30' },
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
import { cli, Strategy } from '../../registry.js';
|
|
3
3
|
import type { IPage } from '../../types.js';
|
|
4
|
-
import { chatwiseRequiredEnv } from './shared.js';
|
|
5
4
|
|
|
6
5
|
export const exportCommand = cli({
|
|
7
6
|
site: 'chatwise',
|
|
@@ -10,7 +9,6 @@ export const exportCommand = cli({
|
|
|
10
9
|
domain: 'localhost',
|
|
11
10
|
strategy: Strategy.UI,
|
|
12
11
|
browser: true,
|
|
13
|
-
requiredEnv: chatwiseRequiredEnv,
|
|
14
12
|
args: [
|
|
15
13
|
{ name: 'output', required: false, help: 'Output file (default: /tmp/chatwise-export.md)' },
|
|
16
14
|
],
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { cli, Strategy } from '../../registry.js';
|
|
2
2
|
import type { IPage } from '../../types.js';
|
|
3
|
-
import { chatwiseRequiredEnv } from './shared.js';
|
|
4
3
|
|
|
5
4
|
export const historyCommand = cli({
|
|
6
5
|
site: 'chatwise',
|
|
@@ -9,7 +8,6 @@ export const historyCommand = cli({
|
|
|
9
8
|
domain: 'localhost',
|
|
10
9
|
strategy: Strategy.UI,
|
|
11
10
|
browser: true,
|
|
12
|
-
requiredEnv: chatwiseRequiredEnv,
|
|
13
11
|
args: [],
|
|
14
12
|
columns: ['Index', 'Title'],
|
|
15
13
|
func: async (page: IPage) => {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { cli, Strategy } from '../../registry.js';
|
|
2
2
|
import { SelectorError } from '../../errors.js';
|
|
3
3
|
import type { IPage } from '../../types.js';
|
|
4
|
-
import { chatwiseRequiredEnv } from './shared.js';
|
|
5
4
|
|
|
6
5
|
export const modelCommand = cli({
|
|
7
6
|
site: 'chatwise',
|
|
@@ -10,7 +9,6 @@ export const modelCommand = cli({
|
|
|
10
9
|
domain: 'localhost',
|
|
11
10
|
strategy: Strategy.UI,
|
|
12
11
|
browser: true,
|
|
13
|
-
requiredEnv: chatwiseRequiredEnv,
|
|
14
12
|
args: [
|
|
15
13
|
{ name: 'model-name', required: false, positional: true, help: 'Model to switch to (e.g. gpt-4, claude-3)' },
|
|
16
14
|
],
|
package/src/clis/chatwise/new.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
import { makeNewCommand } from '../_shared/desktop-commands.js';
|
|
2
|
-
import { chatwiseRequiredEnv } from './shared.js';
|
|
3
2
|
|
|
4
|
-
export const newCommand = makeNewCommand('chatwise', 'ChatWise conversation'
|
|
3
|
+
export const newCommand = makeNewCommand('chatwise', 'ChatWise conversation');
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { cli, Strategy } from '../../registry.js';
|
|
2
2
|
import type { IPage } from '../../types.js';
|
|
3
|
-
import { chatwiseRequiredEnv } from './shared.js';
|
|
4
3
|
|
|
5
4
|
export const readCommand = cli({
|
|
6
5
|
site: 'chatwise',
|
|
@@ -9,7 +8,6 @@ export const readCommand = cli({
|
|
|
9
8
|
domain: 'localhost',
|
|
10
9
|
strategy: Strategy.UI,
|
|
11
10
|
browser: true,
|
|
12
|
-
requiredEnv: chatwiseRequiredEnv,
|
|
13
11
|
args: [],
|
|
14
12
|
columns: ['Content'],
|
|
15
13
|
func: async (page: IPage) => {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
import { makeScreenshotCommand } from '../_shared/desktop-commands.js';
|
|
2
|
-
import { chatwiseRequiredEnv } from './shared.js';
|
|
3
2
|
|
|
4
|
-
export const screenshotCommand = makeScreenshotCommand('chatwise', 'ChatWise'
|
|
3
|
+
export const screenshotCommand = makeScreenshotCommand('chatwise', 'ChatWise');
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { cli, Strategy } from '../../registry.js';
|
|
2
2
|
import { SelectorError } from '../../errors.js';
|
|
3
3
|
import type { IPage } from '../../types.js';
|
|
4
|
-
import { chatwiseRequiredEnv } from './shared.js';
|
|
5
4
|
|
|
6
5
|
export const sendCommand = cli({
|
|
7
6
|
site: 'chatwise',
|
|
@@ -10,7 +9,6 @@ export const sendCommand = cli({
|
|
|
10
9
|
domain: 'localhost',
|
|
11
10
|
strategy: Strategy.UI,
|
|
12
11
|
browser: true,
|
|
13
|
-
requiredEnv: chatwiseRequiredEnv,
|
|
14
12
|
args: [{ name: 'text', required: true, positional: true, help: 'Message to send' }],
|
|
15
13
|
columns: ['Status', 'InjectedText'],
|
|
16
14
|
func: async (page: IPage, kwargs: any) => {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
import { makeStatusCommand } from '../_shared/desktop-commands.js';
|
|
2
|
-
import { chatwiseRequiredEnv } from './shared.js';
|
|
3
2
|
|
|
4
|
-
export const statusCommand = makeStatusCommand('chatwise', 'ChatWise Desktop'
|
|
3
|
+
export const statusCommand = makeStatusCommand('chatwise', 'ChatWise Desktop');
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { getRegistry } from '../../registry.js';
|
|
3
|
+
import './search.js';
|
|
4
|
+
|
|
5
|
+
describe('ctrip search', () => {
|
|
6
|
+
const command = getRegistry().get('ctrip/search');
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
vi.unstubAllGlobals();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('maps live endpoint results into ranked rows', async () => {
|
|
13
|
+
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(JSON.stringify({
|
|
14
|
+
Response: {
|
|
15
|
+
searchResults: [
|
|
16
|
+
{
|
|
17
|
+
displayName: '苏州, 江苏, 中国',
|
|
18
|
+
displayType: '城市',
|
|
19
|
+
commentScore: 0,
|
|
20
|
+
price: '',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
word: '姑苏区',
|
|
24
|
+
type: '行政区',
|
|
25
|
+
cStar: 4.8,
|
|
26
|
+
minPrice: 320,
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
}), { status: 200 })));
|
|
31
|
+
|
|
32
|
+
const result = await command!.func!(null as any, { query: '苏州', limit: 3 });
|
|
33
|
+
expect(result).toEqual([
|
|
34
|
+
{
|
|
35
|
+
rank: 1,
|
|
36
|
+
name: '苏州, 江苏, 中国',
|
|
37
|
+
type: '城市',
|
|
38
|
+
score: 0,
|
|
39
|
+
price: '',
|
|
40
|
+
url: '',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
rank: 2,
|
|
44
|
+
name: '姑苏区',
|
|
45
|
+
type: '行政区',
|
|
46
|
+
score: 4.8,
|
|
47
|
+
price: 320,
|
|
48
|
+
url: '',
|
|
49
|
+
},
|
|
50
|
+
]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('rejects empty queries', async () => {
|
|
54
|
+
await expect(command!.func!(null as any, { query: ' ', limit: 3 })).rejects.toThrow('Search keyword cannot be empty');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('surfaces fetch failures as CliError', async () => {
|
|
58
|
+
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response('{}', { status: 503 })));
|
|
59
|
+
|
|
60
|
+
await expect(command!.func!(null as any, { query: '苏州', limit: 3 })).rejects.toMatchObject({
|
|
61
|
+
code: 'FETCH_ERROR',
|
|
62
|
+
message: 'ctrip search failed with status 503',
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('surfaces empty results as EmptyResultError', async () => {
|
|
67
|
+
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(JSON.stringify({
|
|
68
|
+
Response: { searchResults: [] },
|
|
69
|
+
}), { status: 200 })));
|
|
70
|
+
|
|
71
|
+
await expect(command!.func!(null as any, { query: '苏州', limit: 3 })).rejects.toThrow('ctrip search returned no data');
|
|
72
|
+
});
|
|
73
|
+
});
|
package/src/clis/ctrip/search.ts
CHANGED
|
@@ -1,61 +1,111 @@
|
|
|
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';
|
|
5
6
|
|
|
7
|
+
interface CtripSearchResultItem {
|
|
8
|
+
displayName?: string;
|
|
9
|
+
word?: string;
|
|
10
|
+
cityName?: string;
|
|
11
|
+
displayType?: string;
|
|
12
|
+
type?: string;
|
|
13
|
+
commentScore?: number | string;
|
|
14
|
+
cStar?: number | string;
|
|
15
|
+
price?: number | string;
|
|
16
|
+
minPrice?: number | string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function clampLimit(raw: unknown, fallback = 15): number {
|
|
20
|
+
const parsed = Number(raw);
|
|
21
|
+
if (!Number.isFinite(parsed)) return fallback;
|
|
22
|
+
return Math.max(1, Math.min(Math.floor(parsed), 50));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function mapSearchResults(results: unknown[], limit: number) {
|
|
26
|
+
return results
|
|
27
|
+
.filter((item): item is CtripSearchResultItem => !!item && typeof item === 'object')
|
|
28
|
+
.slice(0, limit)
|
|
29
|
+
.map((item, index) => ({
|
|
30
|
+
rank: index + 1,
|
|
31
|
+
name: String(item.displayName || item.word || item.cityName || '').replace(/\s+/g, ' ').trim(),
|
|
32
|
+
type: String(item.displayType || item.type || '').replace(/\s+/g, ' ').trim(),
|
|
33
|
+
score: item.commentScore ?? item.cStar ?? '',
|
|
34
|
+
price: item.price ?? item.minPrice ?? '',
|
|
35
|
+
url: '',
|
|
36
|
+
}))
|
|
37
|
+
.filter((item) => item.name);
|
|
38
|
+
}
|
|
39
|
+
|
|
6
40
|
cli({
|
|
7
41
|
site: 'ctrip',
|
|
8
42
|
name: 'search',
|
|
9
|
-
description: '
|
|
10
|
-
|
|
11
|
-
|
|
43
|
+
description: '搜索携程目的地、景区和酒店联想结果',
|
|
44
|
+
strategy: Strategy.PUBLIC,
|
|
45
|
+
browser: false,
|
|
12
46
|
args: [
|
|
13
47
|
{ name: 'query', required: true, positional: true, help: 'Search keyword (city or attraction)' },
|
|
14
48
|
{ name: 'limit', type: 'int', default: 15, help: 'Number of results' },
|
|
15
49
|
],
|
|
16
50
|
columns: ['rank', 'name', 'type', 'score', 'price', 'url'],
|
|
17
|
-
func: async (
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
51
|
+
func: async (_page, kwargs) => {
|
|
52
|
+
const query = String(kwargs.query || '').trim();
|
|
53
|
+
if (!query) {
|
|
54
|
+
throw new ArgumentError('Search keyword cannot be empty');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const limit = clampLimit(kwargs.limit);
|
|
58
|
+
const response = await fetch('https://m.ctrip.com/restapi/soa2/21881/json/gaHotelSearchEngine', {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers: {
|
|
61
|
+
'content-type': 'application/json',
|
|
62
|
+
},
|
|
63
|
+
body: JSON.stringify({
|
|
64
|
+
keyword: query,
|
|
65
|
+
searchType: 'D',
|
|
66
|
+
platform: 'online',
|
|
67
|
+
pageID: '102001',
|
|
68
|
+
head: {
|
|
69
|
+
Locale: 'zh-CN',
|
|
70
|
+
LocaleController: 'zh_cn',
|
|
71
|
+
Currency: 'CNY',
|
|
72
|
+
PageId: '102001',
|
|
73
|
+
clientID: 'opencli-ctrip-search',
|
|
74
|
+
group: 'ctrip',
|
|
75
|
+
Frontend: {
|
|
76
|
+
sessionID: 1,
|
|
77
|
+
pvid: 1,
|
|
78
|
+
},
|
|
79
|
+
HotelExtension: {
|
|
80
|
+
group: 'CTRIP',
|
|
81
|
+
WebpSupport: false,
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
}),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
throw new CliError(
|
|
89
|
+
'FETCH_ERROR',
|
|
90
|
+
`ctrip search failed with status ${response.status}`,
|
|
91
|
+
'Retry the command or verify ctrip.com is reachable',
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const payload = await response.json();
|
|
96
|
+
const rawResults = Array.isArray(payload?.Response?.searchResults) ? payload.Response.searchResults : [];
|
|
97
|
+
const results = mapSearchResults(rawResults, limit);
|
|
98
|
+
if (!results.length) {
|
|
99
|
+
throw new EmptyResultError(
|
|
100
|
+
'ctrip search',
|
|
101
|
+
'Try a destination, scenic spot, or hotel keyword such as "苏州" or "朱家尖"',
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
return results;
|
|
60
105
|
},
|
|
61
106
|
});
|
|
107
|
+
|
|
108
|
+
export const __test__ = {
|
|
109
|
+
clampLimit,
|
|
110
|
+
mapSearchResults,
|
|
111
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { AuthRequiredError } from '../../../errors.js';
|
|
3
|
+
import { getSts2Credentials } from './sts2.js';
|
|
4
|
+
|
|
5
|
+
describe('douyin sts2 credentials', () => {
|
|
6
|
+
it('accepts top-level credential fields returned by creator center', async () => {
|
|
7
|
+
const page = {
|
|
8
|
+
evaluate: async () => ({
|
|
9
|
+
access_key_id: 'ak',
|
|
10
|
+
secret_access_key: 'sk',
|
|
11
|
+
session_token: 'token',
|
|
12
|
+
expired_time: 1_234_567_890,
|
|
13
|
+
}),
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
await expect(getSts2Credentials(page as never)).resolves.toEqual({
|
|
17
|
+
access_key_id: 'ak',
|
|
18
|
+
secret_access_key: 'sk',
|
|
19
|
+
session_token: 'token',
|
|
20
|
+
expired_time: 1_234_567_890,
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('still rejects responses without credential fields', async () => {
|
|
25
|
+
const page = {
|
|
26
|
+
evaluate: async () => ({ status_code: 8 }),
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
await expect(getSts2Credentials(page as never)).rejects.toBeInstanceOf(AuthRequiredError);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -12,9 +12,17 @@ const STS2_URL =
|
|
|
12
12
|
*/
|
|
13
13
|
export async function getSts2Credentials(page: IPage): Promise<Sts2Credentials> {
|
|
14
14
|
const js = `fetch(${JSON.stringify(STS2_URL)}, { credentials: 'include' }).then(r => r.json())`;
|
|
15
|
-
const res = await page.evaluate(js) as { data
|
|
16
|
-
|
|
15
|
+
const res = await page.evaluate(js) as Sts2Credentials | { data?: Sts2Credentials };
|
|
16
|
+
const credentials = (
|
|
17
|
+
typeof res === 'object' &&
|
|
18
|
+
res !== null &&
|
|
19
|
+
'data' in res &&
|
|
20
|
+
res.data
|
|
21
|
+
)
|
|
22
|
+
? res.data
|
|
23
|
+
: (res as Sts2Credentials);
|
|
24
|
+
if (!credentials?.access_key_id) {
|
|
17
25
|
throw new AuthRequiredError('creator.douyin.com', 'STS2 credentials missing');
|
|
18
26
|
}
|
|
19
|
-
return
|
|
27
|
+
return credentials;
|
|
20
28
|
}
|
|
@@ -1,8 +1,21 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
const { browserFetchMock } = vi.hoisted(() => ({
|
|
4
|
+
browserFetchMock: vi.fn(),
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
vi.mock('./_shared/browser-fetch.js', () => ({
|
|
8
|
+
browserFetch: browserFetchMock,
|
|
9
|
+
}));
|
|
10
|
+
|
|
2
11
|
import { getRegistry } from '../../registry.js';
|
|
3
12
|
import './activities.js';
|
|
4
13
|
|
|
5
14
|
describe('douyin activities registration', () => {
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
browserFetchMock.mockReset();
|
|
17
|
+
});
|
|
18
|
+
|
|
6
19
|
it('registers the activities command', () => {
|
|
7
20
|
const registry = getRegistry();
|
|
8
21
|
const cmd = [...registry.values()].find(c => c.site === 'douyin' && c.name === 'activities');
|
|
@@ -22,4 +35,31 @@ describe('douyin activities registration', () => {
|
|
|
22
35
|
const cmd = [...registry.values()].find(c => c.site === 'douyin' && c.name === 'activities');
|
|
23
36
|
expect(cmd?.strategy).toBe('cookie');
|
|
24
37
|
});
|
|
38
|
+
|
|
39
|
+
it('maps the current activity payload shape returned by creator center', async () => {
|
|
40
|
+
const registry = getRegistry();
|
|
41
|
+
const cmd = [...registry.values()].find(c => c.site === 'douyin' && c.name === 'activities');
|
|
42
|
+
expect(cmd?.func).toBeDefined();
|
|
43
|
+
if (!cmd?.func) throw new Error('douyin activities command not registered');
|
|
44
|
+
|
|
45
|
+
browserFetchMock.mockResolvedValueOnce({
|
|
46
|
+
activity_list: [
|
|
47
|
+
{
|
|
48
|
+
activity_id: '200',
|
|
49
|
+
activity_name: '超会玩派对',
|
|
50
|
+
show_end_time: '2026.05.31',
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const rows = await cmd.func({} as never, {});
|
|
56
|
+
|
|
57
|
+
expect(rows).toEqual([
|
|
58
|
+
{
|
|
59
|
+
activity_id: '200',
|
|
60
|
+
title: '超会玩派对',
|
|
61
|
+
end_time: '2026.05.31',
|
|
62
|
+
},
|
|
63
|
+
]);
|
|
64
|
+
});
|
|
25
65
|
});
|
|
@@ -12,12 +12,21 @@ cli({
|
|
|
12
12
|
func: async (page, _kwargs) => {
|
|
13
13
|
const url = 'https://creator.douyin.com/web/api/media/activity/get/?aid=1128';
|
|
14
14
|
const res = await browserFetch(page, 'GET', url) as {
|
|
15
|
-
activity_list: Array<{
|
|
15
|
+
activity_list: Array<{
|
|
16
|
+
activity_id: string;
|
|
17
|
+
title?: string;
|
|
18
|
+
activity_name?: string;
|
|
19
|
+
end_time?: number;
|
|
20
|
+
show_end_time?: string;
|
|
21
|
+
}>
|
|
16
22
|
};
|
|
17
23
|
return (res.activity_list ?? []).map(a => ({
|
|
18
24
|
activity_id: a.activity_id,
|
|
19
|
-
title: a.title,
|
|
20
|
-
end_time:
|
|
25
|
+
title: a.title ?? a.activity_name ?? '',
|
|
26
|
+
end_time:
|
|
27
|
+
typeof a.end_time === 'number'
|
|
28
|
+
? new Date(a.end_time * 1000).toLocaleString('zh-CN', { timeZone: 'Asia/Tokyo' })
|
|
29
|
+
: (a.show_end_time ?? ''),
|
|
21
30
|
}));
|
|
22
31
|
},
|
|
23
32
|
});
|