
Security News
New Website “Is It Really FOSS?” Tracks Transparency in Open Source Distribution Models
A new site reviews software projects to reveal if they’re truly FOSS, making complex licensing and distribution models easy to understand.
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.
Adicione a gem ao seu Gemfile
:
gem 'evolution_api'
E execute:
bundle install
Ou instale diretamente:
gem install evolution_api
# Gemfile
gem 'evolution_api'
bundle install
# 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
rails credentials:edit
Adicione no arquivo de credenciais:
evolution_api:
base_url: "https://sua-evolution-api.com"
api_key: "sua_api_key_aqui"
# app/controllers/whatsapp_controller.rb
class WhatsAppController < ApplicationController
before_action :set_client
def send_message
begin
response = @client.send_text_message(
params[:instance_name],
params[:phone_number],
params[:message]
)
render json: { success: true, data: response }
rescue EvolutionApi::Error => e
render json: { success: false, error: e.message }, 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
# 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 message_type
when 'text'
client.send_text_message(instance_name, phone_number, content)
when 'image'
client.send_image_message(instance_name, phone_number, content, caption)
when 'audio'
client.send_audio_message(instance_name, phone_number, content)
when 'video'
client.send_video_message(instance_name, phone_number, content, caption)
when 'document'
client.send_document_message(instance_name, phone_number, content, caption)
end
rescue EvolutionApi::Error => e
update(status: 'failed', error_message: e.message)
end
end
# 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 send_bulk_messages(phone_numbers, message)
results = []
phone_numbers.each do |phone|
begin
response = @client.send_text_message(@instance_name, phone, message)
results << { phone: phone, success: true, response: response }
rescue EvolutionApi::Error => e
results << { phone: phone, success: false, error: e.message }
end
end
results
end
def broadcast_message(message, options = {})
contacts = @client.get_contacts(@instance_name)
contacts.each do |contact|
next if options[:exclude_numbers]&.include?(contact['id'])
@client.send_text_message(@instance_name, contact['id'], message)
sleep(options[: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
# app/jobs/whatsapp_message_job.rb
class WhatsAppMessageJob < ApplicationJob
queue_as :whatsapp
def perform(instance_name, phone_number, message, message_type = 'text')
client = EvolutionApi.client
case message_type
when 'text'
client.send_text_message(instance_name, phone_number, message)
when 'image'
client.send_image_message(instance_name, phone_number, message[:url], message[:caption])
when 'audio'
client.send_audio_message(instance_name, phone_number, message[:url])
when 'video'
client.send_video_message(instance_name, phone_number, message[:url], message[:caption])
when 'document'
client.send_document_message(instance_name, phone_number, message[:url], message[:caption])
end
rescue EvolutionApi::Error => e
Rails.logger.error "WhatsApp message failed: #{e.message}"
raise e
end
end
# 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'
handle_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 handle_message_upsert
message_data = params[:data]
instance_name = params[:instance]
# Processar mensagem recebida
message = Message.create!(
instance_name: instance_name,
phone_number: message_data[:key][:remoteJid],
message_type: detect_message_type(message_data[:message]),
content: extract_message_content(message_data[:message]),
from_me: message_data[:key][:fromMe],
timestamp: Time.at(message_data[:messageTimestamp])
)
# Processar automaticamente se necessário
AutoReplyService.new(message).process if should_auto_reply?(message)
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 detect_message_type(message)
return 'text' if message[:conversation] || message[:extendedTextMessage]
return 'image' if message[:imageMessage]
return 'audio' if message[:audioMessage]
return 'video' if message[:videoMessage]
return 'document' if message[:documentMessage]
return 'location' if message[:locationMessage]
return 'contact' if message[:contactMessage]
'unknown'
end
def extract_message_content(message)
return message[:conversation] if message[:conversation]
return message[:extendedTextMessage][:text] if message[:extendedTextMessage]
return message[:imageMessage][:url] if message[:imageMessage]
return message[:audioMessage][:url] if message[:audioMessage]
return message[:videoMessage][:url] if message[:videoMessage]
return message[:documentMessage][:url] if message[:documentMessage]
nil
end
def should_auto_reply?(message)
!message.from_me && message.message_type == 'text'
end
end
# 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
<!-- 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>
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
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
# Usando o cliente global
client = EvolutionApi.client
# Ou criando uma instância personalizada
client = EvolutionApi::Client.new
# 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")
# Mensagem de texto
client.send_text_message("minha_instancia", "5511999999999", "Olá! Como vai?")
# Mensagem de imagem
client.send_image_message(
"minha_instancia",
"5511999999999",
"https://exemplo.com/imagem.jpg",
"Legenda da imagem"
)
# Mensagem de áudio
client.send_audio_message(
"minha_instancia",
"5511999999999",
"https://exemplo.com/audio.mp3"
)
# Mensagem de vídeo
client.send_video_message(
"minha_instancia",
"5511999999999",
"https://exemplo.com/video.mp4",
"Descrição do vídeo"
)
# Documento
client.send_document_message(
"minha_instancia",
"5511999999999",
"https://exemplo.com/documento.pdf",
"Descrição do documento"
)
# Localização
client.send_location_message(
"minha_instancia",
"5511999999999",
-23.5505,
-46.6333,
"São Paulo, SP"
)
# Contato
client.send_contact_message(
"minha_instancia",
"5511999999999",
"5511888888888",
"João Silva"
)
# Mensagem com botões
buttons = [
{ id: "btn1", body: "Opção 1" },
{ id: "btn2", body: "Opção 2" },
{ id: "btn3", body: "Opção 3" }
]
client.send_button_message(
"minha_instancia",
"5511999999999",
"Título da mensagem",
"Descrição da mensagem",
buttons
)
# 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.send_list_message(
"minha_instancia",
"5511999999999",
"Título da lista",
"Descrição da lista",
sections
)
# Obter todos os chats
chats = client.get_chats("minha_instancia")
# Obter mensagens de um chat
messages = client.get_messages("minha_instancia", "5511999999999", {
limit: 50,
cursor: "cursor_para_paginacao"
})
# Marcar mensagens como lidas
client.mark_messages_as_read("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")
# 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")
# 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")
# 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
# Obter mensagens e processar
messages_data = client.get_messages("minha_instancia", "5511999999999")
messages = messages_data.map { |msg| EvolutionApi::Message.new(msg, "minha_instancia") }
messages.each do |message|
case message.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
# 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
# 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
A gem fornece exceções específicas para diferentes tipos de erro:
begin
client.send_text_message("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
# 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
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.send_text_message(
'test_instance',
'5511999999999',
'Test message'
)
expect(response['status']).to eq('success')
end
end
end
end
A documentação completa está disponível em:
Para gerar a documentação localmente:
bundle exec yard doc
bundle exec yard server
git checkout -b feature/AmazingFeature
)git commit -m 'Add some AmazingFeature'
)git push origin feature/AmazingFeature
)# Verificar estilo do código
bundle exec rubocop
# Corrigir automaticamente
bundle exec rubocop -a
# Verificar apenas arquivos modificados
bundle exec rubocop --only-guide-cops
Este projeto está licenciado sob a Licença MIT - veja o arquivo LICENSE.txt para detalhes.
Desenvolvido com ❤️ para a comunidade Ruby
FAQs
Unknown package
We found that evolution_api demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
A new site reviews software projects to reveal if they’re truly FOSS, making complex licensing and distribution models easy to understand.
Security News
Astral unveils pyx, a Python-native package registry in beta, designed to speed installs, enhance security, and integrate deeply with uv.
Security News
The Latio podcast explores how static and runtime reachability help teams prioritize exploitable vulnerabilities and streamline AppSec workflows.