aiconductor-cli 1.6.0 → 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.
Files changed (92) hide show
  1. package/README.md +565 -488
  2. package/bin/aiconductor-cli.js +401 -351
  3. package/package.json +1 -1
  4. package/src/api.js +135 -3
  5. package/src/api.test.js +292 -4
  6. package/src/commands/content.js +184 -184
  7. package/src/commands/douyin.js +174 -0
  8. package/src/commands/html_publish.js +54 -54
  9. package/src/commands/md_publish.js +175 -175
  10. package/src/commands/web.js +69 -0
  11. package/src/commands/web_search.js +99 -99
  12. package/src/commands/wx.js +116 -116
  13. package/src/commands/xhs.js +763 -609
  14. package/src/lib/cli-utils.js +172 -172
  15. package/src/lib/daren-score.js +91 -0
  16. package/src/lib/daren-score.test.js +57 -0
  17. package/AGENTS.md +0 -193
  18. package/api_docs/aic_web_search.md +0 -84
  19. package/api_docs/audio_converter.md +0 -51
  20. package/api_docs/balance_query.md +0 -38
  21. package/api_docs/bing_search.md +0 -55
  22. package/api_docs/china_stock_data.md +0 -221
  23. package/api_docs/delete_published_webpage.md +0 -38
  24. package/api_docs/doubao_search.md +0 -80
  25. package/api_docs/douyin_link_to_text_v1.md +0 -50
  26. package/api_docs/douyin_link_to_text_v2.md +0 -50
  27. package/api_docs/douyin_user_detail.md +0 -65
  28. package/api_docs/douyin_user_search.md +0 -58
  29. package/api_docs/douyin_user_videos.md +0 -49
  30. package/api_docs/douyin_user_videos_async.md +0 -45
  31. package/api_docs/douyin_user_videos_async_query.md +0 -41
  32. package/api_docs/douyin_user_videos_max.md +0 -59
  33. package/api_docs/douyin_video_comments.md +0 -68
  34. package/api_docs/douyin_video_info.md +0 -70
  35. package/api_docs/douyin_video_search.md +0 -60
  36. package/api_docs/douyin_video_search_fast.md +0 -48
  37. package/api_docs/fast_ocr.md +0 -51
  38. package/api_docs/flow_article2podcast_creat.md +0 -41
  39. package/api_docs/flow_article2podcast_query.md +0 -41
  40. package/api_docs/flow_wx_history_articles.md +0 -45
  41. package/api_docs/generate_image.md +0 -68
  42. package/api_docs/get_duration.md +0 -37
  43. package/api_docs/hot_list_mixed.md +0 -122
  44. package/api_docs/html_publish.md +0 -50
  45. package/api_docs/html_publish_flexible.md +0 -65
  46. package/api_docs/image_resize.md +0 -58
  47. package/api_docs/image_to_permalink.md +0 -61
  48. package/api_docs/link_expander.md +0 -39
  49. package/api_docs/llm_proxy.md +0 -61
  50. package/api_docs/markitdown.md +0 -47
  51. package/api_docs/md_to_docx.md +0 -51
  52. package/api_docs/md_to_html.md +0 -53
  53. package/api_docs/md_to_mindmap.md +0 -52
  54. package/api_docs/md_to_pdf.md +0 -52
  55. package/api_docs/md_to_png.md +0 -53
  56. package/api_docs/md_to_txt.md +0 -51
  57. package/api_docs/md_to_wxhtml.md +0 -64
  58. package/api_docs/md_to_xhs.md +0 -87
  59. package/api_docs/md_to_xlsx.md +0 -52
  60. package/api_docs/portrait_enhancement.md +0 -69
  61. package/api_docs/published_webpages.md +0 -45
  62. package/api_docs/r2_uploader.md +0 -51
  63. package/api_docs/schedulerx_job_create.md +0 -56
  64. package/api_docs/transaction_query.md +0 -52
  65. package/api_docs/url_to_markdown.md +0 -50
  66. package/api_docs/video_download_url.md +0 -57
  67. package/api_docs/video_download_url_oss.md +0 -55
  68. package/api_docs/video_to_text.md +0 -48
  69. package/api_docs/video_to_text_v2.md +0 -49
  70. package/api_docs/volcengine_face_pretty.md +0 -55
  71. package/api_docs/volcengine_faceswap.md +0 -55
  72. package/api_docs/website_screenshot.md +0 -49
  73. package/api_docs/wx_access_token.md +0 -44
  74. package/api_docs/wx_article_analysis_data_push.md +0 -43
  75. package/api_docs/wx_article_data.md +0 -48
  76. package/api_docs/wx_article_style_rewrite.md +0 -45
  77. package/api_docs/wx_draft_add.md +0 -56
  78. package/api_docs/wx_draft_publish.md +0 -47
  79. package/api_docs/wx_history_articles.md +0 -89
  80. package/api_docs/wx_image_upload.md +0 -46
  81. package/api_docs/wx_md_to_html.md +0 -48
  82. package/api_docs/wx_rss_reader.md +0 -59
  83. package/api_docs/xhs_hot_list.md +0 -48
  84. package/api_docs/xhs_note_comments_fast.md +0 -54
  85. package/api_docs/xhs_note_info_v2.md +0 -45
  86. package/api_docs/xhs_note_info_v3.md +0 -69
  87. package/api_docs/xhs_note_publish.md +0 -54
  88. package/api_docs/xhs_note_search_v2.md +0 -73
  89. package/api_docs/xhs_note_search_v3.md +0 -54
  90. package/api_docs/xhs_user_info_v2.md +0 -61
  91. package/api_docs/xhs_user_notes_v2.md +0 -56
  92. package/api_docs/xhs_user_notes_v3.md +0 -48
