@vreden/meta
Version:
Baileys is a lightweight JavaScript library for interacting with the WhatsApp Web API using WebSocket.
1,467 lines (1,278 loc) • 59.9 kB
JavaScript
"use strict"
var __importDefault = (this && this.__importDefault) || function(mod) {
return (mod && mod.__esModule) ? mod : {
"default": mod
}
}
Object.defineProperty(exports, "__esModule", {
value: true
})
const boom_1 = require("@hapi/boom")
const axios_1 = __importDefault(require("axios"))
const crypto_1 = require("crypto")
const fs_1 = require("fs")
const WAProto_1 = require("../../WAProto")
const Defaults_1 = require("../Defaults")
const Types_1 = require("../Types")
const WABinary_1 = require("../WABinary")
const crypto_2 = require("./crypto")
const generics_1 = require("./generics")
const messages_media_1 = require("./messages-media")
const MIMETYPE_MAP = {
image: 'image/jpeg',
video: 'video/mp4',
document: 'application/pdf',
audio: 'audio/ogg codecs=opus',
sticker: 'image/webp',
'product-catalog-image': 'image/jpeg'
}
const MessageTypeProto = {
'image': Types_1.WAProto.Message.ImageMessage,
'video': Types_1.WAProto.Message.VideoMessage,
'audio': Types_1.WAProto.Message.AudioMessage,
'sticker': Types_1.WAProto.Message.StickerMessage,
'document': Types_1.WAProto.Message.DocumentMessage,
}
const extractUrlFromText = (text) => text.match(Defaults_1.URL_REGEX)?.[0]
const generateLinkPreviewIfRequired = async (text, getUrlInfo, logger) => {
const url = extractUrlFromText(text)
if (!!getUrlInfo && url) {
try {
const urlInfo = await getUrlInfo(url)
return urlInfo
} catch (error) {
logger?.warn({
trace: error.stack
}, 'url generation failed')
}
}
}
const assertColor = async (color) => {
let assertedColor
if (typeof color === 'number') {
assertedColor = color > 0 ? color : 0xffffffff + Number(color) + 1
} else {
let hex = color.trim().replace('#', '')
if (hex.length <= 6) {
hex = 'FF' + hex.padStart(6, '0')
}
assertedColor = parseInt(hex, 16)
return assertedColor
}
}
const prepareWAMessageMedia = async (message, options) => {
const logger = options.logger
let mediaType
for (const key of Defaults_1.MEDIA_KEYS) {
if (key in message) {
mediaType = key
}
}
if (!mediaType) {
throw new boom_1.Boom('Invalid media type', {
statusCode: 400
});
}
const uploadData = {
...message,
media: message[mediaType]
}
delete uploadData[mediaType]
const cacheableKey = typeof uploadData.media === 'object' && 'url' in uploadData.media && !!uploadData.media.url && !!options.mediaCache && mediaType + ':' + uploadData.media.url.toString()
if (mediaType === 'document' && !uploadData.fileName) {
uploadData.fileName = 'file'
}
if (!uploadData.mimetype) {
uploadData.mimetype = MIMETYPE_MAP[mediaType]
}
if (cacheableKey) {
const mediaBuff = await options.mediaCache.get(cacheableKey)
if (mediaBuff) {
logger?.debug({
cacheableKey
}, 'got media cache hit')
const obj = Types_1.WAProto.Message.decode(mediaBuff)
const key = `${mediaType}Message`
Object.assign(obj[key], {
...uploadData,
media: undefined
})
return obj
}
}
const isNewsletter = !!options.jid && WABinary_1.isJidNewsletter(options.jid)
if (isNewsletter) {
logger?.info({
key: cacheableKey
}, 'Preparing raw media for newsletter')
const {
filePath,
fileSha256,
fileLength
} = await messages_media_1.getRawMediaUploadData(uploadData.media, options.mediaTypeOverride || mediaType, logger)
const fileSha256B64 = fileSha256.toString('base64')
const {
mediaUrl,
directPath
} = await options.upload(filePath, {
fileEncSha256B64: fileSha256B64,
mediaType: mediaType,
timeoutMs: options.mediaUploadTimeoutMs
})
await fs_1.promises.unlink(filePath)
const obj = Types_1.WAProto.Message.fromObject({
[`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({
url: mediaUrl,
directPath,
fileSha256,
fileLength,
...uploadData,
media: undefined
})
})
if (uploadData.ptv) {
obj.ptvMessage = obj.videoMessage
delete obj.videoMessage
}
if (cacheableKey) {
logger?.debug({
cacheableKey
}, 'set cache');
await options.mediaCache.set(cacheableKey, Types_1.WAProto.Message.encode(obj).finish())
}
return obj
}
const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined'
const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') && typeof uploadData['jpegThumbnail'] === 'undefined'
const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true
const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true
const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation
const {
mediaKey,
encFilePath,
originalFilePath,
fileEncSha256,
fileSha256,
fileLength
} = await messages_media_1.encryptedStream(uploadData.media, options.mediaTypeOverride || mediaType, {
logger,
saveOriginalFileIfRequired: requiresOriginalForSomeProcessing,
opts: options.options
})
const fileEncSha256B64 = fileEncSha256.toString('base64')
const [{
mediaUrl,
directPath
}] = await Promise.all([
(async () => {
const result = await options.upload(encFilePath, {
fileEncSha256B64,
mediaType,
timeoutMs: options.mediaUploadTimeoutMs
})
logger?.debug({
mediaType,
cacheableKey
}, 'uploaded media')
return result
})(),
(async () => {
try {
if (requiresThumbnailComputation) {
const {
thumbnail,
originalImageDimensions
} = await messages_media_1.generateThumbnail(originalFilePath, mediaType, options)
uploadData.jpegThumbnail = thumbnail
if (!uploadData.width && originalImageDimensions) {
uploadData.width = originalImageDimensions.width
uploadData.height = originalImageDimensions.height
logger?.debug('set dimensions')
}
logger?.debug('generated thumbnail');
}
if (requiresDurationComputation) {
uploadData.seconds = await messages_media_1.getAudioDuration(originalFilePath)
logger?.debug('computed audio duration')
}
if (requiresWaveformProcessing) {
uploadData.waveform = await messages_media_1.getAudioWaveform(originalFilePath, logger)
logger?.debug('processed waveform')
}
if (requiresAudioBackground) {
uploadData.backgroundArgb = await assertColor(options.backgroundColor)
logger?.debug('computed backgroundColor audio status')
}
} catch (error) {
logger?.warn({
trace: error.stack
}, 'failed to obtain extra info')
}
})()
]).finally(async () => {
try {
await fs_1.promises.unlink(encFilePath)
if (originalFilePath) {
await fs_1.promises.unlink(originalFilePath)
}
logger?.debug('removed tmp files')
} catch (error) {
logger?.warn('failed to remove tmp file')
}
})
const obj = Types_1.WAProto.Message.fromObject({
[`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({
url: mediaUrl,
directPath,
mediaKey,
fileEncSha256,
fileSha256,
fileLength,
mediaKeyTimestamp: generics_1.unixTimestampSeconds(),
...uploadData,
media: undefined
})
})
if (uploadData.ptv) {
obj.ptvMessage = obj.videoMessage
delete obj.videoMessage
}
if (cacheableKey) {
logger?.debug({
cacheableKey
}, 'set cache');
await options.mediaCache.set(cacheableKey, Types_1.WAProto.Message.encode(obj).finish())
}
return obj
}
const prepareAlbumMessageContent = async (jid, albums, options) => {
let mediaHandle
let mediaMsg
let message = []
const albumMsg = generateWAMessageFromContent(jid, {
albumMessage: {
expectedImageCount: albums.filter(item => 'image' in item).length,
expectedVideoCount: albums.filter(item => 'video' in item).length
}
}, options)
await options.sock.relayMessage(jid, albumMsg.message, {
messageId: albumMsg.key.id
})
for (const i in albums) {
const media = albums[i]
if ('image' in media) {
mediaMsg = await generateWAMessage(jid, {
image: media.image,
...media,
...options
}, {
userJid: options.userJid,
upload: async (encFilePath, opts) => {
const up = await options.sock.waUploadToServer(encFilePath, {
...opts,
newsletter: WABinary_1.isJidNewsletter(jid)
})
mediaHandle = up.handle
return up
},
...options
})
} else if ('video' in message) {
mediaMsg = await generateWAMessage(jid, {
video: media.video,
...media,
...options
}, {
userJid: options.userJid,
upload: async (encFilePath, opts) => {
const up = await options.sock.waUploadToServer(encFilePath, {
...opts,
newsletter: WABinary_1.isJidNewsletter(jid)
})
mediaHandle = up.handle
return up
},
...options
})
}
if (mediaMsg) {
mediaMsg.message.messageContextInfo = {
messageSecret: crypto_1.randomBytes(32),
messageAssociation: {
associationType: 1,
parentMessageKey: albumMsg.key
}
}
}
message.push(mediaMsg)
}
return message
}
const prepareDisappearingMessageSettingContent = (expiration) => {
const content = {
ephemeralMessage: {
message: {
protocolMessage: {
type: Types_1.WAProto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
ephemeralExpiration: expiration ? expiration : 0
}
}
}
}
return Types_1.WAProto.Message.fromObject(content)
}
const generateForwardMessageContent = (message, forceForward) => {
let content = message.message
if (!content) {
throw new boom_1.Boom('no content in message', {
statusCode: 400
})
}
content = normalizeMessageContent(content)
content = WAProto_1.proto.Message.decode(WAProto_1.proto.Message.encode(content).finish())
let key = Object.keys(content)[0]
let score = content[key].contextInfo?.forwardingScore || 0
if (forceForward) score += forceForward ? forceForward : 1
if (key === 'conversation') {
content.extendedTextMessage = {
text: content[key]
}
delete content.conversation
key = 'extendedTextMessage'
}
if (score > 0) {
content[key].contextInfo = {
forwardingScore: score,
isForwarded: true
}
} else {
content[key].contextInfo = {}
}
return content
}
const generateWAMessageContent = async (message, options) => {
let m = {}
if ('text' in message) {
const extContent = {
text: message.text
}
let urlInfo = message.linkPreview
if (typeof urlInfo === 'undefined') {
urlInfo = await generateLinkPreviewIfRequired(message.text, options.getUrlInfo, options.logger)
}
if (urlInfo) {
extContent.canonicalUrl = urlInfo['canonical-url']
extContent.matchedText = urlInfo['matched-text']
extContent.jpegThumbnail = urlInfo.jpegThumbnail
extContent.description = urlInfo.description
extContent.title = urlInfo.title
extContent.previewType = 0
const img = urlInfo.highQualityThumbnail
if (img) {
extContent.thumbnailDirectPath = img.directPath
extContent.mediaKey = img.mediaKey
extContent.mediaKeyTimestamp = img.mediaKeyTimestamp
extContent.thumbnailWidth = img.width
extContent.thumbnailHeight = img.height
extContent.thumbnailSha256 = img.fileSha256
extContent.thumbnailEncSha256 = img.fileEncSha256
}
}
if (options.backgroundColor) {
extContent.backgroundArgb = await assertColor(options.backgroundColor)
}
if (options.textColor) {
extContent.textArgb = await assertColor(options.textColor)
}
if (options.font) {
extContent.font = options.font
}
extContent.contextInfo = {
...(message.contextInfo || {}),
...(message.mentions ? {
mentionedJid: message.mentions
} : {})
}
m.extendedTextMessage = extContent
} else if ('contacts' in message) {
const contactLen = message.contacts.contacts.length
let contactMessage
if (!contactLen) {
throw new boom_1.Boom('require atleast 1 contact', {
statusCode: 400
})
}
if (contactLen === 1) {
contactMessage = {
contactMessage: Types_1.WAProto.Message.ContactMessage.fromObject(message.contacts.contacts[0])
}
} else {
contactMessage = {
contactsArrayMessage: Types_1.WAProto.Message.ContactsArrayMessage.fromObject(message.contacts)
}
}
const [type] = Object.keys(contactMessage)
contactMessage[type].contextInfo = {
...(message.contextInfo || {}),
...(message.mentions ? {
mentionedJid: message.mentions
} : {})
}
m = contactMessage
} else if ('location' in message) {
let locationMessage
if (message.live) {
locationMessage = {
liveLocationMessage: Types_1.WAProto.Message.LiveLocationMessage.fromObject(message.location)
}
} else {
locationMessage = {
locationMessage: Types_1.WAProto.Message.LocationMessage.fromObject(message.location)
}
}
const [type] = Object.keys(locationMessage)
locationMessage[type].contextInfo = {
...(message.contextInfo || {}),
...(message.mentions ? {
mentionedJid: message.mentions
} : {})
}
m = locationMessage
} else if ('react' in message) {
if (!message.react.senderTimestampMs) {
message.react.senderTimestampMs = Date.now()
}
m.reactionMessage = Types_1.WAProto.Message.ReactionMessage.fromObject(message.react)
} else if ('delete' in message) {
m.protocolMessage = {
key: message.delete,
type: Types_1.WAProto.Message.ProtocolMessage.Type.REVOKE
}
} else if ('forward' in message) {
const mess = generateForwardMessageContent(message.forward, message.force)
const [type] = Object.keys(mess)
mess[type].contextInfo = {
...(message.contextInfo || {}),
...(message.mentions ? {
mentionedJid: message.mentions
} : {})
}
m = mess
} else if ('disappearingMessagesInChat' in message) {
const exp = typeof message.disappearingMessagesInChat === 'boolean' ? (message.disappearingMessagesInChat ? Defaults_1.WA_DEFAULT_EPHEMERAL : 0) : message.disappearingMessagesInChat
m = prepareDisappearingMessageSettingContent(exp)
} else if ('groupInvite' in message) {
m.groupInviteMessage = {}
m.groupInviteMessage.inviteCode = message.groupInvite.code
m.groupInviteMessage.inviteExpiration = message.groupInvite.expiration
m.groupInviteMessage.caption = message.groupInvite.caption
m.groupInviteMessage.groupJid = message.groupInvite.jid
m.groupInviteMessage.groupName = message.groupInvite.name
m.groupInviteMessage.contextInfo = message.contextInfo
if (options.getProfilePicUrl) {
const pfpUrl = await options.getProfilePicUrl(message.groupInvite.jid)
const { thumbnail } = await messages_media_1.generateThumbnail(pfpUrl, 'image')
m.groupInviteMessage.jpegThumbnail = thumbnail
}
m.groupInviteMessage.contextInfo = {
...(message.contextInfo || {}),
...(message.mentions ? {
mentionedJid: message.mentions
} : {})
}
} else if ('adminInvite' in message) {
m.newsletterAdminInviteMessage = {}
m.newsletterAdminInviteMessage.newsletterJid = message.adminInvite.jid
m.newsletterAdminInviteMessage.newsletterName = message.adminInvite.name
m.newsletterAdminInviteMessage.caption = message.adminInvite.caption
m.newsletterAdminInviteMessage.inviteExpiration = message.adminInvite.expiration
m.newsletterAdminInviteMessage.contextInfo = message.contextInfo
if (options.getProfilePicUrl) {
const pfpUrl = await options.getProfilePicUrl(message.adminInvite.jid)
const {
thumbnail
} = await messages_media_1.generateThumbnail(pfpUrl, 'image')
m.newsletterAdminInviteMessage.jpegThumbnail = thumbnail
}
m.newsletterAdminInviteMessage.contextInfo = {
...(message.contextInfo || {}),
...(message.mentions ? {
mentionedJid: message.mentions
} : {})
}
} else if ('pin' in message) {
m.pinInChatMessage = {}
m.messageContextInfo = {}
m.pinInChatMessage.key = message.pin.key
m.pinInChatMessage.type = message.pin?.type || 1
m.pinInChatMessage.senderTimestampMs = message.pin?.time || Date.now()
m.messageContextInfo.messageAddOnDurationInSecs = message.pin.type === 1 ? message.pin.time || 86400 : 0
m.messageContextInfo.messageAddOnExpiryType = WAProto_1.proto.MessageContextInfo.MessageAddonExpiryType.STATIC
} else if ('keep' in message) {
m.keepInChatMessage = {}
m.keepInChatMessage.key = message.keep.key
m.keepInChatMessage.keepType = message.keep?.type || 1
m.keepInChatMessage.timestampMs = message.keep?.time || Date.now()
} else if ('call' in message) {
m.scheduledCallCreationMessage = {}
m.scheduledCallCreationMessage.scheduledTimestampMs = message.call?.time || Date.now()
m.scheduledCallCreationMessage.callType = message.call?.type || 1
m.scheduledCallCreationMessage.title = message.call?.name || 'Call Creation'
m.scheduledCallCreationMessage.contextInfo = {
...(message.contextInfo || {}),
...(message.mentions ? {
mentionedJid: message.mentions
} : {})
}
} else if ('paymentInvite' in message) {
m.messageContextInfo = {}
m.paymentInviteMessage = {}
m.paymentInviteMessage.expiryTimestamp = message.paymentInvite?.expiry || 0
m.paymentInviteMessage.serviceType = message.paymentInvite?.type || 2
m.paymentInviteMessage.contextInfo = {
...(message.contextInfo || {}),
...(message.mentions ? {
mentionedJid: message.mentions
} : {})
}
} else if ('buttonReply' in message) {
switch (message.type) {
case 'list':
m.listResponseMessage = {
title: message.buttonReply.title,
description: message.buttonReply.description,
singleSelectReply: {
selectedRowId: message.buttonReply.rowId
},
lisType: WAProto_1.proto.Message.ListResponseMessage.ListType.SINGLE_SELECT
}
break
case 'template':
m.templateButtonReplyMessage = {
selectedDisplayText: message.buttonReply.displayText,
selectedId: message.buttonReply.id,
selectedIndex: message.buttonReply.index
}
break
case 'plain':
m.buttonsResponseMessage = {
selectedButtonId: message.buttonReply.id,
selectedDisplayText: message.buttonReply.displayText,
type: WAProto_1.proto.Message.ButtonsResponseMessage.Type.DISPLAY_TEXT
}
break
case 'interactive':
m.interactiveResponseMessage = {
body: {
text: message.buttonReply.displayText,
format: WAProto_1.proto.Message.InteractiveResponseMessage.Body.Format.EXTENSIONS_1
},
nativeFlowResponseMessage: {
name: message.buttonReply.nativeFlows.name,
paramsJson: message.buttonReply.nativeFlows.paramsJson,
version: message.buttonReply.nativeFlows.version
}
}
break
}
} else if ('ptv' in message && message.ptv) {
const {
videoMessage
} = await prepareWAMessageMedia({
video: message.video
}, options)
m.ptvMessage = videoMessage
} else if ('album' in message) {
const imageMessages = message.album.filter(item => 'image' in item)
const videoMessages = message.album.filter(item => 'video' in item)
m.albumMessage = Types_1.WAProto.Message.AlbumMessage.fromObject({
expectedImageCount: imageMessages.length,
expectedVideoCount: videoMessages.length
})
} else if ('order' in message) {
m.orderMessage = Types_1.WAProto.Message.OrderMessage.fromObject(message.order)
m.orderMessage.contextInfo = {
...(message.contextInfo || {}),
...(message.mentions ? {
mentionedJid: message.mentions
} : {})
}
} else if ('event' in message) {
m.eventMessage = Types_1.WAProto.Message.EventMessage.fromObject(message.event)
if (!message.event.startTime) {
m.eventMessage.startTime = generics_1.unixTimestampSeconds() + 86400
}
if (options.getCallLink && message.event.call) {
const link = await options.getCallLink(message.event.call, m.eventMessage.startTime)
m.eventMessage.joinLink = link
}
m.eventMessage.contextInfo = {
...(message.contextInfo || {}),
...(message.mentions ? {
mentionedJid: message.mentions
} : {})
}
} else if ('product' in message) {
const { imageMessage } = await prepareWAMessageMedia({
image: message.product.productImage
}, options)
m.productMessage = Types_1.WAProto.Message.ProductMessage.fromObject({
...message,
product: {
...message.product,
productImage: imageMessage,
}
})
m.productMessage.contextInfo = {
...(message.contextInfo || {}),
...(message.mentions ? {
mentionedJid: message.mentions
} : {})
}
} else if ('pollResult' in message) {
if (!Array.isArray(message.pollResult.values)) {
throw new boom_1.Boom('Invalid pollResult values', {
statusCode: 400
})
}
const pollResultSnapshotMessage = {
name: message.pollResult.name,
pollVotes: message.pollResult.values.map(([optionName, optionVoteCount]) => ({
optionName,
optionVoteCount
}))
}
pollResultSnapshotMessage.contextInfo = {
...(message.contextInfo || {}),
...(message.mentions ? {
mentionedJid: message.mentions
} : {})
}
m.pollResultSnapshotMessage = pollResultSnapshotMessage
} else if ('poll' in message) {
if (!Array.isArray(message.poll.values)) {
throw new boom_1.Boom('Invalid poll values', {
statusCode: 400
})
}
if (message.poll.selectableCount < 0 ||
message.poll.selectableCount > message.poll.values.length) {
throw new boom_1.Boom(`poll.selectableCount in poll should be >= 0 and <= ${message.poll.values.length}`, {
statusCode: 400
})
}
const pollCreationMessage = {
name: message.poll.name,
selectableOptionsCount: message.poll?.selectableCount || 0,
options: message.poll.values.map(optionName => ({
optionName
})),
}
pollCreationMessage.contextInfo = {
...(message.contextInfo || {}),
...(message.mentions ? {
mentionedJid: message.mentions
} : {})
}
if (message.poll?.toAnnouncementGroup) {
m.pollCreationMessageV2 = pollCreationMessage
} else {
if (message.poll.selectableCount > 0) {
m.pollCreationMessageV3 = pollCreationMessage
} else {
m.pollCreationMessage = pollCreationMessage
}
}
} else if ('payment' in message) {
const requestPaymentMessage = {
amount: {
currencyCode: message.payment?.currency || 'IDR',
offset: message.payment?.offset || 0,
value: message.payment?.amount || 999999999
},
expiryTimestamp: message.payment?.expiry || 0,
amount1000: message.payment?.amount || 999999999 * 1000,
currencyCodeIso4217: message.payment?.currency || 'IDR',
requestFrom: message.payment?.from || '0@s.whatsapp.net',
noteMessage: {
extendedTextMessage: {
text: message.payment?.note || 'Notes'
}
},
background: {
placeholderArgb: message.payment?.image?.placeholderArgb || 4278190080,
textArgb: message.payment?.image?.textArgb || 4294967295,
subtextArgb: message.payment?.image?.subtextArgb || 4294967295,
type: 1
}
}
requestPaymentMessage.noteMessage.extendedTextMessage.contextInfo = {
...(message.contextInfo || {}),
...(message.mentions ? {
mentionedJid: message.mentions
} : {})
}
m.requestPaymentMessage = requestPaymentMessage
} else if ('stickerPack' in message) {
const {
stickers,
cover,
name,
publisher,
packId,
description
} = message.stickerPack
const { zip } = require('fflate')
const stickerData = {}
const stickerPromises = stickers.map(async (s, i) => {
const { stream } = await messages_media_1.getStream(s.sticker)
const buffer = await messages_media_1.toBuffer(stream)
const hash = crypto_2.sha256(buffer).toString('base64url')
const fileName = `${i.toString().padStart(2, '0')}_${hash}.webp`
stickerData[fileName] = [new Uint8Array(buffer), {
level: 0
}]
return {
fileName,
mimetype: 'image/webp',
isAnimated: s.isAnimated || false,
isLottie: s.isLottie || false,
emojis: s.emojis || [],
accessibilityLabel: s.accessibilityLabel || ''
}
})
const stickerMetadata = await Promise.all(stickerPromises)
const zipBuffer = await new Promise((resolve, reject) => {
zip(stickerData, (err, data) => {
if (err) {
reject(err)
} else {
resolve(Buffer.from(data))
}
})
})
const coverBuffer = await messages_media_1.toBuffer((await messages_media_1.getStream(cover)).stream)
const [stickerPackUpload, coverUpload] = await Promise.all([
messages_media_1.encryptedStream(zipBuffer, 'sticker-pack', {
logger: options.logger,
opts: options.options
}),
prepareWAMessageMedia({
image: coverBuffer
}, {
...options,
mediaTypeOverride: 'image'
})
])
const stickerPackUploadResult = await options.upload(stickerPackUpload.encFilePath, {
fileEncSha256B64: stickerPackUpload.fileEncSha256.toString('base64'),
mediaType: 'sticker-pack',
timeoutMs: options.mediaUploadTimeoutMs
})
const coverImage = coverUpload.imageMessage
const imageDataHash = crypto_2.sha256(coverBuffer).toString('base64')
const stickerPackId = packId || generics_1.generateMessageID()
m.stickerPackMessage = {
name,
publisher,
stickerPackId,
packDescription: description,
stickerPackOrigin: WAProto_1.proto.Message.StickerPackMessage.StickerPackOrigin.THIRD_PARTY,
stickerPackSize: stickerPackUpload.fileLength,
stickers: stickerMetadata,
fileSha256: stickerPackUpload.fileSha256,
fileEncSha256: stickerPackUpload.fileEncSha256,
mediaKey: stickerPackUpload.mediaKey,
directPath: stickerPackUploadResult.directPath,
fileLength: stickerPackUpload.fileLength,
mediaKeyTimestamp: generics_1.unixTimestampSeconds(),
trayIconFileName: `${stickerPackId}.png`,
imageDataHash,
thumbnailDirectPath: coverImage.directPath,
thumbnailFileSha256: coverImage.fileSha256,
thumbnailFileEncSha256: coverImage.fileEncSha256,
thumbnailHeight: coverImage.height,
thumbnailWidth: coverImage.width
}
m.stickerPackMessage.contextInfo = {
...(message.contextInfo || {}),
...(message.mentions ? {
mentionedJid: message.mentions
} : {})
}
} else if ('sharePhoneNumber' in message) {
m.protocolMessage = {
type: Types_1.WAProto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER
}
} else if ('requestPhoneNumber' in message) {
m.requestPhoneNumberMessage = {}
} else {
const mess = await prepareWAMessageMedia(message, options)
const [type] = Object.keys(mess)
mess[type].contextInfo = {
...(message.contextInfo || {}),
...(message.mentions ? {
mentionedJid: message.mentions
} : {})
}
m = mess
}
if ('sections' in message && !!message.sections) {
const listMessage = {
title: message.title,
buttonText: message.buttonText,
footerText: message.footer,
description: message.text,
sections: message.sections,
listType: WAProto_1.proto.Message.ListMessage.ListType.SINGLE_SELECT
}
listMessage.contextInfo = {
...(message.contextInfo || {}),
...(message.mentions ? {
mentionedJid: message.mentions
} : {})
}
m = {
listMessage
}
} else if ('productList' in message && !!message.productList) {
const thumbnail = message.thumbnail ? await messages_media_1.generateThumbnail(message.thumbnail, 'image') : null
const listMessage = {
title: message.title,
buttonText: message.buttonText,
footerText: message.footer,
description: message.text,
productListInfo: {
productSections: message.productList,
headerImage: {
productId: message.productList[0].products[0].productId,
jpegThumbnail: thumbnail?.thumbnail || null
},
businessOwnerJid: message.businessOwnerJid
},
listType: WAProto_1.proto.Message.ListMessage.ListType.PRODUCT_LIST
}
listMessage.contextInfo = {
...(message.contextInfo || {}),
...(message.mentions ? {
mentionedJid: message.mentions
} : {})
}
m = {
listMessage
}
} else if ('buttons' in message && !!message.buttons) {
const buttonsMessage = {
buttons: message.buttons.map(b => ({
...b,
type: WAProto_1.proto.Message.ButtonsMessage.Button.Type.RESPONSE
}))
}
if ('text' in message) {
buttonsMessage.contentText = message.text
buttonsMessage.headerType = WAProto_1.proto.Message.ButtonsMessage.HeaderType.EMPTY
} else {
if ('caption' in message) {
buttonsMessage.contentText = message.caption
}
const type = Object.keys(m)[0].replace('Message', '').toUpperCase()
buttonsMessage.headerType = WAProto_1.proto.Message.ButtonsMessage.HeaderType[type]
Object.assign(buttonsMessage, m)
}
if ('footer' in message && !!message.footer) {
buttonsMessage.footerText = message.footer
}
if ('title' in message && !!message.title) {
buttonsMessage.text = message.title
buttonsMessage.headerType = WAProto_1.proto.Message.ButtonsMessage.HeaderType.TEXT
}
buttonsMessage.contextInfo = {
...(message.contextInfo || {}),
...(message.mentions ? {
mentionedJid: message.mentions
} : {})
}
m = {
buttonsMessage
}
} else if ('template' in message && !!message.templateButtons) {
const hydratedTemplate = {
hydratedButtons: message.templateButtons
}
if ('text' in message) {
hydratedTemplate.hydratedContentText = message.text
} else {
if ('caption' in message) {
hydratedTemplate.hydratedContentText = message.caption
}
Object.assign(msg, m)
}
if ('footer' in message && !!message.footer) {
hydratedTemplate.hydratedFooterText = message.footer
}
hydratedTemplate.contextInfo = {
...(message.contextInfo || {}),
...(message.mentions ? {
mentionedJid: message.mentions
} : {})
}
m = {
templateMessage: {
hydratedTemplate
}
}
} else if ('interactive' in message && !!message.interactive) {
const interactiveMessage = {
nativeFlowMessage: {
buttons: message.interactive
}
}
if ('text' in message) {
interactiveMessage.body = {
text: message.text
},
interactiveMessage.header = {
title: message.title,
subtitle: message.subtitle,
hasMediaAttachment: false
}
} else {
if ('caption' in message) {
interactiveMessage.body = {
text: message.caption
}
interactiveMessage.header = {
title: message.title,
subtitle: message.subtitle,
hasMediaAttachment: message.hasMediaAttachment ? message.hasMediaAttachment : false,
...Object.assign(interactiveMessage, m)
}
}
}
if ('footer' in message && !!message.footer) {
interactiveMessage.footer = {
text: message.footer
}
}
interactiveMessage.contextInfo = {
...(message.contextInfo || {}),
...(message.mentions ? {
mentionedJid: message.mentions
} : {})
}
m = {
interactiveMessage
}
} else if ('shop' in message && !!message.shop) {
const interactiveMessage = {
shopStorefrontMessage: {
surface: message.shop.surface,
id: message.shop.id
}
}
if ('text' in message) {
interactiveMessage.body = {
text: message.text
},
interactiveMessage.header = {
title: message.title,
subtitle: message.subtitle,
hasMediaAttachment: false
}
} else {
if ('caption' in message) {
interactiveMessage.body = {
text: message.caption
}
interactiveMessage.header = {
title: message.title,
subtitle: message.subtitle,
hasMediaAttachment: message.hasMediaAttachment ? message.hasMediaAttachment : false,
...Object.assign(interactiveMessage, m)
}
}
}
if ('footer' in message && !!message.footer) {
interactiveMessage.footer = {
text: message.footer
}
}
interactiveMessage.contextInfo = {
...(message.contextInfo || {}),
...(message.mentions ? {
mentionedJid: message.mentions
} : {})
}
m = {
interactiveMessage
}
} else if ('collection' in message && !!message.collection) {
const interactiveMessage = {
collectionMessage: {
bizJid: message.collection.bizJid,
id: message.collection.id,
messageVersion: message?.collection?.version
}
}
if ('text' in message) {
interactiveMessage.body = {
text: message.text
},
interactiveMessage.header = {
title: message.title,
subtitle: message.subtitle,
hasMediaAttachment: false
}
} else {
if ('caption' in message) {
interactiveMessage.body = {
text: message.caption
}
interactiveMessage.header = {
title: message.title,
subtitle: message.subtitle,
hasMediaAttachment: message.hasMediaAttachment ? message.hasMediaAttachment : false,
...Object.assign(interactiveMessage, m)
}
}
}
if ('footer' in message && !message.footer) {
interactiveMessage.footer = {
text: message.footer
}
}
interactiveMessage.contextInfo = {
...(message.contextInfo || {}),
...(message.mentions ? {
mentionedJid: message.mentions
} : {})
}
m = {
interactiveMessage
}
} else if ('cards' in message && !!message.cards) {
const slides = await Promise.all(message.cards.map(async (slide) => {
const {
image,
video,
product,
title,
body,
footer,
buttons
} = slide
let header
if (product) {
const { imageMessage } = await prepareWAMessageMedia({
image: product.productImage,
...options
}, options)
header = {
productMessage: {
product: {
...product,
productImage: imageMessage,
},
...slide
}
}
} else if (image) {
header = await prepareWAMessageMedia({
image: image,
...options
}, options)
} else if (video) {
header = await prepareWAMessageMedia({
video: video,
...options
}, options)
}
const msg = {
header: {
title,
hasMediaAttachment: true,
...header
},
body: {
text: body
},
footer: {
text: footer
},
nativeFlowMessage: {
buttons,
}
}
return msg
}))
const interactiveMessage = {
carouselMessage: {
cards: slides
}
}
if ('text' in message) {
interactiveMessage.body = {
text: message.text
},
interactiveMessage.header = {
title: message.title,
subtitle: message.subtitle,
hasMediaAttachment: false
}
}
if ('footer' in message && !!message.footer) {
interactiveMessage.footer = {
text: message.footer
}
}
interactiveMessage.contextInfo = {
...(message.contextInfo || {}),
...(message.mentions ? {
mentionedJid: message.mentions
} : {})
}
m = {
interactiveMessage
}
}
if ('ephemeral' in message && !!message.ephemeral) {
m = {
ephemeralMessage: {
message: m
}
}
}
if ('viewOnce' in message && !!message.viewOnce) {
m = {
viewOnceMessage: {
message: m
}
}
}
if ('viewOnceV2' in message && !!message.viewOnceV2) {
m = {
viewOnceMessageV2: {
message: m
}
}
}
if ('viewOnceExt' in message && !!message.viewOnceExt) {
m = {
viewOnceMessageV2Extension: {
message: m
}
}
}
if ('edit' in message) {
m = {
protocolMessage: {
key: message.edit,
editedMessage: m,
timestampMs: Date.now(),
type: Types_1.WAProto.Message.ProtocolMessage.Type.MESSAGE_EDIT
}
}
}
return Types_1.WAProto.Message.fromObject(m)
}
const generateWAMessageFromContent = (jid, message, options) => {
if (!options.timestamp) {
options.timestamp = new Date()
}
const innerMessage = normalizeMessageContent(message)
const key = getContentType(innerMessage)
const timestamp = generics_1.unixTimestampSeconds(options.timestamp)
const {
quoted,
userJid
} = options
if (quoted && !WABinary_1.isJidNewsletter(jid)) {
const participant = quoted.key.fromMe ? userJid : quoted.participant || quoted.key.participant || quoted.key.remoteJid
let quotedMsg = normalizeMessageContent(quoted.message)
const msgType = getContentType(quotedMsg)
quotedMsg = WAProto_1.proto.Message.fromObject({
[msgType]: quotedMsg[msgType]
})
const quotedContent = quotedMsg[msgType]
if (typeof quotedContent === 'object' && quotedContent && 'contextInfo' in quotedContent) {
delete quotedContent.contextInfo
}
let requestPayment
if (key === 'requestPaymentMessage') {
if (innerMessage?.requestPaymentMessage && innerMessage?.requestPaymentMessage?.noteMessage?.extendedTextMessage) {
requestPayment = innerMessage?.requestPaymentMessage?.noteMessage?.extendedTextMessage
} else if (innerMessage?.requestPaymentMessage && innerMessage?.requestPaymentMessage?.noteMessage?.stickerMessage) {
requestPayment = innerMessage.requestPaymentMessage?.noteMessage?.stickerMessage
}
}
const contextInfo = (key === 'requestPaymentMessage' ? requestPayment?.contextInfo : innerMessage[key].contextInfo) || {}
contextInfo.participant = WABinary_1.jidNormalizedUser(participant)
contextInfo.stanzaId = quoted.key.id
contextInfo.quotedMessage = quotedMsg
if (jid !== quoted.key.remoteJid) {
contextInfo.remoteJid = quoted.key.remoteJid
}
if (contextInfo.quotedMessage) {
contextInfo.quotedType = WAProto_1.proto.ContextInfo.QuotedType.EXPLICIT
}
if (key === 'requestPaymentMessage' && requestPayment) {
requestPayment.contextInfo = contextInfo
} else {
innerMessage[key].contextInfo = contextInfo
}
}
if (key !== 'protocolMessage' && key !== 'ephemeralMessage' && !WABinary_1.isJidNewsletter(jid)) {
message.messageContextInfo = {
messageSecret: crypto_1.randomBytes(32),
...message.messageContextInfo
}
innerMessage[key].contextInfo = {
...(innerMessage[key].contextInfo || {}),
expiration: options.ephemeralExpiration ? options.ephemeralExpiration : 0
}
}
message = Types_1.WAProto.Message.fromObject(message)
const messageJSON = {
key: {
remoteJid: jid,
fromMe: true,
id: options?.messageId || generics_1.generateMessageID()
},
message: message,
messageTimestamp: timestamp,
messageStubParameters: [],
participant: WABinary_1.isJidGroup(jid) || WABinary_1.isJidStatusBroadcast(jid) ? userJid : undefined,
status: Types_1.WAMessageStatus.PENDING
}
return Types_1.WAProto.WebMessageInfo.fromObject(messageJSON)
}
const generateWAMessage = async (jid, content, options) => {
options.logger = options?.logger?.child({
msgId: options.messageId
})
return generateWAMessageFromContent(jid, await generateWAMessageContent(content, {
newsletter: WABinary_1.isJidNewsletter(jid),
...options
}), options)
}
const getContentType = (content) => {
if (content) {
const keys = Object.keys(content)
const key = keys.find(k => (k === 'conversation' || k.endsWith('Message') || k.endsWith('V2') || k.endsWith('V3') || k.endsWith('V4') || k.endsWith('V5')) && k !== 'senderKeyDistributionMessage' && k !== 'messageContextInfo')
return key
}
}
const normalizeMessageContent = (content) => {
if (!content) {
return undefined
}
for (let i = 0; i < 5; i++) {
const inner = getFutureProofMessage(content)
if (!inner) {
break
}
content = inner.message
}
return content
function getFutureProofMessage(message) {
return ((message === null || message === void 0 ? void 0 : message.editedMessage) || (message === null || message === void 0 ? void 0 : message.statusAddYours) || (message === null || message === void 0 ? void 0 : message.botTaskMessage) || (message === null || message === void 0 ? void 0 : message.eventCoverImage) || (message === null || message === void 0 ? void 0 : message.questionMessage) || (message === null || message === void 0 ? void 0 : message.viewOnceMessage) || (message === null || message === void 0 ? void 0 : message.botInvokeMessage) || (message === null || message === void 0 ? void 0 : message.ephemeralMessage) || (message === null || message === void 0 ? void 0 : message.lottieStickerMessage) || (message === null || message === void 0 ? void 0 : message.groupStatusMessage) || (message === null || message === void 0 ? v