aiconductor-cli 1.2.1 → 1.2.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/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
 
@@ -13,6 +13,7 @@ const DOUYIN_USER_DETAIL_PATH = "/api/douyin_user_detail";
13
13
  const DOUYIN_USER_SEARCH_PATH = "/api/douyin_user_search";
14
14
  const DOUYIN_VIDEO_SEARCH_PATH = "/api/douyin_video_search";
15
15
  const DOUYIN_LINK_TO_TEXT_PATH = "/api/douyin_link_to_text_v2";
16
+ const XHS_NOTE_PUBLISH_PATH = "/api/xhs_note_publish";
16
17
 
17
18
  /**
18
19
  * 解析 API Key,三级优先级:命令行参数 > 系统环境变量 > .env 文件
@@ -40,6 +41,31 @@ function resolveApiKey(optionKey) {
40
41
  return null;
41
42
  }
42
43
 
44
+ /**
45
+ * 过滤响应中的计费信息
46
+ * @param {object} resp
47
+ * @returns {object}
48
+ */
49
+ function stripBilling(resp) {
50
+ if (!resp || typeof resp !== "object") return resp;
51
+ const result = { ...resp };
52
+ delete result.billing;
53
+ return result;
54
+ }
55
+
56
+ /**
57
+ * 校验整数参数,非有效正整数时报错
58
+ * @param {*} value
59
+ * @param {string} name
60
+ */
61
+ function validateInt(value, name) {
62
+ if (value === undefined || value === null) return;
63
+ const n = Number(value);
64
+ if (!Number.isInteger(n) || n < 0) {
65
+ throw Object.assign(new Error(`${name} 须为非负整数,收到: ${value}`), { type: "validation_error" });
66
+ }
67
+ }
68
+
43
69
  /**
44
70
  * HTTP POST 请求封装
45
71
  * @param {string} host
@@ -53,7 +79,7 @@ function httpPost(host, path, body, headers = {}) {
53
79
 
54
80
  const options = {
55
81
  hostname: host,
56
- port: 80,
82
+ port: 443,
57
83
  path: path,
58
84
  method: "POST",
59
85
  headers: {
@@ -63,7 +89,7 @@ function httpPost(host, path, body, headers = {}) {
63
89
  },
64
90
  };
65
91
 
66
- const req = http.request(options, (res) => {
92
+ const req = https.request(options, (res) => {
67
93
  let chunks = [];
68
94
  res.on("data", (chunk) => chunks.push(chunk));
69
95
  res.on("end", () => {
@@ -112,10 +138,7 @@ async function publishHtml(apiKey, htmlCode, title) {
112
138
  if (title) reqBody.title = title;
113
139
 
114
140
  const resp = await httpPost(API_HOST, API_PATH, reqBody);
115
-
116
- // 过滤计费信息
117
- const { billing, ...safe } = resp;
118
- return safe;
141
+ return stripBilling(resp);
119
142
  }
120
143
 
121
144
  /**
@@ -125,6 +148,10 @@ async function publishHtml(apiKey, htmlCode, title) {
125
148
  * @returns {Promise<object>}
126
149
  */
127
150
  async function webSearch(apiKey, params) {
151
+ // 输入校验
152
+ validateInt(params.count, "count");
153
+ validateInt(params.authLevel, "authLevel");
154
+
128
155
  const reqBody = {
129
156
  query: params.query,
130
157
  search_type: params.searchType,
@@ -140,9 +167,7 @@ async function webSearch(apiKey, params) {
140
167
  Authorization: `Bearer ${apiKey}`,
141
168
  });
142
169
 
143
- // 过滤计费信息
144
- const { billing, ...safe } = resp;
145
- return safe;
170
+ return stripBilling(resp);
146
171
  }
147
172
 
