@max1874/feishu 0.2.3 → 0.2.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/package.json +1 -1
- package/src/docx.ts +121 -110
package/package.json
CHANGED
package/src/docx.ts
CHANGED
|
@@ -65,11 +65,9 @@ const BLOCK_TYPE_NAMES: Record<number, string> = {
|
|
|
65
65
|
};
|
|
66
66
|
|
|
67
67
|
// Block types that cannot be created via documentBlockChildren.create API
|
|
68
|
-
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
const UNSUPPORTED_CREATE_TYPES = new Set<number>([
|
|
72
|
-
// Empty for now - let's try creating tables directly
|
|
68
|
+
const UNSUPPORTED_CREATE_TYPES = new Set([
|
|
69
|
+
31, // Table - must use different API or workaround
|
|
70
|
+
32, // TableCell - child of Table
|
|
73
71
|
]);
|
|
74
72
|
|
|
75
73
|
/** Clean blocks for insertion (remove unsupported types and read-only fields) */
|
|
@@ -85,7 +83,7 @@ function cleanBlocksForInsert(blocks: any[]): { cleaned: any[]; skipped: string[
|
|
|
85
83
|
return true;
|
|
86
84
|
})
|
|
87
85
|
.map((block) => {
|
|
88
|
-
// Remove read-only fields
|
|
86
|
+
// Remove any read-only fields that might slip through
|
|
89
87
|
if (block.block_type === 31 && block.table?.merge_info) {
|
|
90
88
|
const { merge_info, ...tableRest } = block.table;
|
|
91
89
|
return { ...block, table: tableRest };
|
|
@@ -97,6 +95,70 @@ function cleanBlocksForInsert(blocks: any[]): { cleaned: any[]; skipped: string[
|
|
|
97
95
|
|
|
98
96
|
// ============ Core Functions ============
|
|
99
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Convert Markdown tables to code blocks.
|
|
100
|
+
* Feishu API doesn't support creating Table blocks directly, so we preserve
|
|
101
|
+
* table formatting by wrapping them in code blocks.
|
|
102
|
+
*/
|
|
103
|
+
function convertTablesToCodeBlocks(markdown: string): string {
|
|
104
|
+
const lines = markdown.split("\n");
|
|
105
|
+
const result: string[] = [];
|
|
106
|
+
let tableLines: string[] = [];
|
|
107
|
+
let inTable = false;
|
|
108
|
+
|
|
109
|
+
const isTableLine = (line: string): boolean => {
|
|
110
|
+
const trimmed = line.trim();
|
|
111
|
+
// Table line starts with | or is a separator line like |---|---|
|
|
112
|
+
return trimmed.startsWith("|") && trimmed.endsWith("|");
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const isSeparatorLine = (line: string): boolean => {
|
|
116
|
+
const trimmed = line.trim();
|
|
117
|
+
// Separator line contains only |, -, :, and spaces
|
|
118
|
+
return /^\|[\s\-:|]+\|$/.test(trimmed);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const flushTable = () => {
|
|
122
|
+
if (tableLines.length >= 2) {
|
|
123
|
+
// Check if we have a valid table (at least header + separator)
|
|
124
|
+
const hasSeparator = tableLines.some(isSeparatorLine);
|
|
125
|
+
if (hasSeparator) {
|
|
126
|
+
// Wrap table in code block to preserve formatting
|
|
127
|
+
result.push("```");
|
|
128
|
+
result.push(...tableLines);
|
|
129
|
+
result.push("```");
|
|
130
|
+
} else {
|
|
131
|
+
// Not a valid table, keep as-is
|
|
132
|
+
result.push(...tableLines);
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
// Single line with |, keep as-is
|
|
136
|
+
result.push(...tableLines);
|
|
137
|
+
}
|
|
138
|
+
tableLines = [];
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
for (const line of lines) {
|
|
142
|
+
if (isTableLine(line)) {
|
|
143
|
+
inTable = true;
|
|
144
|
+
tableLines.push(line);
|
|
145
|
+
} else {
|
|
146
|
+
if (inTable) {
|
|
147
|
+
flushTable();
|
|
148
|
+
inTable = false;
|
|
149
|
+
}
|
|
150
|
+
result.push(line);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Flush remaining table if any
|
|
155
|
+
if (inTable) {
|
|
156
|
+
flushTable();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return result.join("\n");
|
|
160
|
+
}
|
|
161
|
+
|
|
100
162
|
/** Convert markdown to Feishu blocks using the Convert API */
|
|
101
163
|
async function convertMarkdown(client: Lark.Client, markdown: string) {
|
|
102
164
|
const res = await client.docx.document.convert({
|
|
@@ -123,34 +185,12 @@ async function insertBlocks(
|
|
|
123
185
|
return { children: [], skipped };
|
|
124
186
|
}
|
|
125
187
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
try {
|
|
133
|
-
const res = await client.docx.documentBlockChildren.create({
|
|
134
|
-
path: { document_id: docToken, block_id: blockId },
|
|
135
|
-
data: { children: cleaned },
|
|
136
|
-
});
|
|
137
|
-
if (res.code !== 0) {
|
|
138
|
-
throw new Error(`API error ${res.code}: ${res.msg}`);
|
|
139
|
-
}
|
|
140
|
-
return { children: res.data?.children ?? [], skipped };
|
|
141
|
-
} catch (err: any) {
|
|
142
|
-
// Enhanced error logging
|
|
143
|
-
const errMsg = err?.response?.data?.msg || err?.message || String(err);
|
|
144
|
-
const errCode = err?.response?.data?.code || err?.code;
|
|
145
|
-
console.error(`[feishu_doc] insertBlocks failed: code=${errCode}, msg=${errMsg}`);
|
|
146
|
-
|
|
147
|
-
// If table blocks exist, report them as problematic
|
|
148
|
-
if (tableBlocks.length > 0) {
|
|
149
|
-
console.error(`[feishu_doc] Table block structure sample:`, JSON.stringify(tableBlocks[0], null, 2).slice(0, 500));
|
|
150
|
-
throw new Error(`Table insertion failed (code=${errCode}): ${errMsg}. Tables may not be supported via this API.`);
|
|
151
|
-
}
|
|
152
|
-
throw err;
|
|
153
|
-
}
|
|
188
|
+
const res = await client.docx.documentBlockChildren.create({
|
|
189
|
+
path: { document_id: docToken, block_id: blockId },
|
|
190
|
+
data: { children: cleaned },
|
|
191
|
+
});
|
|
192
|
+
if (res.code !== 0) throw new Error(res.msg);
|
|
193
|
+
return { children: res.data?.children ?? [], skipped };
|
|
154
194
|
}
|
|
155
195
|
|
|
156
196
|
/** Delete all child blocks from a parent */
|
|
@@ -440,43 +480,65 @@ async function createDoc(
|
|
|
440
480
|
}
|
|
441
481
|
|
|
442
482
|
async function writeDoc(client: Lark.Client, docToken: string, markdown: string) {
|
|
443
|
-
|
|
444
|
-
|
|
483
|
+
let step = "init";
|
|
484
|
+
try {
|
|
485
|
+
// 1. Clear existing content
|
|
486
|
+
step = "clear_content";
|
|
487
|
+
const deleted = await clearDocumentContent(client, docToken);
|
|
488
|
+
|
|
489
|
+
// 2. Pre-process markdown (convert tables to code blocks)
|
|
490
|
+
step = "preprocess_markdown";
|
|
491
|
+
const processedMarkdown = convertTablesToCodeBlocks(markdown);
|
|
492
|
+
|
|
493
|
+
// 3. Convert markdown to blocks
|
|
494
|
+
step = "convert_markdown";
|
|
495
|
+
const res = await client.docx.document.convert({
|
|
496
|
+
data: { content_type: "markdown", content: processedMarkdown },
|
|
497
|
+
});
|
|
498
|
+
if (res.code !== 0) throw new Error(`convert failed: ${res.msg}`);
|
|
499
|
+
const blocks = res.data?.blocks ?? [];
|
|
445
500
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
return { success: true, blocks_deleted: deleted, blocks_added: 0, images_processed: 0 };
|
|
450
|
-
}
|
|
501
|
+
if (blocks.length === 0) {
|
|
502
|
+
return { success: true, blocks_deleted: deleted, blocks_added: 0, images_processed: 0 };
|
|
503
|
+
}
|
|
451
504
|
|
|
452
|
-
|
|
453
|
-
|
|
505
|
+
// 4. Insert new blocks (unsupported types like Table are filtered)
|
|
506
|
+
step = "insert_blocks";
|
|
507
|
+
const { children: inserted, skipped } = await insertBlocks(client, docToken, blocks);
|
|
454
508
|
|
|
455
|
-
|
|
456
|
-
|
|
509
|
+
// 5. Process images
|
|
510
|
+
step = "process_images";
|
|
511
|
+
const imagesProcessed = await processImages(client, docToken, markdown, inserted);
|
|
457
512
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
513
|
+
return {
|
|
514
|
+
success: true,
|
|
515
|
+
blocks_deleted: deleted,
|
|
516
|
+
blocks_added: inserted.length,
|
|
517
|
+
images_processed: imagesProcessed,
|
|
518
|
+
...(skipped.length > 0 && {
|
|
519
|
+
warning: `Skipped unsupported block types: ${skipped.join(", ")}. Tables are not supported via this API.`,
|
|
520
|
+
}),
|
|
521
|
+
};
|
|
522
|
+
} catch (err) {
|
|
523
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
524
|
+
throw new Error(`writeDoc failed at step '${step}': ${errMsg}`);
|
|
525
|
+
}
|
|
467
526
|
}
|
|
468
527
|
|
|
469
528
|
async function appendDoc(client: Lark.Client, docToken: string, markdown: string) {
|
|
470
|
-
// 1.
|
|
471
|
-
const
|
|
529
|
+
// 1. Pre-process markdown (convert tables to code blocks)
|
|
530
|
+
const processedMarkdown = convertTablesToCodeBlocks(markdown);
|
|
531
|
+
|
|
532
|
+
// 2. Convert markdown to blocks
|
|
533
|
+
const { blocks } = await convertMarkdown(client, processedMarkdown);
|
|
472
534
|
if (blocks.length === 0) {
|
|
473
535
|
throw new Error("Content is empty");
|
|
474
536
|
}
|
|
475
537
|
|
|
476
|
-
//
|
|
538
|
+
// 3. Insert blocks (unsupported types like Table are filtered)
|
|
477
539
|
const { children: inserted, skipped } = await insertBlocks(client, docToken, blocks);
|
|
478
540
|
|
|
479
|
-
//
|
|
541
|
+
// 4. Process images
|
|
480
542
|
const imagesProcessed = await processImages(client, docToken, markdown, inserted);
|
|
481
543
|
|
|
482
544
|
return {
|
|
@@ -594,32 +656,6 @@ async function listAppScopes(client: Lark.Client) {
|
|
|
594
656
|
};
|
|
595
657
|
}
|
|
596
658
|
|
|
597
|
-
/** Debug: convert markdown and show resulting block structure */
|
|
598
|
-
async function debugConvertMarkdown(client: Lark.Client, markdown: string) {
|
|
599
|
-
const { blocks } = await convertMarkdown(client, markdown);
|
|
600
|
-
|
|
601
|
-
const blockTypeCounts: Record<string, number> = {};
|
|
602
|
-
for (const block of blocks) {
|
|
603
|
-
const typeName = BLOCK_TYPE_NAMES[block.block_type] || `type_${block.block_type}`;
|
|
604
|
-
blockTypeCounts[typeName] = (blockTypeCounts[typeName] || 0) + 1;
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
// Find table blocks and show their structure
|
|
608
|
-
const tableBlocks = blocks.filter((b) => b.block_type === 31);
|
|
609
|
-
let tableSample: any = null;
|
|
610
|
-
if (tableBlocks.length > 0) {
|
|
611
|
-
tableSample = tableBlocks[0];
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
return {
|
|
615
|
-
total_blocks: blocks.length,
|
|
616
|
-
block_types: blockTypeCounts,
|
|
617
|
-
has_tables: tableBlocks.length > 0,
|
|
618
|
-
table_count: tableBlocks.length,
|
|
619
|
-
table_sample: tableSample ? JSON.stringify(tableSample, null, 2) : null,
|
|
620
|
-
};
|
|
621
|
-
}
|
|
622
|
-
|
|
623
659
|
// ============ Schemas ============
|
|
624
660
|
|
|
625
661
|
const DocTokenSchema = Type.Object({
|
|
@@ -676,10 +712,6 @@ const SetPermissionSchema = Type.Object({
|
|
|
676
712
|
}),
|
|
677
713
|
});
|
|
678
714
|
|
|
679
|
-
const DebugConvertSchema = Type.Object({
|
|
680
|
-
content: Type.String({ description: "Markdown content to convert (for debugging block structure)" }),
|
|
681
|
-
});
|
|
682
|
-
|
|
683
715
|
// Wiki Schemas
|
|
684
716
|
const WikiTokenSchema = Type.Object({
|
|
685
717
|
wiki_token: Type.String({ description: "Wiki token (extract from URL /wiki/XXX)" }),
|
|
@@ -1015,26 +1047,5 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) {
|
|
|
1015
1047
|
{ name: "feishu_app_scopes" },
|
|
1016
1048
|
);
|
|
1017
1049
|
|
|
1018
|
-
|
|
1019
|
-
api.registerTool(
|
|
1020
|
-
{
|
|
1021
|
-
name: "feishu_debug_convert",
|
|
1022
|
-
label: "Feishu Debug Convert",
|
|
1023
|
-
description:
|
|
1024
|
-
"Debug tool: convert markdown to blocks and show the resulting structure. Use to diagnose why certain markdown (like tables) may fail to write.",
|
|
1025
|
-
parameters: DebugConvertSchema,
|
|
1026
|
-
async execute(_toolCallId, params) {
|
|
1027
|
-
const { content } = params as { content: string };
|
|
1028
|
-
try {
|
|
1029
|
-
const result = await debugConvertMarkdown(getClient(), content);
|
|
1030
|
-
return json(result);
|
|
1031
|
-
} catch (err) {
|
|
1032
|
-
return json({ error: err instanceof Error ? err.message : String(err) });
|
|
1033
|
-
}
|
|
1034
|
-
},
|
|
1035
|
-
},
|
|
1036
|
-
{ name: "feishu_debug_convert" },
|
|
1037
|
-
);
|
|
1038
|
-
|
|
1039
|
-
api.logger.info?.(`feishu_doc: Registered 15 document/wiki tools`);
|
|
1050
|
+
api.logger.info?.(`feishu_doc: Registered 14 document/wiki tools`);
|
|
1040
1051
|
}
|