Restructure omni services and add Chatwoot research snapshot
This commit is contained in:
171
research/chatwoot/app/services/line/incoming_message_service.rb
Normal file
171
research/chatwoot/app/services/line/incoming_message_service.rb
Normal file
@@ -0,0 +1,171 @@
|
||||
# ref : https://developers.line.biz/en/docs/messaging-api/receiving-messages/#webhook-event-types
|
||||
# https://developers.line.biz/en/reference/messaging-api/#message-event
|
||||
|
||||
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. We've chosen not to download and display the sticker as an image because the sticker's information
|
||||
# and images are the property of the creator or legal owner. We aim to avoid storing it on our server without their consent.
|
||||
# If there are any permission or rendering issues, the URL may break, and we'll display the sticker ID as text instead.
|
||||
# Ref: https://developers.line.biz/en/reference/messaging-api/#wh-sticker
|
||||
def sticker_image_url(sticker_id)
|
||||
""
|
||||
end
|
||||
|
||||
def message_content_type(event)
|
||||
return 'sticker' if event['message']['type'] == 'sticker'
|
||||
|
||||
'text'
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
def get_file_extension(response)
|
||||
if response.content_type&.include?('/')
|
||||
response.content_type.split('/')[1]
|
||||
else
|
||||
'bin'
|
||||
end
|
||||
end
|
||||
|
||||
def event_type_message?(event)
|
||||
event['type'] == 'message' || event['type'] == 'sticker'
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
def account
|
||||
@account ||= inbox.account
|
||||
end
|
||||
|
||||
def get_line_contact_info(event)
|
||||
@line_contact_info = JSON.parse(inbox.channel.client.get_profile(event['source']['userId']).body)
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
@conversation = @contact_inbox.conversations.first
|
||||
return if @conversation
|
||||
|
||||
@conversation = ::Conversation.create!(conversation_params)
|
||||
end
|
||||
|
||||
def contact_attributes
|
||||
{
|
||||
name: @line_contact_info['displayName'],
|
||||
avatar_url: @line_contact_info['pictureUrl'],
|
||||
additional_attributes: additional_attributes
|
||||
}
|
||||
end
|
||||
|
||||
def additional_attributes
|
||||
{
|
||||
social_line_user_id: @line_contact_info['userId']
|
||||
}
|
||||
end
|
||||
|
||||
def file_content_type(file_content)
|
||||
file_type(file_content.content_type)
|
||||
end
|
||||
end
|
||||
113
research/chatwoot/app/services/line/send_on_line_service.rb
Normal file
113
research/chatwoot/app/services/line/send_on_line_service.rb
Normal file
@@ -0,0 +1,113 @@
|
||||
class Line::SendOnLineService < Base::SendOnChannelService
|
||||
private
|
||||
|
||||
def channel_class
|
||||
Channel::Line
|
||||
end
|
||||
|
||||
def perform_reply
|
||||
response = channel.client.push_message(message.conversation.contact_inbox.source_id, build_payload)
|
||||
|
||||
return if response.blank?
|
||||
|
||||
parsed_json = JSON.parse(response.body)
|
||||
|
||||
if response.code == '200'
|
||||
# If the request is successful, update the message status to delivered
|
||||
Messages::StatusUpdateService.new(message, 'delivered').perform
|
||||
else
|
||||
# If the request is not successful, update the message status to failed and save the external error
|
||||
Messages::StatusUpdateService.new(message, 'failed', external_error(parsed_json)).perform
|
||||
end
|
||||
end
|
||||
|
||||
def build_payload
|
||||
if message.content_type == 'input_select' && message.content_attributes['items'].any?
|
||||
build_input_select_payload
|
||||
else
|
||||
build_text_payload
|
||||
end
|
||||
end
|
||||
|
||||
def build_text_payload
|
||||
if message.content && message.attachments.any?
|
||||
[text_message, *attachments]
|
||||
elsif message.content.nil? && message.attachments.any?
|
||||
attachments
|
||||
else
|
||||
text_message
|
||||
end
|
||||
end
|
||||
|
||||
def attachments
|
||||
message.attachments.map do |attachment|
|
||||
# Support only image and video for now, https://developers.line.biz/en/reference/messaging-api/#image-message
|
||||
next unless attachment.file_type == 'image' || attachment.file_type == 'video'
|
||||
|
||||
{
|
||||
type: attachment.file_type,
|
||||
originalContentUrl: attachment.download_url,
|
||||
previewImageUrl: attachment.download_url
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# https://developers.line.biz/en/reference/messaging-api/#text-message
|
||||
def text_message
|
||||
{
|
||||
type: 'text',
|
||||
text: message.outgoing_content
|
||||
}
|
||||
end
|
||||
|
||||
# https://developers.line.biz/en/reference/messaging-api/#flex-message
|
||||
def build_input_select_payload
|
||||
{
|
||||
type: 'flex',
|
||||
altText: message.content,
|
||||
contents: {
|
||||
type: 'bubble',
|
||||
body: {
|
||||
type: 'box',
|
||||
layout: 'vertical',
|
||||
contents: [
|
||||
{
|
||||
type: 'text',
|
||||
text: message.content,
|
||||
wrap: true
|
||||
},
|
||||
*input_select_to_button
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def input_select_to_button
|
||||
message.content_attributes['items'].map do |item|
|
||||
{
|
||||
type: 'button',
|
||||
style: 'link',
|
||||
height: 'sm',
|
||||
action: {
|
||||
type: 'message',
|
||||
label: item['title'],
|
||||
text: item['value']
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# https://developers.line.biz/en/reference/messaging-api/#error-responses
|
||||
def external_error(error)
|
||||
# Message containing information about the error. See https://developers.line.biz/en/reference/messaging-api/#error-messages
|
||||
message = error['message']
|
||||
# An array of error details. If the array is empty, this property will not be included in the response.
|
||||
details = error['details']
|
||||
|
||||
return message if details.blank?
|
||||
|
||||
detail_messages = details.map { |detail| "#{detail['property']}: #{detail['message']}" }
|
||||
[message, detail_messages].join(', ')
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user