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.
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EvolutionApi
4
+ # Classe base para todos os erros da Evolution API
5
+ class Error < StandardError
6
+ attr_reader :response, :status_code, :error_code
7
+
8
+ def initialize(message = nil, response = nil, status_code = nil, error_code = nil)
9
+ super(message)
10
+ @response = response
11
+ @status_code = status_code
12
+ @error_code = error_code
13
+ end
14
+ end
15
+
16
+ # Erro de autenticação
17
+ class AuthenticationError < Error
18
+ def initialize(message = 'Erro de autenticação', response = nil)
19
+ super(message, response, 401)
20
+ end
21
+ end
22
+
23
+ # Erro de autorização
24
+ class AuthorizationError < Error
25
+ def initialize(message = 'Acesso negado', response = nil)
26
+ super(message, response, 403)
27
+ end
28
+ end
29
+
30
+ # Erro de recurso não encontrado
31
+ class NotFoundError < Error
32
+ def initialize(message = 'Recurso não encontrado', response = nil)
33
+ super(message, response, 404)
34
+ end
35
+ end
36
+
37
+ # Erro de validação
38
+ class ValidationError < Error
39
+ attr_reader :errors
40
+
41
+ def initialize(message = 'Erro de validação', response = nil, errors = {})
42
+ super(message, response, 422)
43
+ @errors = errors
44
+ end
45
+ end
46
+
47
+ # Erro de rate limit
48
+ class RateLimitError < Error
49
+ def initialize(message = 'Limite de requisições excedido', response = nil)
50
+ super(message, response, 429)
51
+ end
52
+ end
53
+
54
+ # Erro de servidor
55
+ class ServerError < Error
56
+ def initialize(message = 'Erro interno do servidor', response = nil)
57
+ super(message, response, 500)
58
+ end
59
+ end
60
+
61
+ # Erro de timeout
62
+ class TimeoutError < Error
63
+ def initialize(message = 'Timeout na requisição', response = nil)
64
+ super(message, response, nil)
65
+ end
66
+ end
67
+
68
+ # Erro de conexão
69
+ class ConnectionError < Error
70
+ def initialize(message = 'Erro de conexão', response = nil)
71
+ super(message, response, nil)
72
+ end
73
+ end
74
+
75
+ # Erro de instância não conectada
76
+ class InstanceNotConnectedError < Error
77
+ def initialize(instance_name)
78
+ super("Instância '#{instance_name}' não está conectada", nil, nil, 'INSTANCE_NOT_CONNECTED')
79
+ end
80
+ end
81
+
82
+ # Erro de QR Code expirado
83
+ class QRCodeExpiredError < Error
84
+ def initialize
85
+ super('QR Code expirado', nil, nil, 'QR_CODE_EXPIRED')
86
+ end
87
+ end
88
+
89
+ # Erro de número inválido
90
+ class InvalidNumberError < Error
91
+ def initialize(number)
92
+ super("Número '#{number}' é inválido", nil, nil, 'INVALID_NUMBER')
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EvolutionApi
4
+ # Classe para gerenciar instâncias do WhatsApp
5
+ class Instance
6
+ attr_reader :name, :client
7
+
8
+ def initialize(name, client)
9
+ @name = name
10
+ @client = client
11
+ end
12
+
13
+ # Obtém informações da instância
14
+ def info
15
+ client.get_instance(name)
16
+ end
17
+
18
+ # Conecta a instância
19
+ def connect
20
+ client.connect_instance(name)
21
+ end
22
+
23
+ # Desconecta a instância
24
+ def disconnect
25
+ client.disconnect_instance(name)
26
+ end
27
+
28
+ # Remove a instância
29
+ def delete
30
+ client.delete_instance(name)
31
+ end
32
+
33
+ # Obtém QR Code para conexão
34
+ def qr_code
35
+ client.get_qr_code(name)
36
+ end
37
+
38
+ # Verifica se a instância está conectada
39
+ def connected?
40
+ info = self.info
41
+ info['status'] == 'open'
42
+ rescue StandardError
43
+ false
44
+ end
45
+
46
+ # Envia uma mensagem de texto
47
+ def send_text(number, text, options = {})
48
+ client.send_text_message(name, number, text, options)
49
+ end
50
+
51
+ # Envia uma mensagem de imagem
52
+ def send_image(number, image_url, caption = nil, options = {})
53
+ client.send_image_message(name, number, image_url, caption, options)
54
+ end
55
+
56
+ # Envia uma mensagem de áudio
57
+ def send_audio(number, audio_url, options = {})
58
+ client.send_audio_message(name, number, audio_url, options)
59
+ end
60
+
61
+ # Envia uma mensagem de vídeo
62
+ def send_video(number, video_url, caption = nil, options = {})
63
+ client.send_video_message(name, number, video_url, caption, options)
64
+ end
65
+
66
+ # Envia um documento
67
+ def send_document(number, document_url, caption = nil, options = {})
68
+ client.send_document_message(name, number, document_url, caption, options)
69
+ end
70
+
71
+ # Envia uma localização
72
+ def send_location(number, latitude, longitude, description = nil)
73
+ client.send_location_message(name, number, latitude, longitude, description)
74
+ end
75
+
76
+ # Envia um contato
77
+ def send_contact(number, contact_number, contact_name)
78
+ client.send_contact_message(name, number, contact_number, contact_name)
79
+ end
80
+
81
+ # Envia uma mensagem com botões
82
+ def send_button(number, title, description, buttons)
83
+ client.send_button_message(name, number, title, description, buttons)
84
+ end
85
+
86
+ # Envia uma lista de opções
87
+ def send_list(number, title, description, sections)
88
+ client.send_list_message(name, number, title, description, sections)
89
+ end
90
+
91
+ # Obtém chats da instância
92
+ def chats
93
+ client.get_chats(name)
94
+ end
95
+
96
+ # Obtém mensagens de um chat
97
+ def messages(number, options = {})
98
+ client.get_messages(name, number, options)
99
+ end
100
+
101
+ # Marca mensagens como lidas
102
+ def mark_as_read(number)
103
+ client.mark_messages_as_read(name, number)
104
+ end
105
+
106
+ # Arquivar chat
107
+ def archive_chat(number)
108
+ client.archive_chat(name, number)
109
+ end
110
+
111
+ # Desarquivar chat
112
+ def unarchive_chat(number)
113
+ client.unarchive_chat(name, number)
114
+ end
115
+
116
+ # Deletar chat
117
+ def delete_chat(number)
118
+ client.delete_chat(name, number)
119
+ end
120
+
121
+ # Obtém contatos da instância
122
+ def contacts
123
+ client.get_contacts(name)
124
+ end
125
+
126
+ # Obtém informações de um contato
127
+ def contact(number)
128
+ client.get_contact(name, number)
129
+ end
130
+
131
+ # Verifica se um número existe no WhatsApp
132
+ def check_number(number)
133
+ client.check_number(name, number)
134
+ end
135
+
136
+ # Bloqueia um contato
137
+ def block_contact(number)
138
+ client.block_contact(name, number)
139
+ end
140
+
141
+ # Desbloqueia um contato
142
+ def unblock_contact(number)
143
+ client.unblock_contact(name, number)
144
+ end
145
+
146
+ # Configura webhook
147
+ def set_webhook(webhook_url, events = nil)
148
+ client.set_webhook(name, webhook_url, events)
149
+ end
150
+
151
+ # Obtém configuração de webhook
152
+ def webhook
153
+ client.get_webhook(name)
154
+ end
155
+
156
+ # Remove webhook
157
+ def delete_webhook
158
+ client.delete_webhook(name)
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,249 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EvolutionApi
4
+ # Classe para representar mensagens do WhatsApp
5
+ class Message
6
+ attr_reader :id, :key, :message, :message_timestamp, :status, :participant, :instance_name
7
+
8
+ def initialize(data, instance_name = nil)
9
+ @id = data['id']
10
+ @key = data['key']
11
+ @message = data['message']
12
+ @message_timestamp = data['messageTimestamp']
13
+ @status = data['status']
14
+ @participant = data['participant']
15
+ @instance_name = instance_name
16
+ end
17
+
18
+ # Verifica se é uma mensagem de texto
19
+ def text?
20
+ message&.key?('conversation') || message&.key?('extendedTextMessage')
21
+ end
22
+
23
+ # Obtém o texto da mensagem
24
+ def text
25
+ return message['conversation'] if message&.key?('conversation')
26
+ return message['extendedTextMessage']['text'] if message&.key?('extendedTextMessage')
27
+
28
+ nil
29
+ end
30
+
31
+ # Verifica se é uma mensagem de imagem
32
+ def image?
33
+ message&.key?('imageMessage')
34
+ end
35
+
36
+ # Obtém informações da imagem
37
+ def image
38
+ return nil unless image?
39
+
40
+ message['imageMessage']
41
+ end
42
+
43
+ # Verifica se é uma mensagem de áudio
44
+ def audio?
45
+ message&.key?('audioMessage')
46
+ end
47
+
48
+ # Obtém informações do áudio
49
+ def audio
50
+ return nil unless audio?
51
+
52
+ message['audioMessage']
53
+ end
54
+
55
+ # Verifica se é uma mensagem de vídeo
56
+ def video?
57
+ message&.key?('videoMessage')
58
+ end
59
+
60
+ # Obtém informações do vídeo
61
+ def video
62
+ return nil unless video?
63
+
64
+ message['videoMessage']
65
+ end
66
+
67
+ # Verifica se é um documento
68
+ def document?
69
+ message&.key?('documentMessage')
70
+ end
71
+
72
+ # Obtém informações do documento
73
+ def document
74
+ return nil unless document?
75
+
76
+ message['documentMessage']
77
+ end
78
+
79
+ # Verifica se é uma localização
80
+ def location?
81
+ message&.key?('locationMessage')
82
+ end
83
+
84
+ # Obtém informações da localização
85
+ def location
86
+ return nil unless location?
87
+
88
+ message['locationMessage']
89
+ end
90
+
91
+ # Verifica se é um contato
92
+ def contact?
93
+ message&.key?('contactMessage')
94
+ end
95
+
96
+ # Obtém informações do contato
97
+ def contact
98
+ return nil unless contact?
99
+
100
+ message['contactMessage']
101
+ end
102
+
103
+ # Verifica se é uma mensagem de botão
104
+ def button?
105
+ message&.key?('buttonsResponseMessage') || message&.key?('buttonMessage')
106
+ end
107
+
108
+ # Obtém informações do botão
109
+ def button
110
+ return message['buttonsResponseMessage'] if message&.key?('buttonsResponseMessage')
111
+ return message['buttonMessage'] if message&.key?('buttonMessage')
112
+
113
+ nil
114
+ end
115
+
116
+ # Verifica se é uma lista
117
+ def list?
118
+ message&.key?('listResponseMessage') || message&.key?('listMessage')
119
+ end
120
+
121
+ # Obtém informações da lista
122
+ def list
123
+ return message['listResponseMessage'] if message&.key?('listResponseMessage')
124
+ return message['listMessage'] if message&.key?('listMessage')
125
+
126
+ nil
127
+ end
128
+
129
+ # Verifica se é uma mensagem de reação
130
+ def reaction?
131
+ message&.key?('reactionMessage')
132
+ end
133
+
134
+ # Obtém informações da reação
135
+ def reaction
136
+ return nil unless reaction?
137
+
138
+ message['reactionMessage']
139
+ end
140
+
141
+ # Verifica se é uma mensagem de sticker
142
+ def sticker?
143
+ message&.key?('stickerMessage')
144
+ end
145
+
146
+ # Obtém informações do sticker
147
+ def sticker
148
+ return nil unless sticker?
149
+
150
+ message['stickerMessage']
151
+ end
152
+
153
+ # Obtém o número do remetente
154
+ def from
155
+ key['remoteJid']&.split('@')&.first
156
+ end
157
+
158
+ # Obtém o ID da mensagem
159
+ def message_id
160
+ key['id']
161
+ end
162
+
163
+ # Verifica se é uma mensagem de grupo
164
+ def group?
165
+ key['remoteJid']&.include?('@g.us')
166
+ end
167
+
168
+ # Verifica se é uma mensagem de broadcast
169
+ def broadcast?
170
+ key['remoteJid']&.include?('@broadcast')
171
+ end
172
+
173
+ # Verifica se é uma mensagem privada
174
+ def private?
175
+ !group? && !broadcast?
176
+ end
177
+
178
+ # Verifica se a mensagem foi enviada pelo próprio usuário
179
+ def from_me?
180
+ key['fromMe'] == true
181
+ end
182
+
183
+ # Obtém o timestamp da mensagem
184
+ def timestamp
185
+ Time.at(message_timestamp) if message_timestamp
186
+ end
187
+
188
+ # Verifica se a mensagem foi lida
189
+ def read?
190
+ status == 'read'
191
+ end
192
+
193
+ # Verifica se a mensagem foi entregue
194
+ def delivered?
195
+ status == 'delivered'
196
+ end
197
+
198
+ # Verifica se a mensagem foi enviada
199
+ def sent?
200
+ status == 'sent'
201
+ end
202
+
203
+ # Verifica se a mensagem falhou
204
+ def failed?
205
+ status == 'failed'
206
+ end
207
+
208
+ # Obtém o tipo da mensagem
209
+ def type
210
+ return 'text' if text?
211
+ return 'image' if image?
212
+ return 'audio' if audio?
213
+ return 'video' if video?
214
+ return 'document' if document?
215
+ return 'location' if location?
216
+ return 'contact' if contact?
217
+ return 'button' if button?
218
+ return 'list' if list?
219
+ return 'reaction' if reaction?
220
+ return 'sticker' if sticker?
221
+
222
+ 'unknown'
223
+ end
224
+
225
+ # Converte para hash
226
+ def to_h
227
+ {
228
+ id: id,
229
+ key: key,
230
+ message: message,
231
+ message_timestamp: message_timestamp,
232
+ status: status,
233
+ participant: participant,
234
+ instance_name: instance_name,
235
+ type: type,
236
+ from: from,
237
+ from_me: from_me?,
238
+ group: group?,
239
+ timestamp: timestamp,
240
+ text: text
241
+ }
242
+ end
243
+
244
+ # Converte para JSON
245
+ def to_json(*args)
246
+ to_h.to_json(*args)
247
+ end
248
+ end
249
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EvolutionApi
4
+ VERSION = '1.0.0'
5
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EvolutionApi
4
+ # Classe para gerenciar webhooks da Evolution API
5
+ class Webhook
6
+ attr_reader :url, :events, :instance_name
7
+
8
+ def initialize(data, instance_name = nil)
9
+ @url = data['webhook']
10
+ @events = data['events'] || []
11
+ @instance_name = instance_name
12
+ end
13
+
14
+ # Verifica se o webhook está configurado
15
+ def configured?
16
+ !url.nil? && !url.empty?
17
+ end
18
+
19
+ # Verifica se um evento específico está habilitado
20
+ def event_enabled?(event)
21
+ events.include?(event)
22
+ end
23
+
24
+ # Lista todos os eventos habilitados
25
+ def enabled_events
26
+ events.dup
27
+ end
28
+
29
+ # Converte para hash
30
+ def to_h
31
+ {
32
+ url: url,
33
+ events: events,
34
+ configured: configured?,
35
+ instance_name: instance_name
36
+ }
37
+ end
38
+
39
+ # Converte para JSON
40
+ def to_json(*args)
41
+ to_h.to_json(*args)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'httparty'
4
+ require 'json'
5
+ require 'dry/configurable'
6
+ require 'dry/validation'
7
+
8
+ require_relative 'evolution_api/version'
9
+ require_relative 'evolution_api/client'
10
+ require_relative 'evolution_api/instance'
11
+ require_relative 'evolution_api/message'
12
+ require_relative 'evolution_api/chat'
13
+ require_relative 'evolution_api/contact'
14
+ require_relative 'evolution_api/webhook'
15
+ require_relative 'evolution_api/errors'
16
+
17
+ # Evolution API Ruby Client
18
+ #
19
+ # Uma gem Ruby para consumir facilmente a Evolution API,
20
+ # permitindo integração com WhatsApp através de uma API REST simples e poderosa.
21
+ #
22
+ # @example Configuração básica
23
+ # EvolutionApi.configure do |config|
24
+ # config.base_url = "http://localhost:8080"
25
+ # config.api_key = "sua_api_key_aqui"
26
+ # end
27
+ #
28
+ # @example Uso básico
29
+ # client = EvolutionApi::Client.new
30
+ # instances = client.list_instances
31
+ # client.send_message("instance_name", "5511999999999", "Olá!")
32
+ #
33
+ # @see https://doc.evolution-api.com/ Evolution API Documentation
34
+ module EvolutionApi
35
+ extend Dry::Configurable
36
+
37
+ # Configurações padrão
38
+ setting :base_url, default: 'http://localhost:8080'
39
+ setting :api_key, default: nil
40
+ setting :timeout, default: 30
41
+ setting :retry_attempts, default: 3
42
+ setting :retry_delay, default: 1
43
+
44
+ # Configuração de webhooks
45
+ setting :webhook_url, default: nil
46
+ setting :webhook_events, default: %w[connection.update message.upsert]
47
+
48
+ # Configuração de logs
49
+ setting :logger, default: nil
50
+ setting :log_level, default: :info
51
+
52
+ # Configuração de cache
53
+ setting :cache_enabled, default: false
54
+ setting :cache_ttl, default: 300 # 5 minutos
55
+
56
+ class << self
57
+ # Configura a gem com as opções fornecidas
58
+ #
59
+ # @param options [Hash] Opções de configuração
60
+ # @yield [config] Bloco para configuração
61
+ # @yieldparam config [Dry::Configurable::Config] Objeto de configuração
62
+ #
63
+ # @example
64
+ # EvolutionApi.configure do |config|
65
+ # config.base_url = "https://api.evolution.com"
66
+ # config.api_key = "sua_chave_api"
67
+ # config.timeout = 60
68
+ # end
69
+ def configure(options = {})
70
+ options.each { |key, value| config.public_send("#{key}=", value) }
71
+ yield config if block_given?
72
+ end
73
+
74
+ # Retorna um novo cliente configurado
75
+ #
76
+ # @return [EvolutionApi::Client] Cliente configurado
77
+ def client
78
+ @client ||= Client.new
79
+ end
80
+
81
+ # Reseta o cliente (útil para testes)
82
+ def reset_client!
83
+ @client = nil
84
+ end
85
+ end
86
+ end