feishu-mcp 0.0.7 → 0.0.8
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/dist/server.js +152 -3
- package/dist/services/feishuApiService.js +143 -1
- package/dist/types/feishuSchema.js +22 -0
- package/dist/utils/document.js +38 -0
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -5,7 +5,8 @@ import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
|
5
5
|
import { formatErrorMessage } from './utils/error.js';
|
|
6
6
|
import { FeishuApiService } from './services/feishuApiService.js';
|
|
7
7
|
import { Logger } from './utils/logger.js';
|
|
8
|
-
import {
|
|
8
|
+
import { detectMimeType } from './utils/document.js';
|
|
9
|
+
import { DocumentIdSchema, ParentBlockIdSchema, BlockIdSchema, IndexSchema, StartIndexSchema, EndIndexSchema, AlignSchema, AlignSchemaWithValidation, TextElementsArraySchema, CodeLanguageSchema, CodeWrapSchema, BlockConfigSchema, MediaIdSchema, MediaExtraSchema, FolderTokenSchema, FolderNameSchema, OrderBySchema, DirectionSchema } from './types/feishuSchema.js';
|
|
9
10
|
export class FeishuMcpServer {
|
|
10
11
|
constructor() {
|
|
11
12
|
Object.defineProperty(this, "server", {
|
|
@@ -202,11 +203,11 @@ export class FeishuMcpServer {
|
|
|
202
203
|
}
|
|
203
204
|
});
|
|
204
205
|
// 添加通用飞书块创建工具(支持文本、代码、标题)
|
|
205
|
-
this.server.tool('batch_create_feishu_blocks', '
|
|
206
|
+
this.server.tool('batch_create_feishu_blocks', 'PREFERRED: Efficiently creates multiple blocks (text, code, heading, list) in a single API call. USE THIS TOOL when creating multiple consecutive blocks at the same position - reduces API calls by up to 90%. KEY FEATURES: (1) Handles any number of blocks by auto-batching large requests (>50 blocks), (2) Creates blocks at consecutive positions in a document. CORRECT FORMAT: mcp_feishu_batch_create_feishu_blocks({documentId:"doc123",parentBlockId:"para123",startIndex:0,blocks:[{blockType:"text",options:{...}},{blockType:"heading",options:{...}}]}). For separate positions, use individual block creation tools instead. For wiki links (https://xxx.feishu.cn/wiki/xxx), first convert with convert_feishu_wiki_to_document_id tool.', {
|
|
206
207
|
documentId: DocumentIdSchema,
|
|
207
208
|
parentBlockId: ParentBlockIdSchema,
|
|
208
209
|
startIndex: StartIndexSchema,
|
|
209
|
-
blocks: z.array(BlockConfigSchema).describe('Array of block configurations
|
|
210
|
+
blocks: z.array(BlockConfigSchema).describe('Array of block configurations. CRITICAL: Must be a JSON array object, NOT a string. CORRECT: blocks:[{...}] - WITHOUT quotes around array. INCORRECT: blocks:"[{...}]". Example: [{blockType:"text",options:{text:{textStyles:[{text:"Hello",style:{bold:true}}]}}},{blockType:"code",options:{code:{code:"console.log()",language:30}}}]. Auto-batches requests when exceeding 50 blocks.'),
|
|
210
211
|
}, async ({ documentId, parentBlockId, startIndex = 0, blocks }) => {
|
|
211
212
|
try {
|
|
212
213
|
if (!this.feishuService) {
|
|
@@ -219,6 +220,17 @@ export class FeishuMcpServer {
|
|
|
219
220
|
],
|
|
220
221
|
};
|
|
221
222
|
}
|
|
223
|
+
// 类型检查:确保blocks是数组而不是字符串
|
|
224
|
+
if (typeof blocks === 'string') {
|
|
225
|
+
return {
|
|
226
|
+
content: [
|
|
227
|
+
{
|
|
228
|
+
type: 'text',
|
|
229
|
+
text: 'ERROR: The "blocks" parameter was passed as a string instead of an array. Please provide a proper JSON array without quotes. Example: {blocks:[{blockType:"text",options:{...}}]} instead of {blocks:"[{...}]"}',
|
|
230
|
+
},
|
|
231
|
+
],
|
|
232
|
+
};
|
|
233
|
+
}
|
|
222
234
|
// 如果块数量不超过50,直接调用一次API
|
|
223
235
|
if (blocks.length <= 50) {
|
|
224
236
|
Logger.info(`开始批量创建飞书块,文档ID: ${documentId},父块ID: ${parentBlockId},块数量: ${blocks.length},起始插入位置: ${startIndex}`);
|
|
@@ -484,6 +496,143 @@ export class FeishuMcpServer {
|
|
|
484
496
|
};
|
|
485
497
|
}
|
|
486
498
|
});
|
|
499
|
+
// 添加删除文档块工具
|
|
500
|
+
this.server.tool('delete_feishu_document_blocks', 'Deletes one or more consecutive blocks from a Feishu document. Use this tool to remove unwanted content, clean up document structure, or clear space before inserting new content. Supports batch deletion for efficiency. Note: For Feishu wiki links (https://xxx.feishu.cn/wiki/xxx) you must first use convert_feishu_wiki_to_document_id tool to obtain a compatible document ID.', {
|
|
501
|
+
documentId: DocumentIdSchema,
|
|
502
|
+
parentBlockId: ParentBlockIdSchema,
|
|
503
|
+
startIndex: StartIndexSchema,
|
|
504
|
+
endIndex: EndIndexSchema,
|
|
505
|
+
}, async ({ documentId, parentBlockId, startIndex, endIndex }) => {
|
|
506
|
+
try {
|
|
507
|
+
if (!this.feishuService) {
|
|
508
|
+
return {
|
|
509
|
+
content: [{ type: 'text', text: 'Feishu service is not initialized. Please check the configuration' }],
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
Logger.info(`开始删除飞书文档块,文档ID: ${documentId},父块ID: ${parentBlockId},索引范围: ${startIndex}-${endIndex}`);
|
|
513
|
+
const result = await this.feishuService.deleteDocumentBlocks(documentId, parentBlockId, startIndex, endIndex);
|
|
514
|
+
Logger.info(`飞书文档块删除成功,文档修订ID: ${result.document_revision_id}`);
|
|
515
|
+
return {
|
|
516
|
+
content: [{ type: 'text', text: `Successfully deleted blocks from index ${startIndex} to ${endIndex - 1}` }],
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
catch (error) {
|
|
520
|
+
Logger.error(`删除飞书文档块失败:`, error);
|
|
521
|
+
const errorMessage = formatErrorMessage(error);
|
|
522
|
+
return {
|
|
523
|
+
content: [{ type: 'text', text: `Failed to delete document blocks: ${errorMessage}` }],
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
// 添加获取图片资源工具
|
|
528
|
+
this.server.tool('get_feishu_image_resource', 'Downloads an image resource from Feishu by its media ID. Use this to retrieve images referenced in document blocks or other Feishu resources. Returns the binary image data that can be saved or processed further. For example, extract the media_id from an image block in a document, then use this tool to download the actual image.', {
|
|
529
|
+
mediaId: MediaIdSchema,
|
|
530
|
+
extra: MediaExtraSchema,
|
|
531
|
+
}, async ({ mediaId, extra = '' }) => {
|
|
532
|
+
try {
|
|
533
|
+
if (!this.feishuService) {
|
|
534
|
+
return {
|
|
535
|
+
content: [{ type: 'text', text: 'Feishu service is not initialized. Please check the configuration' }],
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
Logger.info(`开始获取飞书图片资源,媒体ID: ${mediaId}`);
|
|
539
|
+
const imageBuffer = await this.feishuService.getImageResource(mediaId, extra);
|
|
540
|
+
Logger.info(`飞书图片资源获取成功,大小: ${imageBuffer.length} 字节`);
|
|
541
|
+
// 将图片数据转为Base64编码,以便在MCP协议中传输
|
|
542
|
+
const base64Image = imageBuffer.toString('base64');
|
|
543
|
+
const mimeType = detectMimeType(imageBuffer);
|
|
544
|
+
return {
|
|
545
|
+
content: [{
|
|
546
|
+
type: 'image',
|
|
547
|
+
mimeType: mimeType,
|
|
548
|
+
data: base64Image
|
|
549
|
+
}],
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
catch (error) {
|
|
553
|
+
Logger.error(`获取飞书图片资源失败:`, error);
|
|
554
|
+
const errorMessage = formatErrorMessage(error);
|
|
555
|
+
return {
|
|
556
|
+
content: [{ type: 'text', text: `Failed to get image resource: ${errorMessage}` }],
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
// 添加获取根文件夹信息工具
|
|
561
|
+
this.server.tool('get_feishu_root_folder_info', 'Retrieves basic information about the root folder in Feishu Drive. Returns the token, ID and user ID of the root folder, which can be used for subsequent folder operations.', {}, async () => {
|
|
562
|
+
try {
|
|
563
|
+
if (!this.feishuService) {
|
|
564
|
+
return {
|
|
565
|
+
content: [{ type: 'text', text: '飞书服务未初始化,请检查配置' }],
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
Logger.info(`开始获取飞书根文件夹信息`);
|
|
569
|
+
const folderInfo = await this.feishuService.getRootFolderInfo();
|
|
570
|
+
Logger.info(`飞书根文件夹信息获取成功,token: ${folderInfo.token}`);
|
|
571
|
+
return {
|
|
572
|
+
content: [{ type: 'text', text: JSON.stringify(folderInfo, null, 2) }],
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
catch (error) {
|
|
576
|
+
Logger.error(`获取飞书根文件夹信息失败:`, error);
|
|
577
|
+
const errorMessage = formatErrorMessage(error, '获取飞书根文件夹信息失败');
|
|
578
|
+
return {
|
|
579
|
+
content: [{ type: 'text', text: errorMessage }],
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
// 添加获取文件夹中的文件清单工具
|
|
584
|
+
this.server.tool('get_feishu_folder_files', 'Retrieves a list of files and subfolders in a specified folder. Use this to explore folder contents, view file metadata, and get URLs and tokens for further operations.', {
|
|
585
|
+
folderToken: FolderTokenSchema,
|
|
586
|
+
orderBy: OrderBySchema,
|
|
587
|
+
direction: DirectionSchema
|
|
588
|
+
}, async ({ folderToken, orderBy = 'EditedTime', direction = 'DESC' }) => {
|
|
589
|
+
try {
|
|
590
|
+
if (!this.feishuService) {
|
|
591
|
+
return {
|
|
592
|
+
content: [{ type: 'text', text: '飞书服务未初始化,请检查配置' }],
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
Logger.info(`开始获取飞书文件夹中的文件清单,文件夹Token: ${folderToken},排序方式: ${orderBy},排序方向: ${direction}`);
|
|
596
|
+
const fileList = await this.feishuService.getFolderFileList(folderToken, orderBy, direction);
|
|
597
|
+
Logger.info(`飞书文件夹中的文件清单获取成功,共 ${fileList.files?.length || 0} 个文件`);
|
|
598
|
+
return {
|
|
599
|
+
content: [{ type: 'text', text: JSON.stringify(fileList, null, 2) }],
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
catch (error) {
|
|
603
|
+
Logger.error(`获取飞书文件夹中的文件清单失败:`, error);
|
|
604
|
+
const errorMessage = formatErrorMessage(error);
|
|
605
|
+
return {
|
|
606
|
+
content: [{ type: 'text', text: `获取飞书文件夹中的文件清单失败: ${errorMessage}` }],
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
});
|
|
610
|
+
// 添加创建文件夹工具
|
|
611
|
+
this.server.tool('create_feishu_folder', 'Creates a new folder in a specified parent folder. Use this to organize documents and files within your Feishu Drive structure. Returns the token and URL of the newly created folder.', {
|
|
612
|
+
folderToken: FolderTokenSchema,
|
|
613
|
+
folderName: FolderNameSchema,
|
|
614
|
+
}, async ({ folderToken, folderName }) => {
|
|
615
|
+
try {
|
|
616
|
+
if (!this.feishuService) {
|
|
617
|
+
return {
|
|
618
|
+
content: [{ type: 'text', text: '飞书服务未初始化,请检查配置' }],
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
Logger.info(`开始创建飞书文件夹,父文件夹Token: ${folderToken},文件夹名称: ${folderName}`);
|
|
622
|
+
const result = await this.feishuService.createFolder(folderToken, folderName);
|
|
623
|
+
Logger.info(`飞书文件夹创建成功,token: ${result.token},URL: ${result.url}`);
|
|
624
|
+
return {
|
|
625
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
catch (error) {
|
|
629
|
+
Logger.error(`创建飞书文件夹失败:`, error);
|
|
630
|
+
const errorMessage = formatErrorMessage(error);
|
|
631
|
+
return {
|
|
632
|
+
content: [{ type: 'text', text: `创建飞书文件夹失败: ${errorMessage}` }],
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
});
|
|
487
636
|
}
|
|
488
637
|
async connect(transport) {
|
|
489
638
|
await this.server.connect(transport);
|
|
@@ -377,9 +377,48 @@ export class FeishuApiService extends BaseApiService {
|
|
|
377
377
|
* @returns 创建结果
|
|
378
378
|
*/
|
|
379
379
|
async createMixedBlocks(documentId, parentBlockId, blocks, index = 0) {
|
|
380
|
-
const blockContents = this.blockFactory.
|
|
380
|
+
const blockContents = blocks.map(block => this.blockFactory.createBlock(block.type, block.options));
|
|
381
381
|
return this.createDocumentBlocks(documentId, parentBlockId, blockContents, index);
|
|
382
382
|
}
|
|
383
|
+
/**
|
|
384
|
+
* 删除文档中的块,支持批量删除
|
|
385
|
+
* @param documentId 文档ID或URL
|
|
386
|
+
* @param parentBlockId 父块ID(通常是文档ID)
|
|
387
|
+
* @param startIndex 起始索引
|
|
388
|
+
* @param endIndex 结束索引
|
|
389
|
+
* @returns 操作结果
|
|
390
|
+
*/
|
|
391
|
+
async deleteDocumentBlocks(documentId, parentBlockId, startIndex, endIndex) {
|
|
392
|
+
try {
|
|
393
|
+
const normalizedDocId = ParamUtils.processDocumentId(documentId);
|
|
394
|
+
const endpoint = `/docx/v1/documents/${normalizedDocId}/blocks/${parentBlockId}/children/batch_delete`;
|
|
395
|
+
// 确保索引有效
|
|
396
|
+
if (startIndex < 0 || endIndex < startIndex) {
|
|
397
|
+
throw new Error('无效的索引范围:起始索引必须大于等于0,结束索引必须大于等于起始索引');
|
|
398
|
+
}
|
|
399
|
+
const payload = {
|
|
400
|
+
start_index: startIndex,
|
|
401
|
+
end_index: endIndex
|
|
402
|
+
};
|
|
403
|
+
Logger.info(`开始删除文档块,文档ID: ${normalizedDocId},父块ID: ${parentBlockId},索引范围: ${startIndex}-${endIndex}`);
|
|
404
|
+
const response = await this.delete(endpoint, payload);
|
|
405
|
+
Logger.info('文档块删除成功');
|
|
406
|
+
return response;
|
|
407
|
+
}
|
|
408
|
+
catch (error) {
|
|
409
|
+
this.handleApiError(error, '删除文档块失败');
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* 删除单个文档块(通过创建起始和结束索引相同的批量删除请求)
|
|
414
|
+
* @param documentId 文档ID或URL
|
|
415
|
+
* @param parentBlockId 父块ID
|
|
416
|
+
* @param blockIndex 块索引
|
|
417
|
+
* @returns 操作结果
|
|
418
|
+
*/
|
|
419
|
+
async deleteDocumentBlock(documentId, parentBlockId, blockIndex) {
|
|
420
|
+
return this.deleteDocumentBlocks(documentId, parentBlockId, blockIndex, blockIndex + 1);
|
|
421
|
+
}
|
|
383
422
|
/**
|
|
384
423
|
* 将飞书Wiki链接转换为文档ID
|
|
385
424
|
* @param wikiUrl Wiki链接或Token
|
|
@@ -485,4 +524,107 @@ export class FeishuApiService extends BaseApiService {
|
|
|
485
524
|
return null;
|
|
486
525
|
}
|
|
487
526
|
}
|
|
527
|
+
/**
|
|
528
|
+
* 获取飞书图片资源
|
|
529
|
+
* @param mediaId 图片媒体ID
|
|
530
|
+
* @param extra 额外参数,可选
|
|
531
|
+
* @returns 图片二进制数据
|
|
532
|
+
*/
|
|
533
|
+
async getImageResource(mediaId, extra = '') {
|
|
534
|
+
try {
|
|
535
|
+
Logger.info(`开始获取图片资源,媒体ID: ${mediaId}`);
|
|
536
|
+
if (!mediaId) {
|
|
537
|
+
throw new Error('媒体ID不能为空');
|
|
538
|
+
}
|
|
539
|
+
const endpoint = `/drive/v1/medias/${mediaId}/download`;
|
|
540
|
+
const params = {};
|
|
541
|
+
if (extra) {
|
|
542
|
+
params.extra = extra;
|
|
543
|
+
}
|
|
544
|
+
// 这里需要特殊处理,因为返回的是二进制数据,不是JSON
|
|
545
|
+
const token = await this.getAccessToken();
|
|
546
|
+
const url = `${this.getBaseUrl()}${endpoint}`;
|
|
547
|
+
const headers = {
|
|
548
|
+
'Authorization': `Bearer ${token}`
|
|
549
|
+
};
|
|
550
|
+
Logger.debug(`请求图片资源URL: ${url}`);
|
|
551
|
+
// 使用axios直接获取二进制响应
|
|
552
|
+
const response = await axios.get(url, {
|
|
553
|
+
params,
|
|
554
|
+
headers,
|
|
555
|
+
responseType: 'arraybuffer'
|
|
556
|
+
});
|
|
557
|
+
// 检查响应状态
|
|
558
|
+
if (response.status !== 200) {
|
|
559
|
+
throw new Error(`获取图片资源失败,状态码: ${response.status}`);
|
|
560
|
+
}
|
|
561
|
+
const imageBuffer = Buffer.from(response.data);
|
|
562
|
+
Logger.info(`图片资源获取成功,大小: ${imageBuffer.length} 字节`);
|
|
563
|
+
return imageBuffer;
|
|
564
|
+
}
|
|
565
|
+
catch (error) {
|
|
566
|
+
this.handleApiError(error, '获取图片资源失败');
|
|
567
|
+
return Buffer.from([]); // 永远不会执行到这里
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* 获取飞书根文件夹信息
|
|
572
|
+
* 获取用户的根文件夹的元数据信息,包括token、id和用户id
|
|
573
|
+
* @returns 根文件夹信息
|
|
574
|
+
*/
|
|
575
|
+
async getRootFolderInfo() {
|
|
576
|
+
try {
|
|
577
|
+
const endpoint = '/drive/explorer/v2/root_folder/meta';
|
|
578
|
+
const response = await this.get(endpoint);
|
|
579
|
+
Logger.debug('获取根文件夹信息成功:', response);
|
|
580
|
+
return response;
|
|
581
|
+
}
|
|
582
|
+
catch (error) {
|
|
583
|
+
this.handleApiError(error, '获取飞书根文件夹信息失败');
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* 获取文件夹中的文件清单
|
|
588
|
+
* @param folderToken 文件夹Token
|
|
589
|
+
* @param orderBy 排序方式,默认按修改时间排序
|
|
590
|
+
* @param direction 排序方向,默认降序
|
|
591
|
+
* @returns 文件清单信息
|
|
592
|
+
*/
|
|
593
|
+
async getFolderFileList(folderToken, orderBy = 'EditedTime', direction = 'DESC') {
|
|
594
|
+
try {
|
|
595
|
+
const endpoint = '/drive/v1/files';
|
|
596
|
+
const params = {
|
|
597
|
+
folder_token: folderToken,
|
|
598
|
+
order_by: orderBy,
|
|
599
|
+
direction: direction
|
|
600
|
+
};
|
|
601
|
+
const response = await this.get(endpoint, params);
|
|
602
|
+
Logger.debug(`获取文件夹(${folderToken})中的文件清单成功,文件数量: ${response.files?.length || 0}`);
|
|
603
|
+
return response;
|
|
604
|
+
}
|
|
605
|
+
catch (error) {
|
|
606
|
+
this.handleApiError(error, '获取文件夹中的文件清单失败');
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* 创建文件夹
|
|
611
|
+
* @param folderToken 父文件夹Token
|
|
612
|
+
* @param name 文件夹名称
|
|
613
|
+
* @returns 创建的文件夹信息
|
|
614
|
+
*/
|
|
615
|
+
async createFolder(folderToken, name) {
|
|
616
|
+
try {
|
|
617
|
+
const endpoint = '/drive/v1/files/create_folder';
|
|
618
|
+
const payload = {
|
|
619
|
+
folder_token: folderToken,
|
|
620
|
+
name: name
|
|
621
|
+
};
|
|
622
|
+
const response = await this.post(endpoint, payload);
|
|
623
|
+
Logger.debug(`文件夹创建成功, token: ${response.token}, url: ${response.url}`);
|
|
624
|
+
return response;
|
|
625
|
+
}
|
|
626
|
+
catch (error) {
|
|
627
|
+
this.handleApiError(error, '创建文件夹失败');
|
|
628
|
+
}
|
|
629
|
+
}
|
|
488
630
|
}
|
|
@@ -17,6 +17,10 @@ export const IndexSchema = z.number().describe('Insertion position index (requir
|
|
|
17
17
|
// 起始插入位置索引参数定义
|
|
18
18
|
export const StartIndexSchema = z.number().describe('Starting insertion position index (required). Specifies where the first block should be inserted. Use 0 to insert at the beginning. ' +
|
|
19
19
|
'Use get_feishu_document_blocks tool to understand document structure if unsure.');
|
|
20
|
+
// 结束位置索引参数定义
|
|
21
|
+
export const EndIndexSchema = z.number().describe('Ending position index (required). Specifies the end of the range for deletion (exclusive). ' +
|
|
22
|
+
'For example, to delete blocks 2, 3, and 4, use startIndex=2, endIndex=5. ' +
|
|
23
|
+
'To delete a single block at position 2, use startIndex=2, endIndex=3.');
|
|
20
24
|
// 文本对齐方式参数定义
|
|
21
25
|
export const AlignSchema = z.number().optional().default(1).describe('Text alignment: 1 for left (default), 2 for center, 3 for right.');
|
|
22
26
|
// 文本对齐方式参数定义(带验证)
|
|
@@ -95,3 +99,21 @@ export const BlockConfigSchema = z.object({
|
|
|
95
99
|
z.object({ list: ListBlockSchema }).describe("List block options. Only used when blockType is 'list'."),
|
|
96
100
|
]).describe('Options for the specific block type. Must provide the corresponding options object based on blockType.'),
|
|
97
101
|
});
|
|
102
|
+
// 媒体ID参数定义
|
|
103
|
+
export const MediaIdSchema = z.string().describe('Media ID (required). The unique identifier for a media resource (image, file, etc.) in Feishu. ' +
|
|
104
|
+
'Usually obtained from image blocks or file references in documents. ' +
|
|
105
|
+
'Format is typically like "boxcnrHpsg1QDqXAAAyachabcef".');
|
|
106
|
+
// 额外参数定义 - 用于媒体资源下载
|
|
107
|
+
export const MediaExtraSchema = z.string().optional().describe('Extra parameters for media download (optional). ' +
|
|
108
|
+
'These parameters are passed directly to the Feishu API and can modify how the media is returned.');
|
|
109
|
+
// 文件夹Token参数定义
|
|
110
|
+
export const FolderTokenSchema = z.string().describe('Folder token (required). The unique identifier for a folder in Feishu. ' +
|
|
111
|
+
'Format is an alphanumeric string like "FWK2fMleClICfodlHHWc4Mygnhb".');
|
|
112
|
+
// 文件夹名称参数定义
|
|
113
|
+
export const FolderNameSchema = z.string().describe('Folder name (required). The name for the new folder to be created.');
|
|
114
|
+
// 排序方式参数定义
|
|
115
|
+
export const OrderBySchema = z.string().optional().default('EditedTime').describe('Order by field (optional). Specifies how to sort the file list. Available values: ' +
|
|
116
|
+
'"EditedTime" (default), "CreatedTime", "Name". For user-friendly display, case insensitive.');
|
|
117
|
+
// 排序方向参数定义
|
|
118
|
+
export const DirectionSchema = z.string().optional().default('DESC').describe('Sort direction (optional). Specifies the sort order. Available values: ' +
|
|
119
|
+
'"DESC" (default) for descending order, "ASC" for ascending order. Case sensitive.');
|
package/dist/utils/document.js
CHANGED
|
@@ -72,3 +72,41 @@ export function normalizeWikiToken(input) {
|
|
|
72
72
|
}
|
|
73
73
|
return token;
|
|
74
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* 根据图片二进制数据检测MIME类型
|
|
77
|
+
* @param buffer 图片二进制数据
|
|
78
|
+
* @returns MIME类型字符串
|
|
79
|
+
*/
|
|
80
|
+
export function detectMimeType(buffer) {
|
|
81
|
+
// 简单的图片格式检测,根据文件头进行判断
|
|
82
|
+
if (buffer.length < 4) {
|
|
83
|
+
return 'application/octet-stream';
|
|
84
|
+
}
|
|
85
|
+
// JPEG格式
|
|
86
|
+
if (buffer[0] === 0xFF && buffer[1] === 0xD8 && buffer[2] === 0xFF) {
|
|
87
|
+
return 'image/jpeg';
|
|
88
|
+
}
|
|
89
|
+
// PNG格式
|
|
90
|
+
else if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4E && buffer[3] === 0x47) {
|
|
91
|
+
return 'image/png';
|
|
92
|
+
}
|
|
93
|
+
// GIF格式
|
|
94
|
+
else if (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46) {
|
|
95
|
+
return 'image/gif';
|
|
96
|
+
}
|
|
97
|
+
// SVG格式 - 检查字符串前缀
|
|
98
|
+
else if (buffer.length > 5 && buffer.toString('ascii', 0, 5).toLowerCase() === '<?xml' ||
|
|
99
|
+
buffer.toString('ascii', 0, 4).toLowerCase() === '<svg') {
|
|
100
|
+
return 'image/svg+xml';
|
|
101
|
+
}
|
|
102
|
+
// WebP格式
|
|
103
|
+
else if (buffer.length > 12 &&
|
|
104
|
+
buffer[0] === 0x52 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x46 &&
|
|
105
|
+
buffer[8] === 0x57 && buffer[9] === 0x45 && buffer[10] === 0x42 && buffer[11] === 0x50) {
|
|
106
|
+
return 'image/webp';
|
|
107
|
+
}
|
|
108
|
+
// 默认二进制流
|
|
109
|
+
else {
|
|
110
|
+
return 'application/octet-stream';
|
|
111
|
+
}
|
|
112
|
+
}
|