## Code Context **Query:** conversation, message, conversation participant, attachment, message template, incoming message service, outgoing message service, conversation status, message type ### Entry Points - **Sms::IncomingMessageService** (class) - app/services/sms/incoming_message_service.rb:1 - **Line::IncomingMessageService** (class) - app/services/line/incoming_message_service.rb:4 - **Twilio::IncomingMessageService** (class) - app/services/twilio/incoming_message_service.rb:1 ### Related Symbols - app/services/sms/incoming_message_service.rb: perform:6, account:23, channel:27, phone_number:31, formatted_phone_number:35, set_contact:39, conversation_params:50, set_conversation:59, contact_attributes:72, attach_files:79 ### Code #### Sms::IncomingMessageService (app/services/sms/incoming_message_service.rb:1) ```ruby class Sms::IncomingMessageService include ::FileTypeHelper pattr_initialize [:inbox!, :params!] def perform set_contact set_conversation @message = @conversation.messages.create!( content: params[:text], account_id: @inbox.account_id, inbox_id: @inbox.id, message_type: :incoming, sender: @contact, source_id: params[:id] ) attach_files @message.save! end private def account @account ||= @inbox.account end def channel @channel ||= @inbox.channel end def phone_number params[:from] end def formatted_phone_number TelephoneNumber.parse(phone_number).international_number end def set_contact contact_inbox = ::ContactInboxWithContactBuilder.new( source_id: params[:from], inbox: @inbox, contact_attributes: contact_attributes ).perform @contact_inbox = contact_inbox @contact = contact_inbox.contact end def conversation_params { account_id: @inbox.account_id, inbox_id: @inbox.id, contact_id: @contact.id, contact_inbox_id: @contact_inbox.id } end def set_conversation # if lock to single conversation is disabled, we will create a new conversation if previous conversation is resolved @conversation = if @inbox.lock_to_single_conversation @contact_inbox.conversations.last else @contact_inbox.conversations.where // ... truncated ... ``` #### Line::IncomingMessageService (app/services/line/incoming_message_service.rb:4) ```ruby class Line::IncomingMessageService include ::FileTypeHelper pattr_initialize [:inbox!, :params!] LINE_STICKER_IMAGE_URL = 'https://stickershop.line-scdn.net/stickershop/v1/sticker/%s/android/sticker.png'.freeze def perform # probably test events return if params[:events].blank? parse_events end private def parse_events params[:events].each do |event| next unless event_type_message?(event) get_line_contact_info(event) next if @line_contact_info['userId'].blank? set_contact set_conversation next unless message_created? event attach_files event['message'] @message.save! end end def message_created?(event) @message = @conversation.messages.build( content: message_content(event), account_id: @inbox.account_id, content_type: message_content_type(event), inbox_id: @inbox.id, message_type: :incoming, sender: @contact, source_id: event['message']['id'].to_s ) @message end def message_content(event) message_type = event.dig('message', 'type') case message_type when 'text' event.dig('message', 'text') when 'sticker' sticker_id = event.dig('message', 'stickerId') sticker_image_url(sticker_id) end end # Currently, Chatwoot doesn't support stickers. As a temporary solution, # we're displaying stickers as images using the sticker ID in markdown format. # This is subject to change in the future // ... truncated ... ``` #### Twilio::IncomingMessageService (app/services/twilio/incoming_message_service.rb:1) ```ruby class Twilio::IncomingMessageService include ::FileTypeHelper include ::Twilio::WhatsappIdentifierHelper pattr_initialize [:params!] def perform return if twilio_channel.blank? set_contact set_conversation @message = @conversation.messages.build( content: message_body, account_id: @inbox.account_id, inbox_id: @inbox.id, message_type: :incoming, sender: @contact, source_id: params[:SmsSid] ) attach_files attach_location if location_message? @message.save! end private def twilio_channel @twilio_channel ||= ::Channel::TwilioSms.find_by(messaging_service_sid: params[:MessagingServiceSid]) if params[:MessagingServiceSid].present? if params[:AccountSid].present? && params[:To].present? @twilio_channel ||= ::Channel::TwilioSms.find_by(account_sid: params[:AccountSid], phone_number: params[:To]) end log_channel_not_found if @twilio_channel.blank? @twilio_channel end def log_channel_not_found Rails.logger.warn( '[TWILIO] Incoming message channel lookup failed ' \ "account_sid=#{params[:AccountSid]} " \ "to=#{params[:To]} " \ "messaging_service_sid=#{params[:MessagingServiceSid]} " \ "sms_sid=#{params[:SmsSid]}" ) end def inbox @inbox ||= twilio_channel.inbox end def account @account ||= inbox.account end # Twilio WhatsApp phone payloads arrive as `whatsap // ... truncated ... ``` #### perform (app/services/sms/incoming_message_service.rb:6) ```ruby def perform set_contact set_conversation @message = @conversation.messages.create!( content: params[:text], account_id: @inbox.account_id, inbox_id: @inbox.id, message_type: :incoming, sender: @contact, source_id: params[:id] ) attach_files @message.save! end ``` #### account (app/services/sms/incoming_message_service.rb:23) ```ruby def account @account ||= @inbox.account end ``` #### channel (app/services/sms/incoming_message_service.rb:27) ```ruby def channel @channel ||= @inbox.channel end ``` #### phone_number (app/services/sms/incoming_message_service.rb:31) ```ruby def phone_number params[:from] end ``` #### formatted_phone_number (app/services/sms/incoming_message_service.rb:35) ```ruby def formatted_phone_number TelephoneNumber.parse(phone_number).international_number end ``` #### set_contact (app/services/sms/incoming_message_service.rb:39) ```ruby def set_contact contact_inbox = ::ContactInboxWithContactBuilder.new( source_id: params[:from], inbox: @inbox, contact_attributes: contact_attributes ).perform @contact_inbox = contact_inbox @contact = contact_inbox.contact end ``` #### conversation_params (app/services/sms/incoming_message_service.rb:50) ```ruby def conversation_params { account_id: @inbox.account_id, inbox_id: @inbox.id, contact_id: @contact.id, contact_inbox_id: @contact_inbox.id } end ``` #### set_conversation (app/services/sms/incoming_message_service.rb:59) ```ruby def set_conversation # if lock to single conversation is disabled, we will create a new conversation if previous conversation is resolved @conversation = if @inbox.lock_to_single_conversation @contact_inbox.conversations.last else @contact_inbox.conversations.where .not(status: :resolved).last end return if @conversation @conversation = ::Conversation.create!(conversation_params) end ``` #### contact_attributes (app/services/sms/incoming_message_service.rb:72) ```ruby def contact_attributes { name: formatted_phone_number, phone_number: phone_number } end ``` #### attach_files (app/services/sms/incoming_message_service.rb:79) ```ruby def attach_files return if params[:media].blank? params[:media].each do |media_url| # we don't need to process this files since chatwoot doesn't support it next if media_url.end_with?('.smil', '.xml') attachment_file = Down.download( media_url, http_basic_authentication: [channel.provider_config['api_key'], channel.provider_config['api_secret']] ) @message.attachments.new( account_id: @message.account_id, file_type: file_type(attachment_file.content_type), file: { io: attachment_file, filename: attachment_file.original_filename, content_type: attachment_file.content_type } ) end end ``` #### process_event_params (app/jobs/webhooks/sms_events_job.rb:17) ```ruby def process_event_params(channel, params) if delivery_event?(params) Sms::DeliveryStatusService.new(channel: channel, params: params[:message].with_indifferent_access).perform else Sms::IncomingMessageService.new(inbox: channel.inbox, params: params[:message].with_indifferent_access).perform end end ``` #### perform (app/services/line/incoming_message_service.rb:9) ```ruby def perform # probably test events return if params[:events].blank? parse_events end ``` #### parse_events (app/services/line/incoming_message_service.rb:18) ```ruby def parse_events params[:events].each do |event| next unless event_type_message?(event) get_line_contact_info(event) next if @line_contact_info['userId'].blank? set_contact set_conversation next unless message_created? event attach_files event['message'] @message.save! end end ``` #### message_created? (app/services/line/incoming_message_service.rb:35) ```ruby def message_created?(event) @message = @conversation.messages.build( content: message_content(event), account_id: @inbox.account_id, content_type: message_content_type(event), inbox_id: @inbox.id, message_type: :incoming, sender: @contact, source_id: event['message']['id'].to_s ) @message end ``` #### message_content (app/services/line/incoming_message_service.rb:48) ```ruby def message_content(event) message_type = event.dig('message', 'type') case message_type when 'text' event.dig('message', 'text') when 'sticker' sticker_id = event.dig('message', 'stickerId') sticker_image_url(sticker_id) end end ``` #### sticker_image_url (app/services/line/incoming_message_service.rb:65) ```ruby def sticker_image_url(sticker_id) "![sticker-#{sticker_id}](#{LINE_STICKER_IMAGE_URL % sticker_id})" end ``` #### message_content_type (app/services/line/incoming_message_service.rb:69) ```ruby def message_content_type(event) return 'sticker' if event['message']['type'] == 'sticker' 'text' end ``` #### attach_files (app/services/line/incoming_message_service.rb:75) ```ruby def attach_files(message) return unless message_type_non_text?(message['type']) response = inbox.channel.client.get_message_content(message['id']) extension = get_file_extension(response) file_name = message['fileName'] || "media-#{message['id']}.#{extension}" temp_file = Tempfile.new(file_name) temp_file.binmode temp_file << response.body temp_file.rewind @message.attachments.new( account_id: @message.account_id, file_type: file_content_type(response), file: { io: temp_file, filename: file_name, content_type: response.content_type } ) end ``` #### get_file_extension (app/services/line/incoming_message_service.rb:98) ```ruby def get_file_extension(response) if response.content_type&.include?('/') response.content_type.split('/')[1] else 'bin' end end ``` #### event_type_message? (app/services/line/incoming_message_service.rb:106) ```ruby def event_type_message?(event) event['type'] == 'message' || event['type'] == 'sticker' end ``` #### message_type_non_text? (app/services/line/incoming_message_service.rb:110) ```ruby def message_type_non_text?(type) [ Line::Bot::Event::MessageType::Video, Line::Bot::Event::MessageType::Audio, Line::Bot::Event::MessageType::Image, Line::Bot::Event::MessageType::File ].include?(type) end ``` #### account (app/services/line/incoming_message_service.rb:119) ```ruby def account @account ||= inbox.account end ``` #### get_line_contact_info (app/services/line/incoming_message_service.rb:123) ```ruby def get_line_contact_info(event) @line_contact_info = JSON.parse(inbox.channel.client.get_profile(event['source']['userId']).body) end ``` #### set_contact (app/services/line/incoming_message_service.rb:127) ```ruby def set_contact contact_inbox = ::ContactInboxWithContactBuilder.new( source_id: @line_contact_info['userId'], inbox: inbox, contact_attributes: contact_attributes ).perform @contact_inbox = contact_inbox @contact = contact_inbox.contact end ``` #### conversation_params (app/services/line/incoming_message_service.rb:138) ```ruby def conversation_params { account_id: @inbox.account_id, inbox_id: @inbox.id, contact_id: @contact.id, contact_inbox_id: @contact_inbox.id } end ``` #### set_conversation (app/services/line/incoming_message_service.rb:147) ```ruby def set_conversation # if lock to single conversation is disabled, we will create a new conversation if previous conversation is resolved @conversation = if @inbox.lock_to_single_conversation @contact_inbox.conversations.last else @contact_inbox.conversations.where.not(status: :resolved).last end return if @conversation @conversation = ::Conversation.create!(conversation_params) end ``` #### contact_attributes (app/services/line/incoming_message_service.rb:159) ```ruby def contact_attributes { name: @line_contact_info['displayName'], avatar_url: @line_contact_info['pictureUrl'], additional_attributes: additional_attributes } end ```