mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-09 00:40:45 +08:00
fix(whatsapp): resolve LID @-mentions in sidecar and trust mentions_bot in gateway
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
dcb779a691
commit
8c665290c4
4 changed files with 99 additions and 7 deletions
|
|
@ -672,6 +672,7 @@ def process_inbound_payload(payload: dict[str, Any]) -> dict[str, Any]:
|
|||
account = store.find_user_by_channel_account(channel=inbound.channel, account_id=account_id) or {}
|
||||
from runtime.orchestration.group_ingest import (
|
||||
build_group_sender_context,
|
||||
metadata_mentions_bot,
|
||||
resolve_group_policy,
|
||||
session_user_key,
|
||||
should_process_group_inbound,
|
||||
|
|
@ -712,10 +713,11 @@ def process_inbound_payload(payload: dict[str, Any]) -> dict[str, Any]:
|
|||
import logging
|
||||
|
||||
logging.getLogger(__name__).info(
|
||||
"whatsapp group inbound skipped chat=%s user=%s mentions=%s require_mention=%s text=%r",
|
||||
"whatsapp group inbound skipped chat=%s user=%s mentions=%s mentions_bot=%s require_mention=%s text=%r",
|
||||
inbound.external_chat_id,
|
||||
inbound.external_user_id,
|
||||
list(inbound.mentions or []),
|
||||
metadata_mentions_bot(inbound.metadata if isinstance(inbound.metadata, dict) else {}),
|
||||
group_policy.require_mention,
|
||||
text[:120],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import dns from "node:dns/promises";
|
|||
import makeWASocket, {
|
||||
Browsers,
|
||||
DisconnectReason,
|
||||
areJidsSameUser,
|
||||
fetchLatestBaileysVersion,
|
||||
jidNormalizedUser,
|
||||
proto,
|
||||
|
|
@ -129,6 +130,53 @@ function extractQuoteContext(m: proto.IMessage | null | undefined): { participan
|
|||
};
|
||||
}
|
||||
|
||||
function resolveSenderJid(key: proto.IMessageKey): string {
|
||||
const participant = String(key.participant || "").trim();
|
||||
const participantAlt = String((key as any).participantAlt || "").trim();
|
||||
if (participantAlt && participant.toLowerCase().endsWith("@lid")) {
|
||||
return jidNormalizedUser(participantAlt);
|
||||
}
|
||||
if (participant) return jidNormalizedUser(participant);
|
||||
return "";
|
||||
}
|
||||
|
||||
function messageMentionsBot(
|
||||
sock: ReturnType<typeof makeWASocket> | null,
|
||||
mentions: string[],
|
||||
botJid: string,
|
||||
): boolean {
|
||||
const bot = String(botJid || "").trim();
|
||||
if (!bot || !mentions.length) return false;
|
||||
const lidMapping = (sock as any)?.signalRepository?.lidMapping;
|
||||
for (const m of mentions) {
|
||||
const mention = String(m || "").trim();
|
||||
if (!mention) continue;
|
||||
try {
|
||||
if (areJidsSameUser(mention, bot)) return true;
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
if (jidsSameUser(mention, bot)) return true;
|
||||
if (lidMapping && typeof lidMapping.getPNForLID === "function" && mention.toLowerCase().endsWith("@lid")) {
|
||||
try {
|
||||
const pn = lidMapping.getPNForLID(mention);
|
||||
if (pn && (areJidsSameUser(String(pn), bot) || jidsSameUser(String(pn), bot))) return true;
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
if (lidMapping && typeof lidMapping.getLIDForPN === "function" && bot.includes("@s.whatsapp")) {
|
||||
try {
|
||||
const lid = lidMapping.getLIDForPN(bot);
|
||||
if (lid && (areJidsSameUser(mention, String(lid)) || jidsSameUser(mention, String(lid)))) return true;
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isStatusOrBroadcastJid(jid: string): boolean {
|
||||
const low = String(jid || "").toLowerCase();
|
||||
return low === "status@broadcast" || low.endsWith("@broadcast");
|
||||
|
|
@ -143,13 +191,16 @@ function buildInboundPayload(params: {
|
|||
mentions: string[];
|
||||
groupName?: string;
|
||||
botJid?: string;
|
||||
mentionsBot?: boolean;
|
||||
}): Json {
|
||||
const metadata: Json = {
|
||||
source: "whatsapp_baileys",
|
||||
raw: params.raw,
|
||||
mentions_bot: params.mentionsBot === true,
|
||||
};
|
||||
if (params.groupName) metadata.group_name = params.groupName;
|
||||
if (params.botJid) metadata.bot_jid = params.botJid;
|
||||
if (params.mentions.length) metadata.mentioned_jids = params.mentions;
|
||||
return {
|
||||
channel: "whatsapp",
|
||||
account_id: ACCOUNT_ID,
|
||||
|
|
@ -493,26 +544,37 @@ async function main(): Promise<void> {
|
|||
if (!text) continue;
|
||||
|
||||
const isGroup = remoteJid.endsWith("@g.us");
|
||||
const from = isGroup ? String(key.participant || "").trim() : remoteJid;
|
||||
const userId = from ? jidNormalizedUser(from) : jidNormalizedUser(remoteJid);
|
||||
const participantRaw = String(key.participant || "").trim();
|
||||
const participantAlt = String((key as any).participantAlt || "").trim();
|
||||
const userId = isGroup
|
||||
? resolveSenderJid(key) || jidNormalizedUser(remoteJid)
|
||||
: jidNormalizedUser(remoteJid);
|
||||
const chatId = jidNormalizedUser(remoteJid);
|
||||
const mentions = extractMentionsFromUpsert(msg);
|
||||
const quote = extractQuoteContext(msg.message);
|
||||
const botJidRaw = sock?.user?.id ? String(sock.user.id).trim() : "";
|
||||
const botJid = botJidRaw ? jidNormalizedUser(botJidRaw) : "";
|
||||
const isReplyToBot = Boolean(botJidRaw && quote.participant && jidsSameUser(botJidRaw, quote.participant));
|
||||
const mentionsBot = messageMentionsBot(sock, mentions, botJidRaw);
|
||||
const isReplyToBot = Boolean(
|
||||
botJidRaw &&
|
||||
quote.participant &&
|
||||
(messageMentionsBot(sock, [quote.participant], botJidRaw) || jidsSameUser(botJidRaw, quote.participant)),
|
||||
);
|
||||
|
||||
const groupName = isGroup ? await resolveGroupName(chatId) : "";
|
||||
|
||||
const raw = {
|
||||
id,
|
||||
remoteJid,
|
||||
participant: key.participant || null,
|
||||
participant: participantRaw || null,
|
||||
participantAlt: participantAlt || null,
|
||||
pushName: (msg as any).pushName || null,
|
||||
messageTimestamp: (msg as any).messageTimestamp || null,
|
||||
quotedParticipant: quote.participant || null,
|
||||
quotedStanzaId: quote.stanzaId || null,
|
||||
isReplyToBot,
|
||||
mentionsBot,
|
||||
mentionedJids: mentions,
|
||||
};
|
||||
|
||||
const inbound = buildInboundPayload({
|
||||
|
|
@ -524,17 +586,18 @@ async function main(): Promise<void> {
|
|||
mentions,
|
||||
groupName: groupName || undefined,
|
||||
botJid: botJidRaw || botJid || undefined,
|
||||
mentionsBot,
|
||||
});
|
||||
if (VERBOSE || isGroup) {
|
||||
log(
|
||||
`inbound group=${isGroup} chat=${chatId} user=${userId} mentions=${mentions.length} replyToBot=${isReplyToBot} textLen=${text.length}`,
|
||||
`inbound group=${isGroup} chat=${chatId} user=${userId} mentions=${mentions.length} mentionsBot=${mentionsBot} replyToBot=${isReplyToBot} textLen=${text.length} mention0=${mentions[0] || ""}`,
|
||||
);
|
||||
} else if (VERBOSE) {
|
||||
log(`inbound posting chat=${chatId} user=${userId} textLen=${text.length}`);
|
||||
}
|
||||
const out = await postInbound(inbound);
|
||||
const replies = Array.isArray(out.replies) ? (out.replies as Json[]) : [];
|
||||
if (VERBOSE) log(`inbound ok replies=${replies.length}`);
|
||||
if (isGroup || VERBOSE) log(`inbound ok chat=${chatId} replies=${replies.length}`);
|
||||
for (const r of replies) {
|
||||
const outText = String((r as any).text || "").trim();
|
||||
if (!shouldSendOutboundText(outText) && !(Array.isArray((r as any).attachments) && (r as any).attachments.length)) {
|
||||
|
|
|
|||
|
|
@ -51,6 +51,16 @@ def _metadata_raw(metadata: dict[str, Any] | None) -> dict[str, Any]:
|
|||
return raw if isinstance(raw, dict) else {}
|
||||
|
||||
|
||||
def metadata_mentions_bot(metadata: dict[str, Any] | None) -> bool:
|
||||
"""Sidecar resolves LID @-mentions via Baileys; gateway trusts that signal."""
|
||||
if not isinstance(metadata, dict):
|
||||
return False
|
||||
if metadata.get("mentions_bot") is True or metadata.get("mentionsBot") is True:
|
||||
return True
|
||||
raw = _metadata_raw(metadata)
|
||||
return raw.get("mentionsBot") is True or raw.get("mentions_bot") is True
|
||||
|
||||
|
||||
def text_mentions_bot(*, text: str, bot_jid: str | None) -> bool:
|
||||
"""Fallback when WhatsApp omits mentionedJid but user visibly @-mentions the bot."""
|
||||
bot = str(bot_jid or "").strip()
|
||||
|
|
@ -184,6 +194,8 @@ def should_process_group_inbound(
|
|||
) -> bool:
|
||||
if not is_group:
|
||||
return True
|
||||
if metadata_mentions_bot(metadata):
|
||||
return True
|
||||
if is_reply_to_bot(metadata=metadata, bot_jid=bot_jid):
|
||||
return True
|
||||
mention_set = normalize_jids(list(mentions or []))
|
||||
|
|
@ -252,6 +264,7 @@ __all__ = [
|
|||
"GroupPolicyConfig",
|
||||
"build_group_sender_context",
|
||||
"build_whatsapp_group_reply_metadata",
|
||||
"metadata_mentions_bot",
|
||||
"normalize_jid",
|
||||
"normalize_jids",
|
||||
"infer_is_group_from_chat_id",
|
||||
|
|
|
|||
|
|
@ -82,6 +82,20 @@ def test_should_accept_group_mention_with_lid_phone_match() -> None:
|
|||
)
|
||||
|
||||
|
||||
def test_should_accept_group_when_sidecar_reports_mentions_bot() -> None:
|
||||
assert (
|
||||
should_process_group_inbound(
|
||||
is_group=True,
|
||||
text="hi",
|
||||
mentions=["unknown-lid@lid"],
|
||||
bot_jid="999@s.whatsapp.net",
|
||||
require_mention=True,
|
||||
metadata={"mentions_bot": True, "raw": {"mentionsBot": True}},
|
||||
)
|
||||
is True
|
||||
)
|
||||
|
||||
|
||||
def test_should_accept_group_reply_to_bot() -> None:
|
||||
assert (
|
||||
should_process_group_inbound(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue