feishu-docs-cli 0.1.0-beta.8 → 1.0.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 (86) hide show
  1. package/README.md +70 -13
  2. package/README.zh.md +53 -13
  3. package/dist/auth.js +31 -17
  4. package/dist/auth.js.map +1 -1
  5. package/dist/cli.js +16 -4
  6. package/dist/cli.js.map +1 -1
  7. package/dist/client.d.ts +4 -2
  8. package/dist/client.js +177 -61
  9. package/dist/client.js.map +1 -1
  10. package/dist/commands/authorize.d.ts +3 -0
  11. package/dist/commands/authorize.js +16 -34
  12. package/dist/commands/authorize.js.map +1 -1
  13. package/dist/commands/cp.d.ts +9 -0
  14. package/dist/commands/cp.js +70 -0
  15. package/dist/commands/cp.js.map +1 -0
  16. package/dist/commands/create.js +21 -7
  17. package/dist/commands/create.js.map +1 -1
  18. package/dist/commands/delete.js +53 -54
  19. package/dist/commands/delete.js.map +1 -1
  20. package/dist/commands/login.js +6 -2
  21. package/dist/commands/login.js.map +1 -1
  22. package/dist/commands/ls.js +38 -38
  23. package/dist/commands/ls.js.map +1 -1
  24. package/dist/commands/mkdir.d.ts +6 -0
  25. package/dist/commands/mkdir.js +49 -0
  26. package/dist/commands/mkdir.js.map +1 -0
  27. package/dist/commands/mv.d.ts +9 -0
  28. package/dist/commands/mv.js +72 -0
  29. package/dist/commands/mv.js.map +1 -0
  30. package/dist/commands/read.d.ts +1 -1
  31. package/dist/commands/read.js +17 -354
  32. package/dist/commands/read.js.map +1 -1
  33. package/dist/commands/search.js +57 -55
  34. package/dist/commands/search.js.map +1 -1
  35. package/dist/commands/share.d.ts +1 -1
  36. package/dist/commands/share.js +164 -91
  37. package/dist/commands/share.js.map +1 -1
  38. package/dist/commands/update.js +43 -60
  39. package/dist/commands/update.js.map +1 -1
  40. package/dist/commands/wiki.js +8 -20
  41. package/dist/commands/wiki.js.map +1 -1
  42. package/dist/parser/block-types.d.ts +0 -1
  43. package/dist/parser/block-types.js +0 -22
  44. package/dist/parser/block-types.js.map +1 -1
  45. package/dist/parser/blocks-to-md.d.ts +10 -18
  46. package/dist/parser/blocks-to-md.js +341 -450
  47. package/dist/parser/blocks-to-md.js.map +1 -1
  48. package/dist/scopes.d.ts +5 -47
  49. package/dist/scopes.js +9 -54
  50. package/dist/scopes.js.map +1 -1
  51. package/dist/services/block-writer.d.ts +3 -2
  52. package/dist/services/block-writer.js +29 -13
  53. package/dist/services/block-writer.js.map +1 -1
  54. package/dist/services/doc-blocks.d.ts +1 -1
  55. package/dist/services/doc-blocks.js +1 -1
  56. package/dist/services/doc-blocks.js.map +1 -1
  57. package/dist/services/doc-enrichment.d.ts +64 -0
  58. package/dist/services/doc-enrichment.js +397 -0
  59. package/dist/services/doc-enrichment.js.map +1 -0
  60. package/dist/services/image-download.d.ts +31 -0
  61. package/dist/services/image-download.js +127 -0
  62. package/dist/services/image-download.js.map +1 -0
  63. package/dist/services/markdown-convert.d.ts +20 -0
  64. package/dist/services/markdown-convert.js +55 -1
  65. package/dist/services/markdown-convert.js.map +1 -1
  66. package/dist/services/wiki-nodes.d.ts +1 -1
  67. package/dist/services/wiki-nodes.js +2 -3
  68. package/dist/services/wiki-nodes.js.map +1 -1
  69. package/dist/types/api-responses.d.ts +34 -0
  70. package/dist/types/api-responses.js +8 -0
  71. package/dist/types/api-responses.js.map +1 -0
  72. package/dist/types/index.d.ts +9 -17
  73. package/dist/utils/concurrency.d.ts +12 -0
  74. package/dist/utils/concurrency.js +37 -0
  75. package/dist/utils/concurrency.js.map +1 -0
  76. package/dist/utils/errors.d.ts +3 -1
  77. package/dist/utils/errors.js +11 -7
  78. package/dist/utils/errors.js.map +1 -1
  79. package/dist/utils/retry.d.ts +49 -0
  80. package/dist/utils/retry.js +70 -0
  81. package/dist/utils/retry.js.map +1 -0
  82. package/dist/utils/scope-prompt.d.ts +23 -18
  83. package/dist/utils/scope-prompt.js +62 -51
  84. package/dist/utils/scope-prompt.js.map +1 -1
  85. package/package.json +3 -1
  86. package/skills/feishu-docs/SKILL.md +37 -2
