@lmcl/ailo-mcp-feishu 0.2.3 → 0.2.5
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/feishu-handler.js +12 -11
- package/dist/feishu-types.js +5 -5
- package/dist/index.js +2 -2
- package/package.json +2 -2
package/dist/feishu-handler.js
CHANGED
|
@@ -42,7 +42,7 @@ export class FeishuHandler {
|
|
|
42
42
|
get storage() {
|
|
43
43
|
return this.ctx?.storage ?? null;
|
|
44
44
|
}
|
|
45
|
-
/** 通过 WS 发给
|
|
45
|
+
/** 通过 WS 发给 Ailo 代打,ctx 未就绪时回退到 console */
|
|
46
46
|
_log(level, message, data) {
|
|
47
47
|
if (this.ctx?.log) {
|
|
48
48
|
this.ctx.log(level, message, data);
|
|
@@ -515,7 +515,7 @@ export class FeishuHandler {
|
|
|
515
515
|
* 收到飞书消息时,先调用此函数保存图片/视频/文件,再传给 LLM。
|
|
516
516
|
* 失败返回 null,不传 ref。
|
|
517
517
|
*/
|
|
518
|
-
async saveResourceToLocal(messageId, fileKey, resourceType,
|
|
518
|
+
async saveResourceToLocal(messageId, fileKey, resourceType, ailoType, fileName) {
|
|
519
519
|
const workDir = getWorkDir() ?? path.join(os.tmpdir(), "ailo-mcp-feishu-blobs");
|
|
520
520
|
const now = new Date();
|
|
521
521
|
const cacheDir = path.join(workDir, "blobs", String(now.getFullYear()), String(now.getMonth() + 1).padStart(2, "0"));
|
|
@@ -534,7 +534,7 @@ export class FeishuHandler {
|
|
|
534
534
|
catch {
|
|
535
535
|
// messageResource 可能失败,图片可尝试 image.get
|
|
536
536
|
}
|
|
537
|
-
if (!buffer && resourceType === "image" &&
|
|
537
|
+
if (!buffer && resourceType === "image" && ailoType === "image") {
|
|
538
538
|
try {
|
|
539
539
|
const res = await this.client.im.v1.image.get({ path: { image_key: fileKey } });
|
|
540
540
|
if (res?.getReadableStream)
|
|
@@ -552,13 +552,13 @@ export class FeishuHandler {
|
|
|
552
552
|
/**
|
|
553
553
|
* 使用飞书 WebSocket 长连接接收事件,无需配置回调地址。
|
|
554
554
|
* 需在飞书开放平台「事件与回调」中选择「使用长连接接收事件」并保存(本客户端需在线)。
|
|
555
|
-
* 必须 await 飞书连接就绪后返回,避免
|
|
555
|
+
* 必须 await 飞书连接就绪后返回,避免 Ailo 在飞书未连接时就认为通道就绪导致消息丢失。
|
|
556
556
|
*/
|
|
557
557
|
async start(ctx) {
|
|
558
558
|
this.ctx = ctx;
|
|
559
559
|
this.loadExternalUserLabels();
|
|
560
560
|
this.fetchBotOpenId();
|
|
561
|
-
// MCP stdio 占用 stdout,Lark SDK 日志通过 WS 发给
|
|
561
|
+
// MCP stdio 占用 stdout,Lark SDK 日志通过 WS 发给 Ailo 代打
|
|
562
562
|
const sink = (level) => (...args) => {
|
|
563
563
|
const msg = args.map((a) => (typeof a === "object" ? JSON.stringify(a) : String(a))).join(" ");
|
|
564
564
|
this._log(level, msg);
|
|
@@ -584,7 +584,7 @@ export class FeishuHandler {
|
|
|
584
584
|
});
|
|
585
585
|
// 启动定时清理过期缓存(每小时)
|
|
586
586
|
const cacheCleanupInterval = setInterval(() => this.cleanExpiredCache(), 60 * 60 * 1000);
|
|
587
|
-
// 必须 await 飞书连接就绪后再返回,否则 SDK 会在飞书未连接时就连到
|
|
587
|
+
// 必须 await 飞书连接就绪后再返回,否则 SDK 会在飞书未连接时就连到 Ailo,导致启动窗口期消息丢失
|
|
588
588
|
const wsClientAny = wsClient;
|
|
589
589
|
wsClientAny.eventDispatcher = eventDispatcher;
|
|
590
590
|
eventDispatcher.register({
|
|
@@ -668,12 +668,12 @@ export class FeishuHandler {
|
|
|
668
668
|
const content = JSON.parse(rawContent || "{}");
|
|
669
669
|
const fileKey = content[mediaConfig.contentKey];
|
|
670
670
|
if (fileKey) {
|
|
671
|
-
const fileName = content["file_name"] ?? content["fileName"] ?? `${mediaConfig.
|
|
672
|
-
const absPath = await this.saveResourceToLocal(messageId, fileKey, mediaConfig.resourceType, mediaConfig.
|
|
671
|
+
const fileName = content["file_name"] ?? content["fileName"] ?? `${mediaConfig.ailoType}_${fileKey.slice(-12)}.${mediaConfig.ailoType === "image" ? "png" : mediaConfig.ailoType === "video" ? "mp4" : mediaConfig.ailoType === "audio" ? "mp3" : "bin"}`;
|
|
672
|
+
const absPath = await this.saveResourceToLocal(messageId, fileKey, mediaConfig.resourceType, mediaConfig.ailoType, fileName);
|
|
673
673
|
if (absPath)
|
|
674
|
-
attachments.push({ type: mediaConfig.
|
|
674
|
+
attachments.push({ type: mediaConfig.ailoType, path: absPath, name: path.basename(absPath) });
|
|
675
675
|
else
|
|
676
|
-
text = `[无法获取${mediaConfig.
|
|
676
|
+
text = `[无法获取${mediaConfig.ailoType}资源]`;
|
|
677
677
|
}
|
|
678
678
|
else {
|
|
679
679
|
text = "[无法解析的媒体消息]";
|
|
@@ -702,7 +702,7 @@ export class FeishuHandler {
|
|
|
702
702
|
if (msg.parent_id) {
|
|
703
703
|
text = `[回复消息 ${msg.parent_id}] ${text}`;
|
|
704
704
|
}
|
|
705
|
-
// 未知消息类型(卡片等)时若文本和附件均空,
|
|
705
|
+
// 未知消息类型(卡片等)时若文本和附件均空,Ailo 会拒绝;提供占位说明
|
|
706
706
|
if (!text.trim() && attachments.length === 0) {
|
|
707
707
|
text = messageType ? `[${messageType} 类型消息,暂不支持解析]` : "[未知类型消息]";
|
|
708
708
|
}
|
|
@@ -878,6 +878,7 @@ export class FeishuHandler {
|
|
|
878
878
|
},
|
|
879
879
|
});
|
|
880
880
|
await wsClientAny.reConnect(true);
|
|
881
|
+
this.ctx?.reportHealth("connected");
|
|
881
882
|
}
|
|
882
883
|
/**
|
|
883
884
|
* 根据文件名/扩展名推断飞书 im.file.create 的 file_type。
|
package/dist/feishu-types.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export const STALE_MESSAGE_THRESHOLD_MS = 5 * 60 * 1000;
|
|
2
2
|
export const NEGATIVE_CACHE_TTL = 5 * 60 * 1000;
|
|
3
3
|
export const MEDIA_MESSAGE_CONFIG = {
|
|
4
|
-
image: { resourceType: "image",
|
|
5
|
-
file: { resourceType: "file",
|
|
6
|
-
audio: { resourceType: "audio",
|
|
7
|
-
media: { resourceType: "video",
|
|
8
|
-
video: { resourceType: "video",
|
|
4
|
+
image: { resourceType: "image", ailoType: "image", contentKey: "image_key" },
|
|
5
|
+
file: { resourceType: "file", ailoType: "file", contentKey: "file_key" },
|
|
6
|
+
audio: { resourceType: "audio", ailoType: "audio", contentKey: "file_key" },
|
|
7
|
+
media: { resourceType: "video", ailoType: "video", contentKey: "file_key" },
|
|
8
|
+
video: { resourceType: "video", ailoType: "video", contentKey: "file_key" },
|
|
9
9
|
};
|
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ if (!FEISHU_APP_ID || !FEISHU_APP_SECRET) {
|
|
|
11
11
|
}
|
|
12
12
|
const handler = new FeishuHandler({ appId: FEISHU_APP_ID, appSecret: FEISHU_APP_SECRET });
|
|
13
13
|
handler.setOnBotRemovedFromGroup(() => { });
|
|
14
|
-
function
|
|
14
|
+
function feishuBuildChannelInstructions() {
|
|
15
15
|
return `ID 格式:ou_xxx 是飞书用户 ID,oc_xxx 是群组 ID。
|
|
16
16
|
|
|
17
17
|
@提及:@提及格式为 @显示名(ou_xxx)。使用此格式可触发飞书强提醒。
|
|
@@ -27,6 +27,6 @@ runMcpChannel({
|
|
|
27
27
|
handler,
|
|
28
28
|
displayName: "飞书",
|
|
29
29
|
defaultRequiresResponse: true,
|
|
30
|
-
|
|
30
|
+
buildChannelInstructions: feishuBuildChannelInstructions,
|
|
31
31
|
mcpServer,
|
|
32
32
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lmcl/ailo-mcp-feishu",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "Ailo 飞书/Lark 通道 MCP",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@larksuiteoapi/node-sdk": "^1.56.1",
|
|
22
|
-
"@lmcl/ailo-mcp-sdk": "
|
|
22
|
+
"@lmcl/ailo-mcp-sdk": "file:../ailo-mcp-sdk",
|
|
23
23
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
24
24
|
"dotenv": "^16.4.5",
|
|
25
25
|
"form-data": "^4.0.0",
|