Skip to content

Commit d6b959a

Browse files
committed
fix: enforce requireMention setting for Feishu group chats
The `bridge_feishu_require_mention` setting existed in the config UI and was loaded from the database, but was never actually checked in the message handling pipeline. Group chat messages were always processed regardless of whether the bot was @mentioned. Changes: - Fetch bot identity (open_id) on plugin startup via getBotInfo() - Check `message.mentions` against botOpenId when requireMention is enabled and the message comes from a group chat (chatId starts with 'oc_') - Strip the @bot mention placeholder from message text so the LLM receives clean input - Return null (skip message) when requireMention is true and the bot is not mentioned
1 parent 8be94c6 commit d6b959a

2 files changed

Lines changed: 55 additions & 6 deletions

File tree

src/lib/channels/feishu/inbound.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,20 @@ import type { FeishuConfig } from './types';
99

1010
const LOG_TAG = '[feishu/inbound]';
1111

12+
/** Find the bot's mention entry in the Feishu mentions array, if present. */
13+
function findBotMention(
14+
mentions: any[] | undefined,
15+
botOpenId: string,
16+
): { key?: string } | undefined {
17+
if (!mentions || !botOpenId) return undefined;
18+
return mentions.find((m: any) => m?.id?.open_id === botOpenId);
19+
}
20+
1221
/** Parse a raw Feishu im.message.receive_v1 event into an InboundMessage. */
1322
export function parseInboundMessage(
1423
eventData: any,
15-
_config: FeishuConfig,
24+
config: FeishuConfig,
25+
botOpenId?: string,
1626
): InboundMessage | null {
1727
try {
1828
const event = eventData?.event ?? eventData;
@@ -24,7 +34,16 @@ export function parseInboundMessage(
2434
const sender = event.sender?.sender_id?.open_id || '';
2535
const msgType = message.message_type;
2636

27-
// Only handle text messages for now
37+
const isGroupChat = chatId.startsWith('oc_');
38+
const botMention = isGroupChat && botOpenId
39+
? findBotMention(message.mentions, botOpenId)
40+
: undefined;
41+
42+
// When requireMention is enabled, drop group messages that don't @mention the bot.
43+
if (isGroupChat && config.requireMention && !botMention) {
44+
return null;
45+
}
46+
2847
let text = '';
2948
if (msgType === 'text') {
3049
try {
@@ -34,13 +53,16 @@ export function parseInboundMessage(
3453
text = message.content || '';
3554
}
3655
} else {
37-
// Non-text messages — skip silently
3856
return null;
3957
}
4058

4159
if (!text.trim()) return null;
4260

43-
// Build thread-session address if applicable
61+
// Strip the @bot placeholder so the LLM sees clean input.
62+
if (botMention?.key) {
63+
text = text.replaceAll(botMention.key, '').trim();
64+
}
65+
4466
const rootId = message.root_id || '';
4567
const effectiveChatId = rootId ? `${chatId}:thread:${rootId}` : chatId;
4668

src/lib/channels/feishu/index.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type { FeishuConfig } from './types';
1111
import { loadFeishuConfig, validateFeishuConfig } from './config';
1212
import { FeishuGateway } from './gateway';
1313
import { parseInboundMessage } from './inbound';
14+
import { getBotInfo } from './identity';
1415
import { sendMessage, addReaction, removeReaction } from './outbound';
1516
import { isUserAuthorized } from './policy';
1617
import { createCardStreamController } from './card-controller';
@@ -23,6 +24,7 @@ export class FeishuChannelPlugin implements ChannelPlugin<FeishuConfig> {
2324

2425
private config: FeishuConfig | null = null;
2526
private gateway: FeishuGateway | null = null;
27+
private botOpenId: string = '';
2628
private messageQueue: InboundMessage[] = [];
2729
private waitResolve: ((msg: InboundMessage | null) => void) | null = null;
2830
/** Track last received messageId per chatId for reaction acknowledgment. */
@@ -64,9 +66,11 @@ export class FeishuChannelPlugin implements ChannelPlugin<FeishuConfig> {
6466

6567
this.gateway = new FeishuGateway(this.config);
6668

67-
// Register message handler — pushes to internal queue
69+
// Register message handler — pushes to internal queue.
70+
// Reads this.botOpenId at call time, so mention checks activate
71+
// once resolveBotIdentity() completes after gateway.start().
6872
this.gateway.registerMessageHandler((data: unknown) => {
69-
const msg = parseInboundMessage(data, this.config!);
73+
const msg = parseInboundMessage(data, this.config!, this.botOpenId);
7074
if (!msg) return;
7175
this.enqueueMessage(msg);
7276
});
@@ -148,6 +152,29 @@ export class FeishuChannelPlugin implements ChannelPlugin<FeishuConfig> {
148152
});
149153

150154
await this.gateway.start();
155+
156+
// Resolve bot identity so mention checks work.
157+
// Handler reads this.botOpenId dynamically — once resolved, all
158+
// subsequent messages get proper mention filtering.
159+
await this.resolveBotIdentity();
160+
}
161+
162+
/** Fetch bot open_id with retry so mention features degrade gracefully. */
163+
private async resolveBotIdentity(maxRetries = 3): Promise<void> {
164+
const client = this.gateway?.getRestClient();
165+
if (!client) return;
166+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
167+
const info = await getBotInfo(client);
168+
if (info?.openId) {
169+
this.botOpenId = info.openId;
170+
return;
171+
}
172+
console.warn('[feishu/plugin]', `Bot identity attempt ${attempt}/${maxRetries} failed`);
173+
if (attempt < maxRetries) {
174+
await new Promise((r) => setTimeout(r, 2000 * attempt));
175+
}
176+
}
177+
console.error('[feishu/plugin]', 'Could not resolve bot identity — mention features disabled');
151178
}
152179

153180
async stop(): Promise<void> {

0 commit comments

Comments
 (0)