feishu-mcp 0.0.9 → 0.0.11
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/LICENSE +21 -21
- package/README.md +164 -164
- package/dist/cli.js +0 -0
- package/dist/config.js +22 -100
- package/dist/mcp/tools/feishuBlockTools.js +50 -17
- package/dist/mcp/tools/feishuTools.js +3 -4
- package/dist/services/blockFactory.js +29 -11
- package/dist/services/feishu.js +0 -3
- package/dist/services/feishuApiService.js +90 -3
- package/dist/services/feishuBlockService.js +179 -0
- package/dist/services/feishuService.js +475 -0
- package/dist/types/feishuSchema.js +8 -7
- package/dist/utils/logger.js +2 -2
- package/package.json +74 -74
- package/README.en.md +0 -201
|
@@ -34,6 +34,28 @@ export class BlockFactory {
|
|
|
34
34
|
}
|
|
35
35
|
return BlockFactory.instance;
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* 获取默认的文本元素样式
|
|
39
|
+
* @returns 默认文本元素样式
|
|
40
|
+
*/
|
|
41
|
+
static getDefaultTextElementStyle() {
|
|
42
|
+
return {
|
|
43
|
+
bold: false,
|
|
44
|
+
inline_code: false,
|
|
45
|
+
italic: false,
|
|
46
|
+
strikethrough: false,
|
|
47
|
+
underline: false
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 应用默认文本样式
|
|
52
|
+
* @param style 已有样式(可选)
|
|
53
|
+
* @returns 合并后的样式
|
|
54
|
+
*/
|
|
55
|
+
static applyDefaultTextStyle(style) {
|
|
56
|
+
const defaultStyle = BlockFactory.getDefaultTextElementStyle();
|
|
57
|
+
return style ? { ...defaultStyle, ...style } : defaultStyle;
|
|
58
|
+
}
|
|
37
59
|
/**
|
|
38
60
|
* 创建块内容
|
|
39
61
|
* @param type 块类型
|
|
@@ -68,7 +90,7 @@ export class BlockFactory {
|
|
|
68
90
|
elements: textContents.map(content => ({
|
|
69
91
|
text_run: {
|
|
70
92
|
content: content.text,
|
|
71
|
-
text_element_style: content.style
|
|
93
|
+
text_element_style: BlockFactory.applyDefaultTextStyle(content.style)
|
|
72
94
|
}
|
|
73
95
|
})),
|
|
74
96
|
style: {
|
|
@@ -84,6 +106,8 @@ export class BlockFactory {
|
|
|
84
106
|
*/
|
|
85
107
|
createCodeBlock(options) {
|
|
86
108
|
const { code, language = 0, wrap = false } = options;
|
|
109
|
+
// 校验 language 合法性,飞书API只允许1~75
|
|
110
|
+
const safeLanguage = language >= 1 && language <= 75 ? language : 1;
|
|
87
111
|
return {
|
|
88
112
|
block_type: 14, // 14表示代码块
|
|
89
113
|
code: {
|
|
@@ -91,18 +115,12 @@ export class BlockFactory {
|
|
|
91
115
|
{
|
|
92
116
|
text_run: {
|
|
93
117
|
content: code,
|
|
94
|
-
text_element_style:
|
|
95
|
-
bold: false,
|
|
96
|
-
inline_code: false,
|
|
97
|
-
italic: false,
|
|
98
|
-
strikethrough: false,
|
|
99
|
-
underline: false
|
|
100
|
-
}
|
|
118
|
+
text_element_style: BlockFactory.getDefaultTextElementStyle()
|
|
101
119
|
}
|
|
102
120
|
}
|
|
103
121
|
],
|
|
104
122
|
style: {
|
|
105
|
-
language:
|
|
123
|
+
language: safeLanguage,
|
|
106
124
|
wrap: wrap
|
|
107
125
|
}
|
|
108
126
|
}
|
|
@@ -131,7 +149,7 @@ export class BlockFactory {
|
|
|
131
149
|
{
|
|
132
150
|
text_run: {
|
|
133
151
|
content: text,
|
|
134
|
-
text_element_style:
|
|
152
|
+
text_element_style: BlockFactory.getDefaultTextElementStyle()
|
|
135
153
|
}
|
|
136
154
|
}
|
|
137
155
|
],
|
|
@@ -162,7 +180,7 @@ export class BlockFactory {
|
|
|
162
180
|
{
|
|
163
181
|
text_run: {
|
|
164
182
|
content: text,
|
|
165
|
-
text_element_style:
|
|
183
|
+
text_element_style: BlockFactory.getDefaultTextElementStyle()
|
|
166
184
|
}
|
|
167
185
|
}
|
|
168
186
|
],
|
package/dist/services/feishu.js
CHANGED
|
@@ -70,7 +70,6 @@ export class FeishuService {
|
|
|
70
70
|
throw {
|
|
71
71
|
status: response.status,
|
|
72
72
|
err: response.data.msg || "Unknown error",
|
|
73
|
-
apiError: response.data
|
|
74
73
|
};
|
|
75
74
|
}
|
|
76
75
|
this.accessToken = response.data.tenant_access_token;
|
|
@@ -87,7 +86,6 @@ export class FeishuService {
|
|
|
87
86
|
throw {
|
|
88
87
|
status: error.response.status,
|
|
89
88
|
err: error.response.data?.msg || "Unknown error",
|
|
90
|
-
apiError: error.response.data
|
|
91
89
|
};
|
|
92
90
|
}
|
|
93
91
|
Logger.error('获取访问令牌时发生未知错误:', error);
|
|
@@ -130,7 +128,6 @@ export class FeishuService {
|
|
|
130
128
|
throw {
|
|
131
129
|
status: error.response.status,
|
|
132
130
|
err: error.response.data?.msg || "Unknown error",
|
|
133
|
-
apiError: error.response.data
|
|
134
131
|
};
|
|
135
132
|
}
|
|
136
133
|
Logger.error('发送请求时发生未知错误:', error);
|
|
@@ -229,7 +229,7 @@ export class FeishuApiService extends BaseApiService {
|
|
|
229
229
|
const elements = textElements.map(item => ({
|
|
230
230
|
text_run: {
|
|
231
231
|
content: item.text,
|
|
232
|
-
text_element_style: item.style
|
|
232
|
+
text_element_style: BlockFactory.applyDefaultTextStyle(item.style)
|
|
233
233
|
}
|
|
234
234
|
}));
|
|
235
235
|
const data = {
|
|
@@ -308,8 +308,13 @@ export class FeishuApiService extends BaseApiService {
|
|
|
308
308
|
* @returns 创建结果
|
|
309
309
|
*/
|
|
310
310
|
async createTextBlock(documentId, parentBlockId, textContents, align = 1, index = 0) {
|
|
311
|
+
// 处理文本内容样式
|
|
312
|
+
const processedTextContents = textContents.map(item => ({
|
|
313
|
+
text: item.text,
|
|
314
|
+
style: BlockFactory.applyDefaultTextStyle(item.style)
|
|
315
|
+
}));
|
|
311
316
|
const blockContent = this.blockFactory.createTextBlock({
|
|
312
|
-
textContents,
|
|
317
|
+
textContents: processedTextContents,
|
|
313
318
|
align
|
|
314
319
|
});
|
|
315
320
|
return this.createDocumentBlock(documentId, parentBlockId, blockContent, index);
|
|
@@ -466,6 +471,33 @@ export class FeishuApiService extends BaseApiService {
|
|
|
466
471
|
*/
|
|
467
472
|
createBlockContent(blockType, options) {
|
|
468
473
|
try {
|
|
474
|
+
// 处理特殊的heading标题格式,如heading1, heading2等
|
|
475
|
+
if (typeof blockType === 'string' && blockType.startsWith('heading')) {
|
|
476
|
+
// 使用正则表达式匹配"heading"后跟1-9的数字格式
|
|
477
|
+
const headingMatch = blockType.match(/^heading([1-9])$/);
|
|
478
|
+
if (headingMatch) {
|
|
479
|
+
// 提取数字部分,例如从"heading1"中提取"1"
|
|
480
|
+
const level = parseInt(headingMatch[1], 10);
|
|
481
|
+
// 额外的安全检查,确保level在1-9范围内
|
|
482
|
+
if (level >= 1 && level <= 9) {
|
|
483
|
+
// 使用level参数创建标题块
|
|
484
|
+
if (!options || Object.keys(options).length === 0) {
|
|
485
|
+
// 没有提供选项时创建默认选项
|
|
486
|
+
options = { heading: { level, content: '', align: 1 } };
|
|
487
|
+
}
|
|
488
|
+
else if (!('heading' in options)) {
|
|
489
|
+
// 提供了选项但没有heading字段
|
|
490
|
+
options = { heading: { level, content: '', align: 1 } };
|
|
491
|
+
}
|
|
492
|
+
else if (options.heading && !('level' in options.heading)) {
|
|
493
|
+
// 提供了heading但没有level字段
|
|
494
|
+
options.heading.level = level;
|
|
495
|
+
}
|
|
496
|
+
blockType = BlockType.HEADING; // 将blockType转为标准的heading类型
|
|
497
|
+
Logger.info(`转换特殊标题格式: ${blockType}${level} -> standard heading with level=${level}`);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
469
501
|
// 使用枚举类型来避免字符串错误
|
|
470
502
|
const blockTypeEnum = blockType;
|
|
471
503
|
// 构建块配置
|
|
@@ -473,12 +505,19 @@ export class FeishuApiService extends BaseApiService {
|
|
|
473
505
|
type: blockTypeEnum,
|
|
474
506
|
options: {}
|
|
475
507
|
};
|
|
508
|
+
// 根据块类型处理不同的选项
|
|
476
509
|
switch (blockTypeEnum) {
|
|
477
510
|
case BlockType.TEXT:
|
|
478
511
|
if ('text' in options && options.text) {
|
|
479
512
|
const textOptions = options.text;
|
|
513
|
+
// 处理文本样式,应用默认样式
|
|
514
|
+
const textStyles = textOptions.textStyles || [];
|
|
515
|
+
const processedTextStyles = textStyles.map((item) => ({
|
|
516
|
+
text: item.text,
|
|
517
|
+
style: BlockFactory.applyDefaultTextStyle(item.style)
|
|
518
|
+
}));
|
|
480
519
|
blockConfig.options = {
|
|
481
|
-
textContents:
|
|
520
|
+
textContents: processedTextStyles,
|
|
482
521
|
align: textOptions.align || 1
|
|
483
522
|
};
|
|
484
523
|
}
|
|
@@ -515,7 +554,55 @@ export class FeishuApiService extends BaseApiService {
|
|
|
515
554
|
};
|
|
516
555
|
}
|
|
517
556
|
break;
|
|
557
|
+
default:
|
|
558
|
+
Logger.warn(`未知的块类型: ${blockType},尝试作为标准类型处理`);
|
|
559
|
+
if ('text' in options) {
|
|
560
|
+
blockConfig.type = BlockType.TEXT;
|
|
561
|
+
const textOptions = options.text;
|
|
562
|
+
// 处理文本样式,应用默认样式
|
|
563
|
+
const textStyles = textOptions.textStyles || [];
|
|
564
|
+
const processedTextStyles = textStyles.map((item) => ({
|
|
565
|
+
text: item.text,
|
|
566
|
+
style: BlockFactory.applyDefaultTextStyle(item.style)
|
|
567
|
+
}));
|
|
568
|
+
blockConfig.options = {
|
|
569
|
+
textContents: processedTextStyles,
|
|
570
|
+
align: textOptions.align || 1
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
else if ('code' in options) {
|
|
574
|
+
blockConfig.type = BlockType.CODE;
|
|
575
|
+
const codeOptions = options.code;
|
|
576
|
+
blockConfig.options = {
|
|
577
|
+
code: codeOptions.code || '',
|
|
578
|
+
language: codeOptions.language === 0 ? 0 : (codeOptions.language || 0),
|
|
579
|
+
wrap: codeOptions.wrap || false
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
else if ('heading' in options) {
|
|
583
|
+
blockConfig.type = BlockType.HEADING;
|
|
584
|
+
const headingOptions = options.heading;
|
|
585
|
+
blockConfig.options = {
|
|
586
|
+
text: headingOptions.content || '',
|
|
587
|
+
level: headingOptions.level || 1,
|
|
588
|
+
align: (headingOptions.align === 1 || headingOptions.align === 2 || headingOptions.align === 3)
|
|
589
|
+
? headingOptions.align : 1
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
else if ('list' in options) {
|
|
593
|
+
blockConfig.type = BlockType.LIST;
|
|
594
|
+
const listOptions = options.list;
|
|
595
|
+
blockConfig.options = {
|
|
596
|
+
text: listOptions.content || '',
|
|
597
|
+
isOrdered: listOptions.isOrdered || false,
|
|
598
|
+
align: (listOptions.align === 1 || listOptions.align === 2 || listOptions.align === 3)
|
|
599
|
+
? listOptions.align : 1
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
break;
|
|
518
603
|
}
|
|
604
|
+
// 记录调试信息
|
|
605
|
+
Logger.debug(`创建块内容: 类型=${blockConfig.type}, 选项=${JSON.stringify(blockConfig.options)}`);
|
|
519
606
|
// 使用BlockFactory创建块
|
|
520
607
|
return this.blockFactory.createBlock(blockConfig.type, blockConfig.options);
|
|
521
608
|
}
|
|
@@ -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
|
+
}
|