aiconductor-cli 1.4.0 → 1.5.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 +160 -78
- package/README.md +27 -2
- package/api_docs/douyin_user_videos.md +2 -2
- package/api_docs/wx_history_articles.md +39 -20
- package/api_docs/xhs_user_info_v2.md +5 -1
- package/api_docs/xhs_user_notes_v3.md +2 -0
- package/bin/aiconductor-cli.js +134 -106
- package/package.json +1 -1
- package/src/api.js +362 -268
- package/src/api.test.js +149 -2
- package/src/commands/wx.js +116 -0
package/src/api.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
const http = require("http");
|
|
2
2
|
const https = require("https");
|
|
3
|
-
const fs = require("fs");
|
|
4
|
-
const path = require("path");
|
|
5
|
-
|
|
6
|
-
const API_HOST = "plugin.aiconductor.fun";
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
const API_HOST = "plugin.aiconductor.fun";
|
|
7
7
|
const API_PATH = "/api/html_publish";
|
|
8
8
|
const MD_TO_HTML_API_PATH = "/api/md_to_html";
|
|
9
9
|
const PUBLISHED_WEBPAGES_API_PATH = "/api/published_webpages";
|
|
@@ -11,73 +11,77 @@ const DELETE_PUBLISHED_WEBPAGE_API_PATH = "/api/delete_published_webpage";
|
|
|
11
11
|
const SEARCH_API_PATH = "/api/doubao_search";
|
|
12
12
|
const HOT_LIST_API_PATH = "/api/hot_list_mixed";
|
|
13
13
|
const XHS_HOT_LIST_API_PATH = "/api/xhs_hot_list";
|
|
14
|
-
const DOUYIN_VIDEO_INFO_PATH = "/api/douyin_video_info";
|
|
15
|
-
const DOUYIN_VIDEO_COMMENTS_PATH = "/api/douyin_video_comments";
|
|
16
|
-
const DOUYIN_USER_VIDEOS_PATH = "/api/douyin_user_videos";
|
|
17
|
-
const DOUYIN_USER_DETAIL_PATH = "/api/douyin_user_detail";
|
|
18
|
-
const DOUYIN_USER_SEARCH_PATH = "/api/douyin_user_search";
|
|
19
|
-
const DOUYIN_VIDEO_SEARCH_PATH = "/api/douyin_video_search";
|
|
14
|
+
const DOUYIN_VIDEO_INFO_PATH = "/api/douyin_video_info";
|
|
15
|
+
const DOUYIN_VIDEO_COMMENTS_PATH = "/api/douyin_video_comments";
|
|
16
|
+
const DOUYIN_USER_VIDEOS_PATH = "/api/douyin_user_videos";
|
|
17
|
+
const DOUYIN_USER_DETAIL_PATH = "/api/douyin_user_detail";
|
|
18
|
+
const DOUYIN_USER_SEARCH_PATH = "/api/douyin_user_search";
|
|
19
|
+
const DOUYIN_VIDEO_SEARCH_PATH = "/api/douyin_video_search";
|
|
20
20
|
const DOUYIN_LINK_TO_TEXT_PATH = "/api/douyin_link_to_text_v2";
|
|
21
21
|
const XHS_NOTE_PUBLISH_PATH = "/api/xhs_note_publish";
|
|
22
|
+
const WX_ARTICLE_DATA_PATH = "/api/wx_article_data";
|
|
23
|
+
const WX_HISTORY_ARTICLES_PATH = "/api/wx_history_articles";
|
|
22
24
|
const R2_UPLOADER_PATH = "/api/r2_uploader";
|
|
23
25
|
const MAX_R2_FILE_SIZE = 100 * 1024 * 1024;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
*
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
*
|
|
67
|
-
|
|
26
|
+
// wx_history_articles 的 max_seconds 平台上限(预留 10s 给平台 180s 兜底)
|
|
27
|
+
const WX_HISTORY_MAX_SECONDS = 170;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 解析 API Key,三级优先级:命令行参数 > 系统环境变量 > .env 文件
|
|
31
|
+
* @returns {string|null}
|
|
32
|
+
*/
|
|
33
|
+
function resolveApiKey(optionKey) {
|
|
34
|
+
// 1. 命令行参数
|
|
35
|
+
if (optionKey) return optionKey;
|
|
36
|
+
|
|
37
|
+
// 2. 系统环境变量
|
|
38
|
+
if (process.env.AICONDUCTOR_API_KEY) return process.env.AICONDUCTOR_API_KEY;
|
|
39
|
+
|
|
40
|
+
// 3. 当前目录 .env 文件
|
|
41
|
+
try {
|
|
42
|
+
const envPath = path.join(process.cwd(), ".env");
|
|
43
|
+
if (fs.existsSync(envPath)) {
|
|
44
|
+
const content = fs.readFileSync(envPath, "utf-8");
|
|
45
|
+
const match = content.match(/^AICONDUCTOR_API_KEY\s*=\s*(.+)$/m);
|
|
46
|
+
if (match) return match[1].trim();
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
// .env 不可读,忽略
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 过滤响应中的计费信息
|
|
57
|
+
* @param {object} resp
|
|
58
|
+
* @returns {object}
|
|
59
|
+
*/
|
|
60
|
+
function stripBilling(resp) {
|
|
61
|
+
if (!resp || typeof resp !== "object") return resp;
|
|
62
|
+
const result = { ...resp };
|
|
63
|
+
delete result.billing;
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 校验整数参数,非有效正整数时报错
|
|
69
|
+
* @param {*} value
|
|
70
|
+
* @param {string} name
|
|
71
|
+
*/
|
|
68
72
|
function validateInt(value, name) {
|
|
69
|
-
if (value === undefined || value === null) return;
|
|
70
|
-
const n = Number(value);
|
|
71
|
-
if (!Number.isInteger(n) || n < 0) {
|
|
72
|
-
throw Object.assign(new Error(`${name} 须为非负整数,收到: ${value}`), { type: "validation_error" });
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* HTTP POST 请求封装
|
|
78
|
-
* @param {string} host
|
|
79
|
-
* @param {string} path
|
|
80
|
-
* @param {object} body
|
|
73
|
+
if (value === undefined || value === null) return;
|
|
74
|
+
const n = Number(value);
|
|
75
|
+
if (!Number.isInteger(n) || n < 0) {
|
|
76
|
+
throw Object.assign(new Error(`${name} 须为非负整数,收到: ${value}`), { type: "validation_error" });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* HTTP POST 请求封装
|
|
82
|
+
* @param {string} host
|
|
83
|
+
* @param {string} path
|
|
84
|
+
* @param {object} body
|
|
81
85
|
* @param {typeof http|typeof https} [transport]
|
|
82
86
|
* @returns {Promise<object>}
|
|
83
87
|
*/
|
|
@@ -95,36 +99,36 @@ function httpPost(host, requestPath, body, headers = {}, transport = https) {
|
|
|
95
99
|
...headers,
|
|
96
100
|
},
|
|
97
101
|
};
|
|
98
|
-
|
|
102
|
+
|
|
99
103
|
const req = transport.request(options, (res) => {
|
|
100
|
-
let chunks = [];
|
|
101
|
-
res.on("data", (chunk) => chunks.push(chunk));
|
|
102
|
-
res.on("end", () => {
|
|
103
|
-
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
104
|
-
let parsed;
|
|
105
|
-
try {
|
|
106
|
-
parsed = JSON.parse(raw);
|
|
107
|
-
} catch
|
|
108
|
-
parsed = { success: false, message: raw };
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (res.statusCode >= 400) {
|
|
112
|
-
reject({
|
|
113
|
-
type: "http_error",
|
|
114
|
-
statusCode: res.statusCode,
|
|
115
|
-
body: parsed,
|
|
116
|
-
});
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
resolve(parsed);
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
req.on("error", (err) => {
|
|
125
|
-
reject({ type: "network_error", message: err.message });
|
|
126
|
-
});
|
|
127
|
-
|
|
104
|
+
let chunks = [];
|
|
105
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
106
|
+
res.on("end", () => {
|
|
107
|
+
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
108
|
+
let parsed;
|
|
109
|
+
try {
|
|
110
|
+
parsed = JSON.parse(raw);
|
|
111
|
+
} catch {
|
|
112
|
+
parsed = { success: false, message: raw };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (res.statusCode >= 400) {
|
|
116
|
+
reject({
|
|
117
|
+
type: "http_error",
|
|
118
|
+
statusCode: res.statusCode,
|
|
119
|
+
body: parsed,
|
|
120
|
+
});
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
resolve(parsed);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
req.on("error", (err) => {
|
|
129
|
+
reject({ type: "network_error", message: err.message });
|
|
130
|
+
});
|
|
131
|
+
|
|
128
132
|
req.write(data);
|
|
129
133
|
req.end();
|
|
130
134
|
});
|
|
@@ -146,6 +150,48 @@ function validatePositiveInt(value, name, max) {
|
|
|
146
150
|
return n;
|
|
147
151
|
}
|
|
148
152
|
|
|
153
|
+
/**
|
|
154
|
+
* 校验非空文本参数,去掉首尾空白
|
|
155
|
+
* @param {*} value
|
|
156
|
+
* @param {string} name
|
|
157
|
+
* @returns {string}
|
|
158
|
+
*/
|
|
159
|
+
function requireText(value, name) {
|
|
160
|
+
const text = value === undefined || value === null ? "" : String(value).trim();
|
|
161
|
+
if (!text) {
|
|
162
|
+
throw Object.assign(new Error(`缺少 ${name} 参数`), { type: "validation_error" });
|
|
163
|
+
}
|
|
164
|
+
return text;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* 把 HTTP 200 但 success=false 的业务失败转成 api_error(错误分类见 AGENTS.md)
|
|
169
|
+
* @param {object} resp
|
|
170
|
+
* @returns {object}
|
|
171
|
+
*/
|
|
172
|
+
function throwOnBusinessFailure(resp) {
|
|
173
|
+
if (resp && resp.success === false) {
|
|
174
|
+
throw Object.assign(new Error(resp.message || "接口返回失败"), { type: "api_error" });
|
|
175
|
+
}
|
|
176
|
+
return resp;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* wx_history_articles 把上游失败塞在 HTTP 200 + success:true 的 data.code 里
|
|
181
|
+
* (api_docs:data.code 为 0 才算成功,平台侧并不改写成 success:false)。
|
|
182
|
+
* 不认这个码,就会把「上游挂了」当成「这个号没有文章」。
|
|
183
|
+
* @param {object} resp
|
|
184
|
+
* @returns {object}
|
|
185
|
+
*/
|
|
186
|
+
function throwOnWxHistoryFailure(resp) {
|
|
187
|
+
const inner = resp && resp.data;
|
|
188
|
+
if (inner && typeof inner.code === "number" && inner.code !== 0) {
|
|
189
|
+
const detail = resp.message || inner.msg || "上游返回失败";
|
|
190
|
+
throw Object.assign(new Error(`${detail}(上游错误码 ${inner.code})`), { type: "api_error" });
|
|
191
|
+
}
|
|
192
|
+
return resp;
|
|
193
|
+
}
|
|
194
|
+
|
|
149
195
|
function contentTypeForFilename(filename) {
|
|
150
196
|
const types = {
|
|
151
197
|
".avif": "image/avif",
|
|
@@ -257,26 +303,26 @@ async function r2Upload(apiKey, params) {
|
|
|
257
303
|
}
|
|
258
304
|
return stripBilling(resp);
|
|
259
305
|
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* 发布 HTML 到线上
|
|
263
|
-
* @param {string} apiKey
|
|
264
|
-
* @param {string} htmlCode
|
|
265
|
-
* @param {string} [title]
|
|
266
|
-
* @returns {Promise<{success: boolean, message: string, online_url: string}>}
|
|
267
|
-
*/
|
|
268
|
-
async function publishHtml(apiKey, htmlCode, title) {
|
|
269
|
-
const reqBody = {
|
|
270
|
-
api_key: apiKey,
|
|
271
|
-
html_code: htmlCode,
|
|
272
|
-
};
|
|
273
|
-
if (title) reqBody.title = title;
|
|
274
|
-
|
|
275
|
-
const resp = await httpPost(API_HOST, API_PATH, reqBody);
|
|
276
|
-
return stripBilling(resp);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* 发布 HTML 到线上
|
|
309
|
+
* @param {string} apiKey
|
|
310
|
+
* @param {string} htmlCode
|
|
311
|
+
* @param {string} [title]
|
|
312
|
+
* @returns {Promise<{success: boolean, message: string, online_url: string}>}
|
|
313
|
+
*/
|
|
314
|
+
async function publishHtml(apiKey, htmlCode, title) {
|
|
315
|
+
const reqBody = {
|
|
316
|
+
api_key: apiKey,
|
|
317
|
+
html_code: htmlCode,
|
|
318
|
+
};
|
|
319
|
+
if (title) reqBody.title = title;
|
|
320
|
+
|
|
321
|
+
const resp = await httpPost(API_HOST, API_PATH, reqBody);
|
|
322
|
+
return stripBilling(resp);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
280
326
|
* 豆包网页/图片搜索
|
|
281
327
|
* @param {string} apiKey
|
|
282
328
|
* @param {{ query: string, searchType: "web"|"image", count?: number, queryRewrite?: boolean, needContent?: boolean, needUrl?: boolean, sites?: string, blockHosts?: string, authInfoLevel?: number, timeRange?: string, contentFormats?: string, industry?: string, imageWidthMin?: number, imageWidthMax?: number, imageHeightMin?: number, imageHeightMax?: number, imageShapes?: string[]|string }} params
|
|
@@ -333,7 +379,7 @@ async function webSearch(apiKey, params) {
|
|
|
333
379
|
if (imageHeightMax !== undefined) reqBody.image_height_max = imageHeightMax;
|
|
334
380
|
if (params.imageShapes !== undefined) reqBody.image_shapes = params.imageShapes;
|
|
335
381
|
}
|
|
336
|
-
|
|
382
|
+
|
|
337
383
|
const resp = await httpPost(API_HOST, SEARCH_API_PATH, reqBody, {
|
|
338
384
|
Authorization: `Bearer ${apiKey}`,
|
|
339
385
|
});
|
|
@@ -397,25 +443,25 @@ async function xhsHotList(apiKey) {
|
|
|
397
443
|
}
|
|
398
444
|
return result;
|
|
399
445
|
}
|
|
400
|
-
|
|
401
|
-
/**
|
|
402
|
-
* Markdown 转 HTML 并发布
|
|
403
|
-
* @param {string} apiKey
|
|
404
|
-
* @param {string} mdText
|
|
405
|
-
* @param {string} [filename]
|
|
406
|
-
* @param {boolean} [addCopyButton=true]
|
|
407
|
-
* @returns {Promise<{success: boolean, message: string, file_url: string, online_url: string}>}
|
|
408
|
-
*/
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Markdown 转 HTML 并发布
|
|
449
|
+
* @param {string} apiKey
|
|
450
|
+
* @param {string} mdText
|
|
451
|
+
* @param {string} [filename]
|
|
452
|
+
* @param {boolean} [addCopyButton=true]
|
|
453
|
+
* @returns {Promise<{success: boolean, message: string, file_url: string, online_url: string}>}
|
|
454
|
+
*/
|
|
409
455
|
async function publishMdToHtml(apiKey, mdText, filename, addCopyButton = false) {
|
|
410
|
-
const reqBody = {
|
|
411
|
-
api_key: apiKey,
|
|
412
|
-
md_text: mdText,
|
|
413
|
-
add_copy_button: addCopyButton,
|
|
414
|
-
};
|
|
415
|
-
if (filename) reqBody.filename = filename;
|
|
416
|
-
|
|
417
|
-
const resp = await httpPost(API_HOST, MD_TO_HTML_API_PATH, reqBody);
|
|
418
|
-
|
|
456
|
+
const reqBody = {
|
|
457
|
+
api_key: apiKey,
|
|
458
|
+
md_text: mdText,
|
|
459
|
+
add_copy_button: addCopyButton,
|
|
460
|
+
};
|
|
461
|
+
if (filename) reqBody.filename = filename;
|
|
462
|
+
|
|
463
|
+
const resp = await httpPost(API_HOST, MD_TO_HTML_API_PATH, reqBody);
|
|
464
|
+
|
|
419
465
|
return stripBilling(resp);
|
|
420
466
|
}
|
|
421
467
|
|
|
@@ -454,139 +500,185 @@ async function deletePublishedWebpage(apiKey, webpageId) {
|
|
|
454
500
|
|
|
455
501
|
return stripBilling(resp);
|
|
456
502
|
}
|
|
457
|
-
|
|
458
|
-
/**
|
|
459
|
-
* 获取抖音作品详情
|
|
460
|
-
* @param {string} apiKey
|
|
461
|
-
* @param {string} shareUrl
|
|
462
|
-
* @returns {Promise<object>}
|
|
463
|
-
*/
|
|
464
|
-
async function douyinVideoInfo(apiKey, shareUrl) {
|
|
465
|
-
const resp = await httpPost(API_HOST, DOUYIN_VIDEO_INFO_PATH, {
|
|
466
|
-
api_key: apiKey,
|
|
467
|
-
share_url: shareUrl,
|
|
468
|
-
});
|
|
469
|
-
return stripBilling(resp);
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
/**
|
|
473
|
-
* 获取抖音作品评论
|
|
474
|
-
* @param {string} apiKey
|
|
475
|
-
* @param {string} videoUrl
|
|
476
|
-
* @param {number} [maxCount=50]
|
|
477
|
-
* @returns {Promise<object>}
|
|
478
|
-
*/
|
|
479
|
-
async function douyinVideoComments(apiKey, videoUrl, maxCount) {
|
|
480
|
-
const body = { api_key: apiKey, video_url: videoUrl };
|
|
481
|
-
if (maxCount !== undefined && maxCount !== null) body.max_count = maxCount;
|
|
482
|
-
const resp = await httpPost(API_HOST, DOUYIN_VIDEO_COMMENTS_PATH, body);
|
|
483
|
-
return stripBilling(resp);
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
/**
|
|
487
|
-
* 获取抖音用户视频列表
|
|
488
|
-
* @param {string} apiKey
|
|
489
|
-
* @param {string} homePage
|
|
490
|
-
* @param {number} [maxSeconds=50]
|
|
491
|
-
* @param {number} [maxPages=1]
|
|
492
|
-
* @returns {Promise<object>}
|
|
493
|
-
*/
|
|
494
|
-
async function douyinUserVideos(apiKey, homePage, maxSeconds, maxPages) {
|
|
495
|
-
const body = { api_key: apiKey, home_page: homePage };
|
|
496
|
-
if (maxSeconds !== undefined && maxSeconds !== null) body.max_seconds = maxSeconds;
|
|
497
|
-
if (maxPages !== undefined && maxPages !== null) body.max_pages = maxPages;
|
|
498
|
-
const resp = await httpPost(API_HOST, DOUYIN_USER_VIDEOS_PATH, body);
|
|
499
|
-
return stripBilling(resp);
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
/**
|
|
503
|
-
* 获取抖音用户详情
|
|
504
|
-
* @param {string} apiKey
|
|
505
|
-
* @param {string} homePage
|
|
506
|
-
* @returns {Promise<object>}
|
|
507
|
-
*/
|
|
508
|
-
async function douyinUserDetail(apiKey, homePage) {
|
|
509
|
-
const resp = await httpPost(API_HOST, DOUYIN_USER_DETAIL_PATH, {
|
|
510
|
-
api_key: apiKey,
|
|
511
|
-
home_page: homePage,
|
|
512
|
-
});
|
|
513
|
-
return stripBilling(resp);
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
/**
|
|
517
|
-
* 搜索抖音用户
|
|
518
|
-
* @param {string} apiKey
|
|
519
|
-
* @param {string} keyword
|
|
520
|
-
* @param {object} [opts]
|
|
521
|
-
* @returns {Promise<object>}
|
|
522
|
-
*/
|
|
523
|
-
async function douyinUserSearch(apiKey, keyword, opts = {}) {
|
|
524
|
-
const body = { api_key: apiKey, keyword };
|
|
525
|
-
if (opts.maxPages !== undefined && opts.maxPages !== null) body.max_pages = opts.maxPages;
|
|
526
|
-
if (opts.maxSeconds !== undefined && opts.maxSeconds !== null) body.max_seconds = opts.maxSeconds;
|
|
527
|
-
if (opts.fansRange) body.douyin_user_fans = opts.fansRange;
|
|
528
|
-
if (opts.userType) body.douyin_user_type = opts.userType;
|
|
529
|
-
const resp = await httpPost(API_HOST, DOUYIN_USER_SEARCH_PATH, body);
|
|
530
|
-
return stripBilling(resp);
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
/**
|
|
534
|
-
* 搜索抖音视频
|
|
535
|
-
* @param {string} apiKey
|
|
536
|
-
* @param {string} keyword
|
|
537
|
-
* @param {object} [opts]
|
|
538
|
-
* @returns {Promise<object>}
|
|
539
|
-
*/
|
|
540
|
-
async function douyinVideoSearch(apiKey, keyword, opts = {}) {
|
|
541
|
-
const body = { keyword };
|
|
542
|
-
if (opts.sortType !== undefined) body.sort_type = opts.sortType;
|
|
543
|
-
if (opts.publishTime !== undefined) body.publish_time = opts.publishTime;
|
|
544
|
-
if (opts.filterDuration !== undefined) body.filter_duration = opts.filterDuration;
|
|
545
|
-
if (opts.contentType !== undefined) body.content_type = opts.contentType;
|
|
546
|
-
if (opts.maxPages !== undefined && opts.maxPages !== null) body.max_pages = opts.maxPages;
|
|
547
|
-
if (opts.maxSeconds !== undefined && opts.maxSeconds !== null) body.max_seconds = opts.maxSeconds;
|
|
548
|
-
const resp = await httpPost(API_HOST, DOUYIN_VIDEO_SEARCH_PATH, body, {
|
|
549
|
-
Authorization: `Bearer ${apiKey}`,
|
|
550
|
-
});
|
|
551
|
-
return stripBilling(resp);
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
/**
|
|
555
|
-
* 抖音作品链接转文字
|
|
556
|
-
* @param {string} apiKey
|
|
557
|
-
* @param {string} videoUrl
|
|
558
|
-
* @returns {Promise<object>}
|
|
559
|
-
*/
|
|
560
|
-
async function douyinLinkToText(apiKey, videoUrl) {
|
|
561
|
-
const resp = await httpPost(API_HOST, DOUYIN_LINK_TO_TEXT_PATH, {
|
|
562
|
-
api_key: apiKey,
|
|
563
|
-
video_url: videoUrl,
|
|
564
|
-
});
|
|
565
|
-
return stripBilling(resp);
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
/**
|
|
569
|
-
*
|
|
570
|
-
* @param {string} apiKey
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* 获取抖音作品详情
|
|
506
|
+
* @param {string} apiKey
|
|
507
|
+
* @param {string} shareUrl
|
|
508
|
+
* @returns {Promise<object>}
|
|
509
|
+
*/
|
|
510
|
+
async function douyinVideoInfo(apiKey, shareUrl) {
|
|
511
|
+
const resp = await httpPost(API_HOST, DOUYIN_VIDEO_INFO_PATH, {
|
|
512
|
+
api_key: apiKey,
|
|
513
|
+
share_url: shareUrl,
|
|
514
|
+
});
|
|
515
|
+
return stripBilling(resp);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* 获取抖音作品评论
|
|
520
|
+
* @param {string} apiKey
|
|
521
|
+
* @param {string} videoUrl
|
|
522
|
+
* @param {number} [maxCount=50]
|
|
523
|
+
* @returns {Promise<object>}
|
|
524
|
+
*/
|
|
525
|
+
async function douyinVideoComments(apiKey, videoUrl, maxCount) {
|
|
526
|
+
const body = { api_key: apiKey, video_url: videoUrl };
|
|
527
|
+
if (maxCount !== undefined && maxCount !== null) body.max_count = maxCount;
|
|
528
|
+
const resp = await httpPost(API_HOST, DOUYIN_VIDEO_COMMENTS_PATH, body);
|
|
529
|
+
return stripBilling(resp);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* 获取抖音用户视频列表
|
|
534
|
+
* @param {string} apiKey
|
|
535
|
+
* @param {string} homePage
|
|
536
|
+
* @param {number} [maxSeconds=50]
|
|
537
|
+
* @param {number} [maxPages=1]
|
|
538
|
+
* @returns {Promise<object>}
|
|
539
|
+
*/
|
|
540
|
+
async function douyinUserVideos(apiKey, homePage, maxSeconds, maxPages) {
|
|
541
|
+
const body = { api_key: apiKey, home_page: homePage };
|
|
542
|
+
if (maxSeconds !== undefined && maxSeconds !== null) body.max_seconds = maxSeconds;
|
|
543
|
+
if (maxPages !== undefined && maxPages !== null) body.max_pages = maxPages;
|
|
544
|
+
const resp = await httpPost(API_HOST, DOUYIN_USER_VIDEOS_PATH, body);
|
|
545
|
+
return stripBilling(resp);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* 获取抖音用户详情
|
|
550
|
+
* @param {string} apiKey
|
|
551
|
+
* @param {string} homePage
|
|
552
|
+
* @returns {Promise<object>}
|
|
553
|
+
*/
|
|
554
|
+
async function douyinUserDetail(apiKey, homePage) {
|
|
555
|
+
const resp = await httpPost(API_HOST, DOUYIN_USER_DETAIL_PATH, {
|
|
556
|
+
api_key: apiKey,
|
|
557
|
+
home_page: homePage,
|
|
558
|
+
});
|
|
559
|
+
return stripBilling(resp);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* 搜索抖音用户
|
|
564
|
+
* @param {string} apiKey
|
|
565
|
+
* @param {string} keyword
|
|
566
|
+
* @param {object} [opts]
|
|
567
|
+
* @returns {Promise<object>}
|
|
568
|
+
*/
|
|
569
|
+
async function douyinUserSearch(apiKey, keyword, opts = {}) {
|
|
570
|
+
const body = { api_key: apiKey, keyword };
|
|
571
|
+
if (opts.maxPages !== undefined && opts.maxPages !== null) body.max_pages = opts.maxPages;
|
|
572
|
+
if (opts.maxSeconds !== undefined && opts.maxSeconds !== null) body.max_seconds = opts.maxSeconds;
|
|
573
|
+
if (opts.fansRange) body.douyin_user_fans = opts.fansRange;
|
|
574
|
+
if (opts.userType) body.douyin_user_type = opts.userType;
|
|
575
|
+
const resp = await httpPost(API_HOST, DOUYIN_USER_SEARCH_PATH, body);
|
|
576
|
+
return stripBilling(resp);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* 搜索抖音视频
|
|
581
|
+
* @param {string} apiKey
|
|
582
|
+
* @param {string} keyword
|
|
583
|
+
* @param {object} [opts]
|
|
584
|
+
* @returns {Promise<object>}
|
|
585
|
+
*/
|
|
586
|
+
async function douyinVideoSearch(apiKey, keyword, opts = {}) {
|
|
587
|
+
const body = { keyword };
|
|
588
|
+
if (opts.sortType !== undefined) body.sort_type = opts.sortType;
|
|
589
|
+
if (opts.publishTime !== undefined) body.publish_time = opts.publishTime;
|
|
590
|
+
if (opts.filterDuration !== undefined) body.filter_duration = opts.filterDuration;
|
|
591
|
+
if (opts.contentType !== undefined) body.content_type = opts.contentType;
|
|
592
|
+
if (opts.maxPages !== undefined && opts.maxPages !== null) body.max_pages = opts.maxPages;
|
|
593
|
+
if (opts.maxSeconds !== undefined && opts.maxSeconds !== null) body.max_seconds = opts.maxSeconds;
|
|
594
|
+
const resp = await httpPost(API_HOST, DOUYIN_VIDEO_SEARCH_PATH, body, {
|
|
595
|
+
Authorization: `Bearer ${apiKey}`,
|
|
596
|
+
});
|
|
597
|
+
return stripBilling(resp);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* 抖音作品链接转文字
|
|
602
|
+
* @param {string} apiKey
|
|
603
|
+
* @param {string} videoUrl
|
|
604
|
+
* @returns {Promise<object>}
|
|
605
|
+
*/
|
|
606
|
+
async function douyinLinkToText(apiKey, videoUrl) {
|
|
607
|
+
const resp = await httpPost(API_HOST, DOUYIN_LINK_TO_TEXT_PATH, {
|
|
608
|
+
api_key: apiKey,
|
|
609
|
+
video_url: videoUrl,
|
|
610
|
+
});
|
|
611
|
+
return stripBilling(resp);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* 获取微信公众号文章互动数据(按次计费)
|
|
616
|
+
* @param {string} apiKey
|
|
617
|
+
* @param {string} url 微信文章链接
|
|
618
|
+
* @returns {Promise<object>}
|
|
619
|
+
*/
|
|
620
|
+
async function wxArticleData(apiKey, url) {
|
|
621
|
+
const resp = await httpPost(API_HOST, WX_ARTICLE_DATA_PATH, {
|
|
622
|
+
url: requireText(url, "url(微信文章链接)"),
|
|
623
|
+
}, {
|
|
624
|
+
Authorization: `Bearer ${apiKey}`,
|
|
625
|
+
});
|
|
626
|
+
return throwOnBusinessFailure(stripBilling(resp));
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* 获取微信公众号历史文章列表(按成功翻页次数计费)
|
|
631
|
+
* @param {string} apiKey
|
|
632
|
+
* @param {{ url?: string, name?: string, maxPages?: number|string, maxSeconds?: number|string }} params
|
|
633
|
+
* @returns {Promise<object>}
|
|
634
|
+
*/
|
|
635
|
+
async function wxHistoryArticles(apiKey, params = {}) {
|
|
636
|
+
const body = {};
|
|
637
|
+
const url = params.url === undefined || params.url === null ? "" : String(params.url).trim();
|
|
638
|
+
const name = params.name === undefined || params.name === null ? "" : String(params.name).trim();
|
|
639
|
+
if (!url && !name) {
|
|
640
|
+
throw Object.assign(
|
|
641
|
+
new Error("url 与 name 至少提供一个(公众号名称,或该公众号任意一篇文章链接)"),
|
|
642
|
+
{ type: "validation_error" }
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
if (url) body.url = url;
|
|
646
|
+
if (name) body.name = name;
|
|
647
|
+
if (params.maxPages !== undefined && params.maxPages !== null) {
|
|
648
|
+
body.max_pages = validatePositiveInt(params.maxPages, "max_pages");
|
|
649
|
+
}
|
|
650
|
+
if (params.maxSeconds !== undefined && params.maxSeconds !== null) {
|
|
651
|
+
body.max_seconds = validatePositiveInt(params.maxSeconds, "max_seconds", WX_HISTORY_MAX_SECONDS);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const resp = await httpPost(API_HOST, WX_HISTORY_ARTICLES_PATH, body, {
|
|
655
|
+
Authorization: `Bearer ${apiKey}`,
|
|
656
|
+
});
|
|
657
|
+
return throwOnWxHistoryFailure(throwOnBusinessFailure(stripBilling(resp)));
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* 小红书笔记发布 — 生成扫码发布 H5 页面与二维码
|
|
662
|
+
* @param {string} apiKey
|
|
571
663
|
* @param {{ type: string, title?: string, content?: string, images?: string[], video?: string, cover?: string }} params
|
|
572
|
-
* @returns {Promise<object>}
|
|
573
|
-
*/
|
|
574
|
-
async function xhsNotePublish(apiKey, params) {
|
|
575
|
-
const body = { type: params.type };
|
|
576
|
-
if (params.title) body.title = params.title;
|
|
577
|
-
if (params.content) body.content = params.content;
|
|
664
|
+
* @returns {Promise<object>}
|
|
665
|
+
*/
|
|
666
|
+
async function xhsNotePublish(apiKey, params) {
|
|
667
|
+
const body = { type: params.type };
|
|
668
|
+
if (params.title) body.title = params.title;
|
|
669
|
+
if (params.content) body.content = params.content;
|
|
578
670
|
if (params.images && params.images.length > 0) body.images = params.images;
|
|
579
671
|
if (params.video) body.video = params.video;
|
|
580
672
|
if (params.cover) body.cover = params.cover;
|
|
581
|
-
|
|
582
|
-
const resp = await httpPost(API_HOST, XHS_NOTE_PUBLISH_PATH, body, {
|
|
583
|
-
Authorization: `Bearer ${apiKey}`,
|
|
584
|
-
});
|
|
585
|
-
return stripBilling(resp);
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
module.exports = {
|
|
589
|
-
resolveApiKey,
|
|
673
|
+
|
|
674
|
+
const resp = await httpPost(API_HOST, XHS_NOTE_PUBLISH_PATH, body, {
|
|
675
|
+
Authorization: `Bearer ${apiKey}`,
|
|
676
|
+
});
|
|
677
|
+
return stripBilling(resp);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
module.exports = {
|
|
681
|
+
resolveApiKey,
|
|
590
682
|
publishHtml,
|
|
591
683
|
publishMdToHtml,
|
|
592
684
|
getPublishedWebpages,
|
|
@@ -594,13 +686,15 @@ module.exports = {
|
|
|
594
686
|
webSearch,
|
|
595
687
|
hotList,
|
|
596
688
|
xhsHotList,
|
|
597
|
-
douyinVideoInfo,
|
|
598
|
-
douyinVideoComments,
|
|
599
|
-
douyinUserVideos,
|
|
600
|
-
douyinUserDetail,
|
|
601
|
-
douyinUserSearch,
|
|
689
|
+
douyinVideoInfo,
|
|
690
|
+
douyinVideoComments,
|
|
691
|
+
douyinUserVideos,
|
|
692
|
+
douyinUserDetail,
|
|
693
|
+
douyinUserSearch,
|
|
602
694
|
douyinVideoSearch,
|
|
603
695
|
douyinLinkToText,
|
|
604
696
|
r2Upload,
|
|
605
697
|
xhsNotePublish,
|
|
698
|
+
wxArticleData,
|
|
699
|
+
wxHistoryArticles,
|
|
606
700
|
};
|