feat: add group chat and mention-gating to channel adapters
Slack: add requireMention option, resolve bot user ID on connect. Telegram: add group chat mention/reply-to-bot detection, strip @mention from message text, default requireMention=true for groups. WhatsApp: add allowedGroupIds for group chat support, mention detection via mentionedIds and body text, strip bot mention from messages.
This commit is contained in:
@@ -253,4 +253,187 @@ describe('TelegramAdapter', () => {
|
||||
|
||||
expect(next).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
// ── Group chat mention gating ──────────────────────────────────
|
||||
|
||||
it('ignores group messages without mention when requireMention is true', async () => {
|
||||
const handler = vi.fn();
|
||||
adapter.onMessage(handler);
|
||||
|
||||
await adapter.connect();
|
||||
|
||||
// Simulate onStart callback to set botInfo
|
||||
const startCall = mockStart.mock.calls[0][0];
|
||||
startCall.onStart({ id: 12345, username: 'flynn_bot' });
|
||||
|
||||
const textHandlerCall = mockOn.mock.calls.find(
|
||||
(call) => call[0] === 'message:text',
|
||||
);
|
||||
const textHandler = textHandlerCall![1];
|
||||
|
||||
const ctx = {
|
||||
message: { message_id: 42, text: 'Hello everyone', reply_to_message: undefined },
|
||||
chat: { id: 100, type: 'group' },
|
||||
from: { first_name: 'Will' },
|
||||
replyWithChatAction: vi.fn(),
|
||||
};
|
||||
|
||||
await textHandler(ctx);
|
||||
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
expect(ctx.replyWithChatAction).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('processes group messages with bot mention when requireMention is true', async () => {
|
||||
const handler = vi.fn();
|
||||
adapter.onMessage(handler);
|
||||
|
||||
await adapter.connect();
|
||||
|
||||
// Simulate onStart callback to set botInfo
|
||||
const startCall = mockStart.mock.calls[0][0];
|
||||
startCall.onStart({ id: 12345, username: 'flynn_bot' });
|
||||
|
||||
const textHandlerCall = mockOn.mock.calls.find(
|
||||
(call) => call[0] === 'message:text',
|
||||
);
|
||||
const textHandler = textHandlerCall![1];
|
||||
|
||||
const ctx = {
|
||||
message: { message_id: 42, text: '@flynn_bot What is the weather?', reply_to_message: undefined },
|
||||
chat: { id: 100, type: 'group' },
|
||||
from: { first_name: 'Will' },
|
||||
replyWithChatAction: vi.fn(),
|
||||
};
|
||||
|
||||
await textHandler(ctx);
|
||||
|
||||
expect(handler).toHaveBeenCalledTimes(1);
|
||||
const msg: InboundMessage = handler.mock.calls[0][0];
|
||||
expect(msg.text).toBe('What is the weather?');
|
||||
});
|
||||
|
||||
it('processes group messages as reply to bot when requireMention is true', async () => {
|
||||
const handler = vi.fn();
|
||||
adapter.onMessage(handler);
|
||||
|
||||
await adapter.connect();
|
||||
|
||||
// Simulate onStart callback to set botInfo
|
||||
const startCall = mockStart.mock.calls[0][0];
|
||||
startCall.onStart({ id: 12345, username: 'flynn_bot' });
|
||||
|
||||
const textHandlerCall = mockOn.mock.calls.find(
|
||||
(call) => call[0] === 'message:text',
|
||||
);
|
||||
const textHandler = textHandlerCall![1];
|
||||
|
||||
const ctx = {
|
||||
message: {
|
||||
message_id: 42,
|
||||
text: 'Follow up question',
|
||||
reply_to_message: { from: { id: 12345 } },
|
||||
},
|
||||
chat: { id: 100, type: 'supergroup' },
|
||||
from: { first_name: 'Will' },
|
||||
replyWithChatAction: vi.fn(),
|
||||
};
|
||||
|
||||
await textHandler(ctx);
|
||||
|
||||
expect(handler).toHaveBeenCalledTimes(1);
|
||||
const msg: InboundMessage = handler.mock.calls[0][0];
|
||||
expect(msg.text).toBe('Follow up question');
|
||||
});
|
||||
|
||||
it('processes group messages without mention when requireMention is false', async () => {
|
||||
const noMentionAdapter = new TelegramAdapter({
|
||||
...baseConfig,
|
||||
requireMention: false,
|
||||
});
|
||||
const handler = vi.fn();
|
||||
noMentionAdapter.onMessage(handler);
|
||||
|
||||
await noMentionAdapter.connect();
|
||||
|
||||
// Simulate onStart callback
|
||||
const startCall = mockStart.mock.calls[0][0];
|
||||
startCall.onStart({ id: 12345, username: 'flynn_bot' });
|
||||
|
||||
const textHandlerCall = mockOn.mock.calls.find(
|
||||
(call) => call[0] === 'message:text',
|
||||
);
|
||||
const textHandler = textHandlerCall![1];
|
||||
|
||||
const ctx = {
|
||||
message: { message_id: 42, text: 'Hello everyone', reply_to_message: undefined },
|
||||
chat: { id: 100, type: 'group' },
|
||||
from: { first_name: 'Will' },
|
||||
replyWithChatAction: vi.fn(),
|
||||
};
|
||||
|
||||
await textHandler(ctx);
|
||||
|
||||
expect(handler).toHaveBeenCalledTimes(1);
|
||||
const msg: InboundMessage = handler.mock.calls[0][0];
|
||||
expect(msg.text).toBe('Hello everyone');
|
||||
});
|
||||
|
||||
it('DMs are always processed regardless of requireMention setting', async () => {
|
||||
const handler = vi.fn();
|
||||
adapter.onMessage(handler);
|
||||
|
||||
await adapter.connect();
|
||||
|
||||
// Simulate onStart callback to set botInfo
|
||||
const startCall = mockStart.mock.calls[0][0];
|
||||
startCall.onStart({ id: 12345, username: 'flynn_bot' });
|
||||
|
||||
const textHandlerCall = mockOn.mock.calls.find(
|
||||
(call) => call[0] === 'message:text',
|
||||
);
|
||||
const textHandler = textHandlerCall![1];
|
||||
|
||||
const ctx = {
|
||||
message: { message_id: 42, text: 'Hello Flynn', reply_to_message: undefined },
|
||||
chat: { id: 100, type: 'private' },
|
||||
from: { first_name: 'Will' },
|
||||
replyWithChatAction: vi.fn(),
|
||||
};
|
||||
|
||||
await textHandler(ctx);
|
||||
|
||||
expect(handler).toHaveBeenCalledTimes(1);
|
||||
const msg: InboundMessage = handler.mock.calls[0][0];
|
||||
expect(msg.text).toBe('Hello Flynn');
|
||||
});
|
||||
|
||||
it('strips bot mention from group message text', async () => {
|
||||
const handler = vi.fn();
|
||||
adapter.onMessage(handler);
|
||||
|
||||
await adapter.connect();
|
||||
|
||||
// Simulate onStart callback to set botInfo
|
||||
const startCall = mockStart.mock.calls[0][0];
|
||||
startCall.onStart({ id: 12345, username: 'flynn_bot' });
|
||||
|
||||
const textHandlerCall = mockOn.mock.calls.find(
|
||||
(call) => call[0] === 'message:text',
|
||||
);
|
||||
const textHandler = textHandlerCall![1];
|
||||
|
||||
const ctx = {
|
||||
message: { message_id: 42, text: '@flynn_bot tell me a joke', reply_to_message: undefined },
|
||||
chat: { id: 100, type: 'group' },
|
||||
from: { first_name: 'Will' },
|
||||
replyWithChatAction: vi.fn(),
|
||||
};
|
||||
|
||||
await textHandler(ctx);
|
||||
|
||||
expect(handler).toHaveBeenCalledTimes(1);
|
||||
const msg: InboundMessage = handler.mock.calls[0][0];
|
||||
expect(msg.text).toBe('tell me a joke');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user