diff --git a/src/Defaults/index.ts b/src/Defaults/index.ts index a348b93..498216d 100644 --- a/src/Defaults/index.ts +++ b/src/Defaults/index.ts @@ -50,6 +50,7 @@ export const DEFAULT_CONNECTION_CONFIG: SocketConfig = { auth: undefined as any, markOnlineOnConnect: true, syncFullHistory: false, + patchMessageBeforeSending: msg => msg, shouldSyncHistoryMessage: () => true, shouldIgnoreJid: () => false, linkPreviewImageThumbnailWidth: 192, diff --git a/src/Socket/messages-send.ts b/src/Socket/messages-send.ts index c5be931..b6f3fa6 100644 --- a/src/Socket/messages-send.ts +++ b/src/Socket/messages-send.ts @@ -4,7 +4,7 @@ import NodeCache from 'node-cache' import { proto } from '../../WAProto' import { WA_DEFAULT_EPHEMERAL } from '../Defaults' import { AnyMessageContent, MediaConnInfo, MessageReceiptType, MessageRelayOptions, MiscMessageGenerationOptions, SocketConfig, WAMessageKey } from '../Types' -import { aggregateMessageKeysNotFromMe, assertMediaContent, bindWaitForEvent, decryptMediaRetryData, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, encryptSenderKeyMsgSignalProto, encryptSignalProto, extractDeviceJids, generateMessageID, generateWAMessage, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, jidToSignalProtocolAddress, parseAndInjectE2ESessions, patchMessageForMdIfRequired, unixTimestampSeconds } from '../Utils' +import { aggregateMessageKeysNotFromMe, assertMediaContent, bindWaitForEvent, decryptMediaRetryData, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, encryptSenderKeyMsgSignalProto, encryptSignalProto, extractDeviceJids, generateMessageID, generateWAMessage, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, jidToSignalProtocolAddress, parseAndInjectE2ESessions, unixTimestampSeconds } from '../Utils' import { getUrlInfo } from '../Utils/link-preview' import { areJidsSameUser, BinaryNode, BinaryNodeAttributes, getBinaryNodeChild, getBinaryNodeChildren, isJidGroup, isJidUser, jidDecode, jidEncode, jidNormalizedUser, JidWithDevice, S_WHATSAPP_NET } from '../WABinary' import { makeGroupsSocket } from './groups' @@ -13,7 +13,8 @@ export const makeMessagesSocket = (config: SocketConfig) => { const { logger, linkPreviewImageThumbnailWidth, - generateHighQualityLinkPreview + generateHighQualityLinkPreview, + patchMessageBeforeSending, } = config const sock = makeGroupsSocket(config) const { @@ -250,9 +251,12 @@ export const makeMessagesSocket = (config: SocketConfig) => { const createParticipantNodes = async( jids: string[], - bytes: Buffer, + message: proto.IMessage, extraAttrs?: BinaryNode['attrs'] ) => { + const patched = await patchMessageBeforeSending(message, jids) + const bytes = encodeWAMessage(patched) + let shouldIncludeDeviceIdentity = false const nodes = await Promise.all( jids.map( @@ -296,14 +300,18 @@ export const makeMessagesSocket = (config: SocketConfig) => { msgId = msgId || generateMessageID() useUserDevicesCache = useUserDevicesCache !== false - const encodedMsg = encodeWAMessage(message) const participants: BinaryNode[] = [] - const destinationJid = jidEncode(user, isGroup ? 'g.us' : 's.whatsapp.net') - const binaryNodeContent: BinaryNode[] = [] - const devices: JidWithDevice[] = [] + + const meMsg: proto.IMessage = { + deviceSentMessage: { + destinationJid, + message + } + } + if(participant) { // when the retry request is not for a group // only send to the specific device that asked for a retry @@ -319,8 +327,6 @@ export const makeMessagesSocket = (config: SocketConfig) => { await authState.keys.transaction( async() => { if(isGroup) { - const { ciphertext, senderKeyDistributionMessageKey } = await encryptSenderKeyMsgSignalProto(destinationJid, encodedMsg, meId, authState) - const [groupData, senderKeyMap] = await Promise.all([ (async() => { let groupData = cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined @@ -350,6 +356,16 @@ export const makeMessagesSocket = (config: SocketConfig) => { devices.push(...additionalDevices) } + const patched = await patchMessageBeforeSending(message, devices.map(d => jidEncode(d.user, 's.whatsapp.net', d.device))) + const bytes = encodeWAMessage(patched) + + const { ciphertext, senderKeyDistributionMessageKey } = await encryptSenderKeyMsgSignalProto( + destinationJid, + bytes, + meId, + authState + ) + const senderKeyJids: string[] = [] // ensure a connection is established with every device for(const { user, device } of devices) { @@ -366,16 +382,16 @@ export const makeMessagesSocket = (config: SocketConfig) => { if(senderKeyJids.length) { logger.debug({ senderKeyJids }, 'sending new sender key') - const encSenderKeyMsg = encodeWAMessage({ + const senderKeyMsg: proto.IMessage = { senderKeyDistributionMessage: { axolotlSenderKeyDistributionMessage: senderKeyDistributionMessageKey, groupId: destinationJid } - }) + } await assertSessions(senderKeyJids, false) - const result = await createParticipantNodes(senderKeyJids, encSenderKeyMsg) + const result = await createParticipantNodes(senderKeyJids, senderKeyMsg) shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || result.shouldIncludeDeviceIdentity participants.push(...result.nodes) @@ -391,13 +407,6 @@ export const makeMessagesSocket = (config: SocketConfig) => { } else { const { user: meUser } = jidDecode(meId)! - const encodedMeMsg = encodeWAMessage({ - deviceSentMessage: { - destinationJid, - message - } - }) - if(!participant) { devices.push({ user }) devices.push({ user: meUser }) @@ -427,8 +436,8 @@ export const makeMessagesSocket = (config: SocketConfig) => { { nodes: meNodes, shouldIncludeDeviceIdentity: s1 }, { nodes: otherNodes, shouldIncludeDeviceIdentity: s2 } ] = await Promise.all([ - createParticipantNodes(meJids, encodedMeMsg), - createParticipantNodes(otherJids, encodedMsg) + createParticipantNodes(meJids, meMsg), + createParticipantNodes(otherJids, message) ]) participants.push(...meNodes) participants.push(...otherNodes) @@ -637,7 +646,6 @@ export const makeMessagesSocket = (config: SocketConfig) => { } } - fullMsg.message = patchMessageForMdIfRequired(fullMsg.message!) await relayMessage(jid, fullMsg.message!, { messageId: fullMsg.key.id!, cachedGroupMetadata: options.cachedGroupMetadata, additionalAttributes }) if(config.emitOwnEvents) { process.nextTick(() => { diff --git a/src/Types/Socket.ts b/src/Types/Socket.ts index a6d9685..5144fdd 100644 --- a/src/Types/Socket.ts +++ b/src/Types/Socket.ts @@ -77,6 +77,14 @@ export type SocketConfig = { * */ shouldIgnoreJid: (jid: string) => boolean | undefined + /** + * Optionally patch the message before sending out + * */ + patchMessageBeforeSending: ( + msg: proto.IMessage, + recipientJids: string[], + ) => Promise | proto.IMessage + /** verify app state MACs */ appStateMacVerification: { patch: boolean diff --git a/src/Utils/messages.ts b/src/Utils/messages.ts index 4a34996..7148e2a 100644 --- a/src/Utils/messages.ts +++ b/src/Utils/messages.ts @@ -763,37 +763,4 @@ export const assertMediaContent = (content: proto.IMessage | null | undefined) = } return mediaContent -} - - -const generateContextInfo = () => { - const info: proto.IMessageContextInfo = { - deviceListMetadataVersion: 2, - deviceListMetadata: { } - } - return info -} - -/** - * this is an experimental patch to make buttons work - * Don't know how it works, but it does for now - */ -export const patchMessageForMdIfRequired = (message: proto.IMessage) => { - const requiresPatch = !!( - message.buttonsMessage - // || message.templateMessage - || message.listMessage - ) - if(requiresPatch) { - message = { - viewOnceMessage: { - message: { - messageContextInfo: generateContextInfo(), - ...message - } - } - } - } - - return message } \ No newline at end of file