fix: unify mention gating across providers
This commit is contained in:
@@ -36,6 +36,7 @@
|
||||
- WhatsApp: set sender E.164 for direct chats so owner commands work in DMs.
|
||||
- Slack: keep auto-replies in the original thread when responding to thread messages. Thanks @scald for PR #251.
|
||||
- Control UI: avoid Slack config ReferenceError by reading slack config snapshots. Thanks @sreekaransrinath for PR #249.
|
||||
- Telegram: honor routing.groupChat.mentionPatterns for group mention gating. Thanks @regenrek for PR #242.
|
||||
|
||||
### Maintenance
|
||||
- Deps: bump pi-* stack, Slack SDK, discord-api-types, file-type, zod, and Biome.
|
||||
|
||||
@@ -1,6 +1,35 @@
|
||||
import type { ClawdbotConfig } from "../../config/config.js";
|
||||
import type { MsgContext } from "../templating.js";
|
||||
|
||||
export function buildMentionRegexes(cfg: ClawdbotConfig | undefined): RegExp[] {
|
||||
const patterns = cfg?.routing?.groupChat?.mentionPatterns ?? [];
|
||||
return patterns
|
||||
.map((pattern) => {
|
||||
try {
|
||||
return new RegExp(pattern, "i");
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter((value): value is RegExp => Boolean(value));
|
||||
}
|
||||
|
||||
export function normalizeMentionText(text: string): string {
|
||||
return (text ?? "")
|
||||
.replace(/[\u200b-\u200f\u202a-\u202e\u2060-\u206f]/g, "")
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
export function matchesMentionPatterns(
|
||||
text: string,
|
||||
mentionRegexes: RegExp[],
|
||||
): boolean {
|
||||
if (mentionRegexes.length === 0) return false;
|
||||
const cleaned = normalizeMentionText(text ?? "");
|
||||
if (!cleaned) return false;
|
||||
return mentionRegexes.some((re) => re.test(cleaned));
|
||||
}
|
||||
|
||||
export function stripStructuralPrefixes(text: string): string {
|
||||
// Ignore wrapper labels, timestamps, and sender prefixes so directive-only
|
||||
// detection still works in group batches that include history/context.
|
||||
|
||||
@@ -147,4 +147,59 @@ describe("monitorDiscordProvider tool results", () => {
|
||||
expect(sendMock.mock.calls[0][1]).toBe("PFX tool update");
|
||||
expect(sendMock.mock.calls[1][1]).toBe("PFX final reply");
|
||||
});
|
||||
|
||||
it("accepts guild messages when mentionPatterns match", async () => {
|
||||
config = {
|
||||
messages: { responsePrefix: "PFX" },
|
||||
discord: {
|
||||
dm: { enabled: true },
|
||||
guilds: { "*": { requireMention: true } },
|
||||
},
|
||||
routing: {
|
||||
allowFrom: [],
|
||||
groupChat: { mentionPatterns: ["\\bclawd\\b"] },
|
||||
},
|
||||
};
|
||||
replyMock.mockResolvedValue({ text: "hi" });
|
||||
|
||||
const controller = new AbortController();
|
||||
const run = monitorDiscordProvider({
|
||||
token: "token",
|
||||
abortSignal: controller.signal,
|
||||
});
|
||||
|
||||
const discord = await import("discord.js");
|
||||
const client = await waitForClient();
|
||||
if (!client) throw new Error("Discord client not created");
|
||||
|
||||
client.emit(discord.Events.MessageCreate, {
|
||||
id: "m2",
|
||||
content: "clawd: hello",
|
||||
author: { id: "u1", bot: false, username: "Ada", tag: "Ada#1" },
|
||||
member: { displayName: "Ada" },
|
||||
channelId: "c1",
|
||||
channel: {
|
||||
type: discord.ChannelType.GuildText,
|
||||
name: "general",
|
||||
isSendable: () => false,
|
||||
},
|
||||
guild: { id: "g1", name: "Guild" },
|
||||
mentions: {
|
||||
has: () => false,
|
||||
everyone: false,
|
||||
users: { size: 0 },
|
||||
roles: { size: 0 },
|
||||
},
|
||||
attachments: { first: () => undefined },
|
||||
type: discord.MessageType.Default,
|
||||
createdTimestamp: Date.now(),
|
||||
});
|
||||
|
||||
await flush();
|
||||
controller.abort();
|
||||
await run;
|
||||
|
||||
expect(replyMock).toHaveBeenCalledTimes(1);
|
||||
expect(replyMock.mock.calls[0][0].WasMentioned).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,6 +18,10 @@ import {
|
||||
import { chunkText, resolveTextChunkLimit } from "../auto-reply/chunk.js";
|
||||
import { hasControlCommand } from "../auto-reply/command-detection.js";
|
||||
import { formatAgentEnvelope } from "../auto-reply/envelope.js";
|
||||
import {
|
||||
buildMentionRegexes,
|
||||
matchesMentionPatterns,
|
||||
} from "../auto-reply/reply/mentions.js";
|
||||
import { createReplyDispatcher } from "../auto-reply/reply/reply-dispatcher.js";
|
||||
import { getReplyFromConfig } from "../auto-reply/reply.js";
|
||||
import type { ReplyPayload } from "../auto-reply/types.js";
|
||||
@@ -140,6 +144,7 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
|
||||
const mediaMaxBytes =
|
||||
(opts.mediaMaxMb ?? cfg.discord?.mediaMaxMb ?? 8) * 1024 * 1024;
|
||||
const textLimit = resolveTextChunkLimit(cfg, "discord");
|
||||
const mentionRegexes = buildMentionRegexes(cfg);
|
||||
const historyLimit = Math.max(
|
||||
0,
|
||||
opts.historyLimit ?? cfg.discord?.historyLimit ?? 20,
|
||||
@@ -202,13 +207,15 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
|
||||
return;
|
||||
}
|
||||
const botId = client.user?.id;
|
||||
const wasMentioned =
|
||||
!isDirectMessage && Boolean(botId && message.mentions.has(botId));
|
||||
const forwardedSnapshot = resolveForwardedSnapshot(message);
|
||||
const forwardedText = forwardedSnapshot
|
||||
? resolveDiscordSnapshotText(forwardedSnapshot.snapshot)
|
||||
: "";
|
||||
const baseText = resolveDiscordMessageText(message, forwardedText);
|
||||
const wasMentioned =
|
||||
!isDirectMessage &&
|
||||
(Boolean(botId && message.mentions.has(botId)) ||
|
||||
matchesMentionPatterns(baseText, mentionRegexes));
|
||||
if (shouldLogVerbose()) {
|
||||
logVerbose(
|
||||
`discord: inbound id=${message.id} guild=${message.guild?.id ?? "dm"} channel=${message.channelId} mention=${wasMentioned ? "yes" : "no"} type=${isDirectMessage ? "dm" : isGroupDm ? "group-dm" : "guild"} content=${baseText ? "yes" : "no"}`,
|
||||
@@ -309,8 +316,9 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
|
||||
!hasAnyMention &&
|
||||
commandAuthorized &&
|
||||
hasControlCommand(baseText);
|
||||
if (isGuildMessage && resolvedRequireMention) {
|
||||
if (botId && !wasMentioned && !shouldBypassMention) {
|
||||
const canDetectMention = Boolean(botId) || mentionRegexes.length > 0;
|
||||
if (isGuildMessage && resolvedRequireMention && canDetectMention) {
|
||||
if (!wasMentioned && !shouldBypassMention) {
|
||||
logVerbose(
|
||||
`discord: drop guild message (mention required, botId=${botId})`,
|
||||
);
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { chunkText, resolveTextChunkLimit } from "../auto-reply/chunk.js";
|
||||
import { hasControlCommand } from "../auto-reply/command-detection.js";
|
||||
import { formatAgentEnvelope } from "../auto-reply/envelope.js";
|
||||
import {
|
||||
buildMentionRegexes,
|
||||
matchesMentionPatterns,
|
||||
} from "../auto-reply/reply/mentions.js";
|
||||
import { createReplyDispatcher } from "../auto-reply/reply/reply-dispatcher.js";
|
||||
import { getReplyFromConfig } from "../auto-reply/reply.js";
|
||||
import type { ReplyPayload } from "../auto-reply/types.js";
|
||||
@@ -67,20 +71,6 @@ function resolveAllowFrom(opts: MonitorIMessageOpts): string[] {
|
||||
return raw.map((entry) => String(entry).trim()).filter(Boolean);
|
||||
}
|
||||
|
||||
function resolveMentionRegexes(cfg: ReturnType<typeof loadConfig>): RegExp[] {
|
||||
return (
|
||||
cfg.routing?.groupChat?.mentionPatterns
|
||||
?.map((pattern) => {
|
||||
try {
|
||||
return new RegExp(pattern, "i");
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter((val): val is RegExp => Boolean(val)) ?? []
|
||||
);
|
||||
}
|
||||
|
||||
function resolveGroupRequireMention(
|
||||
cfg: ReturnType<typeof loadConfig>,
|
||||
opts: MonitorIMessageOpts,
|
||||
@@ -99,14 +89,6 @@ function resolveGroupRequireMention(
|
||||
return true;
|
||||
}
|
||||
|
||||
function isMentioned(text: string, regexes: RegExp[]): boolean {
|
||||
if (!text) return false;
|
||||
const cleaned = text
|
||||
.replace(/[\u200b-\u200f\u202a-\u202e\u2060-\u206f]/g, "")
|
||||
.toLowerCase();
|
||||
return regexes.some((re) => re.test(cleaned));
|
||||
}
|
||||
|
||||
async function deliverReplies(params: {
|
||||
replies: ReplyPayload[];
|
||||
target: string;
|
||||
@@ -148,7 +130,7 @@ export async function monitorIMessageProvider(
|
||||
const cfg = loadConfig();
|
||||
const textLimit = resolveTextChunkLimit(cfg, "imessage");
|
||||
const allowFrom = resolveAllowFrom(opts);
|
||||
const mentionRegexes = resolveMentionRegexes(cfg);
|
||||
const mentionRegexes = buildMentionRegexes(cfg);
|
||||
const includeAttachments =
|
||||
opts.includeAttachments ?? cfg.imessage?.includeAttachments ?? false;
|
||||
const mediaMaxBytes =
|
||||
@@ -183,15 +165,24 @@ export async function monitorIMessageProvider(
|
||||
}
|
||||
|
||||
const messageText = (message.text ?? "").trim();
|
||||
const mentioned = isGroup ? isMentioned(messageText, mentionRegexes) : true;
|
||||
const mentioned = isGroup
|
||||
? matchesMentionPatterns(messageText, mentionRegexes)
|
||||
: true;
|
||||
const requireMention = resolveGroupRequireMention(cfg, opts, chatId);
|
||||
const canDetectMention = mentionRegexes.length > 0;
|
||||
const shouldBypassMention =
|
||||
isGroup &&
|
||||
requireMention &&
|
||||
!mentioned &&
|
||||
commandAuthorized &&
|
||||
hasControlCommand(messageText);
|
||||
if (isGroup && requireMention && !mentioned && !shouldBypassMention) {
|
||||
if (
|
||||
isGroup &&
|
||||
requireMention &&
|
||||
canDetectMention &&
|
||||
!mentioned &&
|
||||
!shouldBypassMention
|
||||
) {
|
||||
logVerbose(`imessage: skipping group message (no mention)`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -123,6 +123,51 @@ describe("monitorSlackProvider tool results", () => {
|
||||
expect(sendMock.mock.calls[1][1]).toBe("PFX final reply");
|
||||
});
|
||||
|
||||
it("accepts channel messages when mentionPatterns match", async () => {
|
||||
config = {
|
||||
messages: { responsePrefix: "PFX" },
|
||||
slack: {
|
||||
dm: { enabled: true },
|
||||
groupDm: { enabled: false },
|
||||
channels: { C1: { allow: true, requireMention: true } },
|
||||
},
|
||||
routing: {
|
||||
allowFrom: [],
|
||||
groupChat: { mentionPatterns: ["\\bclawd\\b"] },
|
||||
},
|
||||
};
|
||||
replyMock.mockResolvedValue({ text: "hi" });
|
||||
|
||||
const controller = new AbortController();
|
||||
const run = monitorSlackProvider({
|
||||
botToken: "bot-token",
|
||||
appToken: "app-token",
|
||||
abortSignal: controller.signal,
|
||||
});
|
||||
|
||||
await waitForEvent("message");
|
||||
const handler = getSlackHandlers()?.get("message");
|
||||
if (!handler) throw new Error("Slack message handler not registered");
|
||||
|
||||
await handler({
|
||||
event: {
|
||||
type: "message",
|
||||
user: "U1",
|
||||
text: "clawd: hello",
|
||||
ts: "123",
|
||||
channel: "C1",
|
||||
channel_type: "channel",
|
||||
},
|
||||
});
|
||||
|
||||
await flush();
|
||||
controller.abort();
|
||||
await run;
|
||||
|
||||
expect(replyMock).toHaveBeenCalledTimes(1);
|
||||
expect(replyMock.mock.calls[0][0].WasMentioned).toBe(true);
|
||||
});
|
||||
|
||||
it("threads replies when incoming message is in a thread", async () => {
|
||||
replyMock.mockResolvedValue({ text: "thread reply" });
|
||||
|
||||
|
||||
@@ -6,6 +6,10 @@ import bolt from "@slack/bolt";
|
||||
import { chunkText, resolveTextChunkLimit } from "../auto-reply/chunk.js";
|
||||
import { hasControlCommand } from "../auto-reply/command-detection.js";
|
||||
import { formatAgentEnvelope } from "../auto-reply/envelope.js";
|
||||
import {
|
||||
buildMentionRegexes,
|
||||
matchesMentionPatterns,
|
||||
} from "../auto-reply/reply/mentions.js";
|
||||
import { createReplyDispatcher } from "../auto-reply/reply/reply-dispatcher.js";
|
||||
import { getReplyFromConfig } from "../auto-reply/reply.js";
|
||||
import { SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js";
|
||||
@@ -379,6 +383,7 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
||||
opts.slashCommand ?? cfg.slack?.slashCommand,
|
||||
);
|
||||
const textLimit = resolveTextChunkLimit(cfg, "slack");
|
||||
const mentionRegexes = buildMentionRegexes(cfg);
|
||||
const mediaMaxBytes =
|
||||
(opts.mediaMaxMb ?? cfg.slack?.mediaMaxMb ?? 20) * 1024 * 1024;
|
||||
|
||||
@@ -581,7 +586,8 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
||||
const wasMentioned =
|
||||
opts.wasMentioned ??
|
||||
(!isDirectMessage &&
|
||||
Boolean(botUserId && message.text?.includes(`<@${botUserId}>`)));
|
||||
(Boolean(botUserId && message.text?.includes(`<@${botUserId}>`)) ||
|
||||
matchesMentionPatterns(message.text ?? "", mentionRegexes)));
|
||||
const sender = await resolveUserName(message.user);
|
||||
const senderName = sender?.name ?? message.user;
|
||||
const allowList = normalizeAllowListLower(allowFrom);
|
||||
@@ -600,9 +606,11 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
||||
!hasAnyMention &&
|
||||
commandAuthorized &&
|
||||
hasControlCommand(message.text ?? "");
|
||||
const canDetectMention = Boolean(botUserId) || mentionRegexes.length > 0;
|
||||
if (
|
||||
isRoom &&
|
||||
channelConfig?.requireMention &&
|
||||
canDetectMention &&
|
||||
!wasMentioned &&
|
||||
!shouldBypassMention
|
||||
) {
|
||||
|
||||
@@ -126,6 +126,73 @@ describe("createTelegramBot", () => {
|
||||
expect(sendChatActionSpy).toHaveBeenCalledWith(42, "typing");
|
||||
});
|
||||
|
||||
it("accepts group messages when mentionPatterns match (without @botUsername)", async () => {
|
||||
onSpy.mockReset();
|
||||
const replySpy = replyModule.__replySpy as unknown as ReturnType<
|
||||
typeof vi.fn
|
||||
>;
|
||||
replySpy.mockReset();
|
||||
|
||||
loadConfig.mockReturnValue({
|
||||
identity: { name: "Bert" },
|
||||
routing: { groupChat: { mentionPatterns: ["\\bbert\\b"] } },
|
||||
telegram: { groups: { "*": { requireMention: true } } },
|
||||
});
|
||||
|
||||
createTelegramBot({ token: "tok" });
|
||||
const handler = onSpy.mock.calls[0][1] as (
|
||||
ctx: Record<string, unknown>,
|
||||
) => Promise<void>;
|
||||
|
||||
await handler({
|
||||
message: {
|
||||
chat: { id: 7, type: "group", title: "Test Group" },
|
||||
text: "bert: introduce yourself",
|
||||
date: 1736380800,
|
||||
message_id: 1,
|
||||
from: { id: 9, first_name: "Ada" },
|
||||
},
|
||||
me: { username: "clawdbot_bot" },
|
||||
getFile: async () => ({ download: async () => new Uint8Array() }),
|
||||
});
|
||||
|
||||
expect(replySpy).toHaveBeenCalledTimes(1);
|
||||
const payload = replySpy.mock.calls[0][0];
|
||||
expect(payload.WasMentioned).toBe(true);
|
||||
});
|
||||
|
||||
it("skips group messages when requireMention is enabled and no mention matches", async () => {
|
||||
onSpy.mockReset();
|
||||
const replySpy = replyModule.__replySpy as unknown as ReturnType<
|
||||
typeof vi.fn
|
||||
>;
|
||||
replySpy.mockReset();
|
||||
|
||||
loadConfig.mockReturnValue({
|
||||
routing: { groupChat: { mentionPatterns: ["\\bbert\\b"] } },
|
||||
telegram: { groups: { "*": { requireMention: true } } },
|
||||
});
|
||||
|
||||
createTelegramBot({ token: "tok" });
|
||||
const handler = onSpy.mock.calls[0][1] as (
|
||||
ctx: Record<string, unknown>,
|
||||
) => Promise<void>;
|
||||
|
||||
await handler({
|
||||
message: {
|
||||
chat: { id: 7, type: "group", title: "Test Group" },
|
||||
text: "hello everyone",
|
||||
date: 1736380800,
|
||||
message_id: 2,
|
||||
from: { id: 9, first_name: "Ada" },
|
||||
},
|
||||
me: { username: "clawdbot_bot" },
|
||||
getFile: async () => ({ download: async () => new Uint8Array() }),
|
||||
});
|
||||
|
||||
expect(replySpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("includes reply-to context when a Telegram reply is received", async () => {
|
||||
onSpy.mockReset();
|
||||
sendMessageSpy.mockReset();
|
||||
|
||||
@@ -7,6 +7,10 @@ import { Bot, InputFile, webhookCallback } from "grammy";
|
||||
import { chunkText, resolveTextChunkLimit } from "../auto-reply/chunk.js";
|
||||
import { hasControlCommand } from "../auto-reply/command-detection.js";
|
||||
import { formatAgentEnvelope } from "../auto-reply/envelope.js";
|
||||
import {
|
||||
buildMentionRegexes,
|
||||
matchesMentionPatterns,
|
||||
} from "../auto-reply/reply/mentions.js";
|
||||
import { createReplyDispatcher } from "../auto-reply/reply/reply-dispatcher.js";
|
||||
import { getReplyFromConfig } from "../auto-reply/reply.js";
|
||||
import type { ReplyPayload } from "../auto-reply/types.js";
|
||||
@@ -67,6 +71,7 @@ export function createTelegramBot(opts: TelegramBotOptions) {
|
||||
const mediaMaxBytes =
|
||||
(opts.mediaMaxMb ?? cfg.telegram?.mediaMaxMb ?? 5) * 1024 * 1024;
|
||||
const logger = getChildLogger({ module: "telegram-auto-reply" });
|
||||
const mentionRegexes = buildMentionRegexes(cfg);
|
||||
const resolveGroupRequireMention = (chatId: string | number) => {
|
||||
const groupId = String(chatId);
|
||||
const groupConfig = cfg.telegram?.groups?.[groupId];
|
||||
@@ -132,7 +137,8 @@ export function createTelegramBot(opts: TelegramBotOptions) {
|
||||
entry.toLowerCase() === `@${senderUsername.toLowerCase()}`,
|
||||
));
|
||||
const wasMentioned =
|
||||
Boolean(botUsername) && hasBotMention(msg, botUsername);
|
||||
(Boolean(botUsername) && hasBotMention(msg, botUsername)) ||
|
||||
matchesMentionPatterns(msg.text ?? msg.caption ?? "", mentionRegexes);
|
||||
const hasAnyMention = (msg.entities ?? msg.caption_entities ?? []).some(
|
||||
(ent) => ent.type === "mention",
|
||||
);
|
||||
@@ -143,7 +149,9 @@ export function createTelegramBot(opts: TelegramBotOptions) {
|
||||
!hasAnyMention &&
|
||||
commandAuthorized &&
|
||||
hasControlCommand(msg.text ?? msg.caption ?? "");
|
||||
if (isGroup && resolveGroupRequireMention(chatId) && botUsername) {
|
||||
const canDetectMention =
|
||||
Boolean(botUsername) || mentionRegexes.length > 0;
|
||||
if (isGroup && resolveGroupRequireMention(chatId) && canDetectMention) {
|
||||
if (!wasMentioned && !shouldBypassMention) {
|
||||
logger.info(
|
||||
{ chatId, reason: "no-mention" },
|
||||
@@ -196,7 +204,7 @@ export function createTelegramBot(opts: TelegramBotOptions) {
|
||||
ReplyToBody: replyTarget?.body,
|
||||
ReplyToSender: replyTarget?.sender,
|
||||
Timestamp: msg.date ? msg.date * 1000 : undefined,
|
||||
WasMentioned: isGroup && botUsername ? wasMentioned : undefined,
|
||||
WasMentioned: isGroup ? wasMentioned : undefined,
|
||||
MediaPath: media?.path,
|
||||
MediaType: media?.contentType,
|
||||
MediaUrl: media?.path,
|
||||
|
||||
@@ -9,6 +9,10 @@ import {
|
||||
HEARTBEAT_PROMPT,
|
||||
stripHeartbeatToken,
|
||||
} from "../auto-reply/heartbeat.js";
|
||||
import {
|
||||
buildMentionRegexes,
|
||||
normalizeMentionText,
|
||||
} from "../auto-reply/reply/mentions.js";
|
||||
import { createReplyDispatcher } from "../auto-reply/reply/reply-dispatcher.js";
|
||||
import { getReplyFromConfig } from "../auto-reply/reply.js";
|
||||
import { HEARTBEAT_TOKEN, SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js";
|
||||
@@ -147,17 +151,7 @@ type MentionConfig = {
|
||||
};
|
||||
|
||||
function buildMentionConfig(cfg: ReturnType<typeof loadConfig>): MentionConfig {
|
||||
const gc = cfg.routing?.groupChat;
|
||||
const mentionRegexes =
|
||||
gc?.mentionPatterns
|
||||
?.map((p) => {
|
||||
try {
|
||||
return new RegExp(p, "i");
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter((r): r is RegExp => Boolean(r)) ?? [];
|
||||
const mentionRegexes = buildMentionRegexes(cfg);
|
||||
return { mentionRegexes, allowFrom: cfg.whatsapp?.allowFrom };
|
||||
}
|
||||
|
||||
@@ -166,10 +160,8 @@ function isBotMentioned(
|
||||
mentionCfg: MentionConfig,
|
||||
): boolean {
|
||||
const clean = (text: string) =>
|
||||
text
|
||||
// Remove zero-width and directionality markers WhatsApp injects around display names
|
||||
.replace(/[\u200b-\u200f\u202a-\u202e\u2060-\u206f]/g, "")
|
||||
.toLowerCase();
|
||||
// Remove zero-width and directionality markers WhatsApp injects around display names
|
||||
normalizeMentionText(text);
|
||||
|
||||
const isSelfChat = isSelfChatMode(msg.selfE164, mentionCfg.allowFrom);
|
||||
|
||||
@@ -212,9 +204,7 @@ function debugMention(
|
||||
const details = {
|
||||
from: msg.from,
|
||||
body: msg.body,
|
||||
bodyClean: msg.body
|
||||
.replace(/[\u200b-\u200f\u202a-\u202e\u2060-\u206f]/g, "")
|
||||
.toLowerCase(),
|
||||
bodyClean: normalizeMentionText(msg.body),
|
||||
mentionedJids: msg.mentionedJids ?? null,
|
||||
selfJid: msg.selfJid ?? null,
|
||||
selfE164: msg.selfE164 ?? null,
|
||||
|
||||
Reference in New Issue
Block a user