fix(baileys): interactive buttons via deviceSentMessage + CTA limits

This commit is contained in:
root
2026-01-26 13:19:50 -03:00
parent d15c434b4c
commit 08f8d055d4
@@ -3310,48 +3310,57 @@ export class BaileysStartupService extends ChannelStartupService {
['random', 'EVP'], ['random', 'EVP'],
]); ]);
public async buttonMessage(data: SendButtonsDto) { aqui?
if (data.buttons.length === 0) {
public async buttonMessage(data: SendButtonsDto) {
if (!data.buttons || data.buttons.length === 0) {
throw new BadRequestException('At least one button is required'); throw new BadRequestException('At least one button is required');
} }
const hasReplyButtons = data.buttons.some((btn) => btn.type === 'reply'); const hasReplyButtons = data.buttons.some((btn) => btn.type === 'reply');
const hasPixButton = data.buttons.some((btn) => btn.type === 'pix'); const hasPixButton = data.buttons.some((btn) => btn.type === 'pix');
const hasOtherButtons = data.buttons.some((btn) => btn.type !== 'reply' && btn.type !== 'pix'); const hasCTAButtons = data.buttons.some(
(btn) => btn.type === 'url' || btn.type === 'call' || btn.type === 'copy',
);
// Reply rules /* =========================
* REGRAS DE VALIDAÇÃO
* ========================= */
// Reply
if (hasReplyButtons) { if (hasReplyButtons) {
if (data.buttons.length > 3) { if (data.buttons.length > 3) {
throw new BadRequestException('Maximum of 3 reply buttons allowed'); throw new BadRequestException('Maximum of 3 reply buttons allowed');
} }
if (hasOtherButtons) { if (hasCTAButtons || hasPixButton) {
throw new BadRequestException('Reply buttons cannot be mixed with other button types'); throw new BadRequestException('Reply buttons cannot be mixed with CTA or PIX buttons');
} }
} }
// CTA rules (url/call/copy) - WhatsApp limits to 2 CTAs // PIX
if (hasOtherButtons && !hasReplyButtons && !hasPixButton) {
if (data.buttons.length > 2) {
throw new BadRequestException('Maximum of 2 CTA buttons allowed (url/call/copy)');
}
}
// PIX rules
if (hasPixButton) { if (hasPixButton) {
if (data.buttons.length > 1) { if (data.buttons.length > 1) {
throw new BadRequestException('Only one PIX button is allowed'); throw new BadRequestException('Only one PIX button is allowed');
} }
if (hasOtherButtons) { if (hasReplyButtons || hasCTAButtons) {
throw new BadRequestException('PIX button cannot be mixed with other button types'); throw new BadRequestException('PIX button cannot be mixed with other button types');
} }
const message: proto.IMessage = { const message: proto.IMessage = {
deviceSentMessage: { viewOnceMessage: {
message: { message: {
interactiveMessage: { interactiveMessage: {
nativeFlowMessage: { nativeFlowMessage: {
buttons: [{ name: this.mapType.get('pix'), buttonParamsJson: this.toJSONString(data.buttons[0]) }], buttons: [
messageParamsJson: JSON.stringify({ from: 'api', templateId: v4() }), {
name: this.mapType.get('pix'),
buttonParamsJson: this.toJSONString(data.buttons[0]),
},
],
messageParamsJson: JSON.stringify({
from: 'api',
templateId: v4(),
}),
}, },
}, },
}, },
@@ -3367,43 +3376,63 @@ export class BaileysStartupService extends ChannelStartupService {
}); });
} }
const generate = await (async () => { // CTA (url / call / copy)
if (data?.thumbnailUrl) { if (hasCTAButtons) {
return await this.prepareMediaMessage({ mediatype: 'image', media: data.thumbnailUrl }); if (data.buttons.length > 2) {
throw new BadRequestException('Maximum of 2 CTA buttons allowed');
}
if (hasReplyButtons) {
throw new BadRequestException('CTA buttons cannot be mixed with reply buttons');
}
} }
})();
const buttons = data.buttons.map((value) => { /* =========================
return { name: this.mapType.get(value.type), buttonParamsJson: this.toJSONString(value) }; * HEADER (opcional)
}); * ========================= */
const generatedMedia = data?.thumbnailUrl
? await this.prepareMediaMessage({ mediatype: 'image', media: data.thumbnailUrl })
: null;
/* =========================
* BOTÕES
* ========================= */
const buttons = data.buttons.map((btn) => ({
name: this.mapType.get(btn.type),
buttonParamsJson: this.toJSONString(btn),
}));
/* =========================
* MENSAGEM FINAL
* ========================= */
const message: proto.IMessage = { const message: proto.IMessage = {
deviceSentMessage: { viewOnceMessage: {
message: { message: {
interactiveMessage: { interactiveMessage: {
body: { body: {
text: (() => { text: (() => {
let t = '*' + data.title + '*'; let text = `*${data.title}*`;
if (data?.description) { if (data?.description) {
t += '\n\n'; text += `\n\n${data.description}`;
t += data.description;
t += '\n';
} }
return t; return text;
})(), })(),
}, },
footer: { text: data?.footer }, footer: data?.footer ? { text: data.footer } : undefined,
header: (() => { header: generatedMedia?.message?.imageMessage
if (generate?.message?.imageMessage) { ? {
return { hasMediaAttachment: true,
hasMediaAttachment: !!generate.message.imageMessage, imageMessage: generatedMedia.message.imageMessage,
imageMessage: generate.message.imageMessage,
};
} }
})(), : undefined,
nativeFlowMessage: { nativeFlowMessage: {
buttons, buttons,
messageParamsJson: JSON.stringify({ from: 'api', templateId: v4() }), messageParamsJson: JSON.stringify({
from: 'api',
templateId: v4(),
}),
}, },
}, },
}, },