@max1874/feishu 0.2.3 → 0.2.4

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/docx.ts +79 -87
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@max1874/feishu",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "type": "module",
5
5
  "description": "OpenClaw Feishu/Lark channel plugin",
6
6
  "license": "MIT",
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
- // Note: Table (31) and TableCell (32) were previously excluded, but the convert API
69
- // returns them with proper structure, so we attempt to create them directly.
70
- // If creation fails, the error will be reported to the user.
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 from table blocks that might cause API errors
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,10 +95,77 @@ 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) {
164
+ // Pre-process: convert tables to code blocks since Feishu API doesn't support Table blocks
165
+ const processedMarkdown = convertTablesToCodeBlocks(markdown);
166
+
102
167
  const res = await client.docx.document.convert({
103
- data: { content_type: "markdown", content: markdown },
168
+ data: { content_type: "markdown", content: processedMarkdown },
104
169
  });
105
170
  if (res.code !== 0) throw new Error(res.msg);
106
171
  return {
@@ -123,34 +188,12 @@ async function insertBlocks(
123
188
  return { children: [], skipped };
124
189
  }
125
190
 
126
- // Log table blocks for debugging
127
- const tableBlocks = cleaned.filter((b) => b.block_type === 31);
128
- if (tableBlocks.length > 0) {
129
- console.log(`[feishu_doc] Attempting to insert ${tableBlocks.length} table block(s)`);
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
- }
191
+ const res = await client.docx.documentBlockChildren.create({
192
+ path: { document_id: docToken, block_id: blockId },
193
+ data: { children: cleaned },
194
+ });
195
+ if (res.code !== 0) throw new Error(res.msg);
196
+ return { children: res.data?.children ?? [], skipped };
154
197
  }
155
198
 
156
199
  /** Delete all child blocks from a parent */
@@ -594,32 +637,6 @@ async function listAppScopes(client: Lark.Client) {
594
637
  };
595
638
  }
596
639
 
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
640
  // ============ Schemas ============
624
641
 
625
642
  const DocTokenSchema = Type.Object({
@@ -676,10 +693,6 @@ const SetPermissionSchema = Type.Object({
676
693
  }),
677
694
  });
678
695
 
679
- const DebugConvertSchema = Type.Object({
680
- content: Type.String({ description: "Markdown content to convert (for debugging block structure)" }),
681
- });
682
-
683
696
  // Wiki Schemas
684
697
  const WikiTokenSchema = Type.Object({
685
698
  wiki_token: Type.String({ description: "Wiki token (extract from URL /wiki/XXX)" }),
@@ -1015,26 +1028,5 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) {
1015
1028
  { name: "feishu_app_scopes" },
1016
1029
  );
1017
1030
 
1018
- // Tool 15: feishu_debug_convert (for diagnosing markdown conversion issues)
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`);
1031
+ api.logger.info?.(`feishu_doc: Registered 14 document/wiki tools`);
1040
1032
  }