fix(types): remove non-null assertions across 14 files
Files fixed: - messages-recv.ts: 58 assertions → null guards, meId extraction, optional chaining - process-message.ts: 38 assertions → remoteJid/participant guards, proto field checks - chat-utils.ts: 31 assertions → proto field validation with Boom errors - messages-send.ts: 20 assertions → meId extraction, participant guards - chats.ts: 15 assertions → me guard, firstChild guard, jid guards - messages.ts: 14 assertions → originalFilePath guard, key guards - groups.ts: 12 assertions → attrs fallbacks with ?? - validate-connection.ts: 9 assertions → grouped proto field validation - generics.ts: 1 assertion → versionLine guard - history.ts: 2 assertions → key guards - libsignal.ts: 1 assertion → deviceId guard - sender-key-message.ts: 3 assertions → proto guards - UsyncBotProfileProtocol.ts: 2 assertions → attrs guards - example.ts: 3 assertions → optional chaining https://claude.ai/code/session_01E2cfX1N3sJgCJBTvzGazSG
This commit is contained in:
+3
-3
@@ -149,11 +149,11 @@ const startSock = async() => {
|
|||||||
|
|
||||||
// go to an old chat and send this
|
// go to an old chat and send this
|
||||||
if (text == "onDemandHistSync") {
|
if (text == "onDemandHistSync") {
|
||||||
const messageId = await sock.fetchMessageHistory(50, msg.key, msg.messageTimestamp!)
|
const messageId = await sock.fetchMessageHistory(50, msg.key, msg.messageTimestamp ?? 0)
|
||||||
logger.debug({ id: messageId }, 'requested on-demand history resync')
|
logger.debug({ id: messageId }, 'requested on-demand history resync')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!msg.key.fromMe && doReplies && !isJidNewsletter(msg.key?.remoteJid!)) {
|
if (!msg.key.fromMe && doReplies && !isJidNewsletter(msg.key?.remoteJid ?? '')) {
|
||||||
const id = generateMessageIDV2(sock.user?.id)
|
const id = generateMessageIDV2(sock.user?.id)
|
||||||
logger.debug({id, orig_id: msg.key.id }, 'replying to message')
|
logger.debug({id, orig_id: msg.key.id }, 'replying to message')
|
||||||
await sock.sendMessage(msg.key.remoteJid!, { text: 'pong '+msg.key.id }, {messageId: id })
|
await sock.sendMessage(msg.key.remoteJid!, { text: 'pong '+msg.key.id }, {messageId: id })
|
||||||
@@ -208,7 +208,7 @@ const startSock = async() => {
|
|||||||
if(typeof contact.imgUrl !== 'undefined') {
|
if(typeof contact.imgUrl !== 'undefined') {
|
||||||
const newUrl = contact.imgUrl === null
|
const newUrl = contact.imgUrl === null
|
||||||
? null
|
? null
|
||||||
: await sock!.profilePictureUrl(contact.id!).catch(() => null)
|
: await sock.profilePictureUrl(contact.id ?? '').catch(() => null)
|
||||||
logger.debug({id: contact.id, newUrl}, `contact has a new profile pic` )
|
logger.debug({id: contact.id, newUrl}, `contact has a new profile pic` )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export class SenderKeyMessage extends CiphertextMessage {
|
|||||||
super()
|
super()
|
||||||
|
|
||||||
if (serialized) {
|
if (serialized) {
|
||||||
const version = serialized[0]!
|
const version = serialized[0] ?? 0
|
||||||
const message = serialized.slice(1, serialized.length - this.SIGNATURE_LENGTH)
|
const message = serialized.slice(1, serialized.length - this.SIGNATURE_LENGTH)
|
||||||
const signature = serialized.slice(-1 * this.SIGNATURE_LENGTH)
|
const signature = serialized.slice(-1 * this.SIGNATURE_LENGTH)
|
||||||
const senderKeyMessage = proto.SenderKeyMessage.decode(message).toJSON() as SenderKeyMessageStructure
|
const senderKeyMessage = proto.SenderKeyMessage.decode(message).toJSON() as SenderKeyMessageStructure
|
||||||
@@ -42,22 +42,26 @@ export class SenderKeyMessage extends CiphertextMessage {
|
|||||||
: senderKeyMessage.ciphertext
|
: senderKeyMessage.ciphertext
|
||||||
this.signature = signature
|
this.signature = signature
|
||||||
} else {
|
} else {
|
||||||
|
if (ciphertext == null || keyId == null || iteration == null || signatureKey == null) {
|
||||||
|
throw new Error('Missing required parameters for SenderKeyMessage construction')
|
||||||
|
}
|
||||||
|
|
||||||
const version = (((this.CURRENT_VERSION << 4) | this.CURRENT_VERSION) & 0xff) % 256
|
const version = (((this.CURRENT_VERSION << 4) | this.CURRENT_VERSION) & 0xff) % 256
|
||||||
const ciphertextBuffer = Buffer.from(ciphertext!)
|
const ciphertextBuffer = Buffer.from(ciphertext)
|
||||||
const message = proto.SenderKeyMessage.encode(
|
const message = proto.SenderKeyMessage.encode(
|
||||||
proto.SenderKeyMessage.create({
|
proto.SenderKeyMessage.create({
|
||||||
id: keyId!,
|
id: keyId,
|
||||||
iteration: iteration!,
|
iteration: iteration,
|
||||||
ciphertext: ciphertextBuffer
|
ciphertext: ciphertextBuffer
|
||||||
})
|
})
|
||||||
).finish()
|
).finish()
|
||||||
|
|
||||||
const signature = this.getSignature(signatureKey!, Buffer.concat([Buffer.from([version]), message]))
|
const signature = this.getSignature(signatureKey, Buffer.concat([Buffer.from([version]), message]))
|
||||||
|
|
||||||
this.serialized = Buffer.concat([Buffer.from([version]), message, Buffer.from(signature)])
|
this.serialized = Buffer.concat([Buffer.from([version]), message, Buffer.from(signature)])
|
||||||
this.messageVersion = this.CURRENT_VERSION
|
this.messageVersion = this.CURRENT_VERSION
|
||||||
this.keyId = keyId!
|
this.keyId = keyId
|
||||||
this.iteration = iteration!
|
this.iteration = iteration
|
||||||
this.ciphertext = ciphertextBuffer
|
this.ciphertext = ciphertextBuffer
|
||||||
this.signature = signature
|
this.signature = signature
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-7
@@ -493,7 +493,12 @@ export function makeLibSignalRepository(
|
|||||||
return { migrated: 0, skipped: 0, total: 1 }
|
return { migrated: 0, skipped: 0, total: 1 }
|
||||||
}
|
}
|
||||||
|
|
||||||
const { user } = jidDecode(fromJid)!
|
const decoded1 = jidDecode(fromJid)
|
||||||
|
if (!decoded1) {
|
||||||
|
return { migrated: 0, skipped: 0, total: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
const { user } = decoded1
|
||||||
|
|
||||||
logger.debug({ fromJid }, 'bulk device migration - loading all user devices')
|
logger.debug({ fromJid }, 'bulk device migration - loading all user devices')
|
||||||
|
|
||||||
@@ -503,7 +508,7 @@ export function makeLibSignalRepository(
|
|||||||
return { migrated: 0, skipped: 0, total: 0 }
|
return { migrated: 0, skipped: 0, total: 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
const { device: fromDevice } = jidDecode(fromJid)!
|
const { device: fromDevice } = decoded1
|
||||||
const fromDeviceStr = fromDevice?.toString() || '0'
|
const fromDeviceStr = fromDevice?.toString() || '0'
|
||||||
if (!userDevices.includes(fromDeviceStr)) {
|
if (!userDevices.includes(fromDeviceStr)) {
|
||||||
userDevices.push(fromDeviceStr)
|
userDevices.push(fromDeviceStr)
|
||||||
@@ -562,8 +567,11 @@ export function makeLibSignalRepository(
|
|||||||
|
|
||||||
const migrationOps: MigrationOp[] = deviceJids.map(jid => {
|
const migrationOps: MigrationOp[] = deviceJids.map(jid => {
|
||||||
const lidWithDevice = transferDevice(jid, toJid)
|
const lidWithDevice = transferDevice(jid, toJid)
|
||||||
const fromDecoded = jidDecode(jid)!
|
const fromDecoded = jidDecode(jid)
|
||||||
const toDecoded = jidDecode(lidWithDevice)!
|
const toDecoded = jidDecode(lidWithDevice)
|
||||||
|
if (!fromDecoded || !toDecoded) {
|
||||||
|
throw new Error(`Failed to decode JID during migration: ${jid} -> ${lidWithDevice}`)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fromJid: jid,
|
fromJid: jid,
|
||||||
@@ -630,7 +638,10 @@ export function makeLibSignalRepository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const jidToSignalProtocolAddress = (jid: string): libsignal.ProtocolAddress => {
|
const jidToSignalProtocolAddress = (jid: string): libsignal.ProtocolAddress => {
|
||||||
const decoded = jidDecode(jid)!
|
const decoded = jidDecode(jid)
|
||||||
|
if (!decoded) {
|
||||||
|
throw new Error(`Failed to decode JID: "${jid}"`)
|
||||||
|
}
|
||||||
const { user, device, server, domainType } = decoded
|
const { user, device, server, domainType } = decoded
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
@@ -688,12 +699,16 @@ function signalStorage(
|
|||||||
const resolveLIDSignalAddress = async (id: string): Promise<string> => {
|
const resolveLIDSignalAddress = async (id: string): Promise<string> => {
|
||||||
if (id.includes('.')) {
|
if (id.includes('.')) {
|
||||||
const [deviceId, device] = id.split('.')
|
const [deviceId, device] = id.split('.')
|
||||||
const [user, domainType_] = deviceId!.split('_')
|
if (!deviceId) {
|
||||||
|
throw new Error('Missing device ID')
|
||||||
|
}
|
||||||
|
|
||||||
|
const [user, domainType_] = deviceId.split('_')
|
||||||
const domainType = parseInt(domainType_ || '0')
|
const domainType = parseInt(domainType_ || '0')
|
||||||
|
|
||||||
if (domainType === WAJIDDomains.LID || domainType === WAJIDDomains.HOSTED_LID) return id
|
if (domainType === WAJIDDomains.LID || domainType === WAJIDDomains.HOSTED_LID) return id
|
||||||
|
|
||||||
const pnJid = `${user!}${device !== '0' ? `:${device}` : ''}@${domainType === WAJIDDomains.HOSTED ? 'hosted' : 's.whatsapp.net'}`
|
const pnJid = `${user ?? ''}${device !== '0' ? `:${device}` : ''}@${domainType === WAJIDDomains.HOSTED ? 'hosted' : 's.whatsapp.net'}`
|
||||||
|
|
||||||
const lidForPN = await lidMapping.getLIDForPN(pnJid)
|
const lidForPN = await lidMapping.getLIDForPN(pnJid)
|
||||||
if (lidForPN) {
|
if (lidForPN) {
|
||||||
|
|||||||
+33
-15
@@ -272,9 +272,12 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
for (const section of getBinaryNodeChildren(botNode, 'section')) {
|
for (const section of getBinaryNodeChildren(botNode, 'section')) {
|
||||||
if (section.attrs.type === 'all') {
|
if (section.attrs.type === 'all') {
|
||||||
for (const bot of getBinaryNodeChildren(section, 'bot')) {
|
for (const bot of getBinaryNodeChildren(section, 'bot')) {
|
||||||
|
const jid = bot.attrs.jid
|
||||||
|
const personaId = bot.attrs['persona_id']
|
||||||
|
if (!jid || !personaId) continue
|
||||||
botList.push({
|
botList.push({
|
||||||
jid: bot.attrs.jid!,
|
jid,
|
||||||
personaId: bot.attrs['persona_id']!
|
personaId
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -322,7 +325,9 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jidNormalizedUser(jid) !== jidNormalizedUser(authState.creds.me!.id)) {
|
const me = authState.creds.me
|
||||||
|
if (!me) throw new Boom('Not authenticated', { statusCode: 401 })
|
||||||
|
if (jidNormalizedUser(jid) !== jidNormalizedUser(me.id)) {
|
||||||
targetJid = jidNormalizedUser(jid) // in case it is someone other than us
|
targetJid = jidNormalizedUser(jid) // in case it is someone other than us
|
||||||
} else {
|
} else {
|
||||||
targetJid = undefined
|
targetJid = undefined
|
||||||
@@ -356,7 +361,9 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jidNormalizedUser(jid) !== jidNormalizedUser(authState.creds.me!.id)) {
|
const me = authState.creds.me
|
||||||
|
if (!me) throw new Boom('Not authenticated', { statusCode: 401 })
|
||||||
|
if (jidNormalizedUser(jid) !== jidNormalizedUser(me.id)) {
|
||||||
targetJid = jidNormalizedUser(jid) // in case it is someone other than us
|
targetJid = jidNormalizedUser(jid) // in case it is someone other than us
|
||||||
} else {
|
} else {
|
||||||
targetJid = undefined
|
targetJid = undefined
|
||||||
@@ -505,10 +512,12 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
const newAppStateChunkHandler = (isInitialSync: boolean) => {
|
const newAppStateChunkHandler = (isInitialSync: boolean) => {
|
||||||
return {
|
return {
|
||||||
onMutation(mutation: ChatMutation) {
|
onMutation(mutation: ChatMutation) {
|
||||||
|
const me = authState.creds.me
|
||||||
|
if (!me) throw new Boom('Not authenticated', { statusCode: 401 })
|
||||||
processSyncAction(
|
processSyncAction(
|
||||||
mutation,
|
mutation,
|
||||||
ev,
|
ev,
|
||||||
authState.creds.me!,
|
me,
|
||||||
isInitialSync ? { accountSettings: authState.creds.accountSettings } : undefined,
|
isInitialSync ? { accountSettings: authState.creds.accountSettings } : undefined,
|
||||||
logger
|
logger
|
||||||
)
|
)
|
||||||
@@ -630,7 +639,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
// if retry attempts overshoot
|
// if retry attempts overshoot
|
||||||
// or key not found
|
// or key not found
|
||||||
const isIrrecoverableError =
|
const isIrrecoverableError =
|
||||||
attemptsMap[name]! >= MAX_SYNC_ATTEMPTS ||
|
(attemptsMap[name] || 0) >= MAX_SYNC_ATTEMPTS ||
|
||||||
error.output?.statusCode === 404 ||
|
error.output?.statusCode === 404 ||
|
||||||
error.name === 'TypeError'
|
error.name === 'TypeError'
|
||||||
logger.info(
|
logger.info(
|
||||||
@@ -652,7 +661,9 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
|
|
||||||
const { onMutation } = newAppStateChunkHandler(isInitialSync)
|
const { onMutation } = newAppStateChunkHandler(isInitialSync)
|
||||||
for (const key in globalMutationMap) {
|
for (const key in globalMutationMap) {
|
||||||
onMutation(globalMutationMap[key]!)
|
const mutation = globalMutationMap[key]
|
||||||
|
if (!mutation) continue
|
||||||
|
onMutation(mutation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -708,7 +719,8 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sendPresenceUpdate = async (type: WAPresence, toJid?: string) => {
|
const sendPresenceUpdate = async (type: WAPresence, toJid?: string) => {
|
||||||
const me = authState.creds.me!
|
const me = authState.creds.me
|
||||||
|
if (!me) throw new Boom('Not authenticated', { statusCode: 401 })
|
||||||
if (type === 'available' || type === 'unavailable') {
|
if (type === 'available' || type === 'unavailable') {
|
||||||
if (!me.name) {
|
if (!me.name) {
|
||||||
logger.warn('no name present, ignoring presence update request...')
|
logger.warn('no name present, ignoring presence update request...')
|
||||||
@@ -733,14 +745,17 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const { server } = jidDecode(toJid)!
|
if (!toJid) return
|
||||||
|
const decoded = jidDecode(toJid)
|
||||||
|
if (!decoded) return
|
||||||
|
const { server } = decoded
|
||||||
const isLid = server === 'lid'
|
const isLid = server === 'lid'
|
||||||
|
|
||||||
await sendNode({
|
await sendNode({
|
||||||
tag: 'chatstate',
|
tag: 'chatstate',
|
||||||
attrs: {
|
attrs: {
|
||||||
from: isLid ? me.lid! : me.id,
|
from: isLid ? (me.lid || me.id) : me.id,
|
||||||
to: toJid!
|
to: toJid
|
||||||
},
|
},
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
@@ -774,8 +789,9 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
let presence: PresenceData | undefined
|
let presence: PresenceData | undefined
|
||||||
const jid = attrs.from
|
const jid = attrs.from
|
||||||
const participant = attrs.participant || attrs.from
|
const participant = attrs.participant || attrs.from
|
||||||
|
if (!jid) return
|
||||||
|
|
||||||
if (shouldIgnoreJid(jid!) && jid !== S_WHATSAPP_NET) {
|
if (shouldIgnoreJid(jid) && jid !== S_WHATSAPP_NET) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -786,12 +802,13 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
} else if (Array.isArray(content)) {
|
} else if (Array.isArray(content)) {
|
||||||
const [firstChild] = content
|
const [firstChild] = content
|
||||||
let type = firstChild!.tag as WAPresence
|
if (!firstChild) return
|
||||||
|
let type = firstChild.tag as WAPresence
|
||||||
if (type === 'paused') {
|
if (type === 'paused') {
|
||||||
type = 'available'
|
type = 'available'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (firstChild!.attrs?.media === 'audio') {
|
if (firstChild.attrs?.media === 'audio') {
|
||||||
type = 'recording'
|
type = 'recording'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -801,7 +818,8 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (presence) {
|
if (presence) {
|
||||||
ev.emit('presence.update', { id: jid!, presences: { [participant!]: presence } })
|
if (!participant) return
|
||||||
|
ev.emit('presence.update', { id: jid, presences: { [participant]: presence } })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -225,12 +225,12 @@ export const makeGroupsSocket = (config: SocketConfig) => {
|
|||||||
groupAcceptInviteV4: ev.createBufferedFunction(
|
groupAcceptInviteV4: ev.createBufferedFunction(
|
||||||
async (key: string | WAMessageKey, inviteMessage: proto.Message.IGroupInviteMessage) => {
|
async (key: string | WAMessageKey, inviteMessage: proto.Message.IGroupInviteMessage) => {
|
||||||
key = typeof key === 'string' ? { remoteJid: key } : key
|
key = typeof key === 'string' ? { remoteJid: key } : key
|
||||||
const results = await groupQuery(inviteMessage.groupJid!, 'set', [
|
const results = await groupQuery(inviteMessage.groupJid ?? '', 'set', [
|
||||||
{
|
{
|
||||||
tag: 'accept',
|
tag: 'accept',
|
||||||
attrs: {
|
attrs: {
|
||||||
code: inviteMessage.inviteCode!,
|
code: inviteMessage.inviteCode ?? '',
|
||||||
expiration: inviteMessage.inviteExpiration!.toString(),
|
expiration: String(inviteMessage.inviteExpiration ?? ''),
|
||||||
admin: key.remoteJid!
|
admin: key.remoteJid!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+52
-33
@@ -274,7 +274,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
case 'view':
|
case 'view':
|
||||||
const viewUpdate = {
|
const viewUpdate = {
|
||||||
id: from,
|
id: from,
|
||||||
server_id: child.attrs.message_id!,
|
server_id: child.attrs.message_id ?? '',
|
||||||
count: parseInt(child.content?.toString() || '0', 10)
|
count: parseInt(child.content?.toString() || '0', 10)
|
||||||
}
|
}
|
||||||
ev.emit('newsletter.view', viewUpdate)
|
ev.emit('newsletter.view', viewUpdate)
|
||||||
@@ -284,9 +284,9 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
const participantUpdate = {
|
const participantUpdate = {
|
||||||
id: from,
|
id: from,
|
||||||
author,
|
author,
|
||||||
user: child.attrs.jid!,
|
user: child.attrs.jid ?? '',
|
||||||
action: child.attrs.action!,
|
action: child.attrs.action ?? '',
|
||||||
new_role: child.attrs.role!
|
new_role: child.attrs.role ?? ''
|
||||||
}
|
}
|
||||||
ev.emit('newsletter-participants.update', participantUpdate)
|
ev.emit('newsletter-participants.update', participantUpdate)
|
||||||
break
|
break
|
||||||
@@ -325,7 +325,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
fromMe: false // TODO: is this really true though
|
fromMe: false // TODO: is this really true though
|
||||||
},
|
},
|
||||||
message: messageProto,
|
message: messageProto,
|
||||||
messageTimestamp: +child.attrs.t!
|
messageTimestamp: +(child.attrs.t ?? 0)
|
||||||
}).toJSON() as WAMessage
|
}).toJSON() as WAMessage
|
||||||
await upsertMessage(fullMessage, 'append')
|
await upsertMessage(fullMessage, 'append')
|
||||||
logger.info('Processed plaintext newsletter message')
|
logger.info('Processed plaintext newsletter message')
|
||||||
@@ -346,8 +346,8 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
const stanza: BinaryNode = {
|
const stanza: BinaryNode = {
|
||||||
tag: 'ack',
|
tag: 'ack',
|
||||||
attrs: {
|
attrs: {
|
||||||
id: attrs.id!,
|
id: attrs.id ?? '',
|
||||||
to: attrs.from!,
|
to: attrs.from ?? '',
|
||||||
class: tag
|
class: tag
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -372,7 +372,9 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (tag === 'message' && getBinaryNodeChild({ tag, attrs, content }, 'unavailable')) {
|
if (tag === 'message' && getBinaryNodeChild({ tag, attrs, content }, 'unavailable')) {
|
||||||
stanza.attrs.from = authState.creds.me!.id
|
const meId = authState.creds.me?.id
|
||||||
|
if (!meId) throw new Boom('Not authenticated', { statusCode: 401 })
|
||||||
|
stanza.attrs.from = meId
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug({ recv: { tag, attrs }, sent: stanza.attrs }, 'sent ack')
|
logger.debug({ recv: { tag, attrs }, sent: stanza.attrs }, 'sent ack')
|
||||||
@@ -380,10 +382,12 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const rejectCall = async (callId: string, callFrom: string) => {
|
const rejectCall = async (callId: string, callFrom: string) => {
|
||||||
|
const meId = authState.creds.me?.id
|
||||||
|
if (!meId) throw new Boom('Not authenticated', { statusCode: 401 })
|
||||||
const stanza: BinaryNode = {
|
const stanza: BinaryNode = {
|
||||||
tag: 'call',
|
tag: 'call',
|
||||||
attrs: {
|
attrs: {
|
||||||
from: authState.creds.me!.id,
|
from: meId,
|
||||||
to: callFrom
|
to: callFrom
|
||||||
},
|
},
|
||||||
content: [
|
content: [
|
||||||
@@ -402,9 +406,16 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sendRetryRequest = async (node: BinaryNode, forceIncludeKeys = false) => {
|
const sendRetryRequest = async (node: BinaryNode, forceIncludeKeys = false) => {
|
||||||
const { fullMessage } = decodeMessageNode(node, authState.creds.me!.id, authState.creds.me!.lid || '')
|
const meId = authState.creds.me?.id
|
||||||
|
if (!meId) throw new Boom('Not authenticated', { statusCode: 401 })
|
||||||
|
const meLid = authState.creds.me?.lid || ''
|
||||||
|
const { fullMessage } = decodeMessageNode(node, meId, meLid)
|
||||||
const { key: msgKey } = fullMessage
|
const { key: msgKey } = fullMessage
|
||||||
const msgId = msgKey.id!
|
const msgId = msgKey.id
|
||||||
|
if (!msgId) {
|
||||||
|
logger.debug({ node }, 'missing message id in retry request')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (messageRetryManager) {
|
if (messageRetryManager) {
|
||||||
// Check if we've exceeded max retries using the new system
|
// Check if we've exceeded max retries using the new system
|
||||||
@@ -442,7 +453,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
const retryCount = (await msgRetryCache.get<number>(key)) || 1
|
const retryCount = (await msgRetryCache.get<number>(key)) || 1
|
||||||
|
|
||||||
const { account, signedPreKey, signedIdentityKey: identityKey } = authState.creds
|
const { account, signedPreKey, signedIdentityKey: identityKey } = authState.creds
|
||||||
const fromJid = node.attrs.from!
|
const fromJid = node.attrs.from ?? ''
|
||||||
|
|
||||||
// Check if we should recreate the session
|
// Check if we should recreate the session
|
||||||
let shouldRecreateSession = false
|
let shouldRecreateSession = false
|
||||||
@@ -499,22 +510,23 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const deviceIdentity = encodeSignedDeviceIdentity(account!, true)
|
if (!account) throw new Boom('Account not available', { statusCode: 401 })
|
||||||
|
const deviceIdentity = encodeSignedDeviceIdentity(account, true)
|
||||||
await authState.keys.transaction(async () => {
|
await authState.keys.transaction(async () => {
|
||||||
const receipt: BinaryNode = {
|
const receipt: BinaryNode = {
|
||||||
tag: 'receipt',
|
tag: 'receipt',
|
||||||
attrs: {
|
attrs: {
|
||||||
id: msgId,
|
id: msgId,
|
||||||
type: 'retry',
|
type: 'retry',
|
||||||
to: node.attrs.from!
|
to: node.attrs.from ?? ''
|
||||||
},
|
},
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
tag: 'retry',
|
tag: 'retry',
|
||||||
attrs: {
|
attrs: {
|
||||||
count: retryCount.toString(),
|
count: retryCount.toString(),
|
||||||
id: node.attrs.id!,
|
id: node.attrs.id ?? '',
|
||||||
t: node.attrs.t!,
|
t: node.attrs.t ?? '',
|
||||||
v: '1',
|
v: '1',
|
||||||
// ADD ERROR FIELD
|
// ADD ERROR FIELD
|
||||||
error: '0'
|
error: '0'
|
||||||
@@ -540,16 +552,19 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
const { update, preKeys } = await getNextPreKeys(authState, 1)
|
const { update, preKeys } = await getNextPreKeys(authState, 1)
|
||||||
|
|
||||||
const [keyId] = Object.keys(preKeys)
|
const [keyId] = Object.keys(preKeys)
|
||||||
const key = preKeys[+keyId!]
|
if (!keyId) throw new Boom('No pre-keys available')
|
||||||
|
const key = preKeys[+keyId]
|
||||||
|
if (!key) throw new Boom('Pre-key not found')
|
||||||
|
|
||||||
const content = receipt.content! as BinaryNode[]
|
const content = receipt.content as BinaryNode[]
|
||||||
|
if (!content) throw new Boom('Receipt content missing')
|
||||||
content.push({
|
content.push({
|
||||||
tag: 'keys',
|
tag: 'keys',
|
||||||
attrs: {},
|
attrs: {},
|
||||||
content: [
|
content: [
|
||||||
{ tag: 'type', attrs: {}, content: Buffer.from(KEY_BUNDLE_TYPE) },
|
{ tag: 'type', attrs: {}, content: Buffer.from(KEY_BUNDLE_TYPE) },
|
||||||
{ tag: 'identity', attrs: {}, content: identityKey.public },
|
{ tag: 'identity', attrs: {}, content: identityKey.public },
|
||||||
xmppPreKey(key!, +keyId!),
|
xmppPreKey(key, +keyId),
|
||||||
xmppSignedPreKey(signedPreKey),
|
xmppSignedPreKey(signedPreKey),
|
||||||
{ tag: 'device-identity', attrs: {}, content: deviceIdentity }
|
{ tag: 'device-identity', attrs: {}, content: deviceIdentity }
|
||||||
]
|
]
|
||||||
@@ -568,7 +583,9 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
const from = node.attrs.from
|
const from = node.attrs.from
|
||||||
if (from === S_WHATSAPP_NET) {
|
if (from === S_WHATSAPP_NET) {
|
||||||
const countChild = getBinaryNodeChild(node, 'count')
|
const countChild = getBinaryNodeChild(node, 'count')
|
||||||
const count = +countChild!.attrs.value!
|
const countVal = countChild?.attrs?.value
|
||||||
|
if (!countVal) return
|
||||||
|
const count = +countVal
|
||||||
const shouldUploadMorePreKeys = count < MIN_PREKEY_COUNT
|
const shouldUploadMorePreKeys = count < MIN_PREKEY_COUNT
|
||||||
|
|
||||||
logger.debug({ count, shouldUploadMorePreKeys }, 'recv pre-key count')
|
logger.debug({ count, shouldUploadMorePreKeys }, 'recv pre-key count')
|
||||||
@@ -597,8 +614,8 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
const actingParticipantLid = fullNode.attrs.participant
|
const actingParticipantLid = fullNode.attrs.participant
|
||||||
const actingParticipantPn = fullNode.attrs.participant_pn
|
const actingParticipantPn = fullNode.attrs.participant_pn
|
||||||
|
|
||||||
const affectedParticipantLid = getBinaryNodeChild(child, 'participant')?.attrs?.jid || actingParticipantLid!
|
const affectedParticipantLid = getBinaryNodeChild(child, 'participant')?.attrs?.jid || actingParticipantLid || ''
|
||||||
const affectedParticipantPn = getBinaryNodeChild(child, 'participant')?.attrs?.phone_number || actingParticipantPn!
|
const affectedParticipantPn = getBinaryNodeChild(child, 'participant')?.attrs?.phone_number || actingParticipantPn || ''
|
||||||
|
|
||||||
switch (child?.tag) {
|
switch (child?.tag) {
|
||||||
case 'create':
|
case 'create':
|
||||||
@@ -633,7 +650,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'modify':
|
case 'modify':
|
||||||
const oldNumber = getBinaryNodeChildren(child, 'participant').map(p => p.attrs.jid!)
|
const oldNumber = getBinaryNodeChildren(child, 'participant').map(p => p.attrs.jid ?? '').filter(Boolean)
|
||||||
msg.messageStubParameters = oldNumber || []
|
msg.messageStubParameters = oldNumber || []
|
||||||
msg.messageStubType = WAMessageStubType.GROUP_PARTICIPANT_CHANGE_NUMBER
|
msg.messageStubType = WAMessageStubType.GROUP_PARTICIPANT_CHANGE_NUMBER
|
||||||
break
|
break
|
||||||
@@ -648,7 +665,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
const participants = getBinaryNodeChildren(child, 'participant').map(({ attrs }) => {
|
const participants = getBinaryNodeChildren(child, 'participant').map(({ attrs }) => {
|
||||||
// TODO: Store LID MAPPINGS
|
// TODO: Store LID MAPPINGS
|
||||||
return {
|
return {
|
||||||
id: attrs.jid!,
|
id: attrs.jid ?? '',
|
||||||
phoneNumber: isLidUser(attrs.jid) && isPnUser(attrs.phone_number) ? attrs.phone_number : undefined,
|
phoneNumber: isLidUser(attrs.jid) && isPnUser(attrs.phone_number) ? attrs.phone_number : undefined,
|
||||||
lid: isPnUser(attrs.jid) && isLidUser(attrs.lid) ? attrs.lid : undefined,
|
lid: isPnUser(attrs.jid) && isLidUser(attrs.lid) ? attrs.lid : undefined,
|
||||||
admin: (attrs.type || null) as GroupParticipant['admin']
|
admin: (attrs.type || null) as GroupParticipant['admin']
|
||||||
@@ -659,8 +676,8 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
participants.length === 1 &&
|
participants.length === 1 &&
|
||||||
// if recv. "remove" message and sender removed themselves
|
// if recv. "remove" message and sender removed themselves
|
||||||
// mark as left
|
// mark as left
|
||||||
(areJidsSameUser(participants[0]!.id, actingParticipantLid) ||
|
(areJidsSameUser(participants[0]?.id, actingParticipantLid) ||
|
||||||
areJidsSameUser(participants[0]!.id, actingParticipantPn)) &&
|
areJidsSameUser(participants[0]?.id, actingParticipantPn)) &&
|
||||||
child.tag === 'remove'
|
child.tag === 'remove'
|
||||||
) {
|
) {
|
||||||
msg.messageStubType = WAMessageStubType.GROUP_PARTICIPANT_LEAVE
|
msg.messageStubType = WAMessageStubType.GROUP_PARTICIPANT_LEAVE
|
||||||
@@ -670,7 +687,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
break
|
break
|
||||||
case 'subject':
|
case 'subject':
|
||||||
msg.messageStubType = WAMessageStubType.GROUP_CHANGE_SUBJECT
|
msg.messageStubType = WAMessageStubType.GROUP_CHANGE_SUBJECT
|
||||||
msg.messageStubParameters = [child.attrs.subject!]
|
msg.messageStubParameters = [child.attrs.subject ?? '']
|
||||||
break
|
break
|
||||||
case 'description':
|
case 'description':
|
||||||
const description = getBinaryNodeChild(child, 'body')?.content?.toString()
|
const description = getBinaryNodeChild(child, 'body')?.content?.toString()
|
||||||
@@ -689,7 +706,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
break
|
break
|
||||||
case 'invite':
|
case 'invite':
|
||||||
msg.messageStubType = WAMessageStubType.GROUP_CHANGE_INVITE_LINK
|
msg.messageStubType = WAMessageStubType.GROUP_CHANGE_INVITE_LINK
|
||||||
msg.messageStubParameters = [child.attrs.code!]
|
msg.messageStubParameters = [child.attrs.code ?? '']
|
||||||
break
|
break
|
||||||
case 'member_add_mode':
|
case 'member_add_mode':
|
||||||
const addMode = child.content
|
const addMode = child.content
|
||||||
@@ -703,7 +720,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
const approvalMode = getBinaryNodeChild(child, 'group_join')
|
const approvalMode = getBinaryNodeChild(child, 'group_join')
|
||||||
if (approvalMode) {
|
if (approvalMode) {
|
||||||
msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_MODE
|
msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_MODE
|
||||||
msg.messageStubParameters = [approvalMode.attrs.state!]
|
msg.messageStubParameters = [approvalMode.attrs.state ?? '']
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
@@ -712,7 +729,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
msg.messageStubParameters = [
|
msg.messageStubParameters = [
|
||||||
JSON.stringify({ lid: affectedParticipantLid, pn: affectedParticipantPn }),
|
JSON.stringify({ lid: affectedParticipantLid, pn: affectedParticipantPn }),
|
||||||
'created',
|
'created',
|
||||||
child.attrs.request_method!
|
child.attrs.request_method ?? ''
|
||||||
]
|
]
|
||||||
break
|
break
|
||||||
case 'revoked_membership_requests':
|
case 'revoked_membership_requests':
|
||||||
@@ -742,7 +759,8 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
break
|
break
|
||||||
case 'w:gp2':
|
case 'w:gp2':
|
||||||
// TODO: HANDLE PARTICIPANT_PN
|
// TODO: HANDLE PARTICIPANT_PN
|
||||||
handleGroupNotification(node, child!, result)
|
if (!child) break
|
||||||
|
handleGroupNotification(node, child, result)
|
||||||
break
|
break
|
||||||
case 'mediaretry':
|
case 'mediaretry':
|
||||||
const event = decodeMediaRetryNode(node)
|
const event = decodeMediaRetryNode(node)
|
||||||
@@ -752,10 +770,11 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
await handleEncryptNotification(node)
|
await handleEncryptNotification(node)
|
||||||
break
|
break
|
||||||
case 'devices':
|
case 'devices':
|
||||||
|
if (!child) break
|
||||||
const devices = getBinaryNodeChildren(child, 'device')
|
const devices = getBinaryNodeChildren(child, 'device')
|
||||||
if (
|
if (
|
||||||
areJidsSameUser(child!.attrs.jid, authState.creds.me!.id) ||
|
areJidsSameUser(child.attrs.jid, authState.creds.me?.id) ||
|
||||||
areJidsSameUser(child!.attrs.lid, authState.creds.me!.lid)
|
areJidsSameUser(child.attrs.lid, authState.creds.me?.lid)
|
||||||
) {
|
) {
|
||||||
const deviceData = devices.map(d => ({ id: d.attrs.jid, lid: d.attrs.lid }))
|
const deviceData = devices.map(d => ({ id: d.attrs.jid, lid: d.attrs.lid }))
|
||||||
logger.info({ deviceData }, 'my own devices changed')
|
logger.info({ deviceData }, 'my own devices changed')
|
||||||
|
|||||||
+40
-26
@@ -126,15 +126,16 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
},
|
},
|
||||||
content: [{ tag: 'media_conn', attrs: {} }]
|
content: [{ tag: 'media_conn', attrs: {} }]
|
||||||
})
|
})
|
||||||
const mediaConnNode = getBinaryNodeChild(result, 'media_conn')!
|
const mediaConnNode = getBinaryNodeChild(result, 'media_conn')
|
||||||
|
if (!mediaConnNode) throw new Boom('Missing media_conn node')
|
||||||
// TODO: explore full length of data that whatsapp provides
|
// TODO: explore full length of data that whatsapp provides
|
||||||
const node: MediaConnInfo = {
|
const node: MediaConnInfo = {
|
||||||
hosts: getBinaryNodeChildren(mediaConnNode, 'host').map(({ attrs }) => ({
|
hosts: getBinaryNodeChildren(mediaConnNode, 'host').map(({ attrs }) => ({
|
||||||
hostname: attrs.hostname!,
|
hostname: attrs.hostname ?? '',
|
||||||
maxContentLengthBytes: +attrs.maxContentLengthBytes!
|
maxContentLengthBytes: +(attrs.maxContentLengthBytes ?? 0)
|
||||||
})),
|
})),
|
||||||
auth: mediaConnNode.attrs.auth!,
|
auth: mediaConnNode.attrs.auth ?? '',
|
||||||
ttl: +mediaConnNode.attrs.ttl!,
|
ttl: +(mediaConnNode.attrs.ttl ?? 0),
|
||||||
fetchDate: new Date()
|
fetchDate: new Date()
|
||||||
}
|
}
|
||||||
logger.debug('fetched media conn')
|
logger.debug('fetched media conn')
|
||||||
@@ -162,7 +163,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
const node: BinaryNode = {
|
const node: BinaryNode = {
|
||||||
tag: 'receipt',
|
tag: 'receipt',
|
||||||
attrs: {
|
attrs: {
|
||||||
id: messageIds[0]!
|
id: messageIds[0] ?? ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const isReadReceipt = type === 'read' || type === 'read-self'
|
const isReadReceipt = type === 'read' || type === 'read-self'
|
||||||
@@ -171,8 +172,9 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'sender' && (isPnUser(jid) || isLidUser(jid))) {
|
if (type === 'sender' && (isPnUser(jid) || isLidUser(jid))) {
|
||||||
|
if (!participant) throw new Boom('Missing participant for sender receipt')
|
||||||
node.attrs.recipient = jid
|
node.attrs.recipient = jid
|
||||||
node.attrs.to = participant!
|
node.attrs.to = participant
|
||||||
} else {
|
} else {
|
||||||
node.attrs.to = jid
|
node.attrs.to = jid
|
||||||
if (participant) {
|
if (participant) {
|
||||||
@@ -266,10 +268,11 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const { jid, user } of jidsWithUser) {
|
for (const { jid, user } of jidsWithUser) {
|
||||||
|
if (!user) continue
|
||||||
if (useCache) {
|
if (useCache) {
|
||||||
const devices =
|
const devices =
|
||||||
mgetDevices?.[user!] ||
|
mgetDevices?.[user] ||
|
||||||
(userDevicesCache.mget ? undefined : ((await userDevicesCache.get(user!)) as FullJid[]))
|
(userDevicesCache.mget ? undefined : ((await userDevicesCache.get(user)) as FullJid[]))
|
||||||
if (devices) {
|
if (devices) {
|
||||||
const devicesWithJid = devices.map(d => ({
|
const devicesWithJid = devices.map(d => ({
|
||||||
...d,
|
...d,
|
||||||
@@ -324,10 +327,14 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const meId = authState.creds.me?.id
|
||||||
|
if (!meId) throw new Boom('Not authenticated', { statusCode: 401 })
|
||||||
|
const meLid = authState.creds.me?.lid || ''
|
||||||
|
|
||||||
const extracted = extractDeviceJids(
|
const extracted = extractDeviceJids(
|
||||||
result?.list,
|
result?.list,
|
||||||
authState.creds.me!.id,
|
meId,
|
||||||
authState.creds.me!.lid!,
|
meLid,
|
||||||
ignoreZeroDevices
|
ignoreZeroDevices
|
||||||
)
|
)
|
||||||
const deviceMap: { [_: string]: FullJid[] } = {}
|
const deviceMap: { [_: string]: FullJid[] } = {}
|
||||||
@@ -547,7 +554,8 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
: recipientJids.map(jid => ({ recipientJid: jid, message: patched }))
|
: recipientJids.map(jid => ({ recipientJid: jid, message: patched }))
|
||||||
|
|
||||||
let shouldIncludeDeviceIdentity = false
|
let shouldIncludeDeviceIdentity = false
|
||||||
const meId = authState.creds.me!.id
|
const meId = authState.creds.me?.id
|
||||||
|
if (!meId) throw new Boom('Not authenticated', { statusCode: 401 })
|
||||||
const meLid = authState.creds.me?.lid
|
const meLid = authState.creds.me?.lid
|
||||||
const meLidUser = meLid ? jidDecode(meLid)?.user : null
|
const meLidUser = meLid ? jidDecode(meLid)?.user : null
|
||||||
|
|
||||||
@@ -559,8 +567,8 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
let msgToEncrypt = patchedMessage
|
let msgToEncrypt = patchedMessage
|
||||||
|
|
||||||
if (dsmMessage) {
|
if (dsmMessage) {
|
||||||
const { user: targetUser } = jidDecode(jid)!
|
const targetUser = jidDecode(jid)?.user ?? ''
|
||||||
const { user: ownPnUser } = jidDecode(meId)!
|
const ownPnUser = jidDecode(meId)?.user ?? ''
|
||||||
const ownLidUser = meLidUser
|
const ownLidUser = meLidUser
|
||||||
|
|
||||||
const isOwnUser = targetUser === ownPnUser || (ownLidUser && targetUser === ownLidUser)
|
const isOwnUser = targetUser === ownPnUser || (ownLidUser && targetUser === ownLidUser)
|
||||||
@@ -816,13 +824,16 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
statusJidList
|
statusJidList
|
||||||
}: MessageRelayOptions
|
}: MessageRelayOptions
|
||||||
) => {
|
) => {
|
||||||
const meId = authState.creds.me!.id
|
const meId = authState.creds.me?.id
|
||||||
|
if (!meId) throw new Boom('Not authenticated', { statusCode: 401 })
|
||||||
const meLid = authState.creds.me?.lid
|
const meLid = authState.creds.me?.lid
|
||||||
const isRetryResend = Boolean(participant?.jid)
|
const isRetryResend = Boolean(participant?.jid)
|
||||||
let shouldIncludeDeviceIdentity = isRetryResend
|
let shouldIncludeDeviceIdentity = isRetryResend
|
||||||
const statusJid = 'status@broadcast'
|
const statusJid = 'status@broadcast'
|
||||||
|
|
||||||
const { user, server } = jidDecode(jid)!
|
const jidDecoded = jidDecode(jid)
|
||||||
|
if (!jidDecoded) throw new Boom('Invalid JID')
|
||||||
|
const { user, server } = jidDecoded
|
||||||
const isGroup = server === 'g.us'
|
const isGroup = server === 'g.us'
|
||||||
const isStatus = jid === statusJid
|
const isStatus = jid === statusJid
|
||||||
const isLid = server === 'lid'
|
const isLid = server === 'lid'
|
||||||
@@ -910,7 +921,9 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
additionalAttributes = { ...additionalAttributes, device_fanout: 'false' }
|
additionalAttributes = { ...additionalAttributes, device_fanout: 'false' }
|
||||||
}
|
}
|
||||||
|
|
||||||
const { user, device } = jidDecode(participant.jid)!
|
const participantDecoded = jidDecode(participant.jid)
|
||||||
|
if (!participantDecoded) throw new Boom('Invalid participant JID')
|
||||||
|
const { user, device } = participantDecoded
|
||||||
devices.push({
|
devices.push({
|
||||||
user,
|
user,
|
||||||
device,
|
device,
|
||||||
@@ -1068,7 +1081,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
logger.debug({ to: jid, ownId }, 'Using PN identity for @s.whatsapp.net conversation')
|
logger.debug({ to: jid, ownId }, 'Using PN identity for @s.whatsapp.net conversation')
|
||||||
}
|
}
|
||||||
|
|
||||||
const { user: ownUser } = jidDecode(ownId)!
|
const ownUser = jidDecode(ownId)?.user ?? ''
|
||||||
if (!participant) {
|
if (!participant) {
|
||||||
const patchedForReporting = await patchMessageBeforeSending(message, [jid])
|
const patchedForReporting = await patchMessageBeforeSending(message, [jid])
|
||||||
reportingMessage = Array.isArray(patchedForReporting)
|
reportingMessage = Array.isArray(patchedForReporting)
|
||||||
@@ -1086,7 +1099,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
|
|
||||||
if (user !== ownUser) {
|
if (user !== ownUser) {
|
||||||
const ownUserServer = isLid ? 'lid' : 's.whatsapp.net'
|
const ownUserServer = isLid ? 'lid' : 's.whatsapp.net'
|
||||||
const ownUserForAddressing = isLid && meLid ? jidDecode(meLid)!.user : jidDecode(meId)!.user
|
const ownUserForAddressing = isLid && meLid ? (jidDecode(meLid)?.user ?? '') : (jidDecode(meId)?.user ?? '')
|
||||||
|
|
||||||
devices.push({
|
devices.push({
|
||||||
user: ownUserForAddressing,
|
user: ownUserForAddressing,
|
||||||
@@ -1102,8 +1115,8 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
// Use conversation-appropriate sender identity
|
// Use conversation-appropriate sender identity
|
||||||
const senderIdentity =
|
const senderIdentity =
|
||||||
isLid && meLid
|
isLid && meLid
|
||||||
? jidEncode(jidDecode(meLid)?.user!, 'lid', undefined)
|
? jidEncode(jidDecode(meLid)?.user ?? '', 'lid', undefined)
|
||||||
: jidEncode(jidDecode(meId)?.user!, 's.whatsapp.net', undefined)
|
: jidEncode(jidDecode(meId)?.user ?? '', 's.whatsapp.net', undefined)
|
||||||
|
|
||||||
// Enumerate devices for sender and target with consistent addressing
|
// Enumerate devices for sender and target with consistent addressing
|
||||||
const sessionDevices = await getUSyncDevices([senderIdentity, jid], true, false)
|
const sessionDevices = await getUSyncDevices([senderIdentity, jid], true, false)
|
||||||
@@ -1122,8 +1135,8 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
const allRecipients: string[] = []
|
const allRecipients: string[] = []
|
||||||
const meRecipients: string[] = []
|
const meRecipients: string[] = []
|
||||||
const otherRecipients: string[] = []
|
const otherRecipients: string[] = []
|
||||||
const { user: mePnUser } = jidDecode(meId)!
|
const mePnUser = jidDecode(meId)?.user ?? ''
|
||||||
const { user: meLidUser } = meLid ? jidDecode(meLid)! : { user: null }
|
const meLidUser = meLid ? (jidDecode(meLid)?.user ?? null) : null
|
||||||
|
|
||||||
// Carousel in DSM wrapper causes error 479 on own devices
|
// Carousel in DSM wrapper causes error 479 on own devices
|
||||||
const isCarouselMsg = isCarouselMessage(message)
|
const isCarouselMsg = isCarouselMessage(message)
|
||||||
@@ -1172,10 +1185,11 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isRetryResend) {
|
if (isRetryResend) {
|
||||||
|
if (!participant) throw new Boom('Missing participant for retry resend')
|
||||||
// Only check for regular LID users, NOT hosted LID users
|
// Only check for regular LID users, NOT hosted LID users
|
||||||
// Hosted LID users should use meId for comparison, not meLid
|
// Hosted LID users should use meId for comparison, not meLid
|
||||||
const isParticipantLid = isLidUser(participant!.jid)
|
const isParticipantLid = isLidUser(participant.jid)
|
||||||
const isMe = areJidsSameUser(participant!.jid, isParticipantLid ? meLid : meId)
|
const isMe = areJidsSameUser(participant.jid, isParticipantLid ? meLid : meId)
|
||||||
|
|
||||||
// Skip DSM for carousel - own devices reject it with error 479
|
// Skip DSM for carousel - own devices reject it with error 479
|
||||||
const usesDSM = isMe && !isCarouselMessage(message)
|
const usesDSM = isMe && !isCarouselMessage(message)
|
||||||
@@ -1190,7 +1204,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
|
|
||||||
const { type, ciphertext: encryptedContent } = await signalRepository.encryptMessage({
|
const { type, ciphertext: encryptedContent } = await signalRepository.encryptMessage({
|
||||||
data: encodedMessageToSend,
|
data: encodedMessageToSend,
|
||||||
jid: participant!.jid
|
jid: participant.jid
|
||||||
})
|
})
|
||||||
|
|
||||||
binaryNodeContent.push({
|
binaryNodeContent.push({
|
||||||
|
|||||||
+164
-36
@@ -154,7 +154,12 @@ export const encodeSyncdPatch = async (
|
|||||||
})
|
})
|
||||||
const encoded = proto.SyncActionData.encode(dataProto).finish()
|
const encoded = proto.SyncActionData.encode(dataProto).finish()
|
||||||
|
|
||||||
const keyValue = mutationKeys(key.keyData!)
|
const keyData = key.keyData
|
||||||
|
if (!keyData) {
|
||||||
|
throw new Boom('Missing keyData for encoding', { statusCode: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyValue = mutationKeys(keyData)
|
||||||
|
|
||||||
const encValue = aesEncrypt(encoded, keyValue.valueEncryptionKey)
|
const encValue = aesEncrypt(encoded, keyValue.valueEncryptionKey)
|
||||||
const valueMac = generateMac(operation, encValue, encKeyId, keyValue.valueMacKey)
|
const valueMac = generateMac(operation, encValue, encKeyId, keyValue.valueMacKey)
|
||||||
@@ -210,15 +215,34 @@ export const decodeSyncdMutations = async (
|
|||||||
// if it's a syncdmutation, get the operation property
|
// if it's a syncdmutation, get the operation property
|
||||||
// otherwise, if it's only a record -- it'll be a SET mutation
|
// otherwise, if it's only a record -- it'll be a SET mutation
|
||||||
const operation = 'operation' in msgMutation ? msgMutation.operation : proto.SyncdMutation.SyncdOperation.SET
|
const operation = 'operation' in msgMutation ? msgMutation.operation : proto.SyncdMutation.SyncdOperation.SET
|
||||||
|
if (operation == null) {
|
||||||
|
throw new Boom('Missing operation in mutation', { statusCode: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
const record =
|
const record =
|
||||||
'record' in msgMutation && !!msgMutation.record ? msgMutation.record : (msgMutation as proto.ISyncdRecord)
|
'record' in msgMutation && !!msgMutation.record ? msgMutation.record : (msgMutation as proto.ISyncdRecord)
|
||||||
|
|
||||||
const key = await getKey(record.keyId!.id!)
|
const keyIdBuf = record.keyId?.id
|
||||||
const content = Buffer.from(record.value!.blob!)
|
if (!keyIdBuf) {
|
||||||
|
throw new Boom('Missing keyId in record', { statusCode: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const recordBlob = record.value?.blob
|
||||||
|
if (!recordBlob) {
|
||||||
|
throw new Boom('Missing record blob', { statusCode: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const indexBlob = record.index?.blob
|
||||||
|
if (!indexBlob) {
|
||||||
|
throw new Boom('Missing index blob in record', { statusCode: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = await getKey(keyIdBuf)
|
||||||
|
const content = Buffer.from(recordBlob)
|
||||||
const encContent = content.slice(0, -32)
|
const encContent = content.slice(0, -32)
|
||||||
const ogValueMac = content.slice(-32)
|
const ogValueMac = content.slice(-32)
|
||||||
if (validateMacs) {
|
if (validateMacs) {
|
||||||
const contentHmac = generateMac(operation!, encContent, record.keyId!.id!, key.valueMacKey)
|
const contentHmac = generateMac(operation, encContent, keyIdBuf, key.valueMacKey)
|
||||||
if (Buffer.compare(contentHmac, ogValueMac) !== 0) {
|
if (Buffer.compare(contentHmac, ogValueMac) !== 0) {
|
||||||
throw new Boom('HMAC content verification failed')
|
throw new Boom('HMAC content verification failed')
|
||||||
}
|
}
|
||||||
@@ -227,20 +251,25 @@ export const decodeSyncdMutations = async (
|
|||||||
const result = aesDecrypt(encContent, key.valueEncryptionKey)
|
const result = aesDecrypt(encContent, key.valueEncryptionKey)
|
||||||
const syncAction = proto.SyncActionData.decode(result)
|
const syncAction = proto.SyncActionData.decode(result)
|
||||||
|
|
||||||
|
const syncActionIndex = syncAction.index
|
||||||
|
if (!syncActionIndex) {
|
||||||
|
throw new Boom('Missing index in sync action data', { statusCode: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
if (validateMacs) {
|
if (validateMacs) {
|
||||||
const hmac = hmacSign(syncAction.index!, key.indexKey)
|
const hmac = hmacSign(syncActionIndex, key.indexKey)
|
||||||
if (Buffer.compare(hmac, record.index!.blob!) !== 0) {
|
if (Buffer.compare(hmac, indexBlob) !== 0) {
|
||||||
throw new Boom('HMAC index verification failed')
|
throw new Boom('HMAC index verification failed')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const indexStr = Buffer.from(syncAction.index!).toString()
|
const indexStr = Buffer.from(syncActionIndex).toString()
|
||||||
onMutation({ syncAction, index: JSON.parse(indexStr) })
|
onMutation({ syncAction, index: JSON.parse(indexStr) })
|
||||||
|
|
||||||
ltGenerator.mix({
|
ltGenerator.mix({
|
||||||
indexMac: record.index!.blob!,
|
indexMac: indexBlob,
|
||||||
valueMac: ogValueMac,
|
valueMac: ogValueMac,
|
||||||
operation: operation!
|
operation: operation
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,7 +285,12 @@ export const decodeSyncdMutations = async (
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return mutationKeys(keyEnc.keyData!)
|
const keyData = keyEnc.keyData
|
||||||
|
if (!keyData) {
|
||||||
|
throw new Boom('Missing keyData in app state sync key', { statusCode: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
|
return mutationKeys(keyData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,28 +303,71 @@ export const decodeSyncdPatch = async (
|
|||||||
validateMacs: boolean
|
validateMacs: boolean
|
||||||
) => {
|
) => {
|
||||||
if (validateMacs) {
|
if (validateMacs) {
|
||||||
const base64Key = Buffer.from(msg.keyId!.id!).toString('base64')
|
const msgKeyId = msg.keyId?.id
|
||||||
|
if (!msgKeyId) {
|
||||||
|
throw new Boom('Missing keyId in patch message', { statusCode: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const base64Key = Buffer.from(msgKeyId).toString('base64')
|
||||||
const mainKeyObj = await getAppStateSyncKey(base64Key)
|
const mainKeyObj = await getAppStateSyncKey(base64Key)
|
||||||
if (!mainKeyObj) {
|
if (!mainKeyObj) {
|
||||||
throw new Boom(`failed to find key "${base64Key}" to decode patch`, { statusCode: 404, data: { msg } })
|
throw new Boom(`failed to find key "${base64Key}" to decode patch`, { statusCode: 404, data: { msg } })
|
||||||
}
|
}
|
||||||
|
|
||||||
const mainKey = mutationKeys(mainKeyObj.keyData!)
|
const mainKeyData = mainKeyObj.keyData
|
||||||
const mutationmacs = msg.mutations!.map(mutation => mutation.record!.value!.blob!.slice(-32))
|
if (!mainKeyData) {
|
||||||
|
throw new Boom('Missing keyData in main key object', { statusCode: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const mainKey = mutationKeys(mainKeyData)
|
||||||
|
|
||||||
|
const msgMutations = msg.mutations
|
||||||
|
if (!msgMutations) {
|
||||||
|
throw new Boom('Missing mutations in patch message', { statusCode: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const mutationmacs = msgMutations.map(mutation => {
|
||||||
|
const mutBlob = mutation.record?.value?.blob
|
||||||
|
if (!mutBlob) {
|
||||||
|
throw new Boom('Missing blob in mutation record', { statusCode: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
|
return mutBlob.slice(-32)
|
||||||
|
})
|
||||||
|
|
||||||
|
const msgSnapshotMac = msg.snapshotMac
|
||||||
|
if (!msgSnapshotMac) {
|
||||||
|
throw new Boom('Missing snapshotMac in patch message', { statusCode: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const msgVersion = msg.version?.version
|
||||||
|
if (msgVersion == null) {
|
||||||
|
throw new Boom('Missing version in patch message', { statusCode: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const msgPatchMac = msg.patchMac
|
||||||
|
if (!msgPatchMac) {
|
||||||
|
throw new Boom('Missing patchMac in patch message', { statusCode: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
const patchMac = generatePatchMac(
|
const patchMac = generatePatchMac(
|
||||||
msg.snapshotMac!,
|
msgSnapshotMac,
|
||||||
mutationmacs,
|
mutationmacs,
|
||||||
toNumber(msg.version!.version),
|
toNumber(msgVersion),
|
||||||
name,
|
name,
|
||||||
mainKey.patchMacKey
|
mainKey.patchMacKey
|
||||||
)
|
)
|
||||||
if (Buffer.compare(patchMac, msg.patchMac!) !== 0) {
|
if (Buffer.compare(patchMac, msgPatchMac) !== 0) {
|
||||||
throw new Boom('Invalid patch mac')
|
throw new Boom('Invalid patch mac')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await decodeSyncdMutations(msg.mutations!, initialState, getAppStateSyncKey, onMutation, validateMacs)
|
const patchMutations = msg.mutations
|
||||||
|
if (!patchMutations) {
|
||||||
|
throw new Boom('Missing mutations in patch message', { statusCode: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await decodeSyncdMutations(patchMutations, initialState, getAppStateSyncKey, onMutation, validateMacs)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,7 +409,7 @@ export const extractSyncdPatches = async (result: BinaryNode, options: RequestIn
|
|||||||
|
|
||||||
const syncd = proto.SyncdPatch.decode(content as Uint8Array)
|
const syncd = proto.SyncdPatch.decode(content as Uint8Array)
|
||||||
if (!syncd.version) {
|
if (!syncd.version) {
|
||||||
syncd.version = { version: +collectionNode.attrs.version! + 1 }
|
syncd.version = { version: +(collectionNode.attrs.version ?? 0) + 1 }
|
||||||
}
|
}
|
||||||
|
|
||||||
syncds.push(syncd)
|
syncds.push(syncd)
|
||||||
@@ -370,19 +447,30 @@ export const decodeSyncdSnapshot = async (
|
|||||||
validateMacs = true
|
validateMacs = true
|
||||||
) => {
|
) => {
|
||||||
const newState = newLTHashState()
|
const newState = newLTHashState()
|
||||||
newState.version = toNumber(snapshot.version!.version)
|
|
||||||
|
const snapshotVersion = snapshot.version?.version
|
||||||
|
if (snapshotVersion == null) {
|
||||||
|
throw new Boom('Missing version in snapshot', { statusCode: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
|
newState.version = toNumber(snapshotVersion)
|
||||||
|
|
||||||
const mutationMap: ChatMutationMap = {}
|
const mutationMap: ChatMutationMap = {}
|
||||||
const areMutationsRequired = typeof minimumVersionNumber === 'undefined' || newState.version > minimumVersionNumber
|
const areMutationsRequired = typeof minimumVersionNumber === 'undefined' || newState.version > minimumVersionNumber
|
||||||
|
|
||||||
|
const snapshotRecords = snapshot.records
|
||||||
|
if (!snapshotRecords) {
|
||||||
|
throw new Boom('Missing records in snapshot', { statusCode: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
const { hash, indexValueMap } = await decodeSyncdMutations(
|
const { hash, indexValueMap } = await decodeSyncdMutations(
|
||||||
snapshot.records!,
|
snapshotRecords,
|
||||||
newState,
|
newState,
|
||||||
getAppStateSyncKey,
|
getAppStateSyncKey,
|
||||||
areMutationsRequired
|
areMutationsRequired
|
||||||
? mutation => {
|
? mutation => {
|
||||||
const index = mutation.syncAction.index?.toString()
|
const index = mutation.syncAction.index?.toString()
|
||||||
mutationMap[index!] = mutation
|
mutationMap[index ?? ''] = mutation
|
||||||
}
|
}
|
||||||
: () => {},
|
: () => {},
|
||||||
validateMacs
|
validateMacs
|
||||||
@@ -391,15 +479,31 @@ export const decodeSyncdSnapshot = async (
|
|||||||
newState.indexValueMap = indexValueMap
|
newState.indexValueMap = indexValueMap
|
||||||
|
|
||||||
if (validateMacs) {
|
if (validateMacs) {
|
||||||
const base64Key = Buffer.from(snapshot.keyId!.id!).toString('base64')
|
const snapKeyId = snapshot.keyId?.id
|
||||||
|
if (!snapKeyId) {
|
||||||
|
throw new Boom('Missing keyId in snapshot', { statusCode: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const base64Key = Buffer.from(snapKeyId).toString('base64')
|
||||||
const keyEnc = await getAppStateSyncKey(base64Key)
|
const keyEnc = await getAppStateSyncKey(base64Key)
|
||||||
if (!keyEnc) {
|
if (!keyEnc) {
|
||||||
throw new Boom(`failed to find key "${base64Key}" to decode mutation`)
|
throw new Boom(`failed to find key "${base64Key}" to decode mutation`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = mutationKeys(keyEnc.keyData!)
|
const snapKeyData = keyEnc.keyData
|
||||||
|
if (!snapKeyData) {
|
||||||
|
throw new Boom('Missing keyData in snapshot key', { statusCode: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = mutationKeys(snapKeyData)
|
||||||
const computedSnapshotMac = generateSnapshotMac(newState.hash, newState.version, name, result.snapshotMacKey)
|
const computedSnapshotMac = generateSnapshotMac(newState.hash, newState.version, name, result.snapshotMacKey)
|
||||||
if (Buffer.compare(snapshot.mac!, computedSnapshotMac) !== 0) {
|
|
||||||
|
const snapshotMac = snapshot.mac
|
||||||
|
if (!snapshotMac) {
|
||||||
|
throw new Boom('Missing mac in snapshot', { statusCode: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Buffer.compare(snapshotMac, computedSnapshotMac) !== 0) {
|
||||||
throw new Boom(`failed to verify LTHash at ${newState.version} of ${name} from snapshot`)
|
throw new Boom(`failed to verify LTHash at ${newState.version} of ${name} from snapshot`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -436,7 +540,12 @@ export const decodePatches = async (
|
|||||||
syncd.mutations?.push(...ref.mutations)
|
syncd.mutations?.push(...ref.mutations)
|
||||||
}
|
}
|
||||||
|
|
||||||
const patchVersion = toNumber(version!.version)
|
const ver = version?.version
|
||||||
|
if (ver == null) {
|
||||||
|
throw new Boom('Missing version in patch', { statusCode: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const patchVersion = toNumber(ver)
|
||||||
|
|
||||||
newState.version = patchVersion
|
newState.version = patchVersion
|
||||||
const shouldMutate = typeof minimumVersionNumber === 'undefined' || patchVersion > minimumVersionNumber
|
const shouldMutate = typeof minimumVersionNumber === 'undefined' || patchVersion > minimumVersionNumber
|
||||||
@@ -449,7 +558,7 @@ export const decodePatches = async (
|
|||||||
shouldMutate
|
shouldMutate
|
||||||
? mutation => {
|
? mutation => {
|
||||||
const index = mutation.syncAction.index?.toString()
|
const index = mutation.syncAction.index?.toString()
|
||||||
mutationMap[index!] = mutation
|
mutationMap[index ?? ''] = mutation
|
||||||
}
|
}
|
||||||
: () => {},
|
: () => {},
|
||||||
true
|
true
|
||||||
@@ -459,15 +568,30 @@ export const decodePatches = async (
|
|||||||
newState.indexValueMap = decodeResult.indexValueMap
|
newState.indexValueMap = decodeResult.indexValueMap
|
||||||
|
|
||||||
if (validateMacs) {
|
if (validateMacs) {
|
||||||
const base64Key = Buffer.from(keyId!.id!).toString('base64')
|
const patchKeyId = keyId?.id
|
||||||
|
if (!patchKeyId) {
|
||||||
|
throw new Boom('Missing keyId in patch', { statusCode: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const base64Key = Buffer.from(patchKeyId).toString('base64')
|
||||||
const keyEnc = await getAppStateSyncKey(base64Key)
|
const keyEnc = await getAppStateSyncKey(base64Key)
|
||||||
if (!keyEnc) {
|
if (!keyEnc) {
|
||||||
throw new Boom(`failed to find key "${base64Key}" to decode mutation`)
|
throw new Boom(`failed to find key "${base64Key}" to decode mutation`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = mutationKeys(keyEnc.keyData!)
|
const patchKeyData = keyEnc.keyData
|
||||||
|
if (!patchKeyData) {
|
||||||
|
throw new Boom('Missing keyData in patch key', { statusCode: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = mutationKeys(patchKeyData)
|
||||||
const computedSnapshotMac = generateSnapshotMac(newState.hash, newState.version, name, result.snapshotMacKey)
|
const computedSnapshotMac = generateSnapshotMac(newState.hash, newState.version, name, result.snapshotMacKey)
|
||||||
if (Buffer.compare(snapshotMac!, computedSnapshotMac) !== 0) {
|
|
||||||
|
if (!snapshotMac) {
|
||||||
|
throw new Boom('Missing snapshotMac in patch', { statusCode: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Buffer.compare(snapshotMac, computedSnapshotMac) !== 0) {
|
||||||
throw new Boom(`failed to verify LTHash at ${newState.version} of ${name}`)
|
throw new Boom(`failed to verify LTHash at ${newState.version} of ${name}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -565,7 +689,7 @@ export const chatModificationToAppPatch = (mod: ChatModification, jid: string) =
|
|||||||
messageTimestamp: timestamp
|
messageTimestamp: timestamp
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
index: ['deleteMessageForMe', jid, key.id!, key.fromMe ? '1' : '0', '0'],
|
index: ['deleteMessageForMe', jid, key.id ?? '', key.fromMe ? '1' : '0', '0'],
|
||||||
type: 'regular_high',
|
type: 'regular_high',
|
||||||
apiVersion: 3,
|
apiVersion: 3,
|
||||||
operation: OP.SET
|
operation: OP.SET
|
||||||
@@ -615,7 +739,11 @@ export const chatModificationToAppPatch = (mod: ChatModification, jid: string) =
|
|||||||
operation: OP.SET
|
operation: OP.SET
|
||||||
}
|
}
|
||||||
} else if ('star' in mod) {
|
} else if ('star' in mod) {
|
||||||
const key = mod.star.messages[0]!
|
const key = mod.star.messages[0]
|
||||||
|
if (!key) {
|
||||||
|
throw new Boom('Missing star message key', { statusCode: 400 })
|
||||||
|
}
|
||||||
|
|
||||||
patch = {
|
patch = {
|
||||||
syncAction: {
|
syncAction: {
|
||||||
starAction: {
|
starAction: {
|
||||||
@@ -768,7 +896,7 @@ export const processSyncAction = (
|
|||||||
{
|
{
|
||||||
id,
|
id,
|
||||||
muteEndTime: action.muteAction?.muted ? toNumber(action.muteAction.muteEndTimestamp) : null,
|
muteEndTime: action.muteAction?.muted ? toNumber(action.muteAction.muteEndTimestamp) : null,
|
||||||
conditional: getChatUpdateConditional(id!, undefined)
|
conditional: getChatUpdateConditional(id ?? '', undefined)
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
} else if (action?.archiveChatAction || type === 'archive' || type === 'unarchive') {
|
} else if (action?.archiveChatAction || type === 'archive' || type === 'unarchive') {
|
||||||
@@ -797,7 +925,7 @@ export const processSyncAction = (
|
|||||||
{
|
{
|
||||||
id,
|
id,
|
||||||
archived: isArchived,
|
archived: isArchived,
|
||||||
conditional: getChatUpdateConditional(id!, msgRange)
|
conditional: getChatUpdateConditional(id ?? '', msgRange)
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
} else if (action?.markChatAsReadAction) {
|
} else if (action?.markChatAsReadAction) {
|
||||||
@@ -811,7 +939,7 @@ export const processSyncAction = (
|
|||||||
{
|
{
|
||||||
id,
|
id,
|
||||||
unreadCount: isNullUpdate ? null : !!markReadAction?.read ? 0 : -1,
|
unreadCount: isNullUpdate ? null : !!markReadAction?.read ? 0 : -1,
|
||||||
conditional: getChatUpdateConditional(id!, markReadAction?.messageRange)
|
conditional: getChatUpdateConditional(id ?? '', markReadAction?.messageRange)
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
} else if (action?.deleteMessageForMeAction || type === 'deleteMessageForMe') {
|
} else if (action?.deleteMessageForMeAction || type === 'deleteMessageForMe') {
|
||||||
@@ -837,7 +965,7 @@ export const processSyncAction = (
|
|||||||
{
|
{
|
||||||
id,
|
id,
|
||||||
pinned: action.pinAction?.pinned ? toNumber(action.timestamp) : null,
|
pinned: action.pinAction?.pinned ? toNumber(action.timestamp) : null,
|
||||||
conditional: getChatUpdateConditional(id!, undefined)
|
conditional: getChatUpdateConditional(id ?? '', undefined)
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
} else if (action?.unarchiveChatsSetting) {
|
} else if (action?.unarchiveChatsSetting) {
|
||||||
@@ -862,7 +990,7 @@ export const processSyncAction = (
|
|||||||
])
|
])
|
||||||
} else if (action?.deleteChatAction || type === 'deleteChat') {
|
} else if (action?.deleteChatAction || type === 'deleteChat') {
|
||||||
if (!isInitialSync) {
|
if (!isInitialSync) {
|
||||||
ev.emit('chats.delete', [id!])
|
ev.emit('chats.delete', [id ?? ''])
|
||||||
}
|
}
|
||||||
} else if (action?.labelEditAction) {
|
} else if (action?.labelEditAction) {
|
||||||
const { name, color, deleted, predefinedId } = action.labelEditAction
|
const { name, color, deleted, predefinedId } = action.labelEditAction
|
||||||
|
|||||||
+10
-6
@@ -56,7 +56,7 @@ export const isStringNullOrEmpty = (value: string | null | undefined): value is
|
|||||||
|
|
||||||
export const writeRandomPadMax16 = (msg: Uint8Array) => {
|
export const writeRandomPadMax16 = (msg: Uint8Array) => {
|
||||||
const pad = randomBytes(1)
|
const pad = randomBytes(1)
|
||||||
const padLength = (pad[0]! & 0x0f) + 1
|
const padLength = ((pad[0] ?? 0) & 0x0f) + 1
|
||||||
|
|
||||||
return Buffer.concat([msg, Buffer.alloc(padLength, padLength)])
|
return Buffer.concat([msg, Buffer.alloc(padLength, padLength)])
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,7 @@ export const unpadRandomMax16 = (e: Uint8Array | Buffer) => {
|
|||||||
throw new Error('unpadPkcs7 given empty bytes')
|
throw new Error('unpadPkcs7 given empty bytes')
|
||||||
}
|
}
|
||||||
|
|
||||||
var r = t[t.length - 1]!
|
var r = t[t.length - 1] ?? 0
|
||||||
if (r > t.length) {
|
if (r > t.length) {
|
||||||
throw new Error(`unpad given ${t.length} bytes, but pad is ${r}`)
|
throw new Error(`unpad given ${t.length} bytes, but pad is ${r}`)
|
||||||
}
|
}
|
||||||
@@ -85,7 +85,7 @@ export const generateParticipantHashV2 = (participants: string[]): string => {
|
|||||||
export const encodeWAMessage = (message: proto.IMessage) => writeRandomPadMax16(proto.Message.encode(message).finish())
|
export const encodeWAMessage = (message: proto.IMessage) => writeRandomPadMax16(proto.Message.encode(message).finish())
|
||||||
|
|
||||||
export const generateRegistrationId = (): number => {
|
export const generateRegistrationId = (): number => {
|
||||||
return Uint16Array.from(randomBytes(2))[0]! & 16383
|
return (Uint16Array.from(randomBytes(2))[0] ?? 0) & 16383
|
||||||
}
|
}
|
||||||
|
|
||||||
export const encodeBigEndian = (e: number, t = 4) => {
|
export const encodeBigEndian = (e: number, t = 4) => {
|
||||||
@@ -246,10 +246,14 @@ export const fetchLatestBaileysVersion = async (options: RequestInit = {}) => {
|
|||||||
// Extract version from line 7 (const version = [...])
|
// Extract version from line 7 (const version = [...])
|
||||||
const lines = text.split('\n')
|
const lines = text.split('\n')
|
||||||
const versionLine = lines[6] // Line 7 (0-indexed)
|
const versionLine = lines[6] // Line 7 (0-indexed)
|
||||||
const versionMatch = versionLine!.match(/const version = \[(\d+),\s*(\d+),\s*(\d+)\]/)
|
if (!versionLine) {
|
||||||
|
throw new Error('Version line not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
const versionMatch = versionLine.match(/const version = \[(\d+),\s*(\d+),\s*(\d+)\]/)
|
||||||
|
|
||||||
if (versionMatch) {
|
if (versionMatch) {
|
||||||
const version = [parseInt(versionMatch[1]!), parseInt(versionMatch[2]!), parseInt(versionMatch[3]!)] as WAVersion
|
const version = [parseInt(versionMatch[1] ?? '0'), parseInt(versionMatch[2] ?? '0'), parseInt(versionMatch[3] ?? '0')] as WAVersion
|
||||||
|
|
||||||
return {
|
return {
|
||||||
version,
|
version,
|
||||||
@@ -339,7 +343,7 @@ const STATUS_MAP: { [_: string]: proto.WebMessageInfo.Status } = {
|
|||||||
* @param type type from receipt
|
* @param type type from receipt
|
||||||
*/
|
*/
|
||||||
export const getStatusFromReceiptType = (type: string | undefined) => {
|
export const getStatusFromReceiptType = (type: string | undefined) => {
|
||||||
const status = STATUS_MAP[type!]
|
const status = STATUS_MAP[type ?? '']
|
||||||
if (typeof type === 'undefined') {
|
if (typeof type === 'undefined') {
|
||||||
return proto.WebMessageInfo.Status.DELIVERY_ACK
|
return proto.WebMessageInfo.Status.DELIVERY_ACK
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -293,8 +293,8 @@ export const processHistoryMessage = (item: proto.IHistorySync, logger?: ILogger
|
|||||||
case proto.HistorySync.HistorySyncType.RECENT:
|
case proto.HistorySync.HistorySyncType.RECENT:
|
||||||
case proto.HistorySync.HistorySyncType.FULL:
|
case proto.HistorySync.HistorySyncType.FULL:
|
||||||
case proto.HistorySync.HistorySyncType.ON_DEMAND:
|
case proto.HistorySync.HistorySyncType.ON_DEMAND:
|
||||||
for (const chat of item.conversations! as Chat[]) {
|
for (const chat of (item.conversations ?? []) as Chat[]) {
|
||||||
const chatId = chat.id!
|
const chatId = chat.id ?? ''
|
||||||
|
|
||||||
// Source 2: Extract LID-PN mapping from conversation object
|
// Source 2: Extract LID-PN mapping from conversation object
|
||||||
// This handles cases where the mapping isn't in phoneNumberToLidMappings
|
// This handles cases where the mapping isn't in phoneNumberToLidMappings
|
||||||
@@ -328,7 +328,8 @@ export const processHistoryMessage = (item: proto.IHistorySync, logger?: ILogger
|
|||||||
delete chat.messages
|
delete chat.messages
|
||||||
|
|
||||||
for (const item of msgs) {
|
for (const item of msgs) {
|
||||||
const message = item.message! as WAMessage
|
if (!item.message) continue
|
||||||
|
const message = item.message as WAMessage
|
||||||
messages.push(message)
|
messages.push(message)
|
||||||
|
|
||||||
// Source 3: Extract LID-PN mapping from message's alternative JID fields
|
// Source 3: Extract LID-PN mapping from message's alternative JID fields
|
||||||
@@ -358,7 +359,7 @@ export const processHistoryMessage = (item: proto.IHistorySync, logger?: ILogger
|
|||||||
message.messageStubParameters?.[0]
|
message.messageStubParameters?.[0]
|
||||||
) {
|
) {
|
||||||
contacts.push({
|
contacts.push({
|
||||||
id: message.key.participant || message.key.remoteJid!,
|
id: message.key.participant || message.key.remoteJid || '',
|
||||||
verifiedName: message.messageStubParameters?.[0]
|
verifiedName: message.messageStubParameters?.[0]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -369,8 +370,8 @@ export const processHistoryMessage = (item: proto.IHistorySync, logger?: ILogger
|
|||||||
|
|
||||||
break
|
break
|
||||||
case proto.HistorySync.HistorySyncType.PUSH_NAME:
|
case proto.HistorySync.HistorySyncType.PUSH_NAME:
|
||||||
for (const c of item.pushnames!) {
|
for (const c of (item.pushnames ?? [])) {
|
||||||
contacts.push({ id: c.id!, notify: c.pushname! })
|
contacts.push({ id: c.id ?? '', notify: c.pushname ?? '' })
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
@@ -424,7 +425,7 @@ export const downloadAndProcessHistorySyncNotification = async (
|
|||||||
*/
|
*/
|
||||||
export const getHistoryMsg = (message: proto.IMessage) => {
|
export const getHistoryMsg = (message: proto.IMessage) => {
|
||||||
const normalizedContent = !!message ? normalizeMessageContent(message) : undefined
|
const normalizedContent = !!message ? normalizeMessageContent(message) : undefined
|
||||||
const anyHistoryMsg = normalizedContent?.protocolMessage?.historySyncNotification!
|
const anyHistoryMsg = normalizedContent?.protocolMessage?.historySyncNotification
|
||||||
|
|
||||||
return anyHistoryMsg
|
return anyHistoryMsg
|
||||||
}
|
}
|
||||||
|
|||||||
+76
-38
@@ -168,14 +168,17 @@ export const prepareWAMessageMedia = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cacheableKey) {
|
if (cacheableKey) {
|
||||||
const mediaBuff = await options.mediaCache!.get<Buffer>(cacheableKey)
|
const mediaBuff = await options.mediaCache?.get<Buffer>(cacheableKey)
|
||||||
if (mediaBuff) {
|
if (mediaBuff) {
|
||||||
logger?.debug({ cacheableKey }, 'got media cache hit')
|
logger?.debug({ cacheableKey }, 'got media cache hit')
|
||||||
|
|
||||||
const obj = proto.Message.decode(mediaBuff)
|
const obj = proto.Message.decode(mediaBuff)
|
||||||
const key = `${mediaType}Message`
|
const key = `${mediaType}Message`
|
||||||
|
|
||||||
Object.assign(obj[key as keyof proto.Message]!, { ...uploadData, media: undefined })
|
const mediaMsg = obj[key as keyof proto.Message]
|
||||||
|
if (mediaMsg) {
|
||||||
|
Object.assign(mediaMsg, { ...uploadData, media: undefined })
|
||||||
|
}
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
@@ -222,7 +225,7 @@ export const prepareWAMessageMedia = async (
|
|||||||
|
|
||||||
if (cacheableKey) {
|
if (cacheableKey) {
|
||||||
logger?.debug({ cacheableKey }, 'set cache')
|
logger?.debug({ cacheableKey }, 'set cache')
|
||||||
await options.mediaCache!.set(cacheableKey, WAProto.Message.encode(obj).finish())
|
await options.mediaCache?.set(cacheableKey, WAProto.Message.encode(obj).finish())
|
||||||
}
|
}
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
@@ -257,9 +260,13 @@ export const prepareWAMessageMedia = async (
|
|||||||
})(),
|
})(),
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
|
if ((requiresThumbnailComputation || requiresDurationComputation || requiresWaveformProcessing) && !originalFilePath) {
|
||||||
|
throw new Boom('Missing file path for processing')
|
||||||
|
}
|
||||||
|
|
||||||
if (requiresThumbnailComputation) {
|
if (requiresThumbnailComputation) {
|
||||||
const { thumbnail, originalImageDimensions } = await generateThumbnail(
|
const { thumbnail, originalImageDimensions } = await generateThumbnail(
|
||||||
originalFilePath!,
|
originalFilePath,
|
||||||
mediaType as 'image' | 'video',
|
mediaType as 'image' | 'video',
|
||||||
options
|
options
|
||||||
)
|
)
|
||||||
@@ -274,12 +281,12 @@ export const prepareWAMessageMedia = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (requiresDurationComputation) {
|
if (requiresDurationComputation) {
|
||||||
uploadData.seconds = await getAudioDuration(originalFilePath!)
|
uploadData.seconds = await getAudioDuration(originalFilePath)
|
||||||
logger?.debug('computed audio duration')
|
logger?.debug('computed audio duration')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requiresWaveformProcessing) {
|
if (requiresWaveformProcessing) {
|
||||||
uploadData.waveform = await getAudioWaveform(originalFilePath!, logger)
|
uploadData.waveform = await getAudioWaveform(originalFilePath, logger)
|
||||||
logger?.debug('processed waveform')
|
logger?.debug('processed waveform')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,7 +332,7 @@ export const prepareWAMessageMedia = async (
|
|||||||
|
|
||||||
if (cacheableKey) {
|
if (cacheableKey) {
|
||||||
logger?.debug({ cacheableKey }, 'set cache')
|
logger?.debug({ cacheableKey }, 'set cache')
|
||||||
await options.mediaCache!.set(cacheableKey, WAProto.Message.encode(obj).finish())
|
await options.mediaCache?.set(cacheableKey, WAProto.Message.encode(obj).finish())
|
||||||
}
|
}
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
@@ -359,7 +366,11 @@ export const generateForwardMessageContent = (message: WAMessage, forceForward?:
|
|||||||
|
|
||||||
// hacky copy
|
// hacky copy
|
||||||
content = normalizeMessageContent(content)
|
content = normalizeMessageContent(content)
|
||||||
content = proto.Message.decode(proto.Message.encode(content!).finish())
|
if (!content) {
|
||||||
|
throw new Boom('no content in message', { statusCode: 400 })
|
||||||
|
}
|
||||||
|
|
||||||
|
content = proto.Message.decode(proto.Message.encode(content).finish())
|
||||||
|
|
||||||
let key = Object.keys(content)[0] as keyof proto.IMessage
|
let key = Object.keys(content)[0] as keyof proto.IMessage
|
||||||
|
|
||||||
@@ -591,7 +602,8 @@ export const generateCarouselMessage = async (
|
|||||||
|
|
||||||
// Validate cards
|
// Validate cards
|
||||||
for (let i = 0; i < cards.length; i++) {
|
for (let i = 0; i < cards.length; i++) {
|
||||||
const card = cards[i]!
|
const card = cards[i]
|
||||||
|
if (!card) continue
|
||||||
|
|
||||||
// Validate mutual exclusivity of media types
|
// Validate mutual exclusivity of media types
|
||||||
if (card.image && card.video) {
|
if (card.image && card.video) {
|
||||||
@@ -1042,7 +1054,9 @@ export const generateProductCarouselMessage = (
|
|||||||
|
|
||||||
// Validate each product has a valid productId
|
// Validate each product has a valid productId
|
||||||
for (let i = 0; i < products.length; i++) {
|
for (let i = 0; i < products.length; i++) {
|
||||||
const product = products[i]!
|
const product = products[i]
|
||||||
|
if (!product) continue
|
||||||
|
|
||||||
if (!product.productId || typeof product.productId !== 'string' || product.productId.trim().length === 0) {
|
if (!product.productId || typeof product.productId !== 'string' || product.productId.trim().length === 0) {
|
||||||
throw new Boom(`Product at index ${i} must have a non-empty productId`, { statusCode: 400 })
|
throw new Boom(`Product at index ${i} must have a non-empty productId`, { statusCode: 400 })
|
||||||
}
|
}
|
||||||
@@ -1261,14 +1275,7 @@ export const generateWAMessageContent = async (
|
|||||||
options.logger?.warn('[EXPERIMENTAL] Sending buttonsMessage - this may not work and can cause bans')
|
options.logger?.warn('[EXPERIMENTAL] Sending buttonsMessage - this may not work and can cause bans')
|
||||||
} else if (hasNonNullishProperty(message, 'text') && hasNonNullishProperty(message, 'templateButtons')) {
|
} else if (hasNonNullishProperty(message, 'text') && hasNonNullishProperty(message, 'templateButtons')) {
|
||||||
// Process templateButtons
|
// Process templateButtons
|
||||||
const templateMessage: proto.Message.ITemplateMessage = {
|
const hydratedButtons = ((message as any).templateButtons as any[]).map((btn: any) => {
|
||||||
hydratedTemplate: {
|
|
||||||
hydratedContentText: (message as any).text,
|
|
||||||
hydratedFooterText: (message as any).footer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
templateMessage.hydratedTemplate!.hydratedButtons = ((message as any).templateButtons as any[]).map((btn: any) => {
|
|
||||||
if (btn.quickReplyButton) {
|
if (btn.quickReplyButton) {
|
||||||
return { index: btn.index, quickReplyButton: btn.quickReplyButton }
|
return { index: btn.index, quickReplyButton: btn.quickReplyButton }
|
||||||
} else if (btn.urlButton) {
|
} else if (btn.urlButton) {
|
||||||
@@ -1279,6 +1286,14 @@ export const generateWAMessageContent = async (
|
|||||||
return btn
|
return btn
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const templateMessage: proto.Message.ITemplateMessage = {
|
||||||
|
hydratedTemplate: {
|
||||||
|
hydratedContentText: (message as any).text,
|
||||||
|
hydratedFooterText: (message as any).footer,
|
||||||
|
hydratedButtons
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
m.templateMessage = templateMessage
|
m.templateMessage = templateMessage
|
||||||
options.logger?.warn('[EXPERIMENTAL] Sending templateMessage - this may not work and can cause bans')
|
options.logger?.warn('[EXPERIMENTAL] Sending templateMessage - this may not work and can cause bans')
|
||||||
} else if (hasNonNullishProperty(message, 'sections')) {
|
} else if (hasNonNullishProperty(message, 'sections')) {
|
||||||
@@ -1354,7 +1369,9 @@ export const generateWAMessageContent = async (
|
|||||||
let expectedVideoCount = 0
|
let expectedVideoCount = 0
|
||||||
|
|
||||||
for (let i = 0; i < medias.length; i++) {
|
for (let i = 0; i < medias.length; i++) {
|
||||||
const media = medias[i]!
|
const media = medias[i]
|
||||||
|
if (!media) continue
|
||||||
|
|
||||||
if (hasNonNullishProperty(media as AnyMessageContent, 'image')) {
|
if (hasNonNullishProperty(media as AnyMessageContent, 'image')) {
|
||||||
expectedImageCount++
|
expectedImageCount++
|
||||||
} else if (hasNonNullishProperty(media as AnyMessageContent, 'video')) {
|
} else if (hasNonNullishProperty(media as AnyMessageContent, 'video')) {
|
||||||
@@ -1653,13 +1670,15 @@ export const generateWAMessageContent = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hasOptionalProperty(message, 'mentions') && message.mentions?.length) {
|
if (hasOptionalProperty(message, 'mentions') && message.mentions?.length) {
|
||||||
const messageType = Object.keys(m)[0]! as Extract<keyof proto.IMessage, MessageWithContextInfo>
|
const messageType = Object.keys(m)[0] as Extract<keyof proto.IMessage, MessageWithContextInfo>
|
||||||
const key = m[messageType]
|
if (messageType) {
|
||||||
if ('contextInfo' in key! && !!key.contextInfo) {
|
const key = m[messageType]
|
||||||
key.contextInfo.mentionedJid = message.mentions
|
if (key && 'contextInfo' in key && !!key.contextInfo) {
|
||||||
} else if (key!) {
|
key.contextInfo.mentionedJid = message.mentions
|
||||||
key.contextInfo = {
|
} else if (key) {
|
||||||
mentionedJid: message.mentions
|
key.contextInfo = {
|
||||||
|
mentionedJid: message.mentions
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1676,12 +1695,14 @@ export const generateWAMessageContent = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hasOptionalProperty(message, 'contextInfo') && !!message.contextInfo) {
|
if (hasOptionalProperty(message, 'contextInfo') && !!message.contextInfo) {
|
||||||
const messageType = Object.keys(m)[0]! as Extract<keyof proto.IMessage, MessageWithContextInfo>
|
const messageType = Object.keys(m)[0] as Extract<keyof proto.IMessage, MessageWithContextInfo>
|
||||||
const key = m[messageType]
|
if (messageType) {
|
||||||
if ('contextInfo' in key! && !!key.contextInfo) {
|
const key = m[messageType]
|
||||||
key.contextInfo = { ...key.contextInfo, ...message.contextInfo }
|
if (key && 'contextInfo' in key && !!key.contextInfo) {
|
||||||
} else if (key!) {
|
key.contextInfo = { ...key.contextInfo, ...message.contextInfo }
|
||||||
key.contextInfo = message.contextInfo
|
} else if (key) {
|
||||||
|
key.contextInfo = message.contextInfo
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1708,8 +1729,16 @@ export const generateWAMessageFromContent = (
|
|||||||
options.timestamp = new Date()
|
options.timestamp = new Date()
|
||||||
}
|
}
|
||||||
|
|
||||||
const innerMessage = normalizeMessageContent(message)!
|
const innerMessage = normalizeMessageContent(message)
|
||||||
const key = getContentType(innerMessage)! as Exclude<keyof proto.IMessage, 'conversation'>
|
if (!innerMessage) {
|
||||||
|
throw new Boom('no content in message', { statusCode: 400 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = getContentType(innerMessage)
|
||||||
|
if (!key) {
|
||||||
|
throw new Boom('unable to determine message content type', { statusCode: 400 })
|
||||||
|
}
|
||||||
|
|
||||||
const timestamp = unixTimestampSeconds(options.timestamp)
|
const timestamp = unixTimestampSeconds(options.timestamp)
|
||||||
const { quoted, userJid } = options
|
const { quoted, userJid } = options
|
||||||
|
|
||||||
@@ -1718,8 +1747,16 @@ export const generateWAMessageFromContent = (
|
|||||||
? userJid // TODO: Add support for LIDs
|
? userJid // TODO: Add support for LIDs
|
||||||
: quoted.participant || quoted.key.participant || quoted.key.remoteJid
|
: quoted.participant || quoted.key.participant || quoted.key.remoteJid
|
||||||
|
|
||||||
let quotedMsg = normalizeMessageContent(quoted.message)!
|
let quotedMsg = normalizeMessageContent(quoted.message)
|
||||||
const msgType = getContentType(quotedMsg)!
|
if (!quotedMsg) {
|
||||||
|
throw new Boom('no content in quoted message', { statusCode: 400 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const msgType = getContentType(quotedMsg)
|
||||||
|
if (!msgType) {
|
||||||
|
throw new Boom('unable to determine quoted message content type', { statusCode: 400 })
|
||||||
|
}
|
||||||
|
|
||||||
// strip any redundant properties
|
// strip any redundant properties
|
||||||
quotedMsg = proto.Message.create({ [msgType]: quotedMsg[msgType] })
|
quotedMsg = proto.Message.create({ [msgType]: quotedMsg[msgType] })
|
||||||
|
|
||||||
@@ -1728,9 +1765,10 @@ export const generateWAMessageFromContent = (
|
|||||||
delete quotedContent.contextInfo
|
delete quotedContent.contextInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const innerContent = innerMessage[key as Exclude<keyof proto.IMessage, 'conversation'>]
|
||||||
const contextInfo: proto.IContextInfo =
|
const contextInfo: proto.IContextInfo =
|
||||||
('contextInfo' in innerMessage[key]! && innerMessage[key]?.contextInfo) || {}
|
(innerContent && 'contextInfo' in innerContent && innerMessage[key as Exclude<keyof proto.IMessage, 'conversation'>]?.contextInfo) || {}
|
||||||
contextInfo.participant = jidNormalizedUser(participant!)
|
contextInfo.participant = jidNormalizedUser(participant ?? '')
|
||||||
contextInfo.stanzaId = quoted.key.id
|
contextInfo.stanzaId = quoted.key.id
|
||||||
contextInfo.quotedMessage = quotedMsg
|
contextInfo.quotedMessage = quotedMsg
|
||||||
|
|
||||||
|
|||||||
@@ -59,32 +59,40 @@ const REAL_MSG_REQ_ME_STUB_TYPES = new Set([WAMessageStubType.GROUP_PARTICIPANT_
|
|||||||
/** Cleans a received message to further processing */
|
/** Cleans a received message to further processing */
|
||||||
export const cleanMessage = (message: WAMessage, meId: string, meLid: string) => {
|
export const cleanMessage = (message: WAMessage, meId: string, meLid: string) => {
|
||||||
// ensure remoteJid and participant doesn't have device or agent in it
|
// ensure remoteJid and participant doesn't have device or agent in it
|
||||||
if (isHostedPnUser(message.key.remoteJid!) || isHostedLidUser(message.key.remoteJid!)) {
|
const remoteJid = message.key.remoteJid ?? ''
|
||||||
|
if (isHostedPnUser(remoteJid) || isHostedLidUser(remoteJid)) {
|
||||||
message.key.remoteJid = jidEncode(
|
message.key.remoteJid = jidEncode(
|
||||||
jidDecode(message.key?.remoteJid!)?.user!,
|
jidDecode(remoteJid)?.user ?? '',
|
||||||
isHostedPnUser(message.key.remoteJid!) ? 's.whatsapp.net' : 'lid'
|
isHostedPnUser(remoteJid) ? 's.whatsapp.net' : 'lid'
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
message.key.remoteJid = jidNormalizedUser(message.key.remoteJid!)
|
message.key.remoteJid = jidNormalizedUser(remoteJid)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isHostedPnUser(message.key.participant!) || isHostedLidUser(message.key.participant!)) {
|
const participantJid = message.key.participant ?? ''
|
||||||
|
if (isHostedPnUser(participantJid) || isHostedLidUser(participantJid)) {
|
||||||
message.key.participant = jidEncode(
|
message.key.participant = jidEncode(
|
||||||
jidDecode(message.key.participant!)?.user!,
|
jidDecode(participantJid)?.user ?? '',
|
||||||
isHostedPnUser(message.key.participant!) ? 's.whatsapp.net' : 'lid'
|
isHostedPnUser(participantJid) ? 's.whatsapp.net' : 'lid'
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
message.key.participant = jidNormalizedUser(message.key.participant!)
|
message.key.participant = jidNormalizedUser(participantJid)
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = normalizeMessageContent(message.message)
|
const content = normalizeMessageContent(message.message)
|
||||||
// if the message has a reaction, ensure fromMe & remoteJid are from our perspective
|
// if the message has a reaction, ensure fromMe & remoteJid are from our perspective
|
||||||
if (content?.reactionMessage) {
|
if (content?.reactionMessage) {
|
||||||
normaliseKey(content.reactionMessage.key!)
|
const reactionKey = content.reactionMessage.key
|
||||||
|
if (reactionKey) {
|
||||||
|
normaliseKey(reactionKey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content?.pollUpdateMessage) {
|
if (content?.pollUpdateMessage) {
|
||||||
normaliseKey(content.pollUpdateMessage.pollCreationMessageKey!)
|
const pollCreationKey = content.pollUpdateMessage.pollCreationMessageKey
|
||||||
|
if (pollCreationKey) {
|
||||||
|
normaliseKey(pollCreationKey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function normaliseKey(msgKey: WAMessageKey) {
|
function normaliseKey(msgKey: WAMessageKey) {
|
||||||
@@ -94,8 +102,8 @@ export const cleanMessage = (message: WAMessage, meId: string, meLid: string) =>
|
|||||||
// if the sender believed the message being reacted to is not from them
|
// if the sender believed the message being reacted to is not from them
|
||||||
// we've to correct the key to be from them, or some other participant
|
// we've to correct the key to be from them, or some other participant
|
||||||
msgKey.fromMe = !msgKey.fromMe
|
msgKey.fromMe = !msgKey.fromMe
|
||||||
? areJidsSameUser(msgKey.participant || msgKey.remoteJid!, meId) ||
|
? areJidsSameUser(msgKey.participant || (msgKey.remoteJid ?? ''), meId) ||
|
||||||
areJidsSameUser(msgKey.participant || msgKey.remoteJid!, meLid)
|
areJidsSameUser(msgKey.participant || (msgKey.remoteJid ?? ''), meLid)
|
||||||
: // if the message being reacted to, was from them
|
: // if the message being reacted to, was from them
|
||||||
// fromMe automatically becomes false
|
// fromMe automatically becomes false
|
||||||
false
|
false
|
||||||
@@ -150,10 +158,11 @@ export const normalizeMessageJids = async (
|
|||||||
export const isRealMessage = (message: WAMessage) => {
|
export const isRealMessage = (message: WAMessage) => {
|
||||||
const normalizedContent = normalizeMessageContent(message.message)
|
const normalizedContent = normalizeMessageContent(message.message)
|
||||||
const hasSomeContent = !!getContentType(normalizedContent)
|
const hasSomeContent = !!getContentType(normalizedContent)
|
||||||
|
const stubType = message.messageStubType ?? 0
|
||||||
return (
|
return (
|
||||||
(!!normalizedContent ||
|
(!!normalizedContent ||
|
||||||
REAL_MSG_STUB_TYPES.has(message.messageStubType!) ||
|
REAL_MSG_STUB_TYPES.has(stubType) ||
|
||||||
REAL_MSG_REQ_ME_STUB_TYPES.has(message.messageStubType!)) &&
|
REAL_MSG_REQ_ME_STUB_TYPES.has(stubType)) &&
|
||||||
hasSomeContent &&
|
hasSomeContent &&
|
||||||
!normalizedContent?.protocolMessage &&
|
!normalizedContent?.protocolMessage &&
|
||||||
!normalizedContent?.reactionMessage &&
|
!normalizedContent?.reactionMessage &&
|
||||||
@@ -168,11 +177,12 @@ export const shouldIncrementChatUnread = (message: WAMessage) => !message.key.fr
|
|||||||
* Typically -- that'll be the remoteJid, but for broadcasts, it'll be the participant
|
* Typically -- that'll be the remoteJid, but for broadcasts, it'll be the participant
|
||||||
*/
|
*/
|
||||||
export const getChatId = ({ remoteJid, participant, fromMe }: WAMessageKey) => {
|
export const getChatId = ({ remoteJid, participant, fromMe }: WAMessageKey) => {
|
||||||
if (isJidBroadcast(remoteJid!) && !isJidStatusBroadcast(remoteJid!) && !fromMe) {
|
const jid = remoteJid ?? ''
|
||||||
return participant!
|
if (isJidBroadcast(jid) && !isJidStatusBroadcast(jid) && !fromMe) {
|
||||||
|
return participant ?? ''
|
||||||
}
|
}
|
||||||
|
|
||||||
return remoteJid!
|
return jid
|
||||||
}
|
}
|
||||||
|
|
||||||
type PollContext = {
|
type PollContext = {
|
||||||
@@ -219,7 +229,11 @@ export function decryptPollVote(
|
|||||||
const decKey = hmacSign(sign, key0, 'sha256')
|
const decKey = hmacSign(sign, key0, 'sha256')
|
||||||
const aad = toBinary(`${pollMsgId}\u0000${voterJid}`)
|
const aad = toBinary(`${pollMsgId}\u0000${voterJid}`)
|
||||||
|
|
||||||
const decrypted = aesDecryptGCM(encPayload!, decKey, encIv!, aad)
|
if (!encPayload || !encIv) {
|
||||||
|
throw new Error('Missing encPayload or encIv for poll vote decryption')
|
||||||
|
}
|
||||||
|
|
||||||
|
const decrypted = aesDecryptGCM(encPayload, decKey, encIv, aad)
|
||||||
return proto.Message.PollVoteMessage.decode(decrypted)
|
return proto.Message.PollVoteMessage.decode(decrypted)
|
||||||
|
|
||||||
function toBinary(txt: string) {
|
function toBinary(txt: string) {
|
||||||
@@ -249,7 +263,11 @@ export function decryptEventResponse(
|
|||||||
const decKey = hmacSign(sign, key0, 'sha256')
|
const decKey = hmacSign(sign, key0, 'sha256')
|
||||||
const aad = toBinary(`${eventMsgId}\u0000${responderJid}`)
|
const aad = toBinary(`${eventMsgId}\u0000${responderJid}`)
|
||||||
|
|
||||||
const decrypted = aesDecryptGCM(encPayload!, decKey, encIv!, aad)
|
if (!encPayload || !encIv) {
|
||||||
|
throw new Error('Missing encPayload or encIv for event response decryption')
|
||||||
|
}
|
||||||
|
|
||||||
|
const decrypted = aesDecryptGCM(encPayload, decKey, encIv, aad)
|
||||||
return proto.Message.EventResponseMessage.decode(decrypted)
|
return proto.Message.EventResponseMessage.decode(decrypted)
|
||||||
|
|
||||||
function toBinary(txt: string) {
|
function toBinary(txt: string) {
|
||||||
@@ -271,7 +289,12 @@ const processMessage = async (
|
|||||||
getMessage
|
getMessage
|
||||||
}: ProcessMessageContext
|
}: ProcessMessageContext
|
||||||
) => {
|
) => {
|
||||||
const meId = creds.me!.id
|
const meUser = creds.me
|
||||||
|
if (!meUser) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const meId = meUser.id
|
||||||
const { accountSettings } = creds
|
const { accountSettings } = creds
|
||||||
|
|
||||||
const chat: Partial<Chat> = { id: jidNormalizedUser(getChatId(message.key)) }
|
const chat: Partial<Chat> = { id: jidNormalizedUser(getChatId(message.key)) }
|
||||||
@@ -299,7 +322,10 @@ const processMessage = async (
|
|||||||
if (protocolMsg) {
|
if (protocolMsg) {
|
||||||
switch (protocolMsg.type) {
|
switch (protocolMsg.type) {
|
||||||
case proto.Message.ProtocolMessage.Type.HISTORY_SYNC_NOTIFICATION:
|
case proto.Message.ProtocolMessage.Type.HISTORY_SYNC_NOTIFICATION:
|
||||||
const histNotification = protocolMsg.historySyncNotification!
|
const histNotification = protocolMsg.historySyncNotification
|
||||||
|
if (!histNotification) {
|
||||||
|
break
|
||||||
|
}
|
||||||
const process = shouldProcessHistoryMsg
|
const process = shouldProcessHistoryMsg
|
||||||
const isLatest = !creds.processedHistoryMessages?.length
|
const isLatest = !creds.processedHistoryMessages?.length
|
||||||
|
|
||||||
@@ -366,16 +392,23 @@ const processMessage = async (
|
|||||||
|
|
||||||
break
|
break
|
||||||
case proto.Message.ProtocolMessage.Type.APP_STATE_SYNC_KEY_SHARE:
|
case proto.Message.ProtocolMessage.Type.APP_STATE_SYNC_KEY_SHARE:
|
||||||
const keys = protocolMsg.appStateSyncKeyShare!.keys
|
const keys = protocolMsg.appStateSyncKeyShare?.keys
|
||||||
if (keys?.length) {
|
if (keys?.length) {
|
||||||
let newAppStateSyncKeyId = ''
|
let newAppStateSyncKeyId = ''
|
||||||
await keyStore.transaction(async () => {
|
await keyStore.transaction(async () => {
|
||||||
const newKeys: string[] = []
|
const newKeys: string[] = []
|
||||||
for (const { keyData, keyId } of keys) {
|
for (const { keyData, keyId } of keys) {
|
||||||
const strKeyId = Buffer.from(keyId!.keyId!).toString('base64')
|
const keyIdValue = keyId?.keyId
|
||||||
|
if (!keyIdValue) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const strKeyId = Buffer.from(keyIdValue).toString('base64')
|
||||||
newKeys.push(strKeyId)
|
newKeys.push(strKeyId)
|
||||||
|
|
||||||
await keyStore.set({ 'app-state-sync-key': { [strKeyId]: keyData! } })
|
if (keyData) {
|
||||||
|
await keyStore.set({ 'app-state-sync-key': { [strKeyId]: keyData } })
|
||||||
|
}
|
||||||
|
|
||||||
newAppStateSyncKeyId = strKeyId
|
newAppStateSyncKeyId = strKeyId
|
||||||
}
|
}
|
||||||
@@ -394,7 +427,7 @@ const processMessage = async (
|
|||||||
{
|
{
|
||||||
key: {
|
key: {
|
||||||
...message.key,
|
...message.key,
|
||||||
id: protocolMsg.key!.id
|
id: protocolMsg.key?.id
|
||||||
},
|
},
|
||||||
update: { message: null, messageStubType: WAMessageStubType.REVOKE, key: message.key }
|
update: { message: null, messageStubType: WAMessageStubType.REVOKE, key: message.key }
|
||||||
}
|
}
|
||||||
@@ -407,17 +440,27 @@ const processMessage = async (
|
|||||||
})
|
})
|
||||||
break
|
break
|
||||||
case proto.Message.ProtocolMessage.Type.PEER_DATA_OPERATION_REQUEST_RESPONSE_MESSAGE:
|
case proto.Message.ProtocolMessage.Type.PEER_DATA_OPERATION_REQUEST_RESPONSE_MESSAGE:
|
||||||
const response = protocolMsg.peerDataOperationRequestResponseMessage!
|
const response = protocolMsg.peerDataOperationRequestResponseMessage
|
||||||
if (response) {
|
if (response) {
|
||||||
await placeholderResendCache?.del(response.stanzaId!)
|
if (response.stanzaId) {
|
||||||
|
await placeholderResendCache?.del(response.stanzaId)
|
||||||
|
}
|
||||||
// TODO: IMPLEMENT HISTORY SYNC ETC (sticker uploads etc.).
|
// TODO: IMPLEMENT HISTORY SYNC ETC (sticker uploads etc.).
|
||||||
const { peerDataOperationResult } = response
|
const { peerDataOperationResult } = response
|
||||||
|
if (!peerDataOperationResult) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
let recoveredCount = 0
|
let recoveredCount = 0
|
||||||
for (const result of peerDataOperationResult!) {
|
for (const result of peerDataOperationResult) {
|
||||||
const { placeholderMessageResendResponse: retryResponse } = result
|
const { placeholderMessageResendResponse: retryResponse } = result
|
||||||
//eslint-disable-next-line max-depth
|
//eslint-disable-next-line max-depth
|
||||||
if (retryResponse) {
|
if (retryResponse) {
|
||||||
const webMessageInfo = proto.WebMessageInfo.decode(retryResponse.webMessageInfoBytes!)
|
if (!retryResponse.webMessageInfoBytes) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const webMessageInfo = proto.WebMessageInfo.decode(retryResponse.webMessageInfoBytes)
|
||||||
|
|
||||||
// Track CTWA message recovery success
|
// Track CTWA message recovery success
|
||||||
recoveredCount++
|
recoveredCount++
|
||||||
@@ -435,7 +478,7 @@ const processMessage = async (
|
|||||||
ev.emit('messages.upsert', {
|
ev.emit('messages.upsert', {
|
||||||
messages: [webMessageInfo as WAMessage],
|
messages: [webMessageInfo as WAMessage],
|
||||||
type: 'notify',
|
type: 'notify',
|
||||||
requestId: response.stanzaId!
|
requestId: response.stanzaId ?? ''
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -474,17 +517,21 @@ const processMessage = async (
|
|||||||
const labelAssociationMsg = protocolMsg.memberLabel
|
const labelAssociationMsg = protocolMsg.memberLabel
|
||||||
if (labelAssociationMsg?.label) {
|
if (labelAssociationMsg?.label) {
|
||||||
ev.emit('group.member-tag.update', {
|
ev.emit('group.member-tag.update', {
|
||||||
groupId: chat.id!,
|
groupId: chat.id ?? '',
|
||||||
label: labelAssociationMsg.label,
|
label: labelAssociationMsg.label,
|
||||||
participant: message.key.participant!,
|
participant: message.key.participant ?? '',
|
||||||
participantAlt: message.key.participantAlt!,
|
participantAlt: message.key.participantAlt ?? '',
|
||||||
messageTimestamp: Number(message.messageTimestamp)
|
messageTimestamp: Number(message.messageTimestamp)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
case proto.Message.ProtocolMessage.Type.LID_MIGRATION_MAPPING_SYNC:
|
case proto.Message.ProtocolMessage.Type.LID_MIGRATION_MAPPING_SYNC:
|
||||||
const encodedPayload = protocolMsg.lidMigrationMappingSyncMessage?.encodedMappingPayload!
|
const encodedPayload = protocolMsg.lidMigrationMappingSyncMessage?.encodedMappingPayload
|
||||||
|
if (!encodedPayload) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
const { pnToLidMappings, chatDbMigrationTimestamp } =
|
const { pnToLidMappings, chatDbMigrationTimestamp } =
|
||||||
proto.LIDMigrationMappingSyncPayload.decode(encodedPayload)
|
proto.LIDMigrationMappingSyncPayload.decode(encodedPayload)
|
||||||
logger?.debug({ pnToLidMappings, chatDbMigrationTimestamp }, 'got lid mappings and chat db migration timestamp')
|
logger?.debug({ pnToLidMappings, chatDbMigrationTimestamp }, 'got lid mappings and chat db migration timestamp')
|
||||||
@@ -502,6 +549,11 @@ const processMessage = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (content?.reactionMessage) {
|
} else if (content?.reactionMessage) {
|
||||||
|
const reactionKey = content.reactionMessage.key
|
||||||
|
if (!reactionKey) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const reaction: proto.IReaction = {
|
const reaction: proto.IReaction = {
|
||||||
...content.reactionMessage,
|
...content.reactionMessage,
|
||||||
key: message.key
|
key: message.key
|
||||||
@@ -509,12 +561,15 @@ const processMessage = async (
|
|||||||
ev.emit('messages.reaction', [
|
ev.emit('messages.reaction', [
|
||||||
{
|
{
|
||||||
reaction,
|
reaction,
|
||||||
key: content.reactionMessage?.key!
|
key: reactionKey
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
} else if (content?.encEventResponseMessage) {
|
} else if (content?.encEventResponseMessage) {
|
||||||
const encEventResponse = content.encEventResponseMessage
|
const encEventResponse = content.encEventResponseMessage
|
||||||
const creationMsgKey = encEventResponse.eventCreationMessageKey!
|
const creationMsgKey = encEventResponse.eventCreationMessageKey
|
||||||
|
if (!creationMsgKey) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// we need to fetch the event creation message to get the event enc key
|
// we need to fetch the event creation message to get the event enc key
|
||||||
const eventMsg = await getMessage(creationMsgKey)
|
const eventMsg = await getMessage(creationMsgKey)
|
||||||
@@ -523,12 +578,16 @@ const processMessage = async (
|
|||||||
const meIdNormalised = jidNormalizedUser(meId)
|
const meIdNormalised = jidNormalizedUser(meId)
|
||||||
|
|
||||||
// all jids need to be PN
|
// all jids need to be PN
|
||||||
const eventCreatorKey = creationMsgKey.participant || creationMsgKey.remoteJid!
|
const eventCreatorKey = creationMsgKey.participant || (creationMsgKey.remoteJid ?? '')
|
||||||
const eventCreatorPn = isLidUser(eventCreatorKey)
|
const eventCreatorPn = isLidUser(eventCreatorKey)
|
||||||
? await signalRepository.lidMapping.getPNForLID(eventCreatorKey)
|
? await signalRepository.lidMapping.getPNForLID(eventCreatorKey)
|
||||||
: eventCreatorKey
|
: eventCreatorKey
|
||||||
|
if (!eventCreatorPn) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const eventCreatorJid = getKeyAuthor(
|
const eventCreatorJid = getKeyAuthor(
|
||||||
{ remoteJid: jidNormalizedUser(eventCreatorPn!), fromMe: meIdNormalised === eventCreatorPn },
|
{ remoteJid: jidNormalizedUser(eventCreatorPn), fromMe: meIdNormalised === eventCreatorPn },
|
||||||
meIdNormalised
|
meIdNormalised
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -541,7 +600,7 @@ const processMessage = async (
|
|||||||
const responseMsg = decryptEventResponse(encEventResponse, {
|
const responseMsg = decryptEventResponse(encEventResponse, {
|
||||||
eventEncKey,
|
eventEncKey,
|
||||||
eventCreatorJid,
|
eventCreatorJid,
|
||||||
eventMsgId: creationMsgKey.id!,
|
eventMsgId: creationMsgKey.id ?? '',
|
||||||
responderJid
|
responderJid
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,12 @@ const getClientPayload = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const generateLoginNode = (userJid: string, config: SocketConfig): proto.IClientPayload => {
|
export const generateLoginNode = (userJid: string, config: SocketConfig): proto.IClientPayload => {
|
||||||
const { user, device } = jidDecode(userJid)!
|
const decoded = jidDecode(userJid)
|
||||||
|
if (!decoded) {
|
||||||
|
throw new Boom('Invalid user JID', { statusCode: 400 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const { user, device } = decoded
|
||||||
const payload: proto.IClientPayload = {
|
const payload: proto.IClientPayload = {
|
||||||
...getClientPayload(config),
|
...getClientPayload(config),
|
||||||
passive: true,
|
passive: true,
|
||||||
@@ -153,6 +158,9 @@ export const configureSuccessfulPairing = (
|
|||||||
}: Pick<AuthenticationCreds, 'advSecretKey' | 'signedIdentityKey' | 'signalIdentities'>
|
}: Pick<AuthenticationCreds, 'advSecretKey' | 'signedIdentityKey' | 'signalIdentities'>
|
||||||
) => {
|
) => {
|
||||||
const msgId = stanza.attrs.id
|
const msgId = stanza.attrs.id
|
||||||
|
if (!msgId) {
|
||||||
|
throw new Boom('Missing message ID', { statusCode: 400 })
|
||||||
|
}
|
||||||
|
|
||||||
const pairSuccessNode = getBinaryNodeChild(stanza, 'pair-success')
|
const pairSuccessNode = getBinaryNodeChild(stanza, 'pair-success')
|
||||||
|
|
||||||
@@ -168,42 +176,51 @@ export const configureSuccessfulPairing = (
|
|||||||
const bizName = businessNode?.attrs.name
|
const bizName = businessNode?.attrs.name
|
||||||
const jid = deviceNode.attrs.jid
|
const jid = deviceNode.attrs.jid
|
||||||
const lid = deviceNode.attrs.lid
|
const lid = deviceNode.attrs.lid
|
||||||
|
if (!jid || !lid) {
|
||||||
|
throw new Boom('Missing JID or LID in device node', { statusCode: 400 })
|
||||||
|
}
|
||||||
|
|
||||||
const { details, hmac, accountType } = proto.ADVSignedDeviceIdentityHMAC.decode(deviceIdentityNode.content as Buffer)
|
const { details, hmac, accountType } = proto.ADVSignedDeviceIdentityHMAC.decode(deviceIdentityNode.content as Buffer)
|
||||||
|
if (!details || !hmac) {
|
||||||
|
throw new Boom('Missing ADV signature fields', { statusCode: 400 })
|
||||||
|
}
|
||||||
|
|
||||||
let hmacPrefix = Buffer.from([])
|
let hmacPrefix = Buffer.from([])
|
||||||
if (accountType !== undefined && accountType === proto.ADVEncryptionType.HOSTED) {
|
if (accountType !== undefined && accountType === proto.ADVEncryptionType.HOSTED) {
|
||||||
hmacPrefix = WA_ADV_HOSTED_ACCOUNT_SIG_PREFIX
|
hmacPrefix = WA_ADV_HOSTED_ACCOUNT_SIG_PREFIX
|
||||||
}
|
}
|
||||||
|
|
||||||
const advSign = hmacSign(Buffer.concat([hmacPrefix, details!]), Buffer.from(advSecretKey, 'base64'))
|
const advSign = hmacSign(Buffer.concat([hmacPrefix, details]), Buffer.from(advSecretKey, 'base64'))
|
||||||
if (Buffer.compare(hmac!, advSign) !== 0) {
|
if (Buffer.compare(hmac, advSign) !== 0) {
|
||||||
throw new Boom('Invalid account signature')
|
throw new Boom('Invalid account signature')
|
||||||
}
|
}
|
||||||
|
|
||||||
const account = proto.ADVSignedDeviceIdentity.decode(details!)
|
const account = proto.ADVSignedDeviceIdentity.decode(details)
|
||||||
const { accountSignatureKey, accountSignature, details: deviceDetails } = account
|
const { accountSignatureKey, accountSignature, details: deviceDetails } = account
|
||||||
|
if (!accountSignatureKey || !accountSignature || !deviceDetails) {
|
||||||
|
throw new Boom('Missing ADV account fields', { statusCode: 400 })
|
||||||
|
}
|
||||||
|
|
||||||
const deviceIdentity = proto.ADVDeviceIdentity.decode(deviceDetails!)
|
const deviceIdentity = proto.ADVDeviceIdentity.decode(deviceDetails)
|
||||||
|
|
||||||
const accountSignaturePrefix =
|
const accountSignaturePrefix =
|
||||||
deviceIdentity.deviceType === proto.ADVEncryptionType.HOSTED
|
deviceIdentity.deviceType === proto.ADVEncryptionType.HOSTED
|
||||||
? WA_ADV_HOSTED_ACCOUNT_SIG_PREFIX
|
? WA_ADV_HOSTED_ACCOUNT_SIG_PREFIX
|
||||||
: WA_ADV_ACCOUNT_SIG_PREFIX
|
: WA_ADV_ACCOUNT_SIG_PREFIX
|
||||||
const accountMsg = Buffer.concat([accountSignaturePrefix, deviceDetails!, signedIdentityKey.public])
|
const accountMsg = Buffer.concat([accountSignaturePrefix, deviceDetails, signedIdentityKey.public])
|
||||||
if (!Curve.verify(accountSignatureKey!, accountMsg, accountSignature!)) {
|
if (!Curve.verify(accountSignatureKey, accountMsg, accountSignature)) {
|
||||||
throw new Boom('Failed to verify account signature')
|
throw new Boom('Failed to verify account signature')
|
||||||
}
|
}
|
||||||
|
|
||||||
const deviceMsg = Buffer.concat([
|
const deviceMsg = Buffer.concat([
|
||||||
WA_ADV_DEVICE_SIG_PREFIX,
|
WA_ADV_DEVICE_SIG_PREFIX,
|
||||||
deviceDetails!,
|
deviceDetails,
|
||||||
signedIdentityKey.public,
|
signedIdentityKey.public,
|
||||||
accountSignatureKey!
|
accountSignatureKey
|
||||||
])
|
])
|
||||||
account.deviceSignature = Curve.sign(signedIdentityKey.private, deviceMsg)
|
account.deviceSignature = Curve.sign(signedIdentityKey.private, deviceMsg)
|
||||||
|
|
||||||
const identity = createSignalIdentity(lid!, accountSignatureKey!)
|
const identity = createSignalIdentity(lid, accountSignatureKey)
|
||||||
const accountEnc = encodeSignedDeviceIdentity(account, false)
|
const accountEnc = encodeSignedDeviceIdentity(account, false)
|
||||||
|
|
||||||
const reply: BinaryNode = {
|
const reply: BinaryNode = {
|
||||||
@@ -211,7 +228,7 @@ export const configureSuccessfulPairing = (
|
|||||||
attrs: {
|
attrs: {
|
||||||
to: S_WHATSAPP_NET,
|
to: S_WHATSAPP_NET,
|
||||||
type: 'result',
|
type: 'result',
|
||||||
id: msgId!
|
id: msgId
|
||||||
},
|
},
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
@@ -220,7 +237,7 @@ export const configureSuccessfulPairing = (
|
|||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
tag: 'device-identity',
|
tag: 'device-identity',
|
||||||
attrs: { 'key-index': deviceIdentity.keyIndex!.toString() },
|
attrs: { 'key-index': String(deviceIdentity.keyIndex ?? '') },
|
||||||
content: accountEnc
|
content: accountEnc
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -230,7 +247,7 @@ export const configureSuccessfulPairing = (
|
|||||||
|
|
||||||
const authUpdate: Partial<AuthenticationCreds> = {
|
const authUpdate: Partial<AuthenticationCreds> = {
|
||||||
account,
|
account,
|
||||||
me: { id: jid!, name: bizName, lid },
|
me: { id: jid, name: bizName, lid },
|
||||||
signalIdentities: [...(signalIdentities || []), identity],
|
signalIdentities: [...(signalIdentities || []), identity],
|
||||||
platform: platformNode?.attrs.name
|
platform: platformNode?.attrs.name
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export class USyncBotProfileProtocol implements USyncQueryProtocol {
|
|||||||
return {
|
return {
|
||||||
tag: 'bot',
|
tag: 'bot',
|
||||||
attrs: {},
|
attrs: {},
|
||||||
content: [{ tag: 'profile', attrs: { persona_id: user.personaId! } }]
|
content: [{ tag: 'profile', attrs: { persona_id: user.personaId ?? '' } }]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,24 +51,24 @@ export class USyncBotProfileProtocol implements USyncQueryProtocol {
|
|||||||
|
|
||||||
for (const command of getBinaryNodeChildren(commandsNode, 'command')) {
|
for (const command of getBinaryNodeChildren(commandsNode, 'command')) {
|
||||||
commands.push({
|
commands.push({
|
||||||
name: getBinaryNodeChildString(command, 'name')!,
|
name: getBinaryNodeChildString(command, 'name') ?? '',
|
||||||
description: getBinaryNodeChildString(command, 'description')!
|
description: getBinaryNodeChildString(command, 'description') ?? ''
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const prompt of getBinaryNodeChildren(promptsNode, 'prompt')) {
|
for (const prompt of getBinaryNodeChildren(promptsNode, 'prompt')) {
|
||||||
prompts.push(`${getBinaryNodeChildString(prompt, 'emoji')!} ${getBinaryNodeChildString(prompt, 'text')!}`)
|
prompts.push(`${getBinaryNodeChildString(prompt, 'emoji') ?? ''} ${getBinaryNodeChildString(prompt, 'text') ?? ''}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isDefault: !!getBinaryNodeChild(profile, 'default'),
|
isDefault: !!getBinaryNodeChild(profile, 'default'),
|
||||||
jid: node.attrs.jid!,
|
jid: node.attrs.jid ?? '',
|
||||||
name: getBinaryNodeChildString(profile, 'name')!,
|
name: getBinaryNodeChildString(profile, 'name') ?? '',
|
||||||
attributes: getBinaryNodeChildString(profile, 'attributes')!,
|
attributes: getBinaryNodeChildString(profile, 'attributes') ?? '',
|
||||||
description: getBinaryNodeChildString(profile, 'description')!,
|
description: getBinaryNodeChildString(profile, 'description') ?? '',
|
||||||
category: getBinaryNodeChildString(profile, 'category')!,
|
category: getBinaryNodeChildString(profile, 'category') ?? '',
|
||||||
personaId: profile!.attrs['persona_id']!,
|
personaId: profile?.attrs['persona_id'] ?? '',
|
||||||
commandsDescription: getBinaryNodeChildString(commandsNode, 'description')!,
|
commandsDescription: getBinaryNodeChildString(commandsNode, 'description') ?? '',
|
||||||
commands,
|
commands,
|
||||||
prompts
|
prompts
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user