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:
William Valentin
2026-02-06 16:51:52 -08:00
parent 20930a4816
commit 647d7779c7
6 changed files with 510 additions and 16 deletions
+49 -11
View File
@@ -20,6 +20,10 @@ import { splitMessage } from '../utils.js';
export interface WhatsAppAdapterConfig {
/** Phone numbers allowed to interact. Empty = all numbers. */
allowedNumbers?: string[];
/** Group IDs (without @g.us suffix) allowed to interact. Empty = no groups. */
allowedGroupIds?: string[];
/** Require bot mention to respond in group chats (default: true). DMs always respond. */
requireMention?: boolean;
/** Directory for session persistence (LocalAuth data path). */
dataDir?: string;
}
@@ -48,6 +52,7 @@ export class WhatsAppAdapter implements ChannelAdapter {
private client: Client | null = null;
private messageHandler?: (msg: InboundMessage) => void;
private config: WhatsAppAdapterConfig;
private botId?: string;
get status(): ChannelStatus {
return this._status;
@@ -84,6 +89,8 @@ export class WhatsAppAdapter implements ChannelAdapter {
this.client!.on('ready', () => {
console.log('WhatsApp bot connected');
this._status = 'connected';
// Capture bot's own JID for mention detection
this.botId = (this.client as any)?.info?.wid?._serialized;
resolve();
});
@@ -150,20 +157,51 @@ export class WhatsAppAdapter implements ChannelAdapter {
const from = message.from;
// Ignore group messages (only handle direct messages)
if (from.endsWith('@g.us')) return;
// Group message handling
const isGroup = from.endsWith('@g.us');
if (isGroup) {
// Check allowed group IDs
const groupId = from.replace(/@g\.us$/, '');
if (
!this.config.allowedGroupIds ||
this.config.allowedGroupIds.length === 0 ||
!this.config.allowedGroupIds.includes(groupId)
) {
return; // Group not allowed (empty list = no groups)
}
// Check allowed numbers (strip @c.us suffix for comparison)
const phoneNumber = from.replace(/@c\.us$/, '');
if (
this.config.allowedNumbers &&
this.config.allowedNumbers.length > 0 &&
!this.config.allowedNumbers.includes(phoneNumber)
) {
return;
// Mention requirement in group chats
const requireMention = this.config.requireMention ?? true;
if (requireMention) {
// WhatsApp mentions use @phone_number format in body
// Also check for mentions in the message mentionedIds
const mentionsBot = this.botId
? message.body?.includes(`@${this.botId.replace(/@c\.us$/, '')}`) ||
(message as any).mentionedIds?.some((id: string) => id === this.botId)
: false;
if (!mentionsBot) return;
}
}
// Check allowed numbers for DMs (strip @c.us suffix for comparison)
if (!isGroup) {
const phoneNumber = from.replace(/@c\.us$/, '');
if (
this.config.allowedNumbers &&
this.config.allowedNumbers.length > 0 &&
!this.config.allowedNumbers.includes(phoneNumber)
) {
return;
}
}
// Strip bot mention from message body for group messages
let text = message.body ?? '';
if (isGroup && this.botId) {
const botNumber = this.botId.replace(/@c\.us$/, '');
text = text.replace(new RegExp(`@${botNumber}\\b`, 'g'), '').trim();
}
const text = message.body ?? '';
const senderName = message._data?.notifyName;
// Detect reset command