Skip to content

Commit c55312d

Browse files
committed
feat: typebot send list
1 parent 52216ec commit c55312d

File tree

1 file changed

+170
-10
lines changed

1 file changed

+170
-10
lines changed

src/api/integrations/chatbot/typebot/services/typebot.service.ts

Lines changed: 170 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,51 @@ export class TypebotService {
223223

224224
formattedText = formattedText.replace(/\n$/, '');
225225

226-
if (formattedText.includes('[buttons]')) {
226+
if (formattedText.includes('[list]')) {
227+
const listJson = {
228+
number: remoteJid.split('@')[0],
229+
title: '',
230+
description: '',
231+
buttonText: '',
232+
footerText: '',
233+
sections: [],
234+
};
235+
236+
const titleMatch = formattedText.match(/\[title\]([\s\S]*?)(?=\[description\])/);
237+
const descriptionMatch = formattedText.match(/\[description\]([\s\S]*?)(?=\[buttonText\])/);
238+
const buttonTextMatch = formattedText.match(/\[buttonText\]([\s\S]*?)(?=\[footerText\])/);
239+
const footerTextMatch = formattedText.match(/\[footerText\]([\s\S]*?)(?=\[menu\])/);
240+
241+
if (titleMatch) listJson.title = titleMatch[1].trim();
242+
if (descriptionMatch) listJson.description = descriptionMatch[1].trim();
243+
if (buttonTextMatch) listJson.buttonText = buttonTextMatch[1].trim();
244+
if (footerTextMatch) listJson.footerText = footerTextMatch[1].trim();
245+
246+
const menuContent = formattedText.match(/\[menu\]([\s\S]*?)\[\/menu\]/)?.[1];
247+
if (menuContent) {
248+
const sections = menuContent.match(/\[section\]([\s\S]*?)(?=\[section\]|\[\/section\])/g);
249+
if (sections) {
250+
sections.forEach((section) => {
251+
const sectionTitle = section.match(/title: (.*?)(?:\n|$)/)?.[1]?.trim();
252+
const rows = section.match(/\[row\]([\s\S]*?)(?=\[row\]|\[\/section\]|\[\/menu\])/g);
253+
254+
const sectionData = {
255+
title: sectionTitle,
256+
rows:
257+
rows?.map((row) => ({
258+
title: row.match(/title: (.*?)(?:\n|$)/)?.[1]?.trim(),
259+
description: row.match(/description: (.*?)(?:\n|$)/)?.[1]?.trim(),
260+
rowId: row.match(/rowId: (.*?)(?:\n|$)/)?.[1]?.trim(),
261+
})) || [],
262+
};
263+
264+
listJson.sections.push(sectionData);
265+
});
266+
}
267+
}
268+
269+
await instance.listMessage(listJson);
270+
} else if (formattedText.includes('[buttons]')) {
227271
const buttonJson = {
228272
number: remoteJid.split('@')[0],
229273
thumbnailUrl: undefined,
@@ -302,9 +346,9 @@ export class TypebotService {
302346
},
303347
false,
304348
);
305-
306-
sendTelemetry('/message/sendText');
307349
}
350+
351+
sendTelemetry('/message/sendText');
308352
}
309353

310354
if (message.type === 'image') {
@@ -371,14 +415,130 @@ export class TypebotService {
371415

372416
formattedText = formattedText.replace(/\n$/, '');
373417

374-
await instance.textMessage(
375-
{
418+
if (formattedText.includes('[list]')) {
419+
const listJson = {
376420
number: remoteJid.split('@')[0],
377-
delay: settings?.delayMessage || 1000,
378-
text: formattedText,
379-
},
380-
false,
381-
);
421+
title: '',
422+
description: '',
423+
buttonText: '',
424+
footerText: '',
425+
sections: [],
426+
};
427+
428+
const titleMatch = formattedText.match(/\[title\]([\s\S]*?)(?=\[description\])/);
429+
const descriptionMatch = formattedText.match(/\[description\]([\s\S]*?)(?=\[buttonText\])/);
430+
const buttonTextMatch = formattedText.match(/\[buttonText\]([\s\S]*?)(?=\[footerText\])/);
431+
const footerTextMatch = formattedText.match(/\[footerText\]([\s\S]*?)(?=\[menu\])/);
432+
433+
if (titleMatch) listJson.title = titleMatch[1].trim();
434+
if (descriptionMatch) listJson.description = descriptionMatch[1].trim();
435+
if (buttonTextMatch) listJson.buttonText = buttonTextMatch[1].trim();
436+
if (footerTextMatch) listJson.footerText = footerTextMatch[1].trim();
437+
438+
const menuContent = formattedText.match(/\[menu\]([\s\S]*?)\[\/menu\]/)?.[1];
439+
if (menuContent) {
440+
const sections = menuContent.match(/\[section\]([\s\S]*?)(?=\[section\]|\[\/section\])/g);
441+
if (sections) {
442+
sections.forEach((section) => {
443+
const sectionTitle = section.match(/title: (.*?)(?:\n|$)/)?.[1]?.trim();
444+
const rows = section.match(/\[row\]([\s\S]*?)(?=\[row\]|\[\/section\]|\[\/menu\])/g);
445+
446+
const sectionData = {
447+
title: sectionTitle,
448+
rows:
449+
rows?.map((row) => ({
450+
title: row.match(/title: (.*?)(?:\n|$)/)?.[1]?.trim(),
451+
description: row.match(/description: (.*?)(?:\n|$)/)?.[1]?.trim(),
452+
rowId: row.match(/rowId: (.*?)(?:\n|$)/)?.[1]?.trim(),
453+
})) || [],
454+
};
455+
456+
listJson.sections.push(sectionData);
457+
});
458+
}
459+
}
460+
461+
await instance.listMessage(listJson);
462+
} else if (formattedText.includes('[buttons]')) {
463+
const buttonJson = {
464+
number: remoteJid.split('@')[0],
465+
thumbnailUrl: undefined,
466+
title: '',
467+
description: '',
468+
footer: '',
469+
buttons: [],
470+
};
471+
472+
const thumbnailUrlMatch = formattedText.match(/\[thumbnailUrl\]([\s\S]*?)(?=\[title\])/);
473+
const titleMatch = formattedText.match(/\[title\]([\s\S]*?)(?=\[description\])/);
474+
const descriptionMatch = formattedText.match(/\[description\]([\s\S]*?)(?=\[footer\])/);
475+
const footerMatch = formattedText.match(/\[footer\]([\s\S]*?)(?=\[(?:reply|pix|copy|call|url))/);
476+
477+
if (titleMatch) buttonJson.title = titleMatch[1].trim();
478+
if (thumbnailUrlMatch) buttonJson.thumbnailUrl = thumbnailUrlMatch[1].trim();
479+
if (descriptionMatch) buttonJson.description = descriptionMatch[1].trim();
480+
if (footerMatch) buttonJson.footer = footerMatch[1].trim();
481+
482+
const buttonTypes = {
483+
reply: /\[reply\]([\s\S]*?)(?=\[(?:reply|pix|copy|call|url)|$)/g,
484+
pix: /\[pix\]([\s\S]*?)(?=\[(?:reply|pix|copy|call|url)|$)/g,
485+
copy: /\[copy\]([\s\S]*?)(?=\[(?:reply|pix|copy|call|url)|$)/g,
486+
call: /\[call\]([\s\S]*?)(?=\[(?:reply|pix|copy|call|url)|$)/g,
487+
url: /\[url\]([\s\S]*?)(?=\[(?:reply|pix|copy|call|url)|$)/g,
488+
};
489+
490+
for (const [type, pattern] of Object.entries(buttonTypes)) {
491+
let match;
492+
while ((match = pattern.exec(formattedText)) !== null) {
493+
const content = match[1].trim();
494+
const button: any = { type };
495+
496+
switch (type) {
497+
case 'pix':
498+
button.currency = content.match(/currency: (.*?)(?:\n|$)/)?.[1]?.trim();
499+
button.name = content.match(/name: (.*?)(?:\n|$)/)?.[1]?.trim();
500+
button.keyType = content.match(/keyType: (.*?)(?:\n|$)/)?.[1]?.trim();
501+
button.key = content.match(/key: (.*?)(?:\n|$)/)?.[1]?.trim();
502+
break;
503+
504+
case 'reply':
505+
button.displayText = content.match(/displayText: (.*?)(?:\n|$)/)?.[1]?.trim();
506+
button.id = content.match(/id: (.*?)(?:\n|$)/)?.[1]?.trim();
507+
break;
508+
509+
case 'copy':
510+
button.displayText = content.match(/displayText: (.*?)(?:\n|$)/)?.[1]?.trim();
511+
button.copyCode = content.match(/copyCode: (.*?)(?:\n|$)/)?.[1]?.trim();
512+
break;
513+
514+
case 'call':
515+
button.displayText = content.match(/displayText: (.*?)(?:\n|$)/)?.[1]?.trim();
516+
button.phoneNumber = content.match(/phone: (.*?)(?:\n|$)/)?.[1]?.trim();
517+
break;
518+
519+
case 'url':
520+
button.displayText = content.match(/displayText: (.*?)(?:\n|$)/)?.[1]?.trim();
521+
button.url = content.match(/url: (.*?)(?:\n|$)/)?.[1]?.trim();
522+
break;
523+
}
524+
525+
if (Object.keys(button).length > 1) {
526+
buttonJson.buttons.push(button);
527+
}
528+
}
529+
}
530+
531+
await instance.buttonMessage(buttonJson);
532+
} else {
533+
await instance.textMessage(
534+
{
535+
number: remoteJid.split('@')[0],
536+
delay: settings?.delayMessage || 1000,
537+
text: formattedText,
538+
},
539+
false,
540+
);
541+
}
382542

383543
sendTelemetry('/message/sendText');
384544
}

0 commit comments

Comments
 (0)