feishu-bridge 1.0.6 → 1.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/dist/cli/index.js +208 -239
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +208 -239
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.js +207 -239
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +207 -239
- 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,17 +758,14 @@ 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);
|
|
@@ -784,227 +778,141 @@ var FeishuWebSocketHandler = class {
|
|
|
784
778
|
this.logger.warn("WebSocket handler already running");
|
|
785
779
|
return;
|
|
786
780
|
}
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
781
|
+
const config = ConfigManager.getInstance().getFeishuConfig();
|
|
782
|
+
try {
|
|
783
|
+
this.logger.info("Creating Feishu WebSocket client...");
|
|
784
|
+
this.wsClient = new Lark.WSClient({
|
|
785
|
+
appId: config.appId,
|
|
786
|
+
appSecret: config.appSecret,
|
|
787
|
+
domain: config.domain === "lark" ? Lark.Domain.Lark : Lark.Domain.Feishu,
|
|
788
|
+
loggerLevel: Lark.LoggerLevel.info
|
|
789
|
+
});
|
|
790
|
+
this.eventDispatcher = new Lark.EventDispatcher({
|
|
791
|
+
encryptKey: config.encryptKey,
|
|
792
|
+
verificationToken: config.verificationToken
|
|
793
|
+
});
|
|
794
|
+
this.registerEventHandlers();
|
|
795
|
+
this.wsClient.on("message", async (event) => {
|
|
796
|
+
await this.handleEvent(event);
|
|
797
|
+
});
|
|
798
|
+
this.wsClient.on("error", (error) => {
|
|
799
|
+
this.logger.error("WebSocket error:", error);
|
|
800
|
+
});
|
|
801
|
+
this.wsClient.on("close", () => {
|
|
802
|
+
this.logger.info("WebSocket connection closed");
|
|
803
|
+
if (this.isRunning) {
|
|
804
|
+
this.logger.info("Attempting to reconnect...");
|
|
805
|
+
setTimeout(() => this.start(), 5e3);
|
|
806
|
+
}
|
|
807
|
+
});
|
|
808
|
+
this.isRunning = true;
|
|
809
|
+
this.logger.info("Feishu WebSocket handler started successfully");
|
|
810
|
+
} catch (error) {
|
|
811
|
+
this.logger.error("Failed to start WebSocket handler:", error);
|
|
812
|
+
throw error;
|
|
813
|
+
}
|
|
790
814
|
}
|
|
791
815
|
/**
|
|
792
816
|
* 停止WebSocket连接
|
|
793
817
|
*/
|
|
794
818
|
async stop() {
|
|
795
819
|
this.isRunning = false;
|
|
796
|
-
if (this.
|
|
797
|
-
|
|
798
|
-
this.
|
|
799
|
-
}
|
|
800
|
-
if (this.ws) {
|
|
801
|
-
this.ws.close();
|
|
802
|
-
this.ws = null;
|
|
820
|
+
if (this.wsClient) {
|
|
821
|
+
this.wsClient.close();
|
|
822
|
+
this.wsClient = null;
|
|
803
823
|
}
|
|
804
824
|
this.logger.info("WebSocket handler stopped");
|
|
805
825
|
}
|
|
806
826
|
/**
|
|
807
|
-
*
|
|
808
|
-
*/
|
|
809
|
-
async connect() {
|
|
810
|
-
try {
|
|
811
|
-
const config = ConfigManager.getInstance().getFeishuConfig();
|
|
812
|
-
const wsUrl = config.domain === "feishu" ? "wss://ws.feishu.cn/passport/" : "wss://ws.larksuite.com/passport/";
|
|
813
|
-
this.logger.info(`Connecting to WebSocket: ${wsUrl}`);
|
|
814
|
-
this.ws = new import_ws.WebSocket(wsUrl, {
|
|
815
|
-
headers: {
|
|
816
|
-
"Authorization": `Bearer ${await this.getAccessToken()}`
|
|
817
|
-
}
|
|
818
|
-
});
|
|
819
|
-
this.setupWebSocketHandlers();
|
|
820
|
-
} catch (error) {
|
|
821
|
-
this.logger.error("Failed to connect WebSocket:", error);
|
|
822
|
-
this.handleReconnect();
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
/**
|
|
826
|
-
* 设置WebSocket事件处理器
|
|
827
|
+
* 注册事件处理器
|
|
827
828
|
*/
|
|
828
|
-
|
|
829
|
-
if (!this.
|
|
830
|
-
this.
|
|
831
|
-
this.
|
|
832
|
-
this.reconnectAttempts = 0;
|
|
833
|
-
this.startHeartbeat();
|
|
834
|
-
});
|
|
835
|
-
this.ws.on("message", (data) => {
|
|
836
|
-
this.handleMessage(data.toString());
|
|
829
|
+
registerEventHandlers() {
|
|
830
|
+
if (!this.eventDispatcher) return;
|
|
831
|
+
this.eventDispatcher.on("im.message.receive_v1", async (event) => {
|
|
832
|
+
await this.handleMessageEvent(event);
|
|
837
833
|
});
|
|
838
|
-
this.
|
|
839
|
-
this.logger.info(
|
|
840
|
-
this.stopHeartbeat();
|
|
841
|
-
if (this.isRunning) {
|
|
842
|
-
this.handleReconnect();
|
|
843
|
-
}
|
|
834
|
+
this.eventDispatcher.on("im.chat.member.bot.added_v1", (event) => {
|
|
835
|
+
this.logger.info("Bot added to chat:", event);
|
|
844
836
|
});
|
|
845
|
-
this.
|
|
846
|
-
this.logger.
|
|
847
|
-
});
|
|
848
|
-
this.ws.on("ping", () => {
|
|
849
|
-
this.ws?.pong();
|
|
837
|
+
this.eventDispatcher.on("im.chat.member.bot.deleted_v1", (event) => {
|
|
838
|
+
this.logger.info("Bot removed from chat:", event);
|
|
850
839
|
});
|
|
851
840
|
}
|
|
852
841
|
/**
|
|
853
|
-
*
|
|
842
|
+
* 处理事件
|
|
854
843
|
*/
|
|
855
|
-
async
|
|
844
|
+
async handleEvent(event) {
|
|
856
845
|
try {
|
|
857
|
-
|
|
858
|
-
|
|
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);
|
|
846
|
+
if (this.eventDispatcher) {
|
|
847
|
+
await this.eventDispatcher.dispatch(event);
|
|
865
848
|
}
|
|
866
849
|
} catch (error) {
|
|
867
|
-
this.logger.error("Error
|
|
850
|
+
this.logger.error("Error dispatching event:", error);
|
|
868
851
|
}
|
|
869
852
|
}
|
|
870
853
|
/**
|
|
871
|
-
*
|
|
854
|
+
* 处理消息事件
|
|
872
855
|
*/
|
|
873
|
-
async
|
|
874
|
-
const { message } = event;
|
|
856
|
+
async handleMessageEvent(event) {
|
|
857
|
+
const { message, sender } = event;
|
|
875
858
|
if (!message || message.message_type !== "text") {
|
|
876
859
|
this.logger.debug("Ignoring non-text message");
|
|
877
860
|
return;
|
|
878
861
|
}
|
|
879
862
|
try {
|
|
880
863
|
const content = JSON.parse(message.content);
|
|
881
|
-
const text = content.text
|
|
882
|
-
const
|
|
883
|
-
if (!
|
|
864
|
+
const text = content.text?.trim() || "";
|
|
865
|
+
const senderId = sender?.sender_id?.open_id;
|
|
866
|
+
if (!senderId) {
|
|
884
867
|
this.logger.warn("Missing sender ID");
|
|
885
868
|
return;
|
|
886
869
|
}
|
|
887
|
-
this.logger.info(`Received message from ${
|
|
870
|
+
this.logger.info(`Received message from ${senderId}: ${text}`);
|
|
888
871
|
const route = await this.messageRouter.route(text);
|
|
889
872
|
if (route.type === "error") {
|
|
890
873
|
await this.client.sendMessage(message.chat_id, {
|
|
891
874
|
msg_type: "text",
|
|
892
|
-
content: {
|
|
893
|
-
text: route.content
|
|
894
|
-
}
|
|
875
|
+
content: { text: route.content }
|
|
895
876
|
});
|
|
896
877
|
return;
|
|
897
878
|
}
|
|
898
879
|
if (route.type === "prompt_choice") {
|
|
899
880
|
await this.client.sendMessage(message.chat_id, {
|
|
900
881
|
msg_type: "text",
|
|
901
|
-
content: {
|
|
902
|
-
text: route.content
|
|
903
|
-
}
|
|
882
|
+
content: { text: route.content }
|
|
904
883
|
});
|
|
905
884
|
return;
|
|
906
885
|
}
|
|
907
886
|
if (route.type === "direct" && route.target) {
|
|
908
887
|
const command = this.messageRouter.extractCommand(text);
|
|
909
|
-
this.logger.info(`
|
|
888
|
+
this.logger.info(`Executing command for ${route.target}: ${command}`);
|
|
889
|
+
await this.client.sendMessage(message.chat_id, {
|
|
890
|
+
msg_type: "text",
|
|
891
|
+
content: { text: `\u23F3 \u6B63\u5728\u6267\u884C: ${command}` }
|
|
892
|
+
});
|
|
910
893
|
const result = await this.commandExecutor.execute({
|
|
911
894
|
target: route.target,
|
|
912
895
|
command,
|
|
913
|
-
sender,
|
|
896
|
+
sender: senderId,
|
|
914
897
|
chatId: message.chat_id
|
|
915
898
|
});
|
|
916
899
|
await this.client.sendMessage(message.chat_id, {
|
|
917
900
|
msg_type: "text",
|
|
918
|
-
content: {
|
|
919
|
-
text: this.formatResponse(result)
|
|
920
|
-
}
|
|
901
|
+
content: { text: this.formatResponse(result) }
|
|
921
902
|
});
|
|
922
903
|
}
|
|
923
904
|
} catch (error) {
|
|
924
|
-
this.logger.error("Error processing event:", error);
|
|
905
|
+
this.logger.error("Error processing message event:", error);
|
|
925
906
|
try {
|
|
926
907
|
await this.client.sendMessage(message.chat_id, {
|
|
927
908
|
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
|
-
}
|
|
909
|
+
content: { text: `\u274C \u5904\u7406\u6D88\u606F\u65F6\u51FA\u9519: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}` }
|
|
931
910
|
});
|
|
932
911
|
} catch (sendError) {
|
|
933
912
|
this.logger.error("Failed to send error message:", sendError);
|
|
934
913
|
}
|
|
935
914
|
}
|
|
936
915
|
}
|
|
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
916
|
/**
|
|
1009
917
|
* 格式化响应
|
|
1010
918
|
*/
|
|
@@ -3445,18 +3353,42 @@ var ReverseChannel = class extends import_events.EventEmitter {
|
|
|
3445
3353
|
};
|
|
3446
3354
|
|
|
3447
3355
|
// src/skills/builtin/feishu.ts
|
|
3356
|
+
init_config();
|
|
3448
3357
|
var FeishuSkills = class {
|
|
3449
3358
|
constructor(client) {
|
|
3359
|
+
this.accessToken = null;
|
|
3360
|
+
this.tokenExpireTime = 0;
|
|
3450
3361
|
this.client = client;
|
|
3362
|
+
const config = ConfigManager.getInstance().getFeishuConfig();
|
|
3363
|
+
this.baseUrl = config.domain === "lark" ? "https://open.larksuite.com/open-apis" : "https://open.feishu.cn/open-apis";
|
|
3364
|
+
}
|
|
3365
|
+
async getAccessToken() {
|
|
3366
|
+
const now = Date.now();
|
|
3367
|
+
if (this.accessToken && now < this.tokenExpireTime) {
|
|
3368
|
+
return this.accessToken;
|
|
3369
|
+
}
|
|
3370
|
+
const config = ConfigManager.getInstance().getFeishuConfig();
|
|
3371
|
+
const response = await fetch(`${this.baseUrl}/auth/v3/tenant_access_token/internal`, {
|
|
3372
|
+
method: "POST",
|
|
3373
|
+
headers: { "Content-Type": "application/json" },
|
|
3374
|
+
body: JSON.stringify({
|
|
3375
|
+
app_id: config.appId,
|
|
3376
|
+
app_secret: config.appSecret
|
|
3377
|
+
})
|
|
3378
|
+
});
|
|
3379
|
+
const data = await response.json();
|
|
3380
|
+
if (data.code !== 0) {
|
|
3381
|
+
throw new Error(`Failed to get access token: ${data.msg}`);
|
|
3382
|
+
}
|
|
3383
|
+
this.accessToken = data.tenant_access_token;
|
|
3384
|
+
this.tokenExpireTime = now + (data.expire - 300) * 1e3;
|
|
3385
|
+
return this.accessToken;
|
|
3451
3386
|
}
|
|
3452
|
-
/**
|
|
3453
|
-
* 发送消息技能
|
|
3454
|
-
*/
|
|
3455
3387
|
sendMessageSkill() {
|
|
3456
3388
|
return {
|
|
3457
3389
|
name: "feishu.sendMessage",
|
|
3458
3390
|
description: "\u53D1\u9001\u6D88\u606F\u5230\u6307\u5B9A\u4F1A\u8BDD",
|
|
3459
|
-
trigger:
|
|
3391
|
+
trigger: /发送消息|发信息|发消息/,
|
|
3460
3392
|
riskLevel: "low",
|
|
3461
3393
|
examples: [
|
|
3462
3394
|
"\u53D1\u9001\u6D88\u606F\u7ED9\u5F20\u4E09\uFF1A\u4E0B\u5348\u5F00\u4F1A",
|
|
@@ -3464,32 +3396,23 @@ var FeishuSkills = class {
|
|
|
3464
3396
|
],
|
|
3465
3397
|
execute: async (context) => {
|
|
3466
3398
|
try {
|
|
3467
|
-
const {
|
|
3399
|
+
const { content } = this.parseMessageArgs(context.message);
|
|
3468
3400
|
await this.client.sendMessage(context.chatId, {
|
|
3469
3401
|
msg_type: "text",
|
|
3470
3402
|
content: { text: content }
|
|
3471
3403
|
});
|
|
3472
|
-
return {
|
|
3473
|
-
success: true,
|
|
3474
|
-
content: "\u2705 \u6D88\u606F\u5DF2\u53D1\u9001"
|
|
3475
|
-
};
|
|
3404
|
+
return { success: true, content: "\u2705 \u6D88\u606F\u5DF2\u53D1\u9001" };
|
|
3476
3405
|
} catch (error) {
|
|
3477
|
-
return {
|
|
3478
|
-
success: false,
|
|
3479
|
-
content: `\u274C \u53D1\u9001\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`
|
|
3480
|
-
};
|
|
3406
|
+
return { success: false, content: `\u274C \u53D1\u9001\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}` };
|
|
3481
3407
|
}
|
|
3482
3408
|
}
|
|
3483
3409
|
};
|
|
3484
3410
|
}
|
|
3485
|
-
/**
|
|
3486
|
-
* 创建日程技能
|
|
3487
|
-
*/
|
|
3488
3411
|
createEventSkill() {
|
|
3489
3412
|
return {
|
|
3490
3413
|
name: "feishu.createEvent",
|
|
3491
3414
|
description: "\u5728\u98DE\u4E66\u65E5\u5386\u521B\u5EFA\u65E5\u7A0B",
|
|
3492
|
-
trigger:
|
|
3415
|
+
trigger: /创建日程|创建会议|添加日程|新建日程/,
|
|
3493
3416
|
riskLevel: "medium",
|
|
3494
3417
|
examples: [
|
|
3495
3418
|
"\u521B\u5EFA\u65E5\u7A0B\uFF1A\u660E\u5929\u4E0B\u53483\u70B9\u56E2\u961F\u5468\u4F1A",
|
|
@@ -3497,36 +3420,57 @@ var FeishuSkills = class {
|
|
|
3497
3420
|
],
|
|
3498
3421
|
execute: async (context) => {
|
|
3499
3422
|
try {
|
|
3500
|
-
const { title, startTime,
|
|
3423
|
+
const { title, startTime, endTime, description } = this.parseEventArgs(context.message);
|
|
3424
|
+
const token = await this.getAccessToken();
|
|
3425
|
+
const response = await fetch(`${this.baseUrl}/calendar/v4/calendars/primary/events`, {
|
|
3426
|
+
method: "POST",
|
|
3427
|
+
headers: {
|
|
3428
|
+
"Authorization": `Bearer ${token}`,
|
|
3429
|
+
"Content-Type": "application/json"
|
|
3430
|
+
},
|
|
3431
|
+
body: JSON.stringify({
|
|
3432
|
+
summary: title,
|
|
3433
|
+
description,
|
|
3434
|
+
start_time: {
|
|
3435
|
+
timestamp: Math.floor(startTime / 1e3).toString(),
|
|
3436
|
+
timezone: "Asia/Shanghai"
|
|
3437
|
+
},
|
|
3438
|
+
end_time: {
|
|
3439
|
+
timestamp: Math.floor(endTime / 1e3).toString(),
|
|
3440
|
+
timezone: "Asia/Shanghai"
|
|
3441
|
+
},
|
|
3442
|
+
attendee_ability: "can_see_others",
|
|
3443
|
+
free_busy_status: "busy"
|
|
3444
|
+
})
|
|
3445
|
+
});
|
|
3446
|
+
const data = await response.json();
|
|
3447
|
+
if (data.code !== 0) {
|
|
3448
|
+
throw new Error(data.msg || "\u521B\u5EFA\u65E5\u7A0B\u5931\u8D25");
|
|
3449
|
+
}
|
|
3450
|
+
const eventLink = data.data?.event_link || "";
|
|
3501
3451
|
return {
|
|
3502
3452
|
success: true,
|
|
3503
3453
|
content: `\u2705 \u65E5\u7A0B\u5DF2\u521B\u5EFA
|
|
3504
3454
|
|
|
3505
|
-
\
|
|
3506
|
-
\
|
|
3507
|
-
|
|
3455
|
+
\u{1F4C5} ${title}
|
|
3456
|
+
\u{1F550} ${this.formatDateTime(startTime)} - ${this.formatDateTime(endTime)}
|
|
3457
|
+
|
|
3458
|
+
\u{1F517} ${eventLink}`,
|
|
3508
3459
|
actions: [
|
|
3509
|
-
{ label: "\u67E5\u770B\u65E5\u7A0B", action: "view_calendar" }
|
|
3510
|
-
{ label: "\u7F16\u8F91", action: "edit_event" }
|
|
3460
|
+
{ label: "\u67E5\u770B\u65E5\u7A0B", action: "view_calendar", url: eventLink }
|
|
3511
3461
|
]
|
|
3512
3462
|
};
|
|
3513
3463
|
} catch (error) {
|
|
3514
|
-
return {
|
|
3515
|
-
success: false,
|
|
3516
|
-
content: `\u274C \u521B\u5EFA\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`
|
|
3517
|
-
};
|
|
3464
|
+
return { success: false, content: `\u274C \u521B\u5EFA\u65E5\u7A0B\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}` };
|
|
3518
3465
|
}
|
|
3519
3466
|
}
|
|
3520
3467
|
};
|
|
3521
3468
|
}
|
|
3522
|
-
/**
|
|
3523
|
-
* 创建文档技能
|
|
3524
|
-
*/
|
|
3525
3469
|
createDocSkill() {
|
|
3526
3470
|
return {
|
|
3527
3471
|
name: "feishu.createDoc",
|
|
3528
3472
|
description: "\u521B\u5EFA\u98DE\u4E66\u4E91\u6587\u6863",
|
|
3529
|
-
trigger:
|
|
3473
|
+
trigger: /创建文档|新建文档|生成文档/,
|
|
3530
3474
|
riskLevel: "low",
|
|
3531
3475
|
examples: [
|
|
3532
3476
|
"\u521B\u5EFA\u6587\u6863\uFF1A\u9879\u76EE\u9700\u6C42\u6587\u6863",
|
|
@@ -3534,89 +3478,114 @@ var FeishuSkills = class {
|
|
|
3534
3478
|
],
|
|
3535
3479
|
execute: async (context) => {
|
|
3536
3480
|
try {
|
|
3537
|
-
const { title
|
|
3481
|
+
const { title } = this.parseDocArgs(context.message);
|
|
3482
|
+
const token = await this.getAccessToken();
|
|
3483
|
+
const response = await fetch(`${this.baseUrl}/docx/v1/documents`, {
|
|
3484
|
+
method: "POST",
|
|
3485
|
+
headers: {
|
|
3486
|
+
"Authorization": `Bearer ${token}`,
|
|
3487
|
+
"Content-Type": "application/json"
|
|
3488
|
+
},
|
|
3489
|
+
body: JSON.stringify({ title })
|
|
3490
|
+
});
|
|
3491
|
+
const data = await response.json();
|
|
3492
|
+
if (data.code !== 0) {
|
|
3493
|
+
throw new Error(data.msg || "\u521B\u5EFA\u6587\u6863\u5931\u8D25");
|
|
3494
|
+
}
|
|
3495
|
+
const documentId = data.data?.document?.document_id;
|
|
3496
|
+
const domain = ConfigManager.getInstance().getFeishuConfig().domain === "lark" ? "larksuite.com" : "feishu.cn";
|
|
3497
|
+
const documentUrl = `https://${domain}/docx/${documentId}`;
|
|
3538
3498
|
return {
|
|
3539
3499
|
success: true,
|
|
3540
3500
|
content: `\u2705 \u6587\u6863\u5DF2\u521B\u5EFA
|
|
3541
3501
|
|
|
3542
|
-
\
|
|
3543
|
-
|
|
3502
|
+
\u{1F4C4} ${title}
|
|
3503
|
+
|
|
3504
|
+
\u{1F517} ${documentUrl}`,
|
|
3544
3505
|
actions: [
|
|
3545
|
-
{ label: "\u6253\u5F00\u6587\u6863", action: "open_doc" }
|
|
3546
|
-
{ label: "\u5206\u4EAB", action: "share_doc" }
|
|
3506
|
+
{ label: "\u6253\u5F00\u6587\u6863", action: "open_doc", url: documentUrl }
|
|
3547
3507
|
]
|
|
3548
3508
|
};
|
|
3549
3509
|
} catch (error) {
|
|
3550
|
-
return {
|
|
3551
|
-
success: false,
|
|
3552
|
-
content: `\u274C \u521B\u5EFA\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`
|
|
3553
|
-
};
|
|
3510
|
+
return { success: false, content: `\u274C \u521B\u5EFA\u6587\u6863\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}` };
|
|
3554
3511
|
}
|
|
3555
3512
|
}
|
|
3556
3513
|
};
|
|
3557
3514
|
}
|
|
3558
|
-
/**
|
|
3559
|
-
* 上传文件技能
|
|
3560
|
-
*/
|
|
3561
3515
|
uploadFileSkill() {
|
|
3562
3516
|
return {
|
|
3563
3517
|
name: "feishu.uploadFile",
|
|
3564
3518
|
description: "\u4E0A\u4F20\u6587\u4EF6\u5230\u98DE\u4E66",
|
|
3565
|
-
trigger:
|
|
3519
|
+
trigger: /上传文件|发送文件|传文件/,
|
|
3566
3520
|
riskLevel: "low",
|
|
3567
3521
|
execute: async (context) => {
|
|
3568
|
-
return {
|
|
3569
|
-
success: true,
|
|
3570
|
-
content: "\u{1F4CE} \u6587\u4EF6\u4E0A\u4F20\u529F\u80FD\u5F00\u53D1\u4E2D"
|
|
3571
|
-
};
|
|
3522
|
+
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
3523
|
}
|
|
3573
3524
|
};
|
|
3574
3525
|
}
|
|
3575
|
-
/**
|
|
3576
|
-
* 创建群公告技能
|
|
3577
|
-
*/
|
|
3578
3526
|
createAnnouncementSkill() {
|
|
3579
3527
|
return {
|
|
3580
3528
|
name: "feishu.createAnnouncement",
|
|
3581
3529
|
description: "\u53D1\u9001\u7FA4\u516C\u544A",
|
|
3582
|
-
trigger:
|
|
3530
|
+
trigger: /发送公告|群公告|发布公告|发公告/,
|
|
3583
3531
|
riskLevel: "medium",
|
|
3584
3532
|
requireConfirm: true,
|
|
3585
3533
|
execute: async (context) => {
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3534
|
+
try {
|
|
3535
|
+
const { title, content } = this.parseAnnouncementArgs(context.message);
|
|
3536
|
+
await this.client.sendMessage(context.chatId, {
|
|
3537
|
+
msg_type: "text",
|
|
3538
|
+
content: { text: `\u{1F4E2} **${title}**
|
|
3539
|
+
|
|
3540
|
+
${content}` }
|
|
3541
|
+
});
|
|
3542
|
+
return { success: true, content: "\u2705 \u516C\u544A\u5DF2\u53D1\u9001" };
|
|
3543
|
+
} catch (error) {
|
|
3544
|
+
return { success: false, content: `\u274C \u53D1\u5E03\u516C\u544A\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}` };
|
|
3545
|
+
}
|
|
3590
3546
|
}
|
|
3591
3547
|
};
|
|
3592
3548
|
}
|
|
3593
|
-
// 辅助方法
|
|
3594
3549
|
parseMessageArgs(message) {
|
|
3595
|
-
const match = message.match(/给(.+?)
|
|
3596
|
-
if (match) {
|
|
3597
|
-
return { target: match[1], content: match[2] };
|
|
3598
|
-
}
|
|
3550
|
+
const match = message.match(/给(.+?)[::]\s*(.+)/);
|
|
3551
|
+
if (match) return { target: match[1], content: match[2] };
|
|
3599
3552
|
return { target: "current", content: message };
|
|
3600
3553
|
}
|
|
3601
3554
|
parseEventArgs(message) {
|
|
3602
|
-
const
|
|
3603
|
-
const
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3555
|
+
const now = /* @__PURE__ */ new Date();
|
|
3556
|
+
const titleMatch = message.match(/日程[::]\s*(.+?)(?=(明天|今天|后天|时间|地点|$))/i);
|
|
3557
|
+
const title = titleMatch ? titleMatch[1].trim() : "\u65B0\u65E5\u7A0B";
|
|
3558
|
+
const timeMatch = message.match(/(明天|今天|后天)?\s*(上午|下午|晚上)?\s*(\d{1,2})[:点时]?(\d{0,2})?/);
|
|
3559
|
+
let startTime = new Date(now.getTime() + 60 * 60 * 1e3);
|
|
3560
|
+
if (timeMatch) {
|
|
3561
|
+
const day = timeMatch[1];
|
|
3562
|
+
const period = timeMatch[2];
|
|
3563
|
+
let hour = parseInt(timeMatch[3]);
|
|
3564
|
+
const minute = parseInt(timeMatch[4]) || 0;
|
|
3565
|
+
if (day === "\u660E\u5929") startTime.setDate(startTime.getDate() + 1);
|
|
3566
|
+
else if (day === "\u540E\u5929") startTime.setDate(startTime.getDate() + 2);
|
|
3567
|
+
if (period === "\u4E0B\u5348" && hour < 12) hour += 12;
|
|
3568
|
+
if (period === "\u665A\u4E0A" && hour < 12) hour += 12;
|
|
3569
|
+
startTime.setHours(hour, minute, 0, 0);
|
|
3570
|
+
}
|
|
3571
|
+
const endTime = new Date(startTime.getTime() + 60 * 60 * 1e3);
|
|
3572
|
+
return { title, startTime: startTime.getTime(), endTime: endTime.getTime(), description: "" };
|
|
3609
3573
|
}
|
|
3610
3574
|
parseDocArgs(message) {
|
|
3611
|
-
const match = message.match(/文档[::]\s*(.+?)(
|
|
3612
|
-
return {
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3575
|
+
const match = message.match(/文档[::]\s*(.+?)(?=(内容|文件夹|$))/i);
|
|
3576
|
+
return { title: match ? match[1].trim() : "\u672A\u547D\u540D\u6587\u6863" };
|
|
3577
|
+
}
|
|
3578
|
+
parseAnnouncementArgs(message) {
|
|
3579
|
+
const titleMatch = message.match(/公告[::]\s*(.+?)(?=内容|$)/i);
|
|
3580
|
+
const title = titleMatch ? titleMatch[1].trim() : "\u7FA4\u516C\u544A";
|
|
3581
|
+
const contentMatch = message.match(/内容[::]\s*(.+)/);
|
|
3582
|
+
const content = contentMatch ? contentMatch[1] : "";
|
|
3583
|
+
return { title, content };
|
|
3584
|
+
}
|
|
3585
|
+
formatDateTime(timestamp) {
|
|
3586
|
+
const date = new Date(timestamp);
|
|
3587
|
+
return `${date.getMonth() + 1}\u6708${date.getDate()}\u65E5 ${date.getHours()}:${date.getMinutes().toString().padStart(2, "0")}`;
|
|
3616
3588
|
}
|
|
3617
|
-
/**
|
|
3618
|
-
* 获取所有飞书技能
|
|
3619
|
-
*/
|
|
3620
3589
|
getAllSkills() {
|
|
3621
3590
|
return [
|
|
3622
3591
|
this.sendMessageSkill(),
|