mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
PDO protocol (peer data operation): Get more history sync + better message retry mechanism (#919)
* feat(feature/pdo-sync): initial commit * feat(feature/pdo-sync): Moved to conventional send functions, exported, patched some errors * fix(feature/pdo-sync): Linting and more bugsquatting * chore(feature/pdo-sync): linting done * feat/fix(feat/pdo-sync): Newsletter decrypt + ack * merge (#946) * fix: profilePictureUrl (#901) * Update module to latest version (#926) * Update package.json Update the module to the latest * Add files via upload * Fix: Readme use upsert events (#908) * Fix: getUSyncDevices (#862) * Update messages-send.ts * Update messages-send.ts * Update messages-send.ts * Fix lint * Fix lint * fix(master): update linting workflow to node 20 (current LTS) --------- Co-authored-by: Akhlaqul Muhammad Fadwa <75623219+zennn08@users.noreply.github.com> Co-authored-by: Rizz2Dev <muhamad.rizki27483@smp.belajar.id> Co-authored-by: Oscar Guindzberg <oscar.guindzberg@gmail.com> Co-authored-by: Bob <115008575+bobslavtriev@users.noreply.github.com> * chore(feature/pdo-sync): final linting * fix(feature/pdo-sync): make replies optional * feat(feat/pdo-sync): add <unavailable> handle * feat(feature/pdo-sync): Fixed the issues with peer messages and implemented some more logic * fix(feature/pdo-sync): Make progress optional * fix(feature/pdo-sync): Nullify and defeat Message absent from node if it is resolved immediately * feat(feature/pdo-sync): Export message absent from node and export PDO request ID with it --------- Co-authored-by: Akhlaqul Muhammad Fadwa <75623219+zennn08@users.noreply.github.com> Co-authored-by: Rizz2Dev <muhamad.rizki27483@smp.belajar.id> Co-authored-by: Oscar Guindzberg <oscar.guindzberg@gmail.com> Co-authored-by: Bob <115008575+bobslavtriev@users.noreply.github.com>
This commit is contained in:
@@ -2,12 +2,12 @@ import { Boom } from '@hapi/boom'
|
||||
import { Logger } from 'pino'
|
||||
import { proto } from '../../WAProto'
|
||||
import { SignalRepository, WAMessageKey } from '../Types'
|
||||
import { areJidsSameUser, BinaryNode, isJidBroadcast, isJidGroup, isJidStatusBroadcast, isJidUser, isLidUser } from '../WABinary'
|
||||
import { BufferJSON, unpadRandomMax16 } from './generics'
|
||||
import { areJidsSameUser, BinaryNode, isJidBroadcast, isJidGroup, isJidNewsletter, isJidStatusBroadcast, isJidUser, isLidUser } from '../WABinary'
|
||||
import { unpadRandomMax16 } from './generics'
|
||||
|
||||
const NO_MESSAGE_FOUND_ERROR_TEXT = 'Message absent from node'
|
||||
export const NO_MESSAGE_FOUND_ERROR_TEXT = 'Message absent from node'
|
||||
|
||||
type MessageType = 'chat' | 'peer_broadcast' | 'other_broadcast' | 'group' | 'direct_peer_status' | 'other_status'
|
||||
type MessageType = 'chat' | 'peer_broadcast' | 'other_broadcast' | 'group' | 'direct_peer_status' | 'other_status' | 'newsletter'
|
||||
|
||||
/**
|
||||
* Decode the received node as a message.
|
||||
@@ -78,12 +78,16 @@ export function decodeMessageNode(
|
||||
|
||||
chatId = from
|
||||
author = participant
|
||||
} else if(isJidNewsletter(from)) {
|
||||
msgType = 'newsletter'
|
||||
chatId = from
|
||||
author = from
|
||||
} else {
|
||||
throw new Boom('Unknown message type', { data: stanza })
|
||||
}
|
||||
|
||||
const fromMe = (isLidUser(from) ? isMeLid : isMe)(stanza.attrs.participant || stanza.attrs.from)
|
||||
const pushname = stanza.attrs.notify
|
||||
const pushname = stanza?.attrs?.notify
|
||||
|
||||
const key: WAMessageKey = {
|
||||
remoteJid: chatId,
|
||||
@@ -132,7 +136,7 @@ export const decryptMessageNode = (
|
||||
fullMessage.verifiedBizName = details.verifiedName
|
||||
}
|
||||
|
||||
if(tag !== 'enc') {
|
||||
if(tag !== 'enc' && tag !== 'plaintext') {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -145,7 +149,7 @@ export const decryptMessageNode = (
|
||||
let msgBuffer: Uint8Array
|
||||
|
||||
try {
|
||||
const e2eType = attrs.type
|
||||
const e2eType = tag === 'plaintext' ? 'plaintext' : attrs.type
|
||||
switch (e2eType) {
|
||||
case 'skmsg':
|
||||
msgBuffer = await repository.decryptGroupMessage({
|
||||
@@ -163,11 +167,14 @@ export const decryptMessageNode = (
|
||||
ciphertext: content
|
||||
})
|
||||
break
|
||||
case 'plaintext':
|
||||
msgBuffer = content
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unknown e2e type: ${e2eType}`)
|
||||
}
|
||||
|
||||
let msg: proto.IMessage = proto.Message.decode(unpadRandomMax16(msgBuffer))
|
||||
let msg: proto.IMessage = proto.Message.decode(e2eType !== 'plaintext' ? unpadRandomMax16(msgBuffer) : msgBuffer)
|
||||
msg = msg.deviceSentMessage?.message || msg
|
||||
if(msg.senderKeyDistributionMessage) {
|
||||
try {
|
||||
@@ -199,7 +206,7 @@ export const decryptMessageNode = (
|
||||
// if nothing was found to decrypt
|
||||
if(!decryptables) {
|
||||
fullMessage.messageStubType = proto.WebMessageInfo.StubType.CIPHERTEXT
|
||||
fullMessage.messageStubParameters = [NO_MESSAGE_FOUND_ERROR_TEXT, JSON.stringify(stanza, BufferJSON.replacer)]
|
||||
fullMessage.messageStubParameters = [NO_MESSAGE_FOUND_ERROR_TEXT]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ export const processHistoryMessage = (item: proto.IHistorySync) => {
|
||||
case proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP:
|
||||
case proto.HistorySync.HistorySyncType.RECENT:
|
||||
case proto.HistorySync.HistorySyncType.FULL:
|
||||
case proto.HistorySync.HistorySyncType.ON_DEMAND:
|
||||
for(const chat of item.conversations! as Chat[]) {
|
||||
contacts.push({ id: chat.id, name: chat.name || undefined })
|
||||
|
||||
@@ -93,6 +94,8 @@ export const processHistoryMessage = (item: proto.IHistorySync) => {
|
||||
chats,
|
||||
contacts,
|
||||
messages,
|
||||
syncType: item.syncType,
|
||||
progress: item.progress,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AxiosRequestConfig } from 'axios'
|
||||
import type { Logger } from 'pino'
|
||||
import { proto } from '../../WAProto'
|
||||
import { AuthenticationCreds, BaileysEventEmitter, Chat, GroupMetadata, ParticipantAction, RequestJoinAction, RequestJoinMethod, SignalKeyStoreWithTransaction, SocketConfig, WAMessageStubType } from '../Types'
|
||||
import { AuthenticationCreds, BaileysEventEmitter, CacheStore, Chat, GroupMetadata, ParticipantAction, RequestJoinAction, RequestJoinMethod, SignalKeyStoreWithTransaction, SocketConfig, WAMessageStubType } from '../Types'
|
||||
import { getContentType, normalizeMessageContent } from '../Utils/messages'
|
||||
import { areJidsSameUser, isJidBroadcast, isJidStatusBroadcast, jidNormalizedUser } from '../WABinary'
|
||||
import { aesDecryptGCM, hmacSign } from './crypto'
|
||||
@@ -10,6 +10,7 @@ import { downloadAndProcessHistorySyncNotification } from './history'
|
||||
|
||||
type ProcessMessageContext = {
|
||||
shouldProcessHistoryMsg: boolean
|
||||
placeholderResendCache?: CacheStore
|
||||
creds: AuthenticationCreds
|
||||
keyStore: SignalKeyStoreWithTransaction
|
||||
ev: BaileysEventEmitter
|
||||
@@ -33,7 +34,7 @@ const REAL_MSG_REQ_ME_STUB_TYPES = new Set([
|
||||
export const cleanMessage = (message: proto.IWebMessageInfo, meId: string) => {
|
||||
// ensure remoteJid and participant doesn't have device or agent in it
|
||||
message.key.remoteJid = jidNormalizedUser(message.key.remoteJid!)
|
||||
message.key.participant = message.key.participant ? jidNormalizedUser(message.key.participant!) : undefined
|
||||
message.key.participant = message.key.participant ? jidNormalizedUser(message.key.participant) : undefined
|
||||
const content = normalizeMessageContent(message.message)
|
||||
// if the message has a reaction, ensure fromMe & remoteJid are from our perspective
|
||||
if(content?.reactionMessage) {
|
||||
@@ -152,6 +153,7 @@ const processMessage = async(
|
||||
message: proto.IWebMessageInfo,
|
||||
{
|
||||
shouldProcessHistoryMsg,
|
||||
placeholderResendCache,
|
||||
ev,
|
||||
creds,
|
||||
keyStore,
|
||||
@@ -190,7 +192,7 @@ const processMessage = async(
|
||||
if(protocolMsg) {
|
||||
switch (protocolMsg.type) {
|
||||
case proto.Message.ProtocolMessage.Type.HISTORY_SYNC_NOTIFICATION:
|
||||
const histNotification = protocolMsg!.historySyncNotification!
|
||||
const histNotification = protocolMsg.historySyncNotification!
|
||||
const process = shouldProcessHistoryMsg
|
||||
const isLatest = !creds.processedHistoryMessages?.length
|
||||
|
||||
@@ -202,19 +204,27 @@ const processMessage = async(
|
||||
}, 'got history notification')
|
||||
|
||||
if(process) {
|
||||
ev.emit('creds.update', {
|
||||
processedHistoryMessages: [
|
||||
...(creds.processedHistoryMessages || []),
|
||||
{ key: message.key, messageTimestamp: message.messageTimestamp }
|
||||
]
|
||||
})
|
||||
if(histNotification.syncType !== proto.HistorySync.HistorySyncType.ON_DEMAND) {
|
||||
ev.emit('creds.update', {
|
||||
processedHistoryMessages: [
|
||||
...(creds.processedHistoryMessages || []),
|
||||
{ key: message.key, messageTimestamp: message.messageTimestamp }
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
const data = await downloadAndProcessHistorySyncNotification(
|
||||
histNotification,
|
||||
options
|
||||
)
|
||||
|
||||
ev.emit('messaging-history.set', { ...data, isLatest })
|
||||
ev.emit('messaging-history.set', {
|
||||
...data,
|
||||
isLatest:
|
||||
histNotification.syncType !== proto.HistorySync.HistorySyncType.ON_DEMAND
|
||||
? isLatest
|
||||
: undefined
|
||||
})
|
||||
}
|
||||
|
||||
break
|
||||
@@ -267,14 +277,21 @@ const processMessage = async(
|
||||
case proto.Message.ProtocolMessage.Type.PEER_DATA_OPERATION_REQUEST_RESPONSE_MESSAGE:
|
||||
const response = protocolMsg.peerDataOperationRequestResponseMessage!
|
||||
if(response) {
|
||||
placeholderResendCache?.del(response.stanzaId!)
|
||||
// TODO: IMPLEMENT HISTORY SYNC ETC (sticker uploads etc.).
|
||||
const { peerDataOperationResult } = response
|
||||
for(const result of peerDataOperationResult!) {
|
||||
const { placeholderMessageResendResponse: retryResponse } = result
|
||||
if(retryResponse) {
|
||||
const webMessageInfo = proto.WebMessageInfo.decode(retryResponse.webMessageInfoBytes!)
|
||||
ev.emit('messages.update', [
|
||||
{ key: webMessageInfo.key, update: { message: webMessageInfo.message } }
|
||||
])
|
||||
// wait till another upsert event is available, don't want it to be part of the PDO response message
|
||||
setTimeout(() => {
|
||||
ev.emit('messages.upsert', {
|
||||
messages: [webMessageInfo],
|
||||
type: 'notify',
|
||||
requestId: response.stanzaId!
|
||||
})
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -288,10 +305,10 @@ const processMessage = async(
|
||||
}
|
||||
ev.emit('messages.reaction', [{
|
||||
reaction,
|
||||
key: content.reactionMessage!.key!,
|
||||
key: content.reactionMessage?.key!,
|
||||
}])
|
||||
} else if(message.messageStubType) {
|
||||
const jid = message.key!.remoteJid!
|
||||
const jid = message.key?.remoteJid!
|
||||
//let actor = whatsappID (message.participant)
|
||||
let participants: string[]
|
||||
const emitParticipantsUpdate = (action: ParticipantAction) => (
|
||||
@@ -380,7 +397,7 @@ const processMessage = async(
|
||||
if(pollMsg) {
|
||||
const meIdNormalised = jidNormalizedUser(meId)
|
||||
const pollCreatorJid = getKeyAuthor(creationMsgKey, meIdNormalised)
|
||||
const voterJid = getKeyAuthor(message.key!, meIdNormalised)
|
||||
const voterJid = getKeyAuthor(message.key, meIdNormalised)
|
||||
const pollEncKey = pollMsg.messageContextInfo?.messageSecret!
|
||||
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user