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
+4 -19
View File
@@ -53,7 +53,7 @@ import {
S_WHATSAPP_NET
} from '../WABinary'
import { USyncQuery, USyncUser } from '../WAUSync'
import { makeUSyncSocket } from './usync'
import { makeSocket } from './socket.js'
const MAX_SYNC_ATTEMPTS = 2
export const makeChatsSocket = (config: SocketConfig) => {
@@ -65,8 +65,8 @@ export const makeChatsSocket = (config: SocketConfig) => {
shouldIgnoreJid,
shouldSyncHistoryMessage
} = config
const sock = makeUSyncSocket(config)
const { ev, ws, authState, generateMessageTag, sendNode, query, onUnexpectedError } = sock
const sock = makeSocket(config)
const { ev, ws, authState, generateMessageTag, sendNode, query, signalRepository, onUnexpectedError } = sock
let privacySettings: { [_: string]: string } | undefined
@@ -221,21 +221,6 @@ export const makeChatsSocket = (config: SocketConfig) => {
return botList
}
const onWhatsApp = async (...jids: string[]) => {
const usyncQuery = new USyncQuery().withContactProtocol().withLIDProtocol()
for (const jid of jids) {
const phone = `+${jid.replace('+', '').split('@')[0]?.split(':')[0]}`
usyncQuery.withUser(new USyncUser().withPhone(phone))
}
const results = await sock.executeUSyncQuery(usyncQuery)
if (results) {
return results.list.filter(a => !!a.contact).map(({ contact, id, lid }) => ({ jid: id, exists: contact, lid }))
}
}
const fetchStatus = async (...jids: string[]) => {
const usyncQuery = new USyncQuery().withStatusProtocol()
@@ -1092,6 +1077,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
}
})(),
processMessage(msg, {
signalRepository,
shouldProcessHistoryMsg,
placeholderResendCache,
ev,
@@ -1195,7 +1181,6 @@ export const makeChatsSocket = (config: SocketConfig) => {
sendPresenceUpdate,
presenceSubscribe,
profilePictureUrl,
onWhatsApp,
fetchBlocklist,
fetchStatus,
fetchDisappearingDuration,
+11 -10
View File
@@ -1,14 +1,14 @@
import { proto } from '../../WAProto/index.js'
import type { GroupMetadata, GroupParticipant, ParticipantAction, SocketConfig, WAMessageKey } from '../Types'
import { WAMessageStubType } from '../Types'
import { WAMessageAddressingMode, WAMessageStubType } from '../Types'
import { generateMessageIDV2, unixTimestampSeconds } from '../Utils'
import {
type BinaryNode,
getBinaryNodeChild,
getBinaryNodeChildren,
getBinaryNodeChildString,
isJidUser,
isLidUser,
isPnUser,
jidEncode,
jidNormalizedUser
} from '../WABinary'
@@ -305,12 +305,12 @@ export const extractGroupMetadata = (result: BinaryNode) => {
let desc: string | undefined
let descId: string | undefined
let descOwner: string | undefined
let descOwnerJid: string | undefined
let descOwnerPn: string | undefined
let descTime: number | undefined
if (descChild) {
desc = getBinaryNodeChildString(descChild, 'body')
descOwner = descChild.attrs.participant ? jidNormalizedUser(descChild.attrs.participant) : undefined
descOwnerJid = descChild.attrs.participant_pn ? jidNormalizedUser(descChild.attrs.participant_pn) : undefined
descOwnerPn = descChild.attrs.participant_pn ? jidNormalizedUser(descChild.attrs.participant_pn) : undefined
descTime = +descChild.attrs.t!
descId = descChild.attrs.id
}
@@ -320,20 +320,21 @@ export const extractGroupMetadata = (result: BinaryNode) => {
const memberAddMode = getBinaryNodeChildString(group, 'member_add_mode') === 'all_member_add'
const metadata: GroupMetadata = {
id: groupId!,
addressingMode: group.attrs.addressing_mode as 'pn' | 'lid',
notify: group.attrs.notify,
addressingMode: group.attrs.addressing_mode === 'lid' ? WAMessageAddressingMode.LID : WAMessageAddressingMode.PN,
subject: group.attrs.subject!,
subjectOwner: group.attrs.s_o,
subjectOwnerJid: group.attrs.s_o_pn,
subjectOwnerPn: group.attrs.s_o_pn,
subjectTime: +group.attrs.s_t!,
size: group.attrs.size ? +group.attrs.size : getBinaryNodeChildren(group, 'participant').length,
creation: +group.attrs.creation!,
owner: group.attrs.creator ? jidNormalizedUser(group.attrs.creator) : undefined,
ownerJid: group.attrs.creator_pn ? jidNormalizedUser(group.attrs.creator_pn) : undefined,
ownerPn: group.attrs.creator_pn ? jidNormalizedUser(group.attrs.creator_pn) : undefined,
owner_country_code: group.attrs.creator_country_code,
desc,
descId,
descOwner,
descOwnerJid,
descOwnerPn,
descTime,
linkedParent: getBinaryNodeChild(group, 'linked_parent')?.attrs.jid || undefined,
restrict: !!getBinaryNodeChild(group, 'locked'),
@@ -345,8 +346,8 @@ export const extractGroupMetadata = (result: BinaryNode) => {
participants: getBinaryNodeChildren(group, 'participant').map(({ attrs }) => {
return {
id: attrs.jid!,
jid: isJidUser(attrs.jid) ? attrs.jid : jidNormalizedUser(attrs.phone_number),
lid: isLidUser(attrs.jid) ? attrs.jid : attrs.lid,
phoneNumber: isLidUser(attrs.jid) && isPnUser(attrs.phoneNumber) ? attrs.phoneNumber : undefined,
lid: isPnUser(attrs.jid) && isLidUser(attrs.lid) ? attrs.lid : undefined,
admin: (attrs.type || null) as GroupParticipant['admin']
}
}),
+39 -54
View File
@@ -50,7 +50,6 @@ import {
getBinaryNodeChildString,
isJidGroup,
isJidStatusBroadcast,
isJidUser,
isLidUser,
jidDecode,
jidNormalizedUser,
@@ -549,6 +548,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
const handleGroupNotification = (participant: string, child: BinaryNode, msg: Partial<proto.IWebMessageInfo>) => {
const participantJid = getBinaryNodeChild(child, 'participant')?.attrs?.jid || participant
// TODO: Add participant LID
switch (child?.tag) {
case 'create':
const metadata = extractGroupMetadata(child)
@@ -697,11 +697,16 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
break
case 'devices':
const devices = getBinaryNodeChildren(child, 'device')
if (areJidsSameUser(child!.attrs.jid, authState.creds.me!.id)) {
const deviceJids = devices.map(d => d.attrs.jid)
logger.info({ deviceJids }, 'got my own devices')
if (
areJidsSameUser(child!.attrs.jid, authState.creds.me!.id) ||
areJidsSameUser(child!.attrs.lid, authState.creds.me!.lid)
) {
const deviceData = devices.map(d => ({ id: d.attrs.jid, lid: d.attrs.lid }))
logger.info({ deviceData }, 'my own devices changed')
}
//TODO: drop a new event, add hashes
break
case 'server_sync':
const update = getBinaryNodeChild(node, 'collection')
@@ -1034,7 +1039,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
// correctly set who is asking for the retry
key.participant = key.participant || attrs.from
const retryNode = getBinaryNodeChild(node, 'retry')
if (ids[0] && key.participant && willSendMessageAgain(ids[0], key.participant!)) {
if (ids[0] && key.participant && willSendMessageAgain(ids[0], key.participant)) {
if (key.fromMe) {
try {
updateSendMessageAgainCount(ids[0], key.participant)
@@ -1142,56 +1147,36 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
msg.message?.protocolMessage?.type === proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER &&
node.attrs.sender_pn
) {
ev.emit('chats.phoneNumberShare', { lid: node.attrs.from!, jid: node.attrs.sender_pn })
const lid = jidNormalizedUser(node.attrs.from),
pn = jidNormalizedUser(node.attrs.sender_pn)
ev.emit('lid-mapping.update', { lid, pn })
await signalRepository.storeLIDPNMapping(lid, pn)
}
if (msg.key?.remoteJid && msg.key?.id && messageRetryManager) {
messageRetryManager.addRecentMessage(msg.key.remoteJid, msg.key.id, msg.message!)
logger.debug(
{
jid: msg.key.remoteJid,
id: msg.key.id
},
'Added message to recent cache for retry receipts'
)
if (msg.message?.protocolMessage?.lidMigrationMappingSyncMessage?.encodedMappingPayload) {
try {
const payload = msg.message.protocolMessage.lidMigrationMappingSyncMessage.encodedMappingPayload
const decoded = proto.LIDMigrationMappingSyncPayload.decode(payload)
logger.debug(
{
mappingCount: decoded.pnToLidMappings?.length || 0,
timestamp: decoded.chatDbMigrationTimestamp
},
'Received LID migration sync message from server'
)
const lidMapping = signalRepository.getLIDMappingStore()
if (decoded.pnToLidMappings && decoded.pnToLidMappings.length > 0) {
for (const mapping of decoded.pnToLidMappings) {
const pn = `${mapping.pn}@s.whatsapp.net`
// Use latestLid if available, otherwise assignedLid (proper LID refresh)
const lidValue = mapping.latestLid || mapping.assignedLid
const lid = `${lidValue}@lid`
await lidMapping.storeLIDPNMapping(lid, pn)
logger.debug(
{
pn,
lid,
assignedLid: mapping.assignedLid,
latestLid: mapping.latestLid,
usedLatest: !!mapping.latestLid
},
'Stored server-provided PN-LID mapping'
)
}
}
} catch (error) {
logger.error({ error }, 'Failed to process LID migration sync message')
const alt = msg.key.participantAlt || msg.key.remoteJidAlt
// store new mappings we didn't have before
if (!!alt) {
const altServer = jidDecode(alt)?.server
const lidMapping = signalRepository.getLIDMappingStore()
if (altServer === 'lid') {
if (!(await lidMapping.getPNForLID(alt))) {
await lidMapping.storeLIDPNMapping(alt, msg.key.participant || msg.key.remoteJid!)
}
} else {
if (!(await lidMapping.getLIDForPN(alt))) {
await lidMapping.storeLIDPNMapping(msg.key.participant || msg.key.remoteJid!, alt)
}
}
if (msg.key?.remoteJid && msg.key?.id && messageRetryManager) {
messageRetryManager.addRecentMessage(msg.key.remoteJid, msg.key.id, msg.message!)
logger.debug(
{
jid: msg.key.remoteJid,
id: msg.key.id
},
'Added message to recent cache for retry receipts'
)
}
try {
@@ -1263,8 +1248,8 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
// message was sent by us from a different device
type = 'sender'
// need to specially handle this case
if (isJidUser(msg.key.remoteJid!)) {
participant = author
if (isLidUser(msg.key.remoteJid!) || isLidUser(msg.key.remoteJidAlt)) {
participant = author // TODO: investigate sending receipts to LIDs and not PNs
}
} else if (!sendActiveReceipts) {
type = 'inactive'
+9 -12
View File
@@ -11,6 +11,7 @@ import type {
SocketConfig,
WAMessageKey
} from '../Types'
import { WAMessageAddressingMode } from '../Types'
import {
aggregateMessageKeysNotFromMe,
assertMediaContent,
@@ -40,16 +41,15 @@ import {
getBinaryNodeChild,
getBinaryNodeChildren,
isJidGroup,
isJidUser,
isPnUser,
jidDecode,
jidEncode,
jidNormalizedUser,
type JidWithDevice,
S_WHATSAPP_NET
S_WHATSAPP_NET,
transferDevice
} from '../WABinary'
import { USyncQuery, USyncUser } from '../WAUSync'
import { makeGroupsSocket } from './groups'
import type { NewsletterSocket } from './newsletter'
import { makeNewsletterSocket } from './newsletter'
export const makeMessagesSocket = (config: SocketConfig) => {
@@ -63,7 +63,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
enableRecentMessageCache,
maxMsgRetryCount
} = config
const sock: NewsletterSocket = makeNewsletterSocket(makeGroupsSocket(config))
const sock = makeNewsletterSocket(config)
const {
ev,
authState,
@@ -147,7 +147,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
node.attrs.t = unixTimestampSeconds().toString()
}
if (type === 'sender' && isJidUser(jid)) {
if (type === 'sender' && isPnUser(jid)) {
node.attrs.recipient = jid
node.attrs.to = participant!
} else {
@@ -445,11 +445,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
if (!jid.includes('@s.whatsapp.net')) return
try {
const jidDecoded = jidDecode(jid)
const deviceId = jidDecoded?.device || 0
const lidDecoded = jidDecode(lidForPN)
const lidWithDevice = jidEncode(lidDecoded?.user!, 'lid', deviceId)
const lidWithDevice = transferDevice(jid, lidForPN)
await signalRepository.migrateSession(jid, lidWithDevice)
logger.debug({ fromJid: jid, toJid: lidWithDevice }, 'Migrated device session to LID')
@@ -904,7 +900,8 @@ export const makeMessagesSocket = (config: SocketConfig) => {
}
if (!isStatus) {
const groupAddressingMode = groupData?.addressingMode || (isLid ? 'lid' : 'pn')
const groupAddressingMode =
groupData?.addressingMode || (isLid ? WAMessageAddressingMode.LID : WAMessageAddressingMode.PN)
additionalAttributes = {
...additionalAttributes,
addressing_mode: groupAddressingMode
+4 -3
View File
@@ -1,9 +1,9 @@
import type { NewsletterCreateResponse, WAMediaUpload } from '../Types'
import type { NewsletterCreateResponse, SocketConfig, WAMediaUpload } from '../Types'
import type { NewsletterMetadata, NewsletterUpdate } from '../Types'
import { QueryIds, XWAPaths } from '../Types'
import { generateProfilePicture } from '../Utils/messages-media'
import { getBinaryNodeChild } from '../WABinary'
import type { GroupsSocket } from './groups'
import { makeGroupsSocket } from './groups'
import { executeWMexQuery as genericExecuteWMexQuery } from './mex'
const parseNewsletterCreateResponse = (response: NewsletterCreateResponse): NewsletterMetadata => {
@@ -41,7 +41,8 @@ const parseNewsletterMetadata = (result: unknown): NewsletterMetadata | null =>
return null
}
export const makeNewsletterSocket = (sock: GroupsSocket) => {
export const makeNewsletterSocket = (config: SocketConfig) => {
const sock = makeGroupsSocket(config)
const { query, generateMessageTag } = sock
const executeWMexQuery = <T>(variables: Record<string, unknown>, queryId: string, dataPath: string): Promise<T> => {
+170 -93
View File
@@ -43,6 +43,7 @@ import {
jidEncode,
S_WHATSAPP_NET
} from '../WABinary'
import { USyncQuery, USyncUser } from '../WAUSync/'
import { WebSocketClient } from './Client'
/**
@@ -67,6 +68,9 @@ export const makeSocket = (config: SocketConfig) => {
makeSignalRepository
} = config
const uqTagId = generateMdTagPrefix()
const generateMessageTag = () => `${uqTagId}${epoch++}`
if (printQRInTerminal) {
console.warn(
'⚠️ The printQRInTerminal option has been deprecated. You will no longer receive QR codes in the terminal automatically. Please listen to the connection.update event yourself and handle the QR your way. You can remove this message by removing this opttion. This message will be removed in a future version.'
@@ -83,98 +87,6 @@ export const makeSocket = (config: SocketConfig) => {
url.searchParams.append('ED', authState.creds.routingInfo.toString('base64url'))
}
const ws = new WebSocketClient(url, config)
ws.connect()
const ev = makeEventBuffer(logger)
/** ephemeral key pair used to encrypt/decrypt communication. Unique for each connection */
const ephemeralKeyPair = Curve.generateKeyPair()
/** WA noise protocol wrapper */
const noise = makeNoiseHandler({
keyPair: ephemeralKeyPair,
NOISE_HEADER: NOISE_WA_HEADER,
logger,
routingInfo: authState?.creds?.routingInfo
})
const { creds } = authState
// add transaction capability
const keys = addTransactionCapability(authState.keys, logger, transactionOpts)
const signalRepository = makeSignalRepository({ creds, keys })
let lastDateRecv: Date
let epoch = 1
let keepAliveReq: NodeJS.Timeout
let qrTimer: NodeJS.Timeout
let closed = false
const uqTagId = generateMdTagPrefix()
const generateMessageTag = () => `${uqTagId}${epoch++}`
const sendPromise = promisify(ws.send)
/** send a raw buffer */
const sendRawMessage = async (data: Uint8Array | Buffer) => {
if (!ws.isOpen) {
throw new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed })
}
const bytes = noise.encodeFrame(data)
await promiseTimeout<void>(connectTimeoutMs, async (resolve, reject) => {
try {
await sendPromise.call(ws, bytes)
resolve()
} catch (error) {
reject(error)
}
})
}
/** send a binary node */
const sendNode = (frame: BinaryNode) => {
if (logger.level === 'trace') {
logger.trace({ xml: binaryNodeToString(frame), msg: 'xml send' })
}
const buff = encodeBinaryNode(frame)
return sendRawMessage(buff)
}
/** log & process any unexpected errors */
const onUnexpectedError = (err: Error | Boom, msg: string) => {
logger.error({ err }, `unexpected error in '${msg}'`)
}
/** await the next incoming message */
const awaitNextMessage = async <T>(sendMsg?: Uint8Array) => {
if (!ws.isOpen) {
throw new Boom('Connection Closed', {
statusCode: DisconnectReason.connectionClosed
})
}
let onOpen: (data: T) => void
let onClose: (err: Error) => void
const result = promiseTimeout<T>(connectTimeoutMs, (resolve, reject) => {
onOpen = resolve
onClose = mapWebSocketError(reject)
ws.on('frame', onOpen)
ws.on('close', onClose)
ws.on('error', onClose)
}).finally(() => {
ws.off('frame', onOpen)
ws.off('close', onClose)
ws.off('error', onClose)
})
if (sendMsg) {
sendRawMessage(sendMsg).catch(onClose!)
}
return result
}
/**
* Wait for a message with a certain tag to be received
* @param msgId the message tag to await
@@ -244,6 +156,169 @@ export const makeSocket = (config: SocketConfig) => {
return result
}
const executeUSyncQuery = async (usyncQuery: USyncQuery) => {
if (usyncQuery.protocols.length === 0) {
throw new Boom('USyncQuery must have at least one protocol')
}
// todo: validate users, throw WARNING on no valid users
// variable below has only validated users
const validUsers = usyncQuery.users
const userNodes = validUsers.map(user => {
return {
tag: 'user',
attrs: {
jid: !user.phone ? user.id : undefined
},
content: usyncQuery.protocols.map(a => a.getUserElement(user)).filter(a => a !== null)
} as BinaryNode
})
const listNode: BinaryNode = {
tag: 'list',
attrs: {},
content: userNodes
}
const queryNode: BinaryNode = {
tag: 'query',
attrs: {},
content: usyncQuery.protocols.map(a => a.getQueryElement())
}
const iq = {
tag: 'iq',
attrs: {
to: S_WHATSAPP_NET,
type: 'get',
xmlns: 'usync'
},
content: [
{
tag: 'usync',
attrs: {
context: usyncQuery.context,
mode: usyncQuery.mode,
sid: generateMessageTag(),
last: 'true',
index: '0'
},
content: [queryNode, listNode]
}
]
}
const result = await query(iq)
return usyncQuery.parseUSyncQueryResult(result)
}
const onWhatsApp = async (...jids: string[]) => {
const usyncQuery = new USyncQuery().withContactProtocol().withLIDProtocol()
for (const jid of jids) {
const phone = `+${jid.replace('+', '').split('@')[0]?.split(':')[0]}`
usyncQuery.withUser(new USyncUser().withPhone(phone))
}
const results = await executeUSyncQuery(usyncQuery)
if (results) {
return results.list
.filter(a => !!a.contact)
.map(({ contact, id, lid }) => ({ jid: id, exists: contact as boolean, lid: lid as string }))
}
}
const ws = new WebSocketClient(url, config)
ws.connect()
const ev = makeEventBuffer(logger)
/** ephemeral key pair used to encrypt/decrypt communication. Unique for each connection */
const ephemeralKeyPair = Curve.generateKeyPair()
/** WA noise protocol wrapper */
const noise = makeNoiseHandler({
keyPair: ephemeralKeyPair,
NOISE_HEADER: NOISE_WA_HEADER,
logger,
routingInfo: authState?.creds?.routingInfo
})
const { creds } = authState
// add transaction capability
const keys = addTransactionCapability(authState.keys, logger, transactionOpts)
const signalRepository = makeSignalRepository({ creds, keys }, onWhatsApp)
let lastDateRecv: Date
let epoch = 1
let keepAliveReq: NodeJS.Timeout
let qrTimer: NodeJS.Timeout
let closed = false
const sendPromise = promisify(ws.send)
/** send a raw buffer */
const sendRawMessage = async (data: Uint8Array | Buffer) => {
if (!ws.isOpen) {
throw new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed })
}
const bytes = noise.encodeFrame(data)
await promiseTimeout<void>(connectTimeoutMs, async (resolve, reject) => {
try {
await sendPromise.call(ws, bytes)
resolve()
} catch (error) {
reject(error)
}
})
}
/** send a binary node */
const sendNode = (frame: BinaryNode) => {
if (logger.level === 'trace') {
logger.trace({ xml: binaryNodeToString(frame), msg: 'xml send' })
}
const buff = encodeBinaryNode(frame)
return sendRawMessage(buff)
}
/** log & process any unexpected errors */
const onUnexpectedError = (err: Error | Boom, msg: string) => {
logger.error({ err }, `unexpected error in '${msg}'`)
}
/** await the next incoming message */
const awaitNextMessage = async <T>(sendMsg?: Uint8Array) => {
if (!ws.isOpen) {
throw new Boom('Connection Closed', {
statusCode: DisconnectReason.connectionClosed
})
}
let onOpen: (data: T) => void
let onClose: (err: Error) => void
const result = promiseTimeout<T>(connectTimeoutMs, (resolve, reject) => {
onOpen = resolve
onClose = mapWebSocketError(reject)
ws.on('frame', onOpen)
ws.on('close', onClose)
ws.on('error', onClose)
}).finally(() => {
ws.off('frame', onOpen)
ws.off('close', onClose)
ws.off('error', onClose)
})
if (sendMsg) {
sendRawMessage(sendMsg).catch(onClose!)
}
return result
}
/** connection handshake */
const validateConnection = async () => {
let helloMsg: proto.IHandshakeMessage = {
@@ -878,7 +953,9 @@ export const makeSocket = (config: SocketConfig) => {
requestPairingCode,
/** Waits for the connection to WA to reach a state */
waitForConnectionUpdate: bindWaitForConnectionUpdate(ev),
sendWAMBuffer
sendWAMBuffer,
executeUSyncQuery,
onWhatsApp
}
}
-73
View File
@@ -1,73 +0,0 @@
import { Boom } from '@hapi/boom'
import type { SocketConfig } from '../Types'
import { type BinaryNode, S_WHATSAPP_NET } from '../WABinary'
import { USyncQuery } from '../WAUSync'
import { makeSocket } from './socket'
export const makeUSyncSocket = (config: SocketConfig) => {
const sock = makeSocket(config)
const { generateMessageTag, query } = sock
const executeUSyncQuery = async (usyncQuery: USyncQuery) => {
if (usyncQuery.protocols.length === 0) {
throw new Boom('USyncQuery must have at least one protocol')
}
// todo: validate users, throw WARNING on no valid users
// variable below has only validated users
const validUsers = usyncQuery.users
const userNodes = validUsers.map(user => {
return {
tag: 'user',
attrs: {
jid: !user.phone ? user.id : undefined
},
content: usyncQuery.protocols.map(a => a.getUserElement(user)).filter(a => a !== null)
} as BinaryNode
})
const listNode: BinaryNode = {
tag: 'list',
attrs: {},
content: userNodes
}
const queryNode: BinaryNode = {
tag: 'query',
attrs: {},
content: usyncQuery.protocols.map(a => a.getQueryElement())
}
const iq = {
tag: 'iq',
attrs: {
to: S_WHATSAPP_NET,
type: 'get',
xmlns: 'usync'
},
content: [
{
tag: 'usync',
attrs: {
context: usyncQuery.context,
mode: usyncQuery.mode,
sid: generateMessageTag(),
last: 'true',
index: '0'
},
content: [queryNode, listNode]
}
]
}
const result = await query(iq)
return usyncQuery.parseUSyncQueryResult(result)
}
return {
...sock,
executeUSyncQuery
}
}