feat(wuzapi): dispatch interactive messages (buttons/list/url_button)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7a203ccb6d
commit
23a17599c4
@ -1,5 +1,6 @@
|
||||
require_relative 'base_service'
|
||||
|
||||
# rubocop:disable Metrics/ClassLength
|
||||
class Whatsapp::Providers::WuzapiService < Whatsapp::Providers::BaseService
|
||||
attr_reader :whatsapp_channel
|
||||
|
||||
@ -52,6 +53,24 @@ class Whatsapp::Providers::WuzapiService < Whatsapp::Providers::BaseService
|
||||
response
|
||||
end
|
||||
|
||||
# Dispatches an interactive message (buttons / list / url_button).
|
||||
# Called by the lifecycle dispatcher when a rule has message_type != 'text'.
|
||||
def send_interactive_message(phone_number, payload)
|
||||
normalized_phone = normalize_phone(phone_number)
|
||||
user_token = @whatsapp_channel.wuzapi_user_token
|
||||
|
||||
case payload['type'].to_s
|
||||
when 'quick_reply', 'buttons'
|
||||
dispatch_buttons(user_token, normalized_phone, payload)
|
||||
when 'url_button'
|
||||
dispatch_url_button(user_token, normalized_phone, payload)
|
||||
when 'list'
|
||||
dispatch_list(user_token, normalized_phone, payload)
|
||||
else
|
||||
raise ArgumentError, "unsupported interactive type: #{payload['type'].inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
def send_template(_phone_number, _template_info)
|
||||
# Placeholder for template support if Wuzapi supports it.
|
||||
# For now, just logging or no-op as per initial text-focused plan.
|
||||
@ -104,6 +123,30 @@ class Whatsapp::Providers::WuzapiService < Whatsapp::Providers::BaseService
|
||||
|
||||
private
|
||||
|
||||
def dispatch_buttons(user_token, phone, payload)
|
||||
buttons = Array(payload['buttons']).map { |b| { text: b['text'] || b[:text] } }
|
||||
client.send_buttons(user_token, phone, payload['body'].to_s, buttons)
|
||||
end
|
||||
|
||||
def dispatch_url_button(user_token, phone, payload)
|
||||
button = payload['button'] || {}
|
||||
client.send_url_button(
|
||||
user_token, phone,
|
||||
text: payload['body'].to_s,
|
||||
button_text: button['text'].to_s,
|
||||
url: button['url'].to_s
|
||||
)
|
||||
end
|
||||
|
||||
def dispatch_list(user_token, phone, payload)
|
||||
client.send_list(
|
||||
user_token, phone,
|
||||
text: payload['body'].to_s,
|
||||
button_text: payload['button_text'].to_s,
|
||||
sections: payload['sections'] || []
|
||||
)
|
||||
end
|
||||
|
||||
def normalize_phone(phone_number)
|
||||
phone_number.gsub(/[+\s\-()]/, '')
|
||||
end
|
||||
@ -202,3 +245,4 @@ class Whatsapp::Providers::WuzapiService < Whatsapp::Providers::BaseService
|
||||
target_message.outgoing? || target_message.template?
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/ClassLength
|
||||
|
||||
54
spec/services/whatsapp/providers/wuzapi_interactive_spec.rb
Normal file
54
spec/services/whatsapp/providers/wuzapi_interactive_spec.rb
Normal file
@ -0,0 +1,54 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
# rubocop:disable RSpec/SpecFilePathFormat
|
||||
RSpec.describe Whatsapp::Providers::WuzapiService, '#send_interactive_message' do
|
||||
let(:channel) do
|
||||
create(:channel_whatsapp, provider: 'wuzapi', validate_provider_config: false, sync_templates: false,
|
||||
provider_config: { 'wuzapi_base_url' => 'http://localhost:8080' })
|
||||
end
|
||||
let(:service) { described_class.new(whatsapp_channel: channel) }
|
||||
let(:phone) { '+5561999999999' }
|
||||
let(:wuzapi_client) { instance_double(Wuzapi::Client) }
|
||||
|
||||
before do
|
||||
allow(Wuzapi::Client).to receive(:new).and_return(wuzapi_client)
|
||||
allow(channel).to receive(:wuzapi_user_token).and_return('tok')
|
||||
end
|
||||
|
||||
describe '#send_interactive_message' do
|
||||
it 'dispatches quick_reply buttons' do
|
||||
payload = { 'type' => 'quick_reply', 'body' => 'Curtiu?',
|
||||
'buttons' => [{ 'id' => 'yes', 'text' => 'Sim' }] }
|
||||
expect(wuzapi_client).to receive(:send_buttons)
|
||||
.with('tok', '5561999999999', 'Curtiu?', [{ text: 'Sim' }])
|
||||
.and_return({ 'Id' => 'm-1' })
|
||||
|
||||
service.send_interactive_message(phone, payload)
|
||||
end
|
||||
|
||||
it 'dispatches url_button' do
|
||||
payload = { 'type' => 'url_button', 'body' => 'Avalie',
|
||||
'button' => { 'text' => 'Abrir', 'url' => 'https://g.page/r/1' } }
|
||||
expect(wuzapi_client).to receive(:send_url_button)
|
||||
.with('tok', '5561999999999', text: 'Avalie', button_text: 'Abrir', url: 'https://g.page/r/1')
|
||||
.and_return({ 'Id' => 'm-2' })
|
||||
|
||||
service.send_interactive_message(phone, payload)
|
||||
end
|
||||
|
||||
it 'dispatches list' do
|
||||
payload = { 'type' => 'list', 'body' => 'Cardápio', 'button_text' => 'Ver',
|
||||
'sections' => [{ 'title' => 'Bebidas', 'rows' => [{ 'title' => 'Água', 'row_id' => 'a1' }] }] }
|
||||
expect(wuzapi_client).to receive(:send_list).and_return({ 'Id' => 'm-3' })
|
||||
service.send_interactive_message(phone, payload)
|
||||
end
|
||||
|
||||
it 'raises for unknown type' do
|
||||
expect { service.send_interactive_message(phone, 'type' => 'xyz') }
|
||||
.to raise_error(ArgumentError, /unsupported interactive type/)
|
||||
end
|
||||
end
|
||||
end
|
||||
# rubocop:enable RSpec/SpecFilePathFormat
|
||||
Loading…
Reference in New Issue
Block a user