feishu-bridge 1.0.6 → 1.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.
- package/dist/cli/index.js +196 -260
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +196 -260
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.js +195 -260
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +195 -260
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -616,9 +616,6 @@ ${error.substring(0, 2e3)}`;
|
|
|
616
616
|
}
|
|
617
617
|
};
|
|
618
618
|
|
|
619
|
-
// src/feishu/websocket.ts
|
|
620
|
-
var import_ws = require("ws");
|
|
621
|
-
|
|
622
619
|
// src/core/message-router.ts
|
|
623
620
|
init_logger();
|
|
624
621
|
var MessageRouter = class {
|
|
@@ -761,163 +758,109 @@ var MessageRouter = class {
|
|
|
761
758
|
// src/feishu/websocket.ts
|
|
762
759
|
init_logger();
|
|
763
760
|
init_config();
|
|
761
|
+
var Lark = __toESM(require("@larksuiteoapi/node-sdk"));
|
|
764
762
|
var FeishuWebSocketHandler = class {
|
|
765
763
|
constructor(client, commandExecutor, adapters) {
|
|
766
764
|
this.client = client;
|
|
767
765
|
this.commandExecutor = commandExecutor;
|
|
768
766
|
this.adapters = adapters;
|
|
769
|
-
this.
|
|
770
|
-
this.
|
|
771
|
-
this.maxReconnectAttempts = 5;
|
|
772
|
-
this.reconnectInterval = 5e3;
|
|
773
|
-
// 5秒
|
|
774
|
-
this.heartbeatInterval = null;
|
|
767
|
+
this.wsClient = null;
|
|
768
|
+
this.eventDispatcher = null;
|
|
775
769
|
this.isRunning = false;
|
|
776
770
|
this.logger = new Logger();
|
|
777
771
|
this.messageRouter = new MessageRouter(adapters);
|
|
778
772
|
}
|
|
779
|
-
/**
|
|
780
|
-
* 启动WebSocket连接
|
|
781
|
-
*/
|
|
782
773
|
async start() {
|
|
783
774
|
if (this.isRunning) {
|
|
784
775
|
this.logger.warn("WebSocket handler already running");
|
|
785
776
|
return;
|
|
786
777
|
}
|
|
787
|
-
|
|
788
|
-
this.reconnectAttempts = 0;
|
|
789
|
-
await this.connect();
|
|
790
|
-
}
|
|
791
|
-
/**
|
|
792
|
-
* 停止WebSocket连接
|
|
793
|
-
*/
|
|
794
|
-
async stop() {
|
|
795
|
-
this.isRunning = false;
|
|
796
|
-
if (this.heartbeatInterval) {
|
|
797
|
-
clearInterval(this.heartbeatInterval);
|
|
798
|
-
this.heartbeatInterval = null;
|
|
799
|
-
}
|
|
800
|
-
if (this.ws) {
|
|
801
|
-
this.ws.close();
|
|
802
|
-
this.ws = null;
|
|
803
|
-
}
|
|
804
|
-
this.logger.info("WebSocket handler stopped");
|
|
805
|
-
}
|
|
806
|
-
/**
|
|
807
|
-
* 建立WebSocket连接
|
|
808
|
-
*/
|
|
809
|
-
async connect() {
|
|
778
|
+
const config = ConfigManager.getInstance().getFeishuConfig();
|
|
810
779
|
try {
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
}
|
|
780
|
+
this.logger.info("Creating Feishu WebSocket client...");
|
|
781
|
+
this.wsClient = new Lark.WSClient({
|
|
782
|
+
appId: config.appId,
|
|
783
|
+
appSecret: config.appSecret,
|
|
784
|
+
domain: config.domain === "lark" ? Lark.Domain.Lark : Lark.Domain.Feishu,
|
|
785
|
+
loggerLevel: Lark.LoggerLevel.info
|
|
818
786
|
});
|
|
819
|
-
this.
|
|
787
|
+
this.eventDispatcher = new Lark.EventDispatcher({
|
|
788
|
+
encryptKey: config.encryptKey || "",
|
|
789
|
+
verificationToken: config.verificationToken || ""
|
|
790
|
+
});
|
|
791
|
+
this.registerEventHandlers();
|
|
792
|
+
this.logger.info("Starting WebSocket connection...");
|
|
793
|
+
await this.wsClient.start({ eventDispatcher: this.eventDispatcher });
|
|
794
|
+
this.isRunning = true;
|
|
795
|
+
this.logger.info("\u2705 Feishu WebSocket connected successfully!");
|
|
820
796
|
} catch (error) {
|
|
821
|
-
this.logger.error("Failed to
|
|
822
|
-
|
|
797
|
+
this.logger.error("\u274C Failed to start WebSocket handler:", error);
|
|
798
|
+
throw error;
|
|
823
799
|
}
|
|
824
800
|
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
this.logger.info(`WebSocket closed: ${code} - ${reason.toString()}`);
|
|
840
|
-
this.stopHeartbeat();
|
|
841
|
-
if (this.isRunning) {
|
|
842
|
-
this.handleReconnect();
|
|
801
|
+
registerEventHandlers() {
|
|
802
|
+
if (!this.eventDispatcher) return;
|
|
803
|
+
this.eventDispatcher.register({
|
|
804
|
+
"im.message.receive_v1": async (event) => {
|
|
805
|
+
await this.handleMessageEvent(event);
|
|
806
|
+
},
|
|
807
|
+
"im.message.message_read_v1": (event) => {
|
|
808
|
+
this.logger.debug("Message read:", event);
|
|
809
|
+
},
|
|
810
|
+
"im.chat.member.bot.added_v1": (event) => {
|
|
811
|
+
this.logger.info("Bot added to chat:", event);
|
|
812
|
+
},
|
|
813
|
+
"im.chat.member.bot.deleted_v1": (event) => {
|
|
814
|
+
this.logger.info("Bot removed from chat:", event);
|
|
843
815
|
}
|
|
844
816
|
});
|
|
845
|
-
this.ws.on("error", (error) => {
|
|
846
|
-
this.logger.error("WebSocket error:", error);
|
|
847
|
-
});
|
|
848
|
-
this.ws.on("ping", () => {
|
|
849
|
-
this.ws?.pong();
|
|
850
|
-
});
|
|
851
|
-
}
|
|
852
|
-
/**
|
|
853
|
-
* 处理收到的消息
|
|
854
|
-
*/
|
|
855
|
-
async handleMessage(data) {
|
|
856
|
-
try {
|
|
857
|
-
const event = JSON.parse(data);
|
|
858
|
-
this.logger.debug("Received WebSocket message:", event);
|
|
859
|
-
if (event.type === "url_verification") {
|
|
860
|
-
this.logger.info("Received URL verification challenge via WebSocket");
|
|
861
|
-
return;
|
|
862
|
-
}
|
|
863
|
-
if (event.type === "event_callback" && event.event) {
|
|
864
|
-
await this.handleEvent(event.event);
|
|
865
|
-
}
|
|
866
|
-
} catch (error) {
|
|
867
|
-
this.logger.error("Error handling WebSocket message:", error);
|
|
868
|
-
}
|
|
869
817
|
}
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
*/
|
|
873
|
-
async handleEvent(event) {
|
|
874
|
-
const { message } = event;
|
|
818
|
+
async handleMessageEvent(event) {
|
|
819
|
+
const { message, sender } = event;
|
|
875
820
|
if (!message || message.message_type !== "text") {
|
|
876
821
|
this.logger.debug("Ignoring non-text message");
|
|
877
822
|
return;
|
|
878
823
|
}
|
|
879
824
|
try {
|
|
880
825
|
const content = JSON.parse(message.content);
|
|
881
|
-
const text = content.text
|
|
882
|
-
const
|
|
883
|
-
if (!
|
|
826
|
+
const text = content.text?.trim() || "";
|
|
827
|
+
const senderId = sender?.sender_id?.open_id;
|
|
828
|
+
if (!senderId) {
|
|
884
829
|
this.logger.warn("Missing sender ID");
|
|
885
830
|
return;
|
|
886
831
|
}
|
|
887
|
-
this.logger.info(
|
|
832
|
+
this.logger.info(`\u{1F4E8} Received message from ${senderId}: ${text}`);
|
|
888
833
|
const route = await this.messageRouter.route(text);
|
|
889
834
|
if (route.type === "error") {
|
|
890
835
|
await this.client.sendMessage(message.chat_id, {
|
|
891
836
|
msg_type: "text",
|
|
892
|
-
content: {
|
|
893
|
-
text: route.content
|
|
894
|
-
}
|
|
837
|
+
content: { text: route.content }
|
|
895
838
|
});
|
|
896
839
|
return;
|
|
897
840
|
}
|
|
898
841
|
if (route.type === "prompt_choice") {
|
|
899
842
|
await this.client.sendMessage(message.chat_id, {
|
|
900
843
|
msg_type: "text",
|
|
901
|
-
content: {
|
|
902
|
-
text: route.content
|
|
903
|
-
}
|
|
844
|
+
content: { text: route.content }
|
|
904
845
|
});
|
|
905
846
|
return;
|
|
906
847
|
}
|
|
907
848
|
if (route.type === "direct" && route.target) {
|
|
908
849
|
const command = this.messageRouter.extractCommand(text);
|
|
909
|
-
this.logger.info(
|
|
850
|
+
this.logger.info(`\u{1F3AF} Routing to ${route.target}: ${command}`);
|
|
851
|
+
await this.client.sendMessage(message.chat_id, {
|
|
852
|
+
msg_type: "text",
|
|
853
|
+
content: { text: `\u23F3 \u6B63\u5728\u6267\u884C: ${command}` }
|
|
854
|
+
});
|
|
910
855
|
const result = await this.commandExecutor.execute({
|
|
911
856
|
target: route.target,
|
|
912
857
|
command,
|
|
913
|
-
sender,
|
|
858
|
+
sender: senderId,
|
|
914
859
|
chatId: message.chat_id
|
|
915
860
|
});
|
|
916
861
|
await this.client.sendMessage(message.chat_id, {
|
|
917
862
|
msg_type: "text",
|
|
918
|
-
content: {
|
|
919
|
-
text: this.formatResponse(result)
|
|
920
|
-
}
|
|
863
|
+
content: { text: this.formatResponse(result) }
|
|
921
864
|
});
|
|
922
865
|
}
|
|
923
866
|
} catch (error) {
|
|
@@ -925,89 +868,13 @@ var FeishuWebSocketHandler = class {
|
|
|
925
868
|
try {
|
|
926
869
|
await this.client.sendMessage(message.chat_id, {
|
|
927
870
|
msg_type: "text",
|
|
928
|
-
content: {
|
|
929
|
-
text: `\u274C \u5904\u7406\u6D88\u606F\u65F6\u51FA\u9519: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`
|
|
930
|
-
}
|
|
871
|
+
content: { text: `\u274C \u5904\u7406\u6D88\u606F\u65F6\u51FA\u9519` }
|
|
931
872
|
});
|
|
932
873
|
} catch (sendError) {
|
|
933
874
|
this.logger.error("Failed to send error message:", sendError);
|
|
934
875
|
}
|
|
935
876
|
}
|
|
936
877
|
}
|
|
937
|
-
/**
|
|
938
|
-
* 启动心跳保活
|
|
939
|
-
*/
|
|
940
|
-
startHeartbeat() {
|
|
941
|
-
this.heartbeatInterval = setInterval(() => {
|
|
942
|
-
if (this.ws?.readyState === import_ws.WebSocket.OPEN) {
|
|
943
|
-
this.ws.ping();
|
|
944
|
-
}
|
|
945
|
-
}, 3e4);
|
|
946
|
-
}
|
|
947
|
-
/**
|
|
948
|
-
* 停止心跳
|
|
949
|
-
*/
|
|
950
|
-
stopHeartbeat() {
|
|
951
|
-
if (this.heartbeatInterval) {
|
|
952
|
-
clearInterval(this.heartbeatInterval);
|
|
953
|
-
this.heartbeatInterval = null;
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
/**
|
|
957
|
-
* 处理重连
|
|
958
|
-
*/
|
|
959
|
-
handleReconnect() {
|
|
960
|
-
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
961
|
-
this.logger.error(`Max reconnection attempts (${this.maxReconnectAttempts}) reached`);
|
|
962
|
-
return;
|
|
963
|
-
}
|
|
964
|
-
this.reconnectAttempts++;
|
|
965
|
-
const delay = this.reconnectInterval * this.reconnectAttempts;
|
|
966
|
-
this.logger.info(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
|
|
967
|
-
setTimeout(() => {
|
|
968
|
-
if (this.isRunning) {
|
|
969
|
-
this.connect();
|
|
970
|
-
}
|
|
971
|
-
}, delay);
|
|
972
|
-
}
|
|
973
|
-
/**
|
|
974
|
-
* 获取访问令牌
|
|
975
|
-
*/
|
|
976
|
-
async getAccessToken() {
|
|
977
|
-
const config = ConfigManager.getInstance().getFeishuConfig();
|
|
978
|
-
const response = await fetch(
|
|
979
|
-
`https://open.${config.domain === "lark" ? "larksuite" : "feishu"}.cn/open-apis/auth/v3/tenant_access_token/internal`,
|
|
980
|
-
{
|
|
981
|
-
method: "POST",
|
|
982
|
-
headers: { "Content-Type": "application/json" },
|
|
983
|
-
body: JSON.stringify({
|
|
984
|
-
app_id: config.appId,
|
|
985
|
-
app_secret: config.appSecret
|
|
986
|
-
})
|
|
987
|
-
}
|
|
988
|
-
);
|
|
989
|
-
const data = await response.json();
|
|
990
|
-
if (data.code !== 0) {
|
|
991
|
-
throw new Error(`Failed to get access token: ${data.msg}`);
|
|
992
|
-
}
|
|
993
|
-
return data.tenant_access_token;
|
|
994
|
-
}
|
|
995
|
-
/**
|
|
996
|
-
* 提取目标IDE
|
|
997
|
-
*/
|
|
998
|
-
extractTargetIDE(text) {
|
|
999
|
-
const match = text.match(/@(vscode|cursor|trae|antigravity|kiro|opencode|claude)/i);
|
|
1000
|
-
return match ? match[1].toLowerCase() : "auto";
|
|
1001
|
-
}
|
|
1002
|
-
/**
|
|
1003
|
-
* 提取命令
|
|
1004
|
-
*/
|
|
1005
|
-
extractCommand(text) {
|
|
1006
|
-
return text.replace(/@(vscode|cursor|trae|antigravity|kiro|opencode|claude)\s*/i, "").trim();
|
|
1007
|
-
}
|
|
1008
|
-
/**
|
|
1009
|
-
* 格式化响应
|
|
1010
|
-
*/
|
|
1011
878
|
formatResponse(result) {
|
|
1012
879
|
if (result.success) {
|
|
1013
880
|
const output = typeof result.output === "string" ? result.output.substring(0, 2e3) : JSON.stringify(result.output).substring(0, 2e3);
|
|
@@ -1021,6 +888,14 @@ ${output}`;
|
|
|
1021
888
|
${error.substring(0, 2e3)}`;
|
|
1022
889
|
}
|
|
1023
890
|
}
|
|
891
|
+
async stop() {
|
|
892
|
+
this.isRunning = false;
|
|
893
|
+
if (this.wsClient) {
|
|
894
|
+
this.wsClient.close();
|
|
895
|
+
this.wsClient = null;
|
|
896
|
+
}
|
|
897
|
+
this.logger.info("WebSocket handler stopped");
|
|
898
|
+
}
|
|
1024
899
|
};
|
|
1025
900
|
|
|
1026
901
|
// src/feishu/client.ts
|
|
@@ -3445,18 +3320,42 @@ var ReverseChannel = class extends import_events.EventEmitter {
|
|
|
3445
3320
|
};
|
|
3446
3321
|
|
|
3447
3322
|
// src/skills/builtin/feishu.ts
|
|
3323
|
+
init_config();
|
|
3448
3324
|
var FeishuSkills = class {
|
|
3449
3325
|
constructor(client) {
|
|
3326
|
+
this.accessToken = null;
|
|
3327
|
+
this.tokenExpireTime = 0;
|
|
3450
3328
|
this.client = client;
|
|
3329
|
+
const config = ConfigManager.getInstance().getFeishuConfig();
|
|
3330
|
+
this.baseUrl = config.domain === "lark" ? "https://open.larksuite.com/open-apis" : "https://open.feishu.cn/open-apis";
|
|
3331
|
+
}
|
|
3332
|
+
async getAccessToken() {
|
|
3333
|
+
const now = Date.now();
|
|
3334
|
+
if (this.accessToken && now < this.tokenExpireTime) {
|
|
3335
|
+
return this.accessToken;
|
|
3336
|
+
}
|
|
3337
|
+
const config = ConfigManager.getInstance().getFeishuConfig();
|
|
3338
|
+
const response = await fetch(`${this.baseUrl}/auth/v3/tenant_access_token/internal`, {
|
|
3339
|
+
method: "POST",
|
|
3340
|
+
headers: { "Content-Type": "application/json" },
|
|
3341
|
+
body: JSON.stringify({
|
|
3342
|
+
app_id: config.appId,
|
|
3343
|
+
app_secret: config.appSecret
|
|
3344
|
+
})
|
|
3345
|
+
});
|
|
3346
|
+
const data = await response.json();
|
|
3347
|
+
if (data.code !== 0) {
|
|
3348
|
+
throw new Error(`Failed to get access token: ${data.msg}`);
|
|
3349
|
+
}
|
|
3350
|
+
this.accessToken = data.tenant_access_token;
|
|
3351
|
+
this.tokenExpireTime = now + (data.expire - 300) * 1e3;
|
|
3352
|
+
return this.accessToken;
|
|
3451
3353
|
}
|
|
3452
|
-
/**
|
|
3453
|
-
* 发送消息技能
|
|
3454
|
-
*/
|
|
3455
3354
|
sendMessageSkill() {
|
|
3456
3355
|
return {
|
|
3457
3356
|
name: "feishu.sendMessage",
|
|
3458
3357
|
description: "\u53D1\u9001\u6D88\u606F\u5230\u6307\u5B9A\u4F1A\u8BDD",
|
|
3459
|
-
trigger:
|
|
3358
|
+
trigger: /发送消息|发信息|发消息/,
|
|
3460
3359
|
riskLevel: "low",
|
|
3461
3360
|
examples: [
|
|
3462
3361
|
"\u53D1\u9001\u6D88\u606F\u7ED9\u5F20\u4E09\uFF1A\u4E0B\u5348\u5F00\u4F1A",
|
|
@@ -3464,32 +3363,23 @@ var FeishuSkills = class {
|
|
|
3464
3363
|
],
|
|
3465
3364
|
execute: async (context) => {
|
|
3466
3365
|
try {
|
|
3467
|
-
const {
|
|
3366
|
+
const { content } = this.parseMessageArgs(context.message);
|
|
3468
3367
|
await this.client.sendMessage(context.chatId, {
|
|
3469
3368
|
msg_type: "text",
|
|
3470
3369
|
content: { text: content }
|
|
3471
3370
|
});
|
|
3472
|
-
return {
|
|
3473
|
-
success: true,
|
|
3474
|
-
content: "\u2705 \u6D88\u606F\u5DF2\u53D1\u9001"
|
|
3475
|
-
};
|
|
3371
|
+
return { success: true, content: "\u2705 \u6D88\u606F\u5DF2\u53D1\u9001" };
|
|
3476
3372
|
} catch (error) {
|
|
3477
|
-
return {
|
|
3478
|
-
success: false,
|
|
3479
|
-
content: `\u274C \u53D1\u9001\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`
|
|
3480
|
-
};
|
|
3373
|
+
return { success: false, content: `\u274C \u53D1\u9001\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}` };
|
|
3481
3374
|
}
|
|
3482
3375
|
}
|
|
3483
3376
|
};
|
|
3484
3377
|
}
|
|
3485
|
-
/**
|
|
3486
|
-
* 创建日程技能
|
|
3487
|
-
*/
|
|
3488
3378
|
createEventSkill() {
|
|
3489
3379
|
return {
|
|
3490
3380
|
name: "feishu.createEvent",
|
|
3491
3381
|
description: "\u5728\u98DE\u4E66\u65E5\u5386\u521B\u5EFA\u65E5\u7A0B",
|
|
3492
|
-
trigger:
|
|
3382
|
+
trigger: /创建日程|创建会议|添加日程|新建日程/,
|
|
3493
3383
|
riskLevel: "medium",
|
|
3494
3384
|
examples: [
|
|
3495
3385
|
"\u521B\u5EFA\u65E5\u7A0B\uFF1A\u660E\u5929\u4E0B\u53483\u70B9\u56E2\u961F\u5468\u4F1A",
|
|
@@ -3497,36 +3387,57 @@ var FeishuSkills = class {
|
|
|
3497
3387
|
],
|
|
3498
3388
|
execute: async (context) => {
|
|
3499
3389
|
try {
|
|
3500
|
-
const { title, startTime,
|
|
3390
|
+
const { title, startTime, endTime, description } = this.parseEventArgs(context.message);
|
|
3391
|
+
const token = await this.getAccessToken();
|
|
3392
|
+
const response = await fetch(`${this.baseUrl}/calendar/v4/calendars/primary/events`, {
|
|
3393
|
+
method: "POST",
|
|
3394
|
+
headers: {
|
|
3395
|
+
"Authorization": `Bearer ${token}`,
|
|
3396
|
+
"Content-Type": "application/json"
|
|
3397
|
+
},
|
|
3398
|
+
body: JSON.stringify({
|
|
3399
|
+
summary: title,
|
|
3400
|
+
description,
|
|
3401
|
+
start_time: {
|
|
3402
|
+
timestamp: Math.floor(startTime / 1e3).toString(),
|
|
3403
|
+
timezone: "Asia/Shanghai"
|
|
3404
|
+
},
|
|
3405
|
+
end_time: {
|
|
3406
|
+
timestamp: Math.floor(endTime / 1e3).toString(),
|
|
3407
|
+
timezone: "Asia/Shanghai"
|
|
3408
|
+
},
|
|
3409
|
+
attendee_ability: "can_see_others",
|
|
3410
|
+
free_busy_status: "busy"
|
|
3411
|
+
})
|
|
3412
|
+
});
|
|
3413
|
+
const data = await response.json();
|
|
3414
|
+
if (data.code !== 0) {
|
|
3415
|
+
throw new Error(data.msg || "\u521B\u5EFA\u65E5\u7A0B\u5931\u8D25");
|
|
3416
|
+
}
|
|
3417
|
+
const eventLink = data.data?.event_link || "";
|
|
3501
3418
|
return {
|
|
3502
3419
|
success: true,
|
|
3503
3420
|
content: `\u2705 \u65E5\u7A0B\u5DF2\u521B\u5EFA
|
|
3504
3421
|
|
|
3505
|
-
\
|
|
3506
|
-
\
|
|
3507
|
-
|
|
3422
|
+
\u{1F4C5} ${title}
|
|
3423
|
+
\u{1F550} ${this.formatDateTime(startTime)} - ${this.formatDateTime(endTime)}
|
|
3424
|
+
|
|
3425
|
+
\u{1F517} ${eventLink}`,
|
|
3508
3426
|
actions: [
|
|
3509
|
-
{ label: "\u67E5\u770B\u65E5\u7A0B", action: "view_calendar" }
|
|
3510
|
-
{ label: "\u7F16\u8F91", action: "edit_event" }
|
|
3427
|
+
{ label: "\u67E5\u770B\u65E5\u7A0B", action: "view_calendar", url: eventLink }
|
|
3511
3428
|
]
|
|
3512
3429
|
};
|
|
3513
3430
|
} catch (error) {
|
|
3514
|
-
return {
|
|
3515
|
-
success: false,
|
|
3516
|
-
content: `\u274C \u521B\u5EFA\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`
|
|
3517
|
-
};
|
|
3431
|
+
return { success: false, content: `\u274C \u521B\u5EFA\u65E5\u7A0B\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}` };
|
|
3518
3432
|
}
|
|
3519
3433
|
}
|
|
3520
3434
|
};
|
|
3521
3435
|
}
|
|
3522
|
-
/**
|
|
3523
|
-
* 创建文档技能
|
|
3524
|
-
*/
|
|
3525
3436
|
createDocSkill() {
|
|
3526
3437
|
return {
|
|
3527
3438
|
name: "feishu.createDoc",
|
|
3528
3439
|
description: "\u521B\u5EFA\u98DE\u4E66\u4E91\u6587\u6863",
|
|
3529
|
-
trigger:
|
|
3440
|
+
trigger: /创建文档|新建文档|生成文档/,
|
|
3530
3441
|
riskLevel: "low",
|
|
3531
3442
|
examples: [
|
|
3532
3443
|
"\u521B\u5EFA\u6587\u6863\uFF1A\u9879\u76EE\u9700\u6C42\u6587\u6863",
|
|
@@ -3534,89 +3445,114 @@ var FeishuSkills = class {
|
|
|
3534
3445
|
],
|
|
3535
3446
|
execute: async (context) => {
|
|
3536
3447
|
try {
|
|
3537
|
-
const { title
|
|
3448
|
+
const { title } = this.parseDocArgs(context.message);
|
|
3449
|
+
const token = await this.getAccessToken();
|
|
3450
|
+
const response = await fetch(`${this.baseUrl}/docx/v1/documents`, {
|
|
3451
|
+
method: "POST",
|
|
3452
|
+
headers: {
|
|
3453
|
+
"Authorization": `Bearer ${token}`,
|
|
3454
|
+
"Content-Type": "application/json"
|
|
3455
|
+
},
|
|
3456
|
+
body: JSON.stringify({ title })
|
|
3457
|
+
});
|
|
3458
|
+
const data = await response.json();
|
|
3459
|
+
if (data.code !== 0) {
|
|
3460
|
+
throw new Error(data.msg || "\u521B\u5EFA\u6587\u6863\u5931\u8D25");
|
|
3461
|
+
}
|
|
3462
|
+
const documentId = data.data?.document?.document_id;
|
|
3463
|
+
const domain = ConfigManager.getInstance().getFeishuConfig().domain === "lark" ? "larksuite.com" : "feishu.cn";
|
|
3464
|
+
const documentUrl = `https://${domain}/docx/${documentId}`;
|
|
3538
3465
|
return {
|
|
3539
3466
|
success: true,
|
|
3540
3467
|
content: `\u2705 \u6587\u6863\u5DF2\u521B\u5EFA
|
|
3541
3468
|
|
|
3542
|
-
\
|
|
3543
|
-
|
|
3469
|
+
\u{1F4C4} ${title}
|
|
3470
|
+
|
|
3471
|
+
\u{1F517} ${documentUrl}`,
|
|
3544
3472
|
actions: [
|
|
3545
|
-
{ label: "\u6253\u5F00\u6587\u6863", action: "open_doc" }
|
|
3546
|
-
{ label: "\u5206\u4EAB", action: "share_doc" }
|
|
3473
|
+
{ label: "\u6253\u5F00\u6587\u6863", action: "open_doc", url: documentUrl }
|
|
3547
3474
|
]
|
|
3548
3475
|
};
|
|
3549
3476
|
} catch (error) {
|
|
3550
|
-
return {
|
|
3551
|
-
success: false,
|
|
3552
|
-
content: `\u274C \u521B\u5EFA\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`
|
|
3553
|
-
};
|
|
3477
|
+
return { success: false, content: `\u274C \u521B\u5EFA\u6587\u6863\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}` };
|
|
3554
3478
|
}
|
|
3555
3479
|
}
|
|
3556
3480
|
};
|
|
3557
3481
|
}
|
|
3558
|
-
/**
|
|
3559
|
-
* 上传文件技能
|
|
3560
|
-
*/
|
|
3561
3482
|
uploadFileSkill() {
|
|
3562
3483
|
return {
|
|
3563
3484
|
name: "feishu.uploadFile",
|
|
3564
3485
|
description: "\u4E0A\u4F20\u6587\u4EF6\u5230\u98DE\u4E66",
|
|
3565
|
-
trigger:
|
|
3486
|
+
trigger: /上传文件|发送文件|传文件/,
|
|
3566
3487
|
riskLevel: "low",
|
|
3567
3488
|
execute: async (context) => {
|
|
3568
|
-
return {
|
|
3569
|
-
success: true,
|
|
3570
|
-
content: "\u{1F4CE} \u6587\u4EF6\u4E0A\u4F20\u529F\u80FD\u5F00\u53D1\u4E2D"
|
|
3571
|
-
};
|
|
3489
|
+
return { success: true, content: "\u{1F4CE} \u6587\u4EF6\u4E0A\u4F20\u529F\u80FD\u9700\u8981\u6587\u4EF6\u8DEF\u5F84\uFF0C\u8BF7\u4F7F\u7528\uFF1A\u4E0A\u4F20\u6587\u4EF6 /path/to/file" };
|
|
3572
3490
|
}
|
|
3573
3491
|
};
|
|
3574
3492
|
}
|
|
3575
|
-
/**
|
|
3576
|
-
* 创建群公告技能
|
|
3577
|
-
*/
|
|
3578
3493
|
createAnnouncementSkill() {
|
|
3579
3494
|
return {
|
|
3580
3495
|
name: "feishu.createAnnouncement",
|
|
3581
3496
|
description: "\u53D1\u9001\u7FA4\u516C\u544A",
|
|
3582
|
-
trigger:
|
|
3497
|
+
trigger: /发送公告|群公告|发布公告|发公告/,
|
|
3583
3498
|
riskLevel: "medium",
|
|
3584
3499
|
requireConfirm: true,
|
|
3585
3500
|
execute: async (context) => {
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3501
|
+
try {
|
|
3502
|
+
const { title, content } = this.parseAnnouncementArgs(context.message);
|
|
3503
|
+
await this.client.sendMessage(context.chatId, {
|
|
3504
|
+
msg_type: "text",
|
|
3505
|
+
content: { text: `\u{1F4E2} **${title}**
|
|
3506
|
+
|
|
3507
|
+
${content}` }
|
|
3508
|
+
});
|
|
3509
|
+
return { success: true, content: "\u2705 \u516C\u544A\u5DF2\u53D1\u9001" };
|
|
3510
|
+
} catch (error) {
|
|
3511
|
+
return { success: false, content: `\u274C \u53D1\u5E03\u516C\u544A\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}` };
|
|
3512
|
+
}
|
|
3590
3513
|
}
|
|
3591
3514
|
};
|
|
3592
3515
|
}
|
|
3593
|
-
// 辅助方法
|
|
3594
3516
|
parseMessageArgs(message) {
|
|
3595
|
-
const match = message.match(/给(.+?)
|
|
3596
|
-
if (match) {
|
|
3597
|
-
return { target: match[1], content: match[2] };
|
|
3598
|
-
}
|
|
3517
|
+
const match = message.match(/给(.+?)[::]\s*(.+)/);
|
|
3518
|
+
if (match) return { target: match[1], content: match[2] };
|
|
3599
3519
|
return { target: "current", content: message };
|
|
3600
3520
|
}
|
|
3601
3521
|
parseEventArgs(message) {
|
|
3602
|
-
const
|
|
3603
|
-
const
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3522
|
+
const now = /* @__PURE__ */ new Date();
|
|
3523
|
+
const titleMatch = message.match(/日程[::]\s*(.+?)(?=(明天|今天|后天|时间|地点|$))/i);
|
|
3524
|
+
const title = titleMatch ? titleMatch[1].trim() : "\u65B0\u65E5\u7A0B";
|
|
3525
|
+
const timeMatch = message.match(/(明天|今天|后天)?\s*(上午|下午|晚上)?\s*(\d{1,2})[:点时]?(\d{0,2})?/);
|
|
3526
|
+
let startTime = new Date(now.getTime() + 60 * 60 * 1e3);
|
|
3527
|
+
if (timeMatch) {
|
|
3528
|
+
const day = timeMatch[1];
|
|
3529
|
+
const period = timeMatch[2];
|
|
3530
|
+
let hour = parseInt(timeMatch[3]);
|
|
3531
|
+
const minute = parseInt(timeMatch[4]) || 0;
|
|
3532
|
+
if (day === "\u660E\u5929") startTime.setDate(startTime.getDate() + 1);
|
|
3533
|
+
else if (day === "\u540E\u5929") startTime.setDate(startTime.getDate() + 2);
|
|
3534
|
+
if (period === "\u4E0B\u5348" && hour < 12) hour += 12;
|
|
3535
|
+
if (period === "\u665A\u4E0A" && hour < 12) hour += 12;
|
|
3536
|
+
startTime.setHours(hour, minute, 0, 0);
|
|
3537
|
+
}
|
|
3538
|
+
const endTime = new Date(startTime.getTime() + 60 * 60 * 1e3);
|
|
3539
|
+
return { title, startTime: startTime.getTime(), endTime: endTime.getTime(), description: "" };
|
|
3609
3540
|
}
|
|
3610
3541
|
parseDocArgs(message) {
|
|
3611
|
-
const match = message.match(/文档[::]\s*(.+?)(
|
|
3612
|
-
return {
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3542
|
+
const match = message.match(/文档[::]\s*(.+?)(?=(内容|文件夹|$))/i);
|
|
3543
|
+
return { title: match ? match[1].trim() : "\u672A\u547D\u540D\u6587\u6863" };
|
|
3544
|
+
}
|
|
3545
|
+
parseAnnouncementArgs(message) {
|
|
3546
|
+
const titleMatch = message.match(/公告[::]\s*(.+?)(?=内容|$)/i);
|
|
3547
|
+
const title = titleMatch ? titleMatch[1].trim() : "\u7FA4\u516C\u544A";
|
|
3548
|
+
const contentMatch = message.match(/内容[::]\s*(.+)/);
|
|
3549
|
+
const content = contentMatch ? contentMatch[1] : "";
|
|
3550
|
+
return { title, content };
|
|
3551
|
+
}
|
|
3552
|
+
formatDateTime(timestamp) {
|
|
3553
|
+
const date = new Date(timestamp);
|
|
3554
|
+
return `${date.getMonth() + 1}\u6708${date.getDate()}\u65E5 ${date.getHours()}:${date.getMinutes().toString().padStart(2, "0")}`;
|
|
3616
3555
|
}
|
|
3617
|
-
/**
|
|
3618
|
-
* 获取所有飞书技能
|
|
3619
|
-
*/
|
|
3620
3556
|
getAllSkills() {
|
|
3621
3557
|
return [
|
|
3622
3558
|
this.sendMessageSkill(),
|