mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-07-22 12:06:54 -06:00
feat: typebot send buttons
This commit is contained in:
parent
fbccf2eb2a
commit
65a9c78d86
@ -139,9 +139,8 @@ import P from 'pino';
|
|||||||
import qrcode, { QRCodeToDataURLOptions } from 'qrcode';
|
import qrcode, { QRCodeToDataURLOptions } from 'qrcode';
|
||||||
import qrcodeTerminal from 'qrcode-terminal';
|
import qrcodeTerminal from 'qrcode-terminal';
|
||||||
import sharp from 'sharp';
|
import sharp from 'sharp';
|
||||||
import { PassThrough } from 'stream';
|
import { PassThrough, Readable } from 'stream';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
import { Readable } from 'stream';
|
|
||||||
|
|
||||||
const groupMetadataCache = new CacheService(new CacheEngine(configService, 'groups').getEngine());
|
const groupMetadataCache = new CacheService(new CacheEngine(configService, 'groups').getEngine());
|
||||||
|
|
||||||
@ -372,7 +371,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
qrcodeTerminal.generate(qr, { small: true }, (qrcode) =>
|
qrcodeTerminal.generate(qr, { small: true }, (qrcode) =>
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`\n{ instance: ${this.instance.name} pairingCode: ${this.instance.qrcode.pairingCode}, qrcodeCount: ${this.instance.qrcode.count} }\n` +
|
`\n{ instance: ${this.instance.name} pairingCode: ${this.instance.qrcode.pairingCode}, qrcodeCount: ${this.instance.qrcode.count} }\n` +
|
||||||
qrcode,
|
qrcode,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -976,18 +975,18 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
|
|
||||||
const messagesRepository = new Set(
|
const messagesRepository = new Set(
|
||||||
chatwootImport.getRepositoryMessagesCache(instance) ??
|
chatwootImport.getRepositoryMessagesCache(instance) ??
|
||||||
(
|
(
|
||||||
await this.prismaRepository.message.findMany({
|
await this.prismaRepository.message.findMany({
|
||||||
select: { key: true },
|
select: { key: true },
|
||||||
where: { instanceId: this.instanceId },
|
where: { instanceId: this.instanceId },
|
||||||
})
|
})
|
||||||
).map((message) => {
|
).map((message) => {
|
||||||
const key = message.key as {
|
const key = message.key as {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
return key.id;
|
return key.id;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (chatwootImport.getRepositoryMessagesCache(instance) === null) {
|
if (chatwootImport.getRepositoryMessagesCache(instance) === null) {
|
||||||
@ -1138,7 +1137,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
where: { instanceId: this.instanceId, remoteJid: received.key.remoteJid },
|
where: { instanceId: this.instanceId, remoteJid: received.key.remoteJid },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!!existingChat) {
|
if (existingChat) {
|
||||||
const chatToInsert = {
|
const chatToInsert = {
|
||||||
remoteJid: received.key.remoteJid,
|
remoteJid: received.key.remoteJid,
|
||||||
instanceId: this.instanceId,
|
instanceId: this.instanceId,
|
||||||
@ -1474,7 +1473,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
where: { instanceId: this.instanceId, remoteJid: message.remoteJid },
|
where: { instanceId: this.instanceId, remoteJid: message.remoteJid },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!!existingChat) {
|
if (existingChat) {
|
||||||
const chatToInsert = {
|
const chatToInsert = {
|
||||||
remoteJid: message.remoteJid,
|
remoteJid: message.remoteJid,
|
||||||
instanceId: this.instanceId,
|
instanceId: this.instanceId,
|
||||||
@ -2507,9 +2506,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
|
|
||||||
const prepareMedia = await prepareWAMessageMedia(
|
const prepareMedia = await prepareWAMessageMedia(
|
||||||
{
|
{
|
||||||
[type]: isURL(mediaMessage.media)
|
[type]: isURL(mediaMessage.media) ? { url: mediaMessage.media } : Buffer.from(mediaMessage.media, 'base64'),
|
||||||
? { url: mediaMessage.media }
|
|
||||||
: Buffer.from(mediaMessage.media, 'base64'),
|
|
||||||
} as any,
|
} as any,
|
||||||
{ upload: this.client.waUploadToServer },
|
{ upload: this.client.waUploadToServer },
|
||||||
);
|
);
|
||||||
@ -2564,7 +2561,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
if (mediaMessage.mediatype === 'ptv') {
|
if (mediaMessage.mediatype === 'ptv') {
|
||||||
prepareMedia[mediaType] = prepareMedia[type + 'Message'];
|
prepareMedia[mediaType] = prepareMedia[type + 'Message'];
|
||||||
mimetype = 'video/mp4';
|
mimetype = 'video/mp4';
|
||||||
|
|
||||||
if (!prepareMedia[mediaType]) {
|
if (!prepareMedia[mediaType]) {
|
||||||
throw new Error('Failed to prepare video message');
|
throw new Error('Failed to prepare video message');
|
||||||
}
|
}
|
||||||
@ -2588,7 +2585,6 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
|
|
||||||
this.logger.verbose(`Video duration: ${duration} seconds`);
|
this.logger.verbose(`Video duration: ${duration} seconds`);
|
||||||
prepareMedia[mediaType].seconds = duration;
|
prepareMedia[mediaType].seconds = duration;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error('Error getting video duration:');
|
this.logger.error('Error getting video duration:');
|
||||||
this.logger.error(error);
|
this.logger.error(error);
|
||||||
@ -2954,43 +2950,43 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
currency: button.currency,
|
currency: button.currency,
|
||||||
total_amount: {
|
total_amount: {
|
||||||
value: 0,
|
value: 0,
|
||||||
offset: 100
|
offset: 100,
|
||||||
},
|
},
|
||||||
reference_id: this.generateRandomId(),
|
reference_id: this.generateRandomId(),
|
||||||
type: "physical-goods",
|
type: 'physical-goods',
|
||||||
order: {
|
order: {
|
||||||
status: "pending",
|
status: 'pending',
|
||||||
subtotal: {
|
subtotal: {
|
||||||
value: 0,
|
value: 0,
|
||||||
offset: 100
|
offset: 100,
|
||||||
},
|
},
|
||||||
order_type: "ORDER",
|
order_type: 'ORDER',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
name: "",
|
name: '',
|
||||||
amount: {
|
amount: {
|
||||||
value: 0,
|
value: 0,
|
||||||
offset: 100
|
offset: 100,
|
||||||
},
|
},
|
||||||
quantity: 0,
|
quantity: 0,
|
||||||
sale_amount: {
|
sale_amount: {
|
||||||
value: 0,
|
value: 0,
|
||||||
offset: 100
|
offset: 100,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
payment_settings: [
|
payment_settings: [
|
||||||
{
|
{
|
||||||
type: "pix_static_code",
|
type: 'pix_static_code',
|
||||||
pix_static_code: {
|
pix_static_code: {
|
||||||
merchant_name: button.name,
|
merchant_name: button.name,
|
||||||
key: button.key,
|
key: button.key,
|
||||||
key_type: this.mapKeyType.get(button.keyType)
|
key_type: this.mapKeyType.get(button.keyType),
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
share_payment_status: false
|
share_payment_status: false,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -3018,11 +3014,11 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
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 hasOtherButtons = data.buttons.some((btn) => btn.type !== 'reply' && btn.type !== 'pix');
|
||||||
|
|
||||||
if (hasReplyButtons) {
|
if (hasReplyButtons) {
|
||||||
if (data.buttons.length > 3) {
|
if (data.buttons.length > 3) {
|
||||||
@ -3046,10 +3042,12 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
message: {
|
message: {
|
||||||
interactiveMessage: {
|
interactiveMessage: {
|
||||||
nativeFlowMessage: {
|
nativeFlowMessage: {
|
||||||
buttons: [{
|
buttons: [
|
||||||
name: this.mapType.get('pix'),
|
{
|
||||||
buttonParamsJson: this.toJSONString(data.buttons[0]),
|
name: this.mapType.get('pix'),
|
||||||
}],
|
buttonParamsJson: this.toJSONString(data.buttons[0]),
|
||||||
|
},
|
||||||
|
],
|
||||||
messageParamsJson: JSON.stringify({
|
messageParamsJson: JSON.stringify({
|
||||||
from: 'api',
|
from: 'api',
|
||||||
templateId: v4(),
|
templateId: v4(),
|
||||||
|
@ -223,16 +223,88 @@ export class TypebotService {
|
|||||||
|
|
||||||
formattedText = formattedText.replace(/\n$/, '');
|
formattedText = formattedText.replace(/\n$/, '');
|
||||||
|
|
||||||
await instance.textMessage(
|
if (formattedText.includes('[buttons]')) {
|
||||||
{
|
const buttonJson = {
|
||||||
number: remoteJid.split('@')[0],
|
number: remoteJid.split('@')[0],
|
||||||
delay: settings?.delayMessage || 1000,
|
thumbnailUrl: undefined,
|
||||||
text: formattedText,
|
title: '',
|
||||||
},
|
description: '',
|
||||||
false,
|
footer: '',
|
||||||
);
|
buttons: [],
|
||||||
|
};
|
||||||
|
|
||||||
sendTelemetry('/message/sendText');
|
const thumbnailUrlMatch = formattedText.match(/\[thumbnailUrl\]([\s\S]*?)(?=\[title\])/);
|
||||||
|
const titleMatch = formattedText.match(/\[title\]([\s\S]*?)(?=\[description\])/);
|
||||||
|
const descriptionMatch = formattedText.match(/\[description\]([\s\S]*?)(?=\[footer\])/);
|
||||||
|
const footerMatch = formattedText.match(/\[footer\]([\s\S]*?)(?=\[(?:reply|pix|copy|call|url))/);
|
||||||
|
|
||||||
|
if (titleMatch) buttonJson.title = titleMatch[1].trim();
|
||||||
|
if (thumbnailUrlMatch) buttonJson.thumbnailUrl = thumbnailUrlMatch[1].trim();
|
||||||
|
if (descriptionMatch) buttonJson.description = descriptionMatch[1].trim();
|
||||||
|
if (footerMatch) buttonJson.footer = footerMatch[1].trim();
|
||||||
|
|
||||||
|
const buttonTypes = {
|
||||||
|
reply: /\[reply\]([\s\S]*?)(?=\[(?:reply|pix|copy|call|url)|$)/g,
|
||||||
|
pix: /\[pix\]([\s\S]*?)(?=\[(?:reply|pix|copy|call|url)|$)/g,
|
||||||
|
copy: /\[copy\]([\s\S]*?)(?=\[(?:reply|pix|copy|call|url)|$)/g,
|
||||||
|
call: /\[call\]([\s\S]*?)(?=\[(?:reply|pix|copy|call|url)|$)/g,
|
||||||
|
url: /\[url\]([\s\S]*?)(?=\[(?:reply|pix|copy|call|url)|$)/g,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [type, pattern] of Object.entries(buttonTypes)) {
|
||||||
|
let match;
|
||||||
|
while ((match = pattern.exec(formattedText)) !== null) {
|
||||||
|
const content = match[1].trim();
|
||||||
|
const button: any = { type };
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'pix':
|
||||||
|
button.currency = content.match(/currency: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||||
|
button.name = content.match(/name: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||||
|
button.keyType = content.match(/keyType: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||||
|
button.key = content.match(/key: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'reply':
|
||||||
|
button.displayText = content.match(/displayText: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||||
|
button.id = content.match(/id: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'copy':
|
||||||
|
button.displayText = content.match(/displayText: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||||
|
button.copyCode = content.match(/copyCode: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'call':
|
||||||
|
button.displayText = content.match(/displayText: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||||
|
button.phoneNumber = content.match(/phone: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'url':
|
||||||
|
button.displayText = content.match(/displayText: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||||
|
button.url = content.match(/url: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(button).length > 1) {
|
||||||
|
buttonJson.buttons.push(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await instance.buttonMessage(buttonJson);
|
||||||
|
} else {
|
||||||
|
await instance.textMessage(
|
||||||
|
{
|
||||||
|
number: remoteJid.split('@')[0],
|
||||||
|
delay: settings?.delayMessage || 1000,
|
||||||
|
text: formattedText,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
sendTelemetry('/message/sendText');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.type === 'image') {
|
if (message.type === 'image') {
|
||||||
|
Loading…
Reference in New Issue
Block a user