aiconductor-cli 1.2.1 → 1.2.2

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/CLAUDE.md ADDED
@@ -0,0 +1,76 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Commands
6
+
7
+ ### 本地运行
8
+
9
+ ```bash
10
+ node bin/aiconductor-cli.js --help # 查看所有命令
11
+ node bin/aiconductor-cli.js <command> --help # 查看子命令选项
12
+ node bin/aiconductor-cli.js search web "关键词" -n 3 -f text # 真实调用示例
13
+ ```
14
+
15
+ ### 发布到 npm
16
+
17
+ ```bash
18
+ npm version patch|minor|major # 升版本号
19
+ npm pkg fix # 修复 package.json 格式
20
+ npm publish # 发布
21
+ ```
22
+
23
+ 发布前须对本轮新增/修改的每个命令逐条执行真实调用,确认 exit 0 且输出格式正确。
24
+
25
+ ## Architecture
26
+
27
+ ### 分层结构
28
+
29
+ ```
30
+ bin/aiconductor-cli.js ← 入口层:commander 注册子命令树,不含业务逻辑
31
+ src/api.js ← API 层:HTTPS 请求封装 + API Key 解析
32
+ src/lib/cli-utils.js ← 共享工具:output/outputError/key/error 统一处理
33
+ src/commands/<topic>.js ← 命令层:参数组装、调用 API、格式化输出
34
+ ```
35
+
36
+ **`bin/aiconductor-cli.js`** — 纯聚合入口,action 回调只做参数传递。
37
+
38
+ **`src/api.js`** — 所有外部 API 调用的底层封装:
39
+ - `resolveApiKey(optionKey)` — 三级优先级:`-k` 参数 > `AICONDUCTOR_API_KEY` 环境变量 > `.env` 文件
40
+ - `httpPost()` — 通用 HTTPS POST(端口 443),统一处理 HTTP 错误和网络错误
41
+ - 每个 API 端点对应一个独立函数,通过 `stripBilling()` 过滤计费信息
42
+ - 新增 API 端点只改此文件
43
+
44
+ **`src/lib/cli-utils.js`** — 所有 command 文件共享的工具模块:
45
+ - `outputResult(data, format, textFormatter?)` — 统一成功输出(JSON: `{success: true, data}`)
46
+ - `outputError(err, format)` — 统一错误输出(JSON: `{success: false, error}` 到 stderr)
47
+ - `resolveKeyOrExit(optionKey, format)` — 解析 API Key,失败自动输出错误并 exit(1)
48
+ - `handleApiError(err, format)` — 统一处理 http_error / network_error / 未知错误
49
+ - `safeParseInt(value, defaultValue)` — 安全整数解析,防 NaN
50
+
51
+ **`src/commands/<topic>.js`** — 命令编排层,每个文件对应一个子命令组:
52
+ - 导入 `cli-utils` 的 4 个共享函数,消除重复代码
53
+ - 业务逻辑函数接收 argument + options,组装参数 → 调 API → `outputResult`
54
+ - 成功 `exit(0)`,失败 `exit(1)`
55
+
56
+ ### 输出格式约定
57
+
58
+ 所有命令支持 `-f json|text`:
59
+ - **JSON(默认)**:成功 stdout `{ success: true, data: {...} }`,失败 stderr `{ success: false, error: "..." }`
60
+ - **text**:人类可读格式,成功以 `✓` 开头
61
+
62
+ ### API Key 鉴权
63
+
64
+ 两种模式共存(由 API 端决定,不要自行统一):
65
+ - Body 模式:`api_key` 放入请求 body(多数端点)
66
+ - Bearer 模式:`Authorization: Bearer <api_key>` HTTP 头(web_search、douyin_video_search)
67
+
68
+ ### 扩展新 API 的标准流程
69
+
70
+ 1. `src/api.js`:添加 API 端点常量和调用函数(~10 行)
71
+ 2. `src/commands/<新主题>.js`:新建文件,导入 `cli-utils`,写命令函数(~10 行/命令)
72
+ 3. `bin/aiconductor-cli.js`:引入模块并注册子命令组
73
+
74
+ ### Skills 依赖
75
+
76
+ `.codebuddy/skills/` 下 3 个 skills(`aic-web-search`、`webpage-publish`、`douyin-toolbox`)依赖此 CLI。修改命令签名或输出格式会破坏它们。`.codebuddy/` 已被 `.npmignore` 排除。
package/README.md CHANGED
@@ -1,108 +1,266 @@
1
- # AIConductor CLI
2
-
3
- AIConductor 平台命令行工具箱,提供 HTML 发布和网页/图片搜索功能。
4
-
5
- ## 安装
6
-
7
- ```bash
8
- npm install -g aiconductor-cli
9
- ```
10
-
11
- **Node.js >= 16.0.0** 环境。
12
-
13
- ## 配置 API Key
14
-
15
- 前往 [https://dev.aiconductor.fun/register](https://dev.aiconductor.fun/register) 注册获取 API Key,然后配置环境变量:
16
-
17
- **macOS / Linux:**
18
- ```bash
19
- echo 'export AICONDUCTOR_API_KEY="your-api-key"' >> ~/.zshrc && source ~/.zshrc
20
- ```
21
-
22
- **Windows PowerShell:**
23
- ```powershell
24
- [Environment]::SetEnvironmentVariable("AICONDUCTOR_API_KEY", "your-api-key", "User")
25
- ```
26
-
27
- > 也可在项目目录创建 `.env` 文件写入 `AICONDUCTOR_API_KEY=your-api-key`。
28
-
29
- ## 命令
30
-
31
- ### 发布 HTML 网页
32
-
33
- ```bash
34
- aiconductor-cli html publish <HTML代码或文件路径> -t <英文文件名>
35
- ```
36
-
37
- | 选项 | 说明 |
38
- |------|------|
39
- | `-t, --title` | 英文文件名(必填,小写+连字符) |
40
- | `-k, --api-key` | API Key(可选,默认读取环境变量) |
41
- | `-f, --format` | 输出格式:`json`(默认)或 `text` |
42
-
43
- 示例:
44
- ```bash
45
- aiconductor-cli html publish index.html -t my-page
46
- aiconductor-cli html publish "<h1>Hello</h1>" -t hello-page -f text
47
- ```
48
-
49
- ### 网页搜索
50
-
51
- ```bash
52
- aiconductor-cli search web <关键词> [选项]
53
- ```
54
-
55
- | 选项 | 说明 | 默认值 |
56
- |------|------|--------|
57
- | `-n, --count` | 返回条数(最多50) | 5 |
58
- | `--time-range` | 时间范围:`OneDay`/`OneWeek`/`OneMonth`/`OneYear` | 无 |
59
- | `--no-summary` | 不返回内容摘要 | 默认返回 |
60
- | `--content-format` | 内容格式:`Text`/`Markdown` | Text |
61
- | `--auth-level` | 权威过滤:0=不过滤,1=优先权威来源 | 0 |
62
- | `--query-rewrite` | 开启 Query 改写 | 关闭 |
63
-
64
- 示例:
65
- ```bash
66
- aiconductor-cli search web "北京今日天气" -n 3 --auth-level 1
67
- ```
68
-
69
- ### 图片搜索
70
-
71
- ```bash
72
- aiconductor-cli search image <关键词> [选项]
73
- ```
74
-
75
- | 选项 | 说明 | 默认值 |
76
- |------|------|--------|
77
- | `-n, --count` | 返回条数(最多5) | 5 |
78
- | `--time-range` | 时间范围 | 无 |
79
-
80
- 示例:
81
- ```bash
82
- aiconductor-cli search image "长城风景" -n 3 -f text
83
- ```
84
-
85
- ### 通用选项
86
-
87
- 所有命令支持:
88
- - `-k, --api-key <key>` — API Key(优先级:参数 > 环境变量 > .env 文件)
89
- - `-f, --format <json|text>` — 输出格式,默认 JSON
90
-
91
- ## 计费
92
-
93
- - HTML 发布:0.01 元/次
94
- - 网页/图片搜索:0.03 元/次
95
- - 搜索失败不扣费,仅成功时计费
96
-
97
- ## 开发
98
-
99
- ```bash
100
- git clone https://github.com/huyi9531/aiconductor-cli.git
101
- cd aiconductor-cli
102
- npm install
103
- node bin/aiconductor-cli.js --help # 本地运行
104
- ```
105
-
106
- ## License
107
-
108
- MIT
1
+ # AIConductor CLI
2
+
3
+ AIConductor 平台命令行工具箱,提供网页发布、搜索和抖音数据功能。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm install -g aiconductor-cli
9
+ ```
10
+
11
+ **Node.js >= 16.0.0** 环境。
12
+
13
+ ## 配置 API Key
14
+
15
+ 前往 [https://dev.aiconductor.fun/register](https://dev.aiconductor.fun/register) 注册获取 API Key,然后配置环境变量:
16
+
17
+ **macOS / Linux:**
18
+ ```bash
19
+ echo 'export AICONDUCTOR_API_KEY="your-api-key"' >> ~/.zshrc && source ~/.zshrc
20
+ ```
21
+
22
+ **Windows PowerShell:**
23
+ ```powershell
24
+ [Environment]::SetEnvironmentVariable("AICONDUCTOR_API_KEY", "your-api-key", "User")
25
+ ```
26
+
27
+ > 也可在项目目录创建 `.env` 文件写入 `AICONDUCTOR_API_KEY=your-api-key`。
28
+
29
+ ## 命令
30
+
31
+ ### 发布 HTML 网页
32
+
33
+ ```bash
34
+ aiconductor-cli html publish <HTML代码或文件路径> [选项]
35
+ ```
36
+
37
+ | 选项 | 说明 |
38
+ |------|------|
39
+ | `-t, --title` | 文档标题,用作文件名基础 |
40
+ | `-k, --api-key` | API Key(可选,默认读取环境变量) |
41
+ | `-f, --format` | 输出格式:`json`(默认)或 `text` |
42
+
43
+ 示例:
44
+ ```bash
45
+ aiconductor-cli html publish index.html -t my-page
46
+ aiconductor-cli html publish "<h1>Hello</h1>" -t hello-page -f text
47
+ ```
48
+
49
+ ### 发布 Markdown/HTML 网页(推荐)
50
+
51
+ `webpage publish` 是统一的网页发布命令,自动识别 Markdown 和 HTML 并发布为在线网页。
52
+
53
+ ```bash
54
+ aiconductor-cli webpage publish <Markdown/HTML代码或文件路径> [选项]
55
+ ```
56
+
57
+ | 选项 | 说明 | 默认值 |
58
+ |------|------|--------|
59
+ | `-t, --title` | 文件名(不含 .html 后缀) | 自动生成 |
60
+ | `-k, --api-key` | API Key(可选) | 环境变量 |
61
+ | `--type` | 强制指定类型:`md` 或 `html` | 自动识别 |
62
+ | `--copy-button` | 为代码块添加复制按钮(仅 Markdown) | 关闭 |
63
+ | `-f, --format` | 输出格式:`json` 或 `text` | json |
64
+
65
+ 示例:
66
+ ```bash
67
+ # 发布 Markdown 文件
68
+ aiconductor-cli webpage publish README.md -t docs
69
+
70
+ # 发布 Markdown 并添加代码复制按钮
71
+ aiconductor-cli webpage publish article.md -t article --copy-button
72
+
73
+ # 发布 HTML 文件
74
+ aiconductor-cli webpage publish index.html -t my-page
75
+
76
+ # 直接传入内容字符串
77
+ aiconductor-cli webpage publish "# Hello\n\nThis is **markdown**" -t hello
78
+ ```
79
+
80
+ ### 网页搜索
81
+
82
+ ```bash
83
+ aiconductor-cli search web <关键词> [选项]
84
+ ```
85
+
86
+ | 选项 | 说明 | 默认值 |
87
+ |------|------|--------|
88
+ | `-n, --count` | 返回条数(最多50) | 5 |
89
+ | `--time-range` | 时间范围:预设 `OneDay`/`OneWeek`/`OneMonth`/`OneYear`,或自定义 `YYYY-MM-DD..YYYY-MM-DD` | 无 |
90
+ | `--no-summary` | 不返回内容摘要 | 默认返回 |
91
+ | `--content-format` | 内容格式:`Text` 或 `Markdown` | Text |
92
+ | `--auth-level` | 权威过滤:0=不过滤,1=优先权威来源 | 0 |
93
+ | `--query-rewrite` | 开启 Query 改写 | 关闭 |
94
+ | `-f, --format` | 输出格式:`json` 或 `text` | json |
95
+
96
+ 示例:
97
+ ```bash
98
+ aiconductor-cli search web "北京今日天气" -n 3 --auth-level 1
99
+ aiconductor-cli search web "AI 最新进展" --time-range OneWeek --content-format Markdown
100
+ aiconductor-cli search web "技术文章" --time-range 2025-01-01..2025-06-30 -n 10
101
+ ```
102
+
103
+ ### 图片搜索
104
+
105
+ ```bash
106
+ aiconductor-cli search image <关键词> [选项]
107
+ ```
108
+
109
+ | 选项 | 说明 | 默认值 |
110
+ |------|------|--------|
111
+ | `-n, --count` | 返回条数(最多5) | 5 |
112
+ | `--time-range` | 时间范围:预设或自定义日期范围 | 无 |
113
+ | `-f, --format` | 输出格式:`json` 或 `text` | json |
114
+
115
+ 示例:
116
+ ```bash
117
+ aiconductor-cli search image "长城风景" -n 3 -f text
118
+ ```
119
+
120
+ ### 抖音数据工具箱
121
+
122
+ ```bash
123
+ aiconductor-cli douyin <子命令> [参数] [选项]
124
+ ```
125
+
126
+ #### 获取作品详情
127
+
128
+ ```bash
129
+ aiconductor-cli douyin video-info <分享链接> [选项]
130
+ ```
131
+
132
+ 示例:
133
+ ```bash
134
+ aiconductor-cli douyin video-info "https://v.douyin.com/xxxxx/"
135
+ ```
136
+
137
+ #### 获取作品评论
138
+
139
+ ```bash
140
+ aiconductor-cli douyin video-comments <视频URL> [选项]
141
+ ```
142
+
143
+ | 选项 | 说明 | 默认值 |
144
+ |------|------|--------|
145
+ | `--max-count` | 获取评论数量 | 50 |
146
+ | `-f, --format` | 输出格式 | json |
147
+
148
+ 示例:
149
+ ```bash
150
+ aiconductor-cli douyin video-comments "https://www.douyin.com/video/xxxxx" --max-count 100
151
+ ```
152
+
153
+ #### 获取用户视频列表
154
+
155
+ ```bash
156
+ aiconductor-cli douyin user-videos <用户主页URL> [选项]
157
+ ```
158
+
159
+ | 选项 | 说明 | 默认值 |
160
+ |------|------|--------|
161
+ | `--max-pages` | 最大页数 | 1 |
162
+ | `--max-seconds` | 最长运行时间(秒),最大 180 | 50 |
163
+ | `-f, --format` | 输出格式 | json |
164
+
165
+ 示例:
166
+ ```bash
167
+ aiconductor-cli douyin user-videos "https://www.douyin.com/user/xxxxx" --max-pages 3
168
+ ```
169
+
170
+ #### 获取用户详情
171
+
172
+ ```bash
173
+ aiconductor-cli douyin user-detail <用户主页URL> [选项]
174
+ ```
175
+
176
+ 示例:
177
+ ```bash
178
+ aiconductor-cli douyin user-detail "https://www.douyin.com/user/xxxxx"
179
+ ```
180
+
181
+ #### 搜索用户
182
+
183
+ ```bash
184
+ aiconductor-cli douyin user-search <关键词> [选项]
185
+ ```
186
+
187
+ | 选项 | 说明 | 默认值 |
188
+ |------|------|--------|
189
+ | `--max-pages` | 最大页数,0=不限制 | 1 |
190
+ | `--max-seconds` | 最长运行时间(秒),最大 180 | 50 |
191
+ | `--fans-range` | 粉丝数筛选:`0_1k`/`1k_1w`/`1w_10w`/`10w_100w`/`100w_` | 无 |
192
+ | `--user-type` | 用户类型:`common_user`/`enterprise_user`/`personal_user` | 无 |
193
+ | `-f, --format` | 输出格式 | json |
194
+
195
+ 示例:
196
+ ```bash
197
+ aiconductor-cli douyin user-search "美食博主" --fans-range 10w_100w --user-type personal_user
198
+ ```
199
+
200
+ #### 搜索视频
201
+
202
+ ```bash
203
+ aiconductor-cli douyin video-search <关键词> [选项]
204
+ ```
205
+
206
+ | 选项 | 说明 | 默认值 |
207
+ |------|------|--------|
208
+ | `--max-pages` | 最大页数,0=不限制 | 1 |
209
+ | `--max-seconds` | 最长运行时间(秒),最大 50 | 50 |
210
+ | `--sort-type` | 排序:0=综合,1=最多点赞,2=最新发布 | 0 |
211
+ | `--publish-time` | 发布时间:0=不限,1=一天,7=一周,180=半年 | 0 |
212
+ | `--filter-duration` | 时长:0=不限,0-1=1分钟内,1-5=1-5分钟,5-10000=5分钟以上 | 0 |
213
+ | `--content-type` | 内容类型:0=不限,1=视频,2=图片,3=文章 | 0 |
214
+ | `-f, --format` | 输出格式 | json |
215
+
216
+ 示例:
217
+ ```bash
218
+ aiconductor-cli douyin video-search "编程教程" --sort-type 2 --publish-time 7 --content-type 1
219
+ ```
220
+
221
+ #### 视频链接转文字
222
+
223
+ ```bash
224
+ aiconductor-cli douyin link-to-text <视频URL> [选项]
225
+ ```
226
+
227
+ 示例:
228
+ ```bash
229
+ aiconductor-cli douyin link-to-text "https://www.douyin.com/video/xxxxx"
230
+ ```
231
+
232
+ ### 通用选项
233
+
234
+ 所有命令支持:
235
+ - `-k, --api-key <key>` — API Key(优先级:参数 > 环境变量 > .env 文件)
236
+ - `-f, --format <json|text>` — 输出格式,默认 JSON
237
+
238
+ ## 计费
239
+
240
+ | 功能 | 价格 |
241
+ |------|------|
242
+ | HTML 发布 | 0.01 元/次 |
243
+ | Markdown/HTML 网页发布 | 0.01 元/次 |
244
+ | 网页/图片搜索 | 0.03 元/次 |
245
+ | 抖音作品详情 | 0.01 元/次 |
246
+ | 抖音作品评论 | 0.01 元/次 |
247
+ | 抖音用户视频列表 | 0.03 元/页 |
248
+ | 抖音用户详情 | 0.03 元/次 |
249
+ | 抖音用户搜索 | 0.15 元/页 |
250
+ | 抖音视频搜索 | 0.15 元/页 |
251
+ | 抖音链接转文字 | 0.01 元/次 + 0.0003 元/秒 |
252
+
253
+ > 搜索/调用失败不扣费,仅成功时计费。
254
+
255
+ ## 开发
256
+
257
+ ```bash
258
+ git clone https://github.com/huyi9531/aiconductor-cli.git
259
+ cd aiconductor-cli
260
+ npm install
261
+ node bin/aiconductor-cli.js --help # 本地运行
262
+ ```
263
+
264
+ ## License
265
+
266
+ MIT
package/package.json CHANGED
@@ -1,17 +1,18 @@
1
1
  {
2
2
  "name": "aiconductor-cli",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
4
4
  "description": "AIConductor CLI 工具箱",
5
5
  "keywords": [
6
6
  "aiconductor",
7
7
  "html",
8
8
  "publish",
9
9
  "deploy",
10
- "cli"
10
+ "cli",
11
+ "douyin",
12
+ "search"
11
13
  ],
12
14
  "author": "",
13
15
  "license": "MIT",
14
- "main": "src/index.js",
15
16
  "bin": {
16
17
  "aiconductor-cli": "bin/aiconductor-cli.js"
17
18
  },
package/src/api.js CHANGED
@@ -1,4 +1,4 @@
1
- const http = require("http");
1
+ const https = require("https");
2
2
  const fs = require("fs");
3
3
  const path = require("path");
4
4
 
@@ -40,6 +40,31 @@ function resolveApiKey(optionKey) {
40
40
  return null;
41
41
  }
42
42
 
43
+ /**
44
+ * 过滤响应中的计费信息
45
+ * @param {object} resp
46
+ * @returns {object}
47
+ */
48
+ function stripBilling(resp) {
49
+ if (!resp || typeof resp !== "object") return resp;
50
+ const result = { ...resp };
51
+ delete result.billing;
52
+ return result;
53
+ }
54
+
55
+ /**
56
+ * 校验整数参数,非有效正整数时报错
57
+ * @param {*} value
58
+ * @param {string} name
59
+ */
60
+ function validateInt(value, name) {
61
+ if (value === undefined || value === null) return;
62
+ const n = Number(value);
63
+ if (!Number.isInteger(n) || n < 0) {
64
+ throw Object.assign(new Error(`${name} 须为非负整数,收到: ${value}`), { type: "validation_error" });
65
+ }
66
+ }
67
+
43
68
  /**
44
69
  * HTTP POST 请求封装
45
70
  * @param {string} host
@@ -53,7 +78,7 @@ function httpPost(host, path, body, headers = {}) {
53
78
 
54
79
  const options = {
55
80
  hostname: host,
56
- port: 80,
81
+ port: 443,
57
82
  path: path,
58
83
  method: "POST",
59
84
  headers: {
@@ -63,7 +88,7 @@ function httpPost(host, path, body, headers = {}) {
63
88
  },
64
89
  };
65
90
 
66
- const req = http.request(options, (res) => {
91
+ const req = https.request(options, (res) => {
67
92
  let chunks = [];
68
93
  res.on("data", (chunk) => chunks.push(chunk));
69
94
  res.on("end", () => {
@@ -112,10 +137,7 @@ async function publishHtml(apiKey, htmlCode, title) {
112
137
  if (title) reqBody.title = title;
113
138
 
114
139
  const resp = await httpPost(API_HOST, API_PATH, reqBody);
115
-
116
- // 过滤计费信息
117
- const { billing, ...safe } = resp;
118
- return safe;
140
+ return stripBilling(resp);
119
141
  }
120
142
 
121
143
  /**
@@ -125,6 +147,10 @@ async function publishHtml(apiKey, htmlCode, title) {
125
147
  * @returns {Promise<object>}
126
148
  */
127
149
  async function webSearch(apiKey, params) {
150
+ // 输入校验
151
+ validateInt(params.count, "count");
152
+ validateInt(params.authLevel, "authLevel");
153
+
128
154
  const reqBody = {
129
155
  query: params.query,
130
156
  search_type: params.searchType,
@@ -140,9 +166,7 @@ async function webSearch(apiKey, params) {
140
166
  Authorization: `Bearer ${apiKey}`,
141
167
  });
142
168
 
143
- // 过滤计费信息
144
- const { billing, ...safe } = resp;
145
- return safe;
169
+ return stripBilling(resp);
146
170
  }
147
171
 
148
172
  /**
@@ -163,8 +187,7 @@ async function publishMdToHtml(apiKey, mdText, filename, addCopyButton = false)
163
187
 
164
188
  const resp = await httpPost(API_HOST, MD_TO_HTML_API_PATH, reqBody);
165
189
 
166
- const { billing, ...safe } = resp;
167
- return safe;
190
+ return stripBilling(resp);
168
191
  }
169
192
 
170
193
  /**
@@ -178,8 +201,7 @@ async function douyinVideoInfo(apiKey, shareUrl) {
178
201
  api_key: apiKey,
179
202
  share_url: shareUrl,
180
203
  });
181
- const { billing, ...safe } = resp;
182
- return safe;
204
+ return stripBilling(resp);
183
205
  }
184
206
 
185
207
  /**
@@ -193,8 +215,7 @@ async function douyinVideoComments(apiKey, videoUrl, maxCount) {
193
215
  const body = { api_key: apiKey, video_url: videoUrl };
194
216
  if (maxCount !== undefined && maxCount !== null) body.max_count = maxCount;
195
217
  const resp = await httpPost(API_HOST, DOUYIN_VIDEO_COMMENTS_PATH, body);
196
- const { billing, ...safe } = resp;
197
- return safe;
218
+ return stripBilling(resp);
198
219
  }
199
220
 
200
221
  /**
@@ -210,8 +231,7 @@ async function douyinUserVideos(apiKey, homePage, maxSeconds, maxPages) {
210
231
  if (maxSeconds !== undefined && maxSeconds !== null) body.max_seconds = maxSeconds;
211
232
  if (maxPages !== undefined && maxPages !== null) body.max_pages = maxPages;
212
233
  const resp = await httpPost(API_HOST, DOUYIN_USER_VIDEOS_PATH, body);
213
- const { billing, ...safe } = resp;
214
- return safe;
234
+ return stripBilling(resp);
215
235
  }
216
236
 
217
237
  /**
@@ -225,8 +245,7 @@ async function douyinUserDetail(apiKey, homePage) {
225
245
  api_key: apiKey,
226
246
  home_page: homePage,
227
247
  });
228
- const { billing, ...safe } = resp;
229
- return safe;
248
+ return stripBilling(resp);
230
249
  }
231
250
 
232
251
  /**
@@ -243,8 +262,7 @@ async function douyinUserSearch(apiKey, keyword, opts = {}) {
243
262
  if (opts.fansRange) body.douyin_user_fans = opts.fansRange;
244
263
  if (opts.userType) body.douyin_user_type = opts.userType;
245
264
  const resp = await httpPost(API_HOST, DOUYIN_USER_SEARCH_PATH, body);
246
- const { billing, ...safe } = resp;
247
- return safe;
265
+ return stripBilling(resp);
248
266
  }
249
267
 
250
268
  /**
@@ -265,8 +283,7 @@ async function douyinVideoSearch(apiKey, keyword, opts = {}) {
265
283
  const resp = await httpPost(API_HOST, DOUYIN_VIDEO_SEARCH_PATH, body, {
266
284
  Authorization: `Bearer ${apiKey}`,
267
285
  });
268
- const { billing, ...safe } = resp;
269
- return safe;
286
+ return stripBilling(resp);
270
287
  }
271
288
 
272
289
  /**
@@ -280,8 +297,7 @@ async function douyinLinkToText(apiKey, videoUrl) {
280
297
  api_key: apiKey,
281
298
  video_url: videoUrl,
282
299
  });
283
- const { billing, ...safe } = resp;
284
- return safe;
300
+ return stripBilling(resp);
285
301
  }
286
302
 
287
303
  module.exports = {