148
173
  /**
@@ -163,8 +188,7 @@ async function publishMdToHtml(apiKey, mdText, filename, addCopyButton = false)
163
188
 
164
189
  const resp = await httpPost(API_HOST, MD_TO_HTML_API_PATH, reqBody);
165
190
 
166
- const { billing, ...safe } = resp;
167
- return safe;
191
+ return stripBilling(resp);
168
192
  }
169
193
 
170
194
  /**
@@ -178,8 +202,7 @@ async function douyinVideoInfo(apiKey, shareUrl) {
178
202
  api_key: apiKey,
179
203
  share_url: shareUrl,
180
204
  });
181
- const { billing, ...safe } = resp;
182
- return safe;
205
+ return stripBilling(resp);
183
206
  }
184
207
 
185
208
  /**
@@ -193,8 +216,7 @@ async function douyinVideoComments(apiKey, videoUrl, maxCount) {
193
216
  const body = { api_key: apiKey, video_url: videoUrl };
194
217
  if (maxCount !== undefined && maxCount !== null) body.max_count = maxCount;
195
218
  const resp = await httpPost(API_HOST, DOUYIN_VIDEO_COMMENTS_PATH, body);
196
- const { billing, ...safe } = resp;
197
- return safe;
219
+ return stripBilling(resp);
198
220
  }
199
221
 
200
222
  /**
@@ -210,8 +232,7 @@ async function douyinUserVideos(apiKey, homePage, maxSeconds, maxPages) {
210
232
  if (maxSeconds !== undefined && maxSeconds !== null) body.max_seconds = maxSeconds;
211
233
  if (maxPages !== undefined && maxPages !== null) body.max_pages = maxPages;
212
234
  const resp = await httpPost(API_HOST, DOUYIN_USER_VIDEOS_PATH, body);
213
- const { billing, ...safe } = resp;
214
- return safe;
235
+ return stripBilling(resp);
215
236
  }
216
237
 
217
238
  /**
@@ -225,8 +246,7 @@ async function douyinUserDetail(apiKey, homePage) {
225
246
  api_key: apiKey,
226
247
  home_page: homePage,
227
248
  });
228
- const { billing, ...safe } = resp;
229
- return safe;
249
+ return stripBilling(resp);
230
250
  }
231
251
 
232
252
  /**
@@ -243,8 +263,7 @@ async function douyinUserSearch(apiKey, keyword, opts = {}) {
243
263
  if (opts.fansRange) body.douyin_user_fans = opts.fansRange;
244
264
  if (opts.userType) body.douyin_user_type = opts.userType;
245
265
  const resp = await httpPost(API_HOST, DOUYIN_USER_SEARCH_PATH, body);
246
- const { billing, ...safe } = resp;
247
- return safe;
266
+ return stripBilling(resp);
248
267
  }
249
268
 
250
269
  /**
@@ -265,8 +284,7 @@ async function douyinVideoSearch(apiKey, keyword, opts = {}) {
265
284
  const resp = await httpPost(API_HOST, DOUYIN_VIDEO_SEARCH_PATH, body, {
266
285
  Authorization: `Bearer ${apiKey}`,
267
286
  });
268
- const { billing, ...safe } = resp;
269
- return safe;
287
+ return stripBilling(resp);
270
288
  }
271
289
 
272
290
  /**
@@ -280,8 +298,28 @@ async function douyinLinkToText(apiKey, videoUrl) {
280
298
  api_key: apiKey,
281
299
  video_url: videoUrl,
282
300
  });
283
- const { billing, ...safe } = resp;
284
- return safe;
301
+ return stripBilling(resp);
302
+ }
303
+
304
+ /**
305
+ * 小红书笔记发布 — 生成扫码发布 H5 页面与二维码
306
+ * @param {string} apiKey
307
+ * @param {{ type: string, title?: string, content?: string, images?: string[], video?: string, cover?: string, transferToOss?: boolean }} params
308
+ * @returns {Promise<object>}
309
+ */
310
+ async function xhsNotePublish(apiKey, params) {
311
+ const body = { type: params.type };
312
+ if (params.title) body.title = params.title;
313
+ if (params.content) body.content = params.content;
314
+ if (params.images && params.images.length > 0) body.images = params.images;
315
+ if (params.video) body.video = params.video;
316
+ if (params.cover) body.cover = params.cover;
317
+ if (params.transferToOss !== undefined) body.transfer_to_oss = params.transferToOss;
318
+
319
+ const resp = await httpPost(API_HOST, XHS_NOTE_PUBLISH_PATH, body, {
320
+ Authorization: `Bearer ${apiKey}`,
321
+ });
322
+ return stripBilling(resp);
285
323
  }
286
324
 
