lid, wip: Support LIDs in Baileys (#1747)

* lid-mapping: get missing lid from usync

* lid-mapping, jid-utils: change to isPnUser and store multiple mappings

* process-message: parse protocolMsg mapping, and store from new msgs

* types: lid-mapping event, addressing enum, alt, contact, group types

* validate, decode: use lid for identity, better logic

* lid: final commit

* linting

* linting

* linting

* linting

* misc: fix testing and also remove version json

* lint: IDE fucking up lint

* lid-mapping: fix build error on NPM

* message-retry: fix proto import
This commit is contained in:
Rajeh Taher
2025-09-08 10:03:28 +03:00
committed by GitHub
parent ca22ae5f9c
commit 20693a59d0
27 changed files with 630 additions and 405 deletions
+2 -9
View File
@@ -19,14 +19,7 @@ import {
LabelAssociationType,
type MessageLabelAssociation
} from '../Types/LabelAssociation'
import {
type BinaryNode,
getBinaryNodeChild,
getBinaryNodeChildren,
isJidGroup,
isJidUser,
jidNormalizedUser
} from '../WABinary'
import { type BinaryNode, getBinaryNodeChild, getBinaryNodeChildren, isJidGroup, jidNormalizedUser } from '../WABinary'
import { aesDecrypt, aesEncrypt, hkdf, hmacSign } from './crypto'
import { toNumber } from './generics'
import type { ILogger } from './logger'
@@ -845,7 +838,7 @@ export const processSyncAction = (
id: id!,
name: action.contactAction.fullName!,
lid: action.contactAction.lidJid || undefined,
jid: isJidUser(id) ? id : undefined
phoneNumber: action.contactAction.pnJid || undefined
}
])
} else if (action?.pushNameSetting) {
+29 -31
View File
@@ -6,14 +6,12 @@ import {
type BinaryNode,
isJidBroadcast,
isJidGroup,
isJidMetaIa,
isJidMetaAI,
isJidNewsletter,
isJidStatusBroadcast,
isJidUser,
isLidUser,
jidDecode,
jidEncode,
jidNormalizedUser
isPnUser,
transferDevice
} from '../WABinary'
import { unpadRandomMax16 } from './generics'
import type { ILogger } from './logger'
@@ -23,17 +21,7 @@ const getDecryptionJid = async (sender: string, repository: SignalRepository): P
return sender
}
const lidMapping = repository.getLIDMappingStore()
const normalizedSender = jidNormalizedUser(sender)
const lidForPN = await lidMapping.getLIDForPN(normalizedSender)
if (lidForPN?.includes('@lid')) {
const senderDecoded = jidDecode(sender)
const deviceId = senderDecoded?.device || 0
return jidEncode(jidDecode(lidForPN)!.user, 'lid', deviceId)
}
return sender
return (await repository.getLIDMappingStore().getLIDForPN(sender))!
}
const storeMappingFromEnvelope = async (
@@ -45,7 +33,7 @@ const storeMappingFromEnvelope = async (
): Promise<void> => {
const { senderAlt } = extractAddressingContext(stanza)
if (senderAlt && isLidUser(senderAlt) && isJidUser(sender) && decryptionJid === sender) {
if (senderAlt && isLidUser(senderAlt) && isPnUser(sender) && decryptionJid === sender) {
try {
await repository.storeLIDPNMapping(senderAlt, sender)
logger.debug({ sender, senderAlt }, 'Stored LID mapping from envelope')
@@ -95,14 +83,23 @@ export const extractAddressingContext = (stanza: BinaryNode) => {
let senderAlt: string | undefined
let recipientAlt: string | undefined
const sender = stanza.attrs.participant || stanza.attrs.from
if (addressingMode === 'lid') {
// Message is LID-addressed: sender is LID, extract corresponding PN
senderAlt = stanza.attrs.participant_pn || stanza.attrs.sender_pn
// without device data
senderAlt = stanza.attrs.participant_pn || stanza.attrs.sender_pn || stanza.attrs.peer_recipient_pn
recipientAlt = stanza.attrs.recipient_pn
// with device data
if (sender && senderAlt) senderAlt = transferDevice(sender, senderAlt)
} else {
// Message is PN-addressed: sender is PN, extract corresponding LID
senderAlt = stanza.attrs.participant_lid || stanza.attrs.sender_lid
// without device data
senderAlt = stanza.attrs.participant_lid || stanza.attrs.sender_lid || stanza.attrs.peer_recipient_lid
recipientAlt = stanza.attrs.recipient_lid
//with device data
if (sender && senderAlt) senderAlt = transferDevice(sender, senderAlt)
}
return {
@@ -126,11 +123,13 @@ export function decodeMessageNode(stanza: BinaryNode, meId: string, meLid: strin
const participant: string | undefined = stanza.attrs.participant
const recipient: string | undefined = stanza.attrs.recipient
const addressingContext = extractAddressingContext(stanza)
const isMe = (jid: string) => areJidsSameUser(jid, meId)
const isMeLid = (jid: string) => areJidsSameUser(jid, meLid)
if (isJidUser(from) || isLidUser(from)) {
if (recipient && !isJidMetaIa(recipient)) {
if (isPnUser(from) || isLidUser(from)) {
if (recipient && !isJidMetaAI(recipient)) {
if (!isMe(from!) && !isMeLid(from!)) {
throw new Boom('receipient present, but msg not from me', { data: stanza })
}
@@ -177,13 +176,11 @@ export function decodeMessageNode(stanza: BinaryNode, meId: string, meLid: strin
const key: WAMessageKey = {
remoteJid: chatId,
remoteJidAlt: !isJidGroup(chatId) ? addressingContext.senderAlt : undefined,
fromMe,
id: msgId,
senderLid: stanza?.attrs?.sender_lid,
senderPn: stanza?.attrs?.sender_pn || stanza?.attrs?.peer_recipient_pn,
participant,
participantPn: stanza?.attrs?.participant_pn,
participantLid: stanza?.attrs?.participant_lid,
participantAlt: isJidGroup(chatId) ? addressingContext.senderAlt : undefined,
...(msgType === 'newsletter' && stanza.attrs.server_id ? { server_id: stanza.attrs.server_id } : {})
}
@@ -228,7 +225,7 @@ export const decryptMessageNode = (
}
if (tag === 'unavailable' && attrs.type === 'view_once') {
fullMessage.key.isViewOnce = true
fullMessage.key.isViewOnce = true // TODO: remove from here and add a STUB TYPE
}
if (tag !== 'enc' && tag !== 'plaintext') {
@@ -243,6 +240,12 @@ export const decryptMessageNode = (
let msgBuffer: Uint8Array
const user = isPnUser(sender) ? sender : author // TODO: flaky logic
const decryptionJid = await getDecryptionJid(user, repository)
if (tag !== 'plaintext') {
await storeMappingFromEnvelope(stanza, user, decryptionJid, repository, logger)
}
try {
const e2eType = tag === 'plaintext' ? 'plaintext' : attrs.type
@@ -256,16 +259,11 @@ export const decryptMessageNode = (
break
case 'pkmsg':
case 'msg':
const user = isJidUser(sender) ? sender : author
const decryptionJid = await getDecryptionJid(user, repository)
msgBuffer = await repository.decryptMessage({
jid: decryptionJid,
type: e2eType,
ciphertext: content
})
await storeMappingFromEnvelope(stanza, user, decryptionJid, repository, logger)
break
case 'plaintext':
msgBuffer = content
+1 -2
View File
@@ -3,8 +3,7 @@ import axios, { type AxiosRequestConfig } from 'axios'
import { createHash, randomBytes } from 'crypto'
import { platform, release } from 'os'
import { proto } from '../../WAProto/index.js'
import version from '../Defaults/baileys-version.json' with { type: 'json' }
const baileysVersion = version.version
const baileysVersion = [2, 3000, 1023223821]
import type {
BaileysEventEmitter,
BaileysEventMap,
+1 -2
View File
@@ -4,7 +4,6 @@ import { inflate } from 'zlib'
import { proto } from '../../WAProto/index.js'
import type { Chat, Contact } from '../Types'
import { WAMessageStubType } from '../Types'
import { isJidUser } from '../WABinary'
import { toNumber } from './generics'
import { normalizeMessageContent } from './messages'
import { downloadContentFromMessage } from './messages-media'
@@ -42,7 +41,7 @@ export const processHistoryMessage = (item: proto.IHistorySync) => {
id: chat.id,
name: chat.name || undefined,
lid: chat.lidJid || undefined,
jid: isJidUser(chat.id) ? chat.id : undefined
phoneNumber: chat.pnJid || undefined
})
const msgs = chat.messages || []
+1 -1
View File
@@ -1,5 +1,5 @@
import { LRUCache } from 'lru-cache'
import { proto } from '../../WAProto'
import type { proto } from '../../WAProto/index.js'
import type { ILogger } from './logger'
/** Number of sent messages to cache in memory for handling retry receipts */
+26 -2
View File
@@ -9,7 +9,8 @@ import type {
ParticipantAction,
RequestJoinAction,
RequestJoinMethod,
SignalKeyStoreWithTransaction
SignalKeyStoreWithTransaction,
SignalRepository
} from '../Types'
import { WAMessageStubType } from '../Types'
import { getContentType, normalizeMessageContent } from '../Utils/messages'
@@ -27,6 +28,7 @@ type ProcessMessageContext = {
ev: BaileysEventEmitter
logger?: ILogger
options: AxiosRequestConfig<{}>
signalRepository: SignalRepository
}
const REAL_MSG_STUB_TYPES = new Set([
@@ -145,7 +147,16 @@ export function decryptPollVote(
const processMessage = async (
message: proto.IWebMessageInfo,
{ shouldProcessHistoryMsg, placeholderResendCache, ev, creds, keyStore, logger, options }: ProcessMessageContext
{
shouldProcessHistoryMsg,
placeholderResendCache,
ev,
creds,
signalRepository,
keyStore,
logger,
options
}: ProcessMessageContext
) => {
const meId = creds.me!.id
const { accountSettings } = creds
@@ -292,6 +303,19 @@ const processMessage = async (
}
])
break
case proto.Message.ProtocolMessage.Type.LID_MIGRATION_MAPPING_SYNC:
const lidMappingStore = signalRepository.getLIDMappingStore()
const encodedPayload = protocolMsg.lidMigrationMappingSyncMessage?.encodedMappingPayload!
const { pnToLidMappings, chatDbMigrationTimestamp } =
proto.LIDMigrationMappingSyncPayload.decode(encodedPayload)
logger?.debug({ pnToLidMappings, chatDbMigrationTimestamp }, 'got lid mappings and chat db migration timestamp')
const pairs = []
for (const { pn, latestLid, assignedLid } of pnToLidMappings) {
const lid = latestLid || assignedLid
pairs.push({ lid: `${lid}@lid`, pn: `${pn}@s.whatsapp.net` })
}
await lidMappingStore.storeLIDPNMappings(pairs)
}
} else if (content?.reactionMessage) {
const reaction: proto.IReaction = {
+3 -2
View File
@@ -133,6 +133,7 @@ export const configureSuccessfulPairing = (
const bizName = businessNode?.attrs.name
const jid = deviceNode.attrs.jid
const lid = deviceNode.attrs.lid
const { details, hmac, accountType } = proto.ADVSignedDeviceIdentityHMAC.decode(deviceIdentityNode.content as Buffer)
const isHostedAccount = accountType !== undefined && accountType === proto.ADVEncryptionType.HOSTED
@@ -154,7 +155,7 @@ export const configureSuccessfulPairing = (
const deviceMsg = Buffer.concat([devicePrefix, deviceDetails, signedIdentityKey.public, accountSignatureKey])
account.deviceSignature = Curve.sign(signedIdentityKey.private, deviceMsg)
const identity = createSignalIdentity(jid!, accountSignatureKey)
const identity = createSignalIdentity(lid!, accountSignatureKey)
const accountEnc = encodeSignedDeviceIdentity(account, false)
const deviceIdentity = proto.ADVDeviceIdentity.decode(account.details)
@@ -183,7 +184,7 @@ export const configureSuccessfulPairing = (
const authUpdate: Partial<AuthenticationCreds> = {
account,
me: { id: jid!, name: bizName },
me: { id: jid!, name: bizName, lid },
signalIdentities: [...(signalIdentities || []), identity],
platform: platformNode?.attrs.name
}