feat: Instagram story replies will display the original story link (#6846)
This commit is contained in:
@@ -70,17 +70,28 @@ class Messages::Instagram::MessageBuilder < Messages::Messenger::MessageBuilder
|
|||||||
@messaging[:message][:text]
|
@messaging[:message][:text]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def story_reply_attributes
|
||||||
|
message[:reply_to][:story] if message[:reply_to].present? && message[:reply_to][:story].present?
|
||||||
|
end
|
||||||
|
|
||||||
def build_message
|
def build_message
|
||||||
return if @outgoing_echo && already_sent_from_chatwoot?
|
return if @outgoing_echo && already_sent_from_chatwoot?
|
||||||
return if message_content.blank? && all_unsupported_files?
|
return if message_content.blank? && all_unsupported_files?
|
||||||
|
|
||||||
@message = conversation.messages.create!(message_params)
|
@message = conversation.messages.create!(message_params)
|
||||||
|
save_story_id
|
||||||
|
|
||||||
attachments.each do |attachment|
|
attachments.each do |attachment|
|
||||||
process_attachment(attachment)
|
process_attachment(attachment)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def save_story_id
|
||||||
|
return if story_reply_attributes.blank?
|
||||||
|
|
||||||
|
@message.save_story_info(story_reply_attributes)
|
||||||
|
end
|
||||||
|
|
||||||
def build_conversation
|
def build_conversation
|
||||||
@contact_inbox ||= contact.contact_inboxes.find_by!(source_id: message_source_id)
|
@contact_inbox ||= contact.contact_inboxes.find_by!(source_id: message_source_id)
|
||||||
Conversation.create!(conversation_params.merge(
|
Conversation.create!(conversation_params.merge(
|
||||||
|
|||||||
@@ -22,6 +22,14 @@
|
|||||||
:bcc="emailHeadAttributes.bcc"
|
:bcc="emailHeadAttributes.bcc"
|
||||||
:is-incoming="isIncoming"
|
:is-incoming="isIncoming"
|
||||||
/>
|
/>
|
||||||
|
<blockquote v-if="storyReply" class="story-reply-quote">
|
||||||
|
<span>{{ $t('CONVERSATION.REPLIED_TO_STORY') }}</span>
|
||||||
|
<bubble-image
|
||||||
|
v-if="!hasStoryError"
|
||||||
|
:url="storyUrl"
|
||||||
|
@error="onStoryLoadError"
|
||||||
|
/>
|
||||||
|
</blockquote>
|
||||||
<bubble-text
|
<bubble-text
|
||||||
v-if="data.content"
|
v-if="data.content"
|
||||||
:message="message"
|
:message="message"
|
||||||
@@ -79,7 +87,7 @@
|
|||||||
:sender="data.sender"
|
:sender="data.sender"
|
||||||
:story-sender="storySender"
|
:story-sender="storySender"
|
||||||
:external-error="externalError"
|
:external-error="externalError"
|
||||||
:story-id="storyId"
|
:story-id="`${storyId}`"
|
||||||
:is-a-tweet="isATweet"
|
:is-a-tweet="isATweet"
|
||||||
:is-a-whatsapp-channel="isAWhatsAppChannel"
|
:is-a-whatsapp-channel="isAWhatsAppChannel"
|
||||||
:has-instagram-story="hasInstagramStory"
|
:has-instagram-story="hasInstagramStory"
|
||||||
@@ -192,6 +200,7 @@ export default {
|
|||||||
hasImageError: false,
|
hasImageError: false,
|
||||||
contextMenuPosition: {},
|
contextMenuPosition: {},
|
||||||
showBackgroundHighlight: false,
|
showBackgroundHighlight: false,
|
||||||
|
hasStoryError: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -277,6 +286,12 @@ export default {
|
|||||||
storyId() {
|
storyId() {
|
||||||
return this.contentAttributes.story_id || null;
|
return this.contentAttributes.story_id || null;
|
||||||
},
|
},
|
||||||
|
storyUrl() {
|
||||||
|
return this.contentAttributes.story_url || null;
|
||||||
|
},
|
||||||
|
storyReply() {
|
||||||
|
return this.storyUrl && this.hasInstagramStory;
|
||||||
|
},
|
||||||
contentType() {
|
contentType() {
|
||||||
const {
|
const {
|
||||||
data: { content_type: contentType },
|
data: { content_type: contentType },
|
||||||
@@ -414,10 +429,12 @@ export default {
|
|||||||
watch: {
|
watch: {
|
||||||
data() {
|
data() {
|
||||||
this.hasImageError = false;
|
this.hasImageError = false;
|
||||||
|
this.hasStoryError = false;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.hasImageError = false;
|
this.hasImageError = false;
|
||||||
|
this.hasStoryError = false;
|
||||||
bus.$on(BUS_EVENTS.ON_MESSAGE_LIST_SCROLL, this.closeContextMenu);
|
bus.$on(BUS_EVENTS.ON_MESSAGE_LIST_SCROLL, this.closeContextMenu);
|
||||||
this.setupHighlightTimer();
|
this.setupHighlightTimer();
|
||||||
},
|
},
|
||||||
@@ -432,6 +449,9 @@ export default {
|
|||||||
const { file_type: fileType } = attachments[0];
|
const { file_type: fileType } = attachments[0];
|
||||||
return fileType === type && !this.hasImageError;
|
return fileType === type && !this.hasImageError;
|
||||||
}
|
}
|
||||||
|
if (this.storyReply) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
handleContextMenuClick() {
|
handleContextMenuClick() {
|
||||||
@@ -443,6 +463,9 @@ export default {
|
|||||||
onImageLoadError() {
|
onImageLoadError() {
|
||||||
this.hasImageError = true;
|
this.hasImageError = true;
|
||||||
},
|
},
|
||||||
|
onStoryLoadError() {
|
||||||
|
this.hasStoryError = true;
|
||||||
|
},
|
||||||
openContextMenu(e) {
|
openContextMenu(e) {
|
||||||
const shouldSkipContextMenu =
|
const shouldSkipContextMenu =
|
||||||
e.target?.classList.contains('skip-context-menu') ||
|
e.target?.classList.contains('skip-context-menu') ||
|
||||||
@@ -672,7 +695,6 @@ li.right {
|
|||||||
blockquote {
|
blockquote {
|
||||||
border-left: var(--space-micro) solid var(--s-75);
|
border-left: var(--space-micro) solid var(--s-75);
|
||||||
color: var(--s-800);
|
color: var(--s-800);
|
||||||
padding: var(--space-smaller) var(--space-small);
|
|
||||||
margin: var(--space-smaller) 0;
|
margin: var(--space-smaller) 0;
|
||||||
padding: var(--space-small) var(--space-small) 0 var(--space-normal);
|
padding: var(--space-small) var(--space-small) 0 var(--space-normal);
|
||||||
}
|
}
|
||||||
@@ -704,4 +726,11 @@ li.right {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.story-reply-quote {
|
||||||
|
border-left: var(--space-micro) solid var(--s-75);
|
||||||
|
color: var(--s-600);
|
||||||
|
margin: var(--space-small) var(--space-normal) 0;
|
||||||
|
padding: var(--space-small) var(--space-small) 0 var(--space-small);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ export default {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
const { storySender, storyId } = this;
|
const { storySender, storyId } = this;
|
||||||
return `https://www.instagram.com/stories/${storySender}/${storyId}`;
|
return `https://www.instagram.com/stories/direct/${storySender}_${storyId}`;
|
||||||
},
|
},
|
||||||
showStatusIndicators() {
|
showStatusIndicators() {
|
||||||
if ((this.isOutgoing || this.isTemplate) && !this.isPrivate) {
|
if ((this.isOutgoing || this.isTemplate) && !this.isPrivate) {
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
"UNKNOWN_FILE_TYPE": "Unknown File",
|
"UNKNOWN_FILE_TYPE": "Unknown File",
|
||||||
"SAVE_CONTACT": "Save",
|
"SAVE_CONTACT": "Save",
|
||||||
"UPLOADING_ATTACHMENTS": "Uploading attachments...",
|
"UPLOADING_ATTACHMENTS": "Uploading attachments...",
|
||||||
|
"REPLIED_TO_STORY": "Replied to your story",
|
||||||
"SUCCESS_DELETE_MESSAGE": "Message deleted successfully",
|
"SUCCESS_DELETE_MESSAGE": "Message deleted successfully",
|
||||||
"FAIL_DELETE_MESSSAGE": "Couldn't delete message! Try again",
|
"FAIL_DELETE_MESSSAGE": "Couldn't delete message! Try again",
|
||||||
"NO_RESPONSE": "No response",
|
"NO_RESPONSE": "No response",
|
||||||
|
|||||||
@@ -198,6 +198,17 @@ class Message < ApplicationRecord
|
|||||||
outgoing? && human_response? && not_created_by_automation? && !private?
|
outgoing? && human_response? && not_created_by_automation? && !private?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def save_story_info(story_info)
|
||||||
|
self.content_attributes = content_attributes.merge(
|
||||||
|
{
|
||||||
|
story_id: story_info['id'],
|
||||||
|
story_sender: inbox.channel.instagram_id,
|
||||||
|
story_url: story_info['url']
|
||||||
|
}
|
||||||
|
)
|
||||||
|
save!
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def ensure_content_type
|
def ensure_content_type
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ describe ::Messages::Instagram::MessageBuilder do
|
|||||||
let!(:instagram_inbox) { create(:inbox, channel: instagram_channel, account: account, greeting_enabled: false) }
|
let!(:instagram_inbox) { create(:inbox, channel: instagram_channel, account: account, greeting_enabled: false) }
|
||||||
let!(:dm_params) { build(:instagram_message_create_event).with_indifferent_access }
|
let!(:dm_params) { build(:instagram_message_create_event).with_indifferent_access }
|
||||||
let!(:story_mention_params) { build(:instagram_story_mention_event).with_indifferent_access }
|
let!(:story_mention_params) { build(:instagram_story_mention_event).with_indifferent_access }
|
||||||
|
let!(:instagram_story_reply_event) { build(:instagram_story_reply_event).with_indifferent_access }
|
||||||
let(:fb_object) { double }
|
let(:fb_object) { double }
|
||||||
let(:contact) { create(:contact, id: 'Sender-id-1', name: 'Jane Dae') }
|
let(:contact) { create(:contact, id: 'Sender-id-1', name: 'Jane Dae') }
|
||||||
let(:contact_inbox) { create(:contact_inbox, contact_id: contact.id, inbox_id: instagram_inbox.id, source_id: 'Sender-id-1') }
|
let(:contact_inbox) { create(:contact_inbox, contact_id: contact.id, inbox_id: instagram_inbox.id, source_id: 'Sender-id-1') }
|
||||||
@@ -44,6 +45,29 @@ describe ::Messages::Instagram::MessageBuilder do
|
|||||||
expect(message.content).to eq('This is the first message from the customer')
|
expect(message.content).to eq('This is the first message from the customer')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'creates message with for reply with story id' do
|
||||||
|
allow(Koala::Facebook::API).to receive(:new).and_return(fb_object)
|
||||||
|
allow(fb_object).to receive(:get_object).and_return(
|
||||||
|
{
|
||||||
|
name: 'Jane',
|
||||||
|
id: 'Sender-id-1',
|
||||||
|
account_id: instagram_inbox.account_id,
|
||||||
|
profile_pic: 'https://chatwoot-assets.local/sample.png'
|
||||||
|
}.with_indifferent_access
|
||||||
|
)
|
||||||
|
messaging = instagram_story_reply_event[:entry][0]['messaging'][0]
|
||||||
|
contact_inbox
|
||||||
|
|
||||||
|
described_class.new(messaging, instagram_inbox).perform
|
||||||
|
|
||||||
|
message = instagram_channel.inbox.messages.first
|
||||||
|
|
||||||
|
expect(message.content).to eq('This is the story reply')
|
||||||
|
expect(message.content_attributes[:story_sender]).to eq(instagram_inbox.channel.instagram_id)
|
||||||
|
expect(message.content_attributes[:story_id]).to eq('chatwoot-app-user-id-1')
|
||||||
|
expect(message.content_attributes[:story_url]).to eq('https://chatwoot-assets.local/sample.png')
|
||||||
|
end
|
||||||
|
|
||||||
it 'raises exception on deleted story' do
|
it 'raises exception on deleted story' do
|
||||||
allow(Koala::Facebook::API).to receive(:new).and_return(fb_object)
|
allow(Koala::Facebook::API).to receive(:new).and_return(fb_object)
|
||||||
allow(fb_object).to receive(:get_object).and_raise(Koala::Facebook::ClientError.new(
|
allow(fb_object).to receive(:get_object).and_raise(Koala::Facebook::ClientError.new(
|
||||||
|
|||||||
@@ -26,6 +26,39 @@ FactoryBot.define do
|
|||||||
initialize_with { attributes }
|
initialize_with { attributes }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
factory :instagram_story_reply_event, class: Hash do
|
||||||
|
entry do
|
||||||
|
[
|
||||||
|
{
|
||||||
|
'id': 'instagram-message-id-123',
|
||||||
|
'time': '2021-09-08T06:34:04+0000',
|
||||||
|
'messaging': [
|
||||||
|
{
|
||||||
|
'sender': {
|
||||||
|
'id': 'Sender-id-1'
|
||||||
|
},
|
||||||
|
'recipient': {
|
||||||
|
'id': 'chatwoot-app-user-id-1'
|
||||||
|
},
|
||||||
|
'timestamp': '2021-09-08T06:34:04+0000',
|
||||||
|
'message': {
|
||||||
|
'mid': 'message-id-1',
|
||||||
|
'text': 'This is the story reply',
|
||||||
|
'reply_to': {
|
||||||
|
'story': {
|
||||||
|
'id': 'chatwoot-app-user-id-1',
|
||||||
|
'url': 'https://chatwoot-assets.local/sample.png'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
initialize_with { attributes }
|
||||||
|
end
|
||||||
|
|
||||||
factory :instagram_test_text_event, class: Hash do
|
factory :instagram_test_text_event, class: Hash do
|
||||||
entry do
|
entry do
|
||||||
[
|
[
|
||||||
|
|||||||
Reference in New Issue
Block a user