mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-30 18:47:51 +00:00 
			
		
		
		
	Bug: Fix duplicate messages from Twitter DM and tweets (#599)
* Bug: Fix duplicate messages from Twitter DM and tweets
This commit is contained in:
		| @@ -1,5 +1,7 @@ | ||||
| class Twitter::CallbacksController < Twitter::BaseController | ||||
|   def show | ||||
|     return redirect_to app_new_twitter_inbox_url if permitted_params[:denied] | ||||
|  | ||||
|     @response = twitter_client.access_token( | ||||
|       oauth_token: permitted_params[:oauth_token], | ||||
|       oauth_verifier: permitted_params[:oauth_verifier] | ||||
| @@ -46,6 +48,6 @@ class Twitter::CallbacksController < Twitter::BaseController | ||||
|   end | ||||
|  | ||||
|   def permitted_params | ||||
|     params.permit(:oauth_token, :oauth_verifier) | ||||
|     params.permit(:oauth_token, :oauth_verifier, :denied) | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -33,14 +33,18 @@ | ||||
|     <div class="reply-box__bottom"> | ||||
|       <ul class="tabs"> | ||||
|         <li class="tabs-title" :class="{ 'is-active': !isPrivate }"> | ||||
|           <a href="#" @click="makeReply">Reply</a> | ||||
|           <a href="#" @click="makeReply">{{ | ||||
|             $t('CONVERSATION.REPLYBOX.REPLY') | ||||
|           }}</a> | ||||
|         </li> | ||||
|         <li class="tabs-title is-private" :class="{ 'is-active': isPrivate }"> | ||||
|           <a href="#" @click="makePrivate">Private Note</a> | ||||
|           <a href="#" @click="makePrivate">{{ | ||||
|             $t('CONVERSATION.REPLYBOX.PRIVATE_NOTE') | ||||
|           }}</a> | ||||
|         </li> | ||||
|         <li v-if="message.length" class="tabs-title message-length"> | ||||
|           <a :class="{ 'message-error': message.length > 620 }"> | ||||
|             {{ message.length }} / 640 | ||||
|           <a :class="{ 'message-error': message.length > maxLength - 40 }"> | ||||
|             {{ message.length }} / {{ maxLength }} | ||||
|           </a> | ||||
|         </li> | ||||
|       </ul> | ||||
| @@ -49,16 +53,12 @@ | ||||
|         class="button send-button" | ||||
|         :disabled="disableButton()" | ||||
|         :class="{ | ||||
|           disabled: message.length === 0 || message.length > 640, | ||||
|           disabled: message.length === 0 || message.length > maxLength, | ||||
|           warning: isPrivate, | ||||
|         }" | ||||
|         @click="sendMessage" | ||||
|       > | ||||
|         {{ | ||||
|           isPrivate | ||||
|             ? $t('CONVERSATION.REPLYBOX.CREATE') | ||||
|             : $t('CONVERSATION.REPLYBOX.SEND') | ||||
|         }} | ||||
|         {{ replyButtonLabel }} | ||||
|         <i | ||||
|           class="icon" | ||||
|           :class="{ | ||||
| @@ -82,6 +82,10 @@ import EmojiInput from '../emoji/EmojiInput'; | ||||
| import CannedResponse from './CannedResponse'; | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     EmojiInput, | ||||
|     CannedResponse, | ||||
|   }, | ||||
|   mixins: [clickaway], | ||||
|   data() { | ||||
|     return { | ||||
| @@ -103,10 +107,32 @@ export default { | ||||
|       } = this.currentChat; | ||||
|       return channel; | ||||
|     }, | ||||
|     conversationType() { | ||||
|       const { | ||||
|         additional_attributes: additionalAttributes = {}, | ||||
|       } = this.currentChat; | ||||
|       return additionalAttributes.type || ''; | ||||
|     }, | ||||
|     maxLength() { | ||||
|       if (this.channelType === 'Channel::FacebookPage') { | ||||
|         return 640; | ||||
|       } | ||||
|       if (this.channelType === 'Channel::TwitterProfile') { | ||||
|         if (this.conversationType === 'tweet') { | ||||
|           return 280; | ||||
|         } | ||||
|       } | ||||
|       return 10000; | ||||
|     }, | ||||
|     replyButtonLabel() { | ||||
|       if (this.isPrivate) { | ||||
|         return this.$t('CONVERSATION.REPLYBOX.CREATE'); | ||||
|       } | ||||
|       if (this.conversationType === 'tweet') { | ||||
|         return this.$t('CONVERSATION.REPLYBOX.TWEET'); | ||||
|       } | ||||
|       return this.$t('CONVERSATION.REPLYBOX.SEND'); | ||||
|     }, | ||||
|   components: { | ||||
|     EmojiInput, | ||||
|     CannedResponse, | ||||
|   }, | ||||
|   watch: { | ||||
|     message(val) { | ||||
|   | ||||
| @@ -21,8 +21,11 @@ | ||||
|       "PRIVATE_MSG_INPUT": "Shift + enter for new line. This will be visible only to Agents" | ||||
|     }, | ||||
|     "REPLYBOX": { | ||||
|       "REPLY": "Reply", | ||||
|       "PRIVATE_NOTE": "Private Note", | ||||
|       "SEND": "Send", | ||||
|       "CREATE": "Add Note" | ||||
|       "CREATE": "Add Note", | ||||
|       "TWEET": "Tweet" | ||||
|     }, | ||||
|     "VISIBLE_TO_AGENTS": "Private Note: Only visible to you and your team", | ||||
|     "CHANGE_STATUS": "Conversation status changed", | ||||
|   | ||||
| @@ -39,6 +39,9 @@ | ||||
|                 <span v-if="item.channel_type === 'Channel::WebWidget'"> | ||||
|                   Website | ||||
|                 </span> | ||||
|                 <span v-if="item.channel_type === 'Channel::TwitterProfile'"> | ||||
|                   Twitter | ||||
|                 </span> | ||||
|               </td> | ||||
|  | ||||
|               <!-- Action Buttons --> | ||||
|   | ||||
| @@ -6,6 +6,7 @@ class Conversations::EventDataPresenter < SimpleDelegator | ||||
|   def push_data | ||||
|     { | ||||
|       id: display_id, | ||||
|       additional_attributes: additional_attributes, | ||||
|       inbox_id: inbox_id, | ||||
|       messages: push_messages, | ||||
|       meta: push_meta, | ||||
|   | ||||
| @@ -11,7 +11,9 @@ class Twitter::DirectMessageParserService < Twitter::WebhooksBaseService | ||||
|       content: message_create_data['message_data']['text'], | ||||
|       account_id: @inbox.account_id, | ||||
|       inbox_id: @inbox.id, | ||||
|       message_type: outgoing_message? ? :outgoing : :incoming | ||||
|       message_type: outgoing_message? ? :outgoing : :incoming, | ||||
|       contact_id: @contact.id, | ||||
|       source_id: direct_message_data['id'] | ||||
|     ) | ||||
|   end | ||||
|  | ||||
|   | ||||
| @@ -39,10 +39,16 @@ class Twitter::SendReplyService | ||||
|   end | ||||
|  | ||||
|   def send_tweet_reply | ||||
|     twitter_client.send_tweet_reply( | ||||
|     response = twitter_client.send_tweet_reply( | ||||
|       reply_to_tweet_id: conversation.additional_attributes['tweet_id'], | ||||
|       tweet: screen_name + message.content | ||||
|     ) | ||||
|     if response.status == '200' | ||||
|       tweet_data = response.body | ||||
|       message.update!(source_id: tweet_data['id_str']) | ||||
|     else | ||||
|       Rails.logger 'TWITTER_TWEET_REPLY_ERROR' + response.body | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def send_reply | ||||
|   | ||||
| @@ -3,16 +3,9 @@ class Twitter::TweetParserService < Twitter::WebhooksBaseService | ||||
|  | ||||
|   def perform | ||||
|     set_inbox | ||||
|     find_or_create_contact(user) | ||||
|     set_conversation | ||||
|     @conversation.messages.create( | ||||
|       account_id: @inbox.account_id, | ||||
|       contact_id: @contact.id, | ||||
|       content: tweet_text, | ||||
|       inbox_id: @inbox.id, | ||||
|       message_type: message_type, | ||||
|       source_id: tweet_data['id'].to_s | ||||
|     ) | ||||
|     return if message_already_exist? | ||||
|  | ||||
|     create_message | ||||
|   end | ||||
|  | ||||
|   private | ||||
| @@ -37,6 +30,10 @@ class Twitter::TweetParserService < Twitter::WebhooksBaseService | ||||
|     tweet_data['user'] | ||||
|   end | ||||
|  | ||||
|   def tweet_id | ||||
|     tweet_data['id'].to_s | ||||
|   end | ||||
|  | ||||
|   def parent_tweet_id | ||||
|     tweet_data['in_reply_to_status_id_str'].nil? ? tweet_data['id'].to_s : tweet_data['in_reply_to_status_id_str'] | ||||
|   end | ||||
| @@ -66,4 +63,21 @@ class Twitter::TweetParserService < Twitter::WebhooksBaseService | ||||
|  | ||||
|     @conversation = ::Conversation.create!(conversation_params) | ||||
|   end | ||||
|  | ||||
|   def message_already_exist? | ||||
|     @inbox.messages.find_by(source_id: tweet_id) | ||||
|   end | ||||
|  | ||||
|   def create_message | ||||
|     find_or_create_contact(user) | ||||
|     set_conversation | ||||
|     @conversation.messages.create( | ||||
|       account_id: @inbox.account_id, | ||||
|       contact_id: @contact.id, | ||||
|       content: tweet_text, | ||||
|       inbox_id: @inbox.id, | ||||
|       message_type: message_type, | ||||
|       source_id: tweet_id | ||||
|     ) | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -1,26 +1,5 @@ | ||||
| json.payload do | ||||
|   json.array! @conversations do |conversation| | ||||
|     json.meta do | ||||
|       json.sender do | ||||
|         json.id conversation.contact.id | ||||
|         json.name conversation.contact.name | ||||
|         json.thumbnail conversation.contact.avatar_url | ||||
|         json.channel conversation.inbox.try(:channel_type) | ||||
|       end | ||||
|       json.assignee conversation.assignee | ||||
|     end | ||||
|  | ||||
|     json.id conversation.display_id | ||||
|     if conversation.unread_incoming_messages.count.zero? | ||||
|       json.messages [conversation.messages.last.try(:push_event_data)] | ||||
|     else | ||||
|       json.messages conversation.unread_messages.map(&:push_event_data) | ||||
|     end | ||||
|     json.inbox_id conversation.inbox_id | ||||
|     json.status conversation.status | ||||
|     json.timestamp conversation.messages.last.try(:created_at).try(:to_i) | ||||
|     json.user_last_seen_at conversation.user_last_seen_at.to_i | ||||
|     json.agent_last_seen_at conversation.agent_last_seen_at.to_i | ||||
|     json.unread_count conversation.unread_incoming_messages.count | ||||
|     json.partial! 'api/v1/conversations/partials/conversation.json.jbuilder', conversation: conversation | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -4,31 +4,9 @@ json.data do | ||||
|     json.unassigned_count @conversations_count[:unassigned_count] | ||||
|     json.all_count @conversations_count[:all_count] | ||||
|   end | ||||
|  | ||||
|   json.payload do | ||||
|     json.array! @conversations do |conversation| | ||||
|       json.meta do | ||||
|         json.sender do | ||||
|           json.id conversation.contact.id | ||||
|           json.name conversation.contact.name | ||||
|           json.thumbnail conversation.contact.avatar_url | ||||
|           json.channel conversation.inbox.try(:channel_type) | ||||
|         end | ||||
|         json.assignee conversation.assignee | ||||
|       end | ||||
|  | ||||
|       json.id conversation.display_id | ||||
|       if conversation.unread_incoming_messages.count.zero? | ||||
|         json.messages [conversation.messages.last.try(:push_event_data)] | ||||
|       else | ||||
|         json.messages conversation.unread_messages.map(&:push_event_data) | ||||
|       end | ||||
|       json.inbox_id conversation.inbox_id | ||||
|       json.status conversation.status | ||||
|       json.timestamp conversation.messages.last.try(:created_at).try(:to_i) | ||||
|       json.user_last_seen_at conversation.user_last_seen_at.to_i | ||||
|       json.agent_last_seen_at conversation.agent_last_seen_at.to_i | ||||
|       json.unread_count conversation.unread_incoming_messages.count | ||||
|       json.partial! 'api/v1/conversations/partials/conversation.json.jbuilder', conversation: conversation | ||||
|     end | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -0,0 +1,24 @@ | ||||
| json.meta do | ||||
|   json.sender do | ||||
|     json.id conversation.contact.id | ||||
|     json.name conversation.contact.name | ||||
|     json.thumbnail conversation.contact.avatar_url | ||||
|     json.channel conversation.inbox.try(:channel_type) | ||||
|   end | ||||
|   json.assignee conversation.assignee | ||||
| end | ||||
|  | ||||
| json.id conversation.display_id | ||||
| if conversation.unread_incoming_messages.count.zero? | ||||
|   json.messages [conversation.messages.last.try(:push_event_data)] | ||||
| else | ||||
|   json.messages conversation.unread_messages.map(&:push_event_data) | ||||
| end | ||||
|  | ||||
| json.inbox_id conversation.inbox_id | ||||
| json.status conversation.status | ||||
| json.timestamp conversation.messages.last.try(:created_at).try(:to_i) | ||||
| json.user_last_seen_at conversation.user_last_seen_at.to_i | ||||
| json.agent_last_seen_at conversation.agent_last_seen_at.to_i | ||||
| json.unread_count conversation.unread_incoming_messages.count | ||||
| json.additional_attributes conversation.additional_attributes | ||||
| @@ -226,6 +226,7 @@ RSpec.describe Conversation, type: :model do | ||||
|     let(:conversation) { create(:conversation) } | ||||
|     let(:expected_data) do | ||||
|       { | ||||
|         additional_attributes: nil, | ||||
|         meta: { | ||||
|           sender: conversation.contact.push_event_data, | ||||
|           assignee: conversation.assignee | ||||
|   | ||||
| @@ -13,6 +13,7 @@ RSpec.describe Conversations::EventDataPresenter do | ||||
|   describe '#push_data' do | ||||
|     let(:expected_data) do | ||||
|       { | ||||
|         additional_attributes: nil, | ||||
|         meta: { | ||||
|           sender: conversation.contact.push_event_data, | ||||
|           assignee: conversation.assignee | ||||
|   | ||||
| @@ -4,6 +4,7 @@ describe Twitter::SendReplyService do | ||||
|   subject(:send_reply_service) { described_class.new(message: message) } | ||||
|  | ||||
|   let(:twitter_client) { instance_double(::Twitty::Facade) } | ||||
|   let(:twitter_response) { instance_double(::Twitty::Response) } | ||||
|   let(:account) { create(:account) } | ||||
|   let(:widget_inbox) { create(:inbox, account: account) } | ||||
|   let(:twitter_channel) { create(:channel_twitter_profile, account: account) } | ||||
| @@ -32,7 +33,9 @@ describe Twitter::SendReplyService do | ||||
|   before do | ||||
|     allow(::Twitty::Facade).to receive(:new).and_return(twitter_client) | ||||
|     allow(twitter_client).to receive(:send_direct_message).and_return(true) | ||||
|     allow(twitter_client).to receive(:send_tweet_reply).and_return(true) | ||||
|     allow(twitter_client).to receive(:send_tweet_reply).and_return(twitter_response) | ||||
|     allow(twitter_response).to receive(:status).and_return('200') | ||||
|     allow(twitter_response).to receive(:body).and_return(JSON.parse({ id_str: '12345' }.to_json)) | ||||
|   end | ||||
|  | ||||
|   describe '#perform' do | ||||
| @@ -61,14 +64,15 @@ describe Twitter::SendReplyService do | ||||
|     context 'with reply' do | ||||
|       it 'if conversation is a direct message' do | ||||
|         create(:message, message_type: :incoming, inbox: twitter_inbox, account: account, conversation: dm_conversation) | ||||
|         create(:message, message_type: 'outgoing', inbox: twitter_inbox, account: account, conversation: dm_conversation) | ||||
|         create(:message, message_type: :outgoing, inbox: twitter_inbox, account: account, conversation: dm_conversation) | ||||
|         expect(twitter_client).to have_received(:send_direct_message) | ||||
|       end | ||||
|  | ||||
|       it 'if conversation is a tweet' do | ||||
|         create(:message, message_type: :incoming, inbox: twitter_inbox, account: account, conversation: tweet_conversation) | ||||
|         create(:message, message_type: 'outgoing', inbox: twitter_inbox, account: account, conversation: tweet_conversation) | ||||
|         tweet = create(:message, message_type: :outgoing, inbox: twitter_inbox, account: account, conversation: tweet_conversation) | ||||
|         expect(twitter_client).to have_received(:send_tweet_reply) | ||||
|         expect(tweet.reload.source_id).to eq '12345' | ||||
|       end | ||||
|     end | ||||
|   end | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Sojan Jose
					Sojan Jose