210 lines
6.9 KiB
Ruby
Executable File
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
|