@@ -0,0 +1,397 @@
1
+ /**
2
+ * Document enrichment service.
3
+ *
4
+ * Extracts and resolves embedded content from document blocks:
5
+ * images, bitable tables, spreadsheets, whiteboards, and @mentions.
6
+ * All enrichment runs in parallel with a configurable concurrency limit.
7
+ */
8
+ import { createClient, fetchWithAuth, fetchBinaryWithAuth, getTenantToken, } from "../client.js";
9
+ import { writeFile, mkdir } from "node:fs/promises";
10
+ import { tmpdir } from "node:os";
11
+ import { join } from "node:path";
12
+ import { BlockType } from "../parser/block-types.js";
13
+ import { CliError } from "../utils/errors.js";
14
+ import { withScopeRecovery } from "../utils/scope-prompt.js";
15
+ import { downloadImages } from "./image-download.js";
16
+ import { pLimit } from "../utils/concurrency.js";
17
+ // ── Constants ──
18
+ const TMP_URL_BATCH_SIZE = 5;
19
+ // ── Token extraction helpers (internal) ──
20
+ function extractFileTokens(blocks) {
21
+ const tokens = [];
22
+ for (const block of blocks) {
23
+ if (block.block_type === BlockType.IMAGE && block.image?.token) {
24
+ tokens.push(block.image.token);
25
+ }
26
+ if (block.block_type === BlockType.FILE && block.file?.token) {
27
+ tokens.push(block.file.token);
28
+ }
29
+ }
30
+ return tokens;
31
+ }
32
+ function extractBitableTokens(blocks) {
33
+ const tokens = [];
34
+ for (const block of blocks) {
35
+ if (block.block_type === BlockType.BITABLE && block.bitable?.token) {
36
+ tokens.push(block.bitable.token);
37
+ }
38
+ }
39
+ return tokens;
40
+ }
41
+ function extractSheetTokens(blocks) {
42
+ const tokens = [];
43
+ for (const block of blocks) {
44
+ if (block.block_type === BlockType.SHEET && block.sheet?.token) {
45
+ tokens.push(block.sheet.token);
46
+ }
47
+ }
48
+ return tokens;
49
+ }
50
+ function extractBoardTokens(blocks) {
51
+ const tokens = [];
52
+ for (const block of blocks) {
53
+ if (block.block_type === BlockType.BOARD &&
54
+ block.board?.token) {
55
+ tokens.push(block.board.token);
56
+ }
57
+ }
58
+ return tokens;
59
+ }
60
+ function extractMentionUserIds(blocks) {
61
+ const ids = new Set();
62
+ for (const block of blocks) {
63
+ const elementSources = [
64
+ block.text?.elements,
65
+ block.heading1?.elements,
66
+ block.heading2?.elements,
67
+ block.heading3?.elements,
68
+ block.heading4?.elements,
69
+ block.heading5?.elements,
70
+ block.heading6?.elements,
71
+ block.heading7?.elements,
72
+ block.heading8?.elements,
73
+ block.heading9?.elements,
74
+ block.bullet?.elements,
75
+ block.ordered?.elements,
76
+ block.todo?.elements,
77
+ block.quote?.elements,
78
+ ];
79
+ for (const elements of elementSources) {
80
+ if (!elements)
81
+ continue;
82
+ for (const el of elements) {
83
+ if (el.mention_user?.user_id) {
84
+ ids.add(el.mention_user.user_id);
85
+ }
86
+ }
87
+ }
88
+ }
89
+ return [...ids];
90
+ }
91
+ // ── Data fetching functions (exported for testing) ──
92
+ /**
93
+ * Batch get temporary download URLs for file tokens (images, files).
94
+ * The API accepts at most 5 file_tokens per request, so larger sets
95
+ * are split into chunks automatically.
96
+ */
97
+ async function batchGetTmpUrls(authInfo, fileTokens) {
98
+ if (fileTokens.length === 0)
99
+ return new Map();
100
+ const urlMap = new Map();
101
+ for (let i = 0; i < fileTokens.length; i += TMP_URL_BATCH_SIZE) {
102
+ const chunk = fileTokens.slice(i, i + TMP_URL_BATCH_SIZE);
103
+ const res = await fetchWithAuth(authInfo, "/open-apis/drive/v1/medias/batch_get_tmp_download_url", { params: { file_tokens: chunk } });
104
+ const data = res?.data;
105
+ const items = (data?.tmp_download_urls || []);
106
+ for (const item of items) {
107
+ urlMap.set(item.file_token, item.tmp_download_url);
108
+ }
109
+ }
110
+ return urlMap;
111
+ }
112
+ /**
113
+ * Fetch bitable fields and records, return as renderable data.
114
+ */
115
+ export async function fetchBitableData(authInfo, fullToken) {
116
+ const idx = fullToken.lastIndexOf("_tbl");
117
+ if (idx === -1) {
118
+ process.stderr.write(`feishu-docs: warning: 多维表格 token 格式无法解析: ${fullToken}\n`);
119
+ return null;
120
+ }
121
+ const appToken = fullToken.slice(0, idx);
122
+ const tableId = fullToken.slice(idx + 1);
123
+ const [fieldsRes, recordsRes] = await Promise.all([
124
+ fetchWithAuth(authInfo, `/open-apis/bitable/v1/apps/${encodeURIComponent(appToken)}/tables/${encodeURIComponent(tableId)}/fields`, {}),
125
+ fetchWithAuth(authInfo, `/open-apis/bitable/v1/apps/${encodeURIComponent(appToken)}/tables/${encodeURIComponent(tableId)}/records`, { params: { page_size: 100 } }),
126
+ ]);
127
+ const fieldsData = fieldsRes.data;
128
+ const recordsData = recordsRes.data;
129
+ const fields = (fieldsData?.items || []).map((f) => f.field_name);
130
+ const records = (recordsData?.items || []).map((r) => {
131
+ return fields.map((name) => {
132
+ const val = r.fields?.[name];
133
+ if (val === undefined || val === null)
134
+ return "";
135
+ if (typeof val === "object")
136
+ return JSON.stringify(val);
137
+ return String(val);
138
+ });
139
+ });
140
+ return { fields, records };
141
+ }
142
+ /**
143
+ * Fetch sheet metadata and cell values, return as renderable data.
144
+ */
145
+ export async function fetchSheetData(authInfo, sheetToken) {
146
+ // Sheet tokens embedded in docs have format: {spreadsheetToken}_{sheetId}
147
+ // The sheets API needs just the spreadsheet token; sheetId selects the tab.
148
+ const underscoreIdx = sheetToken.lastIndexOf("_");
149
+ const spreadsheetToken = underscoreIdx > 0 ? sheetToken.slice(0, underscoreIdx) : sheetToken;
150
+ const embeddedSheetId = underscoreIdx > 0 ? sheetToken.slice(underscoreIdx + 1) : undefined;
151
+ const metaRes = await fetchWithAuth(authInfo, `/open-apis/sheets/v2/spreadsheets/${encodeURIComponent(spreadsheetToken)}/metainfo`, {});
152
+ const metaData = metaRes.data;
153
+ const sheets = (metaData?.sheets || []);
154
+ if (sheets.length === 0)
155
+ return null;
156
+ // Prefer the embedded sheet id; fall back to first sheet
157
+ // Note: metainfo API returns camelCase field names (sheetId, not sheet_id)
158
+ const targetSheet = embeddedSheetId
159
+ ? sheets.find((s) => s.sheetId === embeddedSheetId) || sheets[0]
160
+ : sheets[0];
161
+ const sheetId = targetSheet.sheetId;
162
+ // Suppress title when it equals sheetId (default meaningless title)
163
+ const rawTitle = targetSheet.title || "";
164
+ const title = rawTitle === sheetId ? "" : rawTitle;
165
+ const valuesRes = await fetchWithAuth(authInfo, `/open-apis/sheets/v2/spreadsheets/${encodeURIComponent(spreadsheetToken)}/values/${encodeURIComponent(sheetId)}`, { params: { valueRenderOption: "ToString" } });
166
+ const valuesData = valuesRes.data;
167
+ const rows = (valuesData?.valueRange?.values ||
168
+ []);
169
+ if (rows.length === 0)
170
+ return null;
171
+ const maxRows = 101; // first row = header + 100 data rows
172
+ const limitedRows = rows.length > maxRows ? rows.slice(0, maxRows) : rows;
173
+ const fields = limitedRows[0].map((cell) => String(cell ?? ""));
174
+ const records = limitedRows
175
+ .slice(1)
176
+ .map((row) => fields.map((_, i) => String(row[i] ?? "")));
177
+ return { fields, records, title, truncated: rows.length > maxRows };
178
+ }
179
+ /**
180
+ * Download board/whiteboard as image and save to temp file.
181
+ * Returns the local file path, or null on failure.
182
+ */
183
+ export async function fetchBoardImage(authInfo, boardToken) {
184
+ const buf = await fetchBinaryWithAuth(authInfo, `/open-apis/board/v1/whiteboards/${encodeURIComponent(boardToken)}/download_as_image`);
185
+ if (buf.byteLength === 0)
186
+ return null;
187
+ const dir = join(tmpdir(), "feishu-docs");
188
+ await mkdir(dir, { recursive: true });
189
+ const filePath = join(dir, `board-${boardToken}.png`);
190
+ await writeFile(filePath, Buffer.from(buf));
191
+ return filePath;
192
+ }
193
+ /**
194
+ * Batch resolve user IDs to display names.
195
+ * Uses tenant_access_token + contact:user.base:readonly to get name fields.
196
+ */
197
+ export async function resolveUserNames(authInfo, userIds) {
198
+ const nameMap = new Map();
199
+ // Use tenant token for contact API (contact:user.base:readonly is app-identity permission)
200
+ let tenantToken;
201
+ try {
202
+ tenantToken = await getTenantToken(authInfo);
203
+ }
204
+ catch {
205
+ // Fallback: try authen API with user token for current user
206
+ try {
207
+ const self = await fetchWithAuth(authInfo, "/open-apis/authen/v1/user_info", {});
208
+ const selfData = self?.data;
209
+ if (selfData?.open_id && selfData?.name) {
210
+ nameMap.set(selfData.open_id, selfData.name);
211
+ }
212
+ }
213
+ catch {
214
+ // no way to resolve
215
+ }
216
+ return nameMap;
217
+ }
218
+ // Batch query users (up to 50 per request)
219
+ const BATCH = 50;
220
+ for (let i = 0; i < userIds.length; i += BATCH) {
221
+ const batch = userIds.slice(i, i + BATCH);
222
+ try {
223
+ const params = {
224
+ user_id_type: "open_id",
225
+ user_ids: batch,
226
+ };
227
+ const tenantAuthInfo = {
228
+ ...authInfo,
229
+ mode: "tenant",
230
+ tenantToken,
231
+ };
232
+ const res = await fetchWithAuth(tenantAuthInfo, "/open-apis/contact/v3/users/batch", { params });
233
+ const items = (res?.data?.user_list ||
234
+ []);
235
+ for (const user of items) {
236
+ if (user.open_id && user.name) {
237
+ nameMap.set(user.open_id, user.name);
238
+ }
239
+ }
240
+ }
241
+ catch {
242
+ // skip batch errors
243
+ }
244
+ }
245
+ return nameMap;
246
+ }
247
+ // ── Resolve image URLs (images + download) ──
248
+ async function resolveImageUrls(authInfo, blocks, globalOpts) {
249
+ const fileTokens = extractFileTokens(blocks);
250
+ if (fileTokens.length === 0)
251
+ return new Map();
252
+ const tmpUrlMap = await withScopeRecovery(async () => {
253
+ const { authInfo: freshAuth } = await createClient(globalOpts);
254
+ return batchGetTmpUrls(freshAuth, fileTokens);
255
+ }, globalOpts, ["drive:drive"]);
256
+ return downloadImages(tmpUrlMap);
257
+ }
258
+ // ── Main entry point ──
259
+ /**
260
+ * Enrich document blocks with embedded content data.
261
+ *
262
+ * Resolves images, bitable tables, spreadsheets, board images,
263
+ * and @mention user names in parallel with concurrency control.
264
+ *
265
+ * @param authInfo - Authentication context
266
+ * @param blocks - Document blocks to enrich
267
+ * @param globalOpts - Global CLI options
268
+ * @param options - Control which enrichments to run and concurrency
269
+ * @returns Enrichment maps for blocksToMarkdown rendering
270
+ */
271
+ export async function enrichBlocks(authInfo, blocks, globalOpts, options = {}) {
272
+ const opts = {
273
+ images: options.images ?? true,
274
+ bitable: options.bitable ?? true,
275
+ sheet: options.sheet ?? true,
276
+ board: options.board ?? true,
277
+ mentions: options.mentions ?? true,
278
+ concurrency: options.concurrency ?? 5,
279
+ };
280
+ const limit = pLimit(opts.concurrency);
281
+ const result = {
282
+ imageUrlMap: new Map(),
283
+ userNameMap: new Map(),
284
+ bitableDataMap: new Map(),
285
+ boardImageMap: new Map(),
286
+ sheetDataMap: new Map(),
287
+ };
288
+ const tasks = [];
289
+ // Image enrichment
290
+ if (opts.images) {
291
+ const fileTokens = extractFileTokens(blocks);
292
+ if (fileTokens.length > 0) {
293
+ tasks.push(limit(async () => {
294
+ try {
295
+ const map = await resolveImageUrls(authInfo, blocks, globalOpts);
296
+ for (const [k, v] of map)
297
+ result.imageUrlMap.set(k, v);
298
+ }
299
+ catch {
300
+ process.stderr.write("feishu-docs: warning: 获取图片/文件链接失败,链接将为空\n");
301
+ }
302
+ }));
303
+ }
304
+ }
305
+ // Mention enrichment
306
+ if (opts.mentions) {
307
+ const mentionUserIds = extractMentionUserIds(blocks);
308
+ if (mentionUserIds.length > 0) {
309
+ tasks.push(limit(async () => {
310
+ try {
311
+ const map = await resolveUserNames(authInfo, mentionUserIds);
312
+ for (const [k, v] of map)
313
+ result.userNameMap.set(k, v);
314
+ }
315
+ catch {
316
+ process.stderr.write("feishu-docs: warning: 解析 @用户 名称失败\n");
317
+ }
318
+ }));
319
+ }
320
+ }
321
+ // Bitable enrichment
322
+ if (opts.bitable) {
323
+ const bitableTokens = extractBitableTokens(blocks);
324
+ for (const token of bitableTokens) {
325
+ tasks.push(limit(async () => {
326
+ try {
327
+ const data = await fetchBitableData(authInfo, token);
328
+ if (data)
329
+ result.bitableDataMap.set(token, data);
330
+ }
331
+ catch (err) {
332
+ if (err instanceof CliError &&
333
+ (err.errorType === "PERMISSION_DENIED" ||
334
+ err.errorType === "SCOPE_MISSING")) {
335
+ process.stderr.write(`feishu-docs: warning: 获取多维表格数据权限不足: ${token}\n` +
336
+ ' 请在飞书开发者后台开通权限后运行 feishu-docs authorize --scope "bitable:app:readonly"\n');
337
+ }
338
+ else {
339
+ process.stderr.write(`feishu-docs: warning: 获取多维表格数据失败: ${token}\n`);
340
+ }
341
+ }
342
+ }));
343
+ }
344
+ }
345
+ // Board enrichment
346
+ if (opts.board) {
347
+ const boardTokens = extractBoardTokens(blocks);
348
+ for (const token of boardTokens) {
349
+ tasks.push(limit(async () => {
350
+ try {
351
+ const filePath = await fetchBoardImage(authInfo, token);
352
+ if (filePath)
353
+ result.boardImageMap.set(token, filePath);
354
+ }
355
+ catch (err) {
356
+ if (err instanceof CliError &&
357
+ (err.errorType === "PERMISSION_DENIED" ||
358
+ err.errorType === "SCOPE_MISSING")) {
359
+ process.stderr.write(`feishu-docs: warning: 获取画板图片权限不足: ${token}\n` +
360
+ ' 请在飞书开发者后台开通权限后运行 feishu-docs authorize --scope "board:whiteboard:node:read"\n');
361
+ }
362
+ else {
363
+ process.stderr.write(`feishu-docs: warning: 获取画板图片失败: ${token}\n`);
364
+ }
365
+ }
366
+ }));
367
+ }
368
+ }
369
+ // Sheet enrichment
370
+ if (opts.sheet) {
371
+ const sheetTokens = extractSheetTokens(blocks);
372
+ for (const token of sheetTokens) {
373
+ tasks.push(limit(async () => {
374
+ try {
375
+ const data = await fetchSheetData(authInfo, token);
376
+ if (data)
377
+ result.sheetDataMap.set(token, data);
378
+ }
379
+ catch (err) {
380
+ if (err instanceof CliError &&
381
+ (err.errorType === "PERMISSION_DENIED" ||
382
+ err.errorType === "SCOPE_MISSING")) {
383
+ process.stderr.write(`feishu-docs: warning: 获取电子表格数据权限不足: ${token}\n` +
384
+ ' 请在飞书开发者后台开通权限后运行 feishu-docs authorize --scope "sheets:spreadsheet:readonly"\n');
385
+ }
386
+ else {
387
+ const msg = err instanceof Error ? err.message : String(err);
388
+ process.stderr.write(`feishu-docs: warning: 获取电子表格数据失败: ${token} (${msg})\n`);
389
+ }
390
+ }
391
+ }));
392
+ }
393
+ }
394
+ await Promise.allSettled(tasks);
395
+ return result;
396
+ }
397
+ //# sourceMappingURL=doc-enrichment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doc-enrichment.js","sourceRoot":"","sources":["../../src/services/doc-enrichment.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Download document images to local persistent storage.
3
+ *
4
+ * Images are saved to ~/.feishu-docs/images/{fileToken}.{ext} and served
5
+ * via local file paths in Markdown output. Already-downloaded images are
6
+ * skipped (disk cache by file_token).
7
+ */
8
+ export declare const IMAGES_DIR: string;
9
+ export declare const CONTENT_TYPE_EXT: Record<string, string>;
10
+ /** Maximum age for cached images (30 days in milliseconds). */
11
+ export declare const IMAGE_TTL_MS: number;
12
+ /**
13
+ * Resolve content-type to file extension. Defaults to ".png".
14
+ */
15
+ export declare function resolveExtension(contentType: string): string;
16
+ /**
17
+ * Find a cached image file for the given token (any known extension).
18
+ * Returns the file path if found, or null.
19
+ */
20
+ export declare function findCachedImage(fileToken: string, dir?: string): Promise<string | null>;
21
+ /**
22
+ * Remove cached image files older than IMAGE_TTL_MS.
23
+ * Errors are caught and logged to stderr -- never throws.
24
+ */
25
+ export declare function cleanExpiredImages(dir: string): Promise<void>;
26
+ /**
27
+ * Download images from temporary URLs and save to local directory.
28
+ * Returns a map of file_token → local file path. Already-downloaded images
29
+ * are skipped (simple disk cache by file_token).
30
+ */
31
+ export declare function downloadImages(tmpUrlMap: Map<string, string>, dir?: string): Promise<Map<string, string>>;
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Download document images to local persistent storage.
3
+ *
4
+ * Images are saved to ~/.feishu-docs/images/{fileToken}.{ext} and served
5
+ * via local file paths in Markdown output. Already-downloaded images are
6
+ * skipped (disk cache by file_token).
7
+ */
8
+ import { writeFile, mkdir, stat, readdir, unlink } from "node:fs/promises";
9
+ import { homedir } from "node:os";
10
+ import { join } from "node:path";
11
+ import { validateToken } from "../utils/validate.js";
12
+ export const IMAGES_DIR = join(homedir(), ".feishu-docs", "images");
13
+ export const CONTENT_TYPE_EXT = {
14
+ "image/png": ".png",
15
+ "image/jpeg": ".jpg",
16
+ "image/gif": ".gif",
17
+ "image/webp": ".webp",
18
+ "image/svg+xml": ".svg",
19
+ };
20
+ /** Maximum age for cached images (30 days in milliseconds). */
21
+ export const IMAGE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
22
+ const KNOWN_EXTENSIONS = Object.values(CONTENT_TYPE_EXT);
23
+ /**
24
+ * Check if a file already exists.
25
+ */
26
+ async function fileExists(path) {
27
+ try {
28
+ await stat(path);
29
+ return true;
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ }
35
+ /**
36
+ * Resolve content-type to file extension. Defaults to ".png".
37
+ */
38
+ export function resolveExtension(contentType) {
39
+ return CONTENT_TYPE_EXT[contentType.split(";")[0].trim()] || ".png";
40
+ }
41
+ /**
42
+ * Find a cached image file for the given token (any known extension).
43
+ * Returns the file path if found, or null.
44
+ */
45
+ export async function findCachedImage(fileToken, dir = IMAGES_DIR) {
46
+ for (const ext of KNOWN_EXTENSIONS) {
47
+ const p = join(dir, `${fileToken}${ext}`);
48
+ if (await fileExists(p))
49
+ return p;
50
+ }
51
+ return null;
52
+ }
53
+ /**
54
+ * Remove cached image files older than IMAGE_TTL_MS.
55
+ * Errors are caught and logged to stderr -- never throws.
56
+ */
57
+ export async function cleanExpiredImages(dir) {
58
+ try {
59
+ const files = await readdir(dir);
60
+ const now = Date.now();
61
+ let cleaned = 0;
62
+ for (const file of files) {
63
+ try {
64
+ const filePath = join(dir, file);
65
+ const fileStat = await stat(filePath);
66
+ if (now - fileStat.mtimeMs > IMAGE_TTL_MS) {
67
+ await unlink(filePath);
68
+ cleaned++;
69
+ }
70
+ }
71
+ catch {
72
+ // Individual file cleanup failure -- skip silently
73
+ }
74
+ }
75
+ if (cleaned > 0) {
76
+ process.stderr.write(`feishu-docs: info: 已清理 ${cleaned} 个过期图片缓存\n`);
77
+ }
78
+ }
79
+ catch {
80
+ process.stderr.write("feishu-docs: warning: 图片缓存清理失败\n");
81
+ }
82
+ }
83
+ /**
84
+ * Download images from temporary URLs and save to local directory.
85
+ * Returns a map of file_token → local file path. Already-downloaded images
86
+ * are skipped (simple disk cache by file_token).
87
+ */
88
+ export async function downloadImages(tmpUrlMap, dir = IMAGES_DIR) {
89
+ if (tmpUrlMap.size === 0)
90
+ return new Map();
91
+ await mkdir(dir, { recursive: true });
92
+ void cleanExpiredImages(dir);
93
+ const localMap = new Map();
94
+ for (const [fileToken, tmpUrl] of tmpUrlMap) {
95
+ // Validate token before using as filename to prevent path traversal
96
+ try {
97
+ validateToken(fileToken, "file_token");
98
+ }
99
+ catch {
100
+ continue;
101
+ }
102
+ // Check disk cache (any known extension)
103
+ const cached = await findCachedImage(fileToken, dir);
104
+ if (cached) {
105
+ localMap.set(fileToken, cached);
106
+ continue;
107
+ }
108
+ try {
109
+ const res = await fetch(tmpUrl);
110
+ if (!res.ok)
111
+ continue;
112
+ const contentType = res.headers.get("content-type") || "";
113
+ const ext = resolveExtension(contentType);
114
+ const filePath = join(dir, `${fileToken}${ext}`);
115
+ const buf = await res.arrayBuffer();
116
+ if (buf.byteLength === 0)
117
+ continue;
118
+ await writeFile(filePath, Buffer.from(buf));
119
+ localMap.set(fileToken, filePath);
120
+ }
121
+ catch {
122
+ // Download failed for this image — skip it
123
+ }
124
+ }
125
+ return localMap;
126
+ }
127
+ //# sourceMappingURL=image-download.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-download.js","sourceRoot":"","sources":["../../src/services/image-download.ts"],"names":[],"mappings":"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"}
@@ -19,6 +19,26 @@ import { AuthInfo, ConvertedBlocks, Block } from "../types/index.js";
19
19
  * Replaces unrecognized aliases with their recognized equivalents.
20
20
  */
21
21
  export declare function normalizeLangNames(markdown: string): string;
22
+ /**
23
+ * Extract the first top-level heading (# title) from markdown.
24
+ *
25
+ * Returns the title text and the remaining body with the heading line removed.
26
+ * Only matches `# heading` (H1), not `## heading` (H2+).
27
+ * Ignores leading blank lines before the heading.
28
+ * If no H1 heading is found, returns null title and the original markdown.
29
+ */
30
+ export declare function extractMarkdownTitle(markdown: string): {
31
+ title: string | null;
32
+ body: string;
33
+ };
34
+ /**
35
+ * Replace literal `\n` with `<br>` inside mermaid code blocks.
36
+ *
37
+ * Claude and other AI tools generate mermaid node labels with `\n` for
38
+ * line breaks (e.g. `A[Line 1\nLine 2]`), but standard mermaid syntax
39
+ * requires `<br>` (e.g. `A[Line 1<br>Line 2]`).
40
+ */
41
+ export declare function normalizeMermaidLineBreaks(markdown: string): string;
22
42
  /**
23
43
  * Convert markdown string to Feishu block array via Convert API.
24
44
  * Requires scope: docx:document.block:convert