@@ -1,175 +1,175 @@
1
- const fs = require("fs");
2
- const path = require("path");
3
- const {
4
- publishHtml,
5
- publishMdToHtml,
6
- getPublishedWebpages,
7
- deletePublishedWebpage,
8
- } = require("../api");
9
- const { outputResult, outputError, resolveKeyOrExit, handleApiError } = require("../lib/cli-utils");
10
-
11
- /**
12
- * 解析输入内容:如果是已存在的文件路径则读取,否则当作原始字符串
13
- * @param {string} input
14
- * @returns {{ content: string, filePath: string|null }}
15
- */
16
- function resolveInput(input) {
17
- const trimmed = input.trim();
18
- try {
19
- const filePath = path.resolve(trimmed);
20
- if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
21
- return { content: fs.readFileSync(filePath, "utf-8"), filePath };
22
- }
23
- } catch (_) {}
24
- return { content: trimmed, filePath: null };
25
- }
26
-
27
- /**
28
- * 自动检测内容类型:Markdown 或 HTML
29
- * @param {string} content
30
- * @param {string|null} filePath
31
- * @returns {"md"|"html"}
32
- */
33
- function detectType(content, filePath) {
34
- if (filePath) {
35
- const ext = path.extname(filePath).toLowerCase();
36
- if (ext === ".md" || ext === ".markdown") return "md";
37
- if (ext === ".html" || ext === ".htm") return "html";
38
- }
39
- const trimmed = content.trim();
40
- if (trimmed.startsWith("<!DOCTYPE") || trimmed.startsWith("<html") || trimmed.startsWith("<head")) {
41
- return "html";
42
- }
43
- if (trimmed.startsWith("<") && /<\w+[^>]*>/.test(trimmed.substring(0, 200))) {
44
- return "html";
45
- }
46
- return "md";
47
- }
48
-
49
- /** text 模式的自定义格式化 */
50
- function formatPublishText(data) {
51
- let out = "✓ 发布成功\n";
52
- out += ` URL: ${data.online_url}\n`;
53
- if (data.message) {
54
- out += ` 信息: ${data.message}\n`;
55
- }
56
- return out.trimEnd();
57
- }
58
-
59
- /**
60
- * text 模式的网页列表格式化
61
- * @param {{webpages?: Array, page?: number, page_size?: number, total?: number}} data
62
- * @returns {string}
63
- */
64
- function formatWebpageListText(data) {
65
- const webpages = data.webpages || [];
66
- if (!webpages.length) return "没有已发布网页。";
67
-
68
- const header = `已发布网页(第 ${data.page} 页,每页 ${data.page_size} 条,共 ${data.total} 条)`;
69
- const entries = webpages.map((webpage) => {
70
- const lines = [`[${webpage.id}] ${webpage.title || "未命名网页"}`];
71
- if (webpage.online_url) lines.push(` URL: ${webpage.online_url}`);
72
- if (webpage.source) lines.push(` 来源: ${webpage.source}`);
73
- if (webpage.created_at) lines.push(` 发布时间: ${webpage.created_at}`);
74
- return lines.join("\n");
75
- });
76
- return [header, ...entries].join("\n");
77
- }
78
-
79
- /**
80
- * text 模式的网页删除格式化
81
- * @param {{webpage_id?: number|null, message?: string}} data
82
- * @returns {string}
83
- */
84
- function formatWebpageDeleteText(data) {
85
- let output = `已删除网页 ID: ${data.webpage_id}`;
86
- if (data.message) output += `\n信息: ${data.message}`;
87
- return output;
88
- }
89
-
90
- /**
91
- * API 业务失败时抛出统一错误
92
- * @param {{success?: boolean, message?: string}} result
93
- */
94
- function throwOnApiFailure(result) {
95
- if (result && result.success === false) {
96
- throw Object.assign(new Error(result.message || "API 请求失败"), { type: "api_error" });
97
- }
98
- }
99
-
100
- /**
101
- * webpage publish 命令 — 统一发布 Markdown 或 HTML 为在线网页
102
- * @param {string} input - Markdown/HTML 代码或文件路径
103
- * @param {{ apiKey?: string, format?: string, title?: string, copyButton?: boolean, type?: string }} options
104
- */
105
- async function publishCommand(input, options = {}) {
106
- const format = options.format || "json";
107
- const apiKey = resolveKeyOrExit(options.apiKey, format);
108
-
109
- try {
110
- const { content, filePath } = resolveInput(input);
111
- let type = options.type;
112
- if (!type) {
113
- type = detectType(content, filePath);
114
- } else if (type !== "md" && type !== "html") {
115
- outputError({ message: `无效的 --type 值: "${type}",可选: md 或 html` }, format);
116
- process.exit(1);
117
- }
118
-
119
- let result;
120
- if (type === "html") {
121
- result = await publishHtml(apiKey, content, options.title);
122
- } else {
123
- result = await publishMdToHtml(apiKey, content, options.title, options.copyButton === true);
124
- }
125
- outputResult(result, format, formatPublishText);
126
- process.exit(0);
127
- } catch (err) {
128
- handleApiError(err, format);
129
- process.exit(1);
130
- }
131
- }
132
-
133
- /**
134
- * webpage list 命令 — 查询当前用户已发布的网页
135
- * @param {{ apiKey?: string, format?: string, page?: string, pageSize?: string }} options
136
- */
137
- async function listCommand(options = {}) {
138
- const format = options.format || "json";
139
- const apiKey = resolveKeyOrExit(options.apiKey, format);
140
-
141
- try {
142
- const result = await getPublishedWebpages(apiKey, {
143
- page: options.page,
144
- pageSize: options.pageSize,
145
- });
146
- throwOnApiFailure(result);
147
- outputResult(result, format, formatWebpageListText);
148
- process.exit(0);
149
- } catch (err) {
150
- handleApiError(err, format);
151
- process.exit(1);
152
- }
153
- }
154
-
155
- /**
156
- * webpage delete 命令 — 删除指定的已发布网页
157
- * @param {string} webpageId
158
- * @param {{ apiKey?: string, format?: string }} options
159
- */
160
- async function deleteCommand(webpageId, options = {}) {
161
- const format = options.format || "json";
162
- const apiKey = resolveKeyOrExit(options.apiKey, format);
163
-
164
- try {
165
- const result = await deletePublishedWebpage(apiKey, webpageId);
166
- throwOnApiFailure(result);
167
- outputResult(result, format, formatWebpageDeleteText);
168
- process.exit(0);
169
- } catch (err) {
170
- handleApiError(err, format);
171
- process.exit(1);
172
- }
173
- }
174
-
175
- module.exports = { publishCommand, listCommand, deleteCommand };
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const {
4
+ publishHtml,
5
+ publishMdToHtml,
6
+ getPublishedWebpages,
7
+ deletePublishedWebpage,
8
+ } = require("../api");
9
+ const { outputResult, outputError, resolveKeyOrExit, handleApiError } = require("../lib/cli-utils");
10
+
11
+ /**
12
+ * 解析输入内容:如果是已存在的文件路径则读取,否则当作原始字符串
13
+ * @param {string} input
14
+ * @returns {{ content: string, filePath: string|null }}
15
+ */
16
+ function resolveInput(input) {
17
+ const trimmed = input.trim();
18
+ try {
19
+ const filePath = path.resolve(trimmed);
20
+ if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
21
+ return { content: fs.readFileSync(filePath, "utf-8"), filePath };
22
+ }
23
+ } catch (_) {}
24
+ return { content: trimmed, filePath: null };
25
+ }
26
+
27
+ /**
28
+ * 自动检测内容类型:Markdown 或 HTML
29
+ * @param {string} content
30
+ * @param {string|null} filePath
31
+ * @returns {"md"|"html"}
32
+ */
33
+ function detectType(content, filePath) {
34
+ if (filePath) {
35
+ const ext = path.extname(filePath).toLowerCase();
36
+ if (ext === ".md" || ext === ".markdown") return "md";
37
+ if (ext === ".html" || ext === ".htm") return "html";
38
+ }
39
+ const trimmed = content.trim();
40
+ if (trimmed.startsWith("<!DOCTYPE") || trimmed.startsWith("<html") || trimmed.startsWith("<head")) {
41
+ return "html";
42
+ }
43
+ if (trimmed.startsWith("<") && /<\w+[^>]*>/.test(trimmed.substring(0, 200))) {
44
+ return "html";
45
+ }
46
+ return "md";
47
+ }
48
+
49
+ /** text 模式的自定义格式化 */
50
+ function formatPublishText(data) {
51
+ let out = "✓ 发布成功\n";
52
+ out += ` URL: ${data.online_url}\n`;
53
+ if (data.message) {
54
+ out += ` 信息: ${data.message}\n`;
55
+ }
56
+ return out.trimEnd();
57
+ }
58
+
59
+ /**
60
+ * text 模式的网页列表格式化
61
+ * @param {{webpages?: Array, page?: number, page_size?: number, total?: number}} data
62
+ * @returns {string}
63
+ */
64
+ function formatWebpageListText(data) {
65
+ const webpages = data.webpages || [];
66
+ if (!webpages.length) return "没有已发布网页。";
67
+
68
+ const header = `已发布网页(第 ${data.page} 页,每页 ${data.page_size} 条,共 ${data.total} 条)`;
69
+ const entries = webpages.map((webpage) => {
70
+ const lines = [`[${webpage.id}] ${webpage.title || "未命名网页"}`];
71
+ if (webpage.online_url) lines.push(` URL: ${webpage.online_url}`);
72
+ if (webpage.source) lines.push(` 来源: ${webpage.source}`);
73
+ if (webpage.created_at) lines.push(` 发布时间: ${webpage.created_at}`);
74
+ return lines.join("\n");
75
+ });
76
+ return [header, ...entries].join("\n");
77
+ }
78
+
79
+ /**
80
+ * text 模式的网页删除格式化
81
+ * @param {{webpage_id?: number|null, message?: string}} data
82
+ * @returns {string}
83
+ */
84
+ function formatWebpageDeleteText(data) {
85
+ let output = `已删除网页 ID: ${data.webpage_id}`;
86
+ if (data.message) output += `\n信息: ${data.message}`;
87
+ return output;
88
+ }
89
+
90
+ /**
91
+ * API 业务失败时抛出统一错误
92
+ * @param {{success?: boolean, message?: string}} result
93
+ */
94
+ function throwOnApiFailure(result) {
95
+ if (result && result.success === false) {
96
+ throw Object.assign(new Error(result.message || "API 请求失败"), { type: "api_error" });
97
+ }
98
+ }
99
+
100
+ /**
101
+ * webpage publish 命令 — 统一发布 Markdown 或 HTML 为在线网页
102
+ * @param {string} input - Markdown/HTML 代码或文件路径
103
+ * @param {{ apiKey?: string, format?: string, title?: string, copyButton?: boolean, type?: string }} options
104
+ */
105
+ async function publishCommand(input, options = {}) {
106
+ const format = options.format || "json";
107
+ const apiKey = resolveKeyOrExit(options.apiKey, format);
108
+
109
+ try {
110
+ const { content, filePath } = resolveInput(input);
111
+ let type = options.type;
112
+ if (!type) {
113
+ type = detectType(content, filePath);
114
+ } else if (type !== "md" && type !== "html") {
115
+ outputError({ message: `无效的 --type 值: "${type}",可选: md 或 html` }, format);
116
+ process.exit(1);
117
+ }
118
+
119
+ let result;
120
+ if (type === "html") {
121
+ result = await publishHtml(apiKey, content, options.title);
122
+ } else {
123
+ result = await publishMdToHtml(apiKey, content, options.title, options.copyButton === true);
124
+ }
125
+ outputResult(result, format, formatPublishText);
126
+ process.exit(0);
127
+ } catch (err) {
128
+ handleApiError(err, format);
129
+ process.exit(1);
130
+ }
131
+ }
132
+
133
+ /**
134
+ * webpage list 命令 — 查询当前用户已发布的网页
135
+ * @param {{ apiKey?: string, format?: string, page?: string, pageSize?: string }} options
136
+ */
137
+ async function listCommand(options = {}) {
138
+ const format = options.format || "json";
139
+ const apiKey = resolveKeyOrExit(options.apiKey, format);
140
+
141
+ try {
142
+ const result = await getPublishedWebpages(apiKey, {
143
+ page: options.page,
144
+ pageSize: options.pageSize,
145
+ });
146
+ throwOnApiFailure(result);
147
+ outputResult(result, format, formatWebpageListText);
148
+ process.exit(0);
149
+ } catch (err) {
150
+ handleApiError(err, format);
151
+ process.exit(1);
152
+ }
153
+ }
154
+
155
+ /**
156
+ * webpage delete 命令 — 删除指定的已发布网页
157
+ * @param {string} webpageId
158
+ * @param {{ apiKey?: string, format?: string }} options
159
+ */
160
+ async function deleteCommand(webpageId, options = {}) {
161
+ const format = options.format || "json";
162
+ const apiKey = resolveKeyOrExit(options.apiKey, format);
163
+
164
+ try {
165
+ const result = await deletePublishedWebpage(apiKey, webpageId);
166
+ throwOnApiFailure(result);
167
+ outputResult(result, format, formatWebpageDeleteText);
168
+ process.exit(0);
169
+ } catch (err) {
170
+ handleApiError(err, format);
171
+ process.exit(1);
172
+ }
173
+ }
174
+
175
+ module.exports = { publishCommand, listCommand, deleteCommand };
@@ -0,0 +1,69 @@
1
+ const { urlToMarkdown } = require("../api");
2
+ const { outputResult, resolveKeyOrExit, handleApiError } = require("../lib/cli-utils");
3
+
4
+ const TEXT_PREVIEW_LINES = 20;
5
+
6
+ /**
7
+ * 把任意网页(含微信公众号文章)转成 Markdown
8
+ * @param {string} url 网页链接
9
+ * @param {object} options
10
+ */
11
+ async function toMdCommand(url, options = {}) {
12
+ const format = options.format || "json";
13
+ const apiKey = resolveKeyOrExit(options.apiKey, format);
14
+ try {
15
+ const result = await urlToMarkdown(apiKey, { url, timeout: options.timeout });
16
+ outputResult(result, format, formatToMdText);
17
+ process.exit(0);
18
+ } catch (err) { handleApiError(err, format); process.exit(1); }
19
+ }
20
+
21
+ /**
22
+ * 从 Markdown 的 frontmatter 里取一个字段(没有则返回空串)
23
+ * @param {string} markdown
24
+ * @param {string} key
25
+ * @returns {string}
26
+ */
27
+ function readFrontmatter(markdown, key) {
28
+ if (typeof markdown !== "string" || !markdown.startsWith("---")) return "";
29
+ const end = markdown.indexOf("\n---", 3);
30
+ if (end === -1) return "";
31
+ const block = markdown.slice(3, end);
32
+ const match = block.match(new RegExp(`^${key}:\\s*"?([^"\\n]*)"?\\s*$`, "m"));
33
+ return match ? match[1].trim() : "";
34
+ }
35
+
36
+ /**
37
+ * text 输出:Markdown 结果预览
38
+ * @param {object} resp
39
+ * @returns {string}
40
+ */
41
+ function formatToMdText(resp = {}) {
42
+ const markdown = typeof resp.markdown === "string" ? resp.markdown : "";
43
+ if (!markdown.trim()) return "✗ 未取到 Markdown 内容";
44
+
45
+ const lines = ["✓ 已转换为 Markdown"];
46
+ const title = readFrontmatter(markdown, "title");
47
+ const words = readFrontmatter(markdown, "word_count");
48
+ if (title) lines.push(` 标题: ${title}`);
49
+ if (words) lines.push(` 字数: ${words}`);
50
+ lines.push(` 字符: ${markdown.length}`);
51
+
52
+ const body = markdown.trimEnd().split("\n");
53
+ // 公众号链如果被裁掉 chksm 参数,微信只返回页面框架,抽出来就是“赞/在看”这类壳
54
+ if (!title && markdown.length < 400) {
55
+ lines.push(" 提示: 内容过少——公众号链接若被裁掉 chksm 参数,只能抓到页面框架,请用原始完整链接");
56
+ }
57
+ lines.push("");
58
+ lines.push(...body.slice(0, TEXT_PREVIEW_LINES));
59
+ if (body.length > TEXT_PREVIEW_LINES) {
60
+ lines.push(`…(共 ${body.length} 行,省略 ${body.length - TEXT_PREVIEW_LINES} 行;用 -f json 取全文)`);
61
+ }
62
+ return lines.join("\n");
63
+ }
64
+
65
+ module.exports = {
66
+ toMdCommand,
67
+ formatToMdText,
68
+ readFrontmatter,
69
+ };
@@ -1,99 +1,99 @@
1
- const { webSearch } = require("../api");
2
- const { outputResult, outputError, resolveKeyOrExit, handleApiError } = require("../lib/cli-utils");
3
-
4
- /** text 模式的自定义格式化 */
5
- function formatSearchText(data = {}) {
6
- const inner = data.data?.Result || {};
7
- let out = "✓ 搜索成功\n";
8
- out += ` 结果数: ${inner.ResultCount}\n`;
9
- out += ` 耗时: ${inner.TimeCost}ms\n\n`;
10
-
11
- if (inner.WebResults) {
12
- inner.WebResults.forEach((item) => {
13
- out += `[${item.SortId}] ${item.Title}\n`;
14
- if (item.SiteName) out += ` 站点: ${item.SiteName}\n`;
15
- if (item.Url) out += ` URL: ${item.Url}\n`;
16
- if (item.Snippet) out += ` 摘要: ${item.Snippet}\n`;
17
- if (item.Summary) out += ` 内容摘要: ${item.Summary}\n`;
18
- if (item.PublishTime) out += ` 发布时间: ${item.PublishTime}\n`;
19
- out += `\n`;
20
- });
21
- }
22
-
23
- if (inner.ImageResults) {
24
- inner.ImageResults.forEach((item) => {
25
- out += `[${item.SortId}] ${item.Title}\n`;
26
- if (item.SiteName) out += ` 来源: ${item.SiteName}\n`;
27
- if (item.Image?.Url) out += ` 图片: ${item.Image.Url}\n`;
28
- if (item.Image?.Width && item.Image?.Height) {
29
- out += ` 尺寸: ${item.Image.Width}x${item.Image.Height}\n`;
30
- }
31
- if (item.Url) out += ` 来源页面: ${item.Url}\n`;
32
- out += `\n`;
33
- });
34
- }
35
-
36
- return out.trimEnd();
37
- }
38
-
39
- /**
40
- * search 命令
41
- * @param {string} query - 搜索关键词
42
- * @param {"web"|"image"} searchType - 搜索类型
43
- * @param {object} options - 命令行选项
44
- */
45
- async function searchCommand(query, searchType, options = {}) {
46
- const format = options.format || "json";
47
- const apiKey = resolveKeyOrExit(options.apiKey, format);
48
-
49
- const params = { query, searchType };
50
-
51
- if (options.count !== undefined) {
52
- params.count = options.count;
53
- }
54
- if (options.timeRange) {
55
- params.timeRange = options.timeRange;
56
- }
57
- if (searchType === "web") {
58
- if (options.needContent) params.needContent = true;
59
- if (options.needUrl) params.needUrl = true;
60
- if (options.sites) params.sites = options.sites;
61
- if (options.blockHosts) params.blockHosts = options.blockHosts;
62
- const authInfoLevel = options.authInfoLevel ?? options.authLevel;
63
- if (authInfoLevel !== undefined) {
64
- params.authInfoLevel = authInfoLevel;
65
- }
66
- if (options.contentFormat) params.contentFormats = options.contentFormat;
67
- if (options.industry) params.industry = options.industry;
68
- } else {
69
- for (const [optionName, paramName] of [
70
- ["imageWidthMin", "imageWidthMin"],
71
- ["imageWidthMax", "imageWidthMax"],
72
- ["imageHeightMin", "imageHeightMin"],
73
- ["imageHeightMax", "imageHeightMax"],
74
- ]) {
75
- if (options[optionName] !== undefined) {
76
- params[paramName] = options[optionName];
77
- }
78
- }
79
- if (options.imageShapes) params.imageShapes = options.imageShapes;
80
- }
81
- if (options.queryRewrite) {
82
- params.queryRewrite = true;
83
- }
84
-
85
- try {
86
- const result = await webSearch(apiKey, params);
87
- outputResult(result, format, formatSearchText);
88
- process.exit(0);
89
- } catch (err) {
90
- if (err.type === "validation_error") {
91
- outputError(err, format);
92
- } else {
93
- handleApiError(err, format);
94
- }
95
- process.exit(1);
96
- }
97
- }
98
-
99
- module.exports = { searchCommand, formatSearchText };
1
+ const { webSearch } = require("../api");
2
+ const { outputResult, outputError, resolveKeyOrExit, handleApiError } = require("../lib/cli-utils");
3
+
4
+ /** text 模式的自定义格式化 */
5
+ function formatSearchText(data = {}) {
6
+ const inner = data.data?.Result || {};
7
+ let out = "✓ 搜索成功\n";
8
+ out += ` 结果数: ${inner.ResultCount}\n`;
9
+ out += ` 耗时: ${inner.TimeCost}ms\n\n`;
10
+
11
+ if (inner.WebResults) {
12
+ inner.WebResults.forEach((item) => {
13
+ out += `[${item.SortId}] ${item.Title}\n`;
14
+ if (item.SiteName) out += ` 站点: ${item.SiteName}\n`;
15
+ if (item.Url) out += ` URL: ${item.Url}\n`;
16
+ if (item.Snippet) out += ` 摘要: ${item.Snippet}\n`;
17
+ if (item.Summary) out += ` 内容摘要: ${item.Summary}\n`;
18
+ if (item.PublishTime) out += ` 发布时间: ${item.PublishTime}\n`;
19
+ out += `\n`;
20
+ });
21
+ }
22
+
23
+ if (inner.ImageResults) {
24
+ inner.ImageResults.forEach((item) => {
25
+ out += `[${item.SortId}] ${item.Title}\n`;
26
+ if (item.SiteName) out += ` 来源: ${item.SiteName}\n`;
27
+ if (item.Image?.Url) out += ` 图片: ${item.Image.Url}\n`;
28
+ if (item.Image?.Width && item.Image?.Height) {
29
+ out += ` 尺寸: ${item.Image.Width}x${item.Image.Height}\n`;
30
+ }
31
+ if (item.Url) out += ` 来源页面: ${item.Url}\n`;
32
+ out += `\n`;
33
+ });
34
+ }
35
+
36
+ return out.trimEnd();
37
+ }
38
+
39
+ /**
40
+ * search 命令
41
+ * @param {string} query - 搜索关键词
42
+ * @param {"web"|"image"} searchType - 搜索类型
43
+ * @param {object} options - 命令行选项
44
+ */
45
+ async function searchCommand(query, searchType, options = {}) {
46
+ const format = options.format || "json";
47
+ const apiKey = resolveKeyOrExit(options.apiKey, format);
48
+
49
+ const params = { query, searchType };
50
+
51
+ if (options.count !== undefined) {
52
+ params.count = options.count;
53
+ }
54
+ if (options.timeRange) {
55
+ params.timeRange = options.timeRange;
56
+ }
57
+ if (searchType === "web") {
58
+ if (options.needContent) params.needContent = true;
59
+ if (options.needUrl) params.needUrl = true;
60
+ if (options.sites) params.sites = options.sites;
61
+ if (options.blockHosts) params.blockHosts = options.blockHosts;
62
+ const authInfoLevel = options.authInfoLevel ?? options.authLevel;
63
+ if (authInfoLevel !== undefined) {
64
+ params.authInfoLevel = authInfoLevel;
65
+ }
66
+ if (options.contentFormat) params.contentFormats = options.contentFormat;
67
+ if (options.industry) params.industry = options.industry;
68
+ } else {
69
+ for (const [optionName, paramName] of [
70
+ ["imageWidthMin", "imageWidthMin"],
71
+ ["imageWidthMax", "imageWidthMax"],
72
+ ["imageHeightMin", "imageHeightMin"],
73
+ ["imageHeightMax", "imageHeightMax"],
74
+ ]) {
75
+ if (options[optionName] !== undefined) {
76
+ params[paramName] = options[optionName];
77
+ }
78
+ }
79
+ if (options.imageShapes) params.imageShapes = options.imageShapes;
80
+ }
81
+ if (options.queryRewrite) {
82
+ params.queryRewrite = true;
83
+ }
84
+
85
+ try {
86
+ const result = await webSearch(apiKey, params);
87
+ outputResult(result, format, formatSearchText);
88
+ process.exit(0);
89
+ } catch (err) {
90
+ if (err.type === "validation_error") {
91
+ outputError(err, format);
92
+ } else {
93
+ handleApiError(err, format);
94
+ }
95
+ process.exit(1);
96
+ }
97
+ }
98
+
99
+ module.exports = { searchCommand, formatSearchText };