aiconductor-cli 1.3.5 → 1.5.0

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,82 +1,87 @@
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";
10
10
  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
- const DOUYIN_VIDEO_INFO_PATH = "/api/douyin_video_info";
14
- const DOUYIN_VIDEO_COMMENTS_PATH = "/api/douyin_video_comments";
15
- const DOUYIN_USER_VIDEOS_PATH = "/api/douyin_user_videos";
16
- const DOUYIN_USER_DETAIL_PATH = "/api/douyin_user_detail";
17
- const DOUYIN_USER_SEARCH_PATH = "/api/douyin_user_search";
18
- const DOUYIN_VIDEO_SEARCH_PATH = "/api/douyin_video_search";
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";
19
20
  const DOUYIN_LINK_TO_TEXT_PATH = "/api/douyin_link_to_text_v2";
20
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";
21
24
  const R2_UPLOADER_PATH = "/api/r2_uploader";
22
25
  const MAX_R2_FILE_SIZE = 100 * 1024 * 1024;
23
-
24
- /**
25
- * 解析 API Key,三级优先级:命令行参数 > 系统环境变量 > .env 文件
26
- * @returns {string|null}
27
- */
28
- function resolveApiKey(optionKey) {
29
- // 1. 命令行参数
30
- if (optionKey) return optionKey;
31
-
32
- // 2. 系统环境变量
33
- if (process.env.AICONDUCTOR_API_KEY) return process.env.AICONDUCTOR_API_KEY;
34
-
35
- // 3. 当前目录 .env 文件
36
- try {
37
- const envPath = path.join(process.cwd(), ".env");
38
- if (fs.existsSync(envPath)) {
39
- const content = fs.readFileSync(envPath, "utf-8");
40
- const match = content.match(/^AICONDUCTOR_API_KEY\s*=\s*(.+)$/m);
41
- if (match) return match[1].trim();
42
- }
43
- } catch (_) {
44
- // .env 不可读,忽略
45
- }
46
-
47
- return null;
48
- }
49
-
50
- /**
51
- * 过滤响应中的计费信息
52
- * @param {object} resp
53
- * @returns {object}
54
- */
55
- function stripBilling(resp) {
56
- if (!resp || typeof resp !== "object") return resp;
57
- const result = { ...resp };
58
- delete result.billing;
59
- return result;
60
- }
61
-
62
- /**
63
- * 校验整数参数,非有效正整数时报错
64
- * @param {*} value
65
- * @param {string} name
66
- */
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
+ */
67
72
  function validateInt(value, name) {
68
- if (value === undefined || value === null) return;
69
- const n = Number(value);
70
- if (!Number.isInteger(n) || n < 0) {
71
- throw Object.assign(new Error(`${name} 须为非负整数,收到: ${value}`), { type: "validation_error" });
72
- }
73
- }
74
-
75
- /**
76
- * HTTP POST 请求封装
77
- * @param {string} host
78
- * @param {string} path
79
- * @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
80
85
  * @param {typeof http|typeof https} [transport]
81
86
  * @returns {Promise<object>}
82
87
  */
@@ -94,36 +99,36 @@ function httpPost(host, requestPath, body, headers = {}, transport = https) {
94
99
  ...headers,
95
100
  },
96
101
  };
97
-
102
+
98
103
  const req = transport.request(options, (res) => {
99
- let chunks = [];
100
- res.on("data", (chunk) => chunks.push(chunk));
101
- res.on("end", () => {
102
- const raw = Buffer.concat(chunks).toString("utf-8");
103
- let parsed;
104
- try {
105
- parsed = JSON.parse(raw);
106
- } catch (_) {
107
- parsed = { success: false, message: raw };
108
- }
109
-
110
- if (res.statusCode >= 400) {
111
- reject({
112
- type: "http_error",
113
- statusCode: res.statusCode,
114
- body: parsed,
115
- });
116
- return;
117
- }
118
-
119
- resolve(parsed);
120
- });
121
- });
122
-
123
- req.on("error", (err) => {
124
- reject({ type: "network_error", message: err.message });
125
- });
126
-
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
+
127
132
  req.write(data);
