feishu-bridge 1.0.5 → 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 +243 -246
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +243 -246
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.js +242 -246
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +242 -246
- 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
|
*/
|
|
@@ -2441,6 +2349,7 @@ var KiroAdapter = class extends BaseIDEAdapter {
|
|
|
2441
2349
|
};
|
|
2442
2350
|
|
|
2443
2351
|
// src/adapters/opencode.ts
|
|
2352
|
+
var import_execa6 = require("execa");
|
|
2444
2353
|
var import_child_process = require("child_process");
|
|
2445
2354
|
var net = __toESM(require("net"));
|
|
2446
2355
|
var fs7 = __toESM(require("fs"));
|
|
@@ -2450,6 +2359,8 @@ var OpenCodeAdapter = class extends BaseIDEAdapter {
|
|
|
2450
2359
|
constructor() {
|
|
2451
2360
|
super(...arguments);
|
|
2452
2361
|
this.type = "opencode";
|
|
2362
|
+
this.ideType = "opencode";
|
|
2363
|
+
this.ideName = "OpenCode";
|
|
2453
2364
|
this.useSocket = false;
|
|
2454
2365
|
this.messageId = 0;
|
|
2455
2366
|
this.pendingMessages = /* @__PURE__ */ new Map();
|
|
@@ -2462,6 +2373,17 @@ var OpenCodeAdapter = class extends BaseIDEAdapter {
|
|
|
2462
2373
|
async checkAvailability() {
|
|
2463
2374
|
return this.checkAvailabilitySync();
|
|
2464
2375
|
}
|
|
2376
|
+
async isInstalled() {
|
|
2377
|
+
return this.checkAvailabilitySync();
|
|
2378
|
+
}
|
|
2379
|
+
async getVersion() {
|
|
2380
|
+
try {
|
|
2381
|
+
const { stdout } = await (0, import_execa6.execa)("opencode", ["--version"]);
|
|
2382
|
+
return stdout.trim();
|
|
2383
|
+
} catch {
|
|
2384
|
+
return "unknown";
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2465
2387
|
checkCLIExists() {
|
|
2466
2388
|
try {
|
|
2467
2389
|
const { execSync } = require("child_process");
|
|
@@ -2652,10 +2574,21 @@ var OpenCodeAdapter = class extends BaseIDEAdapter {
|
|
|
2652
2574
|
};
|
|
2653
2575
|
}
|
|
2654
2576
|
}
|
|
2577
|
+
async getAvailableModels() {
|
|
2578
|
+
return [
|
|
2579
|
+
"claude-3-5-sonnet-20241022",
|
|
2580
|
+
"claude-3-opus-20240229",
|
|
2581
|
+
"claude-3-haiku-20240307"
|
|
2582
|
+
];
|
|
2583
|
+
}
|
|
2584
|
+
async executeAI(prompt) {
|
|
2585
|
+
const content = await this.getAIResponse(prompt);
|
|
2586
|
+
return { content, model: "claude-3-5-sonnet" };
|
|
2587
|
+
}
|
|
2655
2588
|
};
|
|
2656
2589
|
|
|
2657
2590
|
// src/adapters/claude-code.ts
|
|
2658
|
-
var
|
|
2591
|
+
var import_execa7 = require("execa");
|
|
2659
2592
|
var fs8 = __toESM(require("fs"));
|
|
2660
2593
|
var path8 = __toESM(require("path"));
|
|
2661
2594
|
var os8 = __toESM(require("os"));
|
|
@@ -2675,7 +2608,7 @@ var ClaudeCodeAdapter = class extends BaseIDEAdapter {
|
|
|
2675
2608
|
findClaudeExecutable() {
|
|
2676
2609
|
const platform5 = os8.platform();
|
|
2677
2610
|
try {
|
|
2678
|
-
const result =
|
|
2611
|
+
const result = import_execa7.execa.sync("claude", ["--version"], { reject: false });
|
|
2679
2612
|
if (result.exitCode === 0) {
|
|
2680
2613
|
return "claude";
|
|
2681
2614
|
}
|
|
@@ -2716,7 +2649,7 @@ var ClaudeCodeAdapter = class extends BaseIDEAdapter {
|
|
|
2716
2649
|
this.logger.info(`Sending command to Claude Code: ${command}`);
|
|
2717
2650
|
if (this.claudePath) {
|
|
2718
2651
|
try {
|
|
2719
|
-
const result = await (0,
|
|
2652
|
+
const result = await (0, import_execa7.execa)(this.claudePath, [
|
|
2720
2653
|
"execute",
|
|
2721
2654
|
"--command",
|
|
2722
2655
|
command
|
|
@@ -2780,7 +2713,7 @@ var ClaudeCodeAdapter = class extends BaseIDEAdapter {
|
|
|
2780
2713
|
this.logger.info(`Getting AI response from Claude Code for prompt: ${prompt}`);
|
|
2781
2714
|
if (this.claudePath) {
|
|
2782
2715
|
try {
|
|
2783
|
-
const result = await (0,
|
|
2716
|
+
const result = await (0, import_execa7.execa)(this.claudePath, [
|
|
2784
2717
|
"ask",
|
|
2785
2718
|
"--prompt",
|
|
2786
2719
|
prompt
|
|
@@ -2845,7 +2778,7 @@ var ClaudeCodeAdapter = class extends BaseIDEAdapter {
|
|
|
2845
2778
|
}
|
|
2846
2779
|
async runProgram(command, cwd) {
|
|
2847
2780
|
try {
|
|
2848
|
-
const result = await
|
|
2781
|
+
const result = await import_execa7.execa.command(command, {
|
|
2849
2782
|
cwd: cwd || process.cwd(),
|
|
2850
2783
|
shell: true,
|
|
2851
2784
|
timeout: 6e4,
|
|
@@ -2874,7 +2807,7 @@ var ClaudeCodeAdapter = class extends BaseIDEAdapter {
|
|
|
2874
2807
|
|
|
2875
2808
|
// src/adapters/gemini-cli.ts
|
|
2876
2809
|
var import_child_process2 = require("child_process");
|
|
2877
|
-
var
|
|
2810
|
+
var import_execa8 = require("execa");
|
|
2878
2811
|
var GeminiCLIAdapter = class extends BaseIDEAdapter {
|
|
2879
2812
|
constructor() {
|
|
2880
2813
|
super(...arguments);
|
|
@@ -2893,6 +2826,9 @@ var GeminiCLIAdapter = class extends BaseIDEAdapter {
|
|
|
2893
2826
|
async checkAvailability() {
|
|
2894
2827
|
return this.checkAvailabilitySync();
|
|
2895
2828
|
}
|
|
2829
|
+
async isInstalled() {
|
|
2830
|
+
return this.checkAvailabilitySync();
|
|
2831
|
+
}
|
|
2896
2832
|
async activate() {
|
|
2897
2833
|
const available = await this.checkAvailability();
|
|
2898
2834
|
if (!available) {
|
|
@@ -3000,7 +2936,7 @@ var GeminiCLIAdapter = class extends BaseIDEAdapter {
|
|
|
3000
2936
|
*/
|
|
3001
2937
|
async getVersion() {
|
|
3002
2938
|
try {
|
|
3003
|
-
const result = await (0,
|
|
2939
|
+
const result = await (0, import_execa8.execa)(this.cliPath, ["version"]);
|
|
3004
2940
|
return result.stdout;
|
|
3005
2941
|
} catch {
|
|
3006
2942
|
return "unknown";
|
|
@@ -3417,18 +3353,42 @@ var ReverseChannel = class extends import_events.EventEmitter {
|
|
|
3417
3353
|
};
|
|
3418
3354
|
|
|
3419
3355
|
// src/skills/builtin/feishu.ts
|
|
3356
|
+
init_config();
|
|
3420
3357
|
var FeishuSkills = class {
|
|
3421
3358
|
constructor(client) {
|
|
3359
|
+
this.accessToken = null;
|
|
3360
|
+
this.tokenExpireTime = 0;
|
|
3422
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;
|
|
3423
3386
|
}
|
|
3424
|
-
/**
|
|
3425
|
-
* 发送消息技能
|
|
3426
|
-
*/
|
|
3427
3387
|
sendMessageSkill() {
|
|
3428
3388
|
return {
|
|
3429
3389
|
name: "feishu.sendMessage",
|
|
3430
3390
|
description: "\u53D1\u9001\u6D88\u606F\u5230\u6307\u5B9A\u4F1A\u8BDD",
|
|
3431
|
-
trigger:
|
|
3391
|
+
trigger: /发送消息|发信息|发消息/,
|
|
3432
3392
|
riskLevel: "low",
|
|
3433
3393
|
examples: [
|
|
3434
3394
|
"\u53D1\u9001\u6D88\u606F\u7ED9\u5F20\u4E09\uFF1A\u4E0B\u5348\u5F00\u4F1A",
|
|
@@ -3436,32 +3396,23 @@ var FeishuSkills = class {
|
|
|
3436
3396
|
],
|
|
3437
3397
|
execute: async (context) => {
|
|
3438
3398
|
try {
|
|
3439
|
-
const {
|
|
3399
|
+
const { content } = this.parseMessageArgs(context.message);
|
|
3440
3400
|
await this.client.sendMessage(context.chatId, {
|
|
3441
3401
|
msg_type: "text",
|
|
3442
3402
|
content: { text: content }
|
|
3443
3403
|
});
|
|
3444
|
-
return {
|
|
3445
|
-
success: true,
|
|
3446
|
-
content: "\u2705 \u6D88\u606F\u5DF2\u53D1\u9001"
|
|
3447
|
-
};
|
|
3404
|
+
return { success: true, content: "\u2705 \u6D88\u606F\u5DF2\u53D1\u9001" };
|
|
3448
3405
|
} catch (error) {
|
|
3449
|
-
return {
|
|
3450
|
-
success: false,
|
|
3451
|
-
content: `\u274C \u53D1\u9001\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`
|
|
3452
|
-
};
|
|
3406
|
+
return { success: false, content: `\u274C \u53D1\u9001\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}` };
|
|
3453
3407
|
}
|
|
3454
3408
|
}
|
|
3455
3409
|
};
|
|
3456
3410
|
}
|
|
3457
|
-
/**
|
|
3458
|
-
* 创建日程技能
|
|
3459
|
-
*/
|
|
3460
3411
|
createEventSkill() {
|
|
3461
3412
|
return {
|
|
3462
3413
|
name: "feishu.createEvent",
|
|
3463
3414
|
description: "\u5728\u98DE\u4E66\u65E5\u5386\u521B\u5EFA\u65E5\u7A0B",
|
|
3464
|
-
trigger:
|
|
3415
|
+
trigger: /创建日程|创建会议|添加日程|新建日程/,
|
|
3465
3416
|
riskLevel: "medium",
|
|
3466
3417
|
examples: [
|
|
3467
3418
|
"\u521B\u5EFA\u65E5\u7A0B\uFF1A\u660E\u5929\u4E0B\u53483\u70B9\u56E2\u961F\u5468\u4F1A",
|
|
@@ -3469,36 +3420,57 @@ var FeishuSkills = class {
|
|
|
3469
3420
|
],
|
|
3470
3421
|
execute: async (context) => {
|
|
3471
3422
|
try {
|
|
3472
|
-
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 || "";
|
|
3473
3451
|
return {
|
|
3474
3452
|
success: true,
|
|
3475
3453
|
content: `\u2705 \u65E5\u7A0B\u5DF2\u521B\u5EFA
|
|
3476
3454
|
|
|
3477
|
-
\
|
|
3478
|
-
\
|
|
3479
|
-
|
|
3455
|
+
\u{1F4C5} ${title}
|
|
3456
|
+
\u{1F550} ${this.formatDateTime(startTime)} - ${this.formatDateTime(endTime)}
|
|
3457
|
+
|
|
3458
|
+
\u{1F517} ${eventLink}`,
|
|
3480
3459
|
actions: [
|
|
3481
|
-
{ label: "\u67E5\u770B\u65E5\u7A0B", action: "view_calendar" }
|
|
3482
|
-
{ label: "\u7F16\u8F91", action: "edit_event" }
|
|
3460
|
+
{ label: "\u67E5\u770B\u65E5\u7A0B", action: "view_calendar", url: eventLink }
|
|
3483
3461
|
]
|
|
3484
3462
|
};
|
|
3485
3463
|
} catch (error) {
|
|
3486
|
-
return {
|
|
3487
|
-
success: false,
|
|
3488
|
-
content: `\u274C \u521B\u5EFA\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`
|
|
3489
|
-
};
|
|
3464
|
+
return { success: false, content: `\u274C \u521B\u5EFA\u65E5\u7A0B\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}` };
|
|
3490
3465
|
}
|
|
3491
3466
|
}
|
|
3492
3467
|
};
|
|
3493
3468
|
}
|
|
3494
|
-
/**
|
|
3495
|
-
* 创建文档技能
|
|
3496
|
-
*/
|
|
3497
3469
|
createDocSkill() {
|
|
3498
3470
|
return {
|
|
3499
3471
|
name: "feishu.createDoc",
|
|
3500
3472
|
description: "\u521B\u5EFA\u98DE\u4E66\u4E91\u6587\u6863",
|
|
3501
|
-
trigger:
|
|
3473
|
+
trigger: /创建文档|新建文档|生成文档/,
|
|
3502
3474
|
riskLevel: "low",
|
|
3503
3475
|
examples: [
|
|
3504
3476
|
"\u521B\u5EFA\u6587\u6863\uFF1A\u9879\u76EE\u9700\u6C42\u6587\u6863",
|
|
@@ -3506,89 +3478,114 @@ var FeishuSkills = class {
|
|
|
3506
3478
|
],
|
|
3507
3479
|
execute: async (context) => {
|
|
3508
3480
|
try {
|
|
3509
|
-
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}`;
|
|
3510
3498
|
return {
|
|
3511
3499
|
success: true,
|
|
3512
3500
|
content: `\u2705 \u6587\u6863\u5DF2\u521B\u5EFA
|
|
3513
3501
|
|
|
3514
|
-
\
|
|
3515
|
-
|
|
3502
|
+
\u{1F4C4} ${title}
|
|
3503
|
+
|
|
3504
|
+
\u{1F517} ${documentUrl}`,
|
|
3516
3505
|
actions: [
|
|
3517
|
-
{ label: "\u6253\u5F00\u6587\u6863", action: "open_doc" }
|
|
3518
|
-
{ label: "\u5206\u4EAB", action: "share_doc" }
|
|
3506
|
+
{ label: "\u6253\u5F00\u6587\u6863", action: "open_doc", url: documentUrl }
|
|
3519
3507
|
]
|
|
3520
3508
|
};
|
|
3521
3509
|
} catch (error) {
|
|
3522
|
-
return {
|
|
3523
|
-
success: false,
|
|
3524
|
-
content: `\u274C \u521B\u5EFA\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`
|
|
3525
|
-
};
|
|
3510
|
+
return { success: false, content: `\u274C \u521B\u5EFA\u6587\u6863\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}` };
|
|
3526
3511
|
}
|
|
3527
3512
|
}
|
|
3528
3513
|
};
|
|
3529
3514
|
}
|
|
3530
|
-
/**
|
|
3531
|
-
* 上传文件技能
|
|
3532
|
-
*/
|
|
3533
3515
|
uploadFileSkill() {
|
|
3534
3516
|
return {
|
|
3535
3517
|
name: "feishu.uploadFile",
|
|
3536
3518
|
description: "\u4E0A\u4F20\u6587\u4EF6\u5230\u98DE\u4E66",
|
|
3537
|
-
trigger:
|
|
3519
|
+
trigger: /上传文件|发送文件|传文件/,
|
|
3538
3520
|
riskLevel: "low",
|
|
3539
3521
|
execute: async (context) => {
|
|
3540
|
-
return {
|
|
3541
|
-
success: true,
|
|
3542
|
-
content: "\u{1F4CE} \u6587\u4EF6\u4E0A\u4F20\u529F\u80FD\u5F00\u53D1\u4E2D"
|
|
3543
|
-
};
|
|
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" };
|
|
3544
3523
|
}
|
|
3545
3524
|
};
|
|
3546
3525
|
}
|
|
3547
|
-
/**
|
|
3548
|
-
* 创建群公告技能
|
|
3549
|
-
*/
|
|
3550
3526
|
createAnnouncementSkill() {
|
|
3551
3527
|
return {
|
|
3552
3528
|
name: "feishu.createAnnouncement",
|
|
3553
3529
|
description: "\u53D1\u9001\u7FA4\u516C\u544A",
|
|
3554
|
-
trigger:
|
|
3530
|
+
trigger: /发送公告|群公告|发布公告|发公告/,
|
|
3555
3531
|
riskLevel: "medium",
|
|
3556
3532
|
requireConfirm: true,
|
|
3557
3533
|
execute: async (context) => {
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
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
|
+
}
|
|
3562
3546
|
}
|
|
3563
3547
|
};
|
|
3564
3548
|
}
|
|
3565
|
-
// 辅助方法
|
|
3566
3549
|
parseMessageArgs(message) {
|
|
3567
|
-
const match = message.match(/给(.+?)
|
|
3568
|
-
if (match) {
|
|
3569
|
-
return { target: match[1], content: match[2] };
|
|
3570
|
-
}
|
|
3550
|
+
const match = message.match(/给(.+?)[::]\s*(.+)/);
|
|
3551
|
+
if (match) return { target: match[1], content: match[2] };
|
|
3571
3552
|
return { target: "current", content: message };
|
|
3572
3553
|
}
|
|
3573
3554
|
parseEventArgs(message) {
|
|
3574
|
-
const
|
|
3575
|
-
const
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
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: "" };
|
|
3581
3573
|
}
|
|
3582
3574
|
parseDocArgs(message) {
|
|
3583
|
-
const match = message.match(/文档[::]\s*(.+?)(
|
|
3584
|
-
return {
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
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")}`;
|
|
3588
3588
|
}
|
|
3589
|
-
/**
|
|
3590
|
-
* 获取所有飞书技能
|
|
3591
|
-
*/
|
|
3592
3589
|
getAllSkills() {
|
|
3593
3590
|
return [
|
|
3594
3591
|
this.sendMessageSkill(),
|