aiconductor-cli 1.5.1 → 1.7.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/README.md +565 -419
- package/bin/aiconductor-cli.js +401 -296
- package/package.json +1 -1
- package/src/api.js +361 -2
- package/src/api.test.js +553 -3
- package/src/commands/content.js +184 -184
- package/src/commands/douyin.js +174 -0
- package/src/commands/html_publish.js +54 -54
- package/src/commands/md_publish.js +175 -175
- package/src/commands/web.js +69 -0
- package/src/commands/web_search.js +99 -99
- package/src/commands/wx.js +116 -116
- package/src/commands/xhs.js +763 -268
- package/src/lib/cli-utils.js +172 -84
- package/src/lib/daren-score.js +91 -0
- package/src/lib/daren-score.test.js +57 -0
- package/AGENTS.md +0 -189
- package/api_docs/aic_web_search.md +0 -84
- package/api_docs/audio_converter.md +0 -51
- package/api_docs/balance_query.md +0 -38
- package/api_docs/bing_search.md +0 -55
- package/api_docs/china_stock_data.md +0 -221
- package/api_docs/delete_published_webpage.md +0 -38
- package/api_docs/doubao_search.md +0 -80
- package/api_docs/douyin_link_to_text_v1.md +0 -50
- package/api_docs/douyin_link_to_text_v2.md +0 -50
- package/api_docs/douyin_user_detail.md +0 -65
- package/api_docs/douyin_user_search.md +0 -58
- package/api_docs/douyin_user_videos.md +0 -49
- package/api_docs/douyin_user_videos_async.md +0 -45
- package/api_docs/douyin_user_videos_async_query.md +0 -41
- package/api_docs/douyin_user_videos_max.md +0 -59
- package/api_docs/douyin_video_comments.md +0 -68
- package/api_docs/douyin_video_info.md +0 -70
- package/api_docs/douyin_video_search.md +0 -60
- package/api_docs/douyin_video_search_fast.md +0 -48
- package/api_docs/fast_ocr.md +0 -51
- package/api_docs/flow_article2podcast_creat.md +0 -41
- package/api_docs/flow_article2podcast_query.md +0 -41
- package/api_docs/flow_wx_history_articles.md +0 -45
- package/api_docs/generate_image.md +0 -68
- package/api_docs/get_duration.md +0 -37
- package/api_docs/hot_list_mixed.md +0 -122
- package/api_docs/html_publish.md +0 -50
- package/api_docs/html_publish_flexible.md +0 -65
- package/api_docs/image_resize.md +0 -58
- package/api_docs/image_to_permalink.md +0 -61
- package/api_docs/link_expander.md +0 -39
- package/api_docs/llm_proxy.md +0 -61
- package/api_docs/markitdown.md +0 -47
- package/api_docs/md_to_docx.md +0 -51
- package/api_docs/md_to_html.md +0 -53
- package/api_docs/md_to_mindmap.md +0 -52
- package/api_docs/md_to_pdf.md +0 -52
- package/api_docs/md_to_png.md +0 -53
- package/api_docs/md_to_txt.md +0 -51
- package/api_docs/md_to_wxhtml.md +0 -64
- package/api_docs/md_to_xhs.md +0 -87
- package/api_docs/md_to_xlsx.md +0 -52
- package/api_docs/portrait_enhancement.md +0 -69
- package/api_docs/published_webpages.md +0 -45
- package/api_docs/r2_uploader.md +0 -51
- package/api_docs/schedulerx_job_create.md +0 -56
- package/api_docs/transaction_query.md +0 -52
- package/api_docs/url_to_markdown.md +0 -50
- package/api_docs/video_download_url.md +0 -57
- package/api_docs/video_download_url_oss.md +0 -55
- package/api_docs/video_to_text.md +0 -48
- package/api_docs/video_to_text_v2.md +0 -49
- package/api_docs/volcengine_face_pretty.md +0 -55
- package/api_docs/volcengine_faceswap.md +0 -55
- package/api_docs/website_screenshot.md +0 -49
- package/api_docs/wx_access_token.md +0 -44
- package/api_docs/wx_article_analysis_data_push.md +0 -43
- package/api_docs/wx_article_data.md +0 -48
- package/api_docs/wx_article_style_rewrite.md +0 -45
- package/api_docs/wx_draft_add.md +0 -56
- package/api_docs/wx_draft_publish.md +0 -47
- package/api_docs/wx_history_articles.md +0 -89
- package/api_docs/wx_image_upload.md +0 -46
- package/api_docs/wx_md_to_html.md +0 -48
- package/api_docs/wx_rss_reader.md +0 -59
- package/api_docs/xhs_hot_list.md +0 -48
- package/api_docs/xhs_note_comments_fast.md +0 -54
- package/api_docs/xhs_note_info_v2.md +0 -45
- package/api_docs/xhs_note_info_v3.md +0 -69
- package/api_docs/xhs_note_publish.md +0 -54
- package/api_docs/xhs_note_search_v2.md +0 -73
- package/api_docs/xhs_note_search_v3.md +0 -54
- package/api_docs/xhs_user_info_v2.md +0 -61
- package/api_docs/xhs_user_notes_v2.md +0 -56
- package/api_docs/xhs_user_notes_v3.md +0 -48
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -11,6 +11,7 @@ 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 URL_TO_MARKDOWN_API_PATH = "/api/url_to_markdown";
|
|
14
15
|
const DOUYIN_VIDEO_INFO_PATH = "/api/douyin_video_info";
|
|
15
16
|
const DOUYIN_VIDEO_COMMENTS_PATH = "/api/douyin_video_comments";
|
|
16
17
|
const DOUYIN_USER_VIDEOS_PATH = "/api/douyin_user_videos";
|
|
@@ -18,7 +19,33 @@ const DOUYIN_USER_DETAIL_PATH = "/api/douyin_user_detail";
|
|
|
18
19
|
const DOUYIN_USER_SEARCH_PATH = "/api/douyin_user_search";
|
|
19
20
|
const DOUYIN_VIDEO_SEARCH_PATH = "/api/douyin_video_search";
|
|
20
21
|
const DOUYIN_LINK_TO_TEXT_PATH = "/api/douyin_link_to_text_v2";
|
|
22
|
+
const DOUYIN_HOT_WORDS_PATH = "/api/douyin_hot_words";
|
|
23
|
+
const DOUYIN_HOT_TOPICS_PATH = "/api/douyin_hot_topics";
|
|
24
|
+
const DOUYIN_RISING_TOPICS_PATH = "/api/douyin_rising_topics";
|
|
25
|
+
const DOUYIN_VIDEO_ORIGINAL_PATH = "/api/douyin_video_original";
|
|
21
26
|
const XHS_NOTE_PUBLISH_PATH = "/api/xhs_note_publish";
|
|
27
|
+
const XHS_NOTE_INFO_PATH = "/api/xhs_note_info_v3";
|
|
28
|
+
const XHS_NOTE_COMMENTS_PATH = "/api/xhs_note_comments_fast";
|
|
29
|
+
const XHS_NOTE_SEARCH_PATH = "/api/xhs_note_search_v3";
|
|
30
|
+
const XHS_USER_INFO_PATH = "/api/xhs_user_info_v2";
|
|
31
|
+
// 刻意打 v2:v3 已于 2026-11-05 迁到同一上游且同输出,但 v2 是历史真实流量所在(backend/api_docs 两篇一致)。
|
|
32
|
+
const XHS_USER_NOTES_PATH = "/api/xhs_user_notes_v2";
|
|
33
|
+
const XHS_PGY_BLOGGER_PATH = "/api/xhs_pgy_blogger";
|
|
34
|
+
const XHS_PGY_AUDIENCE_PATH = "/api/xhs_pgy_audience";
|
|
35
|
+
const XHS_PGY_PERFORMANCE_PATH = "/api/xhs_pgy_performance";
|
|
36
|
+
|
|
37
|
+
// 本地校验区间,命中就在发请求前拦截,不产生计费
|
|
38
|
+
const XHS_MAX_PAGES_LIMIT = 5;
|
|
39
|
+
const XHS_MAX_SECONDS_LIMIT = 170;
|
|
40
|
+
const XHS_SEARCH_SORTS = [
|
|
41
|
+
"general",
|
|
42
|
+
"time_descending",
|
|
43
|
+
"popularity_descending",
|
|
44
|
+
"comment_descending",
|
|
45
|
+
"collect_descending",
|
|
46
|
+
];
|
|
47
|
+
const XHS_SEARCH_NOTE_TIMES = ["day", "week", "halfyear"];
|
|
48
|
+
const XHS_SEARCH_NOTE_TYPES = ["video", "note"];
|
|
22
49
|
const WX_ARTICLE_DATA_PATH = "/api/wx_article_data";
|
|
23
50
|
const WX_HISTORY_ARTICLES_PATH = "/api/wx_history_articles";
|
|
24
51
|
const R2_UPLOADER_PATH = "/api/r2_uploader";
|
|
@@ -178,7 +205,7 @@ function throwOnBusinessFailure(resp) {
|
|
|
178
205
|
|
|
179
206
|
/**
|
|
180
207
|
* wx_history_articles 把上游失败塞在 HTTP 200 + success:true 的 data.code 里
|
|
181
|
-
* (api_docs:data.code 为 0 才算成功,平台侧并不改写成 success:false)。
|
|
208
|
+
* (backend/api_docs:data.code 为 0 才算成功,平台侧并不改写成 success:false)。
|
|
182
209
|
* 不认这个码,就会把「上游挂了」当成「这个号没有文章」。
|
|
183
210
|
* @param {object} resp
|
|
184
211
|
* @returns {object}
|
|
@@ -413,7 +440,7 @@ async function hotList(apiKey, params) {
|
|
|
413
440
|
}
|
|
414
441
|
|
|
415
442
|
const limit = validatePositiveInt(params.limit === undefined ? 30 : params.limit, "limit", 30);
|
|
416
|
-
// The hot-list service is currently served over HTTP; see api_docs/hot_list_mixed.md.
|
|
443
|
+
// The hot-list service is currently served over HTTP; see backend/api_docs/hot_list_mixed.md.
|
|
417
444
|
const resp = await httpPost(API_HOST, HOT_LIST_API_PATH, {
|
|
418
445
|
source_ids: sourceIds,
|
|
419
446
|
latest: true,
|
|
@@ -529,6 +556,53 @@ async function douyinVideoComments(apiKey, videoUrl, maxCount) {
|
|
|
529
556
|
return stripBilling(resp);
|
|
530
557
|
}
|
|
531
558
|
|
|
559
|
+
/**
|
|
560
|
+
* 抖音原画下载(最高画质直链)
|
|
561
|
+
* @param {string} apiKey
|
|
562
|
+
* @param {string} videoUrl 链接、分享文案或作品 ID
|
|
563
|
+
* @returns {Promise<object>}
|
|
564
|
+
*/
|
|
565
|
+
async function douyinVideoOriginal(apiKey, videoUrl) {
|
|
566
|
+
const resp = await httpPost(API_HOST, DOUYIN_VIDEO_ORIGINAL_PATH, {
|
|
567
|
+
api_key: apiKey,
|
|
568
|
+
video_url: videoUrl,
|
|
569
|
+
});
|
|
570
|
+
return stripBilling(resp);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* 抖音热词榜(原子接口)
|
|
575
|
+
* @param {string} apiKey
|
|
576
|
+
* @returns {Promise<object>}
|
|
577
|
+
*/
|
|
578
|
+
async function douyinHotWords(apiKey) {
|
|
579
|
+
const resp = await httpPost(API_HOST, DOUYIN_HOT_WORDS_PATH, { api_key: apiKey });
|
|
580
|
+
return stripBilling(resp);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* 抖音热点榜(原子接口,一次返回 current + rocketing)
|
|
585
|
+
* @param {string} apiKey
|
|
586
|
+
* @returns {Promise<object>}
|
|
587
|
+
*/
|
|
588
|
+
async function douyinHotTopics(apiKey) {
|
|
589
|
+
const resp = await httpPost(API_HOST, DOUYIN_HOT_TOPICS_PATH, { api_key: apiKey });
|
|
590
|
+
return stripBilling(resp);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* 抖音飙升热点榜(原子接口)
|
|
595
|
+
* @param {string} apiKey
|
|
596
|
+
* @param {number} [limit] 1~20,越界时后端回退为 10
|
|
597
|
+
* @returns {Promise<object>}
|
|
598
|
+
*/
|
|
599
|
+
async function douyinRisingTopics(apiKey, limit) {
|
|
600
|
+
const body = { api_key: apiKey };
|
|
601
|
+
if (limit !== undefined && limit !== null) body.limit = limit;
|
|
602
|
+
const resp = await httpPost(API_HOST, DOUYIN_RISING_TOPICS_PATH, body);
|
|
603
|
+
return stripBilling(resp);
|
|
604
|
+
}
|
|
605
|
+
|
|
532
606
|
/**
|
|
533
607
|
* 获取抖音用户视频列表
|
|
534
608
|
* @param {string} apiKey
|
|
@@ -626,6 +700,23 @@ async function wxArticleData(apiKey, url) {
|
|
|
626
700
|
return throwOnBusinessFailure(stripBilling(resp));
|
|
627
701
|
}
|
|
628
702
|
|
|
703
|
+
/**
|
|
704
|
+
* 将任意网页(含微信公众号文章)转成干净的 Markdown
|
|
705
|
+
* @param {string} apiKey
|
|
706
|
+
* @param {{ url: string, timeout?: number|string }} params
|
|
707
|
+
* @returns {Promise<object>} `markdown` 在顶层,不在 data 下
|
|
708
|
+
*/
|
|
709
|
+
async function urlToMarkdown(apiKey, params = {}) {
|
|
710
|
+
const body = { url: requireText(params.url, "url(网页链接)") };
|
|
711
|
+
if (params.timeout !== undefined && params.timeout !== null && params.timeout !== "") {
|
|
712
|
+
body.timeout = validatePositiveInt(params.timeout, "timeout");
|
|
713
|
+
}
|
|
714
|
+
const resp = await httpPost(API_HOST, URL_TO_MARKDOWN_API_PATH, body, {
|
|
715
|
+
Authorization: `Bearer ${apiKey}`,
|
|
716
|
+
});
|
|
717
|
+
return throwOnBusinessFailure(stripBilling(resp));
|
|
718
|
+
}
|
|
719
|
+
|
|
629
720
|
/**
|
|
630
721
|
* 获取微信公众号历史文章列表(按成功翻页次数计费)
|
|
631
722
|
* @param {string} apiKey
|
|
@@ -677,6 +768,260 @@ async function xhsNotePublish(apiKey, params) {
|
|
|
677
768
|
return stripBilling(resp);
|
|
678
769
|
}
|
|
679
770
|
|
|
771
|
+
/**
|
|
772
|
+
* 抛 api_error(上层按业务失败处理)
|
|
773
|
+
* @param {object} result
|
|
774
|
+
* @returns {object}
|
|
775
|
+
*/
|
|
776
|
+
function throwOnXhsFailure(result) {
|
|
777
|
+
if (result?.success === false) {
|
|
778
|
+
throw Object.assign(new Error(result.message || "小红书接口返回失败"), { type: "api_error" });
|
|
779
|
+
}
|
|
780
|
+
return result;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* 校验 1~上限 的正整数(上限单独校验,避免与其他命令的 limit 语义混住)
|
|
785
|
+
* @param {*} value
|
|
786
|
+
* @param {string} name
|
|
787
|
+
* @param {number} max
|
|
788
|
+
* @returns {number|null}
|
|
789
|
+
*/
|
|
790
|
+
function validateXhsPositiveInt(value, name, max) {
|
|
791
|
+
if (value === undefined || value === null || value === "") return null;
|
|
792
|
+
const n = Number(value);
|
|
793
|
+
if (!Number.isInteger(n) || n < 1 || n > max) {
|
|
794
|
+
throw Object.assign(new Error(`${name} 须为 1~${max} 的整数,收到: ${value}`), {
|
|
795
|
+
type: "validation_error",
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
return n;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* 获取小红书笔记详情。
|
|
803
|
+
* 注意:只传 note_id 即可(2026-11-05 实测,不需要 xsec_token)。
|
|
804
|
+
* @param {string} apiKey
|
|
805
|
+
* @param {string} noteId
|
|
806
|
+
* @returns {Promise<object>} `note_data` 在顶层,没有 data.data 嵌套
|
|
807
|
+
*/
|
|
808
|
+
async function xhsNoteInfo(apiKey, noteId) {
|
|
809
|
+
const resp = await httpPost(API_HOST, XHS_NOTE_INFO_PATH, { note_id: noteId }, {
|
|
810
|
+
Authorization: `Bearer ${apiKey}`,
|
|
811
|
+
}, http);
|
|
812
|
+
return throwOnXhsFailure(stripBilling(resp));
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* 获取小红书笔记一级评论(含可见的楼内回复 sub_comments)。
|
|
817
|
+
* @param {string} apiKey
|
|
818
|
+
* @param {{noteId?: string, shareText?: string, maxPages?: number, maxSeconds?: number}} params
|
|
819
|
+
* @returns {Promise<object>} `data.data.comments`,10 条/页
|
|
820
|
+
*/
|
|
821
|
+
async function xhsNoteComments(apiKey, params = {}) {
|
|
822
|
+
const body = {};
|
|
823
|
+
const maxPages = validateXhsPositiveInt(params.maxPages, "max_pages", XHS_MAX_PAGES_LIMIT);
|
|
824
|
+
const maxSeconds = validateXhsPositiveInt(params.maxSeconds, "max_seconds", XHS_MAX_SECONDS_LIMIT);
|
|
825
|
+
if (maxPages !== null) body.max_pages = maxPages;
|
|
826
|
+
if (maxSeconds !== null) body.max_seconds = maxSeconds;
|
|
827
|
+
|
|
828
|
+
if (params.noteId) {
|
|
829
|
+
body.note_id = params.noteId;
|
|
830
|
+
} else if (params.shareText) {
|
|
831
|
+
body.share_text = params.shareText;
|
|
832
|
+
} else {
|
|
833
|
+
throw Object.assign(new Error("note_id 与分享链接至少提供一个"), { type: "validation_error" });
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
const resp = await httpPost(API_HOST, XHS_NOTE_COMMENTS_PATH, body, {
|
|
837
|
+
Authorization: `Bearer ${apiKey}`,
|
|
838
|
+
}, http);
|
|
839
|
+
return throwOnXhsFailure(stripBilling(resp));
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/**
|
|
843
|
+
* 按关键词搜索小红书笔记。
|
|
844
|
+
* @param {string} apiKey
|
|
845
|
+
* @param {{keyword: string, maxPages?: number, sort?: string, noteTime?: string, noteType?: string}} params
|
|
846
|
+
* @returns {Promise<object>} `data.data.items[]`,20 条/页
|
|
847
|
+
*/
|
|
848
|
+
async function xhsNoteSearch(apiKey, params = {}) {
|
|
849
|
+
const keyword = typeof params.keyword === "string" ? params.keyword.trim() : "";
|
|
850
|
+
if (!keyword) {
|
|
851
|
+
throw Object.assign(new Error("keyword 不能为空"), { type: "validation_error" });
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
const body = { keyword };
|
|
855
|
+
const maxPages = validateXhsPositiveInt(params.maxPages, "max_pages", XHS_MAX_PAGES_LIMIT);
|
|
856
|
+
if (maxPages !== null) body.max_pages = maxPages;
|
|
857
|
+
|
|
858
|
+
if (params.sort) {
|
|
859
|
+
if (!XHS_SEARCH_SORTS.includes(params.sort)) {
|
|
860
|
+
throw Object.assign(new Error(`sort 仅支持 ${XHS_SEARCH_SORTS.join("|")},收到: ${params.sort}`), {
|
|
861
|
+
type: "validation_error",
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
body.sort = params.sort;
|
|
865
|
+
}
|
|
866
|
+
if (params.noteTime) {
|
|
867
|
+
if (!XHS_SEARCH_NOTE_TIMES.includes(params.noteTime)) {
|
|
868
|
+
throw Object.assign(new Error(`note_time 仅支持 ${XHS_SEARCH_NOTE_TIMES.join("|")},收到: ${params.noteTime}`), {
|
|
869
|
+
type: "validation_error",
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
body.note_time = params.noteTime;
|
|
873
|
+
}
|
|
874
|
+
if (params.noteType) {
|
|
875
|
+
if (!XHS_SEARCH_NOTE_TYPES.includes(params.noteType)) {
|
|
876
|
+
throw Object.assign(new Error(`note_type 仅支持 ${XHS_SEARCH_NOTE_TYPES.join("|")},收到: ${params.noteType}`), {
|
|
877
|
+
type: "validation_error",
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
body.note_type = params.noteType;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
const resp = await httpPost(API_HOST, XHS_NOTE_SEARCH_PATH, body, {
|
|
884
|
+
Authorization: `Bearer ${apiKey}`,
|
|
885
|
+
}, http);
|
|
886
|
+
return throwOnXhsFailure(stripBilling(resp));
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* 获取小红书用户详情。
|
|
891
|
+
* @param {string} apiKey
|
|
892
|
+
* @param {string} userId
|
|
893
|
+
* @returns {Promise<object>} `user_info` 在顶层,interactions 为 5 项中文
|
|
894
|
+
*/
|
|
895
|
+
async function xhsUserInfo(apiKey, userId) {
|
|
896
|
+
const resp = await httpPost(API_HOST, XHS_USER_INFO_PATH, { user_id: userId }, {
|
|
897
|
+
Authorization: `Bearer ${apiKey}`,
|
|
898
|
+
}, http);
|
|
899
|
+
return throwOnXhsFailure(stripBilling(resp));
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
/**
|
|
903
|
+
* 蒲公英三件套的公共请求体:24 位小写十六进制当 user_id,其余当主页链接。
|
|
904
|
+
* @param {string} apiKey
|
|
905
|
+
* @param {string} input user_id 或主页链接
|
|
906
|
+
* @returns {object}
|
|
907
|
+
*/
|
|
908
|
+
function pgyRequestBody(apiKey, input) {
|
|
909
|
+
const text = input === undefined || input === null ? "" : String(input).trim();
|
|
910
|
+
if (!text) {
|
|
911
|
+
throw Object.assign(new Error("请提供小红书 user_id 或达人主页链接"), {
|
|
912
|
+
type: "validation_error",
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
const body = { api_key: apiKey };
|
|
916
|
+
if (/^[0-9a-f]{24}$/.test(text)) body.user_id = text;
|
|
917
|
+
else body.profile_url = text;
|
|
918
|
+
return body;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
/**
|
|
922
|
+
* 小红书达人详情(蒲公英)
|
|
923
|
+
* @param {string} apiKey
|
|
924
|
+
* @param {string} input user_id 或主页链接
|
|
925
|
+
* @returns {Promise<object>}
|
|
926
|
+
*/
|
|
927
|
+
async function xhsPgyBlogger(apiKey, input) {
|
|
928
|
+
const resp = await httpPost(API_HOST, XHS_PGY_BLOGGER_PATH, pgyRequestBody(apiKey, input));
|
|
929
|
+
return stripBilling(resp);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* 小红书达人粉丝画像(蒲公英)
|
|
934
|
+
* @param {string} apiKey
|
|
935
|
+
* @param {string} input user_id 或主页链接
|
|
936
|
+
* @returns {Promise<object>}
|
|
937
|
+
*/
|
|
938
|
+
async function xhsPgyAudience(apiKey, input) {
|
|
939
|
+
const resp = await httpPost(API_HOST, XHS_PGY_AUDIENCE_PATH, pgyRequestBody(apiKey, input));
|
|
940
|
+
return stripBilling(resp);
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
/**
|
|
944
|
+
* 小红书达人笔记性价比(蒲公英)
|
|
945
|
+
* @param {string} apiKey
|
|
946
|
+
* @param {string} input user_id 或主页链接
|
|
947
|
+
* @returns {Promise<object>}
|
|
948
|
+
*/
|
|
949
|
+
async function xhsPgyPerformance(apiKey, input) {
|
|
950
|
+
const resp = await httpPost(API_HOST, XHS_PGY_PERFORMANCE_PATH, pgyRequestBody(apiKey, input));
|
|
951
|
+
return stripBilling(resp);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
/**
|
|
955
|
+
* 获取小红书用户笔记列表。
|
|
956
|
+
* @param {string} apiKey
|
|
957
|
+
* @param {{userId: string, maxPages?: number}} params
|
|
958
|
+
* @returns {Promise<object>} `data.data.notes[]`,20 条/页
|
|
959
|
+
*/
|
|
960
|
+
async function xhsUserNotes(apiKey, params = {}) {
|
|
961
|
+
if (!params.userId) {
|
|
962
|
+
throw Object.assign(new Error("user_id 不能为空"), { type: "validation_error" });
|
|
963
|
+
}
|
|
964
|
+
const body = { user_id: params.userId };
|
|
965
|
+
const maxPages = validateXhsPositiveInt(params.maxPages, "max_pages", XHS_MAX_PAGES_LIMIT);
|
|
966
|
+
if (maxPages !== null) body.max_pages = maxPages;
|
|
967
|
+
|
|
968
|
+
const resp = await httpPost(API_HOST, XHS_USER_NOTES_PATH, body, {
|
|
969
|
+
Authorization: `Bearer ${apiKey}`,
|
|
970
|
+
}, http);
|
|
971
|
+
return throwOnXhsFailure(stripBilling(resp));
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
/**
|
|
975
|
+
* 展开小红书分享短链(xhslink.cn / xhslink.com)为真实长链。
|
|
976
|
+
* 短链必须跟随 302,否则拿不到 note_id。
|
|
977
|
+
* @param {string} shortUrl
|
|
978
|
+
* @returns {Promise<string>} 长链;不是跳转时原样返回
|
|
979
|
+
*/
|
|
980
|
+
function resolveXhsShortLink(shortUrl) {
|
|
981
|
+
return new Promise((resolve, reject) => {
|
|
982
|
+
let parsed;
|
|
983
|
+
try {
|
|
984
|
+
parsed = new URL(shortUrl);
|
|
985
|
+
} catch {
|
|
986
|
+
reject(Object.assign(new Error(`短链格式无效: ${shortUrl}`), { type: "validation_error" }));
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
990
|
+
reject(Object.assign(new Error(`短链仅支持 HTTP/HTTPS: ${shortUrl}`), { type: "validation_error" }));
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
const transport = parsed.protocol === "https:" ? https : http;
|
|
995
|
+
const req = transport.request({
|
|
996
|
+
hostname: parsed.hostname,
|
|
997
|
+
port: parsed.port || (parsed.protocol === "https:" ? 443 : 80),
|
|
998
|
+
path: `${parsed.pathname}${parsed.search}`,
|
|
999
|
+
method: "GET",
|
|
1000
|
+
headers: {
|
|
1001
|
+
"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
|
|
1002
|
+
},
|
|
1003
|
+
}, (res) => {
|
|
1004
|
+
const location = res.headers?.location;
|
|
1005
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && location) {
|
|
1006
|
+
if (res.resume) res.resume();
|
|
1007
|
+
resolve(location);
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
if (res.resume) res.resume();
|
|
1011
|
+
resolve(shortUrl);
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
req.setTimeout(15000, () => {
|
|
1015
|
+
req.destroy();
|
|
1016
|
+
reject(Object.assign(new Error(`短链解析超时: ${shortUrl}`), { type: "network_error" }));
|
|
1017
|
+
});
|
|
1018
|
+
req.on("error", (err) => {
|
|
1019
|
+
reject(Object.assign(new Error(`短链解析失败: ${err.message}`), { type: "network_error" }));
|
|
1020
|
+
});
|
|
1021
|
+
req.end();
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
|
|
680
1025
|
module.exports = {
|
|
681
1026
|
resolveApiKey,
|
|
682
1027
|
publishHtml,
|
|
@@ -693,8 +1038,22 @@ module.exports = {
|
|
|
693
1038
|
douyinUserSearch,
|
|
694
1039
|
douyinVideoSearch,
|
|
695
1040
|
douyinLinkToText,
|
|
1041
|
+
douyinHotWords,
|
|
1042
|
+
douyinHotTopics,
|
|
1043
|
+
douyinRisingTopics,
|
|
1044
|
+
douyinVideoOriginal,
|
|
696
1045
|
r2Upload,
|
|
697
1046
|
xhsNotePublish,
|
|
1047
|
+
xhsNoteInfo,
|
|
1048
|
+
xhsNoteComments,
|
|
1049
|
+
xhsNoteSearch,
|
|
1050
|
+
xhsUserInfo,
|
|
1051
|
+
xhsUserNotes,
|
|
1052
|
+
resolveXhsShortLink,
|
|
1053
|
+
xhsPgyBlogger,
|
|
1054
|
+
xhsPgyAudience,
|
|
1055
|
+
xhsPgyPerformance,
|
|
698
1056
|
wxArticleData,
|
|
699
1057
|
wxHistoryArticles,
|
|
1058
|
+
urlToMarkdown,
|
|
700
1059
|
};
|