287
325
  module.exports = {
@@ -296,4 +334,5 @@ module.exports = {
296
334
  douyinUserSearch,
297
335
  douyinVideoSearch,
298
336
  douyinLinkToText,
337
+ xhsNotePublish,
299
338
  };
@@ -1,151 +1,109 @@
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
- };
1
+ const {
2
+ douyinVideoInfo,
3
+ douyinVideoComments,
4
+ douyinUserVideos,
5
+ douyinUserDetail,
6
+ douyinUserSearch,
7
+ douyinVideoSearch,
8
+ douyinLinkToText,
9
+ } = require("../api");
10
+ const { outputResult, resolveKeyOrExit, handleApiError, safeParseInt } = require("../lib/cli-utils");
11
+
12
+ async function videoInfoCommand(shareUrl, options = {}) {
13
+ const format = options.format || "json";
14
+ const apiKey = resolveKeyOrExit(options.apiKey, format);
15
+ try {
16
+ const result = await douyinVideoInfo(apiKey, shareUrl);
17
+ outputResult(result, format);
18
+ process.exit(0);
19
+ } catch (err) { handleApiError(err, format); process.exit(1); }
20
+ }
21
+
22
+ async function videoCommentsCommand(videoUrl, options = {}) {
23
+ const format = options.format || "json";
24
+ const apiKey = resolveKeyOrExit(options.apiKey, format);
25
+ try {
26
+ const maxCount = safeParseInt(options.maxCount, undefined);
27
+ const result = await douyinVideoComments(apiKey, videoUrl, maxCount);
28
+ outputResult(result, format);
29
+ process.exit(0);
30
+ } catch (err) { handleApiError(err, format); process.exit(1); }
31
+ }
32
+
33
+ async function userVideosCommand(homePage, options = {}) {
34
+ const format = options.format || "json";
35
+ const apiKey = resolveKeyOrExit(options.apiKey, format);
36
+ try {
37
+ const maxSeconds = safeParseInt(options.maxSeconds, undefined);
38
+ const maxPages = safeParseInt(options.maxPages, undefined);
39
+ const result = await douyinUserVideos(apiKey, homePage, maxSeconds, maxPages);
40
+ outputResult(result, format);
41
+ process.exit(0);
42
+ } catch (err) { handleApiError(err, format); process.exit(1); }
43
+ }
44
+
45
+ async function userDetailCommand(homePage, options = {}) {
46
+ const format = options.format || "json";
47
+ const apiKey = resolveKeyOrExit(options.apiKey, format);
48
+ try {
49
+ const result = await douyinUserDetail(apiKey, homePage);
50
+ outputResult(result, format);
51
+ process.exit(0);
52
+ } catch (err) { handleApiError(err, format); process.exit(1); }
53
+ }
54
+
55
+ async function userSearchCommand(keyword, options = {}) {
56
+ const format = options.format || "json";
57
+ const apiKey = resolveKeyOrExit(options.apiKey, format);
58
+ try {
59
+ const maxPages = safeParseInt(options.maxPages, undefined);
60
+ const maxSeconds = safeParseInt(options.maxSeconds, undefined);
61
+ const result = await douyinUserSearch(apiKey, keyword, {
62
+ maxPages,
63
+ maxSeconds,
64
+ fansRange: options.fansRange,
65
+ userType: options.userType,
66
+ });
67
+ outputResult(result, format);
68
+ process.exit(0);
69
+ } catch (err) { handleApiError(err, format); process.exit(1); }
70
+ }
71
+
72
+ async function videoSearchCommand(keyword, options = {}) {
73
+ const format = options.format || "json";
74
+ const apiKey = resolveKeyOrExit(options.apiKey, format);
75
+ try {
76
+ const maxPages = safeParseInt(options.maxPages, undefined);
77
+ const maxSeconds = safeParseInt(options.maxSeconds, undefined);
78
+ const result = await douyinVideoSearch(apiKey, keyword, {
79
+ maxPages,
80
+ maxSeconds,
81
+ sortType: options.sortType,
82
+ publishTime: options.publishTime,
83
+ filterDuration: options.filterDuration,
84
+ contentType: options.contentType,
85
+ });
86
+ outputResult(result, format);
87
+ process.exit(0);
88
+ } catch (err) { handleApiError(err, format); process.exit(1); }
89
+ }
90
+
91
+ async function linkToTextCommand(videoUrl, options = {}) {
92
+ const format = options.format || "json";
93
+ const apiKey = resolveKeyOrExit(options.apiKey, format);
94
+ try {
95
+ const result = await douyinLinkToText(apiKey, videoUrl);
96
+ outputResult(result, format);
97
+ process.exit(0);
98
+ } catch (err) { handleApiError(err, format); process.exit(1); }
99
+ }
100
+
101
+ module.exports = {
102
+ videoInfoCommand,
103
+ videoCommentsCommand,
104
+ userVideosCommand,
105
+ userDetailCommand,
106
+ userSearchCommand,
107
+ videoSearchCommand,
108
+ linkToTextCommand,
109
+ };
@@ -1,91 +1,54 @@
1
- const fs = require("fs");
2
- const path = require("path");
3
- const { resolveApiKey, publishHtml } = require("../api");
4
-
5
- /**
6
- * 智能解析输入:如果是已存在的文件路径则读取,否则当作原始 HTML 字符串
7
- * @param {string} input
8
- * @returns {string}
9
- */
10
- function resolveHtmlInput(input) {
11
- const trimmed = input.trim();
12
- // 尝试作为文件路径
13
- try {
14
- const filePath = path.resolve(trimmed);
15
- if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
16
- return fs.readFileSync(filePath, "utf-8");
17
- }
18
- } catch (_) {
19
- // 不是文件路径,按原始字符串处理
20
- }
21
- return trimmed;
22
- }
23
-
24
- /**
25
- * 输出成功结果
26
- * @param {object} data
27
- * @param {"json"|"text"} format
28
- */
29
- function outputResult(data, format) {
30
- if (format === "json") {
31
- process.stdout.write(JSON.stringify({ success: true, data }, null, 2) + "\n");
32
- } else {
33
- process.stdout.write(`✓ 发布成功\n`);
34
- process.stdout.write(` URL: ${data.online_url}\n`);
35
- if (data.message) {
36
- process.stdout.write(` 信息: ${data.message}\n`);
37
- }
38
- }
39
- }
40
-
41
- /**
42
- * 输出错误
43
- * @param {object} err
44
- * @param {"json"|"text"} format
45
- */
46
- function outputError(err, format) {
47
- if (format === "json") {
48
- process.stderr.write(JSON.stringify({ success: false, error: err.message || String(err) }, null, 2) + "\n");
49
- } else {
50
- process.stderr.write(`✗ 错误: ${err.message || err}\n`);
51
- }
52
- }
53
-
54
- /**
55
- * publish 命令(子命令模式)
56
- * @param {string} htmlCode - HTML 代码或文件路径
57
- * @param {{ apiKey?: string, format?: string, title?: string }} options
58
- */
59
- async function publishCommand(htmlCode, options = {}) {
60
- const format = options.format || "json";
61
- const resolvedKey = resolveApiKey(options.apiKey);
62
-
63
- if (!resolvedKey) {
64
- outputError(
65
- { message: "缺少 API Key。请通过 -k 参数、AICONDUCTOR_API_KEY 环境变量或 .env 文件提供。" },
66
- format
67
- );
68
- process.exit(1);
69
- }
70
-
71
- try {
72
- const code = resolveHtmlInput(htmlCode);
73
- const result = await publishHtml(resolvedKey, code, options.title);
74
- outputResult(result, format);
75
- process.exit(0);
76
- } catch (err) {
77
- if (err.type === "http_error") {
78
- outputError(
79
- { message: `API 返回错误 (HTTP ${err.statusCode}): ${err.body?.message || JSON.stringify(err.body)}` },
80
- format
81
- );
82
- } else if (err.type === "network_error") {
83
- outputError({ message: `网络错误: ${err.message}` }, format);
84
- } else {
85
- outputError({ message: `未知错误: ${err.message || err}` }, format);
86
- }
87
- process.exit(1);
88
- }
89
- }
90
-
91
- module.exports = { publishCommand };
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { publishHtml } = require("../api");
4
+ const { outputResult, outputError, resolveKeyOrExit, handleApiError } = require("../lib/cli-utils");
5
+
6
+ /**
7
+ * 智能解析输入:如果是已存在的文件路径则读取,否则当作原始 HTML 字符串
8
+ * @param {string} input
9
+ * @returns {string}
10
+ */
11
+ function resolveHtmlInput(input) {
12
+ const trimmed = input.trim();
13
+ try {
14
+ const filePath = path.resolve(trimmed);
15
+ if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
16
+ return fs.readFileSync(filePath, "utf-8");
17
+ }
18
+ } catch (_) {
19
+ // 不是文件路径,按原始字符串处理
20
+ }
21
+ return trimmed;
22
+ }
23
+
24
+ /** text 模式的自定义格式化 */
25
+ function formatPublishText(data) {
26
+ let out = "✓ 发布成功\n";
27
+ out += ` URL: ${data.online_url}\n`;
28
+ if (data.message) {
29
+ out += ` 信息: ${data.message}\n`;
30
+ }
31
+ return out.trimEnd();
32
+ }
33
+
34
+ /**
35
+ * publish 命令(子命令模式)
36
+ * @param {string} htmlCode - HTML 代码或文件路径
37
+ * @param {{ apiKey?: string, format?: string, title?: string }} options
38
+ */
39
+ async function publishCommand(htmlCode, options = {}) {
40
+ const format = options.format || "json";
41
+ const apiKey = resolveKeyOrExit(options.apiKey, format);
42
+
43
+ try {
44
+ const code = resolveHtmlInput(htmlCode);
45
+ const result = await publishHtml(apiKey, code, options.title);
46
+ outputResult(result, format, formatPublishText);
47
+ process.exit(0);
48
+ } catch (err) {
49
+ handleApiError(err, format);
50
+ process.exit(1);
51
+ }
52
+ }
53
+
54
+ module.exports = { publishCommand };