From 42c980778e81626031b6bfb556000f00502dace4 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Sun, 14 Sep 2025 21:35:13 +0300 Subject: [PATCH] send, usync: fix lid Usync (including onWhatsApp), and send phash --- src/Socket/messages-send.ts | 18 ++++++++++++------ src/Utils/generics.ts | 8 ++++++++ src/WAUSync/Protocols/UsyncLIDProtocol.ts | 12 ++++++++++-- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/Socket/messages-send.ts b/src/Socket/messages-send.ts index 91677227..ec80359d 100644 --- a/src/Socket/messages-send.ts +++ b/src/Socket/messages-send.ts @@ -23,6 +23,7 @@ import { encryptMediaRetryRequest, extractDeviceJids, generateMessageIDV2, + generateParticipantHashV2, generateWAMessage, getStatusCodeForMediaRetry, getUrlFromDirectPath, @@ -257,7 +258,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { deviceResults.push({ user, device, - wireJid: jid + wireJid: jid // again this makes no sense }) return null } @@ -311,7 +312,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { const query = new USyncQuery().withContext('message').withDeviceProtocol() for (const jid of toFetch) { - query.withUser(new USyncUser().withId(jid)) + query.withUser(new USyncUser().withId(jid)) // todo: investigate - the idea here is that should have an inline lid field with the lid being the pn equivalent } const result = await sock.executeUSyncQuery(query) @@ -808,6 +809,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { const finalJid = jid // ADDRESSING CONSISTENCY: Match own identity to conversation context + // TODO: investigate if this is true let ownId = meId if (isLid && meLid) { ownId = meLid @@ -844,7 +846,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { additionalAttributes = { ...additionalAttributes, device_fanout: 'false' } } - const { user, device } = jidDecode(participant.jid)! + const { user, device } = jidDecode(participant.jid)! // rajeh: how does this even make sense TODO check out devices.push({ user, device, @@ -883,13 +885,13 @@ export const makeMessagesSocket = (config: SocketConfig) => { } if (normalizeMessageContent(message)?.pinInChatMessage) { - extraAttrs['decrypt-fail'] = 'hide' + extraAttrs['decrypt-fail'] = 'hide' // todo: expand for reactions and other types } if (isGroup || isStatus) { const [groupData, senderKeyMap] = await Promise.all([ (async () => { - let groupData = useCachedGroupMetadata && cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined + let groupData = useCachedGroupMetadata && cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined // todo: should we rely on the cache specially if the cache is outdated and the metadata has new fields? if (groupData && Array.isArray(groupData?.participants)) { logger.trace({ jid, participants: groupData.participants.length }, 'using cached group metadata') } else if (!isStatus) { @@ -900,7 +902,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { })(), (async () => { if (!participant && !isStatus) { - const result = await authState.keys.get('sender-key-memory', [jid]) + const result = await authState.keys.get('sender-key-memory', [jid]) // TODO: check out what if the sender key memory doesn't include the LID stuff now? return result[jid] || {} } @@ -1072,6 +1074,10 @@ export const makeMessagesSocket = (config: SocketConfig) => { participants.push(...meNodes) participants.push(...otherNodes) + if (meJids.length > 0 || otherJids.length > 0) { + extraAttrs['phash'] = generateParticipantHashV2([...meJids, ...otherJids]) + } + shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2 } diff --git a/src/Utils/generics.ts b/src/Utils/generics.ts index ce66dea3..f8531152 100644 --- a/src/Utils/generics.ts +++ b/src/Utils/generics.ts @@ -14,6 +14,7 @@ import type { } from '../Types' import { DisconnectReason } from '../Types' import { type BinaryNode, getAllBinaryNodeChildren, jidDecode } from '../WABinary' +import { sha256 } from './crypto' const PLATFORM_MAP = { aix: 'AIX', @@ -88,6 +89,13 @@ export const unpadRandomMax16 = (e: Uint8Array | Buffer) => { return new Uint8Array(t.buffer, t.byteOffset, t.length - r) } +// code is inspired by whatsmeow +export const generateParticipantHashV2 = (participants: string[]): string => { + participants.sort() + const sha256Hash = sha256(Buffer.from(participants.join(''))).toString('base64') + return '2:' + sha256Hash.slice(0, 6) +} + export const encodeWAMessage = (message: proto.IMessage) => writeRandomPadMax16(proto.Message.encode(message).finish()) export const generateRegistrationId = (): number => { diff --git a/src/WAUSync/Protocols/UsyncLIDProtocol.ts b/src/WAUSync/Protocols/UsyncLIDProtocol.ts index acfa5589..e2e6b58f 100644 --- a/src/WAUSync/Protocols/UsyncLIDProtocol.ts +++ b/src/WAUSync/Protocols/UsyncLIDProtocol.ts @@ -1,5 +1,6 @@ import type { USyncQueryProtocol } from '../../Types/USync' import type { BinaryNode } from '../../WABinary' +import type { USyncUser } from '../USyncUser' export class USyncLIDProtocol implements USyncQueryProtocol { name = 'lid' @@ -11,8 +12,15 @@ export class USyncLIDProtocol implements USyncQueryProtocol { } } - getUserElement(): null { - return null + getUserElement(user: USyncUser): BinaryNode | null { + if (user.lid) { + return { + tag: 'lid', + attrs: { jid: user.lid } + } + } else { + return null + } } parser(node: BinaryNode): string | null {