|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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; |
|
|
|