feishu-mcp
Version:
Model Context Protocol server for Feishu integration
188 lines (187 loc) • 9.16 kB
JavaScript
import { Logger } from '../../../utils/logger.js';
import { FeishuBaseApiService } from '../../../services/feishu/FeishuBaseApiService.js';
/**
* 飞书文件夹与知识空间服务
* 整合云盘文件夹(Drive)和知识空间(Wiki)的查询与创建操作
*/
export class FeishuFoldService extends FeishuBaseApiService {
constructor(authService) {
super(authService);
}
// ─── 云盘文件夹(Drive)─────────────────────────────────────────
/**
* 获取当前用户根文件夹的元数据信息
* @returns 根文件夹信息,包含 token(文件夹 Token)、id 和 user_id 字段
*/
async getRootFolderInfo() {
try {
const response = await this.get('/drive/explorer/v2/root_folder/meta');
Logger.debug('获取根文件夹信息成功:', response);
return response;
}
catch (error) {
this.handleApiError(error, '获取飞书根文件夹信息失败');
}
}
/**
* 获取指定文件夹内的文件和子文件夹列表
* @param folderToken 目标文件夹的 Token
* @param orderBy 排序字段,可选 'EditedTime'(编辑时间)、'CreatedTime'(创建时间)等,默认 'EditedTime'
* @param direction 排序方向,'ASC' 升序 / 'DESC' 降序,默认 'DESC'
* @returns 文件夹文件清单,包含 files 数组,每项含文件名、类型、token 等信息
*/
async getFolderFileList(folderToken, orderBy = 'EditedTime', direction = 'DESC') {
try {
const response = await this.get('/drive/v1/files', {
folder_token: folderToken,
order_by: orderBy,
direction
});
Logger.debug(`获取文件夹(${folderToken})中的文件清单成功,文件数量: ${response.files?.length || 0}`);
return response;
}
catch (error) {
this.handleApiError(error, '获取文件夹中的文件清单失败');
}
}
/**
* 在指定文件夹下创建子文件夹
* @param folderToken 父文件夹的 Token
* @param name 新文件夹的名称
* @returns 创建结果,包含新文件夹的 token 和访问 url
*/
async createFolder(folderToken, name) {
try {
const response = await this.post('/drive/v1/files/create_folder', { folder_token: folderToken, name });
Logger.debug(`文件夹创建成功, token: ${response.token}, url: ${response.url}`);
return response;
}
catch (error) {
this.handleApiError(error, '创建文件夹失败');
}
}
// ─── 知识空间(Wiki)────────────────────────────────────────────
/**
* 获取当前用户可见的所有知识空间列表,自动处理分页直到获取全部数据
* @param pageSize 每次分页请求的数量,默认 20
* @returns 知识空间对象数组,每项包含 space_id、name、description 等字段
*/
async getAllWikiSpacesList(pageSize = 20) {
try {
Logger.info(`开始获取所有知识空间列表,每页数量: ${pageSize}`);
const endpoint = '/wiki/v2/spaces';
let allItems = [];
let pageToken = undefined;
let hasMore = true;
while (hasMore) {
const params = { page_size: pageSize };
if (pageToken)
params.page_token = pageToken;
Logger.debug(`请求知识空间列表,page_token: ${pageToken || 'null'}, page_size: ${pageSize}`);
const response = await this.get(endpoint, params);
if (response?.items) {
allItems = [...allItems, ...response.items];
hasMore = response.has_more || false;
pageToken = response.page_token;
Logger.debug(`当前页获取到 ${response.items.length} 个知识空间,累计 ${allItems.length} 个,hasMore: ${hasMore}`);
}
else {
hasMore = false;
Logger.warn('知识空间列表响应格式异常:', JSON.stringify(response, null, 2));
}
}
Logger.info(`知识空间列表获取完成,共 ${allItems.length} 个空间`);
return allItems;
}
catch (error) {
this.handleApiError(error, '获取知识空间列表失败');
}
}
/**
* 获取指定知识空间下的所有子节点,自动处理分页直到获取全部数据
* @param spaceId 知识空间 ID
* @param parentNodeToken 父节点 Token,不传则获取根节点下的直属子节点
* @param pageSize 每次分页请求的数量,默认 20
* @returns 节点对象数组,每项包含 node_token、obj_token、title、obj_type 等字段
*/
async getAllWikiSpaceNodes(spaceId, parentNodeToken, pageSize = 20) {
try {
Logger.info(`开始获取知识空间子节点列表,space_id: ${spaceId}, parent_node_token: ${parentNodeToken || 'null'}`);
const endpoint = `/wiki/v2/spaces/${spaceId}/nodes`;
let allItems = [];
let pageToken = undefined;
let hasMore = true;
while (hasMore) {
const params = { page_size: pageSize };
if (parentNodeToken)
params.parent_node_token = parentNodeToken;
if (pageToken)
params.page_token = pageToken;
Logger.debug(`请求知识空间子节点列表,page_token: ${pageToken || 'null'}`);
const response = await this.get(endpoint, params);
if (response?.items) {
allItems = [...allItems, ...response.items];
hasMore = response.has_more || false;
pageToken = response.page_token;
Logger.debug(`当前页获取到 ${response.items.length} 个子节点,累计 ${allItems.length} 个,hasMore: ${hasMore}`);
}
else {
hasMore = false;
Logger.warn('知识空间子节点列表响应格式异常:', JSON.stringify(response, null, 2));
}
}
Logger.info(`知识空间子节点列表获取完成,共 ${allItems.length} 个节点`);
return allItems;
}
catch (error) {
this.handleApiError(error, '获取知识空间子节点列表失败');
}
}
/**
* 获取指定知识空间的详细信息
* 当 spaceId 传入 'my_library' 时,获取当前用户的"我的知识库"信息
* @param spaceId 知识空间 ID,或传入 'my_library' 获取个人知识库
* @param lang 语言参数,仅当 spaceId 为 'my_library' 时有效,默认 'en'
* @returns 知识空间信息对象,包含 space_id、name、description 等字段
*/
async getWikiSpaceInfo(spaceId, lang = 'en') {
try {
const params = {};
if (spaceId === 'my_library')
params.lang = lang;
const response = await this.get(`/wiki/v2/spaces/${spaceId}`, params);
Logger.debug(`获取知识空间信息成功 (space_id: ${spaceId}):`, response);
return response?.space || response;
}
catch (error) {
this.handleApiError(error, `获取知识空间信息失败 (space_id: ${spaceId})`);
}
}
/**
* 在知识空间中创建一个新的文档节点(类型固定为 docx)
* @param spaceId 目标知识空间 ID
* @param title 节点标题
* @param parentNodeToken 父节点 Token,不传则在根目录下创建
* @returns 创建的节点信息,包含 node_token(节点 ID)和 obj_token(关联文档 ID)
*/
async createWikiSpaceNode(spaceId, title, parentNodeToken) {
try {
Logger.info(`开始创建知识空间节点,space_id: ${spaceId}, title: ${title}, parent_node_token: ${parentNodeToken || 'null(根节点)'}`);
const endpoint = `/wiki/v2/spaces/${spaceId}/nodes`;
const payload = { title, obj_type: 'docx', node_type: 'origin' };
if (parentNodeToken)
payload.parent_node_token = parentNodeToken;
const response = await this.post(endpoint, payload);
if (response?.data?.node) {
const node = response.data.node;
Logger.info(`知识空间节点创建成功,node_token: ${node.node_token}, obj_token: ${node.obj_token}`);
return node;
}
Logger.info('知识空间节点创建成功');
return response;
}
catch (error) {
this.handleApiError(error, '创建知识空间节点失败');
}
}
}