128
133
  req.end();
129
134
  });
@@ -145,6 +150,32 @@ function validatePositiveInt(value, name, max) {
145
150
  return n;
146
151
  }
147
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
+
148
179
  function contentTypeForFilename(filename) {
149
180
  const types = {
150
181
  ".avif": "image/avif",
@@ -256,26 +287,26 @@ async function r2Upload(apiKey, params) {
256
287
  }
257
288
  return stripBilling(resp);
258
289
  }
259
-
260
- /**
261
- * 发布 HTML 到线上
262
- * @param {string} apiKey
263
- * @param {string} htmlCode
264
- * @param {string} [title]
265
- * @returns {Promise<{success: boolean, message: string, online_url: string}>}
266
- */
267
- async function publishHtml(apiKey, htmlCode, title) {
268
- const reqBody = {
269
- api_key: apiKey,
270
- html_code: htmlCode,
271
- };
272
- if (title) reqBody.title = title;
273
-
274
- const resp = await httpPost(API_HOST, API_PATH, reqBody);
275
- return stripBilling(resp);
276
- }
277
-
278
- /**
290
+
291
+ /**
292
+ * 发布 HTML 到线上
293
+ * @param {string} apiKey
294
+ * @param {string} htmlCode
295
+ * @param {string} [title]
296
+ * @returns {Promise<{success: boolean, message: string, online_url: string}>}
297
+ */
298
+ async function publishHtml(apiKey, htmlCode, title) {
299
+ const reqBody = {
300
+ api_key: apiKey,
301
+ html_code: htmlCode,
302
+ };
303
+ if (title) reqBody.title = title;
304
+
305
+ const resp = await httpPost(API_HOST, API_PATH, reqBody);
306
+ return stripBilling(resp);
307
+ }
308
+
309
+ /**
279
310
  * 豆包网页/图片搜索
280
311
  * @param {string} apiKey
281
312
  * @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
@@ -332,7 +363,7 @@ async function webSearch(apiKey, params) {
332
363
  if (imageHeightMax !== undefined) reqBody.image_height_max = imageHeightMax;
333
364
  if (params.imageShapes !== undefined) reqBody.image_shapes = params.imageShapes;
334
365
  }
335
-
366
+
336
367
  const resp = await httpPost(API_HOST, SEARCH_API_PATH, reqBody, {
337
368
  Authorization: `Bearer ${apiKey}`,
338
369
  });
@@ -380,25 +411,41 @@ async function hotList(apiKey, params) {
380
411
  }
381
412
  return result;
382
413
  }
383
-
384
- /**
385
- * Markdown 转 HTML 并发布
386
- * @param {string} apiKey
387
- * @param {string} mdText
388
- * @param {string} [filename]
389
- * @param {boolean} [addCopyButton=true]
390
- * @returns {Promise<{success: boolean, message: string, file_url: string, online_url: string}>}
391
- */
414
+
415
+ /**
416
+ * 获取小红书实时热榜。
417
+ * @param {string} apiKey
418
+ * @returns {Promise<object>}
419
+ */
420
+ async function xhsHotList(apiKey) {
421
+ const resp = await httpPost(API_HOST, XHS_HOT_LIST_API_PATH, {}, {
422
+ Authorization: `Bearer ${apiKey}`,
423
+ }, http);
424
+ const result = stripBilling(resp);
425
+ if (result?.success === false) {
426
+ throw Object.assign(new Error(result.message || "小红书热榜接口返回失败"), { type: "api_error" });
427
+ }
428
+ return result;
429
+ }
430
+
431
+ /**
432
+ * Markdown 转 HTML 并发布
433
+ * @param {string} apiKey
434
+ * @param {string} mdText
435
+ * @param {string} [filename]
436
+ * @param {boolean} [addCopyButton=true]
437
+ * @returns {Promise<{success: boolean, message: string, file_url: string, online_url: string}>}
438
+ */
392
439
  async function publishMdToHtml(apiKey, mdText, filename, addCopyButton = false) {
393
- const reqBody = {
394
- api_key: apiKey,
395
- md_text: mdText,
396
- add_copy_button: addCopyButton,
397
- };
398
- if (filename) reqBody.filename = filename;
399
-
400
- const resp = await httpPost(API_HOST, MD_TO_HTML_API_PATH, reqBody);
401
-
440
+ const reqBody = {
441
+ api_key: apiKey,
442
+ md_text: mdText,
443
+ add_copy_button: addCopyButton,
444
+ };
445
+ if (filename) reqBody.filename = filename;
446
+
447
+ const resp = await httpPost(API_HOST, MD_TO_HTML_API_PATH, reqBody);
448
+
402
449
  return stripBilling(resp);
403
450
  }
404
451
 
@@ -437,152 +484,201 @@ async function deletePublishedWebpage(apiKey, webpageId) {
437
484
 
438
485
  return stripBilling(resp);
439
486
  }
440
-
441
- /**
442
- * 获取抖音作品详情
443
- * @param {string} apiKey
444
- * @param {string} shareUrl
445
- * @returns {Promise<object>}
446
- */
447
- async function douyinVideoInfo(apiKey, shareUrl) {
448
- const resp = await httpPost(API_HOST, DOUYIN_VIDEO_INFO_PATH, {
449
- api_key: apiKey,
450
- share_url: shareUrl,
451
- });
452
- return stripBilling(resp);
453
- }
454
-
455
- /**
456
- * 获取抖音作品评论
457
- * @param {string} apiKey
458
- * @param {string} videoUrl
459
- * @param {number} [maxCount=50]
460
- * @returns {Promise<object>}
461
- */
462
- async function douyinVideoComments(apiKey, videoUrl, maxCount) {
463
- const body = { api_key: apiKey, video_url: videoUrl };
464
- if (maxCount !== undefined && maxCount !== null) body.max_count = maxCount;
465
- const resp = await httpPost(API_HOST, DOUYIN_VIDEO_COMMENTS_PATH, body);
466
- return stripBilling(resp);
467
- }
468
-
469
- /**
470
- * 获取抖音用户视频列表
471
- * @param {string} apiKey
472
- * @param {string} homePage
473
- * @param {number} [maxSeconds=50]
474
- * @param {number} [maxPages=1]
475
- * @returns {Promise<object>}
476
- */
477
- async function douyinUserVideos(apiKey, homePage, maxSeconds, maxPages) {
478
- const body = { api_key: apiKey, home_page: homePage };
479
- if (maxSeconds !== undefined && maxSeconds !== null) body.max_seconds = maxSeconds;
480
- if (maxPages !== undefined && maxPages !== null) body.max_pages = maxPages;
481
- const resp = await httpPost(API_HOST, DOUYIN_USER_VIDEOS_PATH, body);
482
- return stripBilling(resp);
483
- }
484
-
485
- /**
486
- * 获取抖音用户详情
487
- * @param {string} apiKey
488
- * @param {string} homePage
489
- * @returns {Promise<object>}
490
- */
491
- async function douyinUserDetail(apiKey, homePage) {
492
- const resp = await httpPost(API_HOST, DOUYIN_USER_DETAIL_PATH, {
493
- api_key: apiKey,
494
- home_page: homePage,
495
- });
496
- return stripBilling(resp);
497
- }
498
-
499
- /**
500
- * 搜索抖音用户
501
- * @param {string} apiKey
502
- * @param {string} keyword
503
- * @param {object} [opts]
504
- * @returns {Promise<object>}
505
- */
506
- async function douyinUserSearch(apiKey, keyword, opts = {}) {
507
- const body = { api_key: apiKey, keyword };
508
- if (opts.maxPages !== undefined && opts.maxPages !== null) body.max_pages = opts.maxPages;
509
- if (opts.maxSeconds !== undefined && opts.maxSeconds !== null) body.max_seconds = opts.maxSeconds;
510
- if (opts.fansRange) body.douyin_user_fans = opts.fansRange;
511
- if (opts.userType) body.douyin_user_type = opts.userType;
512
- const resp = await httpPost(API_HOST, DOUYIN_USER_SEARCH_PATH, body);
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 douyinVideoSearch(apiKey, keyword, opts = {}) {
524
- const body = { keyword };
525
- if (opts.sortType !== undefined) body.sort_type = opts.sortType;
526
- if (opts.publishTime !== undefined) body.publish_time = opts.publishTime;
527
- if (opts.filterDuration !== undefined) body.filter_duration = opts.filterDuration;
528
- if (opts.contentType !== undefined) body.content_type = opts.contentType;
529
- if (opts.maxPages !== undefined && opts.maxPages !== null) body.max_pages = opts.maxPages;
530
- if (opts.maxSeconds !== undefined && opts.maxSeconds !== null) body.max_seconds = opts.maxSeconds;
531
- const resp = await httpPost(API_HOST, DOUYIN_VIDEO_SEARCH_PATH, body, {
532
- Authorization: `Bearer ${apiKey}`,
533
- });
534
- return stripBilling(resp);
535
- }
536
-
537
- /**
538
- * 抖音作品链接转文字
539
- * @param {string} apiKey
540
- * @param {string} videoUrl
541
- * @returns {Promise<object>}
542
- */
543
- async function douyinLinkToText(apiKey, videoUrl) {
544
- const resp = await httpPost(API_HOST, DOUYIN_LINK_TO_TEXT_PATH, {
545
- api_key: apiKey,
546
- video_url: videoUrl,
547
- });
548
- return stripBilling(resp);
549
- }
550
-
551
- /**
552
- * 小红书笔记发布 — 生成扫码发布 H5 页面与二维码
553
- * @param {string} apiKey
487
+
488
+ /**
489
+ * 获取抖音作品详情
490
+ * @param {string} apiKey
491
+ * @param {string} shareUrl
492
+ * @returns {Promise<object>}
493
+ */
494
+ async function douyinVideoInfo(apiKey, shareUrl) {
495
+ const resp = await httpPost(API_HOST, DOUYIN_VIDEO_INFO_PATH, {
496
+ api_key: apiKey,
497
+ share_url: shareUrl,
498
+ });
499
+ return stripBilling(resp);
500
+ }
501
+
502
+ /**
503
+ * 获取抖音作品评论
504
+ * @param {string} apiKey
505
+ * @param {string} videoUrl
506
+ * @param {number} [maxCount=50]
507
+ * @returns {Promise<object>}
508
+ */
509
+ async function douyinVideoComments(apiKey, videoUrl, maxCount) {
510
+ const body = { api_key: apiKey, video_url: videoUrl };
511
+ if (maxCount !== undefined && maxCount !== null) body.max_count = maxCount;
512
+ const resp = await httpPost(API_HOST, DOUYIN_VIDEO_COMMENTS_PATH, body);
513
+ return stripBilling(resp);
514
+ }
515
+
516
+ /**
517
+ * 获取抖音用户视频列表
518
+ * @param {string} apiKey
519
+ * @param {string} homePage
520
+ * @param {number} [maxSeconds=50]
521
+ * @param {number} [maxPages=1]
522
+ * @returns {Promise<object>}
523
+ */
524
+ async function douyinUserVideos(apiKey, homePage, maxSeconds, maxPages) {
525
+ const body = { api_key: apiKey, home_page: homePage };
526
+ if (maxSeconds !== undefined && maxSeconds !== null) body.max_seconds = maxSeconds;
527
+ if (maxPages !== undefined && maxPages !== null) body.max_pages = maxPages;
528
+ const resp = await httpPost(API_HOST, DOUYIN_USER_VIDEOS_PATH, body);
529
+ return stripBilling(resp);
530
+ }
531
+
532
+ /**
533
+ * 获取抖音用户详情
534
+ * @param {string} apiKey
535
+ * @param {string} homePage
536
+ * @returns {Promise<object>}
537
+ */
538
+ async function douyinUserDetail(apiKey, homePage) {
539
+ const resp = await httpPost(API_HOST, DOUYIN_USER_DETAIL_PATH, {
540
+ api_key: apiKey,
541
+ home_page: homePage,
542
+ });
543
+ return stripBilling(resp);
544
+ }
545
+
546
+ /**
547
+ * 搜索抖音用户
548
+ * @param {string} apiKey
549
+ * @param {string} keyword
550
+ * @param {object} [opts]
551
+ * @returns {Promise<object>}
552
+ */
553
+ async function douyinUserSearch(apiKey, keyword, opts = {}) {
554
+ const body = { api_key: apiKey, keyword };
555
+ if (opts.maxPages !== undefined && opts.maxPages !== null) body.max_pages = opts.maxPages;
556
+ if (opts.maxSeconds !== undefined && opts.maxSeconds !== null) body.max_seconds = opts.maxSeconds;
557
+ if (opts.fansRange) body.douyin_user_fans = opts.fansRange;
558
+ if (opts.userType) body.douyin_user_type = opts.userType;
559
+ const resp = await httpPost(API_HOST, DOUYIN_USER_SEARCH_PATH, body);
560
+ return stripBilling(resp);
561
+ }
562
+
563
+ /**
564
+ * 搜索抖音视频
565
+ * @param {string} apiKey
566
+ * @param {string} keyword
567
+ * @param {object} [opts]
568
+ * @returns {Promise<object>}
569
+ */
570
+ async function douyinVideoSearch(apiKey, keyword, opts = {}) {
571
+ const body = { keyword };
572
+ if (opts.sortType !== undefined) body.sort_type = opts.sortType;
573
+ if (opts.publishTime !== undefined) body.publish_time = opts.publishTime;
574
+ if (opts.filterDuration !== undefined) body.filter_duration = opts.filterDuration;
575
+ if (opts.contentType !== undefined) body.content_type = opts.contentType;
576
+ if (opts.maxPages !== undefined && opts.maxPages !== null) body.max_pages = opts.maxPages;
577
+ if (opts.maxSeconds !== undefined && opts.maxSeconds !== null) body.max_seconds = opts.maxSeconds;
578
+ const resp = await httpPost(API_HOST, DOUYIN_VIDEO_SEARCH_PATH, body, {
579
+ Authorization: `Bearer ${apiKey}`,
580
+ });
581
+ return stripBilling(resp);
582
+ }
583
+
584
+ /**
585
+ * 抖音作品链接转文字
586
+ * @param {string} apiKey
587
+ * @param {string} videoUrl
588
+ * @returns {Promise<object>}
589
+ */
590
+ async function douyinLinkToText(apiKey, videoUrl) {
591
+ const resp = await httpPost(API_HOST, DOUYIN_LINK_TO_TEXT_PATH, {
592
+ api_key: apiKey,
593
+ video_url: videoUrl,
594
+ });
595
+ return stripBilling(resp);
596
+ }
597
+
598
+ /**
599
+ * 获取微信公众号文章互动数据(按次计费)
600
+ * @param {string} apiKey
601
+ * @param {string} url 微信文章链接
602
+ * @returns {Promise<object>}
603
+ */
604
+ async function wxArticleData(apiKey, url) {
605
+ const resp = await httpPost(API_HOST, WX_ARTICLE_DATA_PATH, {
606
+ url: requireText(url, "url(微信文章链接)"),
607
+ }, {
608
+ Authorization: `Bearer ${apiKey}`,
609
+ });
610
+ return throwOnBusinessFailure(stripBilling(resp));
611
+ }
612
+
613
+ /**
614
+ * 获取微信公众号历史文章列表(按成功翻页次数计费)
615
+ * @param {string} apiKey
616
+ * @param {{ url?: string, name?: string, maxPages?: number|string, maxSeconds?: number|string }} params
617
+ * @returns {Promise<object>}
618
+ */
619
+ async function wxHistoryArticles(apiKey, params = {}) {
620
+ const body = {};
621
+ const url = params.url === undefined || params.url === null ? "" : String(params.url).trim();
622
+ const name = params.name === undefined || params.name === null ? "" : String(params.name).trim();
623
+ if (!url && !name) {
624
+ throw Object.assign(
625
+ new Error("url 与 name 至少提供一个(公众号名称,或该公众号任意一篇文章链接)"),
626
+ { type: "validation_error" }
627
+ );
628
+ }
629
+ if (url) body.url = url;
630
+ if (name) body.name = name;
631
+ if (params.maxPages !== undefined && params.maxPages !== null) {
632
+ body.max_pages = validatePositiveInt(params.maxPages, "max_pages");
633
+ }
634
+ if (params.maxSeconds !== undefined && params.maxSeconds !== null) {
635
+ body.max_seconds = validatePositiveInt(params.maxSeconds, "max_seconds", WX_HISTORY_MAX_SECONDS);
636
+ }
637
+
638
+ const resp = await httpPost(API_HOST, WX_HISTORY_ARTICLES_PATH, body, {
639
+ Authorization: `Bearer ${apiKey}`,
640
+ });
641
+ return throwOnBusinessFailure(stripBilling(resp));
642
+ }
643
+
644
+ /**
645
+ * 小红书笔记发布 — 生成扫码发布 H5 页面与二维码
646
+ * @param {string} apiKey
554
647
  * @param {{ type: string, title?: string, content?: string, images?: string[], video?: string, cover?: string }} params
555
- * @returns {Promise<object>}
556
- */
557
- async function xhsNotePublish(apiKey, params) {
558
- const body = { type: params.type };
559
- if (params.title) body.title = params.title;
560
- if (params.content) body.content = params.content;
648
+ * @returns {Promise<object>}
649
+ */
650
+ async function xhsNotePublish(apiKey, params) {
651
+ const body = { type: params.type };
652
+ if (params.title) body.title = params.title;
653
+ if (params.content) body.content = params.content;
561
654
  if (params.images && params.images.length > 0) body.images = params.images;
562
655
  if (params.video) body.video = params.video;
563
656
  if (params.cover) body.cover = params.cover;
564
-
565
- const resp = await httpPost(API_HOST, XHS_NOTE_PUBLISH_PATH, body, {
566
- Authorization: `Bearer ${apiKey}`,
567
- });
568
- return stripBilling(resp);
569
- }
570
-
571
- module.exports = {
572
- resolveApiKey,
657
+
658
+ const resp = await httpPost(API_HOST, XHS_NOTE_PUBLISH_PATH, body, {
659
+ Authorization: `Bearer ${apiKey}`,
660
+ });
661
+ return stripBilling(resp);
662
+ }
663
+
664
+ module.exports = {
665
+ resolveApiKey,
573
666
  publishHtml,
574
667
  publishMdToHtml,
575
668
  getPublishedWebpages,
576
669
  deletePublishedWebpage,
577
670
  webSearch,
578
671
  hotList,
579
- douyinVideoInfo,
580
- douyinVideoComments,
581
- douyinUserVideos,
582
- douyinUserDetail,
583
- douyinUserSearch,
672
+ xhsHotList,
673
+ douyinVideoInfo,
674
+ douyinVideoComments,
675
+ douyinUserVideos,
676
+ douyinUserDetail,
677
+ douyinUserSearch,
584
678
  douyinVideoSearch,
585
679
  douyinLinkToText,
586
680
  r2Upload,
587
681
  xhsNotePublish,
682
+ wxArticleData,
683
+ wxHistoryArticles,
588
684
  };