Skip to content

Commit 070cd71

Browse files
Merge pull request #2347 from eltonciatto/develop
feat: add typebot file support & fix whatsapp active link preview
2 parents d666d43 + 3514eeb commit 070cd71

File tree

2 files changed

+113
-11
lines changed

2 files changed

+113
-11
lines changed

src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ import { createHash } from 'crypto';
138138
import EventEmitter2 from 'eventemitter2';
139139
import ffmpeg from 'fluent-ffmpeg';
140140
import FormData from 'form-data';
141+
import { getLinkPreview } from 'link-preview-js';
141142
import Long from 'long';
142143
import mimeTypes from 'mime-types';
143144
import NodeCache from 'node-cache';
@@ -434,7 +435,7 @@ export class BaileysStartupService extends ChannelStartupService {
434435
qrcodeTerminal.generate(qr, { small: true }, (qrcode) =>
435436
this.logger.log(
436437
`\n{ instance: ${this.instance.name} pairingCode: ${this.instance.qrcode.pairingCode}, qrcodeCount: ${this.instance.qrcode.count} }\n` +
437-
qrcode,
438+
qrcode,
438439
),
439440
);
440441

@@ -1052,16 +1053,16 @@ export class BaileysStartupService extends ChannelStartupService {
10521053

10531054
const messagesRepository: Set<string> = new Set(
10541055
chatwootImport.getRepositoryMessagesCache(instance) ??
1055-
(
1056-
await this.prismaRepository.message.findMany({
1057-
select: { key: true },
1058-
where: { instanceId: this.instanceId },
1059-
})
1060-
).map((message) => {
1061-
const key = message.key as { id: string };
1056+
(
1057+
await this.prismaRepository.message.findMany({
1058+
select: { key: true },
1059+
where: { instanceId: this.instanceId },
1060+
})
1061+
).map((message) => {
1062+
const key = message.key as { id: string };
10621063

1063-
return key.id;
1064-
}),
1064+
return key.id;
1065+
}),
10651066
);
10661067

10671068
if (chatwootImport.getRepositoryMessagesCache(instance) === null) {
@@ -2220,6 +2221,46 @@ export class BaileysStartupService extends ChannelStartupService {
22202221
}
22212222
}
22222223

2224+
private async generateLinkPreview(text: string) {
2225+
try {
2226+
const linkRegex = /https?:\/\/[^\s]+/;
2227+
const match = text.match(linkRegex);
2228+
2229+
if (!match) return undefined;
2230+
2231+
// Trim common trailing punctuation that may follow URLs in natural text
2232+
const url = match[0].replace(/[.,);\]]+$/u, '');
2233+
if (!url) return undefined;
2234+
2235+
const previewData = await getLinkPreview(url, {
2236+
imagesPropertyType: 'og', // fetches only open-graph images
2237+
headers: {
2238+
'user-agent': 'googlebot', // fetches with googlebot to prevent login pages
2239+
},
2240+
}) as any;
2241+
2242+
if (!previewData || !previewData.title) return undefined;
2243+
2244+
const image = previewData.images && previewData.images.length > 0 ? previewData.images[0] : undefined;
2245+
2246+
return {
2247+
externalAdReply: {
2248+
title: previewData.title,
2249+
body: previewData.description,
2250+
mediaType: 2, // 2 for video/image preview, though usually 1 is for thumbnail
2251+
thumbnailUrl: image,
2252+
sourceUrl: url,
2253+
mediaUrl: url,
2254+
renderLargerThumbnail: true
2255+
// showAdAttribution: true // Removed to prevent "Sent via ad" label
2256+
}
2257+
};
2258+
} catch (error) {
2259+
this.logger.error(`Error generating link preview: ${error}`);
2260+
return undefined;
2261+
}
2262+
}
2263+
22232264
private async sendMessage(
22242265
sender: string,
22252266
message: any,
@@ -2431,7 +2472,12 @@ export class BaileysStartupService extends ChannelStartupService {
24312472
}
24322473
}
24332474

2434-
const linkPreview = options?.linkPreview != false ? undefined : false;
2475+
const linkPreview = options?.linkPreview === false ? false : undefined;
2476+
2477+
let previewContext: any = undefined;
2478+
if (linkPreview !== false && (message as any)?.conversation) {
2479+
previewContext = await this.generateLinkPreview((message as any).conversation);
2480+
}
24352481

24362482
let quoted: WAMessage;
24372483

@@ -2485,6 +2531,7 @@ export class BaileysStartupService extends ChannelStartupService {
24852531
quoted,
24862532
null,
24872533
group?.ephemeralDuration,
2534+
previewContext,
24882535
// group?.participants,
24892536
);
24902537
} else {
@@ -2498,6 +2545,7 @@ export class BaileysStartupService extends ChannelStartupService {
24982545
unsigned: false,
24992546
},
25002547
disappearingMode: { initiator: 0 },
2548+
...previewContext,
25012549
};
25022550
messageSent = await this.sendMessage(
25032551
sender,

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

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,60 @@ export class TypebotService extends BaseChatbotService<TypebotModel, any> {
368368
sendTelemetry('/message/sendWhatsAppAudio');
369369
}
370370

371+
if (message.type === 'file' || message.type === 'embed') {
372+
const content = message.content as { url?: string; name?: string } | undefined;
373+
if (!content?.url) {
374+
sendTelemetry('/message/sendMediaMissingUrl');
375+
return;
376+
}
377+
378+
const mediaUrl = content.url;
379+
const mediaType = this.getMediaType(mediaUrl);
380+
381+
let fileName = content.name;
382+
if (!fileName) {
383+
try {
384+
const urlObj = new URL(mediaUrl);
385+
const path = urlObj.pathname || '';
386+
const candidate = path.split('/').pop() || '';
387+
if (candidate && candidate.includes('.')) {
388+
fileName = candidate;
389+
}
390+
} catch {
391+
// Ignore URL parsing failures
392+
}
393+
394+
if (!fileName) {
395+
fileName = mediaType && mediaType !== 'document' ? `media.${mediaType}` : 'attachment';
396+
}
397+
}
398+
399+
if (mediaType === 'audio') {
400+
await instance.audioWhatsapp(
401+
{
402+
number: session.remoteJid,
403+
delay: settings?.delayMessage || 1000,
404+
encoding: true,
405+
audio: mediaUrl,
406+
},
407+
false,
408+
);
409+
} else {
410+
await instance.mediaMessage(
411+
{
412+
number: session.remoteJid,
413+
delay: settings?.delayMessage || 1000,
414+
mediatype: mediaType || 'document',
415+
media: mediaUrl,
416+
fileName,
417+
},
418+
null,
419+
false,
420+
);
421+
}
422+
sendTelemetry('/message/sendMedia');
423+
}
424+
371425
const wait = findItemAndGetSecondsToWait(clientSideActions, message.id);
372426

373427
if (wait) {

0 commit comments

Comments
 (0)