chatwoot-develop/app/services/whatsapp/baileys_handlers/helpers.rb

210 lines
6.9 KiB
Ruby
Executable File

module Whatsapp::BaileysHandlers::Helpers # rubocop:disable Metrics/ModuleLength
include Whatsapp::IncomingMessageServiceHelpers
private
def unwrap_ephemeral_message(msg)
msg.key?(:ephemeralMessage) ? msg.dig(:ephemeralMessage, :message) : msg
end
def raw_message_id
@raw_message[:key][:id]
end
def incoming?
!@raw_message[:key][:fromMe]
end
def jid_type # rubocop:disable Metrics/CyclomaticComplexity
jid = @raw_message[:key][:remoteJid]
server = jid.split('@').last
# NOTE: Based on Baileys internal functions
# https://github.com/WhiskeySockets/Baileys/blob/v6.7.16/src/WABinary/jid-utils.ts#L48-L58
case server
when 's.whatsapp.net', 'c.us'
'user'
when 'g.us'
'group'
when 'lid'
'lid'
when 'broadcast'
jid.start_with?('status@') ? 'status' : 'broadcast'
when 'newsletter'
'newsletter'
when 'call'
'call'
else
'unknown'
end
end
def message_type # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity,Metrics/MethodLength,Metrics/AbcSize
msg = unwrap_ephemeral_message(@raw_message[:message])
if msg.key?(:conversation) || msg.dig(:extendedTextMessage, :text).present?
'text'
elsif msg.key?(:imageMessage)
'image'
elsif msg.key?(:audioMessage)
'audio'
elsif msg.key?(:videoMessage)
'video'
elsif msg.key?(:documentMessage) || msg.key?(:documentWithCaptionMessage)
'file'
elsif msg.key?(:stickerMessage)
'sticker'
elsif msg.key?(:reactionMessage)
'reaction'
elsif msg.key?(:editedMessage)
'edited'
elsif msg.key?(:contactMessage)
match_phone_number = msg.dig(:contactMessage, :vcard)&.match(/waid=(\d+)/)
match_phone_number ? 'contact' : 'unsupported'
elsif msg.key?(:protocolMessage)
'protocol'
elsif msg.key?(:messageContextInfo) && msg.keys.count == 1
'context'
else
'unsupported'
end
end
def message_content # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity,Metrics/MethodLength
msg = unwrap_ephemeral_message(@raw_message[:message])
case message_type
when 'text'
msg[:conversation] || msg.dig(:extendedTextMessage, :text)
when 'image'
msg.dig(:imageMessage, :caption)
when 'video'
msg.dig(:videoMessage, :caption)
when 'file'
msg.dig(:documentMessage, :caption).presence ||
msg.dig(:documentWithCaptionMessage, :message, :documentMessage, :caption)
when 'reaction'
msg.dig(:reactionMessage, :text)
when 'contact'
# FIXME: Missing specs
display_name = msg.dig(:contactMessage, :displayName)
vcard = msg.dig(:contactMessage, :vcard)
match_phone_number = vcard&.match(/waid=(\d+)/)
return display_name unless match_phone_number
return match_phone_number[1] if display_name&.start_with?('+')
"#{display_name} - #{match_phone_number[1]}" if match_phone_number
end
end
def reply_to_message_id # rubocop:disable Metrics/CyclomaticComplexity
msg = unwrap_ephemeral_message(@raw_message[:message])
message_key = case message_type
when 'text' then :extendedTextMessage
when 'image' then :imageMessage
when 'sticker' then :stickerMessage
when 'audio' then :audioMessage
when 'video' then :videoMessage
when 'contact' then :contactMessage
when 'file'
context_info = msg.dig(:documentMessage, :contextInfo).presence ||
msg.dig(:documentWithCaptionMessage, :message, :documentMessage, :contextInfo)
return context_info&.dig(:stanzaId)
end
msg.dig(message_key, :contextInfo, :stanzaId) if message_key
end
def file_content_type
return :image if message_type.in?(%w[image sticker])
return :video if message_type.in?(%w[video video_note])
return :audio if message_type == 'audio'
:file
end
def message_mimetype
msg = unwrap_ephemeral_message(@raw_message[:message])
case message_type
when 'image'
msg.dig(:imageMessage, :mimetype)
when 'sticker'
msg.dig(:stickerMessage, :mimetype)
when 'video'
msg.dig(:videoMessage, :mimetype)
when 'audio'
msg.dig(:audioMessage, :mimetype)
when 'file'
msg.dig(:documentMessage, :mimetype).presence ||
msg.dig(:documentWithCaptionMessage, :message, :documentMessage, :mimetype)
end
end
def extract_from_jid(type:)
addressing_mode = @raw_message[:key][:addressingMode]
reference_field = addressing_mode && addressing_mode != type ? :remoteJidAlt : :remoteJid
jid = @raw_message[:key][reference_field]
return unless jid
# NOTE: jid shape is `<user>_<agent>:<device>@<server>`
# https://github.com/WhiskeySockets/Baileys/blob/v7.0.0-rc.6/src/WABinary/jid-utils.ts#L52
jid.split('@').first.split(':').first.split('_').first
end
def contact_name
# NOTE: `verifiedBizName` is only available for business accounts and has a higher priority than `pushName`.
name = @raw_message[:verifiedBizName].presence || @raw_message[:pushName]
return name if name.presence && (self_message? || incoming?)
extract_from_jid(type: 'pn') || extract_from_jid(type: 'lid')
end
def self_message?
normalize_phone_number(extract_from_jid(type: 'pn')) == normalize_phone_number(inbox.channel.phone_number.delete('+'))
end
def normalize_phone_number(phone_number)
return unless phone_number
Whatsapp::PhoneNormalizers::BrazilPhoneNormalizer.new.normalize(phone_number)
end
def ignore_message?
message_type.in?(%w[protocol context edited]) ||
(message_type == 'reaction' && message_content.blank?)
end
def fetch_profile_picture_url(phone_number)
jid = "#{phone_number}@s.whatsapp.net"
response = inbox.channel.provider_service.get_profile_pic(jid)
response&.dig('data', 'profilePictureUrl')
rescue StandardError => e
Rails.logger.error "Failed to fetch profile picture for #{phone_number}: #{e.message}"
nil
end
def try_update_contact_avatar
# TODO: Current logic will never update the contact avatar if their profile picture changes on WhatsApp.
return if @contact.avatar.attached?
phone = extract_from_jid(type: 'pn')
profile_pic_url = fetch_profile_picture_url(phone) if phone
::Avatar::AvatarFromUrlJob.perform_later(@contact, profile_pic_url) if profile_pic_url
end
def message_under_process?
key = format(Redis::RedisKeys::MESSAGE_SOURCE_KEY, id: "#{inbox.id}_#{raw_message_id}")
Redis::Alfred.get(key)
end
def cache_message_source_id_in_redis
key = format(Redis::RedisKeys::MESSAGE_SOURCE_KEY, id: "#{inbox.id}_#{raw_message_id}")
::Redis::Alfred.setex(key, true)
end
def clear_message_source_id_from_redis
key = format(Redis::RedisKeys::MESSAGE_SOURCE_KEY, id: "#{inbox.id}_#{raw_message_id}")
::Redis::Alfred.delete(key)
end
end