Skip to content

Conversation

@eltonciatto
Copy link

@eltonciatto eltonciatto commented Jan 4, 2026

📋 Description

This PR addresses two critical issues related to message handling in Typebot and Link Previews in WhatsApp (Baileys):

  1. Typebot Integration Enhancement:

    • Adds missing support for file (e.g., PDFs) and embed message types in TypebotService.
    • Previously, these blocks were ignored. Now, they are correctly identified: audio types utilize audioWhatsapp and other files utilize mediaMessage (handling documents, images, and generic files).
  2. WhatsApp Link Preview Fix (Active Scraping):

    • Fixes the linkPreview functionality which was failing to generate previews for text messages containing links.
    • Change: Replaced the passive partial logic with an Active Scraping strategy using link-preview-js within BaileysStartupService.
    • Mechanism: When linkPreview is enabled, the system now actively fetches metadata (title, description, thumbnail) from the URL and injects it into the message's contextInfo.externalAdReply. This ensures rich previews appear consistently, even for plain text links.
    • Refinement: Removed showAdAttribution: true to prevent the "Sent via ad" label, ensuring a native look.

🔗 Related Issue

Closes # (If there is an issue number, add it here)

🧪 Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📚 Documentation update
  • 🔧 Refactoring (no functional changes)
  • ⚡ Performance improvement
  • 🧹 Code cleanup
  • 🔒 Security fix

🧪 Testing

  • Manual testing completed
  • Functionality verified in development environment
  • No breaking changes introduced
  • Tested with different connection types (Typebot & Direct API)

