fix: Handle Instagram text and attachments as separate messages (#11315)

When we send text with attachments on Instagram, Instagram treats text
and attachments as separate messages. However, Chatwoot keeps them as a
single message. Since Instagram sends echo events for each message, this
can create duplicate messages in Chatwoot. To prevent this, we will send
text and attachments as separate messages.

---------

Co-authored-by: Pranav <pranav@chatwoot.com>
This commit is contained in:
Muhsin Keloth
2025-04-16 20:46:47 +05:30
committed by GitHub
parent 630826baed
commit 0b156f6d4e
6 changed files with 31 additions and 11 deletions

View File

@@ -65,6 +65,7 @@ class DashboardController < ActionController::Base
VAPID_PUBLIC_KEY: VapidService.public_key, VAPID_PUBLIC_KEY: VapidService.public_key,
ENABLE_ACCOUNT_SIGNUP: GlobalConfigService.load('ENABLE_ACCOUNT_SIGNUP', 'false'), ENABLE_ACCOUNT_SIGNUP: GlobalConfigService.load('ENABLE_ACCOUNT_SIGNUP', 'false'),
FB_APP_ID: GlobalConfigService.load('FB_APP_ID', ''), FB_APP_ID: GlobalConfigService.load('FB_APP_ID', ''),
INSTAGRAM_APP_ID: GlobalConfigService.load('INSTAGRAM_APP_ID', ''),
FACEBOOK_API_VERSION: GlobalConfigService.load('FACEBOOK_API_VERSION', 'v17.0'), FACEBOOK_API_VERSION: GlobalConfigService.load('FACEBOOK_API_VERSION', 'v17.0'),
IS_ENTERPRISE: ChatwootApp.enterprise?, IS_ENTERPRISE: ChatwootApp.enterprise?,
AZURE_APP_ID: GlobalConfigService.load('AZURE_APP_ID', ''), AZURE_APP_ID: GlobalConfigService.load('AZURE_APP_ID', ''),

View File

@@ -17,6 +17,9 @@ export default {
hasFbConfigured() { hasFbConfigured() {
return window.chatwootConfig?.fbAppId; return window.chatwootConfig?.fbAppId;
}, },
hasInstagramConfigured() {
return window.chatwootConfig?.instagramAppId;
},
isActive() { isActive() {
const { key } = this.channel; const { key } = this.channel;
if (Object.keys(this.enabledFeatures).length === 0) { if (Object.keys(this.enabledFeatures).length === 0) {
@@ -33,7 +36,9 @@ export default {
} }
if (key === 'instagram') { if (key === 'instagram') {
return this.enabledFeatures.channel_instagram; return (
this.enabledFeatures.channel_instagram && this.hasInstagramConfigured
);
} }
return [ return [

View File

@@ -686,7 +686,11 @@ export default {
this.isATwilioWhatsAppChannel || this.isATwilioWhatsAppChannel ||
this.isAWhatsAppCloudChannel || this.isAWhatsAppCloudChannel ||
this.is360DialogWhatsAppChannel; this.is360DialogWhatsAppChannel;
if (isOnWhatsApp && !this.isPrivate) { // When users send messages containing both text and attachments on Instagram, Instagram treats them as separate messages.
// Although Chatwoot combines these into a single message, Instagram sends separate echo events for each component.
// This can create duplicate messages in Chatwoot. To prevent this issue, we'll handle text and attachments as separate messages.
const isOnInstagram = this.isAInstagramChannel;
if ((isOnWhatsApp || isOnInstagram) && !this.isPrivate) {
this.sendMessageAsMultipleMessages(this.message); this.sendMessageAsMultipleMessages(this.message);
} else { } else {
const messagePayload = this.getMessagePayload(this.message); const messagePayload = this.getMessagePayload(this.message);
@@ -703,7 +707,7 @@ export default {
} }
}, },
sendMessageAsMultipleMessages(message) { sendMessageAsMultipleMessages(message) {
const messages = this.getMessagePayloadForWhatsapp(message); const messages = this.getMultipleMessagesPayload(message);
messages.forEach(messagePayload => { messages.forEach(messagePayload => {
this.sendMessage(messagePayload); this.sendMessage(messagePayload);
}); });
@@ -935,11 +939,11 @@ export default {
return payload; return payload;
}, },
getMessagePayloadForWhatsapp(message) { getMultipleMessagesPayload(message) {
const multipleMessagePayload = []; const multipleMessagePayload = [];
if (this.attachedFiles && this.attachedFiles.length) { if (this.attachedFiles && this.attachedFiles.length) {
let caption = message; let caption = this.isAInstagramChannel ? '' : message;
this.attachedFiles.forEach(attachment => { this.attachedFiles.forEach(attachment => {
const attachedFile = this.globalConfig.directUploadsEnabled const attachedFile = this.globalConfig.directUploadsEnabled
? attachment.blobSignedId ? attachment.blobSignedId
@@ -954,9 +958,19 @@ export default {
attachmentPayload = this.setReplyToInPayload(attachmentPayload); attachmentPayload = this.setReplyToInPayload(attachmentPayload);
multipleMessagePayload.push(attachmentPayload); multipleMessagePayload.push(attachmentPayload);
caption = ''; // For WhatsApp, only the first attachment gets a caption
if (!this.isAInstagramChannel) caption = '';
}); });
} else { }
const hasNoAttachments =
!this.attachedFiles || !this.attachedFiles.length;
// For Instagram, we need a separate text message
// For WhatsApp, we only need a text message if there are no attachments
if (
(this.isAInstagramChannel && this.message) ||
(!this.isAInstagramChannel && hasNoAttachments)
) {
let messagePayload = { let messagePayload = {
conversationId: this.currentChat.id, conversationId: this.currentChat.id,
message, message,
@@ -1136,7 +1150,7 @@ export default {
v-else-if="!showRichContentEditor" v-else-if="!showRichContentEditor"
ref="messageInput" ref="messageInput"
v-model="message" v-model="message"
class="input rounded-none" class="rounded-none input"
:placeholder="messagePlaceHolder" :placeholder="messagePlaceHolder"
:min-height="4" :min-height="4"
:signature="signatureToApply" :signature="signatureToApply"

View File

@@ -35,8 +35,7 @@ export default {
}, },
{ key: 'telegram', name: 'Telegram' }, { key: 'telegram', name: 'Telegram' },
{ key: 'line', name: 'Line' }, { key: 'line', name: 'Line' },
// TODO: Add Instagram to the channel list after the feature is ready to use. { key: 'instagram', name: 'Instagram' },
// { key: 'instagram', name: 'Instagram' },
]; ];
}, },
...mapGetters({ ...mapGetters({

View File

@@ -35,6 +35,7 @@
hostURL: '<%= ENV.fetch('FRONTEND_URL', '') %>', hostURL: '<%= ENV.fetch('FRONTEND_URL', '') %>',
helpCenterURL: '<%= ENV.fetch('HELPCENTER_URL', '') %>', helpCenterURL: '<%= ENV.fetch('HELPCENTER_URL', '') %>',
fbAppId: '<%= @global_config['FB_APP_ID'] %>', fbAppId: '<%= @global_config['FB_APP_ID'] %>',
instagramAppId: '<%= @global_config['INSTAGRAM_APP_ID'] %>',
googleOAuthClientId: '<%= ENV.fetch('GOOGLE_OAUTH_CLIENT_ID', nil) %>', googleOAuthClientId: '<%= ENV.fetch('GOOGLE_OAUTH_CLIENT_ID', nil) %>',
googleOAuthCallbackUrl: '<%= ENV.fetch('GOOGLE_OAUTH_CALLBACK_URL', nil) %>', googleOAuthCallbackUrl: '<%= ENV.fetch('GOOGLE_OAUTH_CALLBACK_URL', nil) %>',
fbApiVersion: '<%= @global_config['FACEBOOK_API_VERSION'] %>', fbApiVersion: '<%= @global_config['FACEBOOK_API_VERSION'] %>',

View File

@@ -163,5 +163,5 @@
enabled: false enabled: false
- name: channel_instagram - name: channel_instagram
display_name: Instagram Channel display_name: Instagram Channel
enabled: false enabled: true
chatwoot_internal: true chatwoot_internal: true