evolution_api 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +421 -0
- data/lib/evolution_api/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bb44daa0ea45acd5e886118b118b5f59ee0a4d377269e1eeeb44bc817cd2e25c
|
4
|
+
data.tar.gz: d7005e170ef810ad634437cda1d8f8302c6b55ad2710fee66e1a2e3877b9f71f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: af4025a664be1f58d9c1181dc14aab7c5255acf2e48e805146e99a4feb817a5770e57fe8ed3a2e227f023a308a074f86b2b027a7cd028c28be47913fe86986a4
|
7
|
+
data.tar.gz: b3b285afe6028f5321426b9341f544edd1b2d907be05e4f0f6432009935b40e7f24cc21e5c32835582933c04fece155ab966004cc186440ddd3f34137340e587
|
data/README.md
CHANGED
@@ -39,6 +39,427 @@ Ou instale diretamente:
|
|
39
39
|
gem install evolution_api
|
40
40
|
```
|
41
41
|
|
42
|
+
## 🚂 Integração com Rails
|
43
|
+
|
44
|
+
### Instalação em Projetos Rails
|
45
|
+
|
46
|
+
1. **Adicione a gem ao seu Gemfile:**
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
# Gemfile
|
50
|
+
gem 'evolution_api'
|
51
|
+
```
|
52
|
+
|
53
|
+
2. **Execute o bundle install:**
|
54
|
+
|
55
|
+
```bash
|
56
|
+
bundle install
|
57
|
+
```
|
58
|
+
|
59
|
+
3. **Configure a gem no initializer:**
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
# config/initializers/evolution_api.rb
|
63
|
+
EvolutionApi.configure do |config|
|
64
|
+
config.base_url = Rails.application.credentials.evolution_api[:base_url] || "http://localhost:8080"
|
65
|
+
config.api_key = Rails.application.credentials.evolution_api[:api_key]
|
66
|
+
config.timeout = 30
|
67
|
+
config.retry_attempts = 3
|
68
|
+
config.retry_delay = 1
|
69
|
+
end
|
70
|
+
```
|
71
|
+
|
72
|
+
4. **Configure as credenciais (Rails 5.2+):**
|
73
|
+
|
74
|
+
```bash
|
75
|
+
rails credentials:edit
|
76
|
+
```
|
77
|
+
|
78
|
+
Adicione no arquivo de credenciais:
|
79
|
+
|
80
|
+
```yaml
|
81
|
+
evolution_api:
|
82
|
+
base_url: "https://sua-evolution-api.com"
|
83
|
+
api_key: "sua_api_key_aqui"
|
84
|
+
```
|
85
|
+
|
86
|
+
### Exemplo de Controller Rails
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
# app/controllers/whatsapp_controller.rb
|
90
|
+
class WhatsAppController < ApplicationController
|
91
|
+
before_action :set_client
|
92
|
+
|
93
|
+
def send_message
|
94
|
+
begin
|
95
|
+
response = @client.send_text_message(
|
96
|
+
params[:instance_name],
|
97
|
+
params[:phone_number],
|
98
|
+
params[:message]
|
99
|
+
)
|
100
|
+
|
101
|
+
render json: { success: true, data: response }
|
102
|
+
rescue EvolutionApi::Error => e
|
103
|
+
render json: { success: false, error: e.message }, status: :unprocessable_entity
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def list_instances
|
108
|
+
instances = @client.list_instances
|
109
|
+
render json: { instances: instances }
|
110
|
+
end
|
111
|
+
|
112
|
+
def create_instance
|
113
|
+
response = @client.create_instance(params[:instance_name], {
|
114
|
+
qrcode: true,
|
115
|
+
webhook: webhook_url
|
116
|
+
})
|
117
|
+
|
118
|
+
render json: { success: true, data: response }
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def set_client
|
124
|
+
@client = EvolutionApi.client
|
125
|
+
end
|
126
|
+
|
127
|
+
def webhook_url
|
128
|
+
"#{request.base_url}/webhooks/whatsapp"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
```
|
132
|
+
|
133
|
+
### Exemplo de Model Rails
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
# app/models/whatsapp_message.rb
|
137
|
+
class WhatsAppMessage < ApplicationRecord
|
138
|
+
validates :instance_name, presence: true
|
139
|
+
validates :phone_number, presence: true
|
140
|
+
validates :message_type, presence: true, inclusion: { in: %w[text image audio video document] }
|
141
|
+
validates :content, presence: true
|
142
|
+
|
143
|
+
after_create :send_to_whatsapp
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
def send_to_whatsapp
|
148
|
+
client = EvolutionApi.client
|
149
|
+
|
150
|
+
case message_type
|
151
|
+
when 'text'
|
152
|
+
client.send_text_message(instance_name, phone_number, content)
|
153
|
+
when 'image'
|
154
|
+
client.send_image_message(instance_name, phone_number, content, caption)
|
155
|
+
when 'audio'
|
156
|
+
client.send_audio_message(instance_name, phone_number, content)
|
157
|
+
when 'video'
|
158
|
+
client.send_video_message(instance_name, phone_number, content, caption)
|
159
|
+
when 'document'
|
160
|
+
client.send_document_message(instance_name, phone_number, content, caption)
|
161
|
+
end
|
162
|
+
rescue EvolutionApi::Error => e
|
163
|
+
update(status: 'failed', error_message: e.message)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
```
|
167
|
+
|
168
|
+
### Exemplo de Service Object
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
# app/services/whatsapp_service.rb
|
172
|
+
class WhatsAppService
|
173
|
+
def initialize(instance_name = nil)
|
174
|
+
@client = EvolutionApi.client
|
175
|
+
@instance_name = instance_name || Rails.application.credentials.evolution_api[:default_instance]
|
176
|
+
end
|
177
|
+
|
178
|
+
def send_bulk_messages(phone_numbers, message)
|
179
|
+
results = []
|
180
|
+
|
181
|
+
phone_numbers.each do |phone|
|
182
|
+
begin
|
183
|
+
response = @client.send_text_message(@instance_name, phone, message)
|
184
|
+
results << { phone: phone, success: true, response: response }
|
185
|
+
rescue EvolutionApi::Error => e
|
186
|
+
results << { phone: phone, success: false, error: e.message }
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
results
|
191
|
+
end
|
192
|
+
|
193
|
+
def broadcast_message(message, options = {})
|
194
|
+
contacts = @client.get_contacts(@instance_name)
|
195
|
+
|
196
|
+
contacts.each do |contact|
|
197
|
+
next if options[:exclude_numbers]&.include?(contact['id'])
|
198
|
+
|
199
|
+
@client.send_text_message(@instance_name, contact['id'], message)
|
200
|
+
sleep(options[:delay] || 1) # Evita rate limiting
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def instance_status
|
205
|
+
@client.get_instance(@instance_name)
|
206
|
+
end
|
207
|
+
|
208
|
+
def is_connected?
|
209
|
+
status = instance_status
|
210
|
+
status['status'] == 'open'
|
211
|
+
end
|
212
|
+
end
|
213
|
+
```
|
214
|
+
|
215
|
+
### Exemplo de Job para Processamento Assíncrono
|
216
|
+
|
217
|
+
```ruby
|
218
|
+
# app/jobs/whatsapp_message_job.rb
|
219
|
+
class WhatsAppMessageJob < ApplicationJob
|
220
|
+
queue_as :whatsapp
|
221
|
+
|
222
|
+
def perform(instance_name, phone_number, message, message_type = 'text')
|
223
|
+
client = EvolutionApi.client
|
224
|
+
|
225
|
+
case message_type
|
226
|
+
when 'text'
|
227
|
+
client.send_text_message(instance_name, phone_number, message)
|
228
|
+
when 'image'
|
229
|
+
client.send_image_message(instance_name, phone_number, message[:url], message[:caption])
|
230
|
+
when 'audio'
|
231
|
+
client.send_audio_message(instance_name, phone_number, message[:url])
|
232
|
+
when 'video'
|
233
|
+
client.send_video_message(instance_name, phone_number, message[:url], message[:caption])
|
234
|
+
when 'document'
|
235
|
+
client.send_document_message(instance_name, phone_number, message[:url], message[:caption])
|
236
|
+
end
|
237
|
+
rescue EvolutionApi::Error => e
|
238
|
+
Rails.logger.error "WhatsApp message failed: #{e.message}"
|
239
|
+
raise e
|
240
|
+
end
|
241
|
+
end
|
242
|
+
```
|
243
|
+
|
244
|
+
### Exemplo de Webhook Controller
|
245
|
+
|
246
|
+
```ruby
|
247
|
+
# app/controllers/webhooks/whatsapp_controller.rb
|
248
|
+
class Webhooks::WhatsappController < ApplicationController
|
249
|
+
skip_before_action :verify_authenticity_token
|
250
|
+
|
251
|
+
def receive
|
252
|
+
case params[:event]
|
253
|
+
when 'connection.update'
|
254
|
+
handle_connection_update
|
255
|
+
when 'message.upsert'
|
256
|
+
handle_message_upsert
|
257
|
+
when 'qr.update'
|
258
|
+
handle_qr_update
|
259
|
+
end
|
260
|
+
|
261
|
+
head :ok
|
262
|
+
end
|
263
|
+
|
264
|
+
private
|
265
|
+
|
266
|
+
def handle_connection_update
|
267
|
+
instance_name = params[:instance]
|
268
|
+
status = params[:data][:status]
|
269
|
+
|
270
|
+
Rails.logger.info "WhatsApp instance #{instance_name} status: #{status}"
|
271
|
+
|
272
|
+
# Atualizar status no banco de dados
|
273
|
+
instance = WhatsAppInstance.find_by(name: instance_name)
|
274
|
+
instance&.update(status: status)
|
275
|
+
end
|
276
|
+
|
277
|
+
def handle_message_upsert
|
278
|
+
message_data = params[:data]
|
279
|
+
instance_name = params[:instance]
|
280
|
+
|
281
|
+
# Processar mensagem recebida
|
282
|
+
message = Message.create!(
|
283
|
+
instance_name: instance_name,
|
284
|
+
phone_number: message_data[:key][:remoteJid],
|
285
|
+
message_type: detect_message_type(message_data[:message]),
|
286
|
+
content: extract_message_content(message_data[:message]),
|
287
|
+
from_me: message_data[:key][:fromMe],
|
288
|
+
timestamp: Time.at(message_data[:messageTimestamp])
|
289
|
+
)
|
290
|
+
|
291
|
+
# Processar automaticamente se necessário
|
292
|
+
AutoReplyService.new(message).process if should_auto_reply?(message)
|
293
|
+
end
|
294
|
+
|
295
|
+
def handle_qr_update
|
296
|
+
instance_name = params[:instance]
|
297
|
+
qr_code = params[:data][:qrcode]
|
298
|
+
|
299
|
+
# Salvar QR code para exibição
|
300
|
+
Rails.cache.write("whatsapp_qr_#{instance_name}", qr_code, expires_in: 2.minutes)
|
301
|
+
end
|
302
|
+
|
303
|
+
def detect_message_type(message)
|
304
|
+
return 'text' if message[:conversation] || message[:extendedTextMessage]
|
305
|
+
return 'image' if message[:imageMessage]
|
306
|
+
return 'audio' if message[:audioMessage]
|
307
|
+
return 'video' if message[:videoMessage]
|
308
|
+
return 'document' if message[:documentMessage]
|
309
|
+
return 'location' if message[:locationMessage]
|
310
|
+
return 'contact' if message[:contactMessage]
|
311
|
+
'unknown'
|
312
|
+
end
|
313
|
+
|
314
|
+
def extract_message_content(message)
|
315
|
+
return message[:conversation] if message[:conversation]
|
316
|
+
return message[:extendedTextMessage][:text] if message[:extendedTextMessage]
|
317
|
+
return message[:imageMessage][:url] if message[:imageMessage]
|
318
|
+
return message[:audioMessage][:url] if message[:audioMessage]
|
319
|
+
return message[:videoMessage][:url] if message[:videoMessage]
|
320
|
+
return message[:documentMessage][:url] if message[:documentMessage]
|
321
|
+
nil
|
322
|
+
end
|
323
|
+
|
324
|
+
def should_auto_reply?(message)
|
325
|
+
!message.from_me && message.message_type == 'text'
|
326
|
+
end
|
327
|
+
end
|
328
|
+
```
|
329
|
+
|
330
|
+
### Configuração de Rotas
|
331
|
+
|
332
|
+
```ruby
|
333
|
+
# config/routes.rb
|
334
|
+
Rails.application.routes.draw do
|
335
|
+
# Rotas para WhatsApp
|
336
|
+
resources :whatsapp, only: [:index] do
|
337
|
+
collection do
|
338
|
+
post :send_message
|
339
|
+
get :list_instances
|
340
|
+
post :create_instance
|
341
|
+
get :qr_code/:instance_name, action: :qr_code, as: :qr_code
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
# Webhook para receber mensagens
|
346
|
+
post 'webhooks/whatsapp', to: 'webhooks/whatsapp#receive'
|
347
|
+
end
|
348
|
+
```
|
349
|
+
|
350
|
+
### Exemplo de View
|
351
|
+
|
352
|
+
```erb
|
353
|
+
<!-- app/views/whatsapp/index.html.erb -->
|
354
|
+
<div class="whatsapp-dashboard">
|
355
|
+
<h1>WhatsApp Dashboard</h1>
|
356
|
+
|
357
|
+
<div class="instances">
|
358
|
+
<h2>Instâncias</h2>
|
359
|
+
<div id="instances-list">
|
360
|
+
<!-- Será preenchido via JavaScript -->
|
361
|
+
</div>
|
362
|
+
|
363
|
+
<button onclick="createInstance()">Nova Instância</button>
|
364
|
+
</div>
|
365
|
+
|
366
|
+
<div class="qr-code" id="qr-code">
|
367
|
+
<!-- QR Code será exibido aqui -->
|
368
|
+
</div>
|
369
|
+
|
370
|
+
<div class="send-message">
|
371
|
+
<h2>Enviar Mensagem</h2>
|
372
|
+
<form id="message-form">
|
373
|
+
<select name="instance_name" required>
|
374
|
+
<option value="">Selecione uma instância</option>
|
375
|
+
</select>
|
376
|
+
|
377
|
+
<input type="tel" name="phone_number" placeholder="Número (ex: 5511999999999)" required>
|
378
|
+
|
379
|
+
<textarea name="message" placeholder="Mensagem" required></textarea>
|
380
|
+
|
381
|
+
<button type="submit">Enviar</button>
|
382
|
+
</form>
|
383
|
+
</div>
|
384
|
+
</div>
|
385
|
+
|
386
|
+
<script>
|
387
|
+
document.addEventListener('DOMContentLoaded', function() {
|
388
|
+
loadInstances();
|
389
|
+
setupMessageForm();
|
390
|
+
});
|
391
|
+
|
392
|
+
function loadInstances() {
|
393
|
+
fetch('/whatsapp/list_instances')
|
394
|
+
.then(response => response.json())
|
395
|
+
.then(data => {
|
396
|
+
const instancesList = document.getElementById('instances-list');
|
397
|
+
const instanceSelect = document.querySelector('select[name="instance_name"]');
|
398
|
+
|
399
|
+
data.instances.forEach(instance => {
|
400
|
+
// Atualizar lista de instâncias
|
401
|
+
instancesList.innerHTML += `
|
402
|
+
<div class="instance">
|
403
|
+
<strong>${instance.instance}</strong>
|
404
|
+
<span class="status ${instance.status}">${instance.status}</span>
|
405
|
+
</div>
|
406
|
+
`;
|
407
|
+
|
408
|
+
// Atualizar select
|
409
|
+
instanceSelect.innerHTML += `
|
410
|
+
<option value="${instance.instance}">${instance.instance} (${instance.status})</option>
|
411
|
+
`;
|
412
|
+
});
|
413
|
+
});
|
414
|
+
}
|
415
|
+
|
416
|
+
function setupMessageForm() {
|
417
|
+
document.getElementById('message-form').addEventListener('submit', function(e) {
|
418
|
+
e.preventDefault();
|
419
|
+
|
420
|
+
const formData = new FormData(this);
|
421
|
+
|
422
|
+
fetch('/whatsapp/send_message', {
|
423
|
+
method: 'POST',
|
424
|
+
body: formData
|
425
|
+
})
|
426
|
+
.then(response => response.json())
|
427
|
+
.then(data => {
|
428
|
+
if (data.success) {
|
429
|
+
alert('Mensagem enviada com sucesso!');
|
430
|
+
this.reset();
|
431
|
+
} else {
|
432
|
+
alert('Erro ao enviar mensagem: ' + data.error);
|
433
|
+
}
|
434
|
+
});
|
435
|
+
});
|
436
|
+
}
|
437
|
+
|
438
|
+
function createInstance() {
|
439
|
+
const instanceName = prompt('Nome da instância:');
|
440
|
+
if (!instanceName) return;
|
441
|
+
|
442
|
+
fetch('/whatsapp/create_instance', {
|
443
|
+
method: 'POST',
|
444
|
+
headers: {
|
445
|
+
'Content-Type': 'application/json',
|
446
|
+
'X-CSRF-Token': document.querySelector('[name="csrf-token"]').content
|
447
|
+
},
|
448
|
+
body: JSON.stringify({ instance_name: instanceName })
|
449
|
+
})
|
450
|
+
.then(response => response.json())
|
451
|
+
.then(data => {
|
452
|
+
if (data.success) {
|
453
|
+
alert('Instância criada! Verifique o QR Code.');
|
454
|
+
loadInstances();
|
455
|
+
} else {
|
456
|
+
alert('Erro ao criar instância: ' + data.error);
|
457
|
+
}
|
458
|
+
});
|
459
|
+
}
|
460
|
+
</script>
|
461
|
+
```
|
462
|
+
|
42
463
|
## ⚙️ Configuração
|
43
464
|
|
44
465
|
### Configuração Básica
|