feat: mark recorded audio messages (#36)

* feat: mark recorded audio

* refactor: cleaner logic for message builder metadata

* test: message builder specs

* test: baileys service specs

* fix: correct method call for checking attachment filename in process_metadata
This commit is contained in:
Gabriel Jablonski 2025-05-02 19:51:43 -03:00 committed by GitHub
parent a52a4cf8ce
commit b764f3997d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 101 additions and 32 deletions

View File

@ -9,6 +9,7 @@ class Messages::MessageBuilder
@user = user
@message_type = params[:message_type] || 'outgoing'
@attachments = params[:attachments]
@is_recorded_audio = params[:is_recorded_audio]
@automation_rule = content_attributes&.dig(:automation_rule_id)
return unless params.instance_of?(ActionController::Parameters)
@ -67,7 +68,7 @@ class Messages::MessageBuilder
account_id: @message.account_id,
file: uploaded_attachment
)
attachment.meta = process_metadata(uploaded_attachment)
attachment.file_type = if uploaded_attachment.is_a?(String)
file_type_by_signed_id(
uploaded_attachment
@ -78,6 +79,10 @@ class Messages::MessageBuilder
end
end
def process_metadata(attachment)
{ is_recorded_audio: true } if @is_recorded_audio && attachment.original_filename.in?(@is_recorded_audio)
end
def process_emails
return unless @conversation.inbox&.inbox_type == 'Email'

View File

@ -8,6 +8,7 @@ export const buildCreatePayload = ({
contentAttributes,
echoId,
files,
isRecordedAudio,
ccEmails = '',
bccEmails = '',
toEmails = '',
@ -22,6 +23,7 @@ export const buildCreatePayload = ({
files.forEach(file => {
payload.append('attachments[]', file);
});
payload.append('is_recorded_audio', JSON.stringify(isRecordedAudio));
payload.append('private', isPrivate);
payload.append('echo_id', echoId);
payload.append('cc_emails', ccEmails);
@ -60,6 +62,7 @@ class MessageApi extends ApiClient {
contentAttributes,
echo_id: echoId,
files,
isRecordedAudio,
ccEmails = '',
bccEmails = '',
toEmails = '',
@ -74,6 +77,7 @@ class MessageApi extends ApiClient {
contentAttributes,
echoId,
files,
isRecordedAudio,
ccEmails,
bccEmails,
toEmails,

View File

@ -1012,11 +1012,17 @@ export default {
if (this.attachedFiles && this.attachedFiles.length) {
messagePayload.files = [];
messagePayload.isRecordedAudio = [];
this.attachedFiles.forEach(attachment => {
if (this.globalConfig.directUploadsEnabled) {
messagePayload.files.push(attachment.blobSignedId);
} else {
messagePayload.files.push(attachment.resource.file);
if (attachment.isRecordedAudio) {
messagePayload.isRecordedAudio.push(
attachment.resource.file.name
);
}
}
});
}

View File

@ -86,7 +86,7 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
}
end
def attachment_message_content
def attachment_message_content # rubocop:disable Metrics/MethodLength
attachment = @message.attachments.first
buffer = Base64.strict_encode64(attachment.file.download)
@ -99,6 +99,7 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
content[:image] = buffer
when 'audio'
content[:audio] = buffer
content[:ptt] = attachment.meta['is_recorded_audio']
when 'file'
content[:document] = buffer
when 'sticker'

View File

@ -134,6 +134,14 @@ describe Messages::MessageBuilder do
expect(message.attachments.first.file_type).to eq 'image'
end
it 'creates attachment with is_recorded_audio metadata' do
params[:is_recorded_audio] = ['avatar.png']
message = message_builder
expect(message.attachments.first.meta).to eq({ 'is_recorded_audio' => true })
end
context 'when DIRECT_UPLOAD_ENABLED' do
let(:params) do
ActionController::Parameters.new({

View File

@ -92,6 +92,8 @@ describe Whatsapp::Providers::WhatsappBaileysService do
end
describe '#send_message' do
let(:request_path) { "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}/send-message" }
context 'when message does not have content nor attachments' do
it 'updates the message with content attribute is_unsupported' do
message.update!(content: nil)
@ -107,7 +109,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
let(:base64_image) { 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=' }
before do
message.attachments.new(
message.attachments.create!(
account_id: message.account_id,
file_type: 'image',
file: {
@ -115,11 +117,10 @@ describe Whatsapp::Providers::WhatsappBaileysService do
filename: 'image.png'
}
)
message.save!
end
it 'sends the attachment message' do
stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}/send-message")
stub_request(:post, request_path)
.with(
headers: stub_headers(whatsapp_channel),
body: {
@ -140,7 +141,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
it 'omits caption if message content is empty' do
message.update!(content: nil)
stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}/send-message")
stub_request(:post, request_path)
.with(
headers: stub_headers(whatsapp_channel),
body: {
@ -158,26 +159,31 @@ describe Whatsapp::Providers::WhatsappBaileysService do
expect(result).to eq('msg_123')
end
context 'when request is unsuccessful' do
it 'raises MessageNotSentError' do
stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}/send-message")
.to_return(
status: 400,
headers: { 'Content-Type' => 'application/json' },
body: { 'data' => { 'key' => { 'id' => 'msg_123' } } }.to_json
)
expect do
service.send_message(test_send_phone_number, message)
end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::MessageNotSentError)
end
end
end
context 'when message is a text' do
it 'sends the message' do
stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}/send-message")
context 'when message is an audio file' do
let(:base64_audio) { 'UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQAAAAA=' }
before do
message.attachments.create!(
account_id: message.account_id,
file_type: 'audio',
file: {
io: StringIO.new(Base64.decode64(base64_audio)),
filename: 'audio.wav'
}
)
end
it 'sends the audio message' do
stub_request(:post, request_path)
.with(
headers: stub_headers(whatsapp_channel),
body: {
jid: test_send_jid,
messageContent: { fileName: 'audio.wav', caption: message.content, audio: base64_audio }
}.to_json
)
.to_return(
status: 200,
headers: { 'Content-Type' => 'application/json' },
@ -189,17 +195,41 @@ describe Whatsapp::Providers::WhatsappBaileysService do
expect(result).to eq('msg_123')
end
it 'raises MessageNotSentError' do
stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}/send-message")
it 'sends message with ptt true if message is recorded audio' do
message.attachments.first.update!(meta: { is_recorded_audio: true })
stub_request(:post, request_path)
.with(
headers: stub_headers(whatsapp_channel),
body: {
jid: test_send_jid,
messageContent: { fileName: 'audio.wav', caption: message.content, audio: base64_audio, ptt: true }
}.to_json
)
.to_return(
status: 400,
status: 200,
headers: { 'Content-Type' => 'application/json' },
body: { 'data' => { 'key' => { 'id' => 'msg_123' } } }.to_json
)
expect do
service.send_message(test_send_phone_number, message)
end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::MessageNotSentError)
result = service.send_message(test_send_phone_number, message)
expect(result).to eq('msg_123')
end
end
context 'when message is a text' do
it 'sends the message' do
stub_request(:post, request_path)
.to_return(
status: 200,
headers: { 'Content-Type' => 'application/json' },
body: { 'data' => { 'key' => { 'id' => 'msg_123' } } }.to_json
)
result = service.send_message(test_send_phone_number, message)
expect(result).to eq('msg_123')
end
end
@ -216,7 +246,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
message = create(:message, inbox: inbox, conversation: conversation, sender: account_user, message_type: 'outgoing', source_id: 'msg_123')
reaction = create(:message, inbox: inbox, conversation: conversation, sender: account_user, content: '👍',
content_attributes: { is_reaction: true, in_reply_to: message.id })
stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}/send-message")
stub_request(:post, request_path)
.with(
headers: stub_headers(whatsapp_channel),
body: {
@ -242,7 +272,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
message = create(:message, inbox: inbox, conversation: conversation, sender: contact, source_id: 'msg_123')
reaction = create(:message, inbox: inbox, conversation: conversation, sender: account_user, content: '👍',
content_attributes: { is_reaction: true, in_reply_to: message.id })
stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}/send-message")
stub_request(:post, request_path)
.with(
headers: stub_headers(whatsapp_channel),
body: {
@ -264,6 +294,21 @@ describe Whatsapp::Providers::WhatsappBaileysService do
expect(result).to eq('reaction_123')
end
end
context 'when request is unsuccessful' do
it 'raises MessageNotSentError' do
stub_request(:post, request_path)
.to_return(
status: 400,
headers: { 'Content-Type' => 'application/json' },
body: { 'data' => { 'key' => { 'id' => 'msg_123' } } }.to_json
)
expect do
service.send_message(test_send_phone_number, message)
end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::MessageNotSentError)
end
end
end
describe '#api_headers' do