Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 112 additions & 56 deletions src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ import makeWASocket, {
DisconnectReason,
downloadContentFromMessage,
downloadMediaMessage,
generateMessageIDV2,
generateWAMessageFromContent,
getAggregateVotesInPollMessage,
GetCatalogOptions,
Expand Down Expand Up @@ -3337,29 +3338,16 @@ export class BaileysStartupService extends ChannelStartupService {
if (hasOtherButtons) {
throw new BadRequestException('PIX button cannot be mixed with other button types');
}
}

const message: proto.IMessage = {
viewOnceMessage: {
message: {
interactiveMessage: {
nativeFlowMessage: {
buttons: [{ name: this.mapType.get('pix'), buttonParamsJson: this.toJSONString(data.buttons[0]) }],
messageParamsJson: JSON.stringify({ from: 'api', templateId: v4() }),
},
},
},
},
};

return await this.sendMessageWithTyping(data.number, message, {
delay: data?.delay,
presence: 'composing',
quoted: data?.quoted,
mentionsEveryOne: data?.mentionsEveryOne,
mentioned: data?.mentioned,
});
const isWA = (await this.whatsappNumber({ numbers: [data.number] }))?.shift();
if (!isWA.exists && !isJidGroup(isWA.jid) && !isWA.jid.includes('@broadcast')) {
Comment on lines +3343 to +3344

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Guard against isWA being undefined before accessing its properties.

Because whatsappNumber is optional-chained, isWA can be undefined (e.g. empty result), and isWA.exists / isWA.jid will then throw. Add a guard before using its properties, for example:

const isWA = (await this.whatsappNumber({ numbers: [data.number] }))?.shift();
if (!isWA) {
  // handle missing lookup
}
if (!isWA.exists && !isJidGroup(isWA.jid) && !isWA.jid.includes('@broadcast')) {
  throw new BadRequestException(isWA);
}

throw new BadRequestException(isWA);
}

const sender = isWA.jid.toLowerCase();
const isGroup = isJidGroup(sender);

const generate = await (async () => {
if (data?.thumbnailUrl) {
return await this.prepareMediaMessage({ mediatype: 'image', media: data.thumbnailUrl });
Expand All @@ -3370,46 +3358,114 @@ export class BaileysStartupService extends ChannelStartupService {
return { name: this.mapType.get(value.type), buttonParamsJson: this.toJSONString(value) };
});

const interactiveMessage: proto.Message.IInteractiveMessage = {
body: {
text: (() => {
let t = '*' + data.title + '*';
if (data?.description) {
t += '\n\n';
t += data.description;
t += '\n';
}
return t;
})(),
},
footer: { text: data?.footer },
header: (() => {
if (generate?.message?.imageMessage) {
return {
hasMediaAttachment: !!generate.message.imageMessage,
imageMessage: generate.message.imageMessage,
};
}
})(),
nativeFlowMessage: {
buttons: buttons,
messageParamsJson: JSON.stringify({ from: 'api', templateId: v4() }),
},
};

const message: proto.IMessage = {
viewOnceMessage: {
message: {
interactiveMessage: {
body: {
text: (() => {
let t = '*' + data.title + '*';
if (data?.description) {
t += '\n\n';
t += data.description;
t += '\n';
}
return t;
})(),
},
footer: { text: data?.footer },
header: (() => {
if (generate?.message?.imageMessage) {
return {
hasMediaAttachment: !!generate.message.imageMessage,
imageMessage: generate.message.imageMessage,
};
}
})(),
nativeFlowMessage: {
buttons: buttons,
messageParamsJson: JSON.stringify({ from: 'api', templateId: v4() }),
interactiveMessage: interactiveMessage,
};

try {
if (data?.delay) {
this.logger.verbose(`Typing for ${data.delay}ms to ${sender}`);
await this.client.presenceSubscribe(sender);
await this.client.sendPresenceUpdate('composing', sender);
await delay(data.delay);
await this.client.sendPresenceUpdate('paused', sender);
}

let quoted: WAMessage;
if (data?.quoted) {
const m = data?.quoted;
const msg = m?.message ? m : ((await this.getMessage(m.key, true)) as WAMessage);
if (msg) {
quoted = msg;
}
}

const messageId = generateMessageIDV2();
const m = generateWAMessageFromContent(sender, message, {
timestamp: new Date(),
userJid: this.instance.wuid,
messageId,
quoted,
});

const additionalNodes: any[] = [
{
tag: 'biz',
attrs: {},
content: [
{
tag: 'interactive',
attrs: { type: 'native_flow', v: '1' },
content: [
{
tag: 'native_flow',
attrs: { v: '2', name: 'mixed' },
content: undefined,
},
],
},
},
],
},
},
};
];

return await this.sendMessageWithTyping(data.number, message, {
delay: data?.delay,
presence: 'composing',
quoted: data?.quoted,
mentionsEveryOne: data?.mentionsEveryOne,
mentioned: data?.mentioned,
});
if (!isGroup) {
additionalNodes[0].content.push({
tag: 'bot',
attrs: { biz_bot: '1' },
content: undefined,
});
}

const id = await this.client.relayMessage(sender, message, {
messageId,
additionalNodes,
});

m.key = {
id: id,
remoteJid: sender,
participant: isPnUser(sender) ? sender : undefined,
fromMe: true,
};

for (const [key, value] of Object.entries(m)) {
if (!value || (isArray(value) && value.length) === 0) {
Comment on lines +3458 to +3459

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The cleanup condition will incorrectly delete most non-array properties from m.

(isArray(value) && value.length) === 0 is evaluated as ((isArray(value) && value.length) === 0). For non-arrays, isArray(value) is false, so the left side is false, and false === 0 is true, causing deletion even when value is truthy. You probably want isArray(value) && value.length === 0, and ideally a more explicit condition that only deletes empty arrays rather than all falsy values.

delete m[key];
}
}

return m;
} catch (error) {
this.logger.error(error);
throw new InternalServerErrorException(error);
}
}

public async locationMessage(data: SendLocationDto) {
Expand Down