Skip to main content
Glama

Feishu MCP Server

blockFactory.ts12.4 kB
import { Logger } from '../utils/logger.js'; /** * 块类型接口 */ export interface FeishuBlock { block_type: number; [key: string]: any; } /** * 文本样式接口 */ export interface TextElementStyle { bold?: boolean; // 是否加粗 italic?: boolean; // 是否斜体 underline?: boolean; // 是否下划线 strikethrough?: boolean; // 是否删除线 inline_code?: boolean; // 是否行内代码 text_color?: number; // 文本颜色 } /** * 文本内容接口 */ export interface TextContent { text: string; // 文本内容 style?: TextElementStyle; // 文本样式 } /** * 公式内容接口 */ export interface EquationContent { equation: string; // 公式内容 style?: TextElementStyle; // 文本样式 } /** * 文本元素类型 - 可以是普通文本或公式 */ export type TextElement = TextContent | EquationContent; /** * 文本块接口 */ export interface TextBlock extends FeishuBlock { block_type: 2; // 文本块类型固定为2 text: { elements: Array<{ text_run: { content: string; text_element_style: TextElementStyle; } }>; style: { align: number; // 对齐方式:1左对齐,2居中,3右对齐 } }; } /** * 代码块接口 */ export interface CodeBlock extends FeishuBlock { block_type: 14; // 代码块类型固定为14 code: { elements: Array<{ text_run: { content: string; text_element_style: TextElementStyle; } }>; style: { language: number; // 语言类型代码 wrap: boolean; // 是否自动换行 } }; } /** * 标题块接口 */ export interface HeadingBlock extends FeishuBlock { block_type: number; // 标题块类型:3-11(对应标题级别1-9) [headingKey: string]: any; // 动态属性名,如heading1, heading2等 } /** * 块类型枚举 */ export enum BlockType { TEXT = 'text', CODE = 'code', HEADING = 'heading', LIST = 'list', IMAGE = 'image', MERMAID = 'mermaid' } /** * 对齐方式枚举 */ export enum AlignType { LEFT = 1, CENTER = 2, RIGHT = 3 } /** * 块工厂类 * 提供统一接口创建不同类型的块内容 */ export class BlockFactory { private static instance: BlockFactory; private constructor() {} /** * 获取块工厂实例 * @returns 块工厂实例 */ public static getInstance(): BlockFactory { if (!BlockFactory.instance) { BlockFactory.instance = new BlockFactory(); } return BlockFactory.instance; } /** * 获取默认的文本元素样式 * @returns 默认文本元素样式 */ public static getDefaultTextElementStyle(): TextElementStyle { return { bold: false, inline_code: false, italic: false, strikethrough: false, underline: false }; } /** * 应用默认文本样式 * @param style 已有样式(可选) * @returns 合并后的样式 */ public static applyDefaultTextStyle(style?: TextElementStyle): TextElementStyle { const defaultStyle = BlockFactory.getDefaultTextElementStyle(); return style ? { ...defaultStyle, ...style } : defaultStyle; } /** * 创建块内容 * @param type 块类型 * @param options 块选项 * @returns 块内容对象 */ public createBlock(type: BlockType, options: any): FeishuBlock { switch (type) { case BlockType.TEXT: return this.createTextBlock(options); case BlockType.CODE: return this.createCodeBlock(options); case BlockType.HEADING: return this.createHeadingBlock(options); case BlockType.LIST: return this.createListBlock(options); case BlockType.IMAGE: return this.createImageBlock(options); case BlockType.MERMAID: return this.createMermaidBlock(options); default: Logger.error(`不支持的块类型: ${type}`); throw new Error(`不支持的块类型: ${type}`); } } /** * 创建文本块内容 * @param options 文本块选项 * @returns 文本块内容对象 */ public createTextBlock(options: { textContents: Array<TextElement>, align?: AlignType }): FeishuBlock { const { textContents, align = AlignType.LEFT } = options; return { block_type: 2, // 2表示文本块 text: { elements: textContents.map(content => { // 检查是否是公式元素 if ('equation' in content) { return { equation: { content: content.equation, text_element_style: BlockFactory.applyDefaultTextStyle(content.style) } }; } else { // 普通文本元素 return { text_run: { content: content.text, text_element_style: BlockFactory.applyDefaultTextStyle(content.style) } }; } }), style: { align: align, // 1 居左,2 居中,3 居右 folded: false } } }; } /** * 创建代码块内容 * @param options 代码块选项 * @returns 代码块内容对象 */ public createCodeBlock(options: { code: string, language?: number, wrap?: boolean }): FeishuBlock { const { code, language = 0, wrap = false } = options; // 校验 language 合法性,飞书API只允许1~75 const safeLanguage = language >= 1 && language <= 75 ? language : 1; return { block_type: 14, // 14表示代码块 code: { elements: [ { text_run: { content: code, text_element_style: BlockFactory.getDefaultTextElementStyle() } } ], style: { language: safeLanguage, wrap: wrap } } }; } /** * 创建标题块内容 * @param options 标题块选项 * @returns 标题块内容对象 */ public createHeadingBlock(options: { text: string, level?: number, align?: AlignType }): FeishuBlock { const { text, level = 1, align = AlignType.LEFT } = options; // 确保标题级别在有效范围内(1-9) const safeLevel = Math.max(1, Math.min(9, level)); // 根据标题级别设置block_type和对应的属性名 // 飞书API中,一级标题的block_type为3,二级标题为4,以此类推 const blockType = 2 + safeLevel; // 一级标题为3,二级标题为4,以此类推 const headingKey = `heading${safeLevel}`; // heading1, heading2, ... // 构建块内容 const blockContent: any = { block_type: blockType }; // 设置对应级别的标题属性 blockContent[headingKey] = { elements: [ { text_run: { content: text, text_element_style: BlockFactory.getDefaultTextElementStyle() } } ], style: { align: align, folded: false } }; return blockContent; } /** * 创建列表块内容(有序或无序) * @param options 列表块选项 * @returns 列表块内容对象 */ public createListBlock(options: { text: string, isOrdered?: boolean, align?: AlignType }): FeishuBlock { const { text, isOrdered = false, align = AlignType.LEFT } = options; // 有序列表是 block_type: 13,无序列表是 block_type: 12 const blockType = isOrdered ? 13 : 12; const propertyKey = isOrdered ? "ordered" : "bullet"; // 构建块内容 const blockContent: any = { block_type: blockType }; // 设置列表属性 blockContent[propertyKey] = { elements: [ { text_run: { content: text, text_element_style: BlockFactory.getDefaultTextElementStyle() } } ], style: { align: align, folded: false } }; return blockContent; } /** * 创建图片块内容(空图片块,需要后续设置图片资源) * @param options 图片块选项 * @returns 图片块内容对象 */ public createImageBlock(options: { width?: number, height?: number } = {}): FeishuBlock { const { width = 100, height = 100 } = options; return { block_type: 27, // 27表示图片块 image: { width: width, height: height, token: "" // 空token,需要后续通过API设置 } }; } /** * 创建Mermaid * @param options Mermaid块选项 * @returns Mermaid块内容对象 */ public createMermaidBlock( options: { code?: string; } = {}, ): FeishuBlock { const { code } = options; return { block_type: 40, add_ons: { component_id: '', component_type_id: 'blk_631fefbbae02400430b8f9f4', record: JSON.stringify({ data: code, }), }, }; } /** * 创建表格块 * @param options 表格块选项 * @returns 表格块内容对象 */ public createTableBlock(options: { columnSize: number; rowSize: number; cells?: Array<{ coordinate: { row: number; column: number }; content: any; }>; }): any { const { columnSize, rowSize, cells = [] } = options; // 生成表格ID const tableId = `table_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const imageBlocks= Array<{ coordinate: { row: number; column: number }; localBlockId: string; }>() // 创建表格单元格 const tableCells = []; const descendants = []; for (let row = 0; row < rowSize; row++) { for (let col = 0; col < columnSize; col++) { const cellId = `table_cell${row}_${col}`; // 查找是否有配置的单元格内容 const cellConfigs = cells.filter(cell => cell.coordinate.row === row && cell.coordinate.column === col ); // 创建单元格内容 const cellContentBlocks = []; const cellContentIds = []; if (cellConfigs.length > 0) { // 处理多个内容块 cellConfigs.forEach((cellConfig, index) => { const cellContentId = `${cellId}_child_${index}`; const cellContentBlock = { block_id: cellContentId, ...cellConfig.content, children: [] }; cellContentBlocks.push(cellContentBlock); cellContentIds.push(cellContentId); Logger.info(`处理块:${JSON.stringify(cellConfig)} ${index}`) if (cellConfig.content.block_type === 27) { //把图片块保存起来,用于后续获取该图片块的token imageBlocks.push({ coordinate: cellConfig.coordinate, localBlockId: cellContentId, }); } }); } else { // 创建空的文本块 const cellContentId = `${cellId}_child`; const cellContentBlock = { block_id: cellContentId, ...this.createTextBlock({ textContents: [{ text: "" }] }), children: [] }; cellContentBlocks.push(cellContentBlock); cellContentIds.push(cellContentId); } // 创建表格单元格块 const tableCell = { block_id: cellId, block_type: 32, // 表格单元格类型 table_cell: {}, children: cellContentIds }; tableCells.push(cellId); descendants.push(tableCell); descendants.push(...cellContentBlocks); } } // 创建表格主体 const tableBlock = { block_id: tableId, block_type: 31, // 表格块类型 table: { property: { row_size: rowSize, column_size: columnSize } }, children: tableCells }; descendants.unshift(tableBlock); // 过滤并记录 block_type 为 27 的元素 Logger.info(`发现 ${imageBlocks.length} 个图片块 (block_type: 27): ${JSON.stringify(imageBlocks)}`); return { children_id: [tableId], descendants: descendants, imageBlocks:imageBlocks }; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/cso1z/Feishu-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server