fix(whatsapp): normalize device-qualified ids in every id comparison, bridge and Python

#89322 fixed the bridge-local normalizeWhatsAppId, but bridge.js has since
moved its id handling to bridge_helpers.js::normalizeWhatsAppId, which still
turned `<user>:<device>@lid` into the malformed `<user>@<device>@lid` for
mentionedJid / quoted participant / reaction keys, and the Python side
(gateway/platforms/whatsapp_common.py::_normalize_whatsapp_id) did the same
':'->'@' swap on botIds. Drop the local duplicate in bridge.js, import the
helper, and strip the `:<device>` suffix on both layers so the bot's own ids
compare equal to the bare ids WhatsApp sends for mentions and quotes.

One invariant test: device-qualified botIds match a bare mentionedId and a
bare quotedParticipant; a plain group message still does not trigger.
This commit is contained in:
teknium1
2026-09-14 18:46:26 -07:00
committed by Teknium
parent 6bf1032609
commit 8a3cded09c
5 changed files with 24 additions and 14 deletions
@@ -0,0 +1 @@
eranbes
+2 -4
View File
@@ -138,10 +138,8 @@ class WhatsAppBehaviorMixin(OwnAccessPolicyMixin):
def _normalize_whatsapp_id(value: Optional[str]) -> str:
if not value:
return ""
normalized = str(value).strip()
if ":" in normalized and "@" in normalized:
normalized = normalized.replace(":", "@", 1)
return normalized
# Device-qualified ids (`<user>:<device>@lid`) must equal their bare form.
return re.sub(r":\d+(?=@)", "", str(value).strip())
@staticmethod
def _is_broadcast_chat(chat_id: str) -> bool:
+1 -9
View File
@@ -46,6 +46,7 @@ import {
inboundReadReceiptKeys,
inferMediaType,
mediaPayloadForFile,
normalizeWhatsAppId,
pollCreationMessageFromPayload,
pollUpdateForAggregation,
} from './bridge_helpers.js';
@@ -205,15 +206,6 @@ function trackSentMessageId(sent) {
rememberSentId(sent?.key?.id);
}
function normalizeWhatsAppId(value) {
if (!value) return '';
// Strip the :<device> suffix (e.g. 447999674698:14@s.whatsapp.net -> ...@s.whatsapp.net)
// so bot/mention/quoted ids compare consistently. The old .replace(':','@') produced a
// malformed '...@14@lid', which never matched the bot's own id (breaking @mention and
// reply-to-bot detection in groups).
return String(value).replace(/:\d+@/, '@').replace(/:\d+$/, '');
}
function redactWhatsAppId(value) {
const raw = String(value || '').trim();
if (!raw) return '';
+5 -1
View File
@@ -15,7 +15,11 @@ export const MIME_MAP = {
export function normalizeWhatsAppId(value) {
if (!value) return '';
return String(value).replace(':', '@');
// Baileys reports the bot's own ids device-qualified (`<user>:<device>@lid`), while
// inbound mentionedJid / contextInfo.participant are not. Drop the suffix so both
// forms compare equal; the old `':' -> '@'` swap produced `<user>@<device>@lid`,
// which never matched and silently broke @mention / reply-to-bot gating in groups.
return String(value).replace(/:\d+(?=@)/, '').replace(/:\d+$/, '');
}
function unwrapMessageEnvelopes(content) {
@@ -244,3 +244,18 @@ def test_broadcast_filter_runs_before_allowlist():
assert adapter._should_process_message(msg) is False
def test_device_qualified_bot_ids_match_bare_mention_and_quote_ids():
"""Baileys reports the bot's own ids as ``<user>:<device>@lid`` while inbound
mentionedJid / quoted participant ids are bare — both must normalize equal."""
adapter = _make_adapter(require_mention=True, group_policy="open")
device_qualified = ["447999674698:14@s.whatsapp.net", "116342762025117:14@lid"]
assert adapter._should_process_message(
_group_message("hi there", botIds=device_qualified, mentionedIds=["116342762025117@lid"])
) is True
assert adapter._should_process_message(
_group_message("and this?", botIds=device_qualified, quotedParticipant="447999674698@s.whatsapp.net")
) is True
assert adapter._should_process_message(_group_message("hello everyone", botIds=device_qualified)) is False