Evolution API Ruby Client
Uma gem Ruby elegante e poderosa para consumir a Evolution API, permitindo integração fácil com WhatsApp através de uma API REST simples e robusta.
🚀 Características
- ✅ Interface Ruby Nativa: API limpa e intuitiva
- ✅ Suporte Completo: Todos os endpoints da Evolution API
- ✅ Tratamento de Erros: Exceções personalizadas e informativas
- ✅ Configuração Flexível: Sistema de configuração robusto
- ✅ Retry Automático: Tentativas automáticas em caso de falha
- ✅ Validação: Validação de dados com Dry::Validation
- ✅ Documentação Completa: YARD documentation
- ✅ Testes Abrangentes: RSpec com VCR para testes confiáveis
📦 Instalação
Adicione a gem ao seu Gemfile:
gem 'evolution_api'
E execute:
bundle install
Ou instale diretamente:
gem install evolution_api
🚂 Integração com Rails
Instalação em Projetos Rails
- Adicione a gem ao seu Gemfile:
# Gemfile
gem 'evolution_api'
- Execute o bundle install:
bundle install
- Configure a gem no initializer:
# config/initializers/evolution_api.rb
EvolutionApi.configure do |config|
config.base_url = Rails.application.credentials.evolution_api[:base_url] || "http://localhost:8080"
config.api_key = Rails.application.credentials.evolution_api[:api_key]
config.timeout = 30
config.retry_attempts = 3
config.retry_delay = 1
end
- Configure as credenciais (Rails 5.2+):
rails credentials:edit
Adicione no arquivo de credenciais:
evolution_api:
base_url: "https://sua-evolution-api.com"
api_key: "sua_api_key_aqui"
Exemplo de Controller Rails
# app/controllers/whatsapp_controller.rb
class WhatsAppController < ApplicationController
before_action :set_client
def
begin
response = @client.(
params[:instance_name],
params[:phone_number],
params[:message]
)
render json: { success: true, data: response }
rescue EvolutionApi::Error => e
render json: { success: false, error: e. }, status: :unprocessable_entity
end
end
def list_instances
instances = @client.list_instances
render json: { instances: instances }
end
def create_instance
response = @client.create_instance(params[:instance_name], {
qrcode: true,
webhook: webhook_url
})
render json: { success: true, data: response }
end
private
def set_client
@client = EvolutionApi.client
end
def webhook_url
"#{request.base_url}/webhooks/whatsapp"
end
end
Exemplo de Model Rails
# app/models/whatsapp_message.rb
class WhatsAppMessage < ApplicationRecord
validates :instance_name, presence: true
validates :phone_number, presence: true
validates :message_type, presence: true, inclusion: { in: %w[text image audio video document] }
validates :content, presence: true
after_create :send_to_whatsapp
private
def send_to_whatsapp
client = EvolutionApi.client
case
when 'text'
client.(instance_name, phone_number, content)
when 'image'
client.(instance_name, phone_number, content, caption)
when 'audio'
client.(instance_name, phone_number, content)
when 'video'
client.(instance_name, phone_number, content, caption)
when 'document'
client.(instance_name, phone_number, content, caption)
end
rescue EvolutionApi::Error => e
update(status: 'failed', error_message: e.)
end
end
Exemplo de Service Object
# app/services/whatsapp_service.rb
class WhatsAppService
def initialize(instance_name = nil)
@client = EvolutionApi.client
@instance_name = instance_name || Rails.application.credentials.evolution_api[:default_instance]
end
def (phone_numbers, )
results = []
phone_numbers.each do |phone|
begin
response = @client.(@instance_name, phone, )
results << { phone: phone, success: true, response: response }
rescue EvolutionApi::Error => e
results << { phone: phone, success: false, error: e. }
end
end
results
end
def (, = {})
contacts = @client.get_contacts(@instance_name)
contacts.each do |contact|
next if [:exclude_numbers]&.include?(contact['id'])
@client.(@instance_name, contact['id'], )
sleep([:delay] || 1) # Evita rate limiting
end
end
def instance_status
@client.get_instance(@instance_name)
end
def is_connected?
status = instance_status
status['status'] == 'open'
end
end
Exemplo de Job para Processamento Assíncrono
# app/jobs/whatsapp_message_job.rb
class WhatsAppMessageJob < ApplicationJob
queue_as :whatsapp
def perform(instance_name, phone_number, , = 'text')
client = EvolutionApi.client
case
when 'text'
client.(instance_name, phone_number, )
when 'image'
client.(instance_name, phone_number, [:url], [:caption])
when 'audio'
client.(instance_name, phone_number, [:url])
when 'video'
client.(instance_name, phone_number, [:url], [:caption])
when 'document'
client.(instance_name, phone_number, [:url], [:caption])
end
rescue EvolutionApi::Error => e
Rails.logger.error "WhatsApp message failed: #{e.message}"
raise e
end
end
Exemplo de Webhook Controller
# app/controllers/webhooks/whatsapp_controller.rb
class Webhooks::WhatsappController < ApplicationController
skip_before_action :verify_authenticity_token
def receive
case params[:event]
when 'connection.update'
handle_connection_update
when 'message.upsert'
when 'qr.update'
handle_qr_update
end
head :ok
end
private
def handle_connection_update
instance_name = params[:instance]
status = params[:data][:status]
Rails.logger.info "WhatsApp instance #{instance_name} status: #{status}"
# Atualizar status no banco de dados
instance = WhatsAppInstance.find_by(name: instance_name)
instance&.update(status: status)
end
def
= params[:data]
instance_name = params[:instance]
# Processar mensagem recebida
= Message.create!(
instance_name: instance_name,
phone_number: [:key][:remoteJid],
message_type: ([:message]),
content: ([:message]),
from_me: [:key][:fromMe],
timestamp: Time.at([:messageTimestamp])
)
# Processar automaticamente se necessário
AutoReplyService.new().process if should_auto_reply?()
end
def handle_qr_update
instance_name = params[:instance]
qr_code = params[:data][:qrcode]
# Salvar QR code para exibição
Rails.cache.write("whatsapp_qr_#{instance_name}", qr_code, expires_in: 2.minutes)
end
def ()
return 'text' if [:conversation] || [:extendedTextMessage]
return 'image' if [:imageMessage]
return 'audio' if [:audioMessage]
return 'video' if [:videoMessage]
return 'document' if [:documentMessage]
return 'location' if [:locationMessage]
return 'contact' if [:contactMessage]
'unknown'
end
def ()
return [:conversation] if [:conversation]
return [:extendedTextMessage][:text] if [:extendedTextMessage]
return [:imageMessage][:url] if [:imageMessage]
return [:audioMessage][:url] if [:audioMessage]
return [:videoMessage][:url] if [:videoMessage]
return [:documentMessage][:url] if [:documentMessage]
nil
end
def should_auto_reply?()
!.from_me && . == 'text'
end
end
Configuração de Rotas
# config/routes.rb
Rails.application.routes.draw do
# Rotas para WhatsApp
resources :whatsapp, only: [:index] do
collection do
post :send_message
get :list_instances
post :create_instance
get :qr_code/:instance_name, action: :qr_code, as: :qr_code
end
end
# Webhook para receber mensagens
post 'webhooks/whatsapp', to: 'webhooks/whatsapp#receive'
end
Exemplo de View
<!-- app/views/whatsapp/index.html.erb -->
<div class="whatsapp-dashboard">
<h1>WhatsApp Dashboard</h1>
<div class="instances">
<h2>Instâncias</h2>
<div id="instances-list">
<!-- Será preenchido via JavaScript -->
</div>
<button onclick="createInstance()">Nova Instância</button>
</div>
<div class="qr-code" id="qr-code">
<!-- QR Code será exibido aqui -->
</div>
<div class="send-message">
<h2>Enviar Mensagem</h2>
<form id="message-form">
<select name="instance_name" required>
<option value="">Selecione uma instância</option>
</select>
<input type="tel" name="phone_number" placeholder="Número (ex: 5511999999999)" required>
<textarea name="message" placeholder="Mensagem" required></textarea>
<button type="submit">Enviar</button>
</form>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
loadInstances();
setupMessageForm();
});
function loadInstances() {
fetch('/whatsapp/list_instances')
.then(response => response.json())
.then(data => {
const instancesList = document.getElementById('instances-list');
const instanceSelect = document.querySelector('select[name="instance_name"]');
data.instances.forEach(instance => {
// Atualizar lista de instâncias
instancesList.innerHTML += `
<div class="instance">
<strong>${instance.instance}</strong>
<span class="status ${instance.status}">${instance.status}</span>
</div>
`;
// Atualizar select
instanceSelect.innerHTML += `
<option value="${instance.instance}">${instance.instance} (${instance.status})</option>
`;
});
});
}
function setupMessageForm() {
document.getElementById('message-form').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
fetch('/whatsapp/send_message', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Mensagem enviada com sucesso!');
this.reset();
} else {
alert('Erro ao enviar mensagem: ' + data.error);
}
});
});
}
function createInstance() {
const instanceName = prompt('Nome da instância:');
if (!instanceName) return;
fetch('/whatsapp/create_instance', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': document.querySelector('[name="csrf-token"]').content
},
body: JSON.stringify({ instance_name: instanceName })
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Instância criada! Verifique o QR Code.');
loadInstances();
} else {
alert('Erro ao criar instância: ' + data.error);
}
});
}
</script>
⚙️ Configuração
Configuração Básica
require 'evolution_api'
EvolutionApi.configure do |config|
config.base_url = "http://localhost:8080" # URL da sua Evolution API
config.api_key = "sua_api_key_aqui" # Sua chave API (opcional)
config.timeout = 30 # Timeout em segundos
config.retry_attempts = 3 # Número de tentativas
config.retry_delay = 1 # Delay entre tentativas (segundos)
end
Configuração Avançada
EvolutionApi.configure do |config|
# Configurações básicas
config.base_url = "https://api.evolution.com"
config.api_key = "sua_chave_api"
# Configurações de webhook
config.webhook_url = "https://seu-site.com/webhook"
config.webhook_events = ["connection.update", "message.upsert"]
# Configurações de log
config.logger = Rails.logger
config.log_level = :info
# Configurações de cache
config.cache_enabled = true
config.cache_ttl = 300 # 5 minutos
end
🎯 Uso Básico
Inicialização do Cliente
# Usando o cliente global
client = EvolutionApi.client
# Ou criando uma instância personalizada
client = EvolutionApi::Client.new
Gerenciamento de Instâncias
# Listar todas as instâncias
instances = client.list_instances
# Criar uma nova instância
client.create_instance("minha_instancia", {
qrcode: true,
webhook: "https://seu-site.com/webhook"
})
# Conectar uma instância
client.connect_instance("minha_instancia")
# Obter QR Code para conexão
qr_code = client.get_qr_code("minha_instancia")
# Verificar status da instância
instance_info = client.get_instance("minha_instancia")
puts "Status: #{instance_info['status']}"
# Desconectar instância
client.disconnect_instance("minha_instancia")
# Remover instância
client.delete_instance("minha_instancia")
Envio de Mensagens
# Mensagem de texto
client.("minha_instancia", "5511999999999", "Olá! Como vai?")
# Mensagem de imagem
client.(
"minha_instancia",
"5511999999999",
"https://exemplo.com/imagem.jpg",
"Legenda da imagem"
)
# Mensagem de áudio
client.(
"minha_instancia",
"5511999999999",
"https://exemplo.com/audio.mp3"
)
# Mensagem de vídeo
client.(
"minha_instancia",
"5511999999999",
"https://exemplo.com/video.mp4",
"Descrição do vídeo"
)
# Documento
client.(
"minha_instancia",
"5511999999999",
"https://exemplo.com/documento.pdf",
"Descrição do documento"
)
# Localização
client.(
"minha_instancia",
"5511999999999",
-23.5505,
-46.6333,
"São Paulo, SP"
)
# Contato
client.(
"minha_instancia",
"5511999999999",
"5511888888888",
"João Silva"
)
Mensagens Interativas
# Mensagem com botões
= [
{ id: "btn1", body: "Opção 1" },
{ id: "btn2", body: "Opção 2" },
{ id: "btn3", body: "Opção 3" }
]
client.(
"minha_instancia",
"5511999999999",
"Título da mensagem",
"Descrição da mensagem",
)
# Lista de opções
sections = [
{
title: "Seção 1",
rows: [
{ id: "1", title: "Item 1", description: "Descrição 1" },
{ id: "2", title: "Item 2", description: "Descrição 2" }
]
}
]
client.(
"minha_instancia",
"5511999999999",
"Título da lista",
"Descrição da lista",
sections
)
Gerenciamento de Chats
# Obter todos os chats
chats = client.get_chats("minha_instancia")
# Obter mensagens de um chat
= client.("minha_instancia", "5511999999999", {
limit: 50,
cursor: "cursor_para_paginacao"
})
# Marcar mensagens como lidas
client.("minha_instancia", "5511999999999")
# Arquivar chat
client.archive_chat("minha_instancia", "5511999999999")
# Desarquivar chat
client.unarchive_chat("minha_instancia", "5511999999999")
# Deletar chat
client.delete_chat("minha_instancia", "5511999999999")
Gerenciamento de Contatos
# Obter todos os contatos
contacts = client.get_contacts("minha_instancia")
# Obter informações de um contato específico
contact = client.get_contact("minha_instancia", "5511999999999")
# Verificar se um número existe no WhatsApp
result = client.check_number("minha_instancia", "5511999999999")
puts "Número existe: #{result['exists']}"
# Bloquear contato
client.block_contact("minha_instancia", "5511999999999")
# Desbloquear contato
client.unblock_contact("minha_instancia", "5511999999999")
Configuração de Webhooks
# Configurar webhook
client.set_webhook(
"minha_instancia",
"https://seu-site.com/webhook",
["connection.update", "message.upsert"]
)
# Obter configuração do webhook
webhook_config = client.get_webhook("minha_instancia")
# Remover webhook
client.delete_webhook("minha_instancia")
🎨 Uso com Classes Auxiliares
Usando a Classe Instance
# Criar uma instância gerenciada
instance = EvolutionApi::Instance.new("minha_instancia", client)
# Verificar se está conectada
if instance.connected?
puts "Instância conectada!"
# Enviar mensagem
instance.send_text("5511999999999", "Olá!")
# Obter chats
chats = instance.chats
# Obter contatos
contacts = instance.contacts
else
puts "Instância não conectada"
qr_code = instance.qr_code
end
Trabalhando com Mensagens
# Obter mensagens e processar
= client.("minha_instancia", "5511999999999")
= .map { |msg| EvolutionApi::Message.new(msg, "minha_instancia") }
.each do ||
case .type
when "text"
puts "Texto: #{message.text}"
when "image"
puts "Imagem: #{message.image['url']}"
when "audio"
puts "Áudio: #{message.audio['url']}"
end
puts "De: #{message.from}"
puts "Enviada por mim: #{message.from_me?}"
puts "Grupo: #{message.group?}"
puts "Timestamp: #{message.timestamp}"
end
Trabalhando com Chats
# Obter chats e processar
chats_data = client.get_chats("minha_instancia")
chats = chats_data.map { |chat| EvolutionApi::Chat.new(chat, "minha_instancia") }
chats.each do |chat|
puts "Chat: #{chat.name}"
puts "Número: #{chat.number}"
puts "Grupo: #{chat.group?}"
puts "Não lidas: #{chat.unread_count}"
puts "Arquivado: #{chat.archived?}"
end
Trabalhando com Contatos
# Obter contatos e processar
contacts_data = client.get_contacts("minha_instancia")
contacts = contacts_data.map { |contact| EvolutionApi::Contact.new(contact, "minha_instancia") }
contacts.each do |contact|
puts "Nome: #{contact.display_name}"
puts "Número: #{contact.number}"
puts "Business: #{contact.business?}"
puts "Verificado: #{contact.verified?}"
end
🚨 Tratamento de Erros
A gem fornece exceções específicas para diferentes tipos de erro:
begin
client.("instancia_inexistente", "5511999999999", "Olá!")
rescue EvolutionApi::NotFoundError => e
puts "Instância não encontrada: #{e.message}"
rescue EvolutionApi::AuthenticationError => e
puts "Erro de autenticação: #{e.message}"
rescue EvolutionApi::ValidationError => e
puts "Erro de validação: #{e.message}"
puts "Detalhes: #{e.errors}"
rescue EvolutionApi::RateLimitError => e
puts "Rate limit excedido: #{e.message}"
rescue EvolutionApi::ServerError => e
puts "Erro do servidor: #{e.message}"
rescue EvolutionApi::ConnectionError => e
puts "Erro de conexão: #{e.message}"
rescue EvolutionApi::TimeoutError => e
puts "Timeout: #{e.message}"
end
🧪 Testes
Executar Testes
# Executar todos os testes
bundle exec rspec
# Executar testes com coverage
bundle exec rspec --format documentation
# Executar testes específicos
bundle exec rspec spec/evolution_api/client_spec.rb
Exemplo de Teste
require 'spec_helper'
RSpec.describe EvolutionApi::Client do
let(:client) { described_class.new }
describe '#list_instances' do
it 'returns list of instances' do
VCR.use_cassette('list_instances') do
response = client.list_instances
expect(response).to be_an(Array)
end
end
end
describe '#send_text_message' do
it 'sends text message successfully' do
VCR.use_cassette('send_text_message') do
response = client.(
'test_instance',
'5511999999999',
'Test message'
)
expect(response['status']).to eq('success')
end
end
end
end
📚 Documentação
A documentação completa está disponível em:
Para gerar a documentação localmente:
bundle exec yard doc
bundle exec yard server
🤝 Contribuindo
- Faça um fork do projeto
- Crie uma branch para sua feature (
git checkout -b feature/AmazingFeature) - Commit suas mudanças (
git commit -m 'Add some AmazingFeature') - Push para a branch (
git push origin feature/AmazingFeature) - Abra um Pull Request
Padrões de Código
# Verificar estilo do código
bundle exec rubocop
# Corrigir automaticamente
bundle exec rubocop -a
# Verificar apenas arquivos modificados
bundle exec rubocop --only-guide-cops
📄 Licença
Este projeto está licenciado sob a Licença MIT - veja o arquivo LICENSE.txt para detalhes.
🆘 Suporte
- 📖 Documentação
- 🐛 Issues
- 💬 Discussions
🙏 Agradecimentos
- Evolution API - API incrível para WhatsApp
- HTTParty - Cliente HTTP elegante
- Dry::Configurable - Sistema de configuração
- Dry::Validation - Validação de dados
Desenvolvido com ❤️ para a comunidade Ruby