Scenarios Verified:

  1. Typebot: Sending a PDF file block → Received correctly as a document.
  2. Typebot: Sending an Embed block → Received correctly as media.
  3. WhatsApp: Sending a text message with a URL (e.g., Testing https://google.com) → Received with a rich link preview card (Title, Description, Thumbnail).

📸 Screenshots (if applicable)

✅ Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have manually tested my changes thoroughly
  • I have verified the changes work with different scenarios
  • Any dependent changes have been merged and published

📝 Additional Notes

The link-preview-js library was already a dependency, so no new packages were added. The logic for link preview handles failures gracefully (falls back to sending without preview if scraping fails).

Summary by Sourcery

Enhance Typebot WhatsApp integration with richer media support and enable reliable active link previews for WhatsApp messages.

New Features:

  • Handle Typebot file and embed blocks by sending them as appropriate WhatsApp media or documents, inferring filenames when needed.

Bug Fixes:

  • Restore and improve WhatsApp text link previews by actively scraping URL metadata and injecting it into outgoing messages when link previews are enabled.

…preview

- Fix linkPreview logic in Baileys to default to true
- Add support for 'file' and 'embed' types in Typebot integration
- Ensure correct media type detection for PDFs and docs
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Jan 4, 2026

Reviewer's Guide

Adds Typebot support for sending file/embed blocks as WhatsApp media/audio and introduces active URL scraping to reliably generate WhatsApp link previews, wiring the preview context into the Baileys send flow while preserving explicit linkPreview disabling.

Sequence diagram for WhatsApp active link preview generation

sequenceDiagram
  actor User
  participant ClientApp
  participant BaileysStartupService
  participant LinkPreviewJS
  participant WhatsAppServer

  User ->> ClientApp: Send text message with URL
  ClientApp ->> BaileysStartupService: sendMessage(sender, message, options, group)
  activate BaileysStartupService

  BaileysStartupService ->> BaileysStartupService: determine linkPreview flag
  alt linkPreview explicitly disabled
    BaileysStartupService ->> WhatsAppServer: send message without previewContext
  else linkPreview enabled or undefined
    BaileysStartupService ->> BaileysStartupService: generateLinkPreview(text)
    activate BaileysStartupService
    BaileysStartupService ->> LinkPreviewJS: getLinkPreview(url, headers)
    LinkPreviewJS -->> BaileysStartupService: previewData(title, description, images)
    BaileysStartupService ->> BaileysStartupService: build externalAdReply context
    deactivate BaileysStartupService

    BaileysStartupService ->> WhatsAppServer: send message with previewContext
  end

  WhatsAppServer -->> User: Deliver message with rich link preview
Loading

Sequence diagram for Typebot file and embed message handling to WhatsApp

sequenceDiagram
  actor TypebotFlow
  participant TypebotService
  participant WhatsAppInstance
  participant WhatsAppServer

  TypebotFlow ->> TypebotService: Emit outgoing message
  TypebotService ->> TypebotService: Inspect message.type

  alt message.type is file or embed
    TypebotService ->> TypebotService: Extract content.url and content.name
    TypebotService ->> TypebotService: mediaType = getMediaType(mediaUrl)
    TypebotService ->> TypebotService: Derive fileName if missing

    alt mediaType is audio
      TypebotService ->> WhatsAppInstance: audioWhatsapp(number, delay, encoding, audioUrl)
    else other media type
      TypebotService ->> WhatsAppInstance: mediaMessage(number, delay, mediatype, mediaUrl, fileName)
    end

    TypebotService ->> TypebotService: sendTelemetry(/message/sendMedia)
  else other message types
    TypebotService ->> WhatsAppInstance: Existing handling for text, image, etc.
  end

  WhatsAppInstance ->> WhatsAppServer: Forward WhatsApp message
  WhatsAppServer -->> TypebotFlow: Message delivered to end user
Loading

Updated class diagram for BaileysStartupService and TypebotService message handling

classDiagram
  class BaileysStartupService {
    +sendMessage(sender:string, message:any, options:any, group:any, previewContext:any) Promise~any~
    -generateLinkPreview(text:string) Promise~any~
  }

  class TypebotService {
    +handleTypebotMessage(instance:any, session:any, message:any, settings:any) Promise~any~
    -getMediaType(mediaUrl:string) string
  }

  TypebotService --> BaileysStartupService : uses for WhatsApp delivery

  class LinkPreviewJS {
    +getLinkPreview(url:string, options:any) Promise~any~
  }

  BaileysStartupService --> LinkPreviewJS : generates link previews using
Loading

File-Level Changes

Change Details Files
Implement active link preview generation for WhatsApp text messages and inject result into sent message context.
  • Added generateLinkPreview helper that extracts the first URL from text, normalizes it, calls link-preview-js, and maps metadata into an externalAdReply structure while handling errors gracefully.
  • Updated sendMessage options handling so linkPreview=false explicitly disables previews, otherwise link previews are attempted for conversation messages.
  • Wired generated preview context into both group and direct-message send paths by passing it into sendMessage and spreading it into the outgoing message object.
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts
Add Typebot support for file and embed message types by routing them through existing WhatsApp media/audio send helpers.
  • Extended TypebotService to detect file/embed message types and extract URL/name from message content, skipping send when URL is missing and emitting telemetry.
  • Derived a best-effort filename from the URL path or media type, falling back to generic names when necessary.
  • Routed audio media URLs through audioWhatsapp and all other media through mediaMessage with mediatype inferred from URL, and emitted a consolidated media telemetry event.
src/api/integrations/chatbot/typebot/services/typebot.service.ts
Minor formatting/indentation adjustments in BaileysStartupService.
  • Normalized indentation in QR code logging block.
  • Re-indented initialization of the messagesRepository Set for readability without changing logic.
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 3 issues, and left some high level feedback:

  • The link preview payload from generateLinkPreview is shaped as { externalAdReply: ... } but is later spread directly into the message options/relay call, so it never ends up under contextInfo.externalAdReply as described in the PR; consider wrapping it under contextInfo (e.g., { contextInfo: { externalAdReply: ... } }) where Baileys expects it.
  • In generateLinkPreview, the error logging interpolates the error into a template string, which may lose stack details; prefer passing the error object directly to the logger (e.g., this.logger.error('Error generating link preview', error)) for better diagnostics.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The link preview payload from `generateLinkPreview` is shaped as `{ externalAdReply: ... }` but is later spread directly into the message options/relay call, so it never ends up under `contextInfo.externalAdReply` as described in the PR; consider wrapping it under `contextInfo` (e.g., `{ contextInfo: { externalAdReply: ... } }`) where Baileys expects it.
- In `generateLinkPreview`, the error logging interpolates the error into a template string, which may lose stack details; prefer passing the error object directly to the logger (e.g., `this.logger.error('Error generating link preview', error)`) for better diagnostics.

## Individual Comments

### Comment 1
<location> `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:2230-2233` </location>
<code_context>

+  private async generateLinkPreview(text: string) {
+    try {
+      const linkRegex = /https?:\/\/[^\s]+/;
+      const match = text.match(linkRegex);
+
+      if (!match) return undefined;
</code_context>

<issue_to_address>
**suggestion:** URL regex is likely to capture trailing punctuation and break previews for common cases.

In natural text, this pattern will often capture trailing punctuation like `)`, `,`, or `.` (e.g., `"Check this (https://example.com)."`), causing preview failures. Please either restrict the regex to exclude common trailing punctuation or post-process the match (e.g., trim `.,);]`) or, ideally, use a URL parser for more robust handling.

```suggestion
      if (!match) return undefined;

      // Trim common trailing punctuation that may follow URLs in natural text
      let url = match[0].replace(/[.,);\]]+$/u, '');
      if (!url) return undefined;

      const previewData = await getLinkPreview(url, {
```
</issue_to_address>

### Comment 2
<location> `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:2546` </location>
<code_context>
             unsigned: false,
           },
           disappearingMode: { initiator: 0 },
+          ...previewContext,
         };
         messageSent = await this.sendMessage(
</code_context>

<issue_to_address>
**issue (bug_risk):** The spread structure of `previewContext` may not match the message format expected by Baileys here.

`generateLinkPreview` returns `{ externalAdReply: { ... } }`, but this is being spread at the root of the message object. If Baileys expects link previews under `contextInfo.externalAdReply`, this structure may be ignored. Consider shaping `previewContext` into the exact structure Baileys requires at this call site (e.g., wrapping it under `contextInfo`) instead of spreading the helper output directly.
</issue_to_address>

### Comment 3
<location> `src/api/integrations/chatbot/typebot/services/typebot.service.ts:371-399` </location>
<code_context>
         sendTelemetry('/message/sendWhatsAppAudio');
       }

+      if (message.type === 'file' || message.type === 'embed') {
+        const mediaUrl = message.content.url;
+        const mediaType = this.getMediaType(mediaUrl);
+
+        if (mediaType === 'audio') {
+          await instance.audioWhatsapp(
+            {
+              number: session.remoteJid,
+              delay: settings?.delayMessage || 1000,
+              encoding: true,
+              audio: mediaUrl,
+            },
+            false,
+          );
+        } else {
+          await instance.mediaMessage(
+            {
+              number: session.remoteJid,
+              delay: settings?.delayMessage || 1000,
+              mediatype: mediaType || 'document',
+              media: mediaUrl,
+              fileName: message.content.name || 'document.pdf',
+            },
+            null,
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Assumptions about `message.content.url`/`name` and defaulting to `document.pdf` may be brittle.

This branch assumes `message.content.url` always exists for both `file` and `embed`, and hard-codes `document.pdf` when `name` is missing, which will be wrong for many embeds or non-PDF files. Consider guarding against missing `content`/`url` (no-op or log), deriving the filename from the URL, or using a more generic/default name (possibly based on `mediaType`).

```suggestion
      if (message.type === 'file' || message.type === 'embed') {
        const content = message.content as { url?: string; name?: string } | undefined;

        if (!content?.url) {
          // No media URL available; skip sending and record telemetry for diagnostics
          sendTelemetry('/message/sendMediaMissingUrl');
          return;
        }

        const mediaUrl = content.url;
        const mediaType = this.getMediaType(mediaUrl);

        let fileName = content.name;
        if (!fileName) {
          let derivedName: string | undefined;

          try {
            const urlObj = new URL(mediaUrl);
            const path = urlObj.pathname || '';
            const candidate = path.split('/').pop() || '';

            if (candidate && candidate.includes('.')) {
              derivedName = candidate;
            }
          } catch {
            // Ignore URL parsing failures; fallback to generic names below
          }

          if (derivedName) {
            fileName = derivedName;
          } else if (mediaType && mediaType !== 'document') {
            fileName = `media.${mediaType}`;
          } else {
            fileName = 'attachment';
          }
        }

        if (mediaType === 'audio') {
          await instance.audioWhatsapp(
            {
              number: session.remoteJid,
              delay: settings?.delayMessage || 1000,
              encoding: true,
              audio: mediaUrl,
            },
            false,
          );
        } else {
          await instance.mediaMessage(
            {
              number: session.remoteJid,
              delay: settings?.delayMessage || 1000,
              mediatype: mediaType || 'document',
              media: mediaUrl,
              fileName,
            },
            null,
            false,
          );
        }
        sendTelemetry('/message/sendMedia');
      }
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@eltonciatto eltonciatto changed the title Develop feat: add typebot file support & fix whatsapp active link preview Jan 4, 2026
…ng, Typebot integration, and comprehensive API functionalities for chat, group, and business profiles.
…tions, messages, groups, profiles, and integrating with Chatwoot, OpenAI, and S3.
@eltonciatto eltonciatto marked this pull request as draft January 4, 2026 03:16
@eltonciatto eltonciatto marked this pull request as ready for review January 4, 2026 03:17
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location> `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:2259-2262` </location>
<code_context>
+          // showAdAttribution: true // Removed to prevent "Sent via ad" label
+        }
+      };
+    } catch (error) {
+      this.logger.error(`Error generating link preview: ${error}`);
+      return undefined;
+    }
</code_context>

<issue_to_address>
**suggestion:** Logging the error object via template string may lose stack/context information.

Instead of interpolating `error` into the string (which will typically emit `"[object Object]"`), pass it as a separate argument so the logger can include stack and metadata, e.g.:

```ts
} catch (error) {
  this.logger.error('Error generating link preview', error);
  return undefined;
}
```

```suggestion
    } catch (error) {
      this.logger.error('Error generating link preview', error);
      return undefined;
    }
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

eltonciatto and others added 2 commits January 4, 2026 00:21
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant