feishu-mcp 0.0.2 → 0.0.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.
- package/README.md +49 -1
- package/dist/cli.js +1 -1
- package/dist/index.js +15 -5
- package/dist/server.js +420 -128
- package/dist/services/feishu.js +102 -3
- package/dist/services/feishuBlocks.js +135 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,8 +21,13 @@
|
|
|
21
21
|
- 插入图表:支持各类数据可视化图表
|
|
22
22
|
- 插入流程图:支持流程图和思维导图
|
|
23
23
|
- 插入公式:支持数学公式和科学符号
|
|
24
|
-
|
|
24
|
+
- 图表、流程图的内容识别
|
|
25
25
|
|
|
26
|
+
快速开始,详见[配置](#配置)部分:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx feishu-mcp --feishu-app-id=<你的飞书应用ID> --feishu-app-secret=<你的飞书应用密钥>
|
|
30
|
+
```
|
|
26
31
|
|
|
27
32
|
## 工作原理
|
|
28
33
|
|
|
@@ -32,6 +37,49 @@
|
|
|
32
37
|
4. Cursor 将从飞书获取相关元数据并使用它来辅助编写代码。
|
|
33
38
|
|
|
34
39
|
这个 MCP 服务器专为 Cursor 设计。在响应来自[飞书 API](https://open.feishu.cn/document/home/introduction-to-lark-open-platform/overview) 的内容之前,它会简化和转换响应,确保只向模型提供最相关的文档信息。
|
|
40
|
+
## 安装
|
|
41
|
+
|
|
42
|
+
### 使用 NPM 快速运行服务器
|
|
43
|
+
|
|
44
|
+
你可以使用 NPM 快速运行服务器,无需安装或构建仓库:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npx feishu-mcp --feishu-app-id=<你的飞书应用ID> --feishu-app-secret=<你的飞书应用密钥>
|
|
48
|
+
|
|
49
|
+
# 或
|
|
50
|
+
pnpx feishu-mcp --feishu-app-id=<你的飞书应用ID> --feishu-app-secret=<你的飞书应用密钥>
|
|
51
|
+
|
|
52
|
+
# 或
|
|
53
|
+
yarn dlx feishu-mcp --feishu-app-id=<你的飞书应用ID> --feishu-app-secret=<你的飞书应用密钥>
|
|
54
|
+
|
|
55
|
+
# 或
|
|
56
|
+
bunx feishu-mcp --feishu-app-id=<你的飞书应用ID> --feishu-app-secret=<你的飞书应用密钥>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
已发布到smithery平台,可访问:https://smithery.ai/server/@cso1z/feishu-mcp
|
|
60
|
+
|
|
61
|
+
关于如何创建飞书应用和获取应用凭证的说明可以在[这里](https://open.feishu.cn/document/home/develop-a-bot-in-5-minutes/create-an-app)找到。
|
|
62
|
+
|
|
63
|
+
### 使用配置文件的工具的 JSON 配置
|
|
64
|
+
|
|
65
|
+
许多工具如 Windsurf、Cline 和 [Claude Desktop](https://claude.ai/download) 使用配置文件来启动服务器。
|
|
66
|
+
|
|
67
|
+
`feishu-mcp` 服务器可以通过在配置文件中添加以下内容来配置:
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"mcpServers": {
|
|
72
|
+
"feishu-mcp": {
|
|
73
|
+
"command": "npx",
|
|
74
|
+
"args": ["-y", "feishu-mcp", "--stdio"],
|
|
75
|
+
"env": {
|
|
76
|
+
"FEISHU_APP_ID": "<你的飞书应用ID>",
|
|
77
|
+
"FEISHU_APP_SECRET": "<你的飞书应用密钥>"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
35
83
|
|
|
36
84
|
### 从本地源代码运行服务器
|
|
37
85
|
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { resolve } from "path";
|
|
3
3
|
import { config } from "dotenv";
|
|
4
|
-
import { startServer } from "./index";
|
|
4
|
+
import { startServer } from "./index.js";
|
|
5
5
|
// Load .env from the current working directory
|
|
6
6
|
config({ path: resolve(process.cwd(), ".env") });
|
|
7
7
|
startServer().catch((error) => {
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2
|
-
import { FeishuMcpServer } from "./server";
|
|
3
|
-
import { getServerConfig } from "./config";
|
|
2
|
+
import { FeishuMcpServer } from "./server.js";
|
|
3
|
+
import { getServerConfig } from "./config.js";
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { resolve } from 'path';
|
|
4
6
|
export async function startServer() {
|
|
5
7
|
// Check if we're running in stdio mode (e.g., via CLI)
|
|
6
8
|
const isStdioMode = process.env.NODE_ENV === "cli" || process.argv.includes("--stdio");
|
|
@@ -14,6 +16,7 @@ export async function startServer() {
|
|
|
14
16
|
console.log(`Feishu App ID: ${feishuConfig.appId.substring(0, 4)}...${feishuConfig.appId.substring(feishuConfig.appId.length - 4)}`);
|
|
15
17
|
console.log(`Feishu App Secret: ${feishuConfig.appSecret.substring(0, 4)}...${feishuConfig.appSecret.substring(feishuConfig.appSecret.length - 4)}`);
|
|
16
18
|
const server = new FeishuMcpServer(feishuConfig);
|
|
19
|
+
console.log(`isStdioMode:${isStdioMode}`);
|
|
17
20
|
if (isStdioMode) {
|
|
18
21
|
const transport = new StdioServerTransport();
|
|
19
22
|
await server.connect(transport);
|
|
@@ -23,10 +26,17 @@ export async function startServer() {
|
|
|
23
26
|
await server.startHttpServer(config.port);
|
|
24
27
|
}
|
|
25
28
|
}
|
|
26
|
-
//
|
|
27
|
-
|
|
29
|
+
// 跨平台兼容的方式检查是否直接运行
|
|
30
|
+
const currentFilePath = fileURLToPath(import.meta.url);
|
|
31
|
+
const executedFilePath = resolve(process.argv[1]);
|
|
32
|
+
console.log(`meta.url:${currentFilePath} argv:${executedFilePath}`);
|
|
33
|
+
if (currentFilePath === executedFilePath) {
|
|
34
|
+
console.log(`startServer`);
|
|
28
35
|
startServer().catch((error) => {
|
|
29
|
-
console.error(
|
|
36
|
+
console.error('Failed to start server:', error);
|
|
30
37
|
process.exit(1);
|
|
31
38
|
});
|
|
32
39
|
}
|
|
40
|
+
else {
|
|
41
|
+
console.log(`not startServer`);
|
|
42
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import { McpServer } from
|
|
2
|
-
import { z } from
|
|
3
|
-
import { FeishuService } from
|
|
4
|
-
import express from
|
|
5
|
-
import { SSEServerTransport } from
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { FeishuService } from './services/feishu.js';
|
|
4
|
+
import express from 'express';
|
|
5
|
+
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
6
6
|
export const Logger = {
|
|
7
|
-
log: (...args) => {
|
|
8
|
-
|
|
7
|
+
log: (...args) => {
|
|
8
|
+
console.log(...args);
|
|
9
|
+
},
|
|
10
|
+
error: (...args) => {
|
|
11
|
+
console.error(...args);
|
|
12
|
+
},
|
|
9
13
|
};
|
|
10
14
|
export class FeishuMcpServer {
|
|
11
15
|
constructor(feishuConfig) {
|
|
@@ -38,8 +42,8 @@ export class FeishuMcpServer {
|
|
|
38
42
|
throw new Error('飞书服务初始化失败');
|
|
39
43
|
}
|
|
40
44
|
this.server = new McpServer({
|
|
41
|
-
name:
|
|
42
|
-
version:
|
|
45
|
+
name: 'Feishu MCP Server',
|
|
46
|
+
version: '0.0.1',
|
|
43
47
|
}, {
|
|
44
48
|
capabilities: {
|
|
45
49
|
logging: {},
|
|
@@ -50,10 +54,12 @@ export class FeishuMcpServer {
|
|
|
50
54
|
}
|
|
51
55
|
registerTools() {
|
|
52
56
|
// 添加创建飞书文档工具
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
this.server.tool('create_feishu_doc', 'Create a new Feishu document', {
|
|
58
|
+
title: z.string().describe('Document title'),
|
|
59
|
+
folderToken: z
|
|
60
|
+
.string()
|
|
61
|
+
.optional()
|
|
62
|
+
.describe('Folder token where the document will be created. If not provided, the document will be created in the root directory'),
|
|
57
63
|
}, async ({ title, folderToken }) => {
|
|
58
64
|
try {
|
|
59
65
|
Logger.log(`开始创建飞书文档,标题: ${title}${folderToken ? `,文件夹Token: ${folderToken}` : ''}`);
|
|
@@ -61,13 +67,13 @@ export class FeishuMcpServer {
|
|
|
61
67
|
const newDoc = await this.feishuService.createDocument(title, folderToken);
|
|
62
68
|
Logger.log(`飞书文档创建成功,文档ID: ${newDoc?.objToken || newDoc?.document_id}`);
|
|
63
69
|
return {
|
|
64
|
-
content: [{ type:
|
|
70
|
+
content: [{ type: 'text', text: JSON.stringify(newDoc, null, 2) }],
|
|
65
71
|
};
|
|
66
72
|
}
|
|
67
73
|
catch (error) {
|
|
68
74
|
Logger.error(`创建飞书文档失败:`, error);
|
|
69
75
|
return {
|
|
70
|
-
content: [{ type:
|
|
76
|
+
content: [{ type: 'text', text: `创建飞书文档失败: ${error}` }],
|
|
71
77
|
};
|
|
72
78
|
}
|
|
73
79
|
});
|
|
@@ -100,203 +106,489 @@ export class FeishuMcpServer {
|
|
|
100
106
|
// },
|
|
101
107
|
// );
|
|
102
108
|
// 添加获取飞书文档内容工具
|
|
103
|
-
this.server.tool(
|
|
104
|
-
documentId: z
|
|
105
|
-
|
|
109
|
+
this.server.tool('get_feishu_doc_content', 'Get the plain text content of a Feishu document', {
|
|
110
|
+
documentId: z
|
|
111
|
+
.string()
|
|
112
|
+
.describe('Document ID or URL. Supported formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID (e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf)'),
|
|
113
|
+
lang: z
|
|
114
|
+
.number()
|
|
115
|
+
.optional()
|
|
116
|
+
.default(0)
|
|
117
|
+
.describe('Language code. Default is 0 (Chinese)'),
|
|
106
118
|
}, async ({ documentId, lang }) => {
|
|
107
119
|
try {
|
|
108
120
|
if (!this.feishuService) {
|
|
109
121
|
return {
|
|
110
|
-
content: [
|
|
122
|
+
content: [
|
|
123
|
+
{
|
|
124
|
+
type: 'text',
|
|
125
|
+
text: 'Feishu service is not initialized. Please check the configuration',
|
|
126
|
+
},
|
|
127
|
+
],
|
|
111
128
|
};
|
|
112
129
|
}
|
|
113
130
|
Logger.log(`开始获取飞书文档内容,文档ID: ${documentId},语言: ${lang}`);
|
|
114
131
|
const content = await this.feishuService.getDocumentContent(documentId, lang);
|
|
115
132
|
Logger.log(`飞书文档内容获取成功,内容长度: ${content.length}字符`);
|
|
116
133
|
return {
|
|
117
|
-
content: [{ type:
|
|
134
|
+
content: [{ type: 'text', text: content }],
|
|
118
135
|
};
|
|
119
136
|
}
|
|
120
137
|
catch (error) {
|
|
121
138
|
Logger.error(`获取飞书文档内容失败:`, error);
|
|
122
139
|
return {
|
|
123
|
-
content: [{ type:
|
|
140
|
+
content: [{ type: 'text', text: `获取飞书文档内容失败: ${error}` }],
|
|
124
141
|
};
|
|
125
142
|
}
|
|
126
143
|
});
|
|
127
144
|
// 添加获取飞书文档块工具
|
|
128
|
-
this.server.tool(
|
|
129
|
-
documentId: z
|
|
130
|
-
|
|
145
|
+
this.server.tool('get_feishu_doc_blocks', 'When document structure is needed, obtain the block information about the Feishu document for content analysis or block insertion', {
|
|
146
|
+
documentId: z
|
|
147
|
+
.string()
|
|
148
|
+
.describe('Document ID or URL. Supported formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID (e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf)'),
|
|
149
|
+
pageSize: z
|
|
150
|
+
.number()
|
|
151
|
+
.optional()
|
|
152
|
+
.default(500)
|
|
153
|
+
.describe('Number of blocks per page. Default is 500'),
|
|
131
154
|
}, async ({ documentId, pageSize }) => {
|
|
132
155
|
try {
|
|
133
156
|
if (!this.feishuService) {
|
|
134
157
|
return {
|
|
135
|
-
content: [
|
|
158
|
+
content: [
|
|
159
|
+
{
|
|
160
|
+
type: 'text',
|
|
161
|
+
text: 'Feishu service is not initialized. Please check the configuration',
|
|
162
|
+
},
|
|
163
|
+
],
|
|
136
164
|
};
|
|
137
165
|
}
|
|
138
166
|
Logger.log(`开始获取飞书文档块,文档ID: ${documentId},页大小: ${pageSize}`);
|
|
139
167
|
const blocks = await this.feishuService.getDocumentBlocks(documentId, pageSize);
|
|
140
168
|
Logger.log(`飞书文档块获取成功,共 ${blocks.length} 个块`);
|
|
141
169
|
return {
|
|
142
|
-
content: [{ type:
|
|
170
|
+
content: [{ type: 'text', text: JSON.stringify(blocks, null, 2) }],
|
|
143
171
|
};
|
|
144
172
|
}
|
|
145
173
|
catch (error) {
|
|
146
174
|
Logger.error(`获取飞书文档块失败:`, error);
|
|
147
175
|
return {
|
|
148
|
-
content: [{ type:
|
|
176
|
+
content: [{ type: 'text', text: `获取飞书文档块失败: ${error}` }],
|
|
149
177
|
};
|
|
150
178
|
}
|
|
151
179
|
});
|
|
152
180
|
// 添加创建飞书文档块工具
|
|
153
|
-
this.server.tool(
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
181
|
+
// this.server.tool(
|
|
182
|
+
// "create_feishu_text_block",
|
|
183
|
+
// "Create a new text block in a Feishu document (AI will automatically convert Markdown syntax to corresponding style attributes: **bold** → bold:true, *italic* → italic:true, ~~strikethrough~~ → strikethrough:true, `code` → inline_code:true)",
|
|
184
|
+
// {
|
|
185
|
+
// documentId: z.string().describe("Document ID or URL. Supported formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID (e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf)"),
|
|
186
|
+
// parentBlockId: z.string().describe("Parent block ID (NOT URL) where the new block will be added as a child. This should be the raw block ID without any URL prefix. When adding blocks at the page level (root level), use the extracted document ID from documentId parameter"),
|
|
187
|
+
// textContents: z.array(
|
|
188
|
+
// z.object({
|
|
189
|
+
// text: z.string().describe("Text content"),
|
|
190
|
+
// style: z.object({
|
|
191
|
+
// bold: z.boolean().optional().describe("Whether to make text bold. Default is false"),
|
|
192
|
+
// italic: z.boolean().optional().describe("Whether to make text italic. Default is false"),
|
|
193
|
+
// underline: z.boolean().optional().describe("Whether to add underline. Default is false"),
|
|
194
|
+
// strikethrough: z.boolean().optional().describe("Whether to add strikethrough. Default is false"),
|
|
195
|
+
// inline_code: z.boolean().optional().describe("Whether to format as inline code. Default is false"),
|
|
196
|
+
// text_color: z.number().optional().describe("Text color as a number. Default is 0")
|
|
197
|
+
// }).optional().describe("Text style settings")
|
|
198
|
+
// })
|
|
199
|
+
// ).describe("Array of text content objects. A block can contain multiple text segments with different styles"),
|
|
200
|
+
// align: z.number().optional().default(1).describe("Text alignment: 1 for left, 2 for center, 3 for right. Default is 1"),
|
|
201
|
+
// index: z.number().optional().default(0).describe("Insertion position index. Default is 0 (insert at the beginning). If unsure about the position, use the get_feishu_doc_blocks tool first to understand the document structure. For consecutive insertions, calculate the next position as previous_index + 1 to avoid querying document structure repeatedly")
|
|
202
|
+
// },
|
|
203
|
+
// async ({ documentId, parentBlockId, textContents, align = 1, index }) => {
|
|
204
|
+
// try {
|
|
205
|
+
// if (!this.feishuService) {
|
|
206
|
+
// return {
|
|
207
|
+
// content: [{ type: "text", text: "Feishu service is not initialized. Please check the configuration" }],
|
|
208
|
+
// };
|
|
209
|
+
// }
|
|
210
|
+
//
|
|
211
|
+
// // 处理Markdown语法转换
|
|
212
|
+
// const processedTextContents = textContents.map(content => {
|
|
213
|
+
// let { text, style = {} } = content;
|
|
214
|
+
//
|
|
215
|
+
// // 创建一个新的style对象,避免修改原始对象
|
|
216
|
+
// const newStyle = { ...style };
|
|
217
|
+
//
|
|
218
|
+
// // 处理粗体 **text**
|
|
219
|
+
// if (text.match(/\*\*([^*]+)\*\*/g)) {
|
|
220
|
+
// text = text.replace(/\*\*([^*]+)\*\*/g, "$1");
|
|
221
|
+
// newStyle.bold = true;
|
|
222
|
+
// }
|
|
223
|
+
//
|
|
224
|
+
// // 处理斜体 *text*
|
|
225
|
+
// if (text.match(/(?<!\*)\*([^*]+)\*(?!\*)/g)) {
|
|
226
|
+
// text = text.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "$1");
|
|
227
|
+
// newStyle.italic = true;
|
|
228
|
+
// }
|
|
229
|
+
//
|
|
230
|
+
// // 处理删除线 ~~text~~
|
|
231
|
+
// if (text.match(/~~([^~]+)~~/g)) {
|
|
232
|
+
// text = text.replace(/~~([^~]+)~~/g, "$1");
|
|
233
|
+
// newStyle.strikethrough = true;
|
|
234
|
+
// }
|
|
235
|
+
//
|
|
236
|
+
// // 处理行内代码 `code`
|
|
237
|
+
// if (text.match(/`([^`]+)`/g)) {
|
|
238
|
+
// text = text.replace(/`([^`]+)`/g, "$1");
|
|
239
|
+
// newStyle.inline_code = true;
|
|
240
|
+
// }
|
|
241
|
+
//
|
|
242
|
+
// return { text, style: newStyle };
|
|
243
|
+
// });
|
|
244
|
+
//
|
|
245
|
+
// Logger.log(`开始创建飞书文本块,文档ID: ${documentId},父块ID: ${parentBlockId},对齐方式: ${align},插入位置: ${index}`);
|
|
246
|
+
// const result = await this.feishuService.createTextBlock(documentId, parentBlockId, processedTextContents, align, index);
|
|
247
|
+
// Logger.log(`飞书文本块创建成功`);
|
|
248
|
+
//
|
|
249
|
+
// return {
|
|
250
|
+
// content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
251
|
+
// };
|
|
252
|
+
// } catch (error) {
|
|
253
|
+
// Logger.error(`创建飞书文本块失败:`, error);
|
|
254
|
+
// return {
|
|
255
|
+
// content: [{ type: "text", text: `创建飞书文本块失败: ${error}` }],
|
|
256
|
+
// };
|
|
257
|
+
// }
|
|
258
|
+
// },
|
|
259
|
+
// );
|
|
217
260
|
// 添加创建飞书代码块工具
|
|
218
|
-
this.server.tool(
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
261
|
+
// this.server.tool(
|
|
262
|
+
// "create_feishu_code_block",
|
|
263
|
+
// "Create a new code block in a Feishu document",
|
|
264
|
+
// {
|
|
265
|
+
// documentId: z.string().describe("Document ID or URL. Supported formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID (e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf)"),
|
|
266
|
+
// parentBlockId: z.string().describe("Parent block ID (NOT URL) where the new block will be added as a child. This should be the raw block ID without any URL prefix. When adding blocks at the page level (root level), use the extracted document ID from documentId parameter"),
|
|
267
|
+
// code: z.string().describe("Code content"),
|
|
268
|
+
// language: z.number().optional().default(0).describe("Programming language code as a number. Examples: 1: PlainText; 7: Bash; 8: CSharp; 9: C++; 10: C; 12: CSS; 22: Go; 24: HTML; 29: Java; 30: JavaScript; 32: Kotlin; 43: PHP; 49: Python; 52: Ruby; 53: Rust; 56: SQL; 60: Shell; 61: Swift; 63: TypeScript. Default is 0"),
|
|
269
|
+
// wrap: z.boolean().optional().default(false).describe("Whether to enable automatic line wrapping. Default is false"),
|
|
270
|
+
// index: z.number().optional().default(0).describe("Insertion position index. Default is 0 (insert at the beginning). If unsure about the position, use the get_feishu_doc_blocks tool first to understand the document structure. For consecutive insertions, calculate the next position as previous_index + 1 to avoid querying document structure repeatedly")
|
|
271
|
+
// },
|
|
272
|
+
// async ({ documentId, parentBlockId, code, language = 0, wrap = false, index = 0 }) => {
|
|
273
|
+
// try {
|
|
274
|
+
// if (!this.feishuService) {
|
|
275
|
+
// return {
|
|
276
|
+
// content: [{ type: "text", text: "Feishu service is not initialized. Please check the configuration" }],
|
|
277
|
+
// };
|
|
278
|
+
// }
|
|
279
|
+
//
|
|
280
|
+
// Logger.log(`开始创建飞书代码块,文档ID: ${documentId},父块ID: ${parentBlockId},语言: ${language},自动换行: ${wrap},插入位置: ${index}`);
|
|
281
|
+
// const result = await this.feishuService.createCodeBlock(documentId, parentBlockId, code, language, wrap, index);
|
|
282
|
+
// Logger.log(`飞书代码块创建成功`);
|
|
283
|
+
//
|
|
284
|
+
// return {
|
|
285
|
+
// content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
286
|
+
// };
|
|
287
|
+
// } catch (error) {
|
|
288
|
+
// Logger.error(`创建飞书代码块失败:`, error);
|
|
289
|
+
// return {
|
|
290
|
+
// content: [{ type: "text", text: `创建飞书代码块失败: ${error}` }],
|
|
291
|
+
// };
|
|
292
|
+
// }
|
|
293
|
+
// },
|
|
294
|
+
// );
|
|
295
|
+
// 添加批量创建飞书块工具
|
|
296
|
+
this.server.tool('create_feishu_blocks', 'Create multiple blocks in a Feishu document at once with a single API call', {
|
|
297
|
+
documentId: z
|
|
298
|
+
.string()
|
|
299
|
+
.describe('Document ID or URL. Supported formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID (e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf)'),
|
|
300
|
+
parentBlockId: z
|
|
301
|
+
.string()
|
|
302
|
+
.describe('Parent block ID (NOT URL) where the new blocks will be added as children. This should be the raw block ID without any URL prefix. When adding blocks at the page level (root level), use the extracted document ID from documentId parameter'),
|
|
303
|
+
children: z
|
|
304
|
+
.array(z.object({
|
|
305
|
+
block_type: z
|
|
306
|
+
.number()
|
|
307
|
+
.describe('Block type number: 2 for text, 3-11 for headings (level+2), 14 for code'),
|
|
308
|
+
content: z
|
|
309
|
+
.any()
|
|
310
|
+
.describe('Block content object that follows Feishu API format'),
|
|
311
|
+
}))
|
|
312
|
+
.describe('Array of block objects to create in a single API call'),
|
|
313
|
+
index: z
|
|
314
|
+
.number()
|
|
315
|
+
.optional()
|
|
316
|
+
.default(0)
|
|
317
|
+
.describe('Insertion position index. Default is 0 (insert at the beginning)'),
|
|
318
|
+
}, async ({ documentId, parentBlockId, children, index = 0 }) => {
|
|
226
319
|
try {
|
|
227
320
|
if (!this.feishuService) {
|
|
228
321
|
return {
|
|
229
|
-
content: [
|
|
322
|
+
content: [
|
|
323
|
+
{
|
|
324
|
+
type: 'text',
|
|
325
|
+
text: 'Feishu service is not initialized. Please check the configuration',
|
|
326
|
+
},
|
|
327
|
+
],
|
|
230
328
|
};
|
|
231
329
|
}
|
|
232
|
-
Logger.log(
|
|
233
|
-
const result = await this.feishuService.
|
|
234
|
-
Logger.log(
|
|
330
|
+
Logger.log(`开始批量创建飞书块,文档ID: ${documentId},父块ID: ${parentBlockId},块数量: ${children.length},插入位置: ${index}`);
|
|
331
|
+
const result = await this.feishuService.createDocumentBlocks(documentId, parentBlockId, children, index);
|
|
332
|
+
Logger.log(`飞书块批量创建成功`);
|
|
235
333
|
return {
|
|
236
|
-
content: [{ type:
|
|
334
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
237
335
|
};
|
|
238
336
|
}
|
|
239
337
|
catch (error) {
|
|
240
|
-
Logger.error(
|
|
338
|
+
Logger.error(`批量创建飞书块失败:`, error);
|
|
241
339
|
return {
|
|
242
|
-
content: [{ type:
|
|
340
|
+
content: [{ type: 'text', text: `批量创建飞书块失败: ${error}` }],
|
|
243
341
|
};
|
|
244
342
|
}
|
|
245
343
|
});
|
|
246
|
-
//
|
|
247
|
-
this.server.tool(
|
|
248
|
-
documentId: z
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
344
|
+
// 添加通用飞书块创建工具(支持文本、代码、标题)
|
|
345
|
+
this.server.tool('create_feishu_common_block', 'Create common blocks in a Feishu document (supports text, code, and heading blocks)', {
|
|
346
|
+
documentId: z
|
|
347
|
+
.string()
|
|
348
|
+
.describe('Document ID or URL. Supported formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID (e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf)'),
|
|
349
|
+
parentBlockId: z
|
|
350
|
+
.string()
|
|
351
|
+
.describe('Parent block ID (NOT URL) where the new blocks will be added as children. This should be the raw block ID without any URL prefix. When adding blocks at the page level (root level), use the extracted document ID from documentId parameter'),
|
|
352
|
+
startIndex: z
|
|
353
|
+
.number()
|
|
354
|
+
.optional()
|
|
355
|
+
.default(0)
|
|
356
|
+
.describe('Starting insertion position index. Default is 0 (insert at the beginning). If individual blocks have their own index specified, those will take precedence'),
|
|
357
|
+
blocks: z
|
|
358
|
+
.array(z.object({
|
|
359
|
+
blockType: z
|
|
360
|
+
.enum(['text', 'code', 'heading'])
|
|
361
|
+
.describe("Type of block to create: 'text', 'code', or 'heading'"),
|
|
362
|
+
options: z
|
|
363
|
+
.object({
|
|
364
|
+
// 文本块选项 - 当blockType为'text'时使用
|
|
365
|
+
text: z
|
|
366
|
+
.object({
|
|
367
|
+
// 文本内容数组,每个元素包含文本内容和样式
|
|
368
|
+
textStyles: z
|
|
369
|
+
.array(z.object({
|
|
370
|
+
text: z.string().describe('Text segment content'),
|
|
371
|
+
style: z
|
|
372
|
+
.object({
|
|
373
|
+
bold: z
|
|
374
|
+
.boolean()
|
|
375
|
+
.optional()
|
|
376
|
+
.describe('Whether to make text bold. Default is false'),
|
|
377
|
+
italic: z
|
|
378
|
+
.boolean()
|
|
379
|
+
.optional()
|
|
380
|
+
.describe('Whether to make text italic. Default is false'),
|
|
381
|
+
underline: z
|
|
382
|
+
.boolean()
|
|
383
|
+
.optional()
|
|
384
|
+
.describe('Whether to add underline. Default is false'),
|
|
385
|
+
strikethrough: z
|
|
386
|
+
.boolean()
|
|
387
|
+
.optional()
|
|
388
|
+
.describe('Whether to add strikethrough. Default is false'),
|
|
389
|
+
inline_code: z
|
|
390
|
+
.boolean()
|
|
391
|
+
.optional()
|
|
392
|
+
.describe('Whether to format as inline code. Default is false'),
|
|
393
|
+
text_color: z
|
|
394
|
+
.number()
|
|
395
|
+
.optional()
|
|
396
|
+
.describe('Text color as a number. Default is 0'),
|
|
397
|
+
})
|
|
398
|
+
.optional()
|
|
399
|
+
.describe('Text style settings'),
|
|
400
|
+
}))
|
|
401
|
+
.optional()
|
|
402
|
+
.describe('Array of text content objects with styles. If not provided, content will be used as plain text'),
|
|
403
|
+
align: z
|
|
404
|
+
.number()
|
|
405
|
+
.optional()
|
|
406
|
+
.default(1)
|
|
407
|
+
.describe('Text alignment: 1 for left, 2 for center, 3 for right. Default is 1'),
|
|
408
|
+
})
|
|
409
|
+
.optional()
|
|
410
|
+
.describe("Text block options. Only used when blockType is 'text'"),
|
|
411
|
+
// 代码块选项 - 当blockType为'code'时使用
|
|
412
|
+
code: z
|
|
413
|
+
.object({
|
|
414
|
+
code: z.string().describe('Code content'),
|
|
415
|
+
language: z
|
|
416
|
+
.number()
|
|
417
|
+
.optional()
|
|
418
|
+
.default(0)
|
|
419
|
+
.describe('Programming language code as a number. Available options:\n1: PlainText, 2: ABAP, 3: Ada, 4: Apache, 5: Apex, 6: Assembly Language, 7: Bash, 8: CSharp, 9: C++, 10: C, 11: COBOL, 12: CSS, 13: CoffeeScript, 14: D, 15: Dart, 16: Delphi, 17: Django, 18: Dockerfile, 19: Erlang, 20: Fortran, 22: Go, 23: Groovy, 24: HTML, 25: HTMLBars, 26: HTTP, 27: Haskell, 28: JSON, 29: Java, 30: JavaScript, 31: Julia, 32: Kotlin, 33: LateX, 34: Lisp, 36: Lua, 37: MATLAB, 38: Makefile, 39: Markdown, 40: Nginx, 41: Objective-C, 43: PHP, 44: Perl, 46: Power Shell, 47: Prolog, 48: ProtoBuf, 49: Python, 50: R, 52: Ruby, 53: Rust, 54: SAS, 55: SCSS, 56: SQL, 57: Scala, 58: Scheme, 60: Shell, 61: Swift, 62: Thrift, 63: TypeScript, 64: VBScript, 65: Visual Basic, 66: XML, 67: YAML, 68: CMake, 69: Diff, 70: Gherkin, 71: GraphQL, 72: OpenGL Shading Language, 73: Properties, 74: Solidity, 75: TOML'),
|
|
420
|
+
wrap: z
|
|
421
|
+
.boolean()
|
|
422
|
+
.optional()
|
|
423
|
+
.default(false)
|
|
424
|
+
.describe('Whether to enable automatic line wrapping for code blocks. Default is false'),
|
|
425
|
+
})
|
|
426
|
+
.optional()
|
|
427
|
+
.describe("Code block options. Only used when blockType is 'code'"),
|
|
428
|
+
// 标题块选项 - 当blockType为'heading'时使用
|
|
429
|
+
heading: z
|
|
430
|
+
.object({
|
|
431
|
+
level: z
|
|
432
|
+
.number()
|
|
433
|
+
.min(1)
|
|
434
|
+
.max(9)
|
|
435
|
+
.describe('Heading level from 1 to 9, where 1 is the largest heading (h1) and 9 is the smallest (h9)'),
|
|
436
|
+
content: z.string().describe('Heading text content'),
|
|
437
|
+
align: z
|
|
438
|
+
.number()
|
|
439
|
+
.optional()
|
|
440
|
+
.default(1)
|
|
441
|
+
.describe('Text alignment: 1 for left, 2 for center, 3 for right. Default is 1'),
|
|
442
|
+
})
|
|
443
|
+
.optional()
|
|
444
|
+
.describe("Heading block options. Only used when blockType is 'heading'"),
|
|
445
|
+
})
|
|
446
|
+
.optional()
|
|
447
|
+
.default({}),
|
|
448
|
+
}))
|
|
449
|
+
.describe('Array of block configurations to create in a single API call'),
|
|
450
|
+
}, async ({ documentId, parentBlockId, startIndex = 0, blocks }) => {
|
|
254
451
|
try {
|
|
255
452
|
if (!this.feishuService) {
|
|
256
453
|
return {
|
|
257
|
-
content: [
|
|
454
|
+
content: [
|
|
455
|
+
{
|
|
456
|
+
type: 'text',
|
|
457
|
+
text: 'Feishu service is not initialized. Please check the configuration',
|
|
458
|
+
},
|
|
459
|
+
],
|
|
258
460
|
};
|
|
259
461
|
}
|
|
260
|
-
Logger.log(
|
|
261
|
-
|
|
262
|
-
|
|
462
|
+
Logger.log(`开始批量创建飞书块,文档ID: ${documentId},父块ID: ${parentBlockId},块数量: ${blocks.length},起始插入位置: ${startIndex}`);
|
|
463
|
+
// 准备要创建的块内容数组
|
|
464
|
+
const blockContents = [];
|
|
465
|
+
// 处理每个块配置
|
|
466
|
+
for (const blockConfig of blocks) {
|
|
467
|
+
const { blockType, options = {} } = blockConfig;
|
|
468
|
+
// 使用指定的索引或当前索引
|
|
469
|
+
let blockContent;
|
|
470
|
+
switch (blockType) {
|
|
471
|
+
case 'text':
|
|
472
|
+
// 处理文本块
|
|
473
|
+
const textOptions = options.text || {
|
|
474
|
+
textStyles: [],
|
|
475
|
+
align: 1,
|
|
476
|
+
};
|
|
477
|
+
// 确保textStyles是一个有效的数组
|
|
478
|
+
const textStyles = textOptions.textStyles || [];
|
|
479
|
+
// 如果textStyles为空,添加一个默认的空文本
|
|
480
|
+
if (textStyles.length === 0) {
|
|
481
|
+
textStyles.push({
|
|
482
|
+
text: '',
|
|
483
|
+
style: {}, // 添加空的style对象作为默认值
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
const align = textOptions.align || 1;
|
|
487
|
+
blockContent = this.feishuService.createTextBlockContent(textStyles, align);
|
|
488
|
+
break;
|
|
489
|
+
case 'code':
|
|
490
|
+
// 处理代码块
|
|
491
|
+
const codeOptions = options.code;
|
|
492
|
+
if (codeOptions == null) {
|
|
493
|
+
break;
|
|
494
|
+
}
|
|
495
|
+
const codeContent = codeOptions.code || '';
|
|
496
|
+
const language = codeOptions.language || 0;
|
|
497
|
+
const wrap = codeOptions.wrap || false;
|
|
498
|
+
blockContent = this.feishuService.createCodeBlockContent(codeContent, language, wrap);
|
|
499
|
+
break;
|
|
500
|
+
case 'heading':
|
|
501
|
+
// 处理标题块
|
|
502
|
+
if (options.heading == null ||
|
|
503
|
+
options.heading.content == null) {
|
|
504
|
+
break;
|
|
505
|
+
}
|
|
506
|
+
const headingOptions = options.heading || {};
|
|
507
|
+
const headingContent = headingOptions.content || '';
|
|
508
|
+
const level = headingOptions.level || 1;
|
|
509
|
+
const headingAlign = headingOptions.align || 1;
|
|
510
|
+
blockContent = this.feishuService.createHeadingBlockContent(headingContent, level, headingAlign);
|
|
511
|
+
break;
|
|
512
|
+
}
|
|
513
|
+
if (blockContent) {
|
|
514
|
+
blockContents.push(blockContent);
|
|
515
|
+
Logger.log(`已准备${blockType}块,内容: ${JSON.stringify(blockContent).substring(0, 100)}...`);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
// 批量创建所有块
|
|
519
|
+
const result = await this.feishuService.createDocumentBlocks(documentId, parentBlockId, blockContents, startIndex);
|
|
520
|
+
Logger.log(`飞书块批量创建成功,共创建 ${blockContents.length} 个块`);
|
|
263
521
|
return {
|
|
264
|
-
content: [{ type:
|
|
522
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
265
523
|
};
|
|
266
524
|
}
|
|
267
525
|
catch (error) {
|
|
268
|
-
Logger.error(
|
|
526
|
+
Logger.error(`批量创建飞书块失败:`, error);
|
|
269
527
|
return {
|
|
270
|
-
content: [{ type:
|
|
528
|
+
content: [{ type: 'text', text: `批量创建飞书块失败: ${error}` }],
|
|
271
529
|
};
|
|
272
530
|
}
|
|
273
531
|
});
|
|
532
|
+
// // 添加创建飞书标题块工具
|
|
533
|
+
// this.server.tool(
|
|
534
|
+
// "create_feishu_heading_block",
|
|
535
|
+
// "Create a heading block in a Feishu document with specified level (1-9)",
|
|
536
|
+
// {
|
|
537
|
+
// documentId: z.string().describe("Document ID or URL. Supported formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID (e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf)"),
|
|
538
|
+
// parentBlockId: z.string().describe("Parent block ID (NOT URL) where the new block will be added as a child. This should be the raw block ID without any URL prefix. When adding blocks at the page level (root level), use the extracted document ID from documentId parameter"),
|
|
539
|
+
// level: z.number().min(1).max(9).describe("Heading level from 1 to 9, where 1 is the largest heading (h1) and 9 is the smallest (h9)"),
|
|
540
|
+
// content: z.string().describe("Heading text content"),
|
|
541
|
+
// index: z.number().optional().default(0).describe("Insertion position index. Default is 0 (insert at the beginning). If unsure about the position, use the get_feishu_doc_blocks tool first to understand the document structure. For consecutive insertions, calculate the next position as previous_index + 1 to avoid querying document structure repeatedly")
|
|
542
|
+
// },
|
|
543
|
+
// async ({ documentId, parentBlockId, level, content, index = 0 }) => {
|
|
544
|
+
// try {
|
|
545
|
+
// if (!this.feishuService) {
|
|
546
|
+
// return {
|
|
547
|
+
// content: [{ type: "text", text: "Feishu service is not initialized. Please check the configuration" }],
|
|
548
|
+
// };
|
|
549
|
+
// }
|
|
550
|
+
//
|
|
551
|
+
// Logger.log(`开始创建飞书标题块,文档ID: ${documentId},父块ID: ${parentBlockId},标题级别: ${level},插入位置: ${index}`);
|
|
552
|
+
// const result = await this.feishuService.createHeadingBlock(documentId, parentBlockId, content, level, index);
|
|
553
|
+
// Logger.log(`飞书标题块创建成功`);
|
|
554
|
+
//
|
|
555
|
+
// return {
|
|
556
|
+
// content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
557
|
+
// };
|
|
558
|
+
// } catch (error) {
|
|
559
|
+
// Logger.error(`创建飞书标题块失败:`, error);
|
|
560
|
+
// return {
|
|
561
|
+
// content: [{ type: "text", text: `创建飞书标题块失败: ${error}` }],
|
|
562
|
+
// };
|
|
563
|
+
// }
|
|
564
|
+
// },
|
|
565
|
+
// );
|
|
274
566
|
}
|
|
275
567
|
async connect(transport) {
|
|
276
568
|
// Logger.log("Connecting to transport...");
|
|
277
569
|
await this.server.connect(transport);
|
|
278
570
|
Logger.log = (...args) => {
|
|
279
571
|
this.server.server.sendLoggingMessage({
|
|
280
|
-
level:
|
|
572
|
+
level: 'info',
|
|
281
573
|
data: args,
|
|
282
574
|
});
|
|
283
575
|
};
|
|
284
576
|
Logger.error = (...args) => {
|
|
285
577
|
this.server.server.sendLoggingMessage({
|
|
286
|
-
level:
|
|
578
|
+
level: 'error',
|
|
287
579
|
data: args,
|
|
288
580
|
});
|
|
289
581
|
};
|
|
290
|
-
Logger.log(
|
|
582
|
+
Logger.log('Server connected and ready to process requests');
|
|
291
583
|
}
|
|
292
584
|
async startHttpServer(port) {
|
|
293
585
|
const app = express();
|
|
294
|
-
app.get(
|
|
295
|
-
console.log(
|
|
296
|
-
this.sseTransport = new SSEServerTransport(
|
|
586
|
+
app.get('/sse', async (_req, res) => {
|
|
587
|
+
console.log('New SSE connection established');
|
|
588
|
+
this.sseTransport = new SSEServerTransport('/messages', res);
|
|
297
589
|
await this.server.connect(this.sseTransport);
|
|
298
590
|
});
|
|
299
|
-
app.post(
|
|
591
|
+
app.post('/messages', async (req, res) => {
|
|
300
592
|
if (!this.sseTransport) {
|
|
301
593
|
res.sendStatus(400);
|
|
302
594
|
return;
|
package/dist/services/feishu.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import axios, { AxiosError } from "axios";
|
|
2
|
-
import { Logger } from "../server";
|
|
2
|
+
import { Logger } from "../server.js";
|
|
3
3
|
export class FeishuService {
|
|
4
4
|
constructor(appId, appSecret) {
|
|
5
5
|
Object.defineProperty(this, "appId", {
|
|
@@ -336,8 +336,107 @@ export class FeishuService {
|
|
|
336
336
|
throw error;
|
|
337
337
|
}
|
|
338
338
|
}
|
|
339
|
+
// 创建文本块内容
|
|
340
|
+
createTextBlockContent(textContents, align = 1) {
|
|
341
|
+
return {
|
|
342
|
+
block_type: 2, // 2表示文本块
|
|
343
|
+
text: {
|
|
344
|
+
elements: textContents.map(content => ({
|
|
345
|
+
text_run: {
|
|
346
|
+
content: content.text,
|
|
347
|
+
text_element_style: content.style || {}
|
|
348
|
+
}
|
|
349
|
+
})),
|
|
350
|
+
style: {
|
|
351
|
+
align: align // 1 居左,2 居中,3 居右
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
// 创建代码块内容
|
|
357
|
+
createCodeBlockContent(code, language = 0, wrap = false) {
|
|
358
|
+
return {
|
|
359
|
+
block_type: 14, // 14表示代码块
|
|
360
|
+
code: {
|
|
361
|
+
elements: [
|
|
362
|
+
{
|
|
363
|
+
text_run: {
|
|
364
|
+
content: code,
|
|
365
|
+
text_element_style: {
|
|
366
|
+
bold: false,
|
|
367
|
+
inline_code: false,
|
|
368
|
+
italic: false,
|
|
369
|
+
strikethrough: false,
|
|
370
|
+
underline: false
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
],
|
|
375
|
+
style: {
|
|
376
|
+
language: language,
|
|
377
|
+
wrap: wrap
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
// 创建标题块内容
|
|
383
|
+
createHeadingBlockContent(text, level = 1, align = 1) {
|
|
384
|
+
// 确保标题级别在有效范围内(1-9)
|
|
385
|
+
const safeLevel = Math.max(1, Math.min(9, level));
|
|
386
|
+
// 根据标题级别设置block_type和对应的属性名
|
|
387
|
+
// 飞书API中,一级标题的block_type为3,二级标题为4,以此类推
|
|
388
|
+
const blockType = 2 + safeLevel; // 一级标题为3,二级标题为4,以此类推
|
|
389
|
+
const headingKey = `heading${safeLevel}`; // heading1, heading2, ...
|
|
390
|
+
// 构建块内容
|
|
391
|
+
const blockContent = {
|
|
392
|
+
block_type: blockType
|
|
393
|
+
};
|
|
394
|
+
// 设置对应级别的标题属性
|
|
395
|
+
blockContent[headingKey] = {
|
|
396
|
+
elements: [
|
|
397
|
+
{
|
|
398
|
+
text_run: {
|
|
399
|
+
content: text,
|
|
400
|
+
text_element_style: {}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
],
|
|
404
|
+
style: {
|
|
405
|
+
align: align,
|
|
406
|
+
folded: false
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
return blockContent;
|
|
410
|
+
}
|
|
411
|
+
// 批量创建文档块
|
|
412
|
+
async createDocumentBlocks(documentId, parentBlockId, blockContents, index = 0) {
|
|
413
|
+
try {
|
|
414
|
+
const docId = this.extractDocIdFromUrl(documentId);
|
|
415
|
+
if (!docId) {
|
|
416
|
+
throw new Error(`无效的文档ID: ${documentId}`);
|
|
417
|
+
}
|
|
418
|
+
Logger.log(`开始批量创建文档块,文档ID: ${docId},父块ID: ${parentBlockId},块数量: ${blockContents.length},插入位置: ${index}`);
|
|
419
|
+
const endpoint = `/docx/v1/documents/${docId}/blocks/${parentBlockId}/children?document_revision_id=-1`;
|
|
420
|
+
Logger.log(`准备请求API端点: ${endpoint}`);
|
|
421
|
+
const data = {
|
|
422
|
+
children: blockContents,
|
|
423
|
+
index: index
|
|
424
|
+
};
|
|
425
|
+
Logger.log(`请求数据: ${JSON.stringify(data, null, 2)}`);
|
|
426
|
+
const response = await this.request(endpoint, 'POST', data);
|
|
427
|
+
if (response.code !== 0) {
|
|
428
|
+
throw new Error(`批量创建文档块失败: ${response.msg}`);
|
|
429
|
+
}
|
|
430
|
+
Logger.log(`批量文档块创建成功: ${JSON.stringify(response.data, null, 2)}`);
|
|
431
|
+
return response.data;
|
|
432
|
+
}
|
|
433
|
+
catch (error) {
|
|
434
|
+
Logger.error(`批量创建文档块失败:`, error);
|
|
435
|
+
throw error;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
339
438
|
// 创建标题块
|
|
340
|
-
async createHeadingBlock(documentId, parentBlockId, text, level = 1, index = 0) {
|
|
439
|
+
async createHeadingBlock(documentId, parentBlockId, text, level = 1, index = 0, align = 1) {
|
|
341
440
|
try {
|
|
342
441
|
const docId = this.extractDocIdFromUrl(documentId);
|
|
343
442
|
if (!docId) {
|
|
@@ -365,7 +464,7 @@ export class FeishuService {
|
|
|
365
464
|
}
|
|
366
465
|
],
|
|
367
466
|
style: {
|
|
368
|
-
align:
|
|
467
|
+
align: align,
|
|
369
468
|
folded: false
|
|
370
469
|
}
|
|
371
470
|
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 构建批量创建块的请求数据
|
|
3
|
+
* @param blocks 块内容数组
|
|
4
|
+
* @param index 插入位置索引
|
|
5
|
+
* @returns 请求数据对象
|
|
6
|
+
*/
|
|
7
|
+
export function buildCreateBlocksRequest(blocks, index = 0) {
|
|
8
|
+
return {
|
|
9
|
+
children: blocks,
|
|
10
|
+
index
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 创建文本块内容
|
|
15
|
+
* @param text 文本内容
|
|
16
|
+
* @param style 文本样式
|
|
17
|
+
* @param align 对齐方式:1左对齐,2居中,3右对齐
|
|
18
|
+
* @returns 文本块内容对象
|
|
19
|
+
*/
|
|
20
|
+
export function createTextBlockContent(textContents, align = 1) {
|
|
21
|
+
return {
|
|
22
|
+
block_type: 2, // 2表示文本块
|
|
23
|
+
text: {
|
|
24
|
+
elements: textContents.map(content => ({
|
|
25
|
+
text_run: {
|
|
26
|
+
content: content.text,
|
|
27
|
+
text_element_style: content.style || {}
|
|
28
|
+
}
|
|
29
|
+
})),
|
|
30
|
+
style: {
|
|
31
|
+
align: align // 1 居左,2 居中,3 居右
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 创建代码块内容
|
|
38
|
+
* @param code 代码内容
|
|
39
|
+
* @param language 语言类型代码
|
|
40
|
+
* @param wrap 是否自动换行
|
|
41
|
+
* @returns 代码块内容对象
|
|
42
|
+
*/
|
|
43
|
+
export function createCodeBlockContent(code, language = 0, wrap = false) {
|
|
44
|
+
return {
|
|
45
|
+
block_type: 14, // 14表示代码块
|
|
46
|
+
code: {
|
|
47
|
+
elements: [
|
|
48
|
+
{
|
|
49
|
+
text_run: {
|
|
50
|
+
content: code,
|
|
51
|
+
text_element_style: {
|
|
52
|
+
bold: false,
|
|
53
|
+
inline_code: false,
|
|
54
|
+
italic: false,
|
|
55
|
+
strikethrough: false,
|
|
56
|
+
underline: false
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
style: {
|
|
62
|
+
language: language,
|
|
63
|
+
wrap: wrap
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 创建标题块内容
|
|
70
|
+
* @param text 标题文本
|
|
71
|
+
* @param level 标题级别(1-9)
|
|
72
|
+
* @param align 对齐方式:1左对齐,2居中,3右对齐
|
|
73
|
+
* @returns 标题块内容对象
|
|
74
|
+
*/
|
|
75
|
+
export function createHeadingBlockContent(text, level = 1, align = 1) {
|
|
76
|
+
// 确保标题级别在有效范围内(1-9)
|
|
77
|
+
const safeLevel = Math.max(1, Math.min(9, level));
|
|
78
|
+
// 根据标题级别设置block_type和对应的属性名
|
|
79
|
+
// 飞书API中,一级标题的block_type为3,二级标题为4,以此类推
|
|
80
|
+
const blockType = 2 + safeLevel; // 一级标题为3,二级标题为4,以此类推
|
|
81
|
+
const headingKey = `heading${safeLevel}`; // heading1, heading2, ...
|
|
82
|
+
// 构建块内容
|
|
83
|
+
const blockContent = {
|
|
84
|
+
block_type: blockType
|
|
85
|
+
};
|
|
86
|
+
// 设置对应级别的标题属性
|
|
87
|
+
blockContent[headingKey] = {
|
|
88
|
+
elements: [
|
|
89
|
+
{
|
|
90
|
+
text_run: {
|
|
91
|
+
content: text,
|
|
92
|
+
text_element_style: {}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
],
|
|
96
|
+
style: {
|
|
97
|
+
align: align,
|
|
98
|
+
folded: false
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
return blockContent;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* 处理Markdown语法转换
|
|
105
|
+
* @param textContents 文本内容数组
|
|
106
|
+
* @returns 处理后的文本内容数组
|
|
107
|
+
*/
|
|
108
|
+
export function processMarkdownSyntax(textContents) {
|
|
109
|
+
return textContents.map(content => {
|
|
110
|
+
let { text, style = {} } = content;
|
|
111
|
+
// 创建一个新的style对象,避免修改原始对象
|
|
112
|
+
const newStyle = { ...style };
|
|
113
|
+
// 处理粗体 **text**
|
|
114
|
+
if (text.match(/\*\*([^*]+)\*\*/g)) {
|
|
115
|
+
text = text.replace(/\*\*([^*]+)\*\*/g, "$1");
|
|
116
|
+
newStyle.bold = true;
|
|
117
|
+
}
|
|
118
|
+
// 处理斜体 *text*
|
|
119
|
+
if (text.match(/(?<!\*)\*([^*]+)\*(?!\*)/g)) {
|
|
120
|
+
text = text.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "$1");
|
|
121
|
+
newStyle.italic = true;
|
|
122
|
+
}
|
|
123
|
+
// 处理删除线 ~~text~~
|
|
124
|
+
if (text.match(/~~([^~]+)~~/g)) {
|
|
125
|
+
text = text.replace(/~~([^~]+)~~/g, "$1");
|
|
126
|
+
newStyle.strikethrough = true;
|
|
127
|
+
}
|
|
128
|
+
// 处理行内代码 `code`
|
|
129
|
+
if (text.match(/`([^`]+)`/g)) {
|
|
130
|
+
text = text.replace(/`([^`]+)`/g, "$1");
|
|
131
|
+
newStyle.inline_code = true;
|
|
132
|
+
}
|
|
133
|
+
return { text, style: newStyle };
|
|
134
|
+
});
|
|
135
|
+
}
|