evolution_api 1.0.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.
data/bin/test_gem.rb ADDED
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Script para testar a gem Evolution API localmente
5
+ # Uso: ruby bin/test_gem.rb
6
+
7
+ require 'bundler/setup'
8
+ require_relative '../lib/evolution_api'
9
+
10
+ puts "🧪 Testando Evolution API Ruby Client"
11
+ puts "=" * 50
12
+
13
+ # Configuração para teste
14
+ EvolutionApi.configure do |config|
15
+ config.base_url = ENV['EVOLUTION_API_URL'] || 'http://localhost:8080'
16
+ config.api_key = ENV['EVOLUTION_API_KEY']
17
+ config.timeout = 10
18
+ config.retry_attempts = 1
19
+ config.retry_delay = 0.1
20
+ end
21
+
22
+ client = EvolutionApi.client
23
+
24
+ # Teste 1: Verificar se a API está acessível
25
+ puts "\n1️⃣ Testando conectividade com a API..."
26
+ begin
27
+ instances = client.list_instances
28
+ puts "✅ API acessível! Instâncias encontradas: #{instances.length}"
29
+ rescue EvolutionApi::ConnectionError => e
30
+ puts "❌ Erro de conexão: #{e.message}"
31
+ puts "💡 Verifique se a Evolution API está rodando em #{EvolutionApi.config.base_url}"
32
+ exit 1
33
+ rescue EvolutionApi::AuthenticationError => e
34
+ puts "❌ Erro de autenticação: #{e.message}"
35
+ puts "💡 Verifique sua API key"
36
+ exit 1
37
+ rescue StandardError => e
38
+ puts "❌ Erro inesperado: #{e.message}"
39
+ exit 1
40
+ end
41
+
42
+ # Teste 2: Criar uma instância de teste
43
+ puts "\n2️⃣ Testando criação de instância..."
44
+ test_instance_name = "test_ruby_#{Time.now.to_i}"
45
+
46
+ begin
47
+ response = client.create_instance(test_instance_name, {
48
+ qrcode: true,
49
+ webhook: 'https://example.com/webhook'
50
+ })
51
+ puts "✅ Instância criada: #{test_instance_name}"
52
+ rescue StandardError => e
53
+ puts "❌ Erro ao criar instância: #{e.message}"
54
+ end
55
+
56
+ # Teste 3: Obter QR Code
57
+ puts "\n3️⃣ Testando obtenção de QR Code..."
58
+ begin
59
+ qr_response = client.get_qr_code(test_instance_name)
60
+ if qr_response['qrcode']
61
+ puts "✅ QR Code obtido com sucesso!"
62
+ puts "📱 QR Code: #{qr_response['qrcode'][0..50]}..."
63
+ else
64
+ puts "⚠️ QR Code não disponível (instância pode estar conectada)"
65
+ end
66
+ rescue StandardError => e
67
+ puts "❌ Erro ao obter QR Code: #{e.message}"
68
+ end
69
+
70
+ # Teste 4: Verificar status da instância
71
+ puts "\n4️⃣ Testando verificação de status..."
72
+ begin
73
+ instance_info = client.get_instance(test_instance_name)
74
+ puts "✅ Status da instância: #{instance_info['status']}"
75
+ puts "📊 Conectada: #{instance_info['status'] == 'open'}"
76
+ rescue StandardError => e
77
+ puts "❌ Erro ao verificar status: #{e.message}"
78
+ end
79
+
80
+ # Teste 5: Testar classes auxiliares
81
+ puts "\n5️⃣ Testando classes auxiliares..."
82
+
83
+ # Teste da classe Instance
84
+ instance = EvolutionApi::Instance.new(test_instance_name, client)
85
+ puts "✅ Classe Instance criada"
86
+
87
+ # Teste da classe Message
88
+ sample_message_data = {
89
+ "id" => "test_id",
90
+ "key" => {
91
+ "remoteJid" => "5511999999999@s.whatsapp.net",
92
+ "fromMe" => false,
93
+ "id" => "test_message_id"
94
+ },
95
+ "message" => {
96
+ "conversation" => "Teste de mensagem"
97
+ },
98
+ "messageTimestamp" => Time.now.to_i,
99
+ "status" => "received"
100
+ }
101
+
102
+ message = EvolutionApi::Message.new(sample_message_data, test_instance_name)
103
+ puts "✅ Classe Message criada"
104
+ puts " Tipo: #{message.type}"
105
+ puts " Texto: #{message.text}"
106
+ puts " De: #{message.from}"
107
+
108
+ # Teste da classe Chat
109
+ sample_chat_data = {
110
+ "id" => "5511999999999@s.whatsapp.net",
111
+ "name" => "Teste Chat",
112
+ "unreadCount" => 0,
113
+ "isGroup" => false,
114
+ "isReadOnly" => false,
115
+ "archived" => false,
116
+ "pinned" => false
117
+ }
118
+
119
+ chat = EvolutionApi::Chat.new(sample_chat_data, test_instance_name)
120
+ puts "✅ Classe Chat criada"
121
+ puts " Nome: #{chat.name}"
122
+ puts " Grupo: #{chat.group?}"
123
+
124
+ # Teste da classe Contact
125
+ sample_contact_data = {
126
+ "id" => "5511999999999@s.whatsapp.net",
127
+ "name" => "Teste Contato",
128
+ "pushName" => "Teste",
129
+ "verifiedName" => nil,
130
+ "isBusiness" => false,
131
+ "isEnterprise" => false,
132
+ "isHighLevelVerified" => false
133
+ }
134
+
135
+ contact = EvolutionApi::Contact.new(sample_contact_data, test_instance_name)
136
+ puts "✅ Classe Contact criada"
137
+ puts " Nome: #{contact.display_name}"
138
+ puts " Business: #{contact.business?}"
139
+
140
+ # Teste 6: Limpeza
141
+ puts "\n6️⃣ Limpando instância de teste..."
142
+ begin
143
+ client.delete_instance(test_instance_name)
144
+ puts "✅ Instância removida: #{test_instance_name}"
145
+ rescue StandardError => e
146
+ puts "⚠️ Erro ao remover instância: #{e.message}"
147
+ end
148
+
149
+ puts "\n🎉 Testes concluídos com sucesso!"
150
+ puts "\n📋 Resumo:"
151
+ puts " ✅ Conectividade com API"
152
+ puts " ✅ Criação de instância"
153
+ puts " ✅ Obtenção de QR Code"
154
+ puts " ✅ Verificação de status"
155
+ puts " ✅ Classes auxiliares"
156
+ puts " ✅ Limpeza de recursos"
157
+
158
+ puts "\n💡 Próximos passos:"
159
+ puts " 1. Configure suas credenciais da Evolution API"
160
+ puts " 2. Execute o exemplo básico: ruby examples/basic_usage.rb"
161
+ puts " 3. Consulte a documentação: https://doc.evolution-api.com/"
162
+ puts " 4. Veja o README.md para mais exemplos"
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EvolutionApi
4
+ # Classe para representar chats do WhatsApp
5
+ class Chat
6
+ attr_reader :id, :name, :unread_count, :is_group, :is_read_only, :archived, :pinned, :instance_name
7
+
8
+ def initialize(data, instance_name = nil)
9
+ @id = data['id']
10
+ @name = data['name']
11
+ @unread_count = data['unreadCount']
12
+ @is_group = data['isGroup']
13
+ @is_read_only = data['isReadOnly']
14
+ @archived = data['archived']
15
+ @pinned = data['pinned']
16
+ @instance_name = instance_name
17
+ end
18
+
19
+ # Verifica se é um grupo
20
+ def group?
21
+ is_group == true
22
+ end
23
+
24
+ # Verifica se é um chat privado
25
+ def private?
26
+ !group?
27
+ end
28
+
29
+ # Verifica se tem mensagens não lidas
30
+ def unread?
31
+ unread_count&.positive?
32
+ end
33
+
34
+ # Obtém o número de mensagens não lidas
35
+ def unread_count
36
+ @unread_count || 0
37
+ end
38
+
39
+ # Verifica se está arquivado
40
+ def archived?
41
+ archived == true
42
+ end
43
+
44
+ # Verifica se está fixado
45
+ def pinned?
46
+ pinned == true
47
+ end
48
+
49
+ # Verifica se é somente leitura
50
+ def read_only?
51
+ is_read_only == true
52
+ end
53
+
54
+ # Obtém o número do chat (remove sufixos)
55
+ def number
56
+ return nil unless id
57
+
58
+ id.split('@').first
59
+ end
60
+
61
+ # Converte para hash
62
+ def to_h
63
+ {
64
+ id: id,
65
+ name: name,
66
+ number: number,
67
+ unread_count: unread_count,
68
+ is_group: group?,
69
+ is_private: private?,
70
+ is_read_only: read_only?,
71
+ archived: archived?,
72
+ pinned: pinned?,
73
+ instance_name: instance_name
74
+ }
75
+ end
76
+
77
+ # Converte para JSON
78
+ def to_json(*args)
79
+ to_h.to_json(*args)
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,351 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EvolutionApi
4
+ # Cliente principal para interagir com a Evolution API
5
+ class Client
6
+ include HTTParty
7
+
8
+ attr_reader :config
9
+
10
+ def initialize(config = nil)
11
+ @config = config || EvolutionApi.config
12
+ setup_http_client
13
+ end
14
+
15
+ # ==================== INSTÂNCIAS ====================
16
+
17
+ # Lista todas as instâncias
18
+ def list_instances
19
+ get('/instance/fetchInstances')
20
+ end
21
+
22
+ # Cria uma nova instância
23
+ def create_instance(instance_name, options = {})
24
+ body = {
25
+ instanceName: instance_name,
26
+ qrcode: options[:qrcode] || true,
27
+ number: options[:number],
28
+ token: options[:token],
29
+ webhook: options[:webhook],
30
+ webhookByEvents: options[:webhook_by_events] || false,
31
+ webhookBase64: options[:webhook_base64] || false
32
+ }.compact
33
+
34
+ post('/instance/create', body)
35
+ end
36
+
37
+ # Conecta uma instância
38
+ def connect_instance(instance_name)
39
+ post("/instance/connect/#{instance_name}")
40
+ end
41
+
42
+ # Desconecta uma instância
43
+ def disconnect_instance(instance_name)
44
+ delete("/instance/logout/#{instance_name}")
45
+ end
46
+
47
+ # Remove uma instância
48
+ def delete_instance(instance_name)
49
+ delete("/instance/delete/#{instance_name}")
50
+ end
51
+
52
+ # Obtém informações de uma instância
53
+ def get_instance(instance_name)
54
+ get("/instance/fetchInstances/#{instance_name}")
55
+ end
56
+
57
+ # Obtém QR Code de uma instância
58
+ def get_qr_code(instance_name)
59
+ get("/instance/connect/#{instance_name}")
60
+ end
61
+
62
+ # ==================== MENSAGENS ====================
63
+
64
+ # Envia uma mensagem de texto
65
+ def send_text_message(instance_name, number, text, options = {})
66
+ body = {
67
+ number: number,
68
+ text: text,
69
+ options: options
70
+ }
71
+
72
+ post("/message/sendText/#{instance_name}", body)
73
+ end
74
+
75
+ # Envia uma mensagem de imagem
76
+ def send_image_message(instance_name, number, image_url, caption = nil, options = {})
77
+ body = {
78
+ number: number,
79
+ image: image_url,
80
+ caption: caption,
81
+ options: options
82
+ }.compact
83
+
84
+ post("/message/sendImage/#{instance_name}", body)
85
+ end
86
+
87
+ # Envia uma mensagem de áudio
88
+ def send_audio_message(instance_name, number, audio_url, options = {})
89
+ body = {
90
+ number: number,
91
+ audio: audio_url,
92
+ options: options
93
+ }
94
+
95
+ post("/message/sendAudio/#{instance_name}", body)
96
+ end
97
+
98
+ # Envia uma mensagem de vídeo
99
+ def send_video_message(instance_name, number, video_url, caption = nil, options = {})
100
+ body = {
101
+ number: number,
102
+ video: video_url,
103
+ caption: caption,
104
+ options: options
105
+ }.compact
106
+
107
+ post("/message/sendVideo/#{instance_name}", body)
108
+ end
109
+
110
+ # Envia um documento
111
+ def send_document_message(instance_name, number, document_url, caption = nil, options = {})
112
+ body = {
113
+ number: number,
114
+ document: document_url,
115
+ caption: caption,
116
+ options: options
117
+ }.compact
118
+
119
+ post("/message/sendDocument/#{instance_name}", body)
120
+ end
121
+
122
+ # Envia uma localização
123
+ def send_location_message(instance_name, number, latitude, longitude, description = nil)
124
+ body = {
125
+ number: number,
126
+ latitude: latitude,
127
+ longitude: longitude,
128
+ description: description
129
+ }.compact
130
+
131
+ post("/message/sendLocation/#{instance_name}", body)
132
+ end
133
+
134
+ # Envia uma mensagem de contato
135
+ def send_contact_message(instance_name, number, contact_number, contact_name)
136
+ body = {
137
+ number: number,
138
+ contacts: [{
139
+ number: contact_number,
140
+ name: contact_name
141
+ }]
142
+ }
143
+
144
+ post("/message/sendContact/#{instance_name}", body)
145
+ end
146
+
147
+ # Envia uma mensagem de botão
148
+ def send_button_message(instance_name, number, title, description, buttons)
149
+ body = {
150
+ number: number,
151
+ title: title,
152
+ description: description,
153
+ buttons: buttons
154
+ }
155
+
156
+ post("/message/sendButton/#{instance_name}", body)
157
+ end
158
+
159
+ # Envia uma lista de opções
160
+ def send_list_message(instance_name, number, title, description, sections)
161
+ body = {
162
+ number: number,
163
+ title: title,
164
+ description: description,
165
+ sections: sections
166
+ }
167
+
168
+ post("/message/sendList/#{instance_name}", body)
169
+ end
170
+
171
+ # ==================== CHAT ====================
172
+
173
+ # Obtém chats de uma instância
174
+ def get_chats(instance_name)
175
+ get("/chat/findChats/#{instance_name}")
176
+ end
177
+
178
+ # Obtém mensagens de um chat
179
+ def get_messages(instance_name, number, options = {})
180
+ params = {
181
+ limit: options[:limit] || 50,
182
+ cursor: options[:cursor]
183
+ }.compact
184
+
185
+ get("/chat/findMessages/#{instance_name}/#{number}", params)
186
+ end
187
+
188
+ # Marca mensagens como lidas
189
+ def mark_messages_as_read(instance_name, number)
190
+ post("/chat/markMessageAsRead/#{instance_name}", { number: number })
191
+ end
192
+
193
+ # Arquivar chat
194
+ def archive_chat(instance_name, number)
195
+ post("/chat/archiveChat/#{instance_name}", { number: number })
196
+ end
197
+
198
+ # Desarquivar chat
199
+ def unarchive_chat(instance_name, number)
200
+ post("/chat/unarchiveChat/#{instance_name}", { number: number })
201
+ end
202
+
203
+ # Deletar chat
204
+ def delete_chat(instance_name, number)
205
+ delete("/chat/deleteChat/#{instance_name}/#{number}")
206
+ end
207
+
208
+ # ==================== CONTATOS ====================
209
+
210
+ # Obtém contatos de uma instância
211
+ def get_contacts(instance_name)
212
+ get("/contact/findContacts/#{instance_name}")
213
+ end
214
+
215
+ # Obtém informações de um contato
216
+ def get_contact(instance_name, number)
217
+ get("/contact/findContact/#{instance_name}/#{number}")
218
+ end
219
+
220
+ # Verifica se um número existe no WhatsApp
221
+ def check_number(instance_name, number)
222
+ post("/contact/checkNumber/#{instance_name}", { number: number })
223
+ end
224
+
225
+ # Bloqueia um contato
226
+ def block_contact(instance_name, number)
227
+ post("/contact/blockContact/#{instance_name}", { number: number })
228
+ end
229
+
230
+ # Desbloqueia um contato
231
+ def unblock_contact(instance_name, number)
232
+ post("/contact/unblockContact/#{instance_name}", { number: number })
233
+ end
234
+
235
+ # ==================== WEBHOOK ====================
236
+
237
+ # Configura webhook para uma instância
238
+ def set_webhook(instance_name, webhook_url, events = nil)
239
+ body = {
240
+ webhook: webhook_url,
241
+ webhookByEvents: events ? true : false,
242
+ webhookBase64: false
243
+ }
244
+
245
+ body[:events] = events if events
246
+
247
+ post("/webhook/set/#{instance_name}", body)
248
+ end
249
+
250
+ # Obtém configuração de webhook
251
+ def get_webhook(instance_name)
252
+ get("/webhook/find/#{instance_name}")
253
+ end
254
+
255
+ # Remove webhook
256
+ def delete_webhook(instance_name)
257
+ delete("/webhook/del/#{instance_name}")
258
+ end
259
+
260
+ # ==================== MÉTODOS HTTP ====================
261
+
262
+ private
263
+
264
+ def setup_http_client
265
+ self.class.base_uri config.base_url
266
+ self.class.headers default_headers
267
+ end
268
+
269
+ def default_headers
270
+ headers = {
271
+ 'Content-Type' => 'application/json',
272
+ 'Accept' => 'application/json'
273
+ }
274
+
275
+ headers['apikey'] = config.api_key if config.api_key
276
+ headers
277
+ end
278
+
279
+ def get(path, params = {})
280
+ make_request(:get, path, params: params)
281
+ end
282
+
283
+ def post(path, body = {})
284
+ make_request(:post, path, body: body)
285
+ end
286
+
287
+ def put(path, body = {})
288
+ make_request(:put, path, body: body)
289
+ end
290
+
291
+ def delete(path)
292
+ make_request(:delete, path)
293
+ end
294
+
295
+ def make_request(method, path, options = {})
296
+ retries = 0
297
+ begin
298
+ response = self.class.public_send(method, path, options)
299
+ handle_response(response)
300
+ rescue HTTParty::Error => e
301
+ retries += 1
302
+ if retries <= config.retry_attempts
303
+ sleep(config.retry_delay)
304
+ retry
305
+ else
306
+ raise ConnectionError, "Erro de conexão após #{config.retry_attempts} tentativas: #{e.message}"
307
+ end
308
+ rescue Net::ReadTimeout, Net::OpenTimeout
309
+ raise TimeoutError, "Timeout na requisição para #{path}"
310
+ end
311
+ end
312
+
313
+ def handle_response(response)
314
+ case response.code
315
+ when 200, 201
316
+ parse_response(response)
317
+ when 401
318
+ raise AuthenticationError.new('Erro de autenticação', response)
319
+ when 403
320
+ raise AuthorizationError.new('Acesso negado', response)
321
+ when 404
322
+ raise NotFoundError.new('Recurso não encontrado', response)
323
+ when 422
324
+ raise ValidationError.new('Erro de validação', response, parse_errors(response))
325
+ when 429
326
+ raise RateLimitError.new('Limite de requisições excedido', response)
327
+ when 500..599
328
+ raise ServerError.new('Erro interno do servidor', response)
329
+ else
330
+ raise Error.new("Erro inesperado: #{response.code}", response, response.code)
331
+ end
332
+ end
333
+
334
+ def parse_response(response)
335
+ return nil if response.body.nil? || response.body.empty?
336
+
337
+ JSON.parse(response.body)
338
+ rescue JSON::ParserError
339
+ response.body
340
+ end
341
+
342
+ def parse_errors(response)
343
+ return {} unless response.body
344
+
345
+ parsed = JSON.parse(response.body)
346
+ parsed['errors'] || parsed
347
+ rescue JSON::ParserError
348
+ { 'body' => response.body }
349
+ end
350
+ end
351
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EvolutionApi
4
+ # Classe para representar contatos do WhatsApp
5
+ class Contact
6
+ attr_reader :id, :name, :push_name, :verified_name, :is_business, :is_enterprise, :is_high_level_verified,
7
+ :instance_name
8
+
9
+ def initialize(data, instance_name = nil)
10
+ @id = data['id']
11
+ @name = data['name']
12
+ @push_name = data['pushName']
13
+ @verified_name = data['verifiedName']
14
+ @is_business = data['isBusiness']
15
+ @is_enterprise = data['isEnterprise']
16
+ @is_high_level_verified = data['isHighLevelVerified']
17
+ @instance_name = instance_name
18
+ end
19
+
20
+ # Verifica se é uma conta business
21
+ def business?
22
+ is_business == true
23
+ end
24
+
25
+ # Verifica se é uma conta enterprise
26
+ def enterprise?
27
+ is_enterprise == true
28
+ end
29
+
30
+ # Verifica se é uma conta verificada de alto nível
31
+ def high_level_verified?
32
+ is_high_level_verified == true
33
+ end
34
+
35
+ # Obtém o número do contato (remove sufixos)
36
+ def number
37
+ return nil unless id
38
+
39
+ id.split('@').first
40
+ end
41
+
42
+ # Obtém o nome de exibição (prioriza nome verificado, depois push name, depois nome)
43
+ def display_name
44
+ verified_name || push_name || name || number
45
+ end
46
+
47
+ # Verifica se tem nome verificado
48
+ def verified?
49
+ !verified_name.nil? && !verified_name.empty?
50
+ end
51
+
52
+ # Converte para hash
53
+ def to_h
54
+ {
55
+ id: id,
56
+ number: number,
57
+ name: name,
58
+ push_name: push_name,
59
+ verified_name: verified_name,
60
+ display_name: display_name,
61
+ is_business: business?,
62
+ is_enterprise: enterprise?,
63
+ is_high_level_verified: high_level_verified?,
64
+ verified: verified?,
65
+ instance_name: instance_name
66
+ }
67
+ end
68
+
69
+ # Converte para JSON
70
+ def to_json(*args)
71
+ to_h.to_json(*args)
72
+ end
73
+ end
74
+ end