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:
@@ -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', ''),
|
||||||
|
|||||||
@@ -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 [
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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'] %>',
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user