mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 06:17:27 +00:00
fix(feishu): add reactionNotifications mode gating (openclaw#29388) thanks @Takhoffman
Verified: - pnpm build - pnpm check - pnpm test:macmini Co-authored-by: Takhoffman <781889+Takhoffman@users.noreply.github.com> Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
@@ -110,6 +110,7 @@ const GroupSessionScopeSchema = z
|
||||
* - "enabled": Messages in different topics get separate sessions
|
||||
*/
|
||||
const TopicSessionModeSchema = z.enum(["disabled", "enabled"]).optional();
|
||||
const ReactionNotificationModeSchema = z.enum(["off", "own", "all"]).optional();
|
||||
|
||||
/**
|
||||
* Reply-in-thread mode for group chats.
|
||||
@@ -159,6 +160,7 @@ const FeishuSharedConfigShape = {
|
||||
streaming: StreamingModeSchema,
|
||||
tools: FeishuToolsConfigSchema,
|
||||
replyInThread: ReplyInThreadSchema,
|
||||
reactionNotifications: ReactionNotificationModeSchema,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -195,6 +197,7 @@ export const FeishuConfigSchema = z
|
||||
webhookPath: z.string().optional().default("/feishu/events"),
|
||||
...FeishuSharedConfigShape,
|
||||
dmPolicy: DmPolicySchema.optional().default("pairing"),
|
||||
reactionNotifications: ReactionNotificationModeSchema.optional().default("own"),
|
||||
groupPolicy: GroupPolicySchema.optional().default("allowlist"),
|
||||
requireMention: z.boolean().optional().default(true),
|
||||
groupSessionScope: GroupSessionScopeSchema,
|
||||
|
||||
@@ -49,6 +49,31 @@ describe("resolveReactionSyntheticEvent", () => {
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("drops reactions when reactionNotifications is off", async () => {
|
||||
const event = makeReactionEvent();
|
||||
const result = await resolveReactionSyntheticEvent({
|
||||
cfg: {
|
||||
channels: {
|
||||
feishu: {
|
||||
reactionNotifications: "off",
|
||||
},
|
||||
},
|
||||
} as ClawdbotConfig,
|
||||
accountId: "default",
|
||||
event,
|
||||
botOpenId: "ou_bot",
|
||||
fetchMessage: async () => ({
|
||||
messageId: "om_msg1",
|
||||
chatId: "oc_group",
|
||||
senderOpenId: "ou_bot",
|
||||
senderType: "app",
|
||||
content: "hello",
|
||||
contentType: "text",
|
||||
}),
|
||||
});
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("filters reactions on non-bot messages", async () => {
|
||||
const event = makeReactionEvent();
|
||||
const result = await resolveReactionSyntheticEvent({
|
||||
@@ -68,6 +93,32 @@ describe("resolveReactionSyntheticEvent", () => {
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("allows non-bot reactions when reactionNotifications is all", async () => {
|
||||
const event = makeReactionEvent();
|
||||
const result = await resolveReactionSyntheticEvent({
|
||||
cfg: {
|
||||
channels: {
|
||||
feishu: {
|
||||
reactionNotifications: "all",
|
||||
},
|
||||
},
|
||||
} as ClawdbotConfig,
|
||||
accountId: "default",
|
||||
event,
|
||||
botOpenId: "ou_bot",
|
||||
fetchMessage: async () => ({
|
||||
messageId: "om_msg1",
|
||||
chatId: "oc_group",
|
||||
senderOpenId: "ou_other",
|
||||
senderType: "user",
|
||||
content: "hello",
|
||||
contentType: "text",
|
||||
}),
|
||||
uuid: () => "fixed-uuid",
|
||||
});
|
||||
expect(result?.message.message_id).toBe("om_msg1:reaction:THUMBSUP:fixed-uuid");
|
||||
});
|
||||
|
||||
it("drops unverified reactions when sender verification times out", async () => {
|
||||
const event = makeReactionEvent();
|
||||
const result = await resolveReactionSyntheticEvent({
|
||||
|
||||
@@ -177,6 +177,12 @@ export async function resolveReactionSyntheticEvent(
|
||||
return null;
|
||||
}
|
||||
|
||||
const account = resolveFeishuAccount({ cfg, accountId });
|
||||
const reactionNotifications = account.config.reactionNotifications ?? "own";
|
||||
if (reactionNotifications === "off") {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Skip bot self-reactions
|
||||
if (event.operator_type === "app" || senderId === botOpenId) {
|
||||
return null;
|
||||
@@ -187,9 +193,7 @@ export async function resolveReactionSyntheticEvent(
|
||||
return null;
|
||||
}
|
||||
|
||||
// Fail closed if bot identity cannot be resolved; otherwise reactions on any
|
||||
// message can leak into the agent.
|
||||
if (!botOpenId) {
|
||||
if (reactionNotifications === "own" && !botOpenId) {
|
||||
logger?.(
|
||||
`feishu[${accountId}]: bot open_id unavailable, skipping reaction ${emoji} on ${messageId}`,
|
||||
);
|
||||
@@ -201,7 +205,7 @@ export async function resolveReactionSyntheticEvent(
|
||||
verificationTimeoutMs,
|
||||
).catch(() => null);
|
||||
const isBotMessage = reactedMsg?.senderType === "app" || reactedMsg?.senderOpenId === botOpenId;
|
||||
if (!reactedMsg || !isBotMessage) {
|
||||
if (!reactedMsg || (reactionNotifications === "own" && !isBotMessage)) {
|
||||
logger?.(
|
||||
`feishu[${accountId}]: ignoring reaction on non-bot/unverified message ${messageId} ` +
|
||||
`(sender: ${reactedMsg?.senderOpenId ?? "unknown"})`,
|
||||
|
||||
Reference in New Issue
Block a user