| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | const axios = require('axios'); |
| | const ConfigManager = require('./ConfigManager'); |
| |
|
| | class APIClient { |
| | constructor(logger = null) { |
| | this.config = ConfigManager.getInstance(); |
| | this.logger = logger || console; |
| | this.requestCount = 0; |
| | this.errorCount = 0; |
| | } |
| |
|
| | |
| | |
| | |
| | buildPayload(messageData) { |
| | const { |
| | usuario, |
| | numero, |
| | mensagem, |
| | tipo_conversa = 'pv', |
| | tipo_mensagem = 'texto', |
| | mensagem_citada = '', |
| | reply_metadata = {}, |
| | imagem_dados = null, |
| | grupo_id = null, |
| | grupo_nome = null, |
| | forcar_pesquisa = false |
| | } = messageData; |
| |
|
| | const payload = { |
| | usuario: String(usuario || 'anonimo').substring(0, 50), |
| | numero: String(numero || 'desconhecido').substring(0, 20), |
| | mensagem: String(mensagem || '').substring(0, 2000), |
| | tipo_conversa: ['pv', 'grupo'].includes(tipo_conversa) ? tipo_conversa : 'pv', |
| | tipo_mensagem: ['texto', 'image', 'audio', 'video'].includes(tipo_mensagem) ? tipo_mensagem : 'texto', |
| | historico: [], |
| | forcar_busca: Boolean(forcar_pesquisa) |
| | }; |
| |
|
| | |
| | if (mensagem_citada) { |
| | payload.mensagem_citada = String(mensagem_citada).substring(0, 500); |
| | payload.reply_metadata = { |
| | is_reply: true, |
| | reply_to_bot: Boolean(reply_metadata.reply_to_bot), |
| | quoted_author_name: String(reply_metadata.quoted_author_name || 'desconhecido').substring(0, 50), |
| | quoted_author_numero: String(reply_metadata.quoted_author_numero || 'desconhecido'), |
| | quoted_type: String(reply_metadata.quoted_type || 'texto'), |
| | quoted_text_original: String(reply_metadata.quoted_text_original || '').substring(0, 200), |
| | context_hint: String(reply_metadata.context_hint || '') |
| | }; |
| | } else { |
| | payload.reply_metadata = { |
| | is_reply: false, |
| | reply_to_bot: false |
| | }; |
| | } |
| |
|
| | |
| | if (imagem_dados && imagem_dados.dados) { |
| | payload.imagem = { |
| | dados: imagem_dados.dados, |
| | mime_type: imagem_dados.mime_type || 'image/jpeg', |
| | descricao: imagem_dados.descricao || 'Imagem enviada', |
| | analise_visao: imagem_dados.analise_visao || {} |
| | }; |
| | } |
| |
|
| | |
| | if (grupo_id) { |
| | payload.grupo_id = grupo_id; |
| | payload.contexto_grupo = grupo_nome || 'Grupo'; |
| | } |
| |
|
| | return payload; |
| | } |
| |
|
| | |
| | |
| | |
| | async request(method, endpoint, data = null, options = {}) { |
| | const url = `${this.config.API_URL}${endpoint}`; |
| | const maxRetries = options.retries || this.config.API_RETRY_ATTEMPTS; |
| | let lastError = null; |
| |
|
| | for (let attempt = 1; attempt <= maxRetries; attempt++) { |
| | try { |
| | this.requestCount++; |
| |
|
| | if (this.config.LOG_API_REQUESTS) { |
| | this.logger.info(`[API] ${method.toUpperCase()} ${endpoint} (tentativa ${attempt}/${maxRetries})`); |
| | } |
| |
|
| | const axiosConfig = { |
| | method, |
| | url, |
| | timeout: this.config.API_TIMEOUT, |
| | headers: { |
| | 'Content-Type': 'application/json', |
| | 'User-Agent': `AkiraBot/${this.config.BOT_VERSION}` |
| | }, |
| | ...options |
| | }; |
| |
|
| | if (data) { |
| | axiosConfig.data = data; |
| | } |
| |
|
| | const response = await axios(axiosConfig); |
| |
|
| | if (response.status >= 200 && response.status < 300) { |
| | if (this.config.LOG_API_REQUESTS) { |
| | this.logger.info(`[API] β
${endpoint} (${response.status})`); |
| | } |
| | return { success: true, data: response.data, status: response.status }; |
| | } |
| |
|
| | } catch (error) { |
| | lastError = error; |
| | const statusCode = error.response?.status; |
| | const errorMsg = error.response?.data?.error || error.message; |
| |
|
| | if (this.config.LOG_API_REQUESTS) { |
| | this.logger.warn(`[API] β οΈ Erro ${statusCode || 'NETWORK'}: ${errorMsg} (tentativa ${attempt}/${maxRetries})`); |
| | } |
| |
|
| | |
| | if (statusCode >= 400 && statusCode < 500 && statusCode !== 408) { |
| | this.errorCount++; |
| | return { success: false, error: errorMsg, status: statusCode }; |
| | } |
| |
|
| | |
| | if (attempt < maxRetries) { |
| | const delayMs = this.config.API_RETRY_DELAY * Math.pow(2, attempt - 1); |
| | await new Promise(resolve => setTimeout(resolve, delayMs)); |
| | } |
| | } |
| | } |
| |
|
| | this.errorCount++; |
| | const errorMsg = lastError?.response?.data?.error || lastError?.message || 'Erro desconhecido'; |
| | |
| | if (this.config.LOG_API_REQUESTS) { |
| | this.logger.error(`[API] β Falhou apΓ³s ${maxRetries} tentativas: ${errorMsg}`); |
| | } |
| |
|
| | return { success: false, error: errorMsg, lastError }; |
| | } |
| |
|
| | |
| | |
| | |
| | async processMessage(messageData) { |
| | try { |
| | const payload = this.buildPayload(messageData); |
| |
|
| | const result = await this.request('POST', '/akira', payload); |
| |
|
| | if (result.success) { |
| | return { |
| | success: true, |
| | resposta: result.data?.resposta || 'Sem resposta', |
| | tipo_mensagem: result.data?.tipo_mensagem || 'texto', |
| | pesquisa_feita: result.data?.pesquisa_feita || false, |
| | metadata: result.data |
| | }; |
| | } else { |
| | return { |
| | success: false, |
| | resposta: 'Eita! Tive um problema aqui. Tenta de novo em um segundo?', |
| | error: result.error |
| | }; |
| | } |
| | } catch (error) { |
| | this.logger.error('[API] Erro ao processar mensagem:', error.message); |
| | return { |
| | success: false, |
| | resposta: 'Deu um erro interno aqui. Tenta depois?', |
| | error: error.message |
| | }; |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | async analyzeImage(imageBase64, usuario = 'anonimo', numero = '') { |
| | try { |
| | const result = await this.request('POST', '/vision/analyze', { |
| | imagem: imageBase64, |
| | usuario, |
| | numero, |
| | include_ocr: true, |
| | include_shapes: true, |
| | include_objects: true |
| | }); |
| |
|
| | if (result.success) { |
| | return { |
| | success: true, |
| | analise: result.data |
| | }; |
| | } else { |
| | return { |
| | success: false, |
| | error: result.error |
| | }; |
| | } |
| | } catch (error) { |
| | this.logger.error('[VISION] Erro ao analisar imagem:', error.message); |
| | return { |
| | success: false, |
| | error: error.message |
| | }; |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | async performOCR(imageBase64, numero = '') { |
| | try { |
| | const result = await this.request('POST', '/vision/ocr', { |
| | imagem: imageBase64, |
| | numero |
| | }); |
| |
|
| | if (result.success) { |
| | return { |
| | success: true, |
| | text: result.data?.text || '', |
| | confidence: result.data?.confidence || 0, |
| | word_count: result.data?.word_count || 0 |
| | }; |
| | } else { |
| | return { |
| | success: false, |
| | error: result.error |
| | }; |
| | } |
| | } catch (error) { |
| | this.logger.error('[OCR] Erro ao fazer OCR:', error.message); |
| | return { |
| | success: false, |
| | error: error.message |
| | }; |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | async reset(usuario = null) { |
| | try { |
| | const payload = usuario ? { usuario } : {}; |
| | const result = await this.request('POST', '/reset', payload); |
| |
|
| | return { |
| | success: result.success, |
| | status: result.data?.status || 'reset_attempted', |
| | message: result.data?.message || 'Reset solicitado' |
| | }; |
| | } catch (error) { |
| | this.logger.error('[RESET] Erro ao fazer reset:', error.message); |
| | return { |
| | success: false, |
| | error: error.message |
| | }; |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | async healthCheck() { |
| | try { |
| | const result = await this.request('GET', '/health'); |
| | return { |
| | success: result.success, |
| | status: result.data?.status || 'unknown', |
| | version: result.data?.version || 'unknown' |
| | }; |
| | } catch (error) { |
| | return { |
| | success: false, |
| | status: 'down', |
| | error: error.message |
| | }; |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | getStats() { |
| | return { |
| | totalRequests: this.requestCount, |
| | totalErrors: this.errorCount, |
| | errorRate: this.requestCount > 0 ? (this.errorCount / this.requestCount * 100).toFixed(2) + '%' : '0%' |
| | }; |
| | } |
| | } |
| |
|
| | module.exports = APIClient; |
| |
|