| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | const { getContentType } = require('@whiskeysockets/baileys'); |
| | const ConfigManager = require('./ConfigManager'); |
| |
|
| | let parsePhoneNumberFromString = null; |
| | try { |
| | |
| | ({ parsePhoneNumberFromString } = require('libphonenumber-js')); |
| | } catch (e) { |
| | |
| | } |
| |
|
| | class MessageProcessor { |
| | constructor(logger = null) { |
| | this.config = ConfigManager.getInstance(); |
| | this.logger = logger || console; |
| | } |
| |
|
| | |
| | |
| | |
| | extractUserNumber(message) { |
| | try { |
| | const key = message.key || {}; |
| | const remoteJid = key.remoteJid || ''; |
| |
|
| | |
| | if (!String(remoteJid).endsWith('@g.us')) { |
| | return String(remoteJid).split('@')[0]; |
| | } |
| |
|
| | |
| | if (key.participant) { |
| | const participant = String(key.participant); |
| | if (participant.includes('@s.whatsapp.net')) { |
| | return participant.split('@')[0]; |
| | } |
| | if (participant.includes('@lid')) { |
| | const limpo = participant.split(':')[0]; |
| | const digitos = limpo.replace(/\D/g, ''); |
| |
|
| | |
| | try { |
| | const cfg = ConfigManager.getInstance(); |
| | let defaultCountry = null; |
| | if (cfg.BOT_NUMERO_REAL && String(cfg.BOT_NUMERO_REAL).startsWith('244')) { |
| | defaultCountry = 'AO'; |
| | } |
| |
|
| | if (parsePhoneNumberFromString) { |
| | const pn = defaultCountry |
| | ? parsePhoneNumberFromString(digitos, defaultCountry) |
| | : parsePhoneNumberFromString(digitos); |
| |
|
| | if (pn && pn.isValid && pn.isValid()) { |
| | |
| | return String(pn.number).replace(/^\+/, ''); |
| | } |
| | } |
| | } catch (err) { |
| | |
| | } |
| |
|
| | |
| | if (digitos.length > 0) return digitos; |
| | } |
| | } |
| |
|
| | return 'desconhecido'; |
| |
|
| | } catch (e) { |
| | this.logger.error('Erro ao extrair nΓΊmero:', e.message); |
| | return 'desconhecido'; |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | extractText(message) { |
| | try { |
| | const tipo = getContentType(message.message); |
| | if (!tipo) return ''; |
| |
|
| | const msg = message.message; |
| |
|
| | switch (tipo) { |
| | case 'conversation': |
| | return msg.conversation || ''; |
| | case 'extendedTextMessage': |
| | return msg.extendedTextMessage?.text || ''; |
| | case 'imageMessage': |
| | return msg.imageMessage?.caption || ''; |
| | case 'videoMessage': |
| | return msg.videoMessage?.caption || ''; |
| | case 'audioMessage': |
| | return '[mensagem de voz]'; |
| | case 'stickerMessage': |
| | return '[figurinha]'; |
| | case 'documentMessage': |
| | return msg.documentMessage?.caption || '[documento]'; |
| | default: |
| | return ''; |
| | } |
| | } catch (e) { |
| | return ''; |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | getConversationType(message) { |
| | const remoteJid = message.key?.remoteJid || ''; |
| | return String(remoteJid).endsWith('@g.us') ? 'grupo' : 'pv'; |
| | } |
| |
|
| | |
| | |
| | |
| | extractReplyInfo(message) { |
| | try { |
| | const context = message.message?.extendedTextMessage?.contextInfo; |
| | if (!context || !context.quotedMessage) return null; |
| |
|
| | const quoted = context.quotedMessage; |
| | const tipo = getContentType(quoted); |
| |
|
| | |
| | let textoMensagemCitada = ''; |
| | let tipoMidia = 'texto'; |
| |
|
| | if (tipo === 'conversation') { |
| | textoMensagemCitada = quoted.conversation || ''; |
| | tipoMidia = 'texto'; |
| | } else if (tipo === 'extendedTextMessage') { |
| | textoMensagemCitada = quoted.extendedTextMessage?.text || ''; |
| | tipoMidia = 'texto'; |
| | } else if (tipo === 'imageMessage') { |
| | textoMensagemCitada = quoted.imageMessage?.caption || '[imagem]'; |
| | tipoMidia = 'imagem'; |
| | } else if (tipo === 'videoMessage') { |
| | textoMensagemCitada = quoted.videoMessage?.caption || '[vΓdeo]'; |
| | tipoMidia = 'video'; |
| | } else if (tipo === 'audioMessage') { |
| | textoMensagemCitada = '[Γ‘udio]'; |
| | tipoMidia = 'audio'; |
| | } else if (tipo === 'stickerMessage') { |
| | textoMensagemCitada = '[figurinha]'; |
| | tipoMidia = 'sticker'; |
| | } else { |
| | textoMensagemCitada = '[conteΓΊdo]'; |
| | tipoMidia = 'outro'; |
| | } |
| |
|
| | const participantJidCitado = context.participant || null; |
| |
|
| | return { |
| | textoMensagemCitada, |
| | tipoMidia, |
| | participantJidCitado, |
| | ehRespostaAoBot: this.isReplyToBot(participantJidCitado), |
| | quemEscreveuCitacao: this.extractUserNumber({ key: { participant: participantJidCitado } }) |
| | }; |
| |
|
| | } catch (e) { |
| | this.logger.error('Erro ao extrair reply info:', e.message); |
| | return null; |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | isReplyToBot(jid) { |
| | if (!jid) return false; |
| |
|
| | const jidStr = String(jid).toLowerCase(); |
| | const jidNumero = jidStr.split('@')[0].split(':')[0]; |
| | const botNumero = String(this.config.BOT_NUMERO_REAL).toLowerCase(); |
| |
|
| | return jidNumero === botNumero || jidStr.includes(botNumero); |
| | } |
| |
|
| | |
| | |
| | |
| | hasAudio(message) { |
| | try { |
| | const tipo = getContentType(message.message); |
| | return tipo === 'audioMessage'; |
| | } catch (e) { |
| | return false; |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | getMediaType(message) { |
| | try { |
| | const tipo = getContentType(message.message); |
| |
|
| | const mimeMap = { |
| | 'imageMessage': 'imagem', |
| | 'videoMessage': 'video', |
| | 'audioMessage': 'audio', |
| | 'stickerMessage': 'sticker', |
| | 'documentMessage': 'documento' |
| | }; |
| |
|
| | return mimeMap[tipo] || 'texto'; |
| | } catch (e) { |
| | return 'texto'; |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | isBotMentioned(message) { |
| | try { |
| | const mentions = message.message?.extendedTextMessage?.contextInfo?.mentionedJid || []; |
| | return mentions.some(jid => this.isReplyToBot(jid)); |
| | } catch (e) { |
| | return false; |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | extractMentions(message) { |
| | try { |
| | return message.message?.extendedTextMessage?.contextInfo?.mentionedJid || []; |
| | } catch (e) { |
| | return []; |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | isCommand(text) { |
| | if (!text) return false; |
| | return text.trim().startsWith(this.config.PREFIXO); |
| | } |
| |
|
| | |
| | |
| | |
| | parseCommand(text) { |
| | if (!this.isCommand(text)) return null; |
| |
|
| | const args = text.slice(this.config.PREFIXO.length).trim().split(/ +/); |
| | const comando = args.shift().toLowerCase(); |
| |
|
| | return { |
| | comando, |
| | args, |
| | textoCompleto: args.join(' ') |
| | }; |
| | } |
| |
|
| | |
| | |
| | |
| | checkRateLimit(userId, windowSeconds = null, maxCalls = null) { |
| | const window = windowSeconds || this.config.RATE_LIMIT_WINDOW; |
| | const max = maxCalls || this.config.RATE_LIMIT_MAX_CALLS; |
| |
|
| | if (!this.rateLimitMap) { |
| | this.rateLimitMap = new Map(); |
| | } |
| |
|
| | const now = Date.now(); |
| | const rec = this.rateLimitMap.get(userId) || []; |
| | const filtered = rec.filter(t => (now - t) < window * 1000); |
| |
|
| | if (filtered.length >= max) { |
| | return false; |
| | } |
| |
|
| | filtered.push(now); |
| | this.rateLimitMap.set(userId, filtered); |
| | return true; |
| | } |
| |
|
| | |
| | |
| | |
| | sanitizeText(text, maxLength = 2000) { |
| | if (!text) return ''; |
| |
|
| | let sanitized = String(text) |
| | .trim() |
| | .substring(0, maxLength); |
| |
|
| | |
| | sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ''); |
| |
|
| | return sanitized; |
| | } |
| |
|
| | |
| | |
| | |
| | clearCache() { |
| | if (this.rateLimitMap) { |
| | this.rateLimitMap.clear(); |
| | } |
| | this.logger.info('πΎ Cache de processamento limpo'); |
| | } |
| |
|
| | |
| | |
| | |
| | getStats() { |
| | return { |
| | rateLimitEntries: this.rateLimitMap?.size || 0, |
| | prefixo: this.config.PREFIXO |
| | }; |
| | } |
| | } |
| |
|
| | module.exports = MessageProcessor; |
| |
|