aiconductor-cli 1.3.4 → 1.3.5
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 +44 -4
- package/README.md +26 -0
- package/api_docs/aic_web_search.md +84 -0
- package/api_docs/audio_converter.md +51 -0
- package/api_docs/balance_query.md +38 -0
- package/api_docs/bing_search.md +55 -0
- package/api_docs/china_stock_data.md +221 -0
- package/api_docs/delete_published_webpage.md +38 -0
- package/api_docs/doubao_search.md +80 -0
- package/api_docs/douyin_link_to_text_v1.md +50 -0
- package/api_docs/douyin_link_to_text_v2.md +50 -0
- package/api_docs/douyin_user_detail.md +65 -0
- package/api_docs/douyin_user_search.md +58 -0
- package/api_docs/douyin_user_videos.md +49 -0
- package/api_docs/douyin_user_videos_async.md +45 -0
- package/api_docs/douyin_user_videos_async_query.md +41 -0
- package/api_docs/douyin_user_videos_max.md +59 -0
- package/api_docs/douyin_video_comments.md +68 -0
- package/api_docs/douyin_video_info.md +70 -0
- package/api_docs/douyin_video_search.md +60 -0
- package/api_docs/douyin_video_search_fast.md +48 -0
- package/api_docs/fast_ocr.md +51 -0
- package/api_docs/flow_article2podcast_creat.md +41 -0
- package/api_docs/flow_article2podcast_query.md +41 -0
- package/api_docs/flow_wx_history_articles.md +45 -0
- package/api_docs/generate_image.md +68 -0
- package/api_docs/get_duration.md +37 -0
- package/api_docs/hot_list_mixed.md +122 -0
- package/api_docs/html_publish.md +50 -0
- package/api_docs/html_publish_flexible.md +65 -0
- package/api_docs/image_resize.md +58 -0
- package/api_docs/image_to_permalink.md +61 -0
- package/api_docs/link_expander.md +39 -0
- package/api_docs/llm_proxy.md +61 -0
- package/api_docs/markitdown.md +47 -0
- package/api_docs/md_to_docx.md +51 -0
- package/api_docs/md_to_html.md +53 -0
- package/api_docs/md_to_mindmap.md +52 -0
- package/api_docs/md_to_pdf.md +52 -0
- package/api_docs/md_to_png.md +53 -0
- package/api_docs/md_to_txt.md +51 -0
- package/api_docs/md_to_wxhtml.md +64 -0
- package/api_docs/md_to_xhs.md +87 -0
- package/api_docs/md_to_xlsx.md +52 -0
- package/api_docs/portrait_enhancement.md +69 -0
- package/api_docs/published_webpages.md +45 -0
- package/api_docs/r2_uploader.md +51 -0
- package/api_docs/schedulerx_job_create.md +56 -0
- package/api_docs/transaction_query.md +52 -0
- package/api_docs/url_to_markdown.md +50 -0
- package/api_docs/video_download_url.md +57 -0
- package/api_docs/video_download_url_oss.md +55 -0
- package/api_docs/video_to_text.md +48 -0
- package/api_docs/video_to_text_v2.md +49 -0
- package/api_docs/volcengine_face_pretty.md +55 -0
- package/api_docs/volcengine_faceswap.md +55 -0
- package/api_docs/website_screenshot.md +49 -0
- package/api_docs/wx_access_token.md +44 -0
- package/api_docs/wx_article_analysis_data_push.md +43 -0
- package/api_docs/wx_article_data.md +48 -0
- package/api_docs/wx_article_style_rewrite.md +45 -0
- package/api_docs/wx_draft_add.md +56 -0
- package/api_docs/wx_draft_publish.md +47 -0
- package/api_docs/wx_history_articles.md +70 -0
- package/api_docs/wx_image_upload.md +46 -0
- package/api_docs/wx_md_to_html.md +48 -0
- package/api_docs/wx_rss_reader.md +59 -0
- package/api_docs/xhs_hot_list.md +48 -0
- package/api_docs/xhs_note_comments_fast.md +54 -0
- package/api_docs/xhs_note_info_v2.md +45 -0
- package/api_docs/xhs_note_info_v3.md +69 -0
- package/api_docs/xhs_note_publish.md +54 -0
- package/api_docs/xhs_note_search_v2.md +73 -0
- package/api_docs/xhs_note_search_v3.md +54 -0
- package/api_docs/xhs_user_info_v2.md +57 -0
- package/api_docs/xhs_user_notes_v2.md +56 -0
- package/api_docs/xhs_user_notes_v3.md +46 -0
- package/bin/aiconductor-cli.js +23 -6
- package/package.json +1 -1
- package/src/api.js +45 -7
- package/src/api.test.js +67 -5
- package/src/commands/content.js +90 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
## 接口信息
|
|
2
|
+
- **URL**: `http://plugin.aiconductor.fun/api/xhs_user_notes_v3`
|
|
3
|
+
- **方法**: `POST` / `GET`
|
|
4
|
+
- **Content-Type**: `application/json`
|
|
5
|
+
- **鉴权**: 请求头 `Authorization: Bearer <api_key>`(兼容 body/query 中 `api_key` 参数)
|
|
6
|
+
|
|
7
|
+
## 功能说明
|
|
8
|
+
根据用户 ID 获取小红书用户笔记列表,支持自动翻页(cursor 机制),兼容图文与视频笔记,按页计费。费用以 api_services 表配置为准(service_code: `xhs_user_notes_v2`)。
|
|
9
|
+
|
|
10
|
+
## 请求参数
|
|
11
|
+
|
|
12
|
+
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
|
13
|
+
|--------|------|------|--------|------|
|
|
14
|
+
| user_id | string | 是 | — | 小红书用户 ID |
|
|
15
|
+
| max_pages | int | 否 | 1 | 最大翻页数 |
|
|
16
|
+
| max_seconds | int | 否 | 50 | 最长运行时长(秒) |
|
|
17
|
+
|
|
18
|
+
> **鉴权参数**:api_key 通过请求头 `Authorization: Bearer <api_key>` 传递,不再列入请求参数表。
|
|
19
|
+
|
|
20
|
+
## 响应参数
|
|
21
|
+
|
|
22
|
+
| 参数名 | 类型 | 说明 |
|
|
23
|
+
|--------|------|------|
|
|
24
|
+
| success | boolean | 是否成功 |
|
|
25
|
+
| message | string | 响应消息 |
|
|
26
|
+
| billing | object | 计费信息(service_fee / discount_applied / balance_before / balance_after / currency / pages_billed / total_service_fee / transaction_ids) |
|
|
27
|
+
| data.notes | array[object] | 笔记列表(见下) |
|
|
28
|
+
|
|
29
|
+
`data.notes[]` 字段:`noteId`(string)、`time`(int, 毫秒时间戳)、`title`(string)、`type`(string, normal/video)、`desc`(string)、`time_desc`(string)、`likes`(int)、`comments_count`(int)、`collected_count`(int)、`shared_count`(int)、`ip_location`(string)、`sticky`(bool)、`is_goods_note`(bool)、`has_music`(bool)、`user`(object{user_id, nickname, avatar})、`images`(array[{url, url_size_large, width, height}])。
|
|
30
|
+
视频笔记额外包含 `video`(object):`duration`(int)、`width`(int)、`height`(int)、`cover_url`(string)、`thumbnail_url`(string)、`streams`(array[{codec, quality, url, backup_urls, width, height, duration, fps, format, size}])。
|
|
31
|
+
|
|
32
|
+
## 请求示例
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
curl -X POST "http://plugin.aiconductor.fun/api/xhs_user_notes_v3" \
|
|
36
|
+
-H "Authorization: Bearer your_api_key" \
|
|
37
|
+
-H "Content-Type: application/json" \
|
|
38
|
+
-d '{"user_id": "5f8e9c1b0000000001001a2b", "max_pages": 3}'
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## 注意事项
|
|
42
|
+
1. 按页计费:每成功获取一页计费一次,余额不足、超时或上游无更多数据自动停止翻页。
|
|
43
|
+
2. 采用后扣费模式:仅成功获取并解析后才扣费,失败不扣费。
|
|
44
|
+
3. 翻页由内部维护 `cursor`(取每页最后一条笔记的 cursor),调用方无需关心。
|
|
45
|
+
4. 返回数据同时兼容图文(normal)与视频(video)笔记,视频笔记带 streams 流信息。
|
|
46
|
+
5. 上游单页请求超时 55s,最多重试 3 次。
|
package/bin/aiconductor-cli.js
CHANGED
|
@@ -7,7 +7,8 @@ const {
|
|
|
7
7
|
listCommand: webpageListCommand,
|
|
8
8
|
deleteCommand: webpageDeleteCommand,
|
|
9
9
|
} = require("../src/commands/md_publish");
|
|
10
|
-
const { searchCommand } = require("../src/commands/web_search");
|
|
10
|
+
const { searchCommand } = require("../src/commands/web_search");
|
|
11
|
+
const { briefingCommand } = require("../src/commands/content");
|
|
11
12
|
const {
|
|
12
13
|
videoInfoCommand,
|
|
13
14
|
videoCommentsCommand,
|
|
@@ -111,8 +112,8 @@ searchCmd
|
|
|
111
112
|
searchCommand(query, "web", options);
|
|
112
113
|
});
|
|
113
114
|
|
|
114
|
-
searchCmd
|
|
115
|
-
.command("image")
|
|
115
|
+
searchCmd
|
|
116
|
+
.command("image")
|
|
116
117
|
.description("图片搜索")
|
|
117
118
|
.argument("<query>", "搜索关键词(1~100字符)")
|
|
118
119
|
.option("-k, --api-key <key>", "AIConductor API Key(也可通过 AICONDUCTOR_API_KEY 环境变量或 .env 文件提供)")
|
|
@@ -124,9 +125,25 @@ searchCmd
|
|
|
124
125
|
.option("--image-shapes <shapes>", "图片形状,以 | 分隔:横长方形、竖长方形、方形")
|
|
125
126
|
.option("--query-rewrite", "开启 Query 改写")
|
|
126
127
|
.option("-f, --format <format>", "输出格式:json 或 text", "json")
|
|
127
|
-
.action((query, options) => {
|
|
128
|
-
searchCommand(query, "image", options);
|
|
129
|
-
});
|
|
128
|
+
.action((query, options) => {
|
|
129
|
+
searchCommand(query, "image", options);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// content 子命令组
|
|
133
|
+
const contentCmd = program
|
|
134
|
+
.command("content")
|
|
135
|
+
.description("内容选题与素材工具");
|
|
136
|
+
|
|
137
|
+
contentCmd
|
|
138
|
+
.command("briefing")
|
|
139
|
+
.description("聚合多平台热点,生成选题原料")
|
|
140
|
+
.option("-k, --api-key <key>", "AIConductor API Key(也可通过 AICONDUCTOR_API_KEY 环境变量或 .env 文件提供)")
|
|
141
|
+
.option("--sources <ids>", "来源 ID,逗号分隔;默认 zhihu,weibo,douyin,aihot")
|
|
142
|
+
.option("--limit <n>", "每个来源返回条数,范围 1 到 30", "30")
|
|
143
|
+
.option("-f, --format <format>", "输出格式:json 或 text", "json")
|
|
144
|
+
.action((options) => {
|
|
145
|
+
briefingCommand(options);
|
|
146
|
+
});
|
|
130
147
|
|
|
131
148
|
// 未知命令处理
|
|
132
149
|
|
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
const
|
|
1
|
+
const http = require("http");
|
|
2
|
+
const https = require("https");
|
|
2
3
|
const fs = require("fs");
|
|
3
4
|
const path = require("path");
|
|
4
5
|
|
|
@@ -8,6 +9,7 @@ const MD_TO_HTML_API_PATH = "/api/md_to_html";
|
|
|
8
9
|
const PUBLISHED_WEBPAGES_API_PATH = "/api/published_webpages";
|
|
9
10
|
const DELETE_PUBLISHED_WEBPAGE_API_PATH = "/api/delete_published_webpage";
|
|
10
11
|
const SEARCH_API_PATH = "/api/doubao_search";
|
|
12
|
+
const HOT_LIST_API_PATH = "/api/hot_list_mixed";
|
|
11
13
|
const DOUYIN_VIDEO_INFO_PATH = "/api/douyin_video_info";
|
|
12
14
|
const DOUYIN_VIDEO_COMMENTS_PATH = "/api/douyin_video_comments";
|
|
13
15
|
const DOUYIN_USER_VIDEOS_PATH = "/api/douyin_user_videos";
|
|
@@ -75,14 +77,15 @@ function validateInt(value, name) {
|
|
|
75
77
|
* @param {string} host
|
|
76
78
|
* @param {string} path
|
|
77
79
|
* @param {object} body
|
|
78
|
-
* @
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
* @param {typeof http|typeof https} [transport]
|
|
81
|
+
* @returns {Promise<object>}
|
|
82
|
+
*/
|
|
83
|
+
function httpPost(host, requestPath, body, headers = {}, transport = https) {
|
|
81
84
|
return new Promise((resolve, reject) => {
|
|
82
85
|
const data = JSON.stringify(body);
|
|
83
86
|
const options = {
|
|
84
87
|
hostname: host,
|
|
85
|
-
port: 443,
|
|
88
|
+
port: transport === http ? 80 : 443,
|
|
86
89
|
path: requestPath,
|
|
87
90
|
method: "POST",
|
|
88
91
|
headers: {
|
|
@@ -92,7 +95,7 @@ function httpPost(host, requestPath, body, headers = {}) {
|
|
|
92
95
|
},
|
|
93
96
|
};
|
|
94
97
|
|
|
95
|
-
const req =
|
|
98
|
+
const req = transport.request(options, (res) => {
|
|
96
99
|
let chunks = [];
|
|
97
100
|
res.on("data", (chunk) => chunks.push(chunk));
|
|
98
101
|
res.on("end", () => {
|
|
@@ -343,6 +346,40 @@ async function webSearch(apiKey, params) {
|
|
|
343
346
|
|
|
344
347
|
return result;
|
|
345
348
|
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* 聚合多个平台的热榜条目。
|
|
352
|
+
* @param {string} apiKey
|
|
353
|
+
* @param {{ sourceIds: string[]|string, limit?: number|string }} params
|
|
354
|
+
* @returns {Promise<object>}
|
|
355
|
+
*/
|
|
356
|
+
async function hotList(apiKey, params) {
|
|
357
|
+
const inputSources = Array.isArray(params.sourceIds)
|
|
358
|
+
? params.sourceIds
|
|
359
|
+
: String(params.sourceIds || "").split(",");
|
|
360
|
+
const sourceIds = [...new Set(inputSources.map((source) => String(source).trim()).filter(Boolean))];
|
|
361
|
+
if (sourceIds.length === 0) {
|
|
362
|
+
throw Object.assign(new Error("sourceIds 至少需要提供一个来源"), { type: "validation_error" });
|
|
363
|
+
}
|
|
364
|
+
if (sourceIds.length > 52) {
|
|
365
|
+
throw Object.assign(new Error("sourceIds 最多支持 52 个来源"), { type: "validation_error" });
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const limit = validatePositiveInt(params.limit === undefined ? 30 : params.limit, "limit", 30);
|
|
369
|
+
// The hot-list service is currently served over HTTP; see api_docs/hot_list_mixed.md.
|
|
370
|
+
const resp = await httpPost(API_HOST, HOT_LIST_API_PATH, {
|
|
371
|
+
source_ids: sourceIds,
|
|
372
|
+
latest: true,
|
|
373
|
+
limit,
|
|
374
|
+
}, {
|
|
375
|
+
Authorization: `Bearer ${apiKey}`,
|
|
376
|
+
}, http);
|
|
377
|
+
const result = stripBilling(resp);
|
|
378
|
+
if (result?.success === false) {
|
|
379
|
+
throw Object.assign(new Error(result.message || "热榜接口返回失败"), { type: "api_error" });
|
|
380
|
+
}
|
|
381
|
+
return result;
|
|
382
|
+
}
|
|
346
383
|
|
|
347
384
|
/**
|
|
348
385
|
* Markdown 转 HTML 并发布
|
|
@@ -537,7 +574,8 @@ module.exports = {
|
|
|
537
574
|
publishMdToHtml,
|
|
538
575
|
getPublishedWebpages,
|
|
539
576
|
deletePublishedWebpage,
|
|
540
|
-
webSearch,
|
|
577
|
+
webSearch,
|
|
578
|
+
hotList,
|
|
541
579
|
douyinVideoInfo,
|
|
542
580
|
douyinVideoComments,
|
|
543
581
|
douyinUserVideos,
|
package/src/api.test.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
const assert = require("node:assert/strict");
|
|
2
2
|
const { EventEmitter } = require("node:events");
|
|
3
|
+
const http = require("node:http");
|
|
3
4
|
const https = require("node:https");
|
|
4
5
|
const test = require("node:test");
|
|
5
6
|
|
|
6
|
-
const { webSearch } = require("./api");
|
|
7
|
+
const { hotList, webSearch } = require("./api");
|
|
8
|
+
const { formatHotListText, parseSourceIds, DEFAULT_HOT_LIST_SOURCES } = require("./commands/content");
|
|
7
9
|
const { formatSearchText } = require("./commands/web_search");
|
|
8
10
|
|
|
9
|
-
function captureRequest(callback, responseBody = { success: true, billing: {} }) {
|
|
10
|
-
const originalRequest =
|
|
11
|
+
function captureRequest(callback, responseBody = { success: true, billing: {} }, transport = https) {
|
|
12
|
+
const originalRequest = transport.request;
|
|
11
13
|
let captured;
|
|
12
|
-
|
|
14
|
+
transport.request = (options, responseCallback) => {
|
|
13
15
|
const request = new EventEmitter();
|
|
14
16
|
const chunks = [];
|
|
15
17
|
request.write = (chunk) => chunks.push(Buffer.from(chunk));
|
|
@@ -25,7 +27,7 @@ function captureRequest(callback, responseBody = { success: true, billing: {} })
|
|
|
25
27
|
};
|
|
26
28
|
|
|
27
29
|
return callback().then(() => captured).finally(() => {
|
|
28
|
-
|
|
30
|
+
transport.request = originalRequest;
|
|
29
31
|
});
|
|
30
32
|
}
|
|
31
33
|
|
|
@@ -155,3 +157,63 @@ test("webSearch raises nested API failures", async () => {
|
|
|
155
157
|
{ type: "api_error", message: "image 搜索的 count 必须为 1~5" }
|
|
156
158
|
);
|
|
157
159
|
});
|
|
160
|
+
|
|
161
|
+
test("hotList sends deduplicated sources with current defaults", async () => {
|
|
162
|
+
const request = await captureRequest(
|
|
163
|
+
() => hotList("test-key", { sourceIds: ["zhihu", "weibo", "zhihu", "aihot"] }),
|
|
164
|
+
undefined,
|
|
165
|
+
http
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
assert.equal(request.options.path, "/api/hot_list_mixed");
|
|
169
|
+
assert.equal(request.options.port, 80);
|
|
170
|
+
assert.equal(request.options.headers.Authorization, "Bearer test-key");
|
|
171
|
+
assert.deepEqual(request.body, {
|
|
172
|
+
source_ids: ["zhihu", "weibo", "aihot"],
|
|
173
|
+
latest: true,
|
|
174
|
+
limit: 30,
|
|
175
|
+
});
|
|
176
|
+
assert.deepEqual(parseSourceIds(), DEFAULT_HOT_LIST_SOURCES);
|
|
177
|
+
assert.deepEqual(parseSourceIds("zhihu, weibo,zhihu"), ["zhihu", "weibo"]);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("hotList validates source IDs and limits", async () => {
|
|
181
|
+
await assert.rejects(hotList("test-key", { sourceIds: [] }), { type: "validation_error" });
|
|
182
|
+
await assert.rejects(
|
|
183
|
+
hotList("test-key", { sourceIds: Array.from({ length: 53 }, (_, index) => `source-${index}`) }),
|
|
184
|
+
{ type: "validation_error" }
|
|
185
|
+
);
|
|
186
|
+
await assert.rejects(hotList("test-key", { sourceIds: ["zhihu"], limit: 0 }), { type: "validation_error" });
|
|
187
|
+
await assert.rejects(hotList("test-key", { sourceIds: ["zhihu"], limit: 31 }), { type: "validation_error" });
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("formatHotListText keeps successful sources and reports failures", () => {
|
|
191
|
+
const text = formatHotListText({
|
|
192
|
+
data: {
|
|
193
|
+
sources: [{
|
|
194
|
+
source_id: "zhihu",
|
|
195
|
+
status: "cache",
|
|
196
|
+
updated_time: 0,
|
|
197
|
+
items: [{ title: "AI 应用趋势", url: "https://example.com/topic", pub_date: "今天" }],
|
|
198
|
+
}],
|
|
199
|
+
failed_sources: [{ source_id: "weibo", message: "上游暂不可用" }],
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
assert.match(text, /【zhihu】缓存/);
|
|
204
|
+
assert.match(text, /AI 应用趋势/);
|
|
205
|
+
assert.match(text, /URL: https:\/\/example\.com\/topic/);
|
|
206
|
+
assert.match(text, /失败来源:/);
|
|
207
|
+
assert.match(text, /weibo: 上游暂不可用/);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("hotList raises API failures", async () => {
|
|
211
|
+
await assert.rejects(
|
|
212
|
+
captureRequest(
|
|
213
|
+
() => hotList("test-key", { sourceIds: ["zhihu"] }),
|
|
214
|
+
{ success: false, message: "没有可用热榜来源" },
|
|
215
|
+
http
|
|
216
|
+
),
|
|
217
|
+
{ type: "api_error", message: "没有可用热榜来源" }
|
|
218
|
+
);
|
|
219
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const { hotList } = require("../api");
|
|
2
|
+
const { outputResult, outputError, resolveKeyOrExit, handleApiError } = require("../lib/cli-utils");
|
|
3
|
+
|
|
4
|
+
const DEFAULT_HOT_LIST_SOURCES = ["zhihu", "weibo", "douyin", "aihot"];
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 解析并去重逗号分隔的平台来源 ID。
|
|
8
|
+
* @param {string|undefined} value
|
|
9
|
+
* @returns {string[]}
|
|
10
|
+
*/
|
|
11
|
+
function parseSourceIds(value) {
|
|
12
|
+
const rawSources = value === undefined ? DEFAULT_HOT_LIST_SOURCES : String(value).split(",");
|
|
13
|
+
const sourceIds = [...new Set(rawSources.map((source) => source.trim()).filter(Boolean))];
|
|
14
|
+
if (sourceIds.length === 0) {
|
|
15
|
+
throw Object.assign(new Error("--sources 至少需要提供一个来源 ID"), { type: "validation_error" });
|
|
16
|
+
}
|
|
17
|
+
if (sourceIds.length > 52) {
|
|
18
|
+
throw Object.assign(new Error("--sources 最多支持 52 个来源"), { type: "validation_error" });
|
|
19
|
+
}
|
|
20
|
+
return sourceIds;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function formatTimestamp(value) {
|
|
24
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return value || "";
|
|
25
|
+
return new Date(value).toISOString();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* text 模式的热点简报格式化。
|
|
30
|
+
* @param {object} result
|
|
31
|
+
* @returns {string}
|
|
32
|
+
*/
|
|
33
|
+
function formatHotListText(result = {}) {
|
|
34
|
+
const data = result.data || {};
|
|
35
|
+
const sources = data.sources || [];
|
|
36
|
+
const failedSources = data.failed_sources || [];
|
|
37
|
+
const lines = ["热点选题简报"];
|
|
38
|
+
|
|
39
|
+
if (!sources.length) {
|
|
40
|
+
lines.push("没有获取到可用热点来源。");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
sources.forEach((source) => {
|
|
44
|
+
const status = source.status === "cache" ? "缓存" : source.status || "未知状态";
|
|
45
|
+
lines.push(`\n【${source.source_id}】${status}`);
|
|
46
|
+
if (source.updated_time) lines.push(`更新时间: ${formatTimestamp(source.updated_time)}`);
|
|
47
|
+
(source.items || []).forEach((item, index) => {
|
|
48
|
+
lines.push(`[${index + 1}] ${item.title || "未命名条目"}`);
|
|
49
|
+
if (item.url) lines.push(` URL: ${item.url}`);
|
|
50
|
+
if (item.pub_date) lines.push(` 发布时间: ${item.pub_date}`);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (failedSources.length) {
|
|
55
|
+
lines.push("\n失败来源:");
|
|
56
|
+
failedSources.forEach((source) => {
|
|
57
|
+
lines.push(`- ${source.source_id}: ${source.message || "未知错误"}`);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return lines.join("\n").trimEnd();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* content briefing 命令。
|
|
66
|
+
* @param {{ apiKey?: string, sources?: string, limit?: string, format?: "json"|"text" }} options
|
|
67
|
+
* @returns {Promise<void>}
|
|
68
|
+
*/
|
|
69
|
+
async function briefingCommand(options = {}) {
|
|
70
|
+
const format = options.format || "json";
|
|
71
|
+
const apiKey = resolveKeyOrExit(options.apiKey, format);
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const result = await hotList(apiKey, {
|
|
75
|
+
sourceIds: parseSourceIds(options.sources),
|
|
76
|
+
limit: options.limit === undefined ? 30 : options.limit,
|
|
77
|
+
});
|
|
78
|
+
outputResult(result, format, formatHotListText);
|
|
79
|
+
process.exit(0);
|
|
80
|
+
} catch (err) {
|
|
81
|
+
if (err.type === "validation_error") {
|
|
82
|
+
outputError(err, format);
|
|
83
|
+
} else {
|
|
84
|
+
handleApiError(err, format);
|
|
85
|
+
}
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = { briefingCommand, formatHotListText, parseSourceIds, DEFAULT_HOT_LIST_SOURCES };
|