@easyasstudio/xhs-cli 1.4.3
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/AGENTS.md +29 -0
- package/LICENSE +641 -0
- package/README.md +108 -0
- package/dist/browser/cookie.d.ts +1 -0
- package/dist/browser/cookie.d.ts.map +1 -0
- package/dist/browser/cookie.js +2 -0
- package/dist/browser/cookie.js.map +1 -0
- package/dist/browser/index.d.ts +7 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +201 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/cli/cliRouter.d.ts +6 -0
- package/dist/cli/cliRouter.d.ts.map +1 -0
- package/dist/cli/cliRouter.js +249 -0
- package/dist/cli/cliRouter.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +18 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/parseArgs.d.ts +13 -0
- package/dist/cli/parseArgs.d.ts.map +1 -0
- package/dist/cli/parseArgs.js +64 -0
- package/dist/cli/parseArgs.js.map +1 -0
- package/dist/config.d.ts +29 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +67 -0
- package/dist/config.js.map +1 -0
- package/dist/toolset/accountRegistry.d.ts +33 -0
- package/dist/toolset/accountRegistry.d.ts.map +1 -0
- package/dist/toolset/accountRegistry.js +198 -0
- package/dist/toolset/accountRegistry.js.map +1 -0
- package/dist/toolset/cachePaths.d.ts +3 -0
- package/dist/toolset/cachePaths.d.ts.map +1 -0
- package/dist/toolset/cachePaths.js +7 -0
- package/dist/toolset/cachePaths.js.map +1 -0
- package/dist/toolset/get_metrics.d.ts +47 -0
- package/dist/toolset/get_metrics.d.ts.map +1 -0
- package/dist/toolset/get_metrics.js +201 -0
- package/dist/toolset/get_metrics.js.map +1 -0
- package/dist/toolset/get_note_detail.d.ts +3 -0
- package/dist/toolset/get_note_detail.d.ts.map +1 -0
- package/dist/toolset/get_note_detail.js +100 -0
- package/dist/toolset/get_note_detail.js.map +1 -0
- package/dist/toolset/get_profile.d.ts +16 -0
- package/dist/toolset/get_profile.d.ts.map +1 -0
- package/dist/toolset/get_profile.js +79 -0
- package/dist/toolset/get_profile.js.map +1 -0
- package/dist/toolset/get_recent_posts.d.ts +3 -0
- package/dist/toolset/get_recent_posts.d.ts.map +1 -0
- package/dist/toolset/get_recent_posts.js +113 -0
- package/dist/toolset/get_recent_posts.js.map +1 -0
- package/dist/toolset/index.d.ts +12 -0
- package/dist/toolset/index.d.ts.map +1 -0
- package/dist/toolset/index.js +48 -0
- package/dist/toolset/index.js.map +1 -0
- package/dist/toolset/login.d.ts +4 -0
- package/dist/toolset/login.d.ts.map +1 -0
- package/dist/toolset/login.js +194 -0
- package/dist/toolset/login.js.map +1 -0
- package/dist/toolset/post.d.ts +27 -0
- package/dist/toolset/post.d.ts.map +1 -0
- package/dist/toolset/post.js +148 -0
- package/dist/toolset/post.js.map +1 -0
- package/dist/toolset/postValidate.d.ts +8 -0
- package/dist/toolset/postValidate.d.ts.map +1 -0
- package/dist/toolset/postValidate.js +59 -0
- package/dist/toolset/postValidate.js.map +1 -0
- package/dist/toolset/publishButton.d.ts +7 -0
- package/dist/toolset/publishButton.d.ts.map +1 -0
- package/dist/toolset/publishButton.js +28 -0
- package/dist/toolset/publishButton.js.map +1 -0
- package/dist/toolset/publishedRecords.d.ts +15 -0
- package/dist/toolset/publishedRecords.d.ts.map +1 -0
- package/dist/toolset/publishedRecords.js +60 -0
- package/dist/toolset/publishedRecords.js.map +1 -0
- package/dist/toolset/sessionResolve.d.ts +12 -0
- package/dist/toolset/sessionResolve.d.ts.map +1 -0
- package/dist/toolset/sessionResolve.js +39 -0
- package/dist/toolset/sessionResolve.js.map +1 -0
- package/dist/toolset/sessionTypes.d.ts +13 -0
- package/dist/toolset/sessionTypes.d.ts.map +1 -0
- package/dist/toolset/sessionTypes.js +3 -0
- package/dist/toolset/sessionTypes.js.map +1 -0
- package/dist/utils/cache.d.ts +10 -0
- package/dist/utils/cache.d.ts.map +1 -0
- package/dist/utils/cache.js +124 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/titleToFilename.d.ts +2 -0
- package/dist/utils/titleToFilename.d.ts.map +1 -0
- package/dist/utils/titleToFilename.js +19 -0
- package/dist/utils/titleToFilename.js.map +1 -0
- package/dist/utils/version_check.d.ts +2 -0
- package/dist/utils/version_check.d.ts.map +1 -0
- package/dist/utils/version_check.js +105 -0
- package/dist/utils/version_check.js.map +1 -0
- package/package.json +63 -0
package/README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# xhs-cli
|
|
2
|
+
|
|
3
|
+
在终端里使用的小红书工具:**登录**、**创作者指标**、**创作者后台已发笔记**(`recent`)、**单篇笔记详情**(`detail`)、**本地发帖归档**(`posted`),以及用**标题 + 正文 + 本地图片路径**在创作后台填表发帖(`post`,需本机 Chrome/Chromium)。
|
|
4
|
+
|
|
5
|
+
支持**多账号隔离**(每账号独立浏览器会话目录),适合与 Agent 或自动化流程编排配合;发帖仍以**本地人机确认**为前提。
|
|
6
|
+
|
|
7
|
+
## 依赖
|
|
8
|
+
|
|
9
|
+
- **Node.js** 20 及以上
|
|
10
|
+
- **Chrome 或 Chromium**(本机已安装;CLI 通过 Puppeteer 连接,不随包下载浏览器)
|
|
11
|
+
|
|
12
|
+
## 安装
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install -g xhs-cli
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
安装后使用命令 **`xhs`**。完整子命令列表:`xhs help`。
|
|
19
|
+
|
|
20
|
+
## 快速开始
|
|
21
|
+
|
|
22
|
+
### 1. 添加账号并登录
|
|
23
|
+
|
|
24
|
+
账号名仅允许 `[a-zA-Z0-9._-]+`:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
xhs account add my-account
|
|
28
|
+
xhs account use my-account
|
|
29
|
+
xhs login
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`login` 成功后会自动把该账号记为**当前账号**。会话保存在 `~/.config/xhs-cli/.cache/accounts/<name>/browser-data`。
|
|
33
|
+
|
|
34
|
+
### 2. 发帖(填表)
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
xhs post \
|
|
38
|
+
--title "标题" \
|
|
39
|
+
--content "正文至少十个字,须满足小红书长度校验" \
|
|
40
|
+
--image ./cover.png
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- `--image` 可重复,至少 1 张、最多 18 张;也可用 `--content-file <路径>` 代替 `--content`。
|
|
44
|
+
- 默认只填表,**不会**自动点击发布;加 `--publish` 会在填表后尝试点击页面「发布」按钮。
|
|
45
|
+
- 临时操作其他账号:`--account other`。
|
|
46
|
+
|
|
47
|
+
### 3. 查看数据
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
xhs metrics
|
|
51
|
+
xhs recent --limit 20
|
|
52
|
+
xhs detail <noteId>
|
|
53
|
+
xhs posted
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## 当前账号
|
|
57
|
+
|
|
58
|
+
| 命令 | 说明 |
|
|
59
|
+
|------|------|
|
|
60
|
+
| `xhs account use <name>` | 设置当前账号(写入 `registry.json`) |
|
|
61
|
+
| `xhs account current` | 打印当前账号 slug |
|
|
62
|
+
| `xhs account list` | 列表中带 `*` 的为当前账号 |
|
|
63
|
+
|
|
64
|
+
业务命令**默认使用当前账号**,无需每次写账号参数。临时切换仅支持 **`--account <slug>`**。
|
|
65
|
+
|
|
66
|
+
未设置当前账号时,命令会提示先执行 `xhs account use <name>`。
|
|
67
|
+
|
|
68
|
+
## 命令一览
|
|
69
|
+
|
|
70
|
+
| 命令 | 说明 |
|
|
71
|
+
|------|------|
|
|
72
|
+
| `xhs help` | 打印帮助 |
|
|
73
|
+
| `xhs account list` | 列出已配置账号 |
|
|
74
|
+
| `xhs account add <name>` | 新建账号(首个账号会自动设为当前) |
|
|
75
|
+
| `xhs account show <name>` | 查看单账号配置 |
|
|
76
|
+
| `xhs account use <name>` | 设置当前账号 |
|
|
77
|
+
| `xhs account current` | 显示当前账号 |
|
|
78
|
+
| `xhs login [--account <name>]` | 登录创作者中心 |
|
|
79
|
+
| `xhs metrics [--account <name>]` | 运营数据摘要 |
|
|
80
|
+
| `xhs recent [--limit N] [--account <name>]` | 创作者后台已发笔记 |
|
|
81
|
+
| `xhs detail <noteId> [--account <name>]` | 笔记详情 |
|
|
82
|
+
| `xhs posted [--account <name>]` | 本地 `published/` 归档列表 |
|
|
83
|
+
| `xhs post --title … (--content … \| --content-file …) --image … [--account <name>]` | 创作页填表发帖 |
|
|
84
|
+
|
|
85
|
+
## 数据目录
|
|
86
|
+
|
|
87
|
+
默认路径:**`~/.config/xhs-cli/.cache/`**
|
|
88
|
+
|
|
89
|
+
| 路径 | 说明 |
|
|
90
|
+
|------|------|
|
|
91
|
+
| `accounts/registry.json` | 账号注册表(含 `currentAccount`) |
|
|
92
|
+
| `accounts/<name>/browser-data/` | 该账号 Chrome 会话(Cookie 等) |
|
|
93
|
+
| `published/` | 本地发帖归档 JSON(`xhs posted`) |
|
|
94
|
+
|
|
95
|
+
## 发帖说明
|
|
96
|
+
|
|
97
|
+
- 本工具**不会**在未经你确认的情况下向小红书发稿。
|
|
98
|
+
- `xhs post` 未加 `--publish` 时:填入标题、正文、图片后保持浏览器窗口,由你在页面中确认并发布。
|
|
99
|
+
- `--publish` 会尝试自动点击「发布」,是否成功以页面实际状态为准。
|
|
100
|
+
|
|
101
|
+
## 许可证
|
|
102
|
+
|
|
103
|
+
[GNU General Public License v3.0](./LICENSE)(GPL-3.0)
|
|
104
|
+
|
|
105
|
+
## 链接
|
|
106
|
+
|
|
107
|
+
- 仓库:<https://github.com/joohw/xhs-cli>
|
|
108
|
+
- Issues:<https://github.com/joohw/xhs-cli/issues>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=cookie.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookie.d.ts","sourceRoot":"","sources":["../../src/browser/cookie.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookie.js","sourceRoot":"","sources":["../../src/browser/cookie.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Browser, Page } from 'puppeteer-core';
|
|
2
|
+
export declare function getUserDataDir(override?: string): string;
|
|
3
|
+
export declare function launchBrowser(headless?: boolean, extraArgs?: string[], userDataDirOverride?: string): Promise<Browser>;
|
|
4
|
+
export declare function createLoggedInPage(browserUserDataDir?: string): Promise<Page>;
|
|
5
|
+
export declare function withBrowser<T>(headless: boolean, callback: (page: Page) => Promise<T>): Promise<T>;
|
|
6
|
+
export declare function withLoggedInPage<T>(callback: (page: Page) => Promise<T>, browserUserDataDir?: string): Promise<T>;
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/browser/index.ts"],"names":[],"mappings":"AACA,OAAkB,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AA+C1D,wBAAgB,cAAc,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAGxD;AAgCD,wBAAsB,aAAa,CACjC,QAAQ,GAAE,OAAc,EACxB,SAAS,GAAE,MAAM,EAAO,EACxB,mBAAmB,CAAC,EAAE,MAAM,GAC3B,OAAO,CAAC,OAAO,CAAC,CA0ElB;AAID,wBAAsB,kBAAkB,CACtC,kBAAkB,CAAC,EAAE,MAAM,GAC1B,OAAO,CAAC,IAAI,CAAC,CAef;AAKD,wBAAsB,WAAW,CAAC,CAAC,EACjC,QAAQ,EAAE,OAAO,EACjB,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,GACnC,OAAO,CAAC,CAAC,CAAC,CAQZ;AAKD,wBAAsB,gBAAgB,CAAC,CAAC,EACtC,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,EACpC,kBAAkB,CAAC,EAAE,MAAM,GAC1B,OAAO,CAAC,CAAC,CAAC,CAiBZ"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// 浏览器工具模块 - 支持无头模式访问
|
|
2
|
+
import puppeteer from 'puppeteer-core';
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { homedir, platform } from 'os';
|
|
6
|
+
import { BROWSER_USER_DATA_DIR, ensureAppDataLayout } from '../config.js';
|
|
7
|
+
// 查找系统 Chrome 路径(跨平台支持)
|
|
8
|
+
function findChromePath() {
|
|
9
|
+
if (process.env.CHROME_PATH && existsSync(process.env.CHROME_PATH)) {
|
|
10
|
+
return process.env.CHROME_PATH;
|
|
11
|
+
}
|
|
12
|
+
const os = platform();
|
|
13
|
+
let possiblePaths = [];
|
|
14
|
+
if (os === 'win32') {
|
|
15
|
+
// Windows 路径
|
|
16
|
+
possiblePaths = [
|
|
17
|
+
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
|
18
|
+
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
|
|
19
|
+
process.env.LOCALAPPDATA ? join(process.env.LOCALAPPDATA, 'Google', 'Chrome', 'Application', 'chrome.exe') : '',
|
|
20
|
+
];
|
|
21
|
+
}
|
|
22
|
+
else if (os === 'darwin') {
|
|
23
|
+
// macOS 路径
|
|
24
|
+
possiblePaths = [
|
|
25
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
26
|
+
join(homedir(), 'Applications', 'Google Chrome.app', 'Contents', 'MacOS', 'Google Chrome'),
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
// Linux 路径
|
|
31
|
+
possiblePaths = [
|
|
32
|
+
'/usr/bin/google-chrome',
|
|
33
|
+
'/usr/bin/google-chrome-stable',
|
|
34
|
+
'/usr/bin/chromium',
|
|
35
|
+
'/usr/bin/chromium-browser',
|
|
36
|
+
'/snap/bin/chromium',
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
for (const path of possiblePaths) {
|
|
40
|
+
if (path && existsSync(path)) {
|
|
41
|
+
return path;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
// 获取可选的 userDataDir;未传入时仍为全局默认 ~/.xhs-cli/.cache/browser-data
|
|
47
|
+
export function getUserDataDir(override) {
|
|
48
|
+
ensureAppDataLayout();
|
|
49
|
+
return override ?? BROWSER_USER_DATA_DIR;
|
|
50
|
+
}
|
|
51
|
+
const PROFILE_IN_USE_RE = /already running for/i;
|
|
52
|
+
/** 同一 userDataDir 已有 Chrome 时,通过 DevTools 端口复用实例(常见于 post 后 disconnect 未关窗) */
|
|
53
|
+
async function connectToRunningBrowser(userDataDir) {
|
|
54
|
+
const portFile = join(userDataDir, 'DevToolsActivePort');
|
|
55
|
+
if (!existsSync(portFile)) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
const [portLine] = readFileSync(portFile, 'utf-8').trim().split('\n');
|
|
60
|
+
if (!portLine || !/^\d+$/.test(portLine)) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
return await puppeteer.connect({
|
|
64
|
+
browserURL: `http://127.0.0.1:${portLine}`,
|
|
65
|
+
defaultViewport: null,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function profileInUseError(userDataDir) {
|
|
73
|
+
return new Error(`该账号的 Chrome 配置目录已被占用,无法再次启动浏览器:\n${userDataDir}\n` +
|
|
74
|
+
'请先关闭使用此配置目录的 Chrome 窗口(含上次 xhs post 未关的窗口),或结束残留 Chrome 进程后重试。');
|
|
75
|
+
}
|
|
76
|
+
// 启动浏览器(支持无头模式)
|
|
77
|
+
export async function launchBrowser(headless = true, extraArgs = [], userDataDirOverride) {
|
|
78
|
+
const chromePath = findChromePath();
|
|
79
|
+
const userDataDir = getUserDataDir(userDataDirOverride);
|
|
80
|
+
if (!existsSync(userDataDir)) {
|
|
81
|
+
mkdirSync(userDataDir, { recursive: true });
|
|
82
|
+
}
|
|
83
|
+
// 基础参数
|
|
84
|
+
const baseArgs = [
|
|
85
|
+
'--no-sandbox',
|
|
86
|
+
'--disable-setuid-sandbox',
|
|
87
|
+
'--disable-dev-shm-usage',
|
|
88
|
+
'--disable-gpu',
|
|
89
|
+
'--disable-blink-features=AutomationControlled',
|
|
90
|
+
'--disable-restore-session-state',
|
|
91
|
+
'--disable-session-crashed-bubble',
|
|
92
|
+
'--disable-infobars',
|
|
93
|
+
'--no-first-run',
|
|
94
|
+
'--no-default-browser-check',
|
|
95
|
+
'--disable-background-networking',
|
|
96
|
+
'--disable-sync',
|
|
97
|
+
'--disable-translate',
|
|
98
|
+
'--disable-features=TranslateUI',
|
|
99
|
+
'--disable-ipc-flooding-protection',
|
|
100
|
+
'--enable-features=NetworkService,NetworkServiceInProcess',
|
|
101
|
+
];
|
|
102
|
+
// 非无头模式时添加最大化参数
|
|
103
|
+
if (!headless) {
|
|
104
|
+
baseArgs.push('--start-maximized');
|
|
105
|
+
}
|
|
106
|
+
// 如果找到了 Chrome 路径,使用它;否则让 Puppeteer 自动查找
|
|
107
|
+
const launchOptions = {
|
|
108
|
+
headless: headless,
|
|
109
|
+
userDataDir: userDataDir,
|
|
110
|
+
args: [...baseArgs, ...extraArgs],
|
|
111
|
+
defaultViewport: headless ? { width: 1280, height: 720 } : null,
|
|
112
|
+
// 确保浏览器进程正确关闭,以便保存cookie
|
|
113
|
+
handleSIGINT: true,
|
|
114
|
+
handleSIGTERM: true,
|
|
115
|
+
handleSIGHUP: true,
|
|
116
|
+
};
|
|
117
|
+
if (chromePath) {
|
|
118
|
+
launchOptions.executablePath = chromePath;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
// 如果找不到系统浏览器,尝试使用 puppeteer 的内置方法查找
|
|
122
|
+
// 注意:如果设置了 PUPPETEER_SKIP_CHROMIUM_DOWNLOAD,这里可能会失败
|
|
123
|
+
try {
|
|
124
|
+
const executablePath = puppeteer.executablePath();
|
|
125
|
+
if (executablePath && existsSync(executablePath)) {
|
|
126
|
+
launchOptions.executablePath = executablePath;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
// 忽略错误,继续尝试启动
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
return await puppeteer.launch(launchOptions);
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
if (error instanceof Error) {
|
|
138
|
+
if (PROFILE_IN_USE_RE.test(error.message)) {
|
|
139
|
+
const existing = await connectToRunningBrowser(userDataDir);
|
|
140
|
+
if (existing) {
|
|
141
|
+
return existing;
|
|
142
|
+
}
|
|
143
|
+
throw profileInUseError(userDataDir);
|
|
144
|
+
}
|
|
145
|
+
if (error.message.includes('Executable doesn\'t exist') || error.message.includes('Could not find browser')) {
|
|
146
|
+
throw new Error('未找到浏览器。请确保已安装 Chrome/Chromium 浏览器,或通过环境变量 CHROME_PATH 指定浏览器路径。\n' +
|
|
147
|
+
'例如:export CHROME_PATH="/path/to/chrome" (Linux/Mac) 或 set CHROME_PATH="C:\\path\\to\\chrome.exe" (Windows)');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// 创建已登录的页面(无头模式)
|
|
154
|
+
export async function createLoggedInPage(browserUserDataDir) {
|
|
155
|
+
const browser = await launchBrowser(true, [], browserUserDataDir);
|
|
156
|
+
const page = await browser.newPage();
|
|
157
|
+
// 访问创作者中心首页验证登录状态
|
|
158
|
+
await page.goto('https://creator.xiaohongshu.com/new/home', {
|
|
159
|
+
waitUntil: 'domcontentloaded',
|
|
160
|
+
timeout: 10000,
|
|
161
|
+
});
|
|
162
|
+
const currentUrl = page.url();
|
|
163
|
+
const isLoginPage = currentUrl.includes('/login') || currentUrl.includes('/signin');
|
|
164
|
+
if (isLoginPage) {
|
|
165
|
+
await browser.close();
|
|
166
|
+
throw new Error('未登录,请先运行 npm run cli login 进行登录');
|
|
167
|
+
}
|
|
168
|
+
return page;
|
|
169
|
+
}
|
|
170
|
+
// 执行页面操作(自动管理浏览器生命周期)
|
|
171
|
+
export async function withBrowser(headless, callback) {
|
|
172
|
+
const browser = await launchBrowser(headless);
|
|
173
|
+
try {
|
|
174
|
+
const page = await browser.newPage();
|
|
175
|
+
return await callback(page);
|
|
176
|
+
}
|
|
177
|
+
finally {
|
|
178
|
+
await browser.close();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// 执行已登录的页面操作(无头模式,自动验证登录状态)
|
|
182
|
+
export async function withLoggedInPage(callback, browserUserDataDir) {
|
|
183
|
+
const browser = await launchBrowser(true, [], browserUserDataDir);
|
|
184
|
+
try {
|
|
185
|
+
const page = await browser.newPage();
|
|
186
|
+
await page.goto('https://creator.xiaohongshu.com/new/home', {
|
|
187
|
+
waitUntil: 'domcontentloaded',
|
|
188
|
+
timeout: 10000,
|
|
189
|
+
});
|
|
190
|
+
const currentUrl = page.url();
|
|
191
|
+
const isLoginPage = currentUrl.includes('/login') || currentUrl.includes('/signin');
|
|
192
|
+
if (isLoginPage) {
|
|
193
|
+
throw new Error('未登录,请先运行 npm run cli login 进行登录');
|
|
194
|
+
}
|
|
195
|
+
return await callback(page);
|
|
196
|
+
}
|
|
197
|
+
finally {
|
|
198
|
+
await browser.close();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/browser/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cliRouter.d.ts","sourceRoot":"","sources":["../../src/cli/cliRouter.ts"],"names":[],"mappings":"AA8IA;;GAEG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAmHjE;AAUD,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAiB1D"}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI:子命令直接调用 toolset 中的 impl*;自然语言 Agent 由外部宿主集成。
|
|
3
|
+
*/
|
|
4
|
+
import { readFileSync, existsSync } from 'fs';
|
|
5
|
+
import { implLogin, implGetOperationData, implPosted, implGetNoteDetail, implPost, resolveSession, } from '../toolset/index.js';
|
|
6
|
+
import { formatAccountListLines, addStoredAccount, formatShowAccount, setCurrentAccount, getCurrentAccount, } from '../toolset/accountRegistry.js';
|
|
7
|
+
import { resolveAccountSlug } from '../toolset/sessionResolve.js';
|
|
8
|
+
import { formatPublishedList, listPublished } from '../toolset/publishedRecords.js';
|
|
9
|
+
import { parseOpts, resolvePostPublish, accountFromOpts as readAccountFromOpts, collectImagePaths, } from './parseArgs.js';
|
|
10
|
+
class CliError extends Error {
|
|
11
|
+
constructor(message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = 'CliError';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function die(msg) {
|
|
17
|
+
throw new CliError(msg);
|
|
18
|
+
}
|
|
19
|
+
function printHelp() {
|
|
20
|
+
console.error(`xhs-cli — 小红书命令行工具(多账号 / 本地归档)
|
|
21
|
+
|
|
22
|
+
用法与说明:
|
|
23
|
+
xhs help
|
|
24
|
+
显示本帮助(无子命令时也会打印本说明)
|
|
25
|
+
|
|
26
|
+
# 业务命令默认使用当前账号(xhs account use);临时切换用 --account <name>
|
|
27
|
+
xhs login [--account <name>]
|
|
28
|
+
xhs metrics [--account <name>]
|
|
29
|
+
xhs recent [--limit <n>] [--account <name>]
|
|
30
|
+
xhs posted [--account <name>]
|
|
31
|
+
xhs detail <noteId> [--account <name>]
|
|
32
|
+
xhs post (--title <标题> (--content <正文> | --content-file <路径>))
|
|
33
|
+
[--image <路径>]... [--publish | --publish=true|false] [--account <name>]
|
|
34
|
+
|
|
35
|
+
# 账号(配置存 ~/.config/xhs-cli/.cache/accounts/registry.json ,每账号独立 browser-data)
|
|
36
|
+
xhs account list
|
|
37
|
+
xhs account add <name>
|
|
38
|
+
xhs account show <name>
|
|
39
|
+
xhs account use <name>
|
|
40
|
+
xhs account current
|
|
41
|
+
|
|
42
|
+
数据目录见 ~/.config/xhs-cli/.cache/(详见 README 与 src/config.ts)。
|
|
43
|
+
备注:不会在无人确认时擅自发帖;xhs post 的 --publish 由人工按需触发。
|
|
44
|
+
`);
|
|
45
|
+
}
|
|
46
|
+
function resolveSessionCli(explicitAccount) {
|
|
47
|
+
try {
|
|
48
|
+
const slug = explicitAccount?.trim();
|
|
49
|
+
return resolveSession(slug || undefined);
|
|
50
|
+
}
|
|
51
|
+
catch (e) {
|
|
52
|
+
die(`❌ ${e instanceof Error ? e.message : String(e)}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** 仅读取 `--account`;未指定时由 resolveSession 使用 registry 当前账号。 */
|
|
56
|
+
function accountFromOpts(opts) {
|
|
57
|
+
if (opts.account !== undefined && !opts.account.trim()) {
|
|
58
|
+
die('❌ --account 需要非空账号名');
|
|
59
|
+
}
|
|
60
|
+
return readAccountFromOpts(opts);
|
|
61
|
+
}
|
|
62
|
+
function runAccountCommand(tail) {
|
|
63
|
+
const sub = tail[0]?.toLowerCase()?.trim();
|
|
64
|
+
const rest = tail.slice(1);
|
|
65
|
+
if (!sub || sub === 'help' || sub === '--help') {
|
|
66
|
+
die(`❌ 用法: account list | add <name> | show <name> | use <name> | current`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
if (sub === 'use') {
|
|
71
|
+
const name = rest[0]?.trim();
|
|
72
|
+
if (!name) {
|
|
73
|
+
die('❌ 用法: account use <name>');
|
|
74
|
+
}
|
|
75
|
+
setCurrentAccount(name);
|
|
76
|
+
console.log(`✅ 当前账号: ${name}`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (sub === 'current') {
|
|
80
|
+
try {
|
|
81
|
+
const slug = resolveAccountSlug();
|
|
82
|
+
console.log(slug);
|
|
83
|
+
}
|
|
84
|
+
catch (e) {
|
|
85
|
+
die(`❌ ${e instanceof Error ? e.message : String(e)}`);
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (sub === 'list') {
|
|
90
|
+
console.log(formatAccountListLines());
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (sub === 'show') {
|
|
94
|
+
const name = rest[0]?.trim();
|
|
95
|
+
if (!name) {
|
|
96
|
+
die('❌ 用法: account show <name>');
|
|
97
|
+
}
|
|
98
|
+
console.log(formatShowAccount(name));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (sub === 'add') {
|
|
102
|
+
const name = rest[0]?.trim();
|
|
103
|
+
if (!name) {
|
|
104
|
+
die('❌ 用法: account add <name>');
|
|
105
|
+
}
|
|
106
|
+
addStoredAccount({ name });
|
|
107
|
+
const cur = getCurrentAccount();
|
|
108
|
+
console.log(cur === name
|
|
109
|
+
? `✅ 已添加账号: ${name}(已设为当前账号)`
|
|
110
|
+
: `✅ 已添加账号: ${name}`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (e) {
|
|
115
|
+
die(`❌ ${e instanceof Error ? e.message : String(e)}`);
|
|
116
|
+
}
|
|
117
|
+
die(`❌ 未知 account 子命令: ${sub}`);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* 执行一条子命令(与传入 `process.argv` 切片语义一致,不含 `xhs` 本身)。
|
|
121
|
+
*/
|
|
122
|
+
export async function runOneCommand(argv) {
|
|
123
|
+
if (argv.length === 0) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const cmd = argv[0];
|
|
127
|
+
const tail = argv.slice(1);
|
|
128
|
+
if (cmd === 'account') {
|
|
129
|
+
runAccountCommand(tail);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (cmd === 'drafts' || cmd === 'draft') {
|
|
133
|
+
die('❌ 本地草稿功能已移除,请使用:xhs post --title <标题> (--content | --content-file) --image <路径> …');
|
|
134
|
+
}
|
|
135
|
+
if (cmd === 'published') {
|
|
136
|
+
die('❌ published 已移除,请使用:xhs posted [--account <name>]');
|
|
137
|
+
}
|
|
138
|
+
if (cmd === 'login') {
|
|
139
|
+
const { opts, rest } = parseOpts(tail);
|
|
140
|
+
if (rest.length > 0) {
|
|
141
|
+
die('❌ 用法: login [--account <name>]');
|
|
142
|
+
}
|
|
143
|
+
const session = resolveSessionCli(accountFromOpts(opts));
|
|
144
|
+
const msg = await implLogin(session);
|
|
145
|
+
if (msg.startsWith('✅')) {
|
|
146
|
+
setCurrentAccount(session.account);
|
|
147
|
+
}
|
|
148
|
+
console.log(msg);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (cmd === 'metrics') {
|
|
152
|
+
const { opts, rest } = parseOpts(tail);
|
|
153
|
+
if (rest.length > 0) {
|
|
154
|
+
die('❌ 用法: metrics [--account <name>]');
|
|
155
|
+
}
|
|
156
|
+
console.log(await implGetOperationData(resolveSessionCli(accountFromOpts(opts))));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (cmd === 'posted') {
|
|
160
|
+
const { opts, rest } = parseOpts(tail);
|
|
161
|
+
if (rest.length > 0) {
|
|
162
|
+
die('❌ 用法: posted [--account <name>]');
|
|
163
|
+
}
|
|
164
|
+
const session = resolveSessionCli(accountFromOpts(opts));
|
|
165
|
+
console.log(formatPublishedList(listPublished(session.account)));
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (cmd === 'recent') {
|
|
169
|
+
const { opts, rest } = parseOpts(tail);
|
|
170
|
+
if (rest.length > 0) {
|
|
171
|
+
die('❌ 用法: recent [--limit <n>] [--account <name>]');
|
|
172
|
+
}
|
|
173
|
+
const lim = opts.limit !== undefined ? parseInt(opts.limit, 10) : undefined;
|
|
174
|
+
if (opts.limit !== undefined && (Number.isNaN(lim) || lim < 1)) {
|
|
175
|
+
die('❌ --limit 需为正整数');
|
|
176
|
+
}
|
|
177
|
+
console.log(await implPosted(lim, resolveSessionCli(accountFromOpts(opts))));
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (cmd === 'detail' || cmd === 'note-detail') {
|
|
181
|
+
const { opts, rest } = parseOpts(tail);
|
|
182
|
+
const id = rest[0]?.trim();
|
|
183
|
+
if (!id || rest.length > 1) {
|
|
184
|
+
die('❌ 用法: detail <noteId> [--account <name>]');
|
|
185
|
+
}
|
|
186
|
+
console.log(await implGetNoteDetail(id, resolveSessionCli(accountFromOpts(opts))));
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (cmd === 'post') {
|
|
190
|
+
const { opts, flags, rest } = parseOpts(tail);
|
|
191
|
+
if (rest.length > 0) {
|
|
192
|
+
die('❌ 用法: post --title <标题> (--content | --content-file) --image <路径> … [--account <name>]');
|
|
193
|
+
}
|
|
194
|
+
const session = resolveSessionCli(accountFromOpts(opts));
|
|
195
|
+
const title = opts.title?.trim();
|
|
196
|
+
if (!title) {
|
|
197
|
+
die('❌ post 需要 --title <标题>');
|
|
198
|
+
}
|
|
199
|
+
let content = opts.content ?? '';
|
|
200
|
+
if (opts['content-file']) {
|
|
201
|
+
const p = opts['content-file'];
|
|
202
|
+
if (!existsSync(p)) {
|
|
203
|
+
die(`❌ 找不到文件: ${p}`);
|
|
204
|
+
}
|
|
205
|
+
content = readFileSync(p, 'utf-8');
|
|
206
|
+
}
|
|
207
|
+
if (!content.trim()) {
|
|
208
|
+
die('❌ 请提供 --content 或 --content-file');
|
|
209
|
+
}
|
|
210
|
+
const imagePaths = collectImagePaths(tail);
|
|
211
|
+
if (imagePaths.length === 0) {
|
|
212
|
+
die('❌ 至少需要一张图片: 重复 --image <本地路径>(1~18 张)');
|
|
213
|
+
}
|
|
214
|
+
console.log(await implPost({
|
|
215
|
+
title,
|
|
216
|
+
content,
|
|
217
|
+
imagePaths,
|
|
218
|
+
publish: resolvePostPublish(opts, flags),
|
|
219
|
+
browserUserDataDir: session.browserUserDataDir,
|
|
220
|
+
}));
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
die(`❌ 未知命令 “${cmd}”。请使用 xhs help 查看用法。`);
|
|
224
|
+
}
|
|
225
|
+
function handleCliError(e) {
|
|
226
|
+
if (e instanceof CliError) {
|
|
227
|
+
console.error(e.message);
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
throw e;
|
|
231
|
+
}
|
|
232
|
+
export async function runCli(argv) {
|
|
233
|
+
if (argv.length === 0) {
|
|
234
|
+
console.error('❌ 请提供子命令,例如 xhs help、xhs login …\n');
|
|
235
|
+
printHelp();
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
if (argv[0] === 'help' || argv[0] === '--help' || argv[0] === '-h') {
|
|
239
|
+
printHelp();
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
await runOneCommand(argv);
|
|
244
|
+
}
|
|
245
|
+
catch (e) {
|
|
246
|
+
handleCliError(e);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
//# sourceMappingURL=cliRouter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cliRouter.js","sourceRoot":"","sources":["../../src/cli/cliRouter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// XHS-CLI 入口:子命令见 cliRouter;业务能力见 toolset(impl*)
|
|
3
|
+
import { runCli } from './cliRouter.js';
|
|
4
|
+
async function main() {
|
|
5
|
+
const args = process.argv.slice(2);
|
|
6
|
+
try {
|
|
7
|
+
await runCli(args);
|
|
8
|
+
}
|
|
9
|
+
catch (error) {
|
|
10
|
+
console.error('❌ 执行出错:', error);
|
|
11
|
+
if (error instanceof Error) {
|
|
12
|
+
console.error('错误信息:', error.message);
|
|
13
|
+
}
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
main();
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AACA,iDAAiD;AAEjD,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAExC,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;IACrB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAChC,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QACxC,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC"}
|