feishu-mcp 0.0.16 → 0.0.17
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/cli.js +0 -0
- package/dist/mcp/tools/feishuBlockTools.js +252 -202
- package/dist/mcp/tools/feishuFolderTools.js +23 -19
- package/dist/mcp/tools/feishuTools.js +68 -54
- package/dist/server.js +20 -0
- package/dist/services/baseService.js +0 -10
- package/dist/services/callbackService.js +80 -0
- package/dist/services/feishuApiService.js +19 -35
- package/dist/services/feishuAuthService.js +185 -0
- package/dist/types/feishuSchema.js +15 -6
- package/dist/utils/cache.js +96 -15
- package/dist/utils/config.js +39 -7
- package/dist/utils/document.js +154 -0
- package/package.json +1 -1
- package/dist/config.js +0 -26
- package/dist/services/feishu.js +0 -495
- package/dist/services/feishuBlockService.js +0 -179
- package/dist/services/feishuBlocks.js +0 -135
- package/dist/services/feishuService.js +0 -475
package/dist/utils/cache.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Config } from './config.js';
|
|
2
2
|
import { Logger } from './logger.js';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
3
5
|
/**
|
|
4
6
|
* 缓存管理器类
|
|
5
7
|
* 提供内存缓存功能,支持TTL和最大容量限制
|
|
@@ -22,8 +24,15 @@ export class CacheManager {
|
|
|
22
24
|
writable: true,
|
|
23
25
|
value: void 0
|
|
24
26
|
});
|
|
27
|
+
Object.defineProperty(this, "userTokenCacheFile", {
|
|
28
|
+
enumerable: true,
|
|
29
|
+
configurable: true,
|
|
30
|
+
writable: true,
|
|
31
|
+
value: path.resolve(process.cwd(), 'user_token_cache.json')
|
|
32
|
+
});
|
|
25
33
|
this.cache = new Map();
|
|
26
34
|
this.config = Config.getInstance();
|
|
35
|
+
this.loadUserTokenCache();
|
|
27
36
|
// 定期清理过期缓存
|
|
28
37
|
setInterval(() => {
|
|
29
38
|
this.cleanExpiredCache();
|
|
@@ -62,6 +71,9 @@ export class CacheManager {
|
|
|
62
71
|
expiresAt: now + (actualTtl * 1000)
|
|
63
72
|
});
|
|
64
73
|
Logger.debug(`缓存设置: ${key} (TTL: ${actualTtl}秒)`);
|
|
74
|
+
if (key.startsWith('user_access_token:')) {
|
|
75
|
+
this.saveUserTokenCache();
|
|
76
|
+
}
|
|
65
77
|
return true;
|
|
66
78
|
}
|
|
67
79
|
/**
|
|
@@ -99,6 +111,9 @@ export class CacheManager {
|
|
|
99
111
|
const result = this.cache.delete(key);
|
|
100
112
|
if (result) {
|
|
101
113
|
Logger.debug(`缓存删除: ${key}`);
|
|
114
|
+
if (key.startsWith('user_access_token:')) {
|
|
115
|
+
this.saveUserTokenCache();
|
|
116
|
+
}
|
|
102
117
|
}
|
|
103
118
|
return result;
|
|
104
119
|
}
|
|
@@ -185,6 +200,47 @@ export class CacheManager {
|
|
|
185
200
|
ttl: this.config.cache.ttl
|
|
186
201
|
};
|
|
187
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* 缓存Wiki到文档ID的转换结果
|
|
205
|
+
* @param wikiToken Wiki Token
|
|
206
|
+
* @param documentId 文档ID
|
|
207
|
+
* @returns 是否成功设置缓存
|
|
208
|
+
*/
|
|
209
|
+
cacheWikiToDocId(wikiToken, documentId) {
|
|
210
|
+
return this.set(`wiki:${wikiToken}`, documentId);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* 获取缓存的Wiki转换结果
|
|
214
|
+
* @param wikiToken Wiki Token
|
|
215
|
+
* @returns 文档ID,如果未找到或已过期则返回null
|
|
216
|
+
*/
|
|
217
|
+
getWikiToDocId(wikiToken) {
|
|
218
|
+
return this.get(`wiki:${wikiToken}`);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* 缓存tenant访问令牌
|
|
222
|
+
* @param token 访问令牌
|
|
223
|
+
* @param expiresInSeconds 过期时间(秒)
|
|
224
|
+
* @param key 缓存键,默认为'access_token'
|
|
225
|
+
* @returns 是否成功设置缓存
|
|
226
|
+
*/
|
|
227
|
+
cacheTenantToken(key, token, expiresInSeconds) {
|
|
228
|
+
return this.set(`tenant_access_token:${key}`, token, expiresInSeconds);
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* 获取tenant缓存的访问令牌
|
|
232
|
+
* @param key 缓存键,默认为'access_token'
|
|
233
|
+
* @returns 访问令牌,如果未找到或已过期则返回null
|
|
234
|
+
*/
|
|
235
|
+
getTenantToken(key) {
|
|
236
|
+
return this.get(`tenant_access_token:${key}`);
|
|
237
|
+
}
|
|
238
|
+
cacheUserToken(key, tokenObj, expiresIn) {
|
|
239
|
+
return this.set(`user_access_token:${key}`, tokenObj, expiresIn);
|
|
240
|
+
}
|
|
241
|
+
getUserToken(key) {
|
|
242
|
+
return this.get(`user_access_token:${key}`);
|
|
243
|
+
}
|
|
188
244
|
/**
|
|
189
245
|
* 缓存访问令牌
|
|
190
246
|
* @param token 访问令牌
|
|
@@ -192,30 +248,55 @@ export class CacheManager {
|
|
|
192
248
|
* @returns 是否成功设置缓存
|
|
193
249
|
*/
|
|
194
250
|
cacheToken(token, expiresInSeconds) {
|
|
195
|
-
return this.set(
|
|
251
|
+
return this.set(`access_token`, token, expiresInSeconds);
|
|
196
252
|
}
|
|
197
253
|
/**
|
|
198
254
|
* 获取缓存的访问令牌
|
|
199
255
|
* @returns 访问令牌,如果未找到或已过期则返回null
|
|
200
256
|
*/
|
|
201
257
|
getToken() {
|
|
202
|
-
return this.get(
|
|
258
|
+
return this.get(`access_token`);
|
|
203
259
|
}
|
|
204
260
|
/**
|
|
205
|
-
*
|
|
206
|
-
* @param
|
|
207
|
-
* @param
|
|
208
|
-
* @returns
|
|
261
|
+
* 生成client_id+client_secret签名
|
|
262
|
+
* @param client_id
|
|
263
|
+
* @param client_secret
|
|
264
|
+
* @returns 唯一key
|
|
209
265
|
*/
|
|
210
|
-
|
|
211
|
-
|
|
266
|
+
static async getClientKey(client_id, client_secret) {
|
|
267
|
+
const crypto = await import('crypto');
|
|
268
|
+
return crypto.createHash('sha256').update(client_id + ':' + client_secret).digest('hex');
|
|
212
269
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
270
|
+
loadUserTokenCache() {
|
|
271
|
+
if (fs.existsSync(this.userTokenCacheFile)) {
|
|
272
|
+
try {
|
|
273
|
+
const raw = fs.readFileSync(this.userTokenCacheFile, 'utf-8');
|
|
274
|
+
const obj = JSON.parse(raw);
|
|
275
|
+
for (const k in obj) {
|
|
276
|
+
if (k.startsWith('user_access_token:')) {
|
|
277
|
+
this.cache.set(k, obj[k]);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
Logger.info(`已加载本地 user_token_cache.json,共${Object.keys(obj).length}条`);
|
|
281
|
+
}
|
|
282
|
+
catch (e) {
|
|
283
|
+
Logger.warn('加载 user_token_cache.json 失败', e);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
saveUserTokenCache() {
|
|
288
|
+
const obj = {};
|
|
289
|
+
for (const [k, v] of this.cache.entries()) {
|
|
290
|
+
if (k.startsWith('user_access_token:')) {
|
|
291
|
+
obj[k] = v;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
fs.writeFileSync(this.userTokenCacheFile, JSON.stringify(obj, null, 2), 'utf-8');
|
|
296
|
+
Logger.debug('user_token_cache.json 已写入');
|
|
297
|
+
}
|
|
298
|
+
catch (e) {
|
|
299
|
+
Logger.warn('写入 user_token_cache.json 失败', e);
|
|
300
|
+
}
|
|
220
301
|
}
|
|
221
302
|
}
|
package/dist/utils/config.js
CHANGED
|
@@ -110,6 +110,14 @@ export class Config {
|
|
|
110
110
|
'cache-ttl': {
|
|
111
111
|
type: 'number',
|
|
112
112
|
description: '缓存生存时间(秒)'
|
|
113
|
+
},
|
|
114
|
+
'feishu-auth-type': {
|
|
115
|
+
type: 'string',
|
|
116
|
+
description: '飞书认证类型 (tenant 或 user)'
|
|
117
|
+
},
|
|
118
|
+
'feishu-token-endpoint': {
|
|
119
|
+
type: 'string',
|
|
120
|
+
description: '获取token的接口地址,默认 http://localhost:3333/getToken'
|
|
113
121
|
}
|
|
114
122
|
})
|
|
115
123
|
.help()
|
|
@@ -144,11 +152,14 @@ export class Config {
|
|
|
144
152
|
* @returns 飞书配置
|
|
145
153
|
*/
|
|
146
154
|
initFeishuConfig(argv) {
|
|
155
|
+
// 先初始化serverConfig以获取端口
|
|
156
|
+
const serverConfig = this.server || this.initServerConfig(argv);
|
|
147
157
|
const feishuConfig = {
|
|
148
158
|
appId: '',
|
|
149
159
|
appSecret: '',
|
|
150
160
|
baseUrl: 'https://open.feishu.cn/open-apis',
|
|
151
|
-
|
|
161
|
+
authType: 'tenant', // 默认
|
|
162
|
+
tokenEndpoint: `http://127.0.0.1:${serverConfig.port}/getToken`, // 默认动态端口
|
|
152
163
|
};
|
|
153
164
|
// 处理App ID
|
|
154
165
|
if (argv['feishu-app-id']) {
|
|
@@ -180,13 +191,29 @@ export class Config {
|
|
|
180
191
|
else {
|
|
181
192
|
this.configSources['feishu.baseUrl'] = ConfigSource.DEFAULT;
|
|
182
193
|
}
|
|
183
|
-
// 处理
|
|
184
|
-
if (
|
|
185
|
-
feishuConfig.
|
|
186
|
-
this.configSources['feishu.
|
|
194
|
+
// 处理authType
|
|
195
|
+
if (argv['feishu-auth-type']) {
|
|
196
|
+
feishuConfig.authType = argv['feishu-auth-type'] === 'user' ? 'user' : 'tenant';
|
|
197
|
+
this.configSources['feishu.authType'] = ConfigSource.CLI;
|
|
198
|
+
}
|
|
199
|
+
else if (process.env.FEISHU_AUTH_TYPE) {
|
|
200
|
+
feishuConfig.authType = process.env.FEISHU_AUTH_TYPE === 'user' ? 'user' : 'tenant';
|
|
201
|
+
this.configSources['feishu.authType'] = ConfigSource.ENV;
|
|
187
202
|
}
|
|
188
203
|
else {
|
|
189
|
-
this.configSources['feishu.
|
|
204
|
+
this.configSources['feishu.authType'] = ConfigSource.DEFAULT;
|
|
205
|
+
}
|
|
206
|
+
// 处理tokenEndpoint
|
|
207
|
+
if (argv['feishu-token-endpoint']) {
|
|
208
|
+
feishuConfig.tokenEndpoint = argv['feishu-token-endpoint'];
|
|
209
|
+
this.configSources['feishu.tokenEndpoint'] = ConfigSource.CLI;
|
|
210
|
+
}
|
|
211
|
+
else if (process.env.FEISHU_TOKEN_ENDPOINT) {
|
|
212
|
+
feishuConfig.tokenEndpoint = process.env.FEISHU_TOKEN_ENDPOINT;
|
|
213
|
+
this.configSources['feishu.tokenEndpoint'] = ConfigSource.ENV;
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
this.configSources['feishu.tokenEndpoint'] = ConfigSource.DEFAULT;
|
|
190
217
|
}
|
|
191
218
|
return feishuConfig;
|
|
192
219
|
}
|
|
@@ -319,7 +346,8 @@ export class Config {
|
|
|
319
346
|
Logger.info(`- App Secret: ${this.maskApiKey(this.feishu.appSecret)} (来源: ${this.configSources['feishu.appSecret']})`);
|
|
320
347
|
}
|
|
321
348
|
Logger.info(`- API URL: ${this.feishu.baseUrl} (来源: ${this.configSources['feishu.baseUrl']})`);
|
|
322
|
-
Logger.info(`-
|
|
349
|
+
Logger.info(`- 认证类型: ${this.feishu.authType} (来源: ${this.configSources['feishu.authType']})`);
|
|
350
|
+
Logger.info(`- Token获取地址: ${this.feishu.tokenEndpoint} (来源: ${this.configSources['feishu.tokenEndpoint']})`);
|
|
323
351
|
Logger.info('日志配置:');
|
|
324
352
|
Logger.info(`- 日志级别: ${LogLevel[this.log.level]} (来源: ${this.configSources['log.level']})`);
|
|
325
353
|
Logger.info(`- 显示时间戳: ${this.log.showTimestamp} (来源: ${this.configSources['log.showTimestamp']})`);
|
|
@@ -358,6 +386,10 @@ export class Config {
|
|
|
358
386
|
Logger.error('缺少飞书应用Secret,请通过环境变量FEISHU_APP_SECRET或命令行参数--feishu-app-secret提供');
|
|
359
387
|
return false;
|
|
360
388
|
}
|
|
389
|
+
if (!this.feishu.tokenEndpoint) {
|
|
390
|
+
Logger.error('缺少飞书Token获取地址,请通过环境变量FEISHU_TOKEN_ENDPOINT或命令行参数--feishu-token-endpoint提供');
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
361
393
|
return true;
|
|
362
394
|
}
|
|
363
395
|
}
|
package/dist/utils/document.js
CHANGED
|
@@ -110,3 +110,157 @@ export function detectMimeType(buffer) {
|
|
|
110
110
|
return 'application/octet-stream';
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
|
+
function formatExpire(seconds) {
|
|
114
|
+
if (!seconds || isNaN(seconds))
|
|
115
|
+
return '';
|
|
116
|
+
if (seconds < 0)
|
|
117
|
+
return `<span style='color:#e53935'>已过期</span> (${seconds}s)`;
|
|
118
|
+
const h = Math.floor(seconds / 3600);
|
|
119
|
+
const m = Math.floor((seconds % 3600) / 60);
|
|
120
|
+
const s = seconds % 60;
|
|
121
|
+
let str = '';
|
|
122
|
+
if (h)
|
|
123
|
+
str += h + '小时';
|
|
124
|
+
if (m)
|
|
125
|
+
str += m + '分';
|
|
126
|
+
if (s || (!h && !m))
|
|
127
|
+
str += s + '秒';
|
|
128
|
+
return `${str} (${seconds}s)`;
|
|
129
|
+
}
|
|
130
|
+
export function renderFeishuAuthResultHtml(data) {
|
|
131
|
+
const isError = data && data.error;
|
|
132
|
+
const now = Math.floor(Date.now() / 1000);
|
|
133
|
+
let expiresIn = data && data.expires_in;
|
|
134
|
+
let refreshExpiresIn = data && (data.refresh_token_expires_in || data.refresh_expires_in);
|
|
135
|
+
if (expiresIn && expiresIn > 1000000000)
|
|
136
|
+
expiresIn = expiresIn - now;
|
|
137
|
+
if (refreshExpiresIn && refreshExpiresIn > 1000000000)
|
|
138
|
+
refreshExpiresIn = refreshExpiresIn - now;
|
|
139
|
+
const tokenBlock = data && !isError ? `
|
|
140
|
+
<div class="card">
|
|
141
|
+
<h3>Token 信息</h3>
|
|
142
|
+
<ul class="kv-list">
|
|
143
|
+
<li><b>token_type:</b> <span>${data.token_type || ''}</span></li>
|
|
144
|
+
<li><b>access_token:</b> <span class="foldable" onclick="toggleFold(this)">点击展开/收起</span><pre class="fold scrollable">${data.access_token || ''}</pre></li>
|
|
145
|
+
<li><b>expires_in:</b> <span>${formatExpire(expiresIn)}</span></li>
|
|
146
|
+
<li><b>refresh_token:</b> <span class="foldable" onclick="toggleFold(this)">点击展开/收起</span><pre class="fold scrollable">${data.refresh_token || ''}</pre></li>
|
|
147
|
+
<li><b>refresh_token_expires_in:</b> <span>${formatExpire(refreshExpiresIn)}</span></li>
|
|
148
|
+
<li><b>scope:</b> <pre class="scope">${(data.scope || '').replace(/ /g, '\n')}</pre></li>
|
|
149
|
+
</ul>
|
|
150
|
+
<div class="success-action">
|
|
151
|
+
<span class="success-msg">授权成功,继续完成任务</span>
|
|
152
|
+
<button class="copy-btn" onclick="copySuccessMsg(this)">点击复制到粘贴板</button>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
` : '';
|
|
156
|
+
let userBlock = '';
|
|
157
|
+
const userInfo = data && data.userInfo && data.userInfo.data;
|
|
158
|
+
if (userInfo) {
|
|
159
|
+
userBlock = `
|
|
160
|
+
<div class="card user-card">
|
|
161
|
+
<div class="avatar-wrap">
|
|
162
|
+
<img src="${userInfo.avatar_big || userInfo.avatar_thumb || userInfo.avatar_url || ''}" class="avatar" />
|
|
163
|
+
</div>
|
|
164
|
+
<div class="user-info">
|
|
165
|
+
<div class="user-name">${userInfo.name || ''}</div>
|
|
166
|
+
<div class="user-en">${userInfo.en_name || ''}</div>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
`;
|
|
170
|
+
}
|
|
171
|
+
const errorBlock = isError ? `
|
|
172
|
+
<div class="card error-card">
|
|
173
|
+
<h3>授权失败</h3>
|
|
174
|
+
<div class="error-msg">${escapeHtml(data.error || '')}</div>
|
|
175
|
+
<div class="error-code">错误码: ${data.code || ''}</div>
|
|
176
|
+
</div>
|
|
177
|
+
` : '';
|
|
178
|
+
return `
|
|
179
|
+
<html>
|
|
180
|
+
<head>
|
|
181
|
+
<title>飞书授权结果</title>
|
|
182
|
+
<meta charset="utf-8"/>
|
|
183
|
+
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
|
184
|
+
<style>
|
|
185
|
+
body { background: #f7f8fa; font-family: 'Segoe UI', Arial, sans-serif; margin:0; padding:0; }
|
|
186
|
+
.container { max-width: 600px; margin: 40px auto; padding: 16px; }
|
|
187
|
+
.card { background: #fff; border-radius: 12px; box-shadow: 0 2px 12px #0001; margin-bottom: 24px; padding: 24px 20px; }
|
|
188
|
+
.user-card { display: flex; align-items: center; gap: 24px; }
|
|
189
|
+
.avatar-wrap { flex-shrink: 0; }
|
|
190
|
+
.avatar { width: 96px; height: 96px; border-radius: 50%; box-shadow: 0 2px 8px #0002; display: block; margin: 0 auto; }
|
|
191
|
+
.user-info { flex: 1; }
|
|
192
|
+
.user-name { font-size: 1.5em; font-weight: bold; margin-bottom: 4px; }
|
|
193
|
+
.user-en { color: #888; margin-bottom: 10px; }
|
|
194
|
+
.kv-list { list-style: none; padding: 0; margin: 0; }
|
|
195
|
+
.kv-list li { margin-bottom: 6px; word-break: break-all; }
|
|
196
|
+
.kv-list b { color: #1976d2; }
|
|
197
|
+
.scope { background: #f0f4f8; border-radius: 4px; padding: 6px; font-size: 0.95em; white-space: pre-line; }
|
|
198
|
+
.foldable { color: #1976d2; cursor: pointer; text-decoration: underline; margin-left: 8px; }
|
|
199
|
+
.fold { display: none; background: #f6f6f6; border-radius: 4px; padding: 6px; margin: 4px 0; font-size: 0.92em; max-width: 100%; overflow-x: auto; word-break: break-all; }
|
|
200
|
+
.scrollable { max-width: 100%; overflow-x: auto; font-family: 'Fira Mono', 'Consolas', 'Menlo', monospace; font-size: 0.93em; }
|
|
201
|
+
.success-action { margin-top: 18px; display: flex; align-items: center; gap: 16px; }
|
|
202
|
+
.success-msg { color: #388e3c; font-weight: bold; }
|
|
203
|
+
.copy-btn { background: #1976d2; color: #fff; border: none; border-radius: 4px; padding: 6px 16px; font-size: 1em; cursor: pointer; transition: background 0.2s; }
|
|
204
|
+
.copy-btn:hover { background: #125ea2; }
|
|
205
|
+
.error-card { border-left: 6px solid #e53935; background: #fff0f0; color: #b71c1c; }
|
|
206
|
+
.error-msg { font-size: 1.1em; margin-bottom: 8px; }
|
|
207
|
+
.error-code { color: #b71c1c; font-size: 0.95em; }
|
|
208
|
+
.raw-block { margin-top: 24px; }
|
|
209
|
+
.raw-toggle { color: #1976d2; cursor: pointer; text-decoration: underline; margin-bottom: 8px; display: inline-block; }
|
|
210
|
+
.raw-pre { display: none; background: #23272e; color: #fff; border-radius: 6px; padding: 12px; font-size: 0.95em; overflow-x: auto; max-width: 100%; }
|
|
211
|
+
@media (max-width: 700px) {
|
|
212
|
+
.container { max-width: 98vw; padding: 4vw; }
|
|
213
|
+
.card { padding: 4vw 3vw; }
|
|
214
|
+
.avatar { width: 64px; height: 64px; }
|
|
215
|
+
}
|
|
216
|
+
</style>
|
|
217
|
+
<script>
|
|
218
|
+
function toggleFold(el) {
|
|
219
|
+
var pre = el.nextElementSibling;
|
|
220
|
+
if (pre.style.display === 'block') {
|
|
221
|
+
pre.style.display = 'none';
|
|
222
|
+
} else {
|
|
223
|
+
pre.style.display = 'block';
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function toggleRaw() {
|
|
227
|
+
var pre = document.getElementById('raw-pre');
|
|
228
|
+
if (pre.style.display === 'block') {
|
|
229
|
+
pre.style.display = 'none';
|
|
230
|
+
} else {
|
|
231
|
+
pre.style.display = 'block';
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function copySuccessMsg(btn) {
|
|
235
|
+
var text = '授权成功,继续完成任务';
|
|
236
|
+
navigator.clipboard.writeText(text).then(function() {
|
|
237
|
+
btn.innerText = '已复制';
|
|
238
|
+
btn.disabled = true;
|
|
239
|
+
setTimeout(function() {
|
|
240
|
+
btn.innerText = '点击复制到粘贴板';
|
|
241
|
+
btn.disabled = false;
|
|
242
|
+
}, 2000);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
</script>
|
|
246
|
+
</head>
|
|
247
|
+
<body>
|
|
248
|
+
<div class="container">
|
|
249
|
+
<h2 style="margin-bottom:24px;">飞书授权结果</h2>
|
|
250
|
+
${errorBlock}
|
|
251
|
+
${tokenBlock}
|
|
252
|
+
${userBlock}
|
|
253
|
+
<div class="card raw-block">
|
|
254
|
+
<span class="raw-toggle" onclick="toggleRaw()">点击展开/收起原始数据</span>
|
|
255
|
+
<pre id="raw-pre" class="raw-pre">${escapeHtml(JSON.stringify(data, null, 2))}</pre>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
</body>
|
|
259
|
+
</html>
|
|
260
|
+
`;
|
|
261
|
+
}
|
|
262
|
+
function escapeHtml(str) {
|
|
263
|
+
return str.replace(/[&<>"]|'/g, function (c) {
|
|
264
|
+
return ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c] || c;
|
|
265
|
+
});
|
|
266
|
+
}
|
package/package.json
CHANGED
package/dist/config.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { Config, ConfigSource } from './utils/config.js';
|
|
2
|
-
/**
|
|
3
|
-
* 为了向后兼容,保留getServerConfig函数
|
|
4
|
-
* 但内部使用Config类
|
|
5
|
-
* @param isStdioMode 是否在stdio模式下
|
|
6
|
-
* @returns 服务器配置
|
|
7
|
-
*/
|
|
8
|
-
export function getServerConfig(isStdioMode) {
|
|
9
|
-
const config = Config.getInstance();
|
|
10
|
-
if (!isStdioMode) {
|
|
11
|
-
config.printConfig(isStdioMode);
|
|
12
|
-
}
|
|
13
|
-
// 为了向后兼容,返回旧格式的配置对象
|
|
14
|
-
return {
|
|
15
|
-
port: config.server.port,
|
|
16
|
-
feishuAppId: config.feishu.appId,
|
|
17
|
-
feishuAppSecret: config.feishu.appSecret,
|
|
18
|
-
configSources: {
|
|
19
|
-
port: config.configSources['server.port'].toLowerCase(),
|
|
20
|
-
feishuAppId: config.configSources['feishu.appId']?.toLowerCase(),
|
|
21
|
-
feishuAppSecret: config.configSources['feishu.appSecret']?.toLowerCase()
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
// 导出Config类
|
|
26
|
-
export { Config, ConfigSource };
|