feishu-mcp 0.0.6 → 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.
@@ -0,0 +1,204 @@
1
+ import axios, { AxiosError } from 'axios';
2
+ import { Logger } from '../utils/logger.js';
3
+ import { formatErrorMessage } from '../utils/error.js';
4
+ /**
5
+ * API服务基类
6
+ * 提供通用的HTTP请求处理和认证功能
7
+ */
8
+ export class BaseApiService {
9
+ constructor() {
10
+ Object.defineProperty(this, "accessToken", {
11
+ enumerable: true,
12
+ configurable: true,
13
+ writable: true,
14
+ value: ''
15
+ });
16
+ Object.defineProperty(this, "tokenExpireTime", {
17
+ enumerable: true,
18
+ configurable: true,
19
+ writable: true,
20
+ value: null
21
+ });
22
+ }
23
+ /**
24
+ * 检查访问令牌是否过期
25
+ * @returns 是否过期
26
+ */
27
+ isTokenExpired() {
28
+ if (!this.accessToken || !this.tokenExpireTime)
29
+ return true;
30
+ // 预留5分钟的缓冲时间
31
+ return Date.now() >= (this.tokenExpireTime - 5 * 60 * 1000);
32
+ }
33
+ /**
34
+ * 处理API错误
35
+ * @param error 错误对象
36
+ * @param message 错误上下文消息
37
+ * @throws 标准化的API错误
38
+ */
39
+ handleApiError(error, message) {
40
+ Logger.error(`${message}:`, error);
41
+ // 如果已经是格式化的API错误,直接重新抛出
42
+ if (error && typeof error === 'object' && 'status' in error && 'err' in error) {
43
+ throw error;
44
+ }
45
+ // 处理Axios错误
46
+ if (error instanceof AxiosError && error.response) {
47
+ const responseData = error.response.data;
48
+ const apiError = {
49
+ status: error.response.status,
50
+ err: formatErrorMessage(error, message),
51
+ apiError: responseData,
52
+ logId: responseData?.log_id
53
+ };
54
+ throw apiError;
55
+ }
56
+ // 处理其他类型的错误
57
+ const errorMessage = error instanceof Error
58
+ ? error.message
59
+ : (typeof error === 'string' ? error : '未知错误');
60
+ throw {
61
+ status: 500,
62
+ err: formatErrorMessage(error, message),
63
+ apiError: {
64
+ code: -1,
65
+ msg: errorMessage,
66
+ error
67
+ }
68
+ };
69
+ }
70
+ /**
71
+ * 执行API请求
72
+ * @param endpoint 请求端点
73
+ * @param method 请求方法
74
+ * @param data 请求数据
75
+ * @param needsAuth 是否需要认证
76
+ * @param additionalHeaders 附加请求头
77
+ * @returns 响应数据
78
+ */
79
+ async request(endpoint, method = 'GET', data, needsAuth = true, additionalHeaders) {
80
+ try {
81
+ // 构建请求URL
82
+ const url = `${this.getBaseUrl()}${endpoint}`;
83
+ // 准备请求头
84
+ const headers = {
85
+ 'Content-Type': 'application/json',
86
+ ...additionalHeaders
87
+ };
88
+ // 添加认证令牌
89
+ if (needsAuth) {
90
+ const accessToken = await this.getAccessToken();
91
+ headers['Authorization'] = `Bearer ${accessToken}`;
92
+ }
93
+ // 记录请求信息
94
+ Logger.debug('准备发送请求:');
95
+ Logger.debug(`请求URL: ${url}`);
96
+ Logger.debug(`请求方法: ${method}`);
97
+ if (data) {
98
+ Logger.debug(`请求数据:`, data);
99
+ }
100
+ // 构建请求配置
101
+ const config = {
102
+ method,
103
+ url,
104
+ headers,
105
+ data: method !== 'GET' ? data : undefined,
106
+ params: method === 'GET' ? data : undefined
107
+ };
108
+ // 发送请求
109
+ const response = await axios(config);
110
+ // 记录响应信息
111
+ Logger.debug('收到响应:');
112
+ Logger.debug(`响应状态码: ${response.status}`);
113
+ Logger.debug(`响应头:`, response.headers);
114
+ Logger.debug(`响应数据:`, response.data);
115
+ // 检查API错误
116
+ if (response.data && typeof response.data.code === 'number' && response.data.code !== 0) {
117
+ Logger.error(`API返回错误码: ${response.data.code}, 错误消息: ${response.data.msg}`);
118
+ throw {
119
+ status: response.status,
120
+ err: response.data.msg || 'API返回错误码',
121
+ apiError: response.data,
122
+ logId: response.data.log_id
123
+ };
124
+ }
125
+ // 返回数据
126
+ return response.data.data;
127
+ }
128
+ catch (error) {
129
+ // 处理401错误,可能是令牌过期
130
+ if (error instanceof AxiosError && error.response?.status === 401) {
131
+ // 清除当前令牌,下次请求会重新获取
132
+ this.accessToken = '';
133
+ this.tokenExpireTime = null;
134
+ Logger.warn('访问令牌可能已过期,已清除缓存的令牌');
135
+ // 如果这是重试请求,避免无限循环
136
+ if (error.isRetry) {
137
+ this.handleApiError(error, `API请求失败 (${endpoint})`);
138
+ }
139
+ // 重试请求
140
+ Logger.info('重试请求...');
141
+ try {
142
+ return await this.request(endpoint, method, data, needsAuth, additionalHeaders);
143
+ }
144
+ catch (retryError) {
145
+ // 标记为重试请求
146
+ retryError.isRetry = true;
147
+ this.handleApiError(retryError, `重试API请求失败 (${endpoint})`);
148
+ }
149
+ }
150
+ // 处理其他错误
151
+ this.handleApiError(error, `API请求失败 (${endpoint})`);
152
+ }
153
+ }
154
+ /**
155
+ * GET请求
156
+ * @param endpoint 请求端点
157
+ * @param params 请求参数
158
+ * @param needsAuth 是否需要认证
159
+ * @returns 响应数据
160
+ */
161
+ async get(endpoint, params, needsAuth = true) {
162
+ return this.request(endpoint, 'GET', params, needsAuth);
163
+ }
164
+ /**
165
+ * POST请求
166
+ * @param endpoint 请求端点
167
+ * @param data 请求数据
168
+ * @param needsAuth 是否需要认证
169
+ * @returns 响应数据
170
+ */
171
+ async post(endpoint, data, needsAuth = true) {
172
+ return this.request(endpoint, 'POST', data, needsAuth);
173
+ }
174
+ /**
175
+ * PUT请求
176
+ * @param endpoint 请求端点
177
+ * @param data 请求数据
178
+ * @param needsAuth 是否需要认证
179
+ * @returns 响应数据
180
+ */
181
+ async put(endpoint, data, needsAuth = true) {
182
+ return this.request(endpoint, 'PUT', data, needsAuth);
183
+ }
184
+ /**
185
+ * PATCH请求
186
+ * @param endpoint 请求端点
187
+ * @param data 请求数据
188
+ * @param needsAuth 是否需要认证
189
+ * @returns 响应数据
190
+ */
191
+ async patch(endpoint, data, needsAuth = true) {
192
+ return this.request(endpoint, 'PATCH', data, needsAuth);
193
+ }
194
+ /**
195
+ * DELETE请求
196
+ * @param endpoint 请求端点
197
+ * @param data 请求数据
198
+ * @param needsAuth 是否需要认证
199
+ * @returns 响应数据
200
+ */
201
+ async delete(endpoint, data, needsAuth = true) {
202
+ return this.request(endpoint, 'DELETE', data, needsAuth);
203
+ }
204
+ }
@@ -0,0 +1,184 @@
1
+ import { Logger } from '../utils/logger.js';
2
+ /**
3
+ * 块类型枚举
4
+ */
5
+ export var BlockType;
6
+ (function (BlockType) {
7
+ BlockType["TEXT"] = "text";
8
+ BlockType["CODE"] = "code";
9
+ BlockType["HEADING"] = "heading";
10
+ BlockType["LIST"] = "list";
11
+ })(BlockType || (BlockType = {}));
12
+ /**
13
+ * 对齐方式枚举
14
+ */
15
+ export var AlignType;
16
+ (function (AlignType) {
17
+ AlignType[AlignType["LEFT"] = 1] = "LEFT";
18
+ AlignType[AlignType["CENTER"] = 2] = "CENTER";
19
+ AlignType[AlignType["RIGHT"] = 3] = "RIGHT";
20
+ })(AlignType || (AlignType = {}));
21
+ /**
22
+ * 块工厂类
23
+ * 提供统一接口创建不同类型的块内容
24
+ */
25
+ export class BlockFactory {
26
+ constructor() { }
27
+ /**
28
+ * 获取块工厂实例
29
+ * @returns 块工厂实例
30
+ */
31
+ static getInstance() {
32
+ if (!BlockFactory.instance) {
33
+ BlockFactory.instance = new BlockFactory();
34
+ }
35
+ return BlockFactory.instance;
36
+ }
37
+ /**
38
+ * 创建块内容
39
+ * @param type 块类型
40
+ * @param options 块选项
41
+ * @returns 块内容对象
42
+ */
43
+ createBlock(type, options) {
44
+ switch (type) {
45
+ case BlockType.TEXT:
46
+ return this.createTextBlock(options);
47
+ case BlockType.CODE:
48
+ return this.createCodeBlock(options);
49
+ case BlockType.HEADING:
50
+ return this.createHeadingBlock(options);
51
+ case BlockType.LIST:
52
+ return this.createListBlock(options);
53
+ default:
54
+ Logger.error(`不支持的块类型: ${type}`);
55
+ throw new Error(`不支持的块类型: ${type}`);
56
+ }
57
+ }
58
+ /**
59
+ * 创建文本块内容
60
+ * @param options 文本块选项
61
+ * @returns 文本块内容对象
62
+ */
63
+ createTextBlock(options) {
64
+ const { textContents, align = AlignType.LEFT } = options;
65
+ return {
66
+ block_type: 2, // 2表示文本块
67
+ text: {
68
+ elements: textContents.map(content => ({
69
+ text_run: {
70
+ content: content.text,
71
+ text_element_style: content.style || {}
72
+ }
73
+ })),
74
+ style: {
75
+ align: align, // 1 居左,2 居中,3 居右
76
+ }
77
+ }
78
+ };
79
+ }
80
+ /**
81
+ * 创建代码块内容
82
+ * @param options 代码块选项
83
+ * @returns 代码块内容对象
84
+ */
85
+ createCodeBlock(options) {
86
+ const { code, language = 0, wrap = false } = options;
87
+ return {
88
+ block_type: 14, // 14表示代码块
89
+ code: {
90
+ elements: [
91
+ {
92
+ text_run: {
93
+ content: code,
94
+ text_element_style: {
95
+ bold: false,
96
+ inline_code: false,
97
+ italic: false,
98
+ strikethrough: false,
99
+ underline: false
100
+ }
101
+ }
102
+ }
103
+ ],
104
+ style: {
105
+ language: language,
106
+ wrap: wrap
107
+ }
108
+ }
109
+ };
110
+ }
111
+ /**
112
+ * 创建标题块内容
113
+ * @param options 标题块选项
114
+ * @returns 标题块内容对象
115
+ */
116
+ createHeadingBlock(options) {
117
+ const { text, level = 1, align = AlignType.LEFT } = options;
118
+ // 确保标题级别在有效范围内(1-9)
119
+ const safeLevel = Math.max(1, Math.min(9, level));
120
+ // 根据标题级别设置block_type和对应的属性名
121
+ // 飞书API中,一级标题的block_type为3,二级标题为4,以此类推
122
+ const blockType = 2 + safeLevel; // 一级标题为3,二级标题为4,以此类推
123
+ const headingKey = `heading${safeLevel}`; // heading1, heading2, ...
124
+ // 构建块内容
125
+ const blockContent = {
126
+ block_type: blockType
127
+ };
128
+ // 设置对应级别的标题属性
129
+ blockContent[headingKey] = {
130
+ elements: [
131
+ {
132
+ text_run: {
133
+ content: text,
134
+ text_element_style: {}
135
+ }
136
+ }
137
+ ],
138
+ style: {
139
+ align: align,
140
+ folded: false
141
+ }
142
+ };
143
+ return blockContent;
144
+ }
145
+ /**
146
+ * 创建列表块内容(有序或无序)
147
+ * @param options 列表块选项
148
+ * @returns 列表块内容对象
149
+ */
150
+ createListBlock(options) {
151
+ const { text, isOrdered = false, align = AlignType.LEFT } = options;
152
+ // 有序列表是 block_type: 13,无序列表是 block_type: 12
153
+ const blockType = isOrdered ? 13 : 12;
154
+ const propertyKey = isOrdered ? "ordered" : "bullet";
155
+ // 构建块内容
156
+ const blockContent = {
157
+ block_type: blockType
158
+ };
159
+ // 设置列表属性
160
+ blockContent[propertyKey] = {
161
+ elements: [
162
+ {
163
+ text_run: {
164
+ content: text,
165
+ text_element_style: {}
166
+ }
167
+ }
168
+ ],
169
+ style: {
170
+ align: align,
171
+ folded: false
172
+ }
173
+ };
174
+ return blockContent;
175
+ }
176
+ /**
177
+ * 创建批量块内容
178
+ * @param blocks 块配置数组
179
+ * @returns 块内容数组
180
+ */
181
+ createBatchBlocks(blocks) {
182
+ return blocks.map(block => this.createBlock(block.type, block.options));
183
+ }
184
+ }