feishu-mcp 0.0.5 → 0.0.7
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/README.md +1 -6
- package/dist/config.js +22 -100
- package/dist/index.js +14 -13
- package/dist/server.js +193 -370
- package/dist/services/baseService.js +204 -0
- package/dist/services/blockFactory.js +184 -0
- package/dist/services/feishu.js +16 -148
- package/dist/services/feishuApiService.js +488 -0
- package/dist/services/feishuBlockService.js +179 -0
- package/dist/services/feishuService.js +475 -0
- package/dist/types/feishuSchema.js +97 -0
- package/dist/utils/cache.js +221 -0
- package/dist/utils/config.js +363 -0
- package/dist/utils/document.js +74 -0
- package/dist/utils/error.js +154 -0
- package/dist/utils/logger.js +257 -0
- package/dist/utils/paramUtils.js +193 -0
- package/package.json +1 -1
|
@@ -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
|
+
}
|
package/dist/services/feishu.js
CHANGED
|
@@ -41,36 +41,6 @@ export class FeishuService {
|
|
|
41
41
|
this.appId = appId;
|
|
42
42
|
this.appSecret = appSecret;
|
|
43
43
|
}
|
|
44
|
-
// 包装和重新抛出错误的辅助方法
|
|
45
|
-
wrapAndThrowError(message, originalError) {
|
|
46
|
-
Logger.error(`${message}:`, originalError);
|
|
47
|
-
// 如果原始错误已经是FeishuError格式,直接重新抛出
|
|
48
|
-
if (originalError && typeof originalError === 'object' && 'status' in originalError && 'err' in originalError) {
|
|
49
|
-
throw originalError;
|
|
50
|
-
}
|
|
51
|
-
// 如果是AxiosError,抽取有用信息
|
|
52
|
-
if (originalError instanceof AxiosError && originalError.response) {
|
|
53
|
-
throw {
|
|
54
|
-
status: originalError.response.status,
|
|
55
|
-
err: `${message}: ${originalError.response.data?.msg || originalError.message || 'Unknown error'}`,
|
|
56
|
-
apiError: originalError.response.data
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
// 其他类型的错误,包装为一致的格式
|
|
60
|
-
if (originalError instanceof Error) {
|
|
61
|
-
throw {
|
|
62
|
-
status: 500,
|
|
63
|
-
err: `${message}: ${originalError.message}`,
|
|
64
|
-
apiError: originalError
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
// 未知错误类型
|
|
68
|
-
throw {
|
|
69
|
-
status: 500,
|
|
70
|
-
err: message,
|
|
71
|
-
apiError: originalError
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
44
|
isTokenExpired() {
|
|
75
45
|
if (!this.accessToken || !this.tokenExpireTime)
|
|
76
46
|
return true;
|
|
@@ -92,7 +62,7 @@ export class FeishuService {
|
|
|
92
62
|
Logger.log(`请求方法: POST`);
|
|
93
63
|
Logger.log(`请求数据: ${JSON.stringify(requestData, null, 2)}`);
|
|
94
64
|
const response = await axios.post(url, requestData);
|
|
95
|
-
Logger.log(`响应状态码: ${response
|
|
65
|
+
Logger.log(`响应状态码: ${response.status}`);
|
|
96
66
|
Logger.log(`响应头: ${JSON.stringify(response.headers, null, 2)}`);
|
|
97
67
|
Logger.log(`响应数据: ${JSON.stringify(response.data, null, 2)}`);
|
|
98
68
|
if (response.data.code !== 0) {
|
|
@@ -100,7 +70,6 @@ export class FeishuService {
|
|
|
100
70
|
throw {
|
|
101
71
|
status: response.status,
|
|
102
72
|
err: response.data.msg || "Unknown error",
|
|
103
|
-
apiError: response.data
|
|
104
73
|
};
|
|
105
74
|
}
|
|
106
75
|
this.accessToken = response.data.tenant_access_token;
|
|
@@ -117,7 +86,6 @@ export class FeishuService {
|
|
|
117
86
|
throw {
|
|
118
87
|
status: error.response.status,
|
|
119
88
|
err: error.response.data?.msg || "Unknown error",
|
|
120
|
-
apiError: error.response.data
|
|
121
89
|
};
|
|
122
90
|
}
|
|
123
91
|
Logger.error('获取访问令牌时发生未知错误:', error);
|
|
@@ -160,7 +128,6 @@ export class FeishuService {
|
|
|
160
128
|
throw {
|
|
161
129
|
status: error.response.status,
|
|
162
130
|
err: error.response.data?.msg || "Unknown error",
|
|
163
|
-
apiError: error.response.data
|
|
164
131
|
};
|
|
165
132
|
}
|
|
166
133
|
Logger.error('发送请求时发生未知错误:', error);
|
|
@@ -190,7 +157,16 @@ export class FeishuService {
|
|
|
190
157
|
return docInfo;
|
|
191
158
|
}
|
|
192
159
|
catch (error) {
|
|
193
|
-
|
|
160
|
+
Logger.error(`创建文档失败:`, error);
|
|
161
|
+
if (error instanceof AxiosError) {
|
|
162
|
+
Logger.error(`请求URL: ${error.config?.url}`);
|
|
163
|
+
Logger.error(`请求方法: ${error.config?.method?.toUpperCase()}`);
|
|
164
|
+
Logger.error(`状态码: ${error.response?.status}`);
|
|
165
|
+
if (error.response?.data) {
|
|
166
|
+
Logger.error(`错误详情: ${JSON.stringify(error.response.data, null, 2)}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
throw error;
|
|
194
170
|
}
|
|
195
171
|
}
|
|
196
172
|
// 获取文档信息
|
|
@@ -407,9 +383,6 @@ export class FeishuService {
|
|
|
407
383
|
createHeadingBlockContent(text, level = 1, align = 1) {
|
|
408
384
|
// 确保标题级别在有效范围内(1-9)
|
|
409
385
|
const safeLevel = Math.max(1, Math.min(9, level));
|
|
410
|
-
// 确保align值在合法范围内(1-3)
|
|
411
|
-
// 1表示居左,2表示居中,3表示居右
|
|
412
|
-
const safeAlign = (align === 1 || align === 2 || align === 3) ? align : 1;
|
|
413
386
|
// 根据标题级别设置block_type和对应的属性名
|
|
414
387
|
// 飞书API中,一级标题的block_type为3,二级标题为4,以此类推
|
|
415
388
|
const blockType = 2 + safeLevel; // 一级标题为3,二级标题为4,以此类推
|
|
@@ -429,7 +402,7 @@ export class FeishuService {
|
|
|
429
402
|
}
|
|
430
403
|
],
|
|
431
404
|
style: {
|
|
432
|
-
align:
|
|
405
|
+
align: align,
|
|
433
406
|
folded: false
|
|
434
407
|
}
|
|
435
408
|
};
|
|
@@ -469,10 +442,7 @@ export class FeishuService {
|
|
|
469
442
|
if (!docId) {
|
|
470
443
|
throw new Error(`无效的文档ID: ${documentId}`);
|
|
471
444
|
}
|
|
472
|
-
|
|
473
|
-
// 1表示居左,2表示居中,3表示居右
|
|
474
|
-
const safeAlign = (align === 1 || align === 2 || align === 3) ? align : 1;
|
|
475
|
-
Logger.log(`开始创建标题块,文档ID: ${docId},父块ID: ${parentBlockId},标题级别: ${level},对齐方式: ${safeAlign},插入位置: ${index}`);
|
|
445
|
+
Logger.log(`开始创建标题块,文档ID: ${docId},父块ID: ${parentBlockId},标题级别: ${level},插入位置: ${index}`);
|
|
476
446
|
// 确保标题级别在有效范围内(1-9)
|
|
477
447
|
const safeLevel = Math.max(1, Math.min(9, level));
|
|
478
448
|
// 根据标题级别设置block_type和对应的属性名
|
|
@@ -494,7 +464,7 @@ export class FeishuService {
|
|
|
494
464
|
}
|
|
495
465
|
],
|
|
496
466
|
style: {
|
|
497
|
-
align:
|
|
467
|
+
align: align,
|
|
498
468
|
folded: false
|
|
499
469
|
}
|
|
500
470
|
};
|
|
@@ -502,110 +472,8 @@ export class FeishuService {
|
|
|
502
472
|
return await this.createDocumentBlock(documentId, parentBlockId, blockContent, index);
|
|
503
473
|
}
|
|
504
474
|
catch (error) {
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
}
|
|
508
|
-
// 获取块内容
|
|
509
|
-
async getBlockContent(documentId, blockId) {
|
|
510
|
-
try {
|
|
511
|
-
const docId = this.extractDocIdFromUrl(documentId);
|
|
512
|
-
if (!docId) {
|
|
513
|
-
throw new Error(`无效的文档ID: ${documentId}`);
|
|
514
|
-
}
|
|
515
|
-
Logger.log(`开始获取块内容,文档ID: ${docId},块ID: ${blockId}`);
|
|
516
|
-
const endpoint = `/docx/v1/documents/${docId}/blocks/${blockId}?document_revision_id=-1`;
|
|
517
|
-
Logger.log(`准备请求API端点: ${endpoint}`);
|
|
518
|
-
const response = await this.request(endpoint);
|
|
519
|
-
if (response.code !== 0) {
|
|
520
|
-
throw new Error(`获取块内容失败: ${response.msg}`);
|
|
521
|
-
}
|
|
522
|
-
const blockContent = response.data?.block;
|
|
523
|
-
Logger.log(`块内容获取成功: ${JSON.stringify(blockContent, null, 2)}`);
|
|
524
|
-
return blockContent;
|
|
525
|
-
}
|
|
526
|
-
catch (error) {
|
|
527
|
-
this.wrapAndThrowError(`获取块内容失败`, error);
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
// 更新块文本内容
|
|
531
|
-
async updateBlockTextContent(documentId, blockId, textElements) {
|
|
532
|
-
try {
|
|
533
|
-
const docId = this.extractDocIdFromUrl(documentId);
|
|
534
|
-
if (!docId) {
|
|
535
|
-
throw new Error(`无效的文档ID: ${documentId}`);
|
|
536
|
-
}
|
|
537
|
-
Logger.log(`开始更新块文本内容,文档ID: ${docId},块ID: ${blockId}`);
|
|
538
|
-
const endpoint = `/docx/v1/documents/${docId}/blocks/${blockId}?document_revision_id=-1`;
|
|
539
|
-
Logger.log(`准备请求API端点: ${endpoint}`);
|
|
540
|
-
const elements = textElements.map(item => ({
|
|
541
|
-
text_run: {
|
|
542
|
-
content: item.text,
|
|
543
|
-
text_element_style: item.style || {}
|
|
544
|
-
}
|
|
545
|
-
}));
|
|
546
|
-
const data = {
|
|
547
|
-
update_text_elements: {
|
|
548
|
-
elements: elements
|
|
549
|
-
}
|
|
550
|
-
};
|
|
551
|
-
Logger.log(`请求数据: ${JSON.stringify(data, null, 2)}`);
|
|
552
|
-
const response = await this.request(endpoint, 'PATCH', data);
|
|
553
|
-
if (response.code !== 0) {
|
|
554
|
-
throw new Error(`更新块文本内容失败: ${response.msg}`);
|
|
555
|
-
}
|
|
556
|
-
Logger.log(`块文本内容更新成功: ${JSON.stringify(response.data, null, 2)}`);
|
|
557
|
-
return response.data;
|
|
558
|
-
}
|
|
559
|
-
catch (error) {
|
|
560
|
-
this.wrapAndThrowError(`更新块文本内容失败`, error);
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
// 创建列表块内容(有序或无序)
|
|
564
|
-
createListBlockContent(text, isOrdered = false, align = 1) {
|
|
565
|
-
// 确保 align 值在合法范围内(1-3)
|
|
566
|
-
const safeAlign = (align === 1 || align === 2 || align === 3) ? align : 1;
|
|
567
|
-
// 有序列表是 block_type: 13,无序列表是 block_type: 12
|
|
568
|
-
const blockType = isOrdered ? 13 : 12;
|
|
569
|
-
const propertyKey = isOrdered ? "ordered" : "bullet";
|
|
570
|
-
// 构建块内容
|
|
571
|
-
const blockContent = {
|
|
572
|
-
block_type: blockType
|
|
573
|
-
};
|
|
574
|
-
// 设置列表属性
|
|
575
|
-
blockContent[propertyKey] = {
|
|
576
|
-
elements: [
|
|
577
|
-
{
|
|
578
|
-
text_run: {
|
|
579
|
-
content: text,
|
|
580
|
-
text_element_style: {}
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
],
|
|
584
|
-
style: {
|
|
585
|
-
align: safeAlign,
|
|
586
|
-
folded: false
|
|
587
|
-
}
|
|
588
|
-
};
|
|
589
|
-
return blockContent;
|
|
590
|
-
}
|
|
591
|
-
// 创建列表块(有序或无序)
|
|
592
|
-
async createListBlock(documentId, parentBlockId, text, isOrdered = false, index = 0, align = 1) {
|
|
593
|
-
try {
|
|
594
|
-
const docId = this.extractDocIdFromUrl(documentId);
|
|
595
|
-
if (!docId) {
|
|
596
|
-
throw new Error(`无效的文档ID: ${documentId}`);
|
|
597
|
-
}
|
|
598
|
-
// 确保align值在合法范围内(1-3)
|
|
599
|
-
const safeAlign = (align === 1 || align === 2 || align === 3) ? align : 1;
|
|
600
|
-
const listType = isOrdered ? "有序" : "无序";
|
|
601
|
-
Logger.log(`开始创建${listType}列表块,文档ID: ${docId},父块ID: ${parentBlockId},对齐方式: ${safeAlign},插入位置: ${index}`);
|
|
602
|
-
// 创建列表块内容
|
|
603
|
-
const blockContent = this.createListBlockContent(text, isOrdered, safeAlign);
|
|
604
|
-
Logger.log(`列表块内容: ${JSON.stringify(blockContent, null, 2)}`);
|
|
605
|
-
return await this.createDocumentBlock(documentId, parentBlockId, blockContent, index);
|
|
606
|
-
}
|
|
607
|
-
catch (error) {
|
|
608
|
-
this.wrapAndThrowError(`创建${isOrdered ? "有序" : "无序"}列表块失败`, error);
|
|
475
|
+
Logger.error(`创建标题块失败:`, error);
|
|
476
|
+
throw error;
|
|
609
477
|
}
|
|
610
478
|
}
|
|
611
479
|
extractDocIdFromUrl(url) {
|