aiconductor-cli 1.2.0 → 1.2.1
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
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# AGENTS.md This file provides guidance to Agent when working with code in this repository.
|
|
2
|
+
|
|
3
|
+
## Commands
|
|
4
|
+
|
|
5
|
+
### 本地运行(开发调试)
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
node bin/aiconductor-cli.js --help # 查看所有命令
|
|
9
|
+
node bin/aiconductor-cli.js html publish --help # 查看某命令选项
|
|
10
|
+
node bin/aiconductor-cli.js search web "关键词" -n 3 -f text # 运行网页搜索(text 输出)
|
|
11
|
+
node bin/aiconductor-cli.js search image "关键词" -n 2 # 运行图片搜索(JSON 输出)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### 发布到 npm
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm version patch|minor|major # 升版本号(新增功能升 minor,修复升 patch)
|
|
18
|
+
npm pkg fix # 修复 package.json 格式问题
|
|
19
|
+
npm publish # 发布(如开启 2FA 需加 --otp=<验证码>)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### 验证流程
|
|
23
|
+
|
|
24
|
+
发布前必须对本轮新增/修改的每个命令执行真实调用,确认 exit code 0 且输出格式正确。用 `node bin/aiconductor-cli.js <command> --help` 检查选项完整性。
|
|
25
|
+
|
|
26
|
+
## Architecture
|
|
27
|
+
|
|
28
|
+
### 分层设计
|
|
29
|
+
|
|
30
|
+
项目采用三层分离架构,每层职责明确:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
bin/aiconductor-cli.js ← 入口层:commander 注册子命令树,不含业务逻辑
|
|
34
|
+
src/api.js ← API 层:HTTP 请求封装 + API Key 解析
|
|
35
|
+
src/commands/<topic>.js ← 命令层:参数组装、调用 API、格式化输出
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**`bin/aiconductor-cli.js`** 是纯聚合入口,用 commander 的 `.command().addCommand()` 结构将各 topic 模块注册为子命令树。action 回调只做参数传递,不含业务逻辑。新增命令分类只需在此文件引入并注册。
|
|
39
|
+
|
|
40
|
+
**`src/api.js`** 是所有外部 API 调用的底层封装:
|
|
41
|
+
- `resolveApiKey(optionKey)` — 三级优先级查找 API Key:`-k` 参数 > `AICONDUCTOR_API_KEY` 环境变量 > 当前目录 `.env` 文件
|
|
42
|
+
- `httpPost(host, path, body, headers)` — 通用 HTTP POST 封装,支持自定义 headers,统一处理 HTTP 错误(`type: "http_error"`)和网络错误(`type: "network_error"`)
|
|
43
|
+
- 每个外部 API 对应一个独立函数(如 `publishHtml`、`webSearch`),函数内过滤 `billing` 字段后返回
|
|
44
|
+
- 新增外部 API 渠道只改此文件
|
|
45
|
+
|
|
46
|
+
**`src/commands/<topic>.js`** 是命令编排层,每个文件对应一个命令分组:
|
|
47
|
+
- 接收业务参数 + options 对象
|
|
48
|
+
- 调用 `resolveApiKey` 解析 Key(失败则 exit 1)
|
|
49
|
+
- 组装参数调用 `api.js` 中的函数
|
|
50
|
+
- 通过 `outputResult` / `outputError` 统一处理输出
|
|
51
|
+
- 成功 `process.exit(0)`,失败 `process.exit(1)`
|
|
52
|
+
|
|
53
|
+
### 命令文件命名规则
|
|
54
|
+
|
|
55
|
+
`src/commands/` 下的文件名与 API 端点名一一对应:
|
|
56
|
+
- `/api/html_publish` → `html_publish.js`
|
|
57
|
+
- `/api/aic_web_search` → `web_search.js`
|
|
58
|
+
|
|
59
|
+
新增命令时遵循同样规则:文件名 = API 端点名(snake_case)。
|
|
60
|
+
|
|
61
|
+
### 输出格式约定
|
|
62
|
+
|
|
63
|
+
所有命令支持 `-f json|text` 两种输出格式:
|
|
64
|
+
- **JSON(默认)**:成功输出 `{ success: true, data: {...} }`,失败输出 `{ success: false, error: "..." }`,便于 AI Agent 解析
|
|
65
|
+
- **text**:人类可读格式,成功以 `✓` 开头,失败以 `✗` 开头
|
|
66
|
+
|
|
67
|
+
### API Key 鉴权
|
|
68
|
+
|
|
69
|
+
不同 API 使用不同鉴权方式:
|
|
70
|
+
- `html_publish`:api_key 放入请求 body
|
|
71
|
+
- `aic_web_search`:使用 `Authorization: Bearer <api_key>` 请求头
|
|
72
|
+
|
|
73
|
+
### 计费信息过滤
|
|
74
|
+
|
|
75
|
+
所有 API 函数在返回前通过解构 `const { billing, ...safe } = resp` 过滤掉计费字段,不暴露给终端用户。
|
|
76
|
+
|
|
77
|
+
### Skills
|
|
78
|
+
|
|
79
|
+
`.codebuddy/skills/` 下存放 CodeBuddy Skill 文档(SKILL.md),描述 CLI 命令的使用方法供 AI Agent 调用。每个 Skill 对应一个 CLI 命令分组,目录名使用 kebab-case。此目录已被 `.npmignore` 排除,不会发布到 npm。
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
# 获取作品详情
|
|
2
|
+
|
|
3
|
+
## 接口信息
|
|
4
|
+
- **URL**: `http://plugin.aiconductor.fun/api/douyin_video_info`
|
|
5
|
+
- **方法**: `POST` / `GET`
|
|
6
|
+
- **Content-Type**: `application/json`
|
|
7
|
+
|
|
8
|
+
## 功能说明
|
|
9
|
+
根据抖音视频分享链接获取视频详细信息,包括作者信息、统计数据、音乐信息等,按次计费(0.01元/次)。
|
|
10
|
+
|
|
11
|
+
## 请求参数
|
|
12
|
+
|
|
13
|
+
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
|
14
|
+
|--------|------|------|--------|------|
|
|
15
|
+
| share_url | string | 是 | - | 抖音视频分享链接 |
|
|
16
|
+
| api_key | string | 是 | - | 计费系统API密钥 |
|
|
17
|
+
|
|
18
|
+
## 响应参数
|
|
19
|
+
|
|
20
|
+
| 参数名 | 类型 | 说明 |
|
|
21
|
+
|--------|------|------|
|
|
22
|
+
| success | boolean | 是否成功 |
|
|
23
|
+
| message | string | 响应消息 |
|
|
24
|
+
| data | object | 视频详情数据 |
|
|
25
|
+
| billing | object | 计费信息 |
|
|
26
|
+
| billing.service_fee | float | 服务费 |
|
|
27
|
+
| billing.total_fee | float | 总费用 |
|
|
28
|
+
| billing.discount_applied | boolean | 是否应用折扣 |
|
|
29
|
+
| billing.balance_before | float | 扣费前余额 |
|
|
30
|
+
| billing.balance_after | float | 扣费后余额 |
|
|
31
|
+
| billing.currency | string | 货币类型(CNY) |
|
|
32
|
+
| billing.transaction_id | string | 交易ID |
|
|
33
|
+
|
|
34
|
+
## 注意事项
|
|
35
|
+
1. 支持抖音分享链接和完整视频URL
|
|
36
|
+
2. 自动解析短链接
|
|
37
|
+
3. 返回视频详细信息包括作者、统计、音乐等
|
|
38
|
+
|
|
39
|
+
# 获取作品评论
|
|
40
|
+
|
|
41
|
+
## 接口信息
|
|
42
|
+
- **URL**: `http://plugin.aiconductor.fun/api/douyin_video_comments`
|
|
43
|
+
- **方法**: `POST` / `GET`
|
|
44
|
+
- **Content-Type**: `application/json`
|
|
45
|
+
|
|
46
|
+
## 功能说明
|
|
47
|
+
根据抖音视频URL获取该视频的评论列表,按次计费(0.01元/次)。
|
|
48
|
+
|
|
49
|
+
## 请求参数
|
|
50
|
+
|
|
51
|
+
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
|
52
|
+
|--------|------|------|--------|------|
|
|
53
|
+
| video_url | string | 是 | - | 抖音视频URL |
|
|
54
|
+
| max_count | integer | 否 | 50 | 获取评论数量 |
|
|
55
|
+
| api_key | string | 是 | - | 计费系统API密钥 |
|
|
56
|
+
|
|
57
|
+
## 响应参数
|
|
58
|
+
|
|
59
|
+
| 参数名 | 类型 | 说明 |
|
|
60
|
+
|--------|------|------|
|
|
61
|
+
| success | boolean | 是否成功 |
|
|
62
|
+
| message | string | 响应消息 |
|
|
63
|
+
| data | object | 评论数据 |
|
|
64
|
+
| billing | object | 计费信息 |
|
|
65
|
+
| billing.service_fee | float | 服务费 |
|
|
66
|
+
| billing.total_fee | float | 总费用 |
|
|
67
|
+
| billing.discount_applied | boolean | 是否应用折扣 |
|
|
68
|
+
| billing.balance_before | float | 扣费前余额 |
|
|
69
|
+
| billing.balance_after | float | 扣费后余额 |
|
|
70
|
+
| billing.currency | string | 货币类型(CNY) |
|
|
71
|
+
| billing.transaction_id | string | 交易ID |
|
|
72
|
+
|
|
73
|
+
## 注意事项
|
|
74
|
+
1. 支持抖音分享链接和完整视频URL
|
|
75
|
+
2. max_count默认获取50条评论
|
|
76
|
+
3. 集成计费系统,按次收费
|
|
77
|
+
|
|
78
|
+
# 获取用户视频_同步
|
|
79
|
+
|
|
80
|
+
## 接口信息
|
|
81
|
+
- **URL**: `http://plugin.aiconductor.fun/api/douyin_user_videos`
|
|
82
|
+
- **方法**: `POST` / `GET`
|
|
83
|
+
- **Content-Type**: `application/json`
|
|
84
|
+
|
|
85
|
+
## 功能说明
|
|
86
|
+
根据抖音用户主页URL获取该用户发布的视频列表,支持分页获取和时间限制,按页计费(0.03元/页)。
|
|
87
|
+
|
|
88
|
+
## 请求参数
|
|
89
|
+
|
|
90
|
+
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
|
91
|
+
|--------|------|------|--------|------|
|
|
92
|
+
| home_page | string | 是 | - | 抖音用户主页URL |
|
|
93
|
+
| api_key | string | 是 | - | 计费系统API密钥 |
|
|
94
|
+
| max_seconds | integer | 否 | 50 | 最长运行时间(秒),最大180 |
|
|
95
|
+
| max_pages | integer | 否 | 1 | 最大页数 |
|
|
96
|
+
|
|
97
|
+
## 响应参数
|
|
98
|
+
|
|
99
|
+
| 参数名 | 类型 | 说明 |
|
|
100
|
+
|--------|------|------|
|
|
101
|
+
| success | boolean | 是否成功 |
|
|
102
|
+
| message | string | 响应消息 |
|
|
103
|
+
| data | object | 视频数据 |
|
|
104
|
+
| data.aweme_list | array | 视频列表 |
|
|
105
|
+
| data.aweme_list[].author | object | 作者信息(nickname、signature) |
|
|
106
|
+
| data.aweme_list[].create_time | integer | 创建时间戳 |
|
|
107
|
+
| data.aweme_list[].create_time_formatted | string | 格式化创建时间 |
|
|
108
|
+
| data.aweme_list[].home_page | string | 用户主页URL |
|
|
109
|
+
| data.aweme_list[].desc | string | 视频描述 |
|
|
110
|
+
| data.aweme_list[].share_url | string | 分享链接 |
|
|
111
|
+
| data.aweme_list[].duration | string | 视频时长(MM:SS) |
|
|
112
|
+
| data.aweme_list[].statistics | object | 统计数据(点赞、收藏、评论、分享) |
|
|
113
|
+
| billing | object | 计费信息 |
|
|
114
|
+
| billing.service_fee | float | 每页服务费 |
|
|
115
|
+
| billing.total_fee | float | 总费用 |
|
|
116
|
+
| billing.discount_applied | boolean | 是否应用折扣 |
|
|
117
|
+
| billing.pages_charged | integer | 计费页数 |
|
|
118
|
+
| billing.balance_before | float | 扣费前余额 |
|
|
119
|
+
| billing.balance_after | float | 扣费后余额 |
|
|
120
|
+
| billing.currency | string | 货币类型(CNY) |
|
|
121
|
+
| billing.transaction_ids | array | 交易ID列表 |
|
|
122
|
+
|
|
123
|
+
## 注意事项
|
|
124
|
+
1. 支持抖音短链接和长链接,自动解析
|
|
125
|
+
2. max_seconds最大值为180秒
|
|
126
|
+
3. 按页收费,每成功获取一页数据扣费一次
|
|
127
|
+
4. 达到时间限制或页数限制时会自动停止翻页
|
|
128
|
+
|
|
129
|
+
# 获取用户详情
|
|
130
|
+
|
|
131
|
+
## 接口信息
|
|
132
|
+
- **URL**: `http://plugin.aiconductor.fun/api/douyin_user_detail`
|
|
133
|
+
- **方法**: `POST` / `GET`
|
|
134
|
+
- **Content-Type**: `application/json`
|
|
135
|
+
|
|
136
|
+
## 功能说明
|
|
137
|
+
根据抖音用户主页URL获取用户详细信息,包括基本信息、统计数据、直播状态等。自动解析短链接并提取sec_user_id,按次计费(0.03元/秒)。
|
|
138
|
+
|
|
139
|
+
## 请求参数
|
|
140
|
+
|
|
141
|
+
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
|
142
|
+
|--------|------|------|--------|------|
|
|
143
|
+
| home_page | string | 是 | - | 抖音用户主页URL |
|
|
144
|
+
| api_key | string | 是 | - | 计费系统API密钥 |
|
|
145
|
+
|
|
146
|
+
## 响应参数
|
|
147
|
+
|
|
148
|
+
| 参数名 | 类型 | 说明 |
|
|
149
|
+
|--------|------|------|
|
|
150
|
+
| success | boolean | 是否成功 |
|
|
151
|
+
| message | string | 响应消息 |
|
|
152
|
+
| data | object | 用户详情数据 |
|
|
153
|
+
| data.identity | object | 身份信息(uid、sec_user_id、unique_id、nickname) |
|
|
154
|
+
| data.profile | object | 资料信息(签名、头像、性别、年龄等) |
|
|
155
|
+
| data.stats | object | 统计数据(关注数、粉丝数、作品数等) |
|
|
156
|
+
| data.live | object | 直播信息(直播状态、房间ID) |
|
|
157
|
+
| billing | object | 计费信息 |
|
|
158
|
+
| billing.service_fee | float | 服务费 |
|
|
159
|
+
| billing.total_fee | float | 总费用 |
|
|
160
|
+
| billing.discount_applied | boolean | 是否应用折扣 |
|
|
161
|
+
| billing.balance_before | float | 扣费前余额 |
|
|
162
|
+
| billing.balance_after | float | 扣费后余额 |
|
|
163
|
+
| billing.currency | string | 货币类型(CNY) |
|
|
164
|
+
| billing.transaction_id | string | 交易ID |
|
|
165
|
+
|
|
166
|
+
## 注意事项
|
|
167
|
+
1. 支持抖音短链接和长链接,自动解析
|
|
168
|
+
2. 返回分类后的用户信息(身份、资料、统计、直播)
|
|
169
|
+
3. 集成计费系统,按次收费
|
|
170
|
+
|
|
171
|
+
# 抖音用户搜索
|
|
172
|
+
|
|
173
|
+
## 接口信息
|
|
174
|
+
- **URL**: `http://plugin.aiconductor.fun/api/douyin_user_search`
|
|
175
|
+
- **方法**: `POST` / `GET`
|
|
176
|
+
- **Content-Type**: `application/json`
|
|
177
|
+
|
|
178
|
+
## 功能说明
|
|
179
|
+
根据关键词搜索抖音用户,支持通过粉丝数量和用户类型进行筛选查询,支持自动翻页,按页计费(0.15元/页)。
|
|
180
|
+
|
|
181
|
+
## 请求参数
|
|
182
|
+
|
|
183
|
+
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
|
184
|
+
|--------|------|------|--------|------|
|
|
185
|
+
| keyword | string | 是 | - | 搜索关键词 |
|
|
186
|
+
| api_key | string | 是 | - | 计费系统API密钥 |
|
|
187
|
+
| douyin_user_fans | string | 否 | "" | 粉丝数量筛选(见下方说明) |
|
|
188
|
+
| douyin_user_type | string | 否 | "" | 用户类型筛选(见下方说明) |
|
|
189
|
+
| max_pages | integer | 否 | 1 | 最大页数,0表示不限制 |
|
|
190
|
+
| max_seconds | integer | 否 | 50 | 最长运行时间(秒),最大180 |
|
|
191
|
+
|
|
192
|
+
### 粉丝数量筛选 (douyin_user_fans)
|
|
193
|
+
|
|
194
|
+
| 值 | 说明 |
|
|
195
|
+
|----|------|
|
|
196
|
+
| "" | 不限 |
|
|
197
|
+
| "0_1k" | 1000以下 |
|
|
198
|
+
| "1k_1w" | 1000到1万 |
|
|
199
|
+
| "1w_10w" | 1万到10万 |
|
|
200
|
+
| "10w_100w" | 10万到100万 |
|
|
201
|
+
| "100w_" | 100万以上 |
|
|
202
|
+
|
|
203
|
+
### 用户类型筛选 (douyin_user_type)
|
|
204
|
+
|
|
205
|
+
| 值 | 说明 |
|
|
206
|
+
|----|------|
|
|
207
|
+
| "" | 不限 |
|
|
208
|
+
| "common_user" | 普通用户 |
|
|
209
|
+
| "enterprise_user" | 企业认证用户 |
|
|
210
|
+
| "personal_user" | 个人认证用户 |
|
|
211
|
+
|
|
212
|
+
## 响应参数
|
|
213
|
+
|
|
214
|
+
| 参数名 | 类型 | 说明 |
|
|
215
|
+
|--------|------|------|
|
|
216
|
+
| success | boolean | 是否成功 |
|
|
217
|
+
| message | string | 响应消息 |
|
|
218
|
+
| data | object | 搜索数据 |
|
|
219
|
+
| data.user_list | array | 用户列表 |
|
|
220
|
+
| data.user_list[].uid | string | 用户ID |
|
|
221
|
+
| data.user_list[].nickname | string | 用户昵称 |
|
|
222
|
+
| data.user_list[].gender | integer | 性别(0=未知,1=男,2=女) |
|
|
223
|
+
| data.user_list[].unique_id | string | 抖音号 |
|
|
224
|
+
| data.user_list[].sec_uid | string | 安全UID |
|
|
225
|
+
| data.user_list[].signature | string | 个性签名 |
|
|
226
|
+
| data.user_list[].follower_count | integer | 粉丝数量 |
|
|
227
|
+
| data.user_list[].avatar_url | string | 头像URL |
|
|
228
|
+
| data.user_list[].follow_status | integer | 是否已关注 |
|
|
229
|
+
| data.user_list[].live_status | integer | 是否正在直播 |
|
|
230
|
+
| data.user_list[].versatile_display | string | 抖音号展示文案 |
|
|
231
|
+
| data.user_list[].account_cert_info | object | 账号认证信息 |
|
|
232
|
+
| data.cursor | integer | 下一页游标 |
|
|
233
|
+
| data.has_more | integer | 是否还有更多数据(1=有,0=无) |
|
|
234
|
+
| billing | object | 计费信息 |
|
|
235
|
+
| billing.service_fee | float | 每页服务费 |
|
|
236
|
+
| billing.total_fee | float | 总费用 |
|
|
237
|
+
| billing.discount_applied | boolean | 是否应用折扣 |
|
|
238
|
+
| billing.pages_charged | integer | 计费页数 |
|
|
239
|
+
| billing.balance_before | float | 扣费前余额 |
|
|
240
|
+
| billing.balance_after | float | 扣费后余额 |
|
|
241
|
+
| billing.currency | string | 货币类型(CNY) |
|
|
242
|
+
| billing.transaction_ids | array | 交易ID列表 |
|
|
243
|
+
|
|
244
|
+
## 注意事项
|
|
245
|
+
1. 自动翻页,无需传入cursor和search_id
|
|
246
|
+
2. max_seconds最大值为50秒
|
|
247
|
+
3. 按页收费,每成功获取一页数据扣费一次
|
|
248
|
+
4. 达到时间限制或页数限制时会自动停止翻页
|
|
249
|
+
|
|
250
|
+
# 抖音视频搜索
|
|
251
|
+
|
|
252
|
+
## 接口信息
|
|
253
|
+
- **URL**: `http://plugin.aiconductor.fun/api/douyin_video_search`
|
|
254
|
+
- **方法**: `POST` / `GET`
|
|
255
|
+
- **Content-Type**: `application/json`
|
|
256
|
+
- **鉴权**: 请求头 `Authorization: Bearer <api_key>`(兼容 body/query 中 `api_key` 参数)
|
|
257
|
+
|
|
258
|
+
## 功能说明
|
|
259
|
+
根据关键词搜索抖音视频,支持排序方式、发布时间、视频时长、内容类型等筛选条件,支持自动翻页,按页计费(0.15元/页)。
|
|
260
|
+
|
|
261
|
+
## 请求参数
|
|
262
|
+
|
|
263
|
+
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
|
264
|
+
|--------|------|------|--------|------|
|
|
265
|
+
| keyword | string | 是 | - | 搜索关键词 |
|
|
266
|
+
| sort_type | string | 否 | "0" | 排序方式(见下方说明) |
|
|
267
|
+
| publish_time | string | 否 | "0" | 发布时间筛选(见下方说明) |
|
|
268
|
+
| filter_duration | string | 否 | "0" | 视频时长筛选(见下方说明) |
|
|
269
|
+
| content_type | string | 否 | "0" | 内容类型筛选(见下方说明) |
|
|
270
|
+
| max_pages | integer | 否 | 1 | 最大页数,0表示不限制 |
|
|
271
|
+
| max_seconds | integer | 否 | 50 | 最长运行时间(秒),最大50 |
|
|
272
|
+
|
|
273
|
+
### 排序方式 (sort_type)
|
|
274
|
+
|
|
275
|
+
| 值 | 说明 |
|
|
276
|
+
|----|------|
|
|
277
|
+
| "0" | 综合排序 |
|
|
278
|
+
| "1" | 最多点赞 |
|
|
279
|
+
| "2" | 最新发布 |
|
|
280
|
+
|
|
281
|
+
### 发布时间筛选 (publish_time)
|
|
282
|
+
|
|
283
|
+
| 值 | 说明 |
|
|
284
|
+
|----|------|
|
|
285
|
+
| "0" | 不限 |
|
|
286
|
+
| "1" | 最近一天 |
|
|
287
|
+
| "7" | 最近一周 |
|
|
288
|
+
| "180" | 最近半年 |
|
|
289
|
+
|
|
290
|
+
### 视频时长筛选 (filter_duration)
|
|
291
|
+
|
|
292
|
+
| 值 | 说明 |
|
|
293
|
+
|----|------|
|
|
294
|
+
| "0" | 不限 |
|
|
295
|
+
| "0-1" | 1分钟以内 |
|
|
296
|
+
| "1-5" | 1-5分钟 |
|
|
297
|
+
| "5-10000" | 5分钟以上 |
|
|
298
|
+
|
|
299
|
+
### 内容类型筛选 (content_type)
|
|
300
|
+
|
|
301
|
+
| 值 | 说明 |
|
|
302
|
+
|----|------|
|
|
303
|
+
| "0" | 不限 |
|
|
304
|
+
| "1" | 视频 |
|
|
305
|
+
| "2" | 图片 |
|
|
306
|
+
| "3" | 文章 |
|
|
307
|
+
|
|
308
|
+
## 响应参数
|
|
309
|
+
|
|
310
|
+
| 参数名 | 类型 | 说明 |
|
|
311
|
+
|--------|------|------|
|
|
312
|
+
| success | boolean | 是否成功 |
|
|
313
|
+
| message | string | 响应消息 |
|
|
314
|
+
| data | object | 搜索数据 |
|
|
315
|
+
| data.video_list | array | 视频列表 |
|
|
316
|
+
| data.video_list[].aweme_id | string | 视频ID |
|
|
317
|
+
| data.video_list[].desc | string | 视频描述 |
|
|
318
|
+
| data.video_list[].create_time | integer | 创建时间戳 |
|
|
319
|
+
| data.video_list[].share_url | string | 分享链接 |
|
|
320
|
+
| data.video_list[].author | object | 作者信息 |
|
|
321
|
+
| data.video_list[].author.uid | string | 用户ID |
|
|
322
|
+
| data.video_list[].author.short_id | string | 短ID |
|
|
323
|
+
| data.video_list[].author.nickname | string | 用户昵称 |
|
|
324
|
+
| data.video_list[].author.sec_uid | string | 安全UID |
|
|
325
|
+
| data.video_list[].author.unique_id | string | 抖音号 |
|
|
326
|
+
| data.video_list[].author.signature | string | 个性签名 |
|
|
327
|
+
| data.video_list[].author.follower_count | integer | 粉丝数量 |
|
|
328
|
+
| data.video_list[].author.total_favorited | integer | 获赞总数 |
|
|
329
|
+
| data.video_list[].author.is_verified | boolean | 是否认证 |
|
|
330
|
+
| data.video_list[].author.verification_type | integer | 认证类型 |
|
|
331
|
+
| data.video_list[].author.enterprise_verify_reason | string | 企业认证原因 |
|
|
332
|
+
| data.video_list[].author.custom_verify | string | 自定义认证 |
|
|
333
|
+
| data.video_list[].author.region | string | 地区 |
|
|
334
|
+
| data.video_list[].author.avatar_url | string | 头像URL |
|
|
335
|
+
| data.video_list[].music | object | 音乐信息 |
|
|
336
|
+
| data.video_list[].music.id | integer | 音乐ID |
|
|
337
|
+
| data.video_list[].music.title | string | 音乐标题 |
|
|
338
|
+
| data.video_list[].music.author | string | 音乐作者 |
|
|
339
|
+
| data.video_list[].music.duration | integer | 音乐时长(秒) |
|
|
340
|
+
| data.video_list[].music.play_url | string | 音乐播放地址 |
|
|
341
|
+
| data.video_list[].video | object | 视频信息 |
|
|
342
|
+
| data.video_list[].video.duration | integer | 视频时长(毫秒) |
|
|
343
|
+
| data.video_list[].video.ratio | string | 视频清晰度 |
|
|
344
|
+
| data.video_list[].video.width | integer | 视频宽度 |
|
|
345
|
+
| data.video_list[].video.height | integer | 视频高度 |
|
|
346
|
+
| data.video_list[].video.play_url | string | 视频播放地址 |
|
|
347
|
+
| data.video_list[].video.cover_url | string | 封面URL |
|
|
348
|
+
| data.video_list[].video.dynamic_cover_url | string | 动态封面URL |
|
|
349
|
+
| data.video_list[].video.origin_cover_url | string | 原始封面URL |
|
|
350
|
+
| data.video_list[].statistics | object | 互动统计数据 |
|
|
351
|
+
| data.video_list[].statistics.aweme_id | string | 视频ID |
|
|
352
|
+
| data.video_list[].statistics.comment_count | integer | 评论数 |
|
|
353
|
+
| data.video_list[].statistics.digg_count | integer | 点赞数 |
|
|
354
|
+
| data.video_list[].statistics.download_count | integer | 下载数 |
|
|
355
|
+
| data.video_list[].statistics.play_count | integer | 播放数 |
|
|
356
|
+
| data.video_list[].statistics.share_count | integer | 分享数 |
|
|
357
|
+
| data.video_list[].statistics.forward_count | integer | 转发数 |
|
|
358
|
+
| data.video_list[].statistics.collect_count | integer | 收藏数 |
|
|
359
|
+
| data.video_list[].video_tag | array | 视频标签列表 |
|
|
360
|
+
| data.video_list[].video_tag[].tag_id | integer | 标签ID |
|
|
361
|
+
| data.video_list[].video_tag[].tag_name | string | 标签名称 |
|
|
362
|
+
| data.video_list[].video_tag[].level | integer | 标签级别 |
|
|
363
|
+
| data.cursor | integer | 下一页游标 |
|
|
364
|
+
| data.has_more | integer | 是否还有更多数据(1=有,0=无) |
|
|
365
|
+
| billing | object | 计费信息 |
|
|
366
|
+
| billing.service_fee | float | 每页服务费 |
|
|
367
|
+
| billing.total_fee | float | 总费用 |
|
|
368
|
+
| billing.discount_applied | boolean | 是否应用折扣 |
|
|
369
|
+
| billing.pages_charged | integer | 计费页数 |
|
|
370
|
+
| billing.balance_before | float | 扣费前余额 |
|
|
371
|
+
| billing.balance_after | float | 扣费后余额 |
|
|
372
|
+
| billing.currency | string | 货币类型(CNY) |
|
|
373
|
+
| billing.transaction_ids | array | 交易ID列表 |
|
|
374
|
+
|
|
375
|
+
## 注意事项
|
|
376
|
+
1. 自动翻页,无需手动配置
|
|
377
|
+
2. max_seconds最大值为50秒
|
|
378
|
+
3. 按页收费,每成功获取一页数据扣费一次
|
|
379
|
+
4. 达到时间限制或页数限制时会自动停止翻页
|
|
380
|
+
|
|
381
|
+
# 作品链接转文字_v2
|
|
382
|
+
|
|
383
|
+
## 接口信息
|
|
384
|
+
- **URL**: `http://plugin.aiconductor.fun/api/douyin_link_to_text_v2`
|
|
385
|
+
- **方法**: `POST` / `GET`
|
|
386
|
+
- **Content-Type**: `application/json`
|
|
387
|
+
|
|
388
|
+
## 功能说明
|
|
389
|
+
将抖音视频链接中的语音转换为文字(增强版)。支持两套语音识别策略,自动检测转写准确度,按量计费(视频下载0.01元/次,文本转写0.0003元/秒)。
|
|
390
|
+
|
|
391
|
+
## 请求参数
|
|
392
|
+
|
|
393
|
+
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
|
394
|
+
|--------|------|------|--------|------|
|
|
395
|
+
| api_key | string | 是 | - | 计费系统API密钥 |
|
|
396
|
+
| video_url | string | 是 | - | 抖音视频URL(支持分享链接) |
|
|
397
|
+
|
|
398
|
+
## 响应参数
|
|
399
|
+
|
|
400
|
+
| 参数名 | 类型 | 说明 |
|
|
401
|
+
|--------|------|------|
|
|
402
|
+
| success | boolean | 是否成功 |
|
|
403
|
+
| message | string | 响应消息 |
|
|
404
|
+
| text | string | 转写文本结果 |
|
|
405
|
+
| billing | object | 计费信息 |
|
|
406
|
+
| billing.service_fee | float | 服务费 |
|
|
407
|
+
| billing.total_fee | float | 总费用 |
|
|
408
|
+
| billing.discount_applied | boolean | 是否应用折扣 |
|
|
409
|
+
| billing.balance_before | float | 扣费前余额 |
|
|
410
|
+
| billing.balance_after | float | 扣费后余额 |
|
|
411
|
+
| billing.currency | string | 货币类型(CNY) |
|
|
412
|
+
| billing.transaction_id | string | 交易ID |
|
|
413
|
+
|
|
414
|
+
## 注意事项
|
|
415
|
+
1. 支持抖音分享链接和完整视频URL
|
|
416
|
+
2. 支持两套语音识别策略,成功率更高
|
|
417
|
+
3. 自动检测转写准确度,确保结果质量
|
package/bin/aiconductor-cli.js
CHANGED
|
@@ -4,6 +4,15 @@ const { Command } = require("commander");
|
|
|
4
4
|
const { publishCommand } = require("../src/commands/html_publish");
|
|
5
5
|
const { publishCommand: webpagePublishCommand } = require("../src/commands/md_publish");
|
|
6
6
|
const { searchCommand } = require("../src/commands/web_search");
|
|
7
|
+
const {
|
|
8
|
+
videoInfoCommand,
|
|
9
|
+
videoCommentsCommand,
|
|
10
|
+
userVideosCommand,
|
|
11
|
+
userDetailCommand,
|
|
12
|
+
userSearchCommand,
|
|
13
|
+
videoSearchCommand,
|
|
14
|
+
linkToTextCommand,
|
|
15
|
+
} = require("../src/commands/douyin");
|
|
7
16
|
|
|
8
17
|
const program = new Command();
|
|
9
18
|
|
|
@@ -79,6 +88,82 @@ searchCmd
|
|
|
79
88
|
searchCommand(query, "image", options);
|
|
80
89
|
});
|
|
81
90
|
|
|
91
|
+
// 未知命令处理
|
|
92
|
+
|
|
93
|
+
// douyin 子命令组
|
|
94
|
+
const douyinCmd = program
|
|
95
|
+
.command("douyin")
|
|
96
|
+
.description("抖音数据工具箱");
|
|
97
|
+
|
|
98
|
+
douyinCmd
|
|
99
|
+
.command("video-info")
|
|
100
|
+
.description("获取抖音作品详情(0.01元/次)")
|
|
101
|
+
.argument("<share_url>", "抖音视频分享链接")
|
|
102
|
+
.option("-k, --api-key <key>", "AIConductor API Key")
|
|
103
|
+
.option("-f, --format <format>", "输出格式:json 或 text", "json")
|
|
104
|
+
.action((shareUrl, options) => { videoInfoCommand(shareUrl, options); });
|
|
105
|
+
|
|
106
|
+
douyinCmd
|
|
107
|
+
.command("video-comments")
|
|
108
|
+
.description("获取抖音作品评论(0.01元/次)")
|
|
109
|
+
.argument("<video_url>", "抖音视频URL")
|
|
110
|
+
.option("-k, --api-key <key>", "AIConductor API Key")
|
|
111
|
+
.option("--max-count <n>", "获取评论数量", "50")
|
|
112
|
+
.option("-f, --format <format>", "输出格式:json 或 text", "json")
|
|
113
|
+
.action((videoUrl, options) => { videoCommentsCommand(videoUrl, options); });
|
|
114
|
+
|
|
115
|
+
douyinCmd
|
|
116
|
+
.command("user-videos")
|
|
117
|
+
.description("获取抖音用户视频列表(0.03元/页)")
|
|
118
|
+
.argument("<home_page>", "抖音用户主页URL")
|
|
119
|
+
.option("-k, --api-key <key>", "AIConductor API Key")
|
|
120
|
+
.option("--max-pages <n>", "最大页数", "1")
|
|
121
|
+
.option("--max-seconds <n>", "最长运行时间(秒),最大180", "50")
|
|
122
|
+
.option("-f, --format <format>", "输出格式:json 或 text", "json")
|
|
123
|
+
.action((homePage, options) => { userVideosCommand(homePage, options); });
|
|
124
|
+
|
|
125
|
+
douyinCmd
|
|
126
|
+
.command("user-detail")
|
|
127
|
+
.description("获取抖音用户详情(0.03元/次)")
|
|
128
|
+
.argument("<home_page>", "抖音用户主页URL")
|
|
129
|
+
.option("-k, --api-key <key>", "AIConductor API Key")
|
|
130
|
+
.option("-f, --format <format>", "输出格式:json 或 text", "json")
|
|
131
|
+
.action((homePage, options) => { userDetailCommand(homePage, options); });
|
|
132
|
+
|
|
133
|
+
douyinCmd
|
|
134
|
+
.command("user-search")
|
|
135
|
+
.description("搜索抖音用户(0.15元/页)")
|
|
136
|
+
.argument("<keyword>", "搜索关键词")
|
|
137
|
+
.option("-k, --api-key <key>", "AIConductor API Key")
|
|
138
|
+
.option("--max-pages <n>", "最大页数,0=不限制", "1")
|
|
139
|
+
.option("--max-seconds <n>", "最长运行时间(秒),最大180", "50")
|
|
140
|
+
.option("--fans-range <range>", "粉丝数量筛选:0_1k/1k_1w/1w_10w/10w_100w/100w_")
|
|
141
|
+
.option("--user-type <type>", "用户类型筛选:common_user/enterprise_user/personal_user")
|
|
142
|
+
.option("-f, --format <format>", "输出格式:json 或 text", "json")
|
|
143
|
+
.action((keyword, options) => { userSearchCommand(keyword, options); });
|
|
144
|
+
|
|
145
|
+
douyinCmd
|
|
146
|
+
.command("video-search")
|
|
147
|
+
.description("搜索抖音视频(0.15元/页)")
|
|
148
|
+
.argument("<keyword>", "搜索关键词")
|
|
149
|
+
.option("-k, --api-key <key>", "AIConductor API Key")
|
|
150
|
+
.option("--max-pages <n>", "最大页数,0=不限制", "1")
|
|
151
|
+
.option("--max-seconds <n>", "最长运行时间(秒),最大50", "50")
|
|
152
|
+
.option("--sort-type <type>", "排序方式:0=综合,1=最多点赞,2=最新发布", "0")
|
|
153
|
+
.option("--publish-time <time>", "发布时间:0=不限,1=一天,7=一周,180=半年", "0")
|
|
154
|
+
.option("--filter-duration <dur>", "视频时长:0=不限,0-1=1分钟内,1-5=1-5分钟,5-10000=5分钟以上", "0")
|
|
155
|
+
.option("--content-type <type>", "内容类型:0=不限,1=视频,2=图片,3=文章", "0")
|
|
156
|
+
.option("-f, --format <format>", "输出格式:json 或 text", "json")
|
|
157
|
+
.action((keyword, options) => { videoSearchCommand(keyword, options); });
|
|
158
|
+
|
|
159
|
+
douyinCmd
|
|
160
|
+
.command("link-to-text")
|
|
161
|
+
.description("抖音作品链接转文字(0.01元/次 + 0.0003元/秒)")
|
|
162
|
+
.argument("<video_url>", "抖音视频URL")
|
|
163
|
+
.option("-k, --api-key <key>", "AIConductor API Key")
|
|
164
|
+
.option("-f, --format <format>", "输出格式:json 或 text", "json")
|
|
165
|
+
.action((videoUrl, options) => { linkToTextCommand(videoUrl, options); });
|
|
166
|
+
|
|
82
167
|
// 未知命令处理
|
|
83
168
|
program.on("command:*", () => {
|
|
84
169
|
console.error(`未知命令: ${program.args.join(" ")}`);
|
package/package.json
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "aiconductor-cli",
|
|
3
|
-
"version": "1.2.
|
|
4
|
-
"description": "AIConductor CLI 工具箱",
|
|
5
|
-
"keywords": [
|
|
6
|
-
"aiconductor",
|
|
7
|
-
"html",
|
|
8
|
-
"publish",
|
|
9
|
-
"deploy",
|
|
10
|
-
"cli"
|
|
11
|
-
],
|
|
12
|
-
"author": "",
|
|
13
|
-
"license": "MIT",
|
|
14
|
-
"main": "src/index.js",
|
|
15
|
-
"bin": {
|
|
16
|
-
"aiconductor-cli": "bin/aiconductor-cli.js"
|
|
17
|
-
},
|
|
18
|
-
"engines": {
|
|
19
|
-
"node": ">=16.0.0"
|
|
20
|
-
},
|
|
21
|
-
"scripts": {},
|
|
22
|
-
"dependencies": {
|
|
23
|
-
"commander": "^12.1.0"
|
|
24
|
-
}
|
|
25
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "aiconductor-cli",
|
|
3
|
+
"version": "1.2.1",
|
|
4
|
+
"description": "AIConductor CLI 工具箱",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"aiconductor",
|
|
7
|
+
"html",
|
|
8
|
+
"publish",
|
|
9
|
+
"deploy",
|
|
10
|
+
"cli"
|
|
11
|
+
],
|
|
12
|
+
"author": "",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"main": "src/index.js",
|
|
15
|
+
"bin": {
|
|
16
|
+
"aiconductor-cli": "bin/aiconductor-cli.js"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=16.0.0"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"commander": "^12.1.0"
|
|
24
|
+
}
|
|
25
|
+
}
|
package/src/api.js
CHANGED
|
@@ -6,6 +6,13 @@ const API_HOST = "plugin.aiconductor.fun";
|
|
|
6
6
|
const API_PATH = "/api/html_publish";
|
|
7
7
|
const MD_TO_HTML_API_PATH = "/api/md_to_html";
|
|
8
8
|
const SEARCH_API_PATH = "/api/aic_web_search";
|
|
9
|
+
const DOUYIN_VIDEO_INFO_PATH = "/api/douyin_video_info";
|
|
10
|
+
const DOUYIN_VIDEO_COMMENTS_PATH = "/api/douyin_video_comments";
|
|
11
|
+
const DOUYIN_USER_VIDEOS_PATH = "/api/douyin_user_videos";
|
|
12
|
+
const DOUYIN_USER_DETAIL_PATH = "/api/douyin_user_detail";
|
|
13
|
+
const DOUYIN_USER_SEARCH_PATH = "/api/douyin_user_search";
|
|
14
|
+
const DOUYIN_VIDEO_SEARCH_PATH = "/api/douyin_video_search";
|
|
15
|
+
const DOUYIN_LINK_TO_TEXT_PATH = "/api/douyin_link_to_text_v2";
|
|
9
16
|
|
|
10
17
|
/**
|
|
11
18
|
* 解析 API Key,三级优先级:命令行参数 > 系统环境变量 > .env 文件
|
|
@@ -160,4 +167,133 @@ async function publishMdToHtml(apiKey, mdText, filename, addCopyButton = false)
|
|
|
160
167
|
return safe;
|
|
161
168
|
}
|
|
162
169
|
|
|
163
|
-
|
|
170
|
+
/**
|
|
171
|
+
* 获取抖音作品详情
|
|
172
|
+
* @param {string} apiKey
|
|
173
|
+
* @param {string} shareUrl
|
|
174
|
+
* @returns {Promise<object>}
|
|
175
|
+
*/
|
|
176
|
+
async function douyinVideoInfo(apiKey, shareUrl) {
|
|
177
|
+
const resp = await httpPost(API_HOST, DOUYIN_VIDEO_INFO_PATH, {
|
|
178
|
+
api_key: apiKey,
|
|
179
|
+
share_url: shareUrl,
|
|
180
|
+
});
|
|
181
|
+
const { billing, ...safe } = resp;
|
|
182
|
+
return safe;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* 获取抖音作品评论
|
|
187
|
+
* @param {string} apiKey
|
|
188
|
+
* @param {string} videoUrl
|
|
189
|
+
* @param {number} [maxCount=50]
|
|
190
|
+
* @returns {Promise<object>}
|
|
191
|
+
*/
|
|
192
|
+
async function douyinVideoComments(apiKey, videoUrl, maxCount) {
|
|
193
|
+
const body = { api_key: apiKey, video_url: videoUrl };
|
|
194
|
+
if (maxCount !== undefined && maxCount !== null) body.max_count = maxCount;
|
|
195
|
+
const resp = await httpPost(API_HOST, DOUYIN_VIDEO_COMMENTS_PATH, body);
|
|
196
|
+
const { billing, ...safe } = resp;
|
|
197
|
+
return safe;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* 获取抖音用户视频列表
|
|
202
|
+
* @param {string} apiKey
|
|
203
|
+
* @param {string} homePage
|
|
204
|
+
* @param {number} [maxSeconds=50]
|
|
205
|
+
* @param {number} [maxPages=1]
|
|
206
|
+
* @returns {Promise<object>}
|
|
207
|
+
*/
|
|
208
|
+
async function douyinUserVideos(apiKey, homePage, maxSeconds, maxPages) {
|
|
209
|
+
const body = { api_key: apiKey, home_page: homePage };
|
|
210
|
+
if (maxSeconds !== undefined && maxSeconds !== null) body.max_seconds = maxSeconds;
|
|
211
|
+
if (maxPages !== undefined && maxPages !== null) body.max_pages = maxPages;
|
|
212
|
+
const resp = await httpPost(API_HOST, DOUYIN_USER_VIDEOS_PATH, body);
|
|
213
|
+
const { billing, ...safe } = resp;
|
|
214
|
+
return safe;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* 获取抖音用户详情
|
|
219
|
+
* @param {string} apiKey
|
|
220
|
+
* @param {string} homePage
|
|
221
|
+
* @returns {Promise<object>}
|
|
222
|
+
*/
|
|
223
|
+
async function douyinUserDetail(apiKey, homePage) {
|
|
224
|
+
const resp = await httpPost(API_HOST, DOUYIN_USER_DETAIL_PATH, {
|
|
225
|
+
api_key: apiKey,
|
|
226
|
+
home_page: homePage,
|
|
227
|
+
});
|
|
228
|
+
const { billing, ...safe } = resp;
|
|
229
|
+
return safe;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* 搜索抖音用户
|
|
234
|
+
* @param {string} apiKey
|
|
235
|
+
* @param {string} keyword
|
|
236
|
+
* @param {object} [opts]
|
|
237
|
+
* @returns {Promise<object>}
|
|
238
|
+
*/
|
|
239
|
+
async function douyinUserSearch(apiKey, keyword, opts = {}) {
|
|
240
|
+
const body = { api_key: apiKey, keyword };
|
|
241
|
+
if (opts.maxPages !== undefined && opts.maxPages !== null) body.max_pages = opts.maxPages;
|
|
242
|
+
if (opts.maxSeconds !== undefined && opts.maxSeconds !== null) body.max_seconds = opts.maxSeconds;
|
|
243
|
+
if (opts.fansRange) body.douyin_user_fans = opts.fansRange;
|
|
244
|
+
if (opts.userType) body.douyin_user_type = opts.userType;
|
|
245
|
+
const resp = await httpPost(API_HOST, DOUYIN_USER_SEARCH_PATH, body);
|
|
246
|
+
const { billing, ...safe } = resp;
|
|
247
|
+
return safe;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* 搜索抖音视频
|
|
252
|
+
* @param {string} apiKey
|
|
253
|
+
* @param {string} keyword
|
|
254
|
+
* @param {object} [opts]
|
|
255
|
+
* @returns {Promise<object>}
|
|
256
|
+
*/
|
|
257
|
+
async function douyinVideoSearch(apiKey, keyword, opts = {}) {
|
|
258
|
+
const body = { keyword };
|
|
259
|
+
if (opts.sortType !== undefined) body.sort_type = opts.sortType;
|
|
260
|
+
if (opts.publishTime !== undefined) body.publish_time = opts.publishTime;
|
|
261
|
+
if (opts.filterDuration !== undefined) body.filter_duration = opts.filterDuration;
|
|
262
|
+
if (opts.contentType !== undefined) body.content_type = opts.contentType;
|
|
263
|
+
if (opts.maxPages !== undefined && opts.maxPages !== null) body.max_pages = opts.maxPages;
|
|
264
|
+
if (opts.maxSeconds !== undefined && opts.maxSeconds !== null) body.max_seconds = opts.maxSeconds;
|
|
265
|
+
const resp = await httpPost(API_HOST, DOUYIN_VIDEO_SEARCH_PATH, body, {
|
|
266
|
+
Authorization: `Bearer ${apiKey}`,
|
|
267
|
+
});
|
|
268
|
+
const { billing, ...safe } = resp;
|
|
269
|
+
return safe;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* 抖音作品链接转文字
|
|
274
|
+
* @param {string} apiKey
|
|
275
|
+
* @param {string} videoUrl
|
|
276
|
+
* @returns {Promise<object>}
|
|
277
|
+
*/
|
|
278
|
+
async function douyinLinkToText(apiKey, videoUrl) {
|
|
279
|
+
const resp = await httpPost(API_HOST, DOUYIN_LINK_TO_TEXT_PATH, {
|
|
280
|
+
api_key: apiKey,
|
|
281
|
+
video_url: videoUrl,
|
|
282
|
+
});
|
|
283
|
+
const { billing, ...safe } = resp;
|
|
284
|
+
return safe;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
module.exports = {
|
|
288
|
+
resolveApiKey,
|
|
289
|
+
publishHtml,
|
|
290
|
+
publishMdToHtml,
|
|
291
|
+
webSearch,
|
|
292
|
+
douyinVideoInfo,
|
|
293
|
+
douyinVideoComments,
|
|
294
|
+
douyinUserVideos,
|
|
295
|
+
douyinUserDetail,
|
|
296
|
+
douyinUserSearch,
|
|
297
|
+
douyinVideoSearch,
|
|
298
|
+
douyinLinkToText,
|
|
299
|
+
};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
const {
|
|
2
|
+
resolveApiKey,
|
|
3
|
+
douyinVideoInfo,
|
|
4
|
+
douyinVideoComments,
|
|
5
|
+
douyinUserVideos,
|
|
6
|
+
douyinUserDetail,
|
|
7
|
+
douyinUserSearch,
|
|
8
|
+
douyinVideoSearch,
|
|
9
|
+
douyinLinkToText,
|
|
10
|
+
} = require("../api");
|
|
11
|
+
|
|
12
|
+
function outputResult(data, format) {
|
|
13
|
+
if (format === "json") {
|
|
14
|
+
process.stdout.write(JSON.stringify({ success: true, data }, null, 2) + "\n");
|
|
15
|
+
} else {
|
|
16
|
+
process.stdout.write("\u2713 \u64cd\u4f5c\u6210\u529f\n");
|
|
17
|
+
process.stdout.write(JSON.stringify(data, null, 2) + "\n");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function outputError(err, format) {
|
|
22
|
+
if (format === "json") {
|
|
23
|
+
process.stderr.write(JSON.stringify({ success: false, error: err.message || String(err) }, null, 2) + "\n");
|
|
24
|
+
} else {
|
|
25
|
+
process.stderr.write("\u2717 \u9519\u8bef: " + (err.message || err) + "\n");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function resolveKey(optionKey, format) {
|
|
30
|
+
const key = resolveApiKey(optionKey);
|
|
31
|
+
if (!key) {
|
|
32
|
+
outputError(
|
|
33
|
+
{ message: "\u7f3a\u5c11 API Key\u3002\u8bf7\u901a\u8fc7 -k \u53c2\u6570\u3001AICONDUCTOR_API_KEY \u73af\u5883\u53d8\u91cf\u6216 .env \u6587\u4ef6\u63d0\u4f9b\u3002" },
|
|
34
|
+
format
|
|
35
|
+
);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
return key;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function handleError(err, format) {
|
|
42
|
+
if (err.type === "http_error") {
|
|
43
|
+
outputError(
|
|
44
|
+
{ message: "API \u8fd4\u56de\u9519\u8bef (HTTP " + err.statusCode + "): " + (err.body?.message || JSON.stringify(err.body)) },
|
|
45
|
+
format
|
|
46
|
+
);
|
|
47
|
+
} else if (err.type === "network_error") {
|
|
48
|
+
outputError({ message: "\u7f51\u7edc\u9519\u8bef: " + err.message }, format);
|
|
49
|
+
} else {
|
|
50
|
+
outputError({ message: "\u672a\u77e5\u9519\u8bef: " + (err.message || err) }, format);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function videoInfoCommand(shareUrl, options = {}) {
|
|
55
|
+
const format = options.format || "json";
|
|
56
|
+
const key = resolveKey(options.apiKey, format);
|
|
57
|
+
try {
|
|
58
|
+
const result = await douyinVideoInfo(key, shareUrl);
|
|
59
|
+
outputResult(result, format);
|
|
60
|
+
process.exit(0);
|
|
61
|
+
} catch (err) { handleError(err, format); process.exit(1); }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function videoCommentsCommand(videoUrl, options = {}) {
|
|
65
|
+
const format = options.format || "json";
|
|
66
|
+
const key = resolveKey(options.apiKey, format);
|
|
67
|
+
try {
|
|
68
|
+
const maxCount = options.maxCount ? parseInt(options.maxCount, 10) : undefined;
|
|
69
|
+
const result = await douyinVideoComments(key, videoUrl, maxCount);
|
|
70
|
+
outputResult(result, format);
|
|
71
|
+
process.exit(0);
|
|
72
|
+
} catch (err) { handleError(err, format); process.exit(1); }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function userVideosCommand(homePage, options = {}) {
|
|
76
|
+
const format = options.format || "json";
|
|
77
|
+
const key = resolveKey(options.apiKey, format);
|
|
78
|
+
try {
|
|
79
|
+
const maxSeconds = options.maxSeconds ? parseInt(options.maxSeconds, 10) : undefined;
|
|
80
|
+
const maxPages = options.maxPages ? parseInt(options.maxPages, 10) : undefined;
|
|
81
|
+
const result = await douyinUserVideos(key, homePage, maxSeconds, maxPages);
|
|
82
|
+
outputResult(result, format);
|
|
83
|
+
process.exit(0);
|
|
84
|
+
} catch (err) { handleError(err, format); process.exit(1); }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function userDetailCommand(homePage, options = {}) {
|
|
88
|
+
const format = options.format || "json";
|
|
89
|
+
const key = resolveKey(options.apiKey, format);
|
|
90
|
+
try {
|
|
91
|
+
const result = await douyinUserDetail(key, homePage);
|
|
92
|
+
outputResult(result, format);
|
|
93
|
+
process.exit(0);
|
|
94
|
+
} catch (err) { handleError(err, format); process.exit(1); }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function userSearchCommand(keyword, options = {}) {
|
|
98
|
+
const format = options.format || "json";
|
|
99
|
+
const key = resolveKey(options.apiKey, format);
|
|
100
|
+
try {
|
|
101
|
+
const maxPages = options.maxPages ? parseInt(options.maxPages, 10) : undefined;
|
|
102
|
+
const maxSeconds = options.maxSeconds ? parseInt(options.maxSeconds, 10) : undefined;
|
|
103
|
+
const result = await douyinUserSearch(key, keyword, {
|
|
104
|
+
maxPages,
|
|
105
|
+
maxSeconds,
|
|
106
|
+
fansRange: options.fansRange,
|
|
107
|
+
userType: options.userType,
|
|
108
|
+
});
|
|
109
|
+
outputResult(result, format);
|
|
110
|
+
process.exit(0);
|
|
111
|
+
} catch (err) { handleError(err, format); process.exit(1); }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function videoSearchCommand(keyword, options = {}) {
|
|
115
|
+
const format = options.format || "json";
|
|
116
|
+
const key = resolveKey(options.apiKey, format);
|
|
117
|
+
try {
|
|
118
|
+
const maxPages = options.maxPages ? parseInt(options.maxPages, 10) : undefined;
|
|
119
|
+
const maxSeconds = options.maxSeconds ? parseInt(options.maxSeconds, 10) : undefined;
|
|
120
|
+
const result = await douyinVideoSearch(key, keyword, {
|
|
121
|
+
maxPages,
|
|
122
|
+
maxSeconds,
|
|
123
|
+
sortType: options.sortType,
|
|
124
|
+
publishTime: options.publishTime,
|
|
125
|
+
filterDuration: options.filterDuration,
|
|
126
|
+
contentType: options.contentType,
|
|
127
|
+
});
|
|
128
|
+
outputResult(result, format);
|
|
129
|
+
process.exit(0);
|
|
130
|
+
} catch (err) { handleError(err, format); process.exit(1); }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function linkToTextCommand(videoUrl, options = {}) {
|
|
134
|
+
const format = options.format || "json";
|
|
135
|
+
const key = resolveKey(options.apiKey, format);
|
|
136
|
+
try {
|
|
137
|
+
const result = await douyinLinkToText(key, videoUrl);
|
|
138
|
+
outputResult(result, format);
|
|
139
|
+
process.exit(0);
|
|
140
|
+
} catch (err) { handleError(err, format); process.exit(1); }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.exports = {
|
|
144
|
+
videoInfoCommand,
|
|
145
|
+
videoCommentsCommand,
|
|
146
|
+
userVideosCommand,
|
|
147
|
+
userDetailCommand,
|
|
148
|
+
userSearchCommand,
|
|
149
|
+
videoSearchCommand,
|
|
150
|
+
linkToTextCommand,
|
|
151
|
+
};
|