mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-30 10:10:18 +00:00
fix(feishu): handle card.action.trigger callbacks (openclaw#17863)
Co-authored-by: Kai <clawborn@users.noreply.github.com>
This commit is contained in:
63
extensions/feishu/src/bot.card-action.test.ts
Normal file
63
extensions/feishu/src/bot.card-action.test.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { handleFeishuCardAction, type FeishuCardActionEvent } from "./card-action.js";
|
||||
|
||||
// Mock resolveFeishuAccount
|
||||
vi.mock("./accounts.js", () => ({
|
||||
resolveFeishuAccount: vi.fn().mockReturnValue({ accountId: "mock-account" }),
|
||||
}));
|
||||
|
||||
// Mock bot.js to verify handleFeishuMessage call
|
||||
vi.mock("./bot.js", () => ({
|
||||
handleFeishuMessage: vi.fn(),
|
||||
}));
|
||||
|
||||
import { handleFeishuMessage } from "./bot.js";
|
||||
|
||||
describe("Feishu Card Action Handler", () => {
|
||||
const cfg = {} as any; // Minimal mock
|
||||
const runtime = { log: vi.fn(), error: vi.fn() } as any;
|
||||
|
||||
it("handles card action with text payload", async () => {
|
||||
const event: FeishuCardActionEvent = {
|
||||
operator: { open_id: "u123", user_id: "uid1", union_id: "un1" },
|
||||
token: "tok1",
|
||||
action: { value: { text: "/ping" }, tag: "button" },
|
||||
context: { open_id: "u123", user_id: "uid1", chat_id: "chat1" },
|
||||
};
|
||||
|
||||
await handleFeishuCardAction({ cfg, event, runtime });
|
||||
|
||||
expect(handleFeishuMessage).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
event: expect.objectContaining({
|
||||
message: expect.objectContaining({
|
||||
content: '{"text":"/ping"}',
|
||||
chat_id: "chat1",
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("handles card action with JSON object payload", async () => {
|
||||
const event: FeishuCardActionEvent = {
|
||||
operator: { open_id: "u123", user_id: "uid1", union_id: "un1" },
|
||||
token: "tok2",
|
||||
action: { value: { key: "val" }, tag: "button" },
|
||||
context: { open_id: "u123", user_id: "uid1", chat_id: "" },
|
||||
};
|
||||
|
||||
await handleFeishuCardAction({ cfg, event, runtime });
|
||||
|
||||
expect(handleFeishuMessage).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
event: expect.objectContaining({
|
||||
message: expect.objectContaining({
|
||||
content: '{"text":"{\\"key\\":\\"val\\"}"}',
|
||||
chat_id: "u123", // Fallback to open_id
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
77
extensions/feishu/src/card-action.ts
Normal file
77
extensions/feishu/src/card-action.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk";
|
||||
import { resolveFeishuAccount } from "./accounts.js";
|
||||
import { handleFeishuMessage, type FeishuMessageEvent } from "./bot.js";
|
||||
|
||||
export type FeishuCardActionEvent = {
|
||||
operator: {
|
||||
open_id: string;
|
||||
user_id: string;
|
||||
union_id: string;
|
||||
};
|
||||
token: string;
|
||||
action: {
|
||||
value: Record<string, unknown>;
|
||||
tag: string;
|
||||
};
|
||||
context: {
|
||||
open_id: string;
|
||||
user_id: string;
|
||||
chat_id: string;
|
||||
};
|
||||
};
|
||||
|
||||
export async function handleFeishuCardAction(params: {
|
||||
cfg: ClawdbotConfig;
|
||||
event: FeishuCardActionEvent;
|
||||
botOpenId?: string;
|
||||
runtime?: RuntimeEnv;
|
||||
accountId?: string;
|
||||
}): Promise<void> {
|
||||
const { cfg, event, runtime, accountId } = params;
|
||||
const account = resolveFeishuAccount({ cfg, accountId });
|
||||
const log = runtime?.log ?? console.log;
|
||||
|
||||
// Extract action value
|
||||
const actionValue = event.action.value;
|
||||
let content = "";
|
||||
if (typeof actionValue === "object" && actionValue !== null) {
|
||||
if ("text" in actionValue && typeof actionValue.text === "string") {
|
||||
content = actionValue.text;
|
||||
} else if ("command" in actionValue && typeof actionValue.command === "string") {
|
||||
content = actionValue.command;
|
||||
} else {
|
||||
content = JSON.stringify(actionValue);
|
||||
}
|
||||
} else {
|
||||
content = String(actionValue);
|
||||
}
|
||||
|
||||
// Construct a synthetic message event
|
||||
const messageEvent: FeishuMessageEvent = {
|
||||
sender: {
|
||||
sender_id: {
|
||||
open_id: event.operator.open_id,
|
||||
user_id: event.operator.user_id,
|
||||
union_id: event.operator.union_id,
|
||||
},
|
||||
},
|
||||
message: {
|
||||
message_id: `card-action-${event.token}`,
|
||||
chat_id: event.context.chat_id || event.operator.open_id,
|
||||
chat_type: event.context.chat_id ? "group" : "p2p",
|
||||
message_type: "text",
|
||||
content: JSON.stringify({ text: content }),
|
||||
},
|
||||
};
|
||||
|
||||
log(`feishu[${account.accountId}]: handling card action from ${event.operator.open_id}: ${content}`);
|
||||
|
||||
// Dispatch as normal message
|
||||
await handleFeishuMessage({
|
||||
cfg,
|
||||
event: messageEvent,
|
||||
botOpenId: params.botOpenId,
|
||||
runtime,
|
||||
accountId,
|
||||
});
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from "openclaw/plugin-sdk";
|
||||
import { resolveFeishuAccount, listEnabledFeishuAccounts } from "./accounts.js";
|
||||
import { handleFeishuMessage, type FeishuMessageEvent, type FeishuBotAddedEvent } from "./bot.js";
|
||||
import { handleFeishuCardAction, type FeishuCardActionEvent } from "./card-action.js";
|
||||
import { createFeishuWSClient, createEventDispatcher } from "./client.js";
|
||||
import { probeFeishu } from "./probe.js";
|
||||
import { getMessageFeishu } from "./send.js";
|
||||
@@ -350,6 +351,27 @@ function registerEventHandlers(
|
||||
"im.message.reaction.deleted_v1": async () => {
|
||||
// Ignore reaction removals
|
||||
},
|
||||
"card.action.trigger": async (data) => {
|
||||
try {
|
||||
const event = data as unknown as FeishuCardActionEvent;
|
||||
const promise = handleFeishuCardAction({
|
||||
cfg,
|
||||
event,
|
||||
botOpenId: botOpenIds.get(accountId),
|
||||
runtime,
|
||||
accountId,
|
||||
});
|
||||
if (fireAndForget) {
|
||||
promise.catch((err) => {
|
||||
error(`feishu[${accountId}]: error handling card action: ${String(err)}`);
|
||||
});
|
||||
} else {
|
||||
await promise;
|
||||
}
|
||||
} catch (err) {
|
||||
error(`feishu[${accountId}]: error handling card action: ${String(err)}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user