chatwoot-develop/app/services/whatsapp/providers/whatsapp_zapi_service.rb

283 lines
7.5 KiB
Ruby
Executable File

class Whatsapp::Providers::WhatsappZapiService < Whatsapp::Providers::BaseService # rubocop:disable Metrics/ClassLength
class ProviderUnavailableError < StandardError; end
API_BASE_PATH = 'https://api.z-api.io'.freeze
def send_template(phone_number, template_info); end
def sync_templates; end
def send_message(phone, message)
phone = phone.delete('+')
params = message.content_attributes[:zapi_args].presence || {}
params[:messageId] = message.in_reply_to_external_id if message.in_reply_to_external_id.present?
if message.content_attributes[:is_reaction]
send_reaction_message(phone, message, **params)
elsif message.attachments.present?
handle_message_with_attachment(message, phone, **params)
elsif message.outgoing_content.present?
send_text_message(phone, message, **params)
else
message.update!(is_unsupported: true)
nil
end
end
def validate_provider_config?
response = HTTParty.get(
"#{api_instance_path_with_token}/status",
headers: api_headers
)
process_response(response)
end
def setup_channel_provider
response = HTTParty.put(
"#{api_instance_path_with_token}/update-every-webhooks",
headers: api_headers,
body: {
value: whatsapp_channel.inbox.callback_webhook_url,
notifySentByMe: true
}.to_json
)
raise ProviderUnavailableError unless process_response(response)
if whatsapp_channel.provider_connection.blank? || whatsapp_channel.provider_connection['connection'] == 'close'
Channels::Whatsapp::ZapiQrCodeJob.perform_later(whatsapp_channel)
end
true
end
def disconnect_channel_provider
response = HTTParty.get(
"#{api_instance_path_with_token}/disconnect",
headers: api_headers
)
raise ProviderUnavailableError unless process_response(response)
true
end
def qr_code_image
response = HTTParty.get(
"#{api_instance_path_with_token}/qr-code/image",
headers: api_headers
)
if response.parsed_response['connected']
whatsapp_channel.update_provider_connection!(connection: 'open')
return
end
return unless process_response(response)
response.parsed_response['value']
end
def read_messages(messages, recipient_id:, **)
phone = recipient_id.delete('+')
messages.each do |message|
next if message.source_id.blank?
Channels::Whatsapp::ZapiReadMessageJob.perform_later(whatsapp_channel, phone, message.source_id)
end
true
end
def send_read_message(phone, message_source_id)
response = HTTParty.post(
"#{api_instance_path_with_token}/read-message",
headers: api_headers,
body: {
phone: phone,
messageId: message_source_id
}.to_json
)
process_response(response)
end
def on_whatsapp(phone_number)
response = HTTParty.get(
"#{api_instance_path_with_token}/phone-exists/#{phone_number.delete('+')}",
headers: api_headers
)
raise ProviderUnavailableError unless process_response(response)
response.parsed_response || { 'exists' => false, 'phone' => nil, 'lid' => nil }
end
private
def api_instance_path
"#{API_BASE_PATH}/instances/#{whatsapp_channel.provider_config['instance_id']}"
end
def api_instance_path_with_token
"#{api_instance_path}/token/#{whatsapp_channel.provider_config['token']}"
end
def api_headers
{ 'Content-Type' => 'application/json', 'Client-Token' => whatsapp_channel.provider_config['client_token'] }
end
def process_response(response)
Rails.logger.error response.body unless response.success?
response.success?
end
def send_text_message(phone, message, **params)
response = HTTParty.post(
"#{api_instance_path_with_token}/send-text",
headers: api_headers,
body: {
phone: phone,
message: message.outgoing_content,
**params
}.compact.to_json
)
unless process_response(response)
message.update!(status: :failed, external_error: response.parsed_response&.dig('error'))
raise ProviderUnavailableError
end
response.parsed_response&.dig('messageId')
end
def handle_message_with_attachment(message, phone, **params)
attachment = message.attachments.first
if attachment.file.byte_size > max_size(attachment)
message.update!(status: :failed, external_error: 'File too large')
return
end
base64_data = attachment_to_base64(attachment)
buffer = "data:#{attachment.file.content_type};base64,#{base64_data}"
case attachment.file_type
when 'image'
send_image_message(phone, message, buffer, **params)
when 'audio'
send_audio_message(phone, message, buffer, **params)
when 'file'
send_document_message(phone, message, attachment, buffer, **params)
when 'video'
send_video_message(phone, message, buffer, **params)
end
end
def max_size(attachment)
case attachment.file_type
when 'image'
5.megabytes
when 'audio', 'video'
16.megabytes
else
100.megabytes
end
end
def send_image_message(phone, message, buffer, **params)
response = HTTParty.post(
"#{api_instance_path_with_token}/send-image",
headers: api_headers,
body: {
phone: phone,
image: buffer,
caption: message.outgoing_content,
**params
}.compact.to_json
)
raise ProviderUnavailableError unless process_response(response)
response.parsed_response&.dig('messageId')
end
def send_audio_message(phone, _message, buffer, **params)
response = HTTParty.post(
"#{api_instance_path_with_token}/send-audio",
headers: api_headers,
body: {
phone: phone,
audio: buffer,
waveform: true,
**params
}.compact.to_json
)
raise ProviderUnavailableError unless process_response(response)
response.parsed_response&.dig('messageId')
end
def send_document_message(phone, message, attachment, buffer, **params)
file_extension = File.extname(attachment.file.filename.to_s).delete('.')
if file_extension.blank?
Rails.logger.warn "Missing file extension for attachment: #{attachment.id}"
file_extension = 'bin'
end
response = HTTParty.post(
"#{api_instance_path_with_token}/send-document/#{file_extension}",
headers: api_headers,
body: {
phone: phone,
document: buffer,
fileName: attachment.file.filename.to_s,
caption: message.outgoing_content,
**params
}.compact.to_json
)
raise ProviderUnavailableError unless process_response(response)
response.parsed_response&.dig('messageId')
end
def send_video_message(phone, message, buffer, **params)
response = HTTParty.post(
"#{api_instance_path_with_token}/send-video",
headers: api_headers,
body: {
phone: phone,
video: buffer,
caption: message.outgoing_content,
**params
}.compact.to_json
)
raise ProviderUnavailableError unless process_response(response)
response.parsed_response&.dig('messageId')
end
def send_reaction_message(phone, message, **params)
response = HTTParty.post(
"#{api_instance_path_with_token}/send-reaction",
headers: api_headers,
body: {
phone: phone,
reaction: message.outgoing_content,
messageId: message.in_reply_to_external_id,
**params
}.compact.to_json
)
raise ProviderUnavailableError unless process_response(response)
response.parsed_response&.dig('messageId')
end
end