mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-06-15 07:14:08 +00:00
Merge PR #4226: feat(bridge): WhatsApp forwarded message detection, startup guard, and contact handling
feat(bridge): WhatsApp forwarded message detection, startup guard, and contact handling
This commit is contained in:
commit
1b3b322674
@ -30,6 +30,7 @@ export interface InboundMessage {
|
||||
content: string;
|
||||
timestamp: number;
|
||||
isGroup: boolean;
|
||||
isForwarded?: boolean;
|
||||
wasMentioned?: boolean;
|
||||
isReplyToBot?: boolean;
|
||||
media?: string[];
|
||||
@ -97,6 +98,10 @@ export class WhatsAppClient {
|
||||
return { wasMentioned, isReplyToBot };
|
||||
}
|
||||
|
||||
private isForwarded(msg: any): boolean {
|
||||
return this.messageContextInfos(msg).some((info) => Boolean(info?.isForwarded));
|
||||
}
|
||||
|
||||
async connect(): Promise<void> {
|
||||
const logger = pino({ level: 'silent' });
|
||||
const { state, saveCreds } = await useMultiFileAuthState(this.options.authDir);
|
||||
@ -104,6 +109,10 @@ export class WhatsAppClient {
|
||||
|
||||
console.log(`Using Baileys version: ${version.join('.')}`);
|
||||
|
||||
// Record startup time — messages older than this will be ignored
|
||||
// to avoid replaying history on reconnect
|
||||
const startupTimestamp = Math.floor(Date.now() / 1000);
|
||||
|
||||
// Create socket following OpenClaw's pattern
|
||||
this.sock = makeWASocket({
|
||||
auth: {
|
||||
@ -168,6 +177,10 @@ export class WhatsAppClient {
|
||||
if (msg.key.fromMe) continue;
|
||||
if (msg.key.remoteJid === 'status@broadcast') continue;
|
||||
|
||||
// Drop messages older than startup time (avoid replaying history on reconnect)
|
||||
const msgTimestamp = msg.messageTimestamp as number;
|
||||
if (msgTimestamp && msgTimestamp < startupTimestamp) continue;
|
||||
|
||||
const unwrapped = baileysExtractMessageContent(msg.message);
|
||||
if (!unwrapped) continue;
|
||||
|
||||
@ -192,8 +205,24 @@ export class WhatsAppClient {
|
||||
fallbackContent = '[Voice Message]';
|
||||
const path = await this.downloadMedia(msg, unwrapped.audioMessage.mimetype ?? undefined);
|
||||
if (path) mediaPaths.push(path);
|
||||
} else if (unwrapped.contactMessage) {
|
||||
// Single shared contact
|
||||
const displayName = unwrapped.contactMessage.displayName || '';
|
||||
const vcard = unwrapped.contactMessage.vcard || '';
|
||||
fallbackContent = `[Contact: ${displayName}]\n${vcard}`;
|
||||
} else if (unwrapped.contactsArrayMessage) {
|
||||
// Multiple shared contacts
|
||||
const vcards = unwrapped.contactsArrayMessage.contacts || [];
|
||||
const parts = vcards.map((c: any) => {
|
||||
const name = c.displayName || '';
|
||||
const vc = c.vcard || '';
|
||||
return `[Contact: ${name}]\n${vc}`;
|
||||
});
|
||||
fallbackContent = parts.join('\n\n');
|
||||
}
|
||||
|
||||
const isForwarded = this.isForwarded(msg);
|
||||
|
||||
const finalContent = content || (mediaPaths.length === 0 ? fallbackContent : '') || '';
|
||||
if (!finalContent && mediaPaths.length === 0) continue;
|
||||
|
||||
@ -208,6 +237,7 @@ export class WhatsAppClient {
|
||||
content: finalContent,
|
||||
timestamp: msg.messageTimestamp as number,
|
||||
isGroup,
|
||||
...(isForwarded ? { isForwarded } : {}),
|
||||
...(isGroup ? { wasMentioned: wasMentioned || isReplyToBot, isReplyToBot } : {}),
|
||||
...(mediaPaths.length > 0 ? { media: mediaPaths } : {}),
|
||||
});
|
||||
|
||||
@ -290,6 +290,7 @@ class WhatsAppChannel(BaseChannel):
|
||||
"message_id": message_id,
|
||||
"timestamp": data.get("timestamp"),
|
||||
"is_group": data.get("isGroup", False),
|
||||
"is_forwarded": bool(data.get("isForwarded", False)),
|
||||
"participant": participant or None,
|
||||
"is_reply_to_bot": data.get("isReplyToBot", False),
|
||||
},
|
||||
|
||||
@ -291,6 +291,30 @@ async def test_voice_message_transcription_uses_media_path():
|
||||
assert kwargs["content"].startswith("Hello world")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_forwarded_voice_message_preserves_metadata_after_transcription():
|
||||
ch = WhatsAppChannel({"enabled": True, "allowFrom": ["*"]}, MagicMock())
|
||||
ch._handle_message = AsyncMock()
|
||||
ch.transcribe_audio = AsyncMock(return_value="Forwarded audio text")
|
||||
|
||||
await ch._handle_bridge_message(
|
||||
json.dumps({
|
||||
"type": "message",
|
||||
"id": "v-forwarded",
|
||||
"sender": "12345@s.whatsapp.net",
|
||||
"pn": "",
|
||||
"content": "[Voice Message]",
|
||||
"timestamp": 1,
|
||||
"media": ["/tmp/voice.ogg"],
|
||||
"isForwarded": True,
|
||||
})
|
||||
)
|
||||
|
||||
kwargs = ch._handle_message.await_args.kwargs
|
||||
assert kwargs["content"] == "Forwarded audio text"
|
||||
assert kwargs["metadata"]["is_forwarded"] is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unauthorized_voice_message_does_not_transcribe() -> None:
|
||||
ch = WhatsAppChannel({"enabled": True, "allowFrom": ["allowed"]}, MagicMock())
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user