feishu-mcp 0.0.6 → 0.0.7
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 +1 -6
- package/dist/config.js +22 -100
- package/dist/index.js +14 -13
- package/dist/server.js +363 -449
- package/dist/services/baseService.js +204 -0
- package/dist/services/blockFactory.js +184 -0
- package/dist/services/feishuApiService.js +488 -0
- package/dist/services/feishuBlockService.js +179 -0
- package/dist/services/feishuService.js +475 -0
- package/dist/types/feishuSchema.js +97 -0
- package/dist/utils/cache.js +221 -0
- package/dist/utils/config.js +363 -0
- package/dist/utils/document.js +74 -0
- package/dist/utils/error.js +154 -0
- package/dist/utils/logger.js +257 -0
- package/dist/utils/paramUtils.js +193 -0
- package/package.json +1 -1
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
import { BaseApiService } from './baseService.js';
|
|
2
|
+
import { Logger } from '../utils/logger.js';
|
|
3
|
+
import { Config } from '../utils/config.js';
|
|
4
|
+
import { CacheManager } from '../utils/cache.js';
|
|
5
|
+
import { ParamUtils } from '../utils/paramUtils.js';
|
|
6
|
+
import { BlockFactory, BlockType } from './blockFactory.js';
|
|
7
|
+
import axios from 'axios';
|
|
8
|
+
/**
|
|
9
|
+
* 飞书API服务类
|
|
10
|
+
* 提供飞书API的所有基础操作,包括认证、请求和缓存管理
|
|
11
|
+
*/
|
|
12
|
+
export class FeishuApiService extends BaseApiService {
|
|
13
|
+
/**
|
|
14
|
+
* 私有构造函数,用于单例模式
|
|
15
|
+
*/
|
|
16
|
+
constructor() {
|
|
17
|
+
super();
|
|
18
|
+
Object.defineProperty(this, "cacheManager", {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
configurable: true,
|
|
21
|
+
writable: true,
|
|
22
|
+
value: void 0
|
|
23
|
+
});
|
|
24
|
+
Object.defineProperty(this, "blockFactory", {
|
|
25
|
+
enumerable: true,
|
|
26
|
+
configurable: true,
|
|
27
|
+
writable: true,
|
|
28
|
+
value: void 0
|
|
29
|
+
});
|
|
30
|
+
Object.defineProperty(this, "config", {
|
|
31
|
+
enumerable: true,
|
|
32
|
+
configurable: true,
|
|
33
|
+
writable: true,
|
|
34
|
+
value: void 0
|
|
35
|
+
});
|
|
36
|
+
this.cacheManager = CacheManager.getInstance();
|
|
37
|
+
this.blockFactory = BlockFactory.getInstance();
|
|
38
|
+
this.config = Config.getInstance();
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 获取飞书API服务实例
|
|
42
|
+
* @returns 飞书API服务实例
|
|
43
|
+
*/
|
|
44
|
+
static getInstance() {
|
|
45
|
+
if (!FeishuApiService.instance) {
|
|
46
|
+
FeishuApiService.instance = new FeishuApiService();
|
|
47
|
+
}
|
|
48
|
+
return FeishuApiService.instance;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 获取API基础URL
|
|
52
|
+
* @returns API基础URL
|
|
53
|
+
*/
|
|
54
|
+
getBaseUrl() {
|
|
55
|
+
return this.config.feishu.baseUrl;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 获取API认证端点
|
|
59
|
+
* @returns 认证端点URL
|
|
60
|
+
*/
|
|
61
|
+
getAuthEndpoint() {
|
|
62
|
+
return '/auth/v3/tenant_access_token/internal';
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 获取访问令牌
|
|
66
|
+
* @returns 访问令牌
|
|
67
|
+
* @throws 如果获取令牌失败则抛出错误
|
|
68
|
+
*/
|
|
69
|
+
async getAccessToken() {
|
|
70
|
+
// 尝试从缓存获取
|
|
71
|
+
const cachedToken = this.cacheManager.getToken();
|
|
72
|
+
if (cachedToken) {
|
|
73
|
+
Logger.debug('使用缓存的访问令牌');
|
|
74
|
+
return cachedToken;
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const requestData = {
|
|
78
|
+
app_id: this.config.feishu.appId,
|
|
79
|
+
app_secret: this.config.feishu.appSecret,
|
|
80
|
+
};
|
|
81
|
+
Logger.info('开始获取新的飞书访问令牌...');
|
|
82
|
+
Logger.debug('认证请求参数:', requestData);
|
|
83
|
+
// 不使用通用的request方法,因为这个请求不需要认证
|
|
84
|
+
// 为了确保正确处理响应,我们直接使用axios
|
|
85
|
+
const url = `${this.getBaseUrl()}${this.getAuthEndpoint()}`;
|
|
86
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
87
|
+
Logger.debug(`发送认证请求到: ${url}`);
|
|
88
|
+
const response = await axios.post(url, requestData, { headers });
|
|
89
|
+
Logger.debug('认证响应:', response.data);
|
|
90
|
+
if (!response.data || typeof response.data !== 'object') {
|
|
91
|
+
throw new Error('获取飞书访问令牌失败:响应格式无效');
|
|
92
|
+
}
|
|
93
|
+
// 检查错误码
|
|
94
|
+
if (response.data.code !== 0) {
|
|
95
|
+
throw new Error(`获取飞书访问令牌失败:${response.data.msg || '未知错误'} (错误码: ${response.data.code})`);
|
|
96
|
+
}
|
|
97
|
+
if (!response.data.tenant_access_token) {
|
|
98
|
+
throw new Error('获取飞书访问令牌失败:响应中没有token');
|
|
99
|
+
}
|
|
100
|
+
this.accessToken = response.data.tenant_access_token;
|
|
101
|
+
this.tokenExpireTime = Date.now() + Math.min(response.data.expire * 1000, this.config.feishu.tokenLifetime);
|
|
102
|
+
// 缓存令牌
|
|
103
|
+
this.cacheManager.cacheToken(this.accessToken, response.data.expire);
|
|
104
|
+
Logger.info(`成功获取新的飞书访问令牌,有效期: ${response.data.expire} 秒`);
|
|
105
|
+
return this.accessToken;
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
Logger.error('获取访问令牌失败:', error);
|
|
109
|
+
this.handleApiError(error, '获取飞书访问令牌失败');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* 创建飞书文档
|
|
114
|
+
* @param title 文档标题
|
|
115
|
+
* @param folderToken 文件夹Token
|
|
116
|
+
* @returns 创建的文档信息
|
|
117
|
+
*/
|
|
118
|
+
async createDocument(title, folderToken) {
|
|
119
|
+
try {
|
|
120
|
+
const endpoint = '/docx/v1/documents';
|
|
121
|
+
const payload = {
|
|
122
|
+
title,
|
|
123
|
+
folder_token: folderToken
|
|
124
|
+
};
|
|
125
|
+
const response = await this.post(endpoint, payload);
|
|
126
|
+
return response;
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
this.handleApiError(error, '创建飞书文档失败');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* 获取文档信息
|
|
134
|
+
* @param documentId 文档ID或URL
|
|
135
|
+
* @returns 文档信息
|
|
136
|
+
*/
|
|
137
|
+
async getDocumentInfo(documentId) {
|
|
138
|
+
try {
|
|
139
|
+
const normalizedDocId = ParamUtils.processDocumentId(documentId);
|
|
140
|
+
const endpoint = `/docx/v1/documents/${normalizedDocId}`;
|
|
141
|
+
const response = await this.get(endpoint);
|
|
142
|
+
return response;
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
this.handleApiError(error, '获取文档信息失败');
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* 获取文档内容
|
|
150
|
+
* @param documentId 文档ID或URL
|
|
151
|
+
* @param lang 语言代码,0为中文,1为英文
|
|
152
|
+
* @returns 文档内容
|
|
153
|
+
*/
|
|
154
|
+
async getDocumentContent(documentId, lang = 0) {
|
|
155
|
+
try {
|
|
156
|
+
const normalizedDocId = ParamUtils.processDocumentId(documentId);
|
|
157
|
+
const endpoint = `/docx/v1/documents/${normalizedDocId}/raw_content`;
|
|
158
|
+
const params = { lang };
|
|
159
|
+
const response = await this.get(endpoint, params);
|
|
160
|
+
return response.content;
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
this.handleApiError(error, '获取文档内容失败');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* 获取文档块结构
|
|
168
|
+
* @param documentId 文档ID或URL
|
|
169
|
+
* @param pageSize 每页块数量
|
|
170
|
+
* @returns 文档块数组
|
|
171
|
+
*/
|
|
172
|
+
async getDocumentBlocks(documentId, pageSize = 500) {
|
|
173
|
+
try {
|
|
174
|
+
const normalizedDocId = ParamUtils.processDocumentId(documentId);
|
|
175
|
+
const endpoint = `/docx/v1/documents/${normalizedDocId}/blocks`;
|
|
176
|
+
let pageToken = '';
|
|
177
|
+
let allBlocks = [];
|
|
178
|
+
// 分页获取所有块
|
|
179
|
+
do {
|
|
180
|
+
const params = {
|
|
181
|
+
page_size: pageSize,
|
|
182
|
+
document_revision_id: -1
|
|
183
|
+
};
|
|
184
|
+
if (pageToken) {
|
|
185
|
+
params.page_token = pageToken;
|
|
186
|
+
}
|
|
187
|
+
const response = await this.get(endpoint, params);
|
|
188
|
+
const blocks = response.items || [];
|
|
189
|
+
allBlocks = [...allBlocks, ...blocks];
|
|
190
|
+
pageToken = response.page_token;
|
|
191
|
+
} while (pageToken);
|
|
192
|
+
return allBlocks;
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
this.handleApiError(error, '获取文档块结构失败');
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* 获取块内容
|
|
200
|
+
* @param documentId 文档ID或URL
|
|
201
|
+
* @param blockId 块ID
|
|
202
|
+
* @returns 块内容
|
|
203
|
+
*/
|
|
204
|
+
async getBlockContent(documentId, blockId) {
|
|
205
|
+
try {
|
|
206
|
+
const normalizedDocId = ParamUtils.processDocumentId(documentId);
|
|
207
|
+
const safeBlockId = ParamUtils.processBlockId(blockId);
|
|
208
|
+
const endpoint = `/docx/v1/documents/${normalizedDocId}/blocks/${safeBlockId}`;
|
|
209
|
+
const params = { document_revision_id: -1 };
|
|
210
|
+
const response = await this.get(endpoint, params);
|
|
211
|
+
return response;
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
this.handleApiError(error, '获取块内容失败');
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* 更新块文本内容
|
|
219
|
+
* @param documentId 文档ID或URL
|
|
220
|
+
* @param blockId 块ID
|
|
221
|
+
* @param textElements 文本元素数组
|
|
222
|
+
* @returns 更新结果
|
|
223
|
+
*/
|
|
224
|
+
async updateBlockTextContent(documentId, blockId, textElements) {
|
|
225
|
+
try {
|
|
226
|
+
const docId = ParamUtils.processDocumentId(documentId);
|
|
227
|
+
const endpoint = `/docx/v1/documents/${docId}/blocks/${blockId}?document_revision_id=-1`;
|
|
228
|
+
Logger.debug(`准备请求API端点: ${endpoint}`);
|
|
229
|
+
const elements = textElements.map(item => ({
|
|
230
|
+
text_run: {
|
|
231
|
+
content: item.text,
|
|
232
|
+
text_element_style: item.style || {}
|
|
233
|
+
}
|
|
234
|
+
}));
|
|
235
|
+
const data = {
|
|
236
|
+
update_text_elements: {
|
|
237
|
+
elements: elements
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
Logger.debug(`请求数据: ${JSON.stringify(data, null, 2)}`);
|
|
241
|
+
const response = await this.patch(endpoint, data);
|
|
242
|
+
return response;
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
this.handleApiError(error, '更新块文本内容失败');
|
|
246
|
+
return null; // 永远不会执行到这里
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* 创建文档块
|
|
251
|
+
* @param documentId 文档ID或URL
|
|
252
|
+
* @param parentBlockId 父块ID
|
|
253
|
+
* @param blockContent 块内容
|
|
254
|
+
* @param index 插入位置索引
|
|
255
|
+
* @returns 创建结果
|
|
256
|
+
*/
|
|
257
|
+
async createDocumentBlock(documentId, parentBlockId, blockContent, index = 0) {
|
|
258
|
+
try {
|
|
259
|
+
const normalizedDocId = ParamUtils.processDocumentId(documentId);
|
|
260
|
+
const endpoint = `/docx/v1/documents/${normalizedDocId}/blocks/${parentBlockId}/children?document_revision_id=-1`;
|
|
261
|
+
Logger.debug(`准备请求API端点: ${endpoint}`);
|
|
262
|
+
const payload = {
|
|
263
|
+
children: [blockContent],
|
|
264
|
+
index
|
|
265
|
+
};
|
|
266
|
+
Logger.debug(`请求数据: ${JSON.stringify(payload, null, 2)}`);
|
|
267
|
+
const response = await this.post(endpoint, payload);
|
|
268
|
+
return response;
|
|
269
|
+
}
|
|
270
|
+
catch (error) {
|
|
271
|
+
this.handleApiError(error, '创建文档块失败');
|
|
272
|
+
return null; // 永远不会执行到这里
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* 批量创建文档块
|
|
277
|
+
* @param documentId 文档ID或URL
|
|
278
|
+
* @param parentBlockId 父块ID
|
|
279
|
+
* @param blockContents 块内容数组
|
|
280
|
+
* @param index 起始插入位置索引
|
|
281
|
+
* @returns 创建结果
|
|
282
|
+
*/
|
|
283
|
+
async createDocumentBlocks(documentId, parentBlockId, blockContents, index = 0) {
|
|
284
|
+
try {
|
|
285
|
+
const normalizedDocId = ParamUtils.processDocumentId(documentId);
|
|
286
|
+
const endpoint = `/docx/v1/documents/${normalizedDocId}/blocks/${parentBlockId}/children?document_revision_id=-1`;
|
|
287
|
+
Logger.debug(`准备请求API端点: ${endpoint}`);
|
|
288
|
+
const payload = {
|
|
289
|
+
children: blockContents,
|
|
290
|
+
index
|
|
291
|
+
};
|
|
292
|
+
Logger.debug(`请求数据: ${JSON.stringify(payload, null, 2)}`);
|
|
293
|
+
const response = await this.post(endpoint, payload);
|
|
294
|
+
return response;
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
this.handleApiError(error, '批量创建文档块失败');
|
|
298
|
+
return null; // 永远不会执行到这里
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* 创建文本块
|
|
303
|
+
* @param documentId 文档ID或URL
|
|
304
|
+
* @param parentBlockId 父块ID
|
|
305
|
+
* @param textContents 文本内容数组
|
|
306
|
+
* @param align 对齐方式,1为左对齐,2为居中,3为右对齐
|
|
307
|
+
* @param index 插入位置索引
|
|
308
|
+
* @returns 创建结果
|
|
309
|
+
*/
|
|
310
|
+
async createTextBlock(documentId, parentBlockId, textContents, align = 1, index = 0) {
|
|
311
|
+
const blockContent = this.blockFactory.createTextBlock({
|
|
312
|
+
textContents,
|
|
313
|
+
align
|
|
314
|
+
});
|
|
315
|
+
return this.createDocumentBlock(documentId, parentBlockId, blockContent, index);
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* 创建代码块
|
|
319
|
+
* @param documentId 文档ID或URL
|
|
320
|
+
* @param parentBlockId 父块ID
|
|
321
|
+
* @param code 代码内容
|
|
322
|
+
* @param language 语言代码
|
|
323
|
+
* @param wrap 是否自动换行
|
|
324
|
+
* @param index 插入位置索引
|
|
325
|
+
* @returns 创建结果
|
|
326
|
+
*/
|
|
327
|
+
async createCodeBlock(documentId, parentBlockId, code, language = 0, wrap = false, index = 0) {
|
|
328
|
+
const blockContent = this.blockFactory.createCodeBlock({
|
|
329
|
+
code,
|
|
330
|
+
language,
|
|
331
|
+
wrap
|
|
332
|
+
});
|
|
333
|
+
return this.createDocumentBlock(documentId, parentBlockId, blockContent, index);
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* 创建标题块
|
|
337
|
+
* @param documentId 文档ID或URL
|
|
338
|
+
* @param parentBlockId 父块ID
|
|
339
|
+
* @param text 标题文本
|
|
340
|
+
* @param level 标题级别,1-9
|
|
341
|
+
* @param index 插入位置索引
|
|
342
|
+
* @param align 对齐方式,1为左对齐,2为居中,3为右对齐
|
|
343
|
+
* @returns 创建结果
|
|
344
|
+
*/
|
|
345
|
+
async createHeadingBlock(documentId, parentBlockId, text, level = 1, index = 0, align = 1) {
|
|
346
|
+
const blockContent = this.blockFactory.createHeadingBlock({
|
|
347
|
+
text,
|
|
348
|
+
level,
|
|
349
|
+
align
|
|
350
|
+
});
|
|
351
|
+
return this.createDocumentBlock(documentId, parentBlockId, blockContent, index);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* 创建列表块
|
|
355
|
+
* @param documentId 文档ID或URL
|
|
356
|
+
* @param parentBlockId 父块ID
|
|
357
|
+
* @param text 列表项文本
|
|
358
|
+
* @param isOrdered 是否是有序列表
|
|
359
|
+
* @param index 插入位置索引
|
|
360
|
+
* @param align 对齐方式,1为左对齐,2为居中,3为右对齐
|
|
361
|
+
* @returns 创建结果
|
|
362
|
+
*/
|
|
363
|
+
async createListBlock(documentId, parentBlockId, text, isOrdered = false, index = 0, align = 1) {
|
|
364
|
+
const blockContent = this.blockFactory.createListBlock({
|
|
365
|
+
text,
|
|
366
|
+
isOrdered,
|
|
367
|
+
align
|
|
368
|
+
});
|
|
369
|
+
return this.createDocumentBlock(documentId, parentBlockId, blockContent, index);
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* 创建混合块
|
|
373
|
+
* @param documentId 文档ID或URL
|
|
374
|
+
* @param parentBlockId 父块ID
|
|
375
|
+
* @param blocks 块配置数组
|
|
376
|
+
* @param index 插入位置索引
|
|
377
|
+
* @returns 创建结果
|
|
378
|
+
*/
|
|
379
|
+
async createMixedBlocks(documentId, parentBlockId, blocks, index = 0) {
|
|
380
|
+
const blockContents = this.blockFactory.createBatchBlocks(blocks);
|
|
381
|
+
return this.createDocumentBlocks(documentId, parentBlockId, blockContents, index);
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* 将飞书Wiki链接转换为文档ID
|
|
385
|
+
* @param wikiUrl Wiki链接或Token
|
|
386
|
+
* @returns 文档ID
|
|
387
|
+
*/
|
|
388
|
+
async convertWikiToDocumentId(wikiUrl) {
|
|
389
|
+
try {
|
|
390
|
+
const wikiToken = ParamUtils.processWikiToken(wikiUrl);
|
|
391
|
+
// 尝试从缓存获取
|
|
392
|
+
const cachedDocId = this.cacheManager.getWikiToDocId(wikiToken);
|
|
393
|
+
if (cachedDocId) {
|
|
394
|
+
Logger.debug(`使用缓存的Wiki转换结果: ${wikiToken} -> ${cachedDocId}`);
|
|
395
|
+
return cachedDocId;
|
|
396
|
+
}
|
|
397
|
+
// 获取Wiki节点信息
|
|
398
|
+
const endpoint = `/wiki/v2/spaces/get_node`;
|
|
399
|
+
const params = { token: wikiToken, obj_type: 'wiki' };
|
|
400
|
+
const response = await this.get(endpoint, params);
|
|
401
|
+
if (!response.node || !response.node.obj_token) {
|
|
402
|
+
throw new Error(`无法从Wiki节点获取文档ID: ${wikiToken}`);
|
|
403
|
+
}
|
|
404
|
+
const documentId = response.node.obj_token;
|
|
405
|
+
// 缓存结果
|
|
406
|
+
this.cacheManager.cacheWikiToDocId(wikiToken, documentId);
|
|
407
|
+
Logger.debug(`Wiki转换为文档ID: ${wikiToken} -> ${documentId}`);
|
|
408
|
+
return documentId;
|
|
409
|
+
}
|
|
410
|
+
catch (error) {
|
|
411
|
+
this.handleApiError(error, 'Wiki转换为文档ID失败');
|
|
412
|
+
return ''; // 永远不会执行到这里
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* 获取BlockFactory实例
|
|
417
|
+
* @returns BlockFactory实例
|
|
418
|
+
*/
|
|
419
|
+
getBlockFactory() {
|
|
420
|
+
return this.blockFactory;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* 创建块内容对象
|
|
424
|
+
* @param blockType 块类型
|
|
425
|
+
* @param options 块选项
|
|
426
|
+
* @returns 块内容对象
|
|
427
|
+
*/
|
|
428
|
+
createBlockContent(blockType, options) {
|
|
429
|
+
try {
|
|
430
|
+
// 使用枚举类型来避免字符串错误
|
|
431
|
+
const blockTypeEnum = blockType;
|
|
432
|
+
// 构建块配置
|
|
433
|
+
let blockConfig = {
|
|
434
|
+
type: blockTypeEnum,
|
|
435
|
+
options: {}
|
|
436
|
+
};
|
|
437
|
+
switch (blockTypeEnum) {
|
|
438
|
+
case BlockType.TEXT:
|
|
439
|
+
if ('text' in options && options.text) {
|
|
440
|
+
const textOptions = options.text;
|
|
441
|
+
blockConfig.options = {
|
|
442
|
+
textContents: textOptions.textStyles || [],
|
|
443
|
+
align: textOptions.align || 1
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
break;
|
|
447
|
+
case BlockType.CODE:
|
|
448
|
+
if ('code' in options && options.code) {
|
|
449
|
+
const codeOptions = options.code;
|
|
450
|
+
blockConfig.options = {
|
|
451
|
+
code: codeOptions.code || '',
|
|
452
|
+
language: codeOptions.language === 0 ? 0 : (codeOptions.language || 0),
|
|
453
|
+
wrap: codeOptions.wrap || false
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
break;
|
|
457
|
+
case BlockType.HEADING:
|
|
458
|
+
if ('heading' in options && options.heading) {
|
|
459
|
+
const headingOptions = options.heading;
|
|
460
|
+
blockConfig.options = {
|
|
461
|
+
text: headingOptions.content || '',
|
|
462
|
+
level: headingOptions.level || 1,
|
|
463
|
+
align: (headingOptions.align === 1 || headingOptions.align === 2 || headingOptions.align === 3)
|
|
464
|
+
? headingOptions.align : 1
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
break;
|
|
468
|
+
case BlockType.LIST:
|
|
469
|
+
if ('list' in options && options.list) {
|
|
470
|
+
const listOptions = options.list;
|
|
471
|
+
blockConfig.options = {
|
|
472
|
+
text: listOptions.content || '',
|
|
473
|
+
isOrdered: listOptions.isOrdered || false,
|
|
474
|
+
align: (listOptions.align === 1 || listOptions.align === 2 || listOptions.align === 3)
|
|
475
|
+
? listOptions.align : 1
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
// 使用BlockFactory创建块
|
|
481
|
+
return this.blockFactory.createBlock(blockConfig.type, blockConfig.options);
|
|
482
|
+
}
|
|
483
|
+
catch (error) {
|
|
484
|
+
Logger.error(`创建块内容对象失败: ${error}`);
|
|
485
|
+
return null;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deprecated 这个文件已被弃用,所有功能已迁移到BlockFactory类。
|
|
3
|
+
* 请使用BlockFactory创建各种块内容。
|
|
4
|
+
* 所有接口定义已迁移到blockFactory.ts文件。
|
|
5
|
+
* 此文件仅保留为历史兼容性目的,将在后续版本中移除。
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* 构建批量创建块的请求数据
|
|
9
|
+
* @param blocks 块内容数组
|
|
10
|
+
* @param index 插入位置索引
|
|
11
|
+
* @returns 请求数据对象
|
|
12
|
+
* @deprecated 请使用BlockFactory.buildCreateBlocksRequest
|
|
13
|
+
*/
|
|
14
|
+
export function buildCreateBlocksRequest(blocks, index = 0) {
|
|
15
|
+
return {
|
|
16
|
+
children: blocks,
|
|
17
|
+
index
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 创建文本块内容
|
|
22
|
+
* @param align 对齐方式:1左对齐,2居中,3右对齐
|
|
23
|
+
* @returns 文本块内容对象
|
|
24
|
+
* @deprecated 请使用BlockFactory.createTextBlock
|
|
25
|
+
*/
|
|
26
|
+
export function createTextBlockContent(textContents, align = 1) {
|
|
27
|
+
return {
|
|
28
|
+
block_type: 2, // 2表示文本块
|
|
29
|
+
text: {
|
|
30
|
+
elements: textContents.map(content => ({
|
|
31
|
+
text_run: {
|
|
32
|
+
content: content.text,
|
|
33
|
+
text_element_style: content.style || {}
|
|
34
|
+
}
|
|
35
|
+
})),
|
|
36
|
+
style: {
|
|
37
|
+
align: align // 1 居左,2 居中,3 居右
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 创建代码块内容
|
|
44
|
+
* @param code 代码内容
|
|
45
|
+
* @param language 语言类型代码
|
|
46
|
+
* @param wrap 是否自动换行
|
|
47
|
+
* @returns 代码块内容对象
|
|
48
|
+
* @deprecated 请使用BlockFactory.createCodeBlock
|
|
49
|
+
*/
|
|
50
|
+
export function createCodeBlockContent(code, language = 0, wrap = false) {
|
|
51
|
+
return {
|
|
52
|
+
block_type: 14, // 14表示代码块
|
|
53
|
+
code: {
|
|
54
|
+
elements: [
|
|
55
|
+
{
|
|
56
|
+
text_run: {
|
|
57
|
+
content: code,
|
|
58
|
+
text_element_style: {
|
|
59
|
+
bold: false,
|
|
60
|
+
inline_code: false,
|
|
61
|
+
italic: false,
|
|
62
|
+
strikethrough: false,
|
|
63
|
+
underline: false
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
style: {
|
|
69
|
+
language: language,
|
|
70
|
+
wrap: wrap
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* 创建标题块内容
|
|
77
|
+
* @param text 标题文本
|
|
78
|
+
* @param level 标题级别(1-9)
|
|
79
|
+
* @param align 对齐方式:1左对齐,2居中,3右对齐
|
|
80
|
+
* @returns 标题块内容对象
|
|
81
|
+
* @deprecated 请使用BlockFactory.createHeadingBlock
|
|
82
|
+
*/
|
|
83
|
+
export function createHeadingBlockContent(text, level = 1, align = 1) {
|
|
84
|
+
// 确保标题级别在有效范围内(1-9)
|
|
85
|
+
const safeLevel = Math.max(1, Math.min(9, level));
|
|
86
|
+
// 根据标题级别设置block_type和对应的属性名
|
|
87
|
+
// 飞书API中,一级标题的block_type为3,二级标题为4,以此类推
|
|
88
|
+
const blockType = 2 + safeLevel; // 一级标题为3,二级标题为4,以此类推
|
|
89
|
+
const headingKey = `heading${safeLevel}`; // heading1, heading2, ...
|
|
90
|
+
// 构建块内容
|
|
91
|
+
const blockContent = {
|
|
92
|
+
block_type: blockType
|
|
93
|
+
};
|
|
94
|
+
// 设置对应级别的标题属性
|
|
95
|
+
blockContent[headingKey] = {
|
|
96
|
+
elements: [
|
|
97
|
+
{
|
|
98
|
+
text_run: {
|
|
99
|
+
content: text,
|
|
100
|
+
text_element_style: {}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
],
|
|
104
|
+
style: {
|
|
105
|
+
align: align,
|
|
106
|
+
folded: false
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
return blockContent;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* 创建列表块内容(有序或无序)
|
|
113
|
+
* @param text 列表项文本
|
|
114
|
+
* @param isOrdered 是否为有序列表
|
|
115
|
+
* @param align 对齐方式:1左对齐,2居中,3右对齐
|
|
116
|
+
* @returns 列表块内容对象
|
|
117
|
+
* @deprecated 请使用BlockFactory.createListBlock
|
|
118
|
+
*/
|
|
119
|
+
export function createListBlockContent(text, isOrdered = false, align = 1) {
|
|
120
|
+
// 确保 align 值在合法范围内(1-3)
|
|
121
|
+
const safeAlign = (align === 1 || align === 2 || align === 3) ? align : 1;
|
|
122
|
+
// 有序列表是 block_type: 13,无序列表是 block_type: 12
|
|
123
|
+
const blockType = isOrdered ? 13 : 12;
|
|
124
|
+
const propertyKey = isOrdered ? "ordered" : "bullet";
|
|
125
|
+
// 构建块内容
|
|
126
|
+
const blockContent = {
|
|
127
|
+
block_type: blockType
|
|
128
|
+
};
|
|
129
|
+
// 设置列表属性
|
|
130
|
+
blockContent[propertyKey] = {
|
|
131
|
+
elements: [
|
|
132
|
+
{
|
|
133
|
+
text_run: {
|
|
134
|
+
content: text,
|
|
135
|
+
text_element_style: {}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
],
|
|
139
|
+
style: {
|
|
140
|
+
align: safeAlign,
|
|
141
|
+
folded: false
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
return blockContent;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* 处理Markdown语法转换
|
|
148
|
+
* @param textContents 文本内容数组
|
|
149
|
+
* @returns 处理后的文本内容数组
|
|
150
|
+
* @deprecated 应当避免使用Markdown语法,请直接使用TextElementStyle设置样式
|
|
151
|
+
*/
|
|
152
|
+
export function processMarkdownSyntax(textContents) {
|
|
153
|
+
return textContents.map(content => {
|
|
154
|
+
let { text, style = {} } = content;
|
|
155
|
+
// 创建一个新的style对象,避免修改原始对象
|
|
156
|
+
const newStyle = { ...style };
|
|
157
|
+
// 处理粗体 **text**
|
|
158
|
+
if (text.match(/\*\*([^*]+)\*\*/g)) {
|
|
159
|
+
text = text.replace(/\*\*([^*]+)\*\*/g, "$1");
|
|
160
|
+
newStyle.bold = true;
|
|
161
|
+
}
|
|
162
|
+
// 处理斜体 *text*
|
|
163
|
+
if (text.match(/(?<!\*)\*([^*]+)\*(?!\*)/g)) {
|
|
164
|
+
text = text.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "$1");
|
|
165
|
+
newStyle.italic = true;
|
|
166
|
+
}
|
|
167
|
+
// 处理删除线 ~~text~~
|
|
168
|
+
if (text.match(/~~([^~]+)~~/g)) {
|
|
169
|
+
text = text.replace(/~~([^~]+)~~/g, "$1");
|
|
170
|
+
newStyle.strikethrough = true;
|
|
171
|
+
}
|
|
172
|
+
// 处理行内代码 `code`
|
|
173
|
+
if (text.match(/`([^`]+)`/g)) {
|
|
174
|
+
text = text.replace(/`([^`]+)`/g, "$1");
|
|
175
|
+
newStyle.inline_code = true;
|
|
176
|
+
}
|
|
177
|
+
return { text, style: newStyle };
|
|
178
|
+
});
|
|
179
|
+
}
|