@jackwener/opencli 0.9.6 → 1.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/.github/ISSUE_TEMPLATE/bug_report.yml +83 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +42 -0
- package/.github/ISSUE_TEMPLATE/new_site_adapter.yml +57 -0
- package/.github/dependabot.yml +27 -0
- package/.github/pull_request_template.md +24 -0
- package/.github/workflows/ci.yml +14 -8
- package/.github/workflows/e2e-headed.yml +6 -2
- package/.github/workflows/pkg-pr-new.yml +2 -2
- package/.github/workflows/release-please.yml +25 -0
- package/.github/workflows/release.yml +2 -2
- package/.github/workflows/security.yml +36 -0
- package/CDP.md +1 -1
- package/CDP.zh-CN.md +1 -1
- package/CLI-ELECTRON.md +89 -36
- package/CLI-EXPLORER.md +4 -4
- package/CONTRIBUTING.md +167 -0
- package/README.md +113 -89
- package/README.zh-CN.md +114 -91
- package/SKILL.md +10 -8
- package/TESTING.md +7 -7
- package/dist/browser/daemon-client.d.ts +37 -0
- package/dist/browser/daemon-client.js +82 -0
- package/dist/browser/discover.d.ts +11 -34
- package/dist/browser/discover.js +15 -190
- package/dist/browser/errors.d.ts +6 -20
- package/dist/browser/errors.js +24 -63
- package/dist/browser/index.d.ts +2 -11
- package/dist/browser/index.js +5 -11
- package/dist/browser/mcp.d.ts +9 -18
- package/dist/browser/mcp.js +70 -284
- package/dist/browser/page.d.ts +28 -6
- package/dist/browser/page.js +210 -85
- package/dist/browser.test.js +4 -202
- package/dist/build-manifest.d.ts +26 -0
- package/dist/build-manifest.js +132 -60
- package/dist/build-manifest.test.d.ts +1 -0
- package/dist/build-manifest.test.js +26 -0
- package/dist/cli-manifest.json +1582 -29
- package/dist/clis/bilibili/download.d.ts +10 -0
- package/dist/clis/bilibili/download.js +135 -0
- package/dist/clis/chatwise/ask.d.ts +1 -0
- package/dist/clis/chatwise/ask.js +76 -0
- package/dist/clis/chatwise/export.d.ts +1 -0
- package/dist/clis/chatwise/export.js +46 -0
- package/dist/clis/chatwise/history.d.ts +1 -0
- package/dist/clis/chatwise/history.js +43 -0
- package/dist/clis/chatwise/model.d.ts +1 -0
- package/dist/clis/chatwise/model.js +81 -0
- package/dist/clis/chatwise/new.d.ts +1 -0
- package/dist/clis/chatwise/new.js +18 -0
- package/dist/clis/chatwise/read.d.ts +1 -0
- package/dist/clis/chatwise/read.js +39 -0
- package/dist/clis/chatwise/screenshot.d.ts +1 -0
- package/dist/clis/chatwise/screenshot.js +27 -0
- package/dist/clis/chatwise/send.d.ts +1 -0
- package/dist/clis/chatwise/send.js +45 -0
- package/dist/clis/chatwise/status.d.ts +1 -0
- package/dist/clis/chatwise/status.js +22 -0
- package/dist/clis/discord-app/channels.d.ts +1 -0
- package/dist/clis/discord-app/channels.js +45 -0
- package/dist/clis/discord-app/members.d.ts +1 -0
- package/dist/clis/discord-app/members.js +38 -0
- package/dist/clis/discord-app/read.d.ts +1 -0
- package/dist/clis/discord-app/read.js +45 -0
- package/dist/clis/discord-app/search.d.ts +1 -0
- package/dist/clis/discord-app/search.js +56 -0
- package/dist/clis/discord-app/send.d.ts +1 -0
- package/dist/clis/discord-app/send.js +27 -0
- package/dist/clis/discord-app/servers.d.ts +1 -0
- package/dist/clis/discord-app/servers.js +36 -0
- package/dist/clis/discord-app/status.d.ts +1 -0
- package/dist/clis/discord-app/status.js +16 -0
- package/dist/clis/feishu/new.d.ts +1 -0
- package/dist/clis/feishu/new.js +27 -0
- package/dist/clis/feishu/read.d.ts +1 -0
- package/dist/clis/feishu/read.js +40 -0
- package/dist/clis/feishu/search.d.ts +1 -0
- package/dist/clis/feishu/search.js +30 -0
- package/dist/clis/feishu/send.d.ts +1 -0
- package/dist/clis/feishu/send.js +39 -0
- package/dist/clis/feishu/status.d.ts +1 -0
- package/dist/clis/feishu/status.js +28 -0
- package/dist/clis/grok/ask.d.ts +1 -0
- package/dist/clis/grok/ask.js +82 -0
- package/dist/clis/grok/debug.d.ts +1 -0
- package/dist/clis/grok/debug.js +45 -0
- package/dist/clis/jimeng/generate.yaml +84 -0
- package/dist/clis/jimeng/history.yaml +47 -0
- package/dist/clis/linux-do/categories.yaml +41 -0
- package/dist/clis/linux-do/category.yaml +49 -0
- package/dist/clis/linux-do/hot.yaml +50 -0
- package/dist/clis/linux-do/latest.yaml +40 -0
- package/dist/clis/linux-do/search.yaml +45 -0
- package/dist/clis/linux-do/topic.yaml +38 -0
- package/dist/clis/neteasemusic/like.d.ts +1 -0
- package/dist/clis/neteasemusic/like.js +25 -0
- package/dist/clis/neteasemusic/lyrics.d.ts +1 -0
- package/dist/clis/neteasemusic/lyrics.js +47 -0
- package/dist/clis/neteasemusic/next.d.ts +1 -0
- package/dist/clis/neteasemusic/next.js +26 -0
- package/dist/clis/neteasemusic/play.d.ts +1 -0
- package/dist/clis/neteasemusic/play.js +26 -0
- package/dist/clis/neteasemusic/playing.d.ts +1 -0
- package/dist/clis/neteasemusic/playing.js +59 -0
- package/dist/clis/neteasemusic/playlist.d.ts +1 -0
- package/dist/clis/neteasemusic/playlist.js +46 -0
- package/dist/clis/neteasemusic/prev.d.ts +1 -0
- package/dist/clis/neteasemusic/prev.js +25 -0
- package/dist/clis/neteasemusic/search.d.ts +1 -0
- package/dist/clis/neteasemusic/search.js +52 -0
- package/dist/clis/neteasemusic/status.d.ts +1 -0
- package/dist/clis/neteasemusic/status.js +16 -0
- package/dist/clis/neteasemusic/volume.d.ts +1 -0
- package/dist/clis/neteasemusic/volume.js +54 -0
- package/dist/clis/notion/export.d.ts +1 -0
- package/dist/clis/notion/export.js +31 -0
- package/dist/clis/notion/favorites.d.ts +1 -0
- package/dist/clis/notion/favorites.js +84 -0
- package/dist/clis/notion/new.d.ts +1 -0
- package/dist/clis/notion/new.js +34 -0
- package/dist/clis/notion/read.d.ts +1 -0
- package/dist/clis/notion/read.js +30 -0
- package/dist/clis/notion/search.d.ts +1 -0
- package/dist/clis/notion/search.js +46 -0
- package/dist/clis/notion/sidebar.d.ts +1 -0
- package/dist/clis/notion/sidebar.js +41 -0
- package/dist/clis/notion/status.d.ts +1 -0
- package/dist/clis/notion/status.js +16 -0
- package/dist/clis/notion/write.d.ts +1 -0
- package/dist/clis/notion/write.js +40 -0
- package/dist/clis/twitter/download.d.ts +8 -0
- package/dist/clis/twitter/download.js +204 -0
- package/dist/clis/wechat/chats.d.ts +1 -0
- package/dist/clis/wechat/chats.js +28 -0
- package/dist/clis/wechat/contacts.d.ts +1 -0
- package/dist/clis/wechat/contacts.js +28 -0
- package/dist/clis/wechat/read.d.ts +1 -0
- package/dist/clis/wechat/read.js +58 -0
- package/dist/clis/wechat/search.d.ts +1 -0
- package/dist/clis/wechat/search.js +31 -0
- package/dist/clis/wechat/send.d.ts +1 -0
- package/dist/clis/wechat/send.js +42 -0
- package/dist/clis/wechat/status.d.ts +1 -0
- package/dist/clis/wechat/status.js +29 -0
- package/dist/clis/xiaohongshu/creator-note-detail.d.ts +10 -0
- package/dist/clis/xiaohongshu/creator-note-detail.js +88 -0
- package/dist/clis/xiaohongshu/creator-notes.d.ts +11 -0
- package/dist/clis/xiaohongshu/creator-notes.js +109 -0
- package/dist/clis/xiaohongshu/creator-profile.d.ts +10 -0
- package/dist/clis/xiaohongshu/creator-profile.js +54 -0
- package/dist/clis/xiaohongshu/creator-stats.d.ts +10 -0
- package/dist/clis/xiaohongshu/creator-stats.js +74 -0
- package/dist/clis/xiaohongshu/download.d.ts +7 -0
- package/dist/clis/xiaohongshu/download.js +155 -0
- package/dist/clis/xiaohongshu/search.js +1 -1
- package/dist/clis/xiaohongshu/user-helpers.d.ts +15 -0
- package/dist/clis/xiaohongshu/user-helpers.js +67 -0
- package/dist/clis/xiaohongshu/user-helpers.test.d.ts +1 -0
- package/dist/clis/xiaohongshu/user-helpers.test.js +81 -0
- package/dist/clis/xiaohongshu/user.js +46 -29
- package/dist/clis/zhihu/download.d.ts +11 -0
- package/dist/clis/zhihu/download.js +186 -0
- package/dist/clis/zhihu/download.test.d.ts +1 -0
- package/dist/clis/zhihu/download.test.js +10 -0
- package/dist/daemon.d.ts +13 -0
- package/dist/daemon.js +187 -0
- package/dist/doctor.d.ts +27 -61
- package/dist/doctor.js +70 -601
- package/dist/doctor.test.js +30 -170
- package/dist/download/index.d.ts +79 -0
- package/dist/download/index.js +325 -0
- package/dist/download/progress.d.ts +36 -0
- package/dist/download/progress.js +111 -0
- package/dist/engine.test.js +15 -0
- package/dist/main.js +22 -28
- package/dist/pipeline/executor.test.js +1 -0
- package/dist/pipeline/registry.js +2 -0
- package/dist/pipeline/steps/browser.js +2 -2
- package/dist/pipeline/steps/download.d.ts +34 -0
- package/dist/pipeline/steps/download.js +251 -0
- package/dist/pipeline/steps/intercept.js +1 -2
- package/dist/pipeline/template.js +28 -0
- package/dist/setup.d.ts +6 -0
- package/dist/setup.js +46 -160
- package/dist/types.d.ts +6 -0
- package/extension/icons/icon-128.png +0 -0
- package/extension/icons/icon-16.png +0 -0
- package/extension/icons/icon-32.png +0 -0
- package/extension/icons/icon-48.png +0 -0
- package/extension/manifest.json +31 -0
- package/extension/package.json +16 -0
- package/extension/src/background.ts +293 -0
- package/extension/src/cdp.ts +125 -0
- package/extension/src/protocol.ts +57 -0
- package/extension/store-assets/screenshot-1280x800.png +0 -0
- package/extension/tsconfig.json +15 -0
- package/extension/vite.config.ts +18 -0
- package/package.json +8 -7
- package/scripts/test-site.mjs +70 -0
- package/src/browser/daemon-client.ts +113 -0
- package/src/browser/discover.ts +18 -216
- package/src/browser/errors.ts +30 -100
- package/src/browser/index.ts +6 -12
- package/src/browser/mcp.ts +78 -278
- package/src/browser/page.ts +222 -88
- package/src/browser.test.ts +3 -210
- package/src/build-manifest.test.ts +28 -0
- package/src/build-manifest.ts +147 -57
- package/src/clis/bilibili/download.ts +161 -0
- package/src/clis/chatgpt/README.md +1 -1
- package/src/clis/chatgpt/README.zh-CN.md +1 -1
- package/src/clis/chatwise/README.md +38 -0
- package/src/clis/chatwise/README.zh-CN.md +38 -0
- package/src/clis/chatwise/ask.ts +87 -0
- package/src/clis/chatwise/export.ts +51 -0
- package/src/clis/chatwise/history.ts +47 -0
- package/src/clis/chatwise/model.ts +87 -0
- package/src/clis/chatwise/new.ts +21 -0
- package/src/clis/chatwise/read.ts +42 -0
- package/src/clis/chatwise/screenshot.ts +33 -0
- package/src/clis/chatwise/send.ts +50 -0
- package/src/clis/chatwise/status.ts +25 -0
- package/src/clis/discord-app/README.md +28 -0
- package/src/clis/discord-app/README.zh-CN.md +28 -0
- package/src/clis/discord-app/channels.ts +48 -0
- package/src/clis/discord-app/members.ts +41 -0
- package/src/clis/discord-app/read.ts +49 -0
- package/src/clis/discord-app/search.ts +64 -0
- package/src/clis/discord-app/send.ts +32 -0
- package/src/clis/discord-app/servers.ts +39 -0
- package/src/clis/discord-app/status.ts +18 -0
- package/src/clis/feishu/README.md +20 -0
- package/src/clis/feishu/README.zh-CN.md +20 -0
- package/src/clis/feishu/new.ts +32 -0
- package/src/clis/feishu/read.ts +48 -0
- package/src/clis/feishu/search.ts +35 -0
- package/src/clis/feishu/send.ts +46 -0
- package/src/clis/feishu/status.ts +34 -0
- package/src/clis/grok/ask.ts +90 -0
- package/src/clis/grok/debug.ts +49 -0
- package/src/clis/jimeng/generate.yaml +84 -0
- package/src/clis/jimeng/history.yaml +47 -0
- package/src/clis/linux-do/categories.yaml +41 -0
- package/src/clis/linux-do/category.yaml +49 -0
- package/src/clis/linux-do/hot.yaml +50 -0
- package/src/clis/linux-do/latest.yaml +40 -0
- package/src/clis/linux-do/search.yaml +45 -0
- package/src/clis/linux-do/topic.yaml +38 -0
- package/src/clis/neteasemusic/README.md +31 -0
- package/src/clis/neteasemusic/README.zh-CN.md +31 -0
- package/src/clis/neteasemusic/like.ts +28 -0
- package/src/clis/neteasemusic/lyrics.ts +53 -0
- package/src/clis/neteasemusic/next.ts +30 -0
- package/src/clis/neteasemusic/play.ts +30 -0
- package/src/clis/neteasemusic/playing.ts +62 -0
- package/src/clis/neteasemusic/playlist.ts +51 -0
- package/src/clis/neteasemusic/prev.ts +29 -0
- package/src/clis/neteasemusic/search.ts +58 -0
- package/src/clis/neteasemusic/status.ts +18 -0
- package/src/clis/neteasemusic/volume.ts +61 -0
- package/src/clis/notion/README.md +29 -0
- package/src/clis/notion/README.zh-CN.md +29 -0
- package/src/clis/notion/export.ts +36 -0
- package/src/clis/notion/favorites.ts +87 -0
- package/src/clis/notion/new.ts +39 -0
- package/src/clis/notion/read.ts +33 -0
- package/src/clis/notion/search.ts +54 -0
- package/src/clis/notion/sidebar.ts +44 -0
- package/src/clis/notion/status.ts +18 -0
- package/src/clis/notion/write.ts +45 -0
- package/src/clis/twitter/download.ts +227 -0
- package/src/clis/wechat/README.md +28 -0
- package/src/clis/wechat/README.zh-CN.md +28 -0
- package/src/clis/wechat/chats.ts +33 -0
- package/src/clis/wechat/contacts.ts +33 -0
- package/src/clis/wechat/read.ts +72 -0
- package/src/clis/wechat/search.ts +36 -0
- package/src/clis/wechat/send.ts +49 -0
- package/src/clis/wechat/status.ts +35 -0
- package/src/clis/xiaohongshu/creator-note-detail.ts +95 -0
- package/src/clis/xiaohongshu/creator-notes.ts +116 -0
- package/src/clis/xiaohongshu/creator-profile.ts +60 -0
- package/src/clis/xiaohongshu/creator-stats.ts +81 -0
- package/src/clis/xiaohongshu/download.ts +173 -0
- package/src/clis/xiaohongshu/search.ts +1 -1
- package/src/clis/xiaohongshu/user-helpers.test.ts +106 -0
- package/src/clis/xiaohongshu/user-helpers.ts +85 -0
- package/src/clis/xiaohongshu/user.ts +52 -32
- package/src/clis/zhihu/download.test.ts +12 -0
- package/src/clis/zhihu/download.ts +223 -0
- package/src/daemon.ts +217 -0
- package/src/doctor.test.ts +32 -193
- package/src/doctor.ts +74 -668
- package/src/download/index.ts +395 -0
- package/src/download/progress.ts +125 -0
- package/src/engine.test.ts +17 -0
- package/src/main.ts +18 -26
- package/src/pipeline/executor.test.ts +1 -0
- package/src/pipeline/registry.ts +2 -0
- package/src/pipeline/steps/browser.ts +2 -2
- package/src/pipeline/steps/download.ts +310 -0
- package/src/pipeline/steps/intercept.ts +1 -2
- package/src/pipeline/template.ts +26 -0
- package/src/setup.ts +47 -183
- package/src/types.ts +1 -0
- package/tests/e2e/browser-auth.test.ts +25 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { cli, Strategy } from '../../registry.js';
|
|
3
|
+
export const chatsCommand = cli({
|
|
4
|
+
site: 'wechat',
|
|
5
|
+
name: 'chats',
|
|
6
|
+
description: 'Open the WeChat chats panel (conversation list)',
|
|
7
|
+
domain: 'localhost',
|
|
8
|
+
strategy: Strategy.PUBLIC,
|
|
9
|
+
browser: false,
|
|
10
|
+
args: [],
|
|
11
|
+
columns: ['Status'],
|
|
12
|
+
func: async (page) => {
|
|
13
|
+
try {
|
|
14
|
+
// Activate WeChat
|
|
15
|
+
execSync("osascript -e 'tell application \"WeChat\" to activate'");
|
|
16
|
+
execSync("osascript -e 'delay 0.3'");
|
|
17
|
+
// Cmd+1 switches to Chats tab in WeChat Mac
|
|
18
|
+
execSync("osascript " +
|
|
19
|
+
"-e 'tell application \"System Events\"' " +
|
|
20
|
+
"-e 'keystroke \"1\" using command down' " +
|
|
21
|
+
"-e 'end tell'");
|
|
22
|
+
return [{ Status: 'Chats panel opened (Cmd+1)' }];
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
return [{ Status: 'Error: ' + err.message }];
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const contactsCommand: import("../../registry.js").CliCommand;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { cli, Strategy } from '../../registry.js';
|
|
3
|
+
export const contactsCommand = cli({
|
|
4
|
+
site: 'wechat',
|
|
5
|
+
name: 'contacts',
|
|
6
|
+
description: 'Open the WeChat contacts panel',
|
|
7
|
+
domain: 'localhost',
|
|
8
|
+
strategy: Strategy.PUBLIC,
|
|
9
|
+
browser: false,
|
|
10
|
+
args: [],
|
|
11
|
+
columns: ['Status'],
|
|
12
|
+
func: async (page) => {
|
|
13
|
+
try {
|
|
14
|
+
// Activate WeChat
|
|
15
|
+
execSync("osascript -e 'tell application \"WeChat\" to activate'");
|
|
16
|
+
execSync("osascript -e 'delay 0.3'");
|
|
17
|
+
// Cmd+2 switches to Contacts tab in WeChat Mac
|
|
18
|
+
execSync("osascript " +
|
|
19
|
+
"-e 'tell application \"System Events\"' " +
|
|
20
|
+
"-e 'keystroke \"2\" using command down' " +
|
|
21
|
+
"-e 'end tell'");
|
|
22
|
+
return [{ Status: 'Contacts panel opened (Cmd+2)' }];
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
return [{ Status: 'Error: ' + err.message }];
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const readCommand: import("../../registry.js").CliCommand;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { execSync, spawnSync } from 'node:child_process';
|
|
2
|
+
import { cli, Strategy } from '../../registry.js';
|
|
3
|
+
export const readCommand = cli({
|
|
4
|
+
site: 'wechat',
|
|
5
|
+
name: 'read',
|
|
6
|
+
description: 'Read the current chat content by selecting all and copying',
|
|
7
|
+
domain: 'localhost',
|
|
8
|
+
strategy: Strategy.PUBLIC,
|
|
9
|
+
browser: false,
|
|
10
|
+
args: [],
|
|
11
|
+
columns: ['Content'],
|
|
12
|
+
func: async (page) => {
|
|
13
|
+
try {
|
|
14
|
+
// Backup clipboard
|
|
15
|
+
let clipBackup = '';
|
|
16
|
+
try {
|
|
17
|
+
clipBackup = execSync('pbpaste', { encoding: 'utf-8' });
|
|
18
|
+
}
|
|
19
|
+
catch { /* empty */ }
|
|
20
|
+
// Activate WeChat
|
|
21
|
+
execSync("osascript -e 'tell application \"WeChat\" to activate'");
|
|
22
|
+
execSync("osascript -e 'delay 0.3'");
|
|
23
|
+
// Click on the chat area first, then select all and copy
|
|
24
|
+
execSync("osascript " +
|
|
25
|
+
"-e 'tell application \"System Events\"' " +
|
|
26
|
+
"-e 'tell application process \"WeChat\"' " +
|
|
27
|
+
// Click in the message area (center-right of the window)
|
|
28
|
+
"-e 'set frontWin to front window' " +
|
|
29
|
+
"-e 'set winPos to position of frontWin' " +
|
|
30
|
+
"-e 'set winSize to size of frontWin' " +
|
|
31
|
+
"-e 'end tell' " +
|
|
32
|
+
"-e 'end tell'");
|
|
33
|
+
execSync("osascript -e 'delay 0.2'");
|
|
34
|
+
// Select all text in chat area and copy
|
|
35
|
+
execSync("osascript " +
|
|
36
|
+
"-e 'tell application \"System Events\"' " +
|
|
37
|
+
"-e 'keystroke \"a\" using command down' " +
|
|
38
|
+
"-e 'delay 0.2' " +
|
|
39
|
+
"-e 'keystroke \"c\" using command down' " +
|
|
40
|
+
"-e 'delay 0.2' " +
|
|
41
|
+
"-e 'end tell'");
|
|
42
|
+
const content = execSync('pbpaste', { encoding: 'utf-8' }).trim();
|
|
43
|
+
// Restore clipboard
|
|
44
|
+
if (clipBackup) {
|
|
45
|
+
spawnSync('pbcopy', { input: clipBackup });
|
|
46
|
+
}
|
|
47
|
+
// Press Escape to deselect
|
|
48
|
+
execSync("osascript " +
|
|
49
|
+
"-e 'tell application \"System Events\"' " +
|
|
50
|
+
"-e 'key code 53' " + // Escape
|
|
51
|
+
"-e 'end tell'");
|
|
52
|
+
return [{ Content: content || '(no content captured)' }];
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
return [{ Content: 'Error: ' + err.message }];
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const searchCommand: import("../../registry.js").CliCommand;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { cli, Strategy } from '../../registry.js';
|
|
3
|
+
export const searchCommand = cli({
|
|
4
|
+
site: 'wechat',
|
|
5
|
+
name: 'search',
|
|
6
|
+
description: 'Open WeChat search and type a query (find contacts or messages)',
|
|
7
|
+
domain: 'localhost',
|
|
8
|
+
strategy: Strategy.PUBLIC,
|
|
9
|
+
browser: false,
|
|
10
|
+
args: [{ name: 'query', required: true, positional: true, help: 'Search query (contact name or keyword)' }],
|
|
11
|
+
columns: ['Status'],
|
|
12
|
+
func: async (page, kwargs) => {
|
|
13
|
+
const query = kwargs.query;
|
|
14
|
+
try {
|
|
15
|
+
// Activate WeChat
|
|
16
|
+
execSync("osascript -e 'tell application \"WeChat\" to activate'");
|
|
17
|
+
execSync("osascript -e 'delay 0.3'");
|
|
18
|
+
// Cmd+F to open search (WeChat Mac uses Cmd+F for search)
|
|
19
|
+
execSync("osascript " +
|
|
20
|
+
"-e 'tell application \"System Events\"' " +
|
|
21
|
+
"-e 'keystroke \"f\" using command down' " +
|
|
22
|
+
"-e 'delay 0.5' " +
|
|
23
|
+
`-e 'keystroke ${JSON.stringify(query)}' ` +
|
|
24
|
+
"-e 'end tell'");
|
|
25
|
+
return [{ Status: `Searching for: ${query}` }];
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
return [{ Status: 'Error: ' + err.message }];
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const sendCommand: import("../../registry.js").CliCommand;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { execSync, spawnSync } from 'node:child_process';
|
|
2
|
+
import { cli, Strategy } from '../../registry.js';
|
|
3
|
+
export const sendCommand = cli({
|
|
4
|
+
site: 'wechat',
|
|
5
|
+
name: 'send',
|
|
6
|
+
description: 'Send a message in the active WeChat conversation via clipboard paste',
|
|
7
|
+
domain: 'localhost',
|
|
8
|
+
strategy: Strategy.PUBLIC,
|
|
9
|
+
browser: false,
|
|
10
|
+
args: [{ name: 'text', required: true, positional: true, help: 'Message to send' }],
|
|
11
|
+
columns: ['Status'],
|
|
12
|
+
func: async (page, kwargs) => {
|
|
13
|
+
const text = kwargs.text;
|
|
14
|
+
try {
|
|
15
|
+
// Backup clipboard
|
|
16
|
+
let clipBackup = '';
|
|
17
|
+
try {
|
|
18
|
+
clipBackup = execSync('pbpaste', { encoding: 'utf-8' });
|
|
19
|
+
}
|
|
20
|
+
catch { /* clipboard may be empty */ }
|
|
21
|
+
// Copy text to clipboard
|
|
22
|
+
spawnSync('pbcopy', { input: text });
|
|
23
|
+
// Activate WeChat and paste
|
|
24
|
+
execSync("osascript -e 'tell application \"WeChat\" to activate'");
|
|
25
|
+
execSync("osascript -e 'delay 0.5'");
|
|
26
|
+
execSync("osascript " +
|
|
27
|
+
"-e 'tell application \"System Events\"' " +
|
|
28
|
+
"-e 'keystroke \"v\" using command down' " +
|
|
29
|
+
"-e 'delay 0.2' " +
|
|
30
|
+
"-e 'keystroke return' " +
|
|
31
|
+
"-e 'end tell'");
|
|
32
|
+
// Restore clipboard
|
|
33
|
+
if (clipBackup) {
|
|
34
|
+
spawnSync('pbcopy', { input: clipBackup });
|
|
35
|
+
}
|
|
36
|
+
return [{ Status: 'Success' }];
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
return [{ Status: 'Error: ' + err.message }];
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const statusCommand: import("../../registry.js").CliCommand;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { cli, Strategy } from '../../registry.js';
|
|
3
|
+
export const statusCommand = cli({
|
|
4
|
+
site: 'wechat',
|
|
5
|
+
name: 'status',
|
|
6
|
+
description: 'Check if WeChat Desktop is running on macOS',
|
|
7
|
+
domain: 'localhost',
|
|
8
|
+
strategy: Strategy.PUBLIC,
|
|
9
|
+
browser: false,
|
|
10
|
+
args: [],
|
|
11
|
+
columns: ['Status', 'Detail'],
|
|
12
|
+
func: async (page) => {
|
|
13
|
+
try {
|
|
14
|
+
const running = execSync("osascript -e 'application \"WeChat\" is running'", { encoding: 'utf-8' }).trim();
|
|
15
|
+
if (running !== 'true') {
|
|
16
|
+
return [{ Status: 'Stopped', Detail: 'WeChat is not running' }];
|
|
17
|
+
}
|
|
18
|
+
// Get window count to check if logged in
|
|
19
|
+
const windowCount = execSync("osascript -e 'tell application \"System Events\" to count windows of application process \"WeChat\"'", { encoding: 'utf-8' }).trim();
|
|
20
|
+
return [{
|
|
21
|
+
Status: 'Running',
|
|
22
|
+
Detail: `${windowCount} window(s) open`,
|
|
23
|
+
}];
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
return [{ Status: 'Error', Detail: err.message }];
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Xiaohongshu Creator Note Detail — per-note analytics breakdown.
|
|
3
|
+
*
|
|
4
|
+
* Uses the creator.xiaohongshu.com internal API (cookie auth).
|
|
5
|
+
* Returns total reads, engagement, likes, collects, comments, shares
|
|
6
|
+
* for a specific note, split by channel (organic vs promoted vs video).
|
|
7
|
+
*
|
|
8
|
+
* Requires: logged into creator.xiaohongshu.com in Chrome.
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Xiaohongshu Creator Note Detail — per-note analytics breakdown.
|
|
3
|
+
*
|
|
4
|
+
* Uses the creator.xiaohongshu.com internal API (cookie auth).
|
|
5
|
+
* Returns total reads, engagement, likes, collects, comments, shares
|
|
6
|
+
* for a specific note, split by channel (organic vs promoted vs video).
|
|
7
|
+
*
|
|
8
|
+
* Requires: logged into creator.xiaohongshu.com in Chrome.
|
|
9
|
+
*/
|
|
10
|
+
import { cli, Strategy } from '../../registry.js';
|
|
11
|
+
cli({
|
|
12
|
+
site: 'xiaohongshu',
|
|
13
|
+
name: 'creator-note-detail',
|
|
14
|
+
description: '小红书单篇笔记详细数据 (阅读/互动/点赞/收藏/评论/分享,区分自然流量/推广/视频)',
|
|
15
|
+
domain: 'creator.xiaohongshu.com',
|
|
16
|
+
strategy: Strategy.COOKIE,
|
|
17
|
+
browser: true,
|
|
18
|
+
args: [
|
|
19
|
+
{ name: 'note_id', type: 'string', required: true, help: 'Note ID (from note URL or creator-notes command)' },
|
|
20
|
+
],
|
|
21
|
+
columns: ['channel', 'reads', 'engagement', 'likes', 'collects', 'comments', 'shares'],
|
|
22
|
+
func: async (page, kwargs) => {
|
|
23
|
+
const noteId = kwargs.note_id;
|
|
24
|
+
const encodedNoteId = encodeURIComponent(noteId);
|
|
25
|
+
// Navigate for cookie context
|
|
26
|
+
await page.goto('https://creator.xiaohongshu.com/new/home');
|
|
27
|
+
await page.wait(2);
|
|
28
|
+
const data = await page.evaluate(`
|
|
29
|
+
async () => {
|
|
30
|
+
try {
|
|
31
|
+
const resp = await fetch(
|
|
32
|
+
'/api/galaxy/creator/data/note_detail?note_id=${encodedNoteId}',
|
|
33
|
+
{ credentials: 'include' }
|
|
34
|
+
);
|
|
35
|
+
if (!resp.ok) return { error: 'HTTP ' + resp.status };
|
|
36
|
+
return await resp.json();
|
|
37
|
+
} catch (e) {
|
|
38
|
+
return { error: e.message };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
`);
|
|
42
|
+
if (data?.error) {
|
|
43
|
+
throw new Error(data.error + '. Check note_id and login status.');
|
|
44
|
+
}
|
|
45
|
+
if (!data?.data) {
|
|
46
|
+
throw new Error('Unexpected response structure');
|
|
47
|
+
}
|
|
48
|
+
const d = data.data;
|
|
49
|
+
return [
|
|
50
|
+
{
|
|
51
|
+
channel: 'Total',
|
|
52
|
+
reads: d.total_read ?? 0,
|
|
53
|
+
engagement: d.total_engage ?? 0,
|
|
54
|
+
likes: d.total_like ?? 0,
|
|
55
|
+
collects: d.total_fav ?? 0,
|
|
56
|
+
comments: d.total_cmt ?? 0,
|
|
57
|
+
shares: d.total_share ?? 0,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
channel: 'Organic',
|
|
61
|
+
reads: d.normal_read ?? 0,
|
|
62
|
+
engagement: d.normal_engage ?? 0,
|
|
63
|
+
likes: d.normal_like ?? 0,
|
|
64
|
+
collects: d.normal_fav ?? 0,
|
|
65
|
+
comments: d.normal_cmt ?? 0,
|
|
66
|
+
shares: d.normal_share ?? 0,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
channel: 'Promoted',
|
|
70
|
+
reads: d.total_promo_read ?? 0,
|
|
71
|
+
engagement: 0,
|
|
72
|
+
likes: 0,
|
|
73
|
+
collects: 0,
|
|
74
|
+
comments: 0,
|
|
75
|
+
shares: 0,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
channel: 'Video',
|
|
79
|
+
reads: d.video_read ?? 0,
|
|
80
|
+
engagement: d.video_engage ?? 0,
|
|
81
|
+
likes: d.video_like ?? 0,
|
|
82
|
+
collects: d.video_fav ?? 0,
|
|
83
|
+
comments: d.video_cmt ?? 0,
|
|
84
|
+
shares: d.video_share ?? 0,
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
},
|
|
88
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Xiaohongshu Creator Note List — per-note metrics from the creator backend.
|
|
3
|
+
*
|
|
4
|
+
* Navigates to the note manager page and extracts per-note data from
|
|
5
|
+
* the rendered DOM. This approach bypasses the v2 API signature requirement.
|
|
6
|
+
*
|
|
7
|
+
* Returns: note title, publish date, views, likes, collects, comments.
|
|
8
|
+
*
|
|
9
|
+
* Requires: logged into creator.xiaohongshu.com in Chrome.
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Xiaohongshu Creator Note List — per-note metrics from the creator backend.
|
|
3
|
+
*
|
|
4
|
+
* Navigates to the note manager page and extracts per-note data from
|
|
5
|
+
* the rendered DOM. This approach bypasses the v2 API signature requirement.
|
|
6
|
+
*
|
|
7
|
+
* Returns: note title, publish date, views, likes, collects, comments.
|
|
8
|
+
*
|
|
9
|
+
* Requires: logged into creator.xiaohongshu.com in Chrome.
|
|
10
|
+
*/
|
|
11
|
+
import { cli, Strategy } from '../../registry.js';
|
|
12
|
+
cli({
|
|
13
|
+
site: 'xiaohongshu',
|
|
14
|
+
name: 'creator-notes',
|
|
15
|
+
description: '小红书创作者笔记列表 + 每篇数据 (标题/日期/观看/点赞/收藏/评论)',
|
|
16
|
+
domain: 'creator.xiaohongshu.com',
|
|
17
|
+
strategy: Strategy.COOKIE,
|
|
18
|
+
browser: true,
|
|
19
|
+
args: [
|
|
20
|
+
{ name: 'limit', type: 'int', default: 20, help: 'Number of notes to return' },
|
|
21
|
+
],
|
|
22
|
+
columns: ['rank', 'id', 'title', 'date', 'views', 'likes', 'collects', 'comments', 'url'],
|
|
23
|
+
func: async (page, kwargs) => {
|
|
24
|
+
const limit = kwargs.limit || 20;
|
|
25
|
+
// Navigate to note manager
|
|
26
|
+
await page.goto('https://creator.xiaohongshu.com/new/note-manager');
|
|
27
|
+
await page.wait(4);
|
|
28
|
+
// Scroll to load more notes if needed
|
|
29
|
+
await page.autoScroll({ times: Math.ceil(limit / 10), delayMs: 1500 });
|
|
30
|
+
// Extract note data from rendered DOM
|
|
31
|
+
const notes = await page.evaluate(`
|
|
32
|
+
(() => {
|
|
33
|
+
const results = [];
|
|
34
|
+
// Note cards in the manager page contain title, date, and metric numbers
|
|
35
|
+
// Each note card has a consistent structure with the title, date line,
|
|
36
|
+
// and a row of 4 numbers (views, likes, collects, comments)
|
|
37
|
+
const cards = document.querySelectorAll('[class*="note-item"], [class*="noteItem"], [class*="card"]');
|
|
38
|
+
|
|
39
|
+
if (cards.length === 0) {
|
|
40
|
+
// Fallback: parse from any container with note-like content
|
|
41
|
+
const allText = document.body.innerText;
|
|
42
|
+
const notePattern = /(.+?)\\s+发布于\\s+(\\d{4}年\\d{2}月\\d{2}日\\s+\\d{2}:\\d{2})\\s*(\\d+)\\s*(\\d+)\\s*(\\d+)\\s*(\\d+)/g;
|
|
43
|
+
let match;
|
|
44
|
+
while ((match = notePattern.exec(allText)) !== null) {
|
|
45
|
+
results.push({
|
|
46
|
+
title: match[1].trim(),
|
|
47
|
+
date: match[2],
|
|
48
|
+
views: parseInt(match[3]) || 0,
|
|
49
|
+
likes: parseInt(match[4]) || 0,
|
|
50
|
+
collects: parseInt(match[5]) || 0,
|
|
51
|
+
comments: parseInt(match[6]) || 0,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return results;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
cards.forEach(card => {
|
|
58
|
+
const text = card.innerText || '';
|
|
59
|
+
const linkEl = card.querySelector('a[href*="/publish/"], a[href*="/note/"], a[href*="/explore/"]');
|
|
60
|
+
const href = linkEl?.getAttribute('href') || '';
|
|
61
|
+
const idMatch = href.match(/\/(?:publish|explore|note)\/([a-zA-Z0-9]+)/);
|
|
62
|
+
// Try to extract structured data
|
|
63
|
+
const lines = text.split('\\n').map(l => l.trim()).filter(Boolean);
|
|
64
|
+
if (lines.length < 2) return;
|
|
65
|
+
|
|
66
|
+
const title = lines[0];
|
|
67
|
+
const dateLine = lines.find(l => l.includes('发布于'));
|
|
68
|
+
const dateMatch = dateLine?.match(/发布于\\s+(\\d{4}年\\d{2}月\\d{2}日\\s+\\d{2}:\\d{2})/);
|
|
69
|
+
|
|
70
|
+
// Remove the publish timestamp before collecting note metrics.
|
|
71
|
+
// Otherwise year/month/day/hour digits are picked up as views/likes/etc.
|
|
72
|
+
const metricText = dateLine ? text.replace(dateLine, ' ') : text;
|
|
73
|
+
const nums = metricText.match(/(?:^|\\s)(\\d+)(?:\\s|$)/g)?.map(n => parseInt(n.trim())) || [];
|
|
74
|
+
|
|
75
|
+
if (title && !title.includes('全部笔记')) {
|
|
76
|
+
results.push({
|
|
77
|
+
id: idMatch ? idMatch[1] : '',
|
|
78
|
+
title: title.replace(/\\s+/g, ' ').substring(0, 80),
|
|
79
|
+
date: dateMatch ? dateMatch[1] : '',
|
|
80
|
+
views: nums[0] || 0,
|
|
81
|
+
likes: nums[1] || 0,
|
|
82
|
+
collects: nums[2] || 0,
|
|
83
|
+
comments: nums[3] || 0,
|
|
84
|
+
url: href ? new URL(href, window.location.origin).toString() : '',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return results;
|
|
90
|
+
})()
|
|
91
|
+
`);
|
|
92
|
+
if (!Array.isArray(notes) || notes.length === 0) {
|
|
93
|
+
throw new Error('No notes found. Are you logged into creator.xiaohongshu.com?');
|
|
94
|
+
}
|
|
95
|
+
return notes
|
|
96
|
+
.slice(0, limit)
|
|
97
|
+
.map((n, i) => ({
|
|
98
|
+
rank: i + 1,
|
|
99
|
+
id: n.id,
|
|
100
|
+
title: n.title,
|
|
101
|
+
date: n.date,
|
|
102
|
+
views: n.views,
|
|
103
|
+
likes: n.likes,
|
|
104
|
+
collects: n.collects,
|
|
105
|
+
comments: n.comments,
|
|
106
|
+
url: n.url,
|
|
107
|
+
}));
|
|
108
|
+
},
|
|
109
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Xiaohongshu Creator Profile — creator account info and growth status.
|
|
3
|
+
*
|
|
4
|
+
* Uses the creator.xiaohongshu.com internal API (cookie auth).
|
|
5
|
+
* Returns follower/following counts, total likes+collects, and
|
|
6
|
+
* creator level growth info.
|
|
7
|
+
*
|
|
8
|
+
* Requires: logged into creator.xiaohongshu.com in Chrome.
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Xiaohongshu Creator Profile — creator account info and growth status.
|
|
3
|
+
*
|
|
4
|
+
* Uses the creator.xiaohongshu.com internal API (cookie auth).
|
|
5
|
+
* Returns follower/following counts, total likes+collects, and
|
|
6
|
+
* creator level growth info.
|
|
7
|
+
*
|
|
8
|
+
* Requires: logged into creator.xiaohongshu.com in Chrome.
|
|
9
|
+
*/
|
|
10
|
+
import { cli, Strategy } from '../../registry.js';
|
|
11
|
+
cli({
|
|
12
|
+
site: 'xiaohongshu',
|
|
13
|
+
name: 'creator-profile',
|
|
14
|
+
description: '小红书创作者账号信息 (粉丝/关注/获赞/成长等级)',
|
|
15
|
+
domain: 'creator.xiaohongshu.com',
|
|
16
|
+
strategy: Strategy.COOKIE,
|
|
17
|
+
browser: true,
|
|
18
|
+
args: [],
|
|
19
|
+
columns: ['field', 'value'],
|
|
20
|
+
func: async (page, _kwargs) => {
|
|
21
|
+
await page.goto('https://creator.xiaohongshu.com/new/home');
|
|
22
|
+
await page.wait(3);
|
|
23
|
+
const data = await page.evaluate(`
|
|
24
|
+
async () => {
|
|
25
|
+
try {
|
|
26
|
+
const resp = await fetch('/api/galaxy/creator/home/personal_info', {
|
|
27
|
+
credentials: 'include',
|
|
28
|
+
});
|
|
29
|
+
if (!resp.ok) return { error: 'HTTP ' + resp.status };
|
|
30
|
+
return await resp.json();
|
|
31
|
+
} catch (e) {
|
|
32
|
+
return { error: e.message };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
`);
|
|
36
|
+
if (data?.error) {
|
|
37
|
+
throw new Error(data.error + '. Are you logged into creator.xiaohongshu.com?');
|
|
38
|
+
}
|
|
39
|
+
if (!data?.data) {
|
|
40
|
+
throw new Error('Unexpected response structure');
|
|
41
|
+
}
|
|
42
|
+
const d = data.data;
|
|
43
|
+
const grow = d.grow_info || {};
|
|
44
|
+
return [
|
|
45
|
+
{ field: 'Name', value: d.name ?? '' },
|
|
46
|
+
{ field: 'Followers', value: d.fans_count ?? 0 },
|
|
47
|
+
{ field: 'Following', value: d.follow_count ?? 0 },
|
|
48
|
+
{ field: 'Likes & Collects', value: d.faved_count ?? 0 },
|
|
49
|
+
{ field: 'Creator Level', value: grow.level ?? 0 },
|
|
50
|
+
{ field: 'Level Progress', value: `${grow.fans_count ?? 0}/${grow.max_fans_count ?? 0} fans` },
|
|
51
|
+
{ field: 'Bio', value: (d.personal_desc ?? '').replace(/\\n/g, ' | ') },
|
|
52
|
+
];
|
|
53
|
+
},
|
|
54
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Xiaohongshu Creator Analytics — account-level metrics overview.
|
|
3
|
+
*
|
|
4
|
+
* Uses the creator.xiaohongshu.com internal API (cookie auth).
|
|
5
|
+
* Returns 7-day and 30-day aggregate stats: views, likes, collects,
|
|
6
|
+
* comments, shares, new followers, and daily trend data.
|
|
7
|
+
*
|
|
8
|
+
* Requires: logged into creator.xiaohongshu.com in Chrome.
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Xiaohongshu Creator Analytics — account-level metrics overview.
|
|
3
|
+
*
|
|
4
|
+
* Uses the creator.xiaohongshu.com internal API (cookie auth).
|
|
5
|
+
* Returns 7-day and 30-day aggregate stats: views, likes, collects,
|
|
6
|
+
* comments, shares, new followers, and daily trend data.
|
|
7
|
+
*
|
|
8
|
+
* Requires: logged into creator.xiaohongshu.com in Chrome.
|
|
9
|
+
*/
|
|
10
|
+
import { cli, Strategy } from '../../registry.js';
|
|
11
|
+
cli({
|
|
12
|
+
site: 'xiaohongshu',
|
|
13
|
+
name: 'creator-stats',
|
|
14
|
+
description: '小红书创作者数据总览 (观看/点赞/收藏/评论/分享/涨粉,含每日趋势)',
|
|
15
|
+
domain: 'creator.xiaohongshu.com',
|
|
16
|
+
strategy: Strategy.COOKIE,
|
|
17
|
+
browser: true,
|
|
18
|
+
args: [
|
|
19
|
+
{
|
|
20
|
+
name: 'period',
|
|
21
|
+
type: 'string',
|
|
22
|
+
default: 'seven',
|
|
23
|
+
help: 'Stats period: seven or thirty',
|
|
24
|
+
choices: ['seven', 'thirty'],
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
columns: ['metric', 'total', 'trend'],
|
|
28
|
+
func: async (page, kwargs) => {
|
|
29
|
+
const period = kwargs.period || 'seven';
|
|
30
|
+
// Navigate to creator center for cookie context
|
|
31
|
+
await page.goto('https://creator.xiaohongshu.com/new/home');
|
|
32
|
+
await page.wait(3);
|
|
33
|
+
const data = await page.evaluate(`
|
|
34
|
+
async () => {
|
|
35
|
+
try {
|
|
36
|
+
const resp = await fetch('/api/galaxy/creator/data/note_detail_new', {
|
|
37
|
+
credentials: 'include',
|
|
38
|
+
});
|
|
39
|
+
if (!resp.ok) return { error: 'HTTP ' + resp.status };
|
|
40
|
+
return await resp.json();
|
|
41
|
+
} catch (e) {
|
|
42
|
+
return { error: e.message };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
`);
|
|
46
|
+
if (data?.error) {
|
|
47
|
+
throw new Error(data.error + '. Are you logged into creator.xiaohongshu.com?');
|
|
48
|
+
}
|
|
49
|
+
if (!data?.data) {
|
|
50
|
+
throw new Error('Unexpected response structure');
|
|
51
|
+
}
|
|
52
|
+
const stats = data.data[period];
|
|
53
|
+
if (!stats) {
|
|
54
|
+
throw new Error(`No data for period "${period}". Available: ${Object.keys(data.data).join(', ')}`);
|
|
55
|
+
}
|
|
56
|
+
// Format daily trend as sparkline-like summary
|
|
57
|
+
const formatTrend = (list) => {
|
|
58
|
+
if (!list || !list.length)
|
|
59
|
+
return '-';
|
|
60
|
+
return list.map((d) => d.count).join(' → ');
|
|
61
|
+
};
|
|
62
|
+
return [
|
|
63
|
+
{ metric: '观看数 (views)', total: stats.view_count ?? 0, trend: formatTrend(stats.view_list) },
|
|
64
|
+
{ metric: '平均观看时长 (avg view time ms)', total: stats.view_time_avg ?? 0, trend: formatTrend(stats.view_time_list) },
|
|
65
|
+
{ metric: '主页访问 (home views)', total: stats.home_view_count ?? 0, trend: formatTrend(stats.home_view_list) },
|
|
66
|
+
{ metric: '点赞数 (likes)', total: stats.like_count ?? 0, trend: formatTrend(stats.like_list) },
|
|
67
|
+
{ metric: '收藏数 (collects)', total: stats.collect_count ?? 0, trend: formatTrend(stats.collect_list) },
|
|
68
|
+
{ metric: '评论数 (comments)', total: stats.comment_count ?? 0, trend: formatTrend(stats.comment_list) },
|
|
69
|
+
{ metric: '弹幕数 (danmaku)', total: stats.danmaku_count ?? 0, trend: '-' },
|
|
70
|
+
{ metric: '分享数 (shares)', total: stats.share_count ?? 0, trend: formatTrend(stats.share_list) },
|
|
71
|
+
{ metric: '涨粉数 (new followers)', total: stats.rise_fans_count ?? 0, trend: formatTrend(stats.rise_fans_list) },
|
|
72
|
+
];
|
|
73
|
+
},
|
|
74
|
+
});
|