Implantação da geração de mensagem de forma livre e mudança na engine de mensagem
This commit is contained in:
98
app/services/notificacao/cliente_whatsapp.rb
Normal file
98
app/services/notificacao/cliente_whatsapp.rb
Normal file
@@ -0,0 +1,98 @@
|
||||
# app/services/notificacao/cliente_whatsapp.rb
|
||||
#
|
||||
# Cliente da ponte Baileys (container `whatsapp`). Substitui o Twilio como canal
|
||||
# padrão de WhatsApp.
|
||||
#
|
||||
# ⚠️ Canal NÃO OFICIAL: a ponte fala o protocolo do WhatsApp Web por engenharia
|
||||
# reversa. É gratuito e ilimitado, mas está fora dos Termos do WhatsApp e a
|
||||
# Meta pode banir o número. Por isso: chip dedicado, e o intervalo entre
|
||||
# mensagens é respeitado por quem chama (Notificacao::Despachante), não aqui.
|
||||
#
|
||||
# TIMEOUT em tudo: sem ele, uma ponte pendurada trava o worker Puma até o proxy
|
||||
# cortar — o mesmo motivo que levou o ClienteTwilio a configurar timeout.
|
||||
module Notificacao
|
||||
class ClienteWhatsapp
|
||||
TIMEOUT_ABERTURA = 5
|
||||
TIMEOUT_LEITURA = 20
|
||||
|
||||
Resposta = Struct.new(:ok, :dados, :erro, keyword_init: true) do
|
||||
def ok? = ok
|
||||
end
|
||||
|
||||
def self.padrao
|
||||
config = ConfiguracaoNotificacao.instancia
|
||||
new(url: config.baileys_url_efetiva, token: config.baileys_token_efetivo)
|
||||
end
|
||||
|
||||
def initialize(url:, token:)
|
||||
@url = url.to_s.strip.chomp('/')
|
||||
@token = token.to_s
|
||||
end
|
||||
|
||||
def configurado?
|
||||
@url.present? && @token.present?
|
||||
end
|
||||
|
||||
# { conectado:, numero:, qr:, desde:, ultimo_erro: }
|
||||
def status
|
||||
get('/status')
|
||||
end
|
||||
|
||||
def enviar(para:, texto:)
|
||||
post('/enviar', para: para, texto: texto)
|
||||
end
|
||||
|
||||
def desconectar
|
||||
post('/logout')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get(caminho)
|
||||
requisitar(Net::HTTP::Get, caminho)
|
||||
end
|
||||
|
||||
def post(caminho, **corpo)
|
||||
requisitar(Net::HTTP::Post, caminho, corpo)
|
||||
end
|
||||
|
||||
def requisitar(classe, caminho, corpo = nil)
|
||||
return Resposta.new(ok: false, erro: 'Ponte do WhatsApp não configurada.') unless configurado?
|
||||
|
||||
uri = URI.join("#{@url}/", caminho.delete_prefix('/'))
|
||||
req = classe.new(uri)
|
||||
req['Authorization'] = "Bearer #{@token}"
|
||||
req['Content-Type'] = 'application/json'
|
||||
req.body = corpo.to_json if corpo
|
||||
|
||||
resposta = Net::HTTP.start(uri.hostname, uri.port,
|
||||
use_ssl: uri.scheme == 'https',
|
||||
open_timeout: TIMEOUT_ABERTURA,
|
||||
read_timeout: TIMEOUT_LEITURA) { |http| http.request(req) }
|
||||
|
||||
dados = parse(resposta.body)
|
||||
if resposta.is_a?(Net::HTTPSuccess)
|
||||
Resposta.new(ok: true, dados: dados)
|
||||
else
|
||||
Resposta.new(ok: false, dados: dados, erro: mensagem_de_erro(resposta, dados))
|
||||
end
|
||||
rescue StandardError => e
|
||||
# A ponte fora do ar não pode virar 500 na tela nem quebrar um fechamento.
|
||||
Rails.logger.error("[ClienteWhatsapp] #{e.class}: #{e.message}")
|
||||
Resposta.new(ok: false, erro: "#{e.class}: #{e.message}")
|
||||
end
|
||||
|
||||
def parse(corpo)
|
||||
JSON.parse(corpo.to_s)
|
||||
rescue JSON::ParserError
|
||||
{}
|
||||
end
|
||||
|
||||
def mensagem_de_erro(resposta, dados)
|
||||
return 'Token da ponte do WhatsApp inválido.' if resposta.code == '401'
|
||||
return 'WhatsApp desconectado — leia o QR code na tela.' if resposta.code == '503'
|
||||
|
||||
dados['erro'].presence || "HTTP #{resposta.code}"
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user