akra35567 commited on
Commit
45cb98b
·
verified ·
1 Parent(s): a594707

Update modules/BotCore.js

Browse files
Files changed (1) hide show
  1. modules/BotCore.js +622 -92
modules/BotCore.js CHANGED
@@ -7,6 +7,53 @@
7
  * ═══════════════════════════════════════════════════════════════════════
8
  */
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  const {
11
  default: makeWASocket,
12
  useMultiFileAuthState,
@@ -28,16 +75,26 @@ const CommandHandler = require('./CommandHandler');
28
  class BotCore {
29
  constructor() {
30
  this.config = ConfigManager.getInstance();
31
- this.logger = pino({ level: this.config.LOG_LEVEL });
 
 
 
 
 
 
 
 
 
 
32
 
33
  // Componentes
34
- this.apiClient = new APIClient(this.logger);
35
- this.audioProcessor = new AudioProcessor(this.logger);
36
- this.mediaProcessor = new MediaProcessor(this.logger);
37
- this.messageProcessor = new MessageProcessor(this.logger);
38
- this.moderationSystem = new ModerationSystem(this.logger);
39
- this.levelSystem = new LevelSystem(this.logger);
40
- this.commandHandler = new CommandHandler(this);
41
 
42
  // Estado
43
  this.sock = null;
@@ -47,9 +104,18 @@ class BotCore {
47
  this.lastProcessedTime = 0;
48
  this.processadas = new Set();
49
  this.reconnectAttempts = 0;
 
 
50
 
51
  // Armazenamento
52
  this.store = null;
 
 
 
 
 
 
 
53
  }
54
 
55
  /**
@@ -58,15 +124,24 @@ class BotCore {
58
  async initialize() {
59
  try {
60
  this.logger.info('🔧 Inicializando BotCore...');
 
 
 
61
 
62
  // Valida configurações
63
  if (!this.config.validate()) {
64
  throw new Error('Configurações inválidas');
65
  }
66
 
67
- // Cria pastas necessárias
 
 
 
68
  this.ensureFolders();
69
 
 
 
 
70
  this.logger.info('✅ BotCore inicializado');
71
  return true;
72
 
@@ -77,23 +152,178 @@ class BotCore {
77
  }
78
 
79
  /**
80
- * Cria pastas necessárias
81
  */
82
  ensureFolders() {
83
  const fs = require('fs');
84
- const folders = [
 
 
 
85
  this.config.TEMP_FOLDER,
86
  this.config.AUTH_FOLDER,
87
  this.config.DATABASE_FOLDER,
88
  this.config.LOGS_FOLDER
89
  ];
90
 
91
- folders.forEach(folder => {
92
- if (!fs.existsSync(folder)) {
93
- fs.mkdirSync(folder, { recursive: true });
94
- this.logger.debug(`📁 Pasta criada: ${folder}`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  }
96
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  }
98
 
99
  /**
@@ -111,14 +341,10 @@ class BotCore {
111
  const stats = fs.statSync(credsPath);
112
  const ageHours = (Date.now() - stats.mtime.getTime()) / (1000 * 60 * 60);
113
 
114
- // Se as credenciais têm mais de 24 horas, força novo login
115
- if (ageHours > 24) {
116
- this.logger.warn(`🧹 Credenciais antigas detectadas (${ageHours.toFixed(1)}h). Forçando novo login...`);
117
- fs.rmSync(authPath, { recursive: true, force: true });
118
- this.isConnected = false;
119
- this.currentQR = null;
120
- return;
121
- }
122
 
123
  // Verifica se o arquivo de credenciais é válido
124
  const credsContent = fs.readFileSync(credsPath, 'utf8');
@@ -129,13 +355,16 @@ class BotCore {
129
  fs.rmSync(authPath, { recursive: true, force: true });
130
  this.isConnected = false;
131
  this.currentQR = null;
 
132
  return;
133
  }
134
 
135
  this.logger.info('✅ Credenciais válidas encontradas');
 
136
  } else {
137
  this.logger.info('📱 Nenhuma credencial salva. Aguardando QR code...');
138
  this.isConnected = false;
 
139
  }
140
  } catch (error) {
141
  this.logger.warn('⚠️ Erro ao verificar credenciais:', error.message);
@@ -144,9 +373,12 @@ class BotCore {
144
  if (fs.existsSync(authPath)) {
145
  fs.rmSync(authPath, { recursive: true, force: true });
146
  }
147
- } catch (e) {}
 
 
148
  this.isConnected = false;
149
  this.currentQR = null;
 
150
  }
151
  }
152
 
@@ -155,6 +387,15 @@ class BotCore {
155
  */
156
  async connect() {
157
  try {
 
 
 
 
 
 
 
 
 
158
  // Evita múltiplas conexões simultâneas
159
  if (this.sock && this.sock.ws && this.sock.ws.readyState === 1) {
160
  this.logger.info('🔄 Já conectado, ignorando tentativa de reconexão');
@@ -162,9 +403,34 @@ class BotCore {
162
  }
163
 
164
  this.logger.info('🔗 Conectando ao WhatsApp...');
 
165
 
166
- // Verifica conectividade de rede antes de tentar conectar
167
- await this._checkNetworkConnectivity();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
 
169
  // Verifica se devemos limpar credenciais antigas
170
  await this._checkAndCleanOldAuth();
@@ -172,23 +438,36 @@ class BotCore {
172
  const { state, saveCreds } = await useMultiFileAuthState(this.config.AUTH_FOLDER);
173
  const { version } = await fetchLatestBaileysVersion();
174
 
175
- this.sock = makeWASocket({
 
 
 
176
  version,
177
  auth: state,
178
- logger: pino({ level: 'silent' }),
179
- browser: Browsers.macOS(this.config.BOT_NAME),
180
  markOnlineOnConnect: true,
181
  syncFullHistory: false,
182
- printQRInTerminal: false,
183
- connectTimeoutMs: 120000, // Aumentado para 2 minutos
184
- qrTimeout: 90000, // 90 segundos para QR
185
  defaultQueryTimeoutMs: 60000,
186
- // Configurações para ambientes com conectividade limitada
187
- keepAliveIntervalMs: 30000,
188
- retryRequestDelayMs: 5000,
189
- maxRetries: 5,
190
  // Configurações específicas para ambientes restritos
191
  agent: this._createCustomAgent(),
 
 
 
 
 
 
 
 
 
 
192
  getMessage: async (key) => {
193
  if (!key) return undefined;
194
  try {
@@ -196,17 +475,26 @@ class BotCore {
196
  const msg = await this.store.loadMessage(key.remoteJid, key.id);
197
  return msg ? msg.message : undefined;
198
  }
199
- } catch (e) {}
 
 
200
  return undefined;
201
  }
202
- });
 
 
 
 
203
 
204
- // Vincula store
205
  try {
206
  if (this.store && typeof this.store.bind === 'function') {
207
  this.store.bind(this.sock.ev);
 
208
  }
209
- } catch (e) {}
 
 
210
 
211
  // Event listeners
212
  this.sock.ev.on('creds.update', saveCreds);
@@ -219,16 +507,35 @@ class BotCore {
219
  this.logger.warn('⏰ QR não gerado automaticamente. Tentando forçar...');
220
  this._forceQRGeneration();
221
  }
222
- }, 10000); // 10 segundos
223
 
224
- this.logger.info('✅ Conexão inicializada');
225
 
226
  } catch (error) {
227
  this.logger.error('❌ Erro na conexão:', error.message);
 
 
 
 
228
  throw error;
229
  }
230
  }
231
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
  /**
233
  * Handle connection update
234
  */
@@ -236,10 +543,31 @@ class BotCore {
236
  try {
237
  const { connection, lastDisconnect, qr } = update;
238
 
 
 
 
 
 
 
 
239
  if (qr) {
240
  this.currentQR = qr;
241
  this.logger.info('📱 QR Code gerado - pronto para scan!');
242
- this.logger.info('🔗 Acesse http://localhost:7860/qr para ver o QR code');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
 
244
  // Limpa timeout de força
245
  if (this.qrTimeout) {
@@ -252,9 +580,12 @@ class BotCore {
252
  this.BOT_JID = (this.sock.user && this.sock.user.id) || null;
253
  this.isConnected = true;
254
  this.lastProcessedTime = Date.now();
255
- this.currentQR = null;
256
  this.reconnectAttempts = 0; // Reseta contador de tentativas
257
 
 
 
 
258
  // Limpa timeout de força
259
  if (this.qrTimeout) {
260
  clearTimeout(this.qrTimeout);
@@ -264,11 +595,30 @@ class BotCore {
264
  this.logger.info('\n' + '═'.repeat(70));
265
  this.logger.info('✅ AKIRA BOT V21 ONLINE!');
266
  this.logger.info('═'.repeat(70));
267
- this.config.logConfig();
 
 
 
268
  this.logger.info('═'.repeat(70) + '\n');
269
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
  } else if (connection === 'close') {
271
  this.isConnected = false;
 
 
272
  const code = (lastDisconnect && lastDisconnect.error && lastDisconnect.error.output && lastDisconnect.error.output.statusCode) || undefined;
273
  const reason = (lastDisconnect && lastDisconnect.error && lastDisconnect.error.message) || 'desconhecido';
274
 
@@ -283,21 +633,48 @@ class BotCore {
283
  this._cleanAuthOnError();
284
  } else if (code === 403) {
285
  this.logger.warn('🚫 Conta banida ou bloqueada');
 
 
 
 
 
 
 
286
  }
287
 
288
- // Reconecta com backoff exponencial
289
- const delay = Math.min(5000 * Math.pow(2, this.reconnectAttempts || 0), 30000);
290
  this.reconnectAttempts = (this.reconnectAttempts || 0) + 1;
291
 
292
- this.logger.info(`🔄 Reconectando em ${delay/1000}s... (tentativa ${this.reconnectAttempts})`);
293
- setTimeout(() => this.connect().catch(e => this.logger.error('Erro na reconexão:', e)), delay);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
294
 
295
  } else if (connection === 'connecting') {
296
  this.logger.info('🔄 Conectando ao WhatsApp...');
 
 
297
  }
298
 
299
  } catch (error) {
300
  this.logger.error('❌ Erro em handleConnectionUpdate:', error.message);
 
301
  }
302
  }
303
 
@@ -314,8 +691,11 @@ class BotCore {
314
  this.processadas.add(m.key.id);
315
  setTimeout(() => this.processadas.delete(m.key.id), this.config.MESSAGE_DEDUP_TIME_MS);
316
 
317
- // Ignorar mensagens antigas
318
- if (m.messageTimestamp && m.messageTimestamp * 1000 < this.lastProcessedTime - 10000) {
 
 
 
319
  return;
320
  }
321
 
@@ -338,6 +718,9 @@ class BotCore {
338
  const texto = this.messageProcessor.extractText(m).trim();
339
  const temAudio = this.messageProcessor.hasAudio(m);
340
 
 
 
 
341
  // Verifica ban
342
  if (this.moderationSystem.isBanned(numeroReal)) {
343
  this.logger.warn(`🚫 Mensagem de usuário banido ignorada: ${nome}`);
@@ -371,11 +754,16 @@ class BotCore {
371
  const { rec, leveled } = this.levelSystem.awardXp(m.key.remoteJid, uid, xpAmount);
372
  if (leveled) {
373
  const patente = typeof this.getPatente === 'function' ? this.getPatente(rec.level) : `Nível ${rec.level}`;
374
- await this.sock.sendMessage(m.key.remoteJid, { text: `🎉 @${uid.split('@')[0]} subiu para o nível ${rec.level}! 🏅 ${patente}`, contextInfo: { mentionedJid: [uid] } });
 
 
 
375
  if (rec.level >= this.levelSystem.maxLevel) {
376
  const maxRes = await this.levelSystem.registerMaxLevelUser(m.key.remoteJid, uid, m.pushName || uid, this.sock);
377
  if (maxRes && maxRes.promoted) {
378
- await this.sock.sendMessage(m.key.remoteJid, { text: `🎊 ${m.pushName || uid} foi promovido automaticamente a ADM!` });
 
 
379
  }
380
  }
381
  }
@@ -409,30 +797,35 @@ class BotCore {
409
  async handleAudioMessage(m, nome, numeroReal, replyInfo, ehGrupo) {
410
  this.logger.info(`🎤 [ÁUDIO] ${nome}`);
411
 
412
- // Decodifica áudio
413
- const audioBuffer = await this.mediaProcessor.downloadMedia(
414
- m.message.audioMessage,
415
- 'audio'
416
- );
 
417
 
418
- if (!audioBuffer) {
419
- this.logger.error('❌ Erro ao baixar áudio');
420
- return;
421
- }
422
 
423
- // STT
424
- const transcricao = await this.audioProcessor.speechToText(audioBuffer);
425
 
426
- if (!transcricao.sucesso) {
427
- this.logger.warn('⚠️ Falha na transcrição');
428
- return;
429
- }
430
 
431
- const textoAudio = transcricao.texto;
432
- this.logger.info(`📝 Transcrição: ${textoAudio.substring(0, 80)}...`);
433
 
434
- // Processa como texto
435
- await this.handleTextMessage(m, nome, numeroReal, textoAudio, replyInfo, ehGrupo, true);
 
 
 
 
436
  }
437
 
438
  /**
@@ -452,7 +845,10 @@ class BotCore {
452
  try {
453
  if (this.commandHandler) {
454
  const handled = await this.commandHandler.handle(m, { nome, numeroReal, texto, replyInfo, ehGrupo });
455
- if (handled) return;
 
 
 
456
  }
457
  } catch (e) {
458
  this.logger.warn('Erro no CommandHandler:', e.message);
@@ -489,7 +885,7 @@ class BotCore {
489
  }
490
 
491
  if (!deveResponder) {
492
- this.logger.info(`⏭️ Mensagem ignorada (sem ativação): ${texto.substring(0, 50)}...`);
493
  return;
494
  }
495
 
@@ -565,7 +961,8 @@ class BotCore {
565
  await delay(durationMs);
566
  await this.sock.sendPresenceUpdate('paused', jid);
567
  } catch (e) {
568
- // Silenciosamente ignora
 
569
  }
570
  }
571
 
@@ -626,6 +1023,7 @@ class BotCore {
626
  this.isConnected = false;
627
  this.currentQR = null;
628
  this.BOT_JID = null;
 
629
  } catch (error) {
630
  this.logger.error('❌ Erro ao limpar credenciais:', error.message);
631
  }
@@ -638,15 +1036,30 @@ class BotCore {
638
  try {
639
  this.logger.info('🔄 Forçando geração de QR code...');
640
 
 
 
 
 
641
  if (this.sock) {
642
- this.sock.ev.removeAllListeners();
643
- if (this.sock.ws) {
644
- this.sock.ws.close();
 
 
 
 
645
  }
 
 
 
 
 
 
 
646
  }
647
 
648
  // Pequeno delay antes de reconectar
649
- await delay(2000);
650
 
651
  // Reconecta
652
  await this.connect();
@@ -660,11 +1073,42 @@ class BotCore {
660
  * Obtém QR Code atual
661
  */
662
  getQRCode() {
663
- // Se não está conectado E não tem QR, significa que precisa de login
664
- if (!this.isConnected && !this.currentQR) {
665
- this.logger.warn('⚠️ Bot não conectado e sem QR code. Verifique se as credenciais expiraram.');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
666
  }
667
- return this.currentQR;
668
  }
669
 
670
  /**
@@ -677,7 +1121,10 @@ class BotCore {
677
  botNumero: this.config.BOT_NUMERO_REAL,
678
  botName: this.config.BOT_NAME,
679
  version: this.config.BOT_VERSION,
680
- uptime: Math.floor(process.uptime())
 
 
 
681
  };
682
  }
683
 
@@ -685,15 +1132,98 @@ class BotCore {
685
  * Retorna estatísticas
686
  */
687
  getStats() {
 
 
688
  return {
689
- ...this.getStatus(),
690
- api: this.apiClient.getStats(),
691
- audio: this.audioProcessor.getStats(),
692
- media: this.mediaProcessor.getStats(),
693
- message: this.messageProcessor.getStats(),
694
- moderation: this.moderationSystem.getStats()
 
 
 
 
 
 
 
 
 
 
695
  };
696
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
697
  }
698
 
699
- module.exports = BotCore;
 
7
  * ═══════════════════════════════════════════════════════════════════════
8
  */
9
 
10
+ // ═══════════════════════════════════════════════════════════════════════
11
+ // HF SPACES DNS CORRECTIONS - CORREÇÃO CRÍTICA PARA QR CODE
12
+ // ═══════════════════════════════════════════════════════════════════════
13
+ const dns = require('dns');
14
+ const https = require('https');
15
+
16
+ // 1. Força IPv4 para todas as operações DNS
17
+ dns.setDefaultResultOrder('ipv4first');
18
+
19
+ // 2. Sobrescreve resolve para usar DNS do Google como fallback
20
+ const originalResolve4 = dns.resolve4.bind(dns);
21
+ const originalResolve = dns.resolve.bind(dns);
22
+
23
+ dns.resolve4 = function(hostname, options, callback) {
24
+ if (typeof options === 'function') {
25
+ callback = options;
26
+ options = { timeout: 10000, family: 4 };
27
+ }
28
+
29
+ originalResolve4(hostname, options, (err, addresses) => {
30
+ if (err && (err.code === 'ENODATA' || err.code === 'ENOTFOUND' || err.code === 'EAI_AGAIN')) {
31
+ console.log(`🔄 DNS fallback para ${hostname}, tentando novamente...`);
32
+ setTimeout(() => {
33
+ originalResolve4(hostname, options, callback);
34
+ }, 3000);
35
+ } else {
36
+ callback(err, addresses);
37
+ }
38
+ });
39
+ };
40
+
41
+ // 4. IP direto do WhatsApp como fallback
42
+ const WHATSAPP_DIRECT_IPS = [
43
+ '108.177.14.0',
44
+ '108.177.15.0',
45
+ '142.250.79.0',
46
+ '172.217.28.0'
47
+ ];
48
+
49
+ function getWhatsAppFallbackIP() {
50
+ return WHATSAPP_DIRECT_IPS[Math.floor(Math.random() * WHATSAPP_DIRECT_IPS.length)];
51
+ }
52
+
53
+ // ═══════════════════════════════════════════════════════════════════════
54
+ // FIM DAS CORREÇÕES HF SPACES
55
+ // ═══════════════════════════════════════════════════════════════════════
56
+
57
  const {
58
  default: makeWASocket,
59
  useMultiFileAuthState,
 
75
  class BotCore {
76
  constructor() {
77
  this.config = ConfigManager.getInstance();
78
+ this.logger = pino({
79
+ level: this.config.LOG_LEVEL,
80
+ transport: {
81
+ target: 'pino-pretty',
82
+ options: {
83
+ colorize: true,
84
+ translateTime: 'SYS:standard',
85
+ ignore: 'pid,hostname'
86
+ }
87
+ }
88
+ });
89
 
90
  // Componentes
91
+ this.apiClient = null;
92
+ this.audioProcessor = null;
93
+ this.mediaProcessor = null;
94
+ this.messageProcessor = null;
95
+ this.moderationSystem = null;
96
+ this.levelSystem = null;
97
+ this.commandHandler = null;
98
 
99
  // Estado
100
  this.sock = null;
 
104
  this.lastProcessedTime = 0;
105
  this.processadas = new Set();
106
  this.reconnectAttempts = 0;
107
+ this.qrTimeout = null;
108
+ this.connectionStartTime = null;
109
 
110
  // Armazenamento
111
  this.store = null;
112
+
113
+ // Event listeners
114
+ this.eventListeners = {
115
+ onQRGenerated: null,
116
+ onConnected: null,
117
+ onDisconnected: null
118
+ };
119
  }
120
 
121
  /**
 
124
  async initialize() {
125
  try {
126
  this.logger.info('🔧 Inicializando BotCore...');
127
+
128
+ // Log da porta do servidor
129
+ this.logger.info(`🌐 Porta do servidor: ${this.config.PORT}`);
130
 
131
  // Valida configurações
132
  if (!this.config.validate()) {
133
  throw new Error('Configurações inválidas');
134
  }
135
 
136
+ // Mostra configurações
137
+ this.config.logConfig();
138
+
139
+ // Cria pastas necessárias com tratamento de erro
140
  this.ensureFolders();
141
 
142
+ // Inicializa componentes
143
+ this.initializeComponents();
144
+
145
  this.logger.info('✅ BotCore inicializado');
146
  return true;
147
 
 
152
  }
153
 
154
  /**
155
+ * Cria pastas necessárias com tratamento robusto para Hugging Face
156
  */
157
  ensureFolders() {
158
  const fs = require('fs');
159
+ const path = require('path');
160
+
161
+ // Primeiro, tenta os caminhos padrão
162
+ const defaultFolders = [
163
  this.config.TEMP_FOLDER,
164
  this.config.AUTH_FOLDER,
165
  this.config.DATABASE_FOLDER,
166
  this.config.LOGS_FOLDER
167
  ];
168
 
169
+ // Lista de fallbacks para Hugging Face
170
+ const fallbackBase = '/tmp/akira_data';
171
+ const fallbackFolders = [
172
+ path.join(fallbackBase, 'temp'),
173
+ path.join(fallbackBase, 'auth_info_baileys'),
174
+ path.join(fallbackBase, 'database'),
175
+ path.join(fallbackBase, 'logs')
176
+ ];
177
+
178
+ this.logger.info('📁 Criando diretórios necessários...');
179
+
180
+ // Tentar criar diretórios padrão primeiro
181
+ let useFallback = false;
182
+ for (let i = 0; i < defaultFolders.length; i++) {
183
+ const folder = defaultFolders[i];
184
+ try {
185
+ if (!fs.existsSync(folder)) {
186
+ fs.mkdirSync(folder, { recursive: true });
187
+ this.logger.debug(`✅ Pasta criada (padrão): ${folder}`);
188
+ } else {
189
+ this.logger.debug(`✅ Pasta já existe: ${folder}`);
190
+ }
191
+ } catch (error) {
192
+ this.logger.warn(`⚠️ Erro ao criar pasta padrão ${folder}: ${error.message}`);
193
+ useFallback = true;
194
+ break;
195
  }
196
+ }
197
+
198
+ // Se falhou em algum diretório padrão, usar fallback para Hugging Face
199
+ if (useFallback) {
200
+ this.logger.info('🔄 Usando diretórios de fallback para Hugging Face Spaces...');
201
+
202
+ // Primeiro cria a pasta base de fallback
203
+ try {
204
+ if (!fs.existsSync(fallbackBase)) {
205
+ fs.mkdirSync(fallbackBase, { recursive: true });
206
+ this.logger.info(`✅ Pasta base de fallback criada: ${fallbackBase}`);
207
+ }
208
+ } catch (error) {
209
+ this.logger.error(`❌ Erro crítico ao criar pasta base de fallback: ${error.message}`);
210
+ throw error;
211
+ }
212
+
213
+ // Cria todas as subpastas de fallback
214
+ fallbackFolders.forEach(folder => {
215
+ try {
216
+ if (!fs.existsSync(folder)) {
217
+ fs.mkdirSync(folder, { recursive: true });
218
+ this.logger.info(`✅ Pasta de fallback criada: ${folder}`);
219
+ }
220
+ } catch (error) {
221
+ this.logger.error(`❌ Erro ao criar pasta de fallback ${folder}: ${error.message}`);
222
+ }
223
+ });
224
+
225
+ // Atualiza configurações para usar os caminhos de fallback
226
+ this.config.TEMP_FOLDER = fallbackFolders[0];
227
+ this.config.AUTH_FOLDER = fallbackFolders[1];
228
+ this.config.DATABASE_FOLDER = fallbackFolders[2];
229
+ this.config.LOGS_FOLDER = fallbackFolders[3];
230
+
231
+ this.logger.info('🔄 Configurações atualizadas para usar caminhos de fallback');
232
+ } else {
233
+ this.logger.info('✅ Todos os diretórios criados com sucesso');
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Inicializa todos os componentes
239
+ */
240
+ initializeComponents() {
241
+ try {
242
+ this.logger.debug('🔧 Inicializando componentes...');
243
+
244
+ this.apiClient = new APIClient(this.logger);
245
+ this.audioProcessor = new AudioProcessor(this.logger);
246
+ this.mediaProcessor = new MediaProcessor(this.logger);
247
+ this.messageProcessor = new MessageProcessor(this.logger);
248
+ this.moderationSystem = new ModerationSystem(this.logger);
249
+ this.levelSystem = new LevelSystem(this.logger);
250
+
251
+ // CommandHandler pode falhar no Hugging Face, tratar separadamente
252
+ try {
253
+ this.commandHandler = new CommandHandler(this);
254
+ this.logger.debug('✅ CommandHandler inicializado');
255
+ } catch (commandError) {
256
+ this.logger.warn(`⚠️ CommandHandler falhou: ${commandError.message}`);
257
+ this.logger.warn('⚠️ Continuando sem CommandHandler (modo limitado)');
258
+ this.commandHandler = null;
259
+ }
260
+
261
+ this.logger.debug('✅ Componentes inicializados');
262
+ } catch (error) {
263
+ this.logger.error('❌ Erro ao inicializar componentes:', error.message);
264
+ // Não lançar erro fatal, tentar continuar com componentes disponíveis
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Verifica conectividade de rede ANTES de tentar conectar
270
+ * Versão otimizada para HF Spaces com fallback de DNS (SIMPLIFICADA)
271
+ */
272
+ async _checkNetworkConnectivitySimple() {
273
+ try {
274
+ console.log('🌐 Verificando conectividade de rede (simplificado)...');
275
+
276
+ // Teste DNS simples
277
+ const addresses = await new Promise((resolve, reject) => {
278
+ dns.resolve4('web.whatsapp.com', { timeout: 5000, family: 4 }, (err, addresses) => {
279
+ if (err) reject(err);
280
+ else resolve(addresses);
281
+ });
282
+ });
283
+
284
+ console.log(`✅ DNS OK: ${addresses.join(', ')}`);
285
+ return true;
286
+
287
+ } catch (error) {
288
+ console.warn(`⚠️ DNS falhou: ${error.message}`);
289
+ // Não bloqueia, deixa tentar conectar mesmo assim
290
+ return true;
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Cria agente HTTP personalizado para ambientes restritos (HF Spaces)
296
+ */
297
+ _createCustomAgent() {
298
+ try {
299
+ // Tenta usar proxy se configurado
300
+ const proxy = process.env.HTTPS_PROXY || process.env.HTTP_PROXY;
301
+
302
+ if (proxy) {
303
+ console.log(`🔌 Usando proxy: ${proxy.substring(0, 40)}...`);
304
+ try {
305
+ const { HttpsProxyAgent } = require('https-proxy-agent');
306
+ return new HttpsProxyAgent(proxy);
307
+ } catch (e) {
308
+ console.warn('⚠️ Não foi possível carregar https-proxy-agent');
309
+ }
310
+ }
311
+
312
+ // Caso contrário, usa agente padrão com configurações otimizadas para HF Spaces
313
+ return new https.Agent({
314
+ keepAlive: true,
315
+ keepAliveMsecs: 30000,
316
+ timeout: 60000,
317
+ maxSockets: 100,
318
+ maxFreeSockets: 20,
319
+ // NÃO define rejectUnauthorized para evitar problemas em containers
320
+ rejectUnauthorized: false
321
+ });
322
+
323
+ } catch (error) {
324
+ console.warn(`⚠️ Erro ao criar agente customizado: ${error.message}`);
325
+ return undefined;
326
+ }
327
  }
328
 
329
  /**
 
341
  const stats = fs.statSync(credsPath);
342
  const ageHours = (Date.now() - stats.mtime.getTime()) / (1000 * 60 * 60);
343
 
344
+ this.logger.info(`📄 Credenciais encontradas (${ageHours.toFixed(1)}h atrás)`);
345
+
346
+ // REMOVIDO: Limpeza automática após 24h - Mantém conexão estável no HF
347
+ // Credenciais são mantidas indefinidamente para evitar QR code diário
 
 
 
 
348
 
349
  // Verifica se o arquivo de credenciais é válido
350
  const credsContent = fs.readFileSync(credsPath, 'utf8');
 
355
  fs.rmSync(authPath, { recursive: true, force: true });
356
  this.isConnected = false;
357
  this.currentQR = null;
358
+ this.BOT_JID = null;
359
  return;
360
  }
361
 
362
  this.logger.info('✅ Credenciais válidas encontradas');
363
+ this.logger.info(`🤖 Bot registrado como: ${creds.me.id || 'Desconhecido'}`);
364
  } else {
365
  this.logger.info('📱 Nenhuma credencial salva. Aguardando QR code...');
366
  this.isConnected = false;
367
+ this.BOT_JID = null;
368
  }
369
  } catch (error) {
370
  this.logger.warn('⚠️ Erro ao verificar credenciais:', error.message);
 
373
  if (fs.existsSync(authPath)) {
374
  fs.rmSync(authPath, { recursive: true, force: true });
375
  }
376
+ } catch (e) {
377
+ this.logger.warn('Erro ao limpar pasta auth:', e.message);
378
+ }
379
  this.isConnected = false;
380
  this.currentQR = null;
381
+ this.BOT_JID = null;
382
  }
383
  }
384
 
 
387
  */
388
  async connect() {
389
  try {
390
+ // Marca o tempo de início da conexão
391
+ this.connectionStartTime = Date.now();
392
+
393
+ // Limpa timeout anterior se existir
394
+ if (this.qrTimeout) {
395
+ clearTimeout(this.qrTimeout);
396
+ this.qrTimeout = null;
397
+ }
398
+
399
  // Evita múltiplas conexões simultâneas
400
  if (this.sock && this.sock.ws && this.sock.ws.readyState === 1) {
401
  this.logger.info('🔄 Já conectado, ignorando tentativa de reconexão');
 
403
  }
404
 
405
  this.logger.info('🔗 Conectando ao WhatsApp...');
406
+ this.logger.info(`📁 Usando pasta de auth: ${this.config.AUTH_FOLDER}`);
407
 
408
+ // ═══ VERIFICAÇÃO DE DNS OTIMIZADA PARA HF SPACES ═══
409
+ console.log('🔍 Verificando resolução DNS...');
410
+ try {
411
+ // Tenta resolver usando a função com fallback
412
+ const addresses = await new Promise((resolve, reject) => {
413
+ dns.resolve4('web.whatsapp.com', { timeout: 15000, family: 4 }, (err, addresses) => {
414
+ if (err) reject(err);
415
+ else resolve(addresses);
416
+ });
417
+ });
418
+ console.log(`✅ DNS: web.whatsapp.com resolvido: ${addresses.join(', ')}`);
419
+ } catch (dnsError) {
420
+ console.warn(`⚠️ Erro DNS: ${dnsError.message}`);
421
+ console.log('🔄 Tentando IP direto do WhatsApp como fallback...');
422
+ const fallbackIP = getWhatsAppFallbackIP();
423
+ console.log(`📍 IP fallback disponível: ${fallbackIP}`);
424
+ // Continua mesmo com erro de DNS, o Baileys pode tentar resolver
425
+ }
426
+
427
+ // Verifica conectividade de rede antes de tentar conectar (simplificado para HF Spaces)
428
+ // Não bloqueia, tenta conectar mesmo se a verificação falhar
429
+ try {
430
+ await this._checkNetworkConnectivitySimple();
431
+ } catch (netError) {
432
+ console.log('⚠️ Verificação de rede ignorada, continuando...');
433
+ }
434
 
435
  // Verifica se devemos limpar credenciais antigas
436
  await this._checkAndCleanOldAuth();
 
438
  const { state, saveCreds } = await useMultiFileAuthState(this.config.AUTH_FOLDER);
439
  const { version } = await fetchLatestBaileysVersion();
440
 
441
+ this.logger.info(`📦 Versão Baileys: ${version}`);
442
+
443
+ // Configurações otimizadas para Hugging Face Spaces
444
+ const socketConfig = {
445
  version,
446
  auth: state,
447
+ logger: pino({ level: 'silent' }), // Silencia logs do Baileys
448
+ browser: Browsers.ubuntu('Chrome'), // Alterado para Ubuntu/Chrome (mais estável em container)
449
  markOnlineOnConnect: true,
450
  syncFullHistory: false,
451
+ printQRInTerminal: false, // Não mostra QR no terminal (usamos web)
452
+ connectTimeoutMs: 180000, // Aumentado para 3 minutos para ambientes lentos
453
+ qrTimeout: 120000, // 120 segundos para QR
454
  defaultQueryTimeoutMs: 60000,
455
+ // Configurações otimizadas para ambientes com conectividade limitada
456
+ keepAliveIntervalMs: 45000, // Aumentado para manter conexão
457
+ retryRequestDelayMs: 8000, // Aumentado delay entre retentativas
458
+ maxRetries: 3, // Reduzido número de retentativas
459
  // Configurações específicas para ambientes restritos
460
  agent: this._createCustomAgent(),
461
+ // Força uso de IPv4
462
+ fetchAgent: this._createCustomAgent(),
463
+ // Configurações de WebSocket otimizadas
464
+ wsOptions: {
465
+ headers: {
466
+ 'Origin': 'https://web.whatsapp.com',
467
+ 'Host': 'web.whatsapp.com',
468
+ 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
469
+ }
470
+ },
471
  getMessage: async (key) => {
472
  if (!key) return undefined;
473
  try {
 
475
  const msg = await this.store.loadMessage(key.remoteJid, key.id);
476
  return msg ? msg.message : undefined;
477
  }
478
+ } catch (e) {
479
+ this.logger.debug('Erro ao carregar mensagem:', e.message);
480
+ }
481
  return undefined;
482
  }
483
+ };
484
+
485
+ this.logger.debug('⚙️ Configuração do socket:', JSON.stringify(socketConfig, null, 2));
486
+
487
+ this.sock = makeWASocket(socketConfig);
488
 
489
+ // Vincula store se existir
490
  try {
491
  if (this.store && typeof this.store.bind === 'function') {
492
  this.store.bind(this.sock.ev);
493
+ this.logger.debug('✅ Store vinculada ao socket');
494
  }
495
+ } catch (e) {
496
+ this.logger.warn('⚠️ Erro ao vincular store:', e.message);
497
+ }
498
 
499
  // Event listeners
500
  this.sock.ev.on('creds.update', saveCreds);
 
507
  this.logger.warn('⏰ QR não gerado automaticamente. Tentando forçar...');
508
  this._forceQRGeneration();
509
  }
510
+ }, 25000); // Aumentado para 25 segundos
511
 
512
+ this.logger.info('✅ Conexão inicializada - Aguardando QR code ou conexão...');
513
 
514
  } catch (error) {
515
  this.logger.error('❌ Erro na conexão:', error.message);
516
+ this.logger.error(error.stack);
517
+
518
+ // Reconecta automaticamente após erro
519
+ this._scheduleReconnect();
520
  throw error;
521
  }
522
  }
523
 
524
+ /**
525
+ * Agenda reconexão automática
526
+ */
527
+ _scheduleReconnect() {
528
+ const reconnectDelay = Math.min(10000 * Math.pow(1.5, this.reconnectAttempts), 120000);
529
+ this.reconnectAttempts = (this.reconnectAttempts || 0) + 1;
530
+
531
+ this.logger.info(`🔄 Reconectando em ${reconnectDelay/1000}s... (tentativa ${this.reconnectAttempts})`);
532
+
533
+ setTimeout(() => {
534
+ this.logger.info('🔄 Iniciando reconexão...');
535
+ this.connect().catch(e => this.logger.error('Erro na reconexão:', e.message));
536
+ }, reconnectDelay);
537
+ }
538
+
539
  /**
540
  * Handle connection update
541
  */
 
543
  try {
544
  const { connection, lastDisconnect, qr } = update;
545
 
546
+ // Log detalhado para debugging
547
+ this.logger.debug(`🔄 Connection update received:`, {
548
+ connection,
549
+ hasQR: !!qr,
550
+ lastDisconnect: lastDisconnect ? 'yes' : 'no'
551
+ });
552
+
553
  if (qr) {
554
  this.currentQR = qr;
555
  this.logger.info('📱 QR Code gerado - pronto para scan!');
556
+ this.logger.info(`🔗 Acesse: http://localhost:${this.config.PORT}/qr`);
557
+
558
+ // Log extra para web
559
+ console.log('\n' + '═'.repeat(70));
560
+ console.log('📱 QR CODE DISPONÍVEL NA WEB!');
561
+ console.log('═'.repeat(70));
562
+ console.log(`🔗 URL: http://localhost:${this.config.PORT}/qr`);
563
+ console.log('⏳ Válido por 120 segundos');
564
+ console.log('📱 Abra essa URL no seu navegador');
565
+ console.log('═'.repeat(70) + '\n');
566
+
567
+ // Chama callback se existir
568
+ if (this.eventListeners.onQRGenerated) {
569
+ this.eventListeners.onQRGenerated(qr);
570
+ }
571
 
572
  // Limpa timeout de força
573
  if (this.qrTimeout) {
 
580
  this.BOT_JID = (this.sock.user && this.sock.user.id) || null;
581
  this.isConnected = true;
582
  this.lastProcessedTime = Date.now();
583
+ this.currentQR = null; // Limpa QR após conexão
584
  this.reconnectAttempts = 0; // Reseta contador de tentativas
585
 
586
+ // Calcula tempo de conexão
587
+ const connectionTime = Date.now() - this.connectionStartTime;
588
+
589
  // Limpa timeout de força
590
  if (this.qrTimeout) {
591
  clearTimeout(this.qrTimeout);
 
595
  this.logger.info('\n' + '═'.repeat(70));
596
  this.logger.info('✅ AKIRA BOT V21 ONLINE!');
597
  this.logger.info('═'.repeat(70));
598
+ this.logger.info(`🤖 Nome: ${this.config.BOT_NAME}`);
599
+ this.logger.info(`📱 Número: ${this.config.BOT_NUMERO_REAL}`);
600
+ this.logger.info(`🔗 JID: ${this.BOT_JID}`);
601
+ this.logger.info(`⏱️ Tempo de conexão: ${connectionTime}ms`);
602
  this.logger.info('═'.repeat(70) + '\n');
603
 
604
+ // Chama callback se existir
605
+ if (this.eventListeners.onConnected) {
606
+ this.eventListeners.onConnected(this.BOT_JID);
607
+ }
608
+
609
+ // Log extra para web
610
+ console.log('\n' + '═'.repeat(70));
611
+ console.log('✅ BOT CONECTADO COM SUCESSO!');
612
+ console.log('═'.repeat(70));
613
+ console.log(`🤖 Bot está online e pronto para uso`);
614
+ console.log(`📱 Número: ${this.config.BOT_NUMERO_REAL}`);
615
+ console.log(`🔗 JID: ${this.BOT_JID}`);
616
+ console.log('═'.repeat(70) + '\n');
617
+
618
  } else if (connection === 'close') {
619
  this.isConnected = false;
620
+ this.currentQR = null;
621
+
622
  const code = (lastDisconnect && lastDisconnect.error && lastDisconnect.error.output && lastDisconnect.error.output.statusCode) || undefined;
623
  const reason = (lastDisconnect && lastDisconnect.error && lastDisconnect.error.message) || 'desconhecido';
624
 
 
633
  this._cleanAuthOnError();
634
  } else if (code === 403) {
635
  this.logger.warn('🚫 Conta banida ou bloqueada');
636
+ } else if (code === 503) {
637
+ this.logger.warn('🌐 Serviço indisponível - servidor WhatsApp offline');
638
+ }
639
+
640
+ // Chama callback se existir
641
+ if (this.eventListeners.onDisconnected) {
642
+ this.eventListeners.onDisconnected(code, reason);
643
  }
644
 
645
+ // Reconecta com backoff exponencial otimizado
646
+ const reconnectDelay = Math.min(8000 * Math.pow(1.8, this.reconnectAttempts || 0), 90000); // Máximo 90 segundos
647
  this.reconnectAttempts = (this.reconnectAttempts || 0) + 1;
648
 
649
+ this.logger.info(`🔄 Reconectando em ${reconnectDelay/1000}s... (tentativa ${this.reconnectAttempts})`);
650
+
651
+ // Limpa socket atual
652
+ if (this.sock) {
653
+ try {
654
+ this.sock.ev.removeAllListeners();
655
+ if (this.sock.ws) {
656
+ this.sock.ws.close();
657
+ }
658
+ } catch (e) {
659
+ this.logger.warn('Erro ao limpar socket:', e.message);
660
+ }
661
+ this.sock = null;
662
+ }
663
+
664
+ setTimeout(() => {
665
+ this.logger.info('🔄 Iniciando reconexão...');
666
+ this.connect().catch(e => this.logger.error('Erro na reconexão:', e.message));
667
+ }, reconnectDelay);
668
 
669
  } else if (connection === 'connecting') {
670
  this.logger.info('🔄 Conectando ao WhatsApp...');
671
+ // Limpa QR code anterior ao tentar nova conexão
672
+ this.currentQR = null;
673
  }
674
 
675
  } catch (error) {
676
  this.logger.error('❌ Erro em handleConnectionUpdate:', error.message);
677
+ this.logger.error(error.stack);
678
  }
679
  }
680
 
 
691
  this.processadas.add(m.key.id);
692
  setTimeout(() => this.processadas.delete(m.key.id), this.config.MESSAGE_DEDUP_TIME_MS);
693
 
694
+ // Ignorar mensagens antigas (mais de 10 segundos)
695
+ const messageTime = m.messageTimestamp * 1000;
696
+ const currentTime = Date.now();
697
+ if (messageTime && messageTime < currentTime - 10000) {
698
+ this.logger.debug(`⏭️ Mensagem antiga ignorada: ${messageTime}`);
699
  return;
700
  }
701
 
 
718
  const texto = this.messageProcessor.extractText(m).trim();
719
  const temAudio = this.messageProcessor.hasAudio(m);
720
 
721
+ // Log da mensagem recebida
722
+ this.logger.info(`📩 [${ehGrupo ? 'GRUPO' : 'PV'}] ${nome}: ${texto.substring(0, 50)}${texto.length > 50 ? '...' : ''}`);
723
+
724
  // Verifica ban
725
  if (this.moderationSystem.isBanned(numeroReal)) {
726
  this.logger.warn(`🚫 Mensagem de usuário banido ignorada: ${nome}`);
 
754
  const { rec, leveled } = this.levelSystem.awardXp(m.key.remoteJid, uid, xpAmount);
755
  if (leveled) {
756
  const patente = typeof this.getPatente === 'function' ? this.getPatente(rec.level) : `Nível ${rec.level}`;
757
+ await this.sock.sendMessage(m.key.remoteJid, {
758
+ text: `🎉 @${uid.split('@')[0]} subiu para o nível ${rec.level}! 🏅 ${patente}`,
759
+ contextInfo: { mentionedJid: [uid] }
760
+ });
761
  if (rec.level >= this.levelSystem.maxLevel) {
762
  const maxRes = await this.levelSystem.registerMaxLevelUser(m.key.remoteJid, uid, m.pushName || uid, this.sock);
763
  if (maxRes && maxRes.promoted) {
764
+ await this.sock.sendMessage(m.key.remoteJid, {
765
+ text: `🎊 ${m.pushName || uid} foi promovido automaticamente a ADM!`
766
+ });
767
  }
768
  }
769
  }
 
797
  async handleAudioMessage(m, nome, numeroReal, replyInfo, ehGrupo) {
798
  this.logger.info(`🎤 [ÁUDIO] ${nome}`);
799
 
800
+ try {
801
+ // Decodifica áudio
802
+ const audioBuffer = await this.mediaProcessor.downloadMedia(
803
+ m.message.audioMessage,
804
+ 'audio'
805
+ );
806
 
807
+ if (!audioBuffer) {
808
+ this.logger.error('❌ Erro ao baixar áudio');
809
+ return;
810
+ }
811
 
812
+ // STT
813
+ const transcricao = await this.audioProcessor.speechToText(audioBuffer);
814
 
815
+ if (!transcricao.sucesso) {
816
+ this.logger.warn('⚠️ Falha na transcrição');
817
+ return;
818
+ }
819
 
820
+ const textoAudio = transcricao.texto;
821
+ this.logger.info(`📝 Transcrição: ${textoAudio.substring(0, 80)}...`);
822
 
823
+ // Processa como texto
824
+ await this.handleTextMessage(m, nome, numeroReal, textoAudio, replyInfo, ehGrupo, true);
825
+
826
+ } catch (error) {
827
+ this.logger.error('❌ Erro ao processar áudio:', error.message);
828
+ }
829
  }
830
 
831
  /**
 
845
  try {
846
  if (this.commandHandler) {
847
  const handled = await this.commandHandler.handle(m, { nome, numeroReal, texto, replyInfo, ehGrupo });
848
+ if (handled) {
849
+ this.logger.info(`⚡ Comando tratado: ${texto.substring(0, 30)}...`);
850
+ return;
851
+ }
852
  }
853
  } catch (e) {
854
  this.logger.warn('Erro no CommandHandler:', e.message);
 
885
  }
886
 
887
  if (!deveResponder) {
888
+ this.logger.debug(`⏭️ Mensagem ignorada (sem ativação): ${texto.substring(0, 50)}...`);
889
  return;
890
  }
891
 
 
961
  await delay(durationMs);
962
  await this.sock.sendPresenceUpdate('paused', jid);
963
  } catch (e) {
964
+ // Ignora erros de presença silenciosamente
965
+ this.logger.debug('Erro na simulação de digitação:', e.message);
966
  }
967
  }
968
 
 
1023
  this.isConnected = false;
1024
  this.currentQR = null;
1025
  this.BOT_JID = null;
1026
+ this.reconnectAttempts = 0;
1027
  } catch (error) {
1028
  this.logger.error('❌ Erro ao limpar credenciais:', error.message);
1029
  }
 
1036
  try {
1037
  this.logger.info('🔄 Forçando geração de QR code...');
1038
 
1039
+ // Limpa estado atual
1040
+ this.currentQR = null;
1041
+ this.isConnected = false;
1042
+
1043
  if (this.sock) {
1044
+ try {
1045
+ this.sock.ev.removeAllListeners();
1046
+ if (this.sock.ws) {
1047
+ this.sock.ws.close();
1048
+ }
1049
+ } catch (e) {
1050
+ this.logger.warn('Erro ao limpar socket:', e.message);
1051
  }
1052
+ this.sock = null;
1053
+ }
1054
+
1055
+ // Limpa timeout se existir
1056
+ if (this.qrTimeout) {
1057
+ clearTimeout(this.qrTimeout);
1058
+ this.qrTimeout = null;
1059
  }
1060
 
1061
  // Pequeno delay antes de reconectar
1062
+ await delay(3000);
1063
 
1064
  // Reconecta
1065
  await this.connect();
 
1073
  * Obtém QR Code atual
1074
  */
1075
  getQRCode() {
1076
+ try {
1077
+ // Se está conectado, não precisa de QR
1078
+ if (this.isConnected) {
1079
+ this.logger.debug('✅ Bot já conectado, sem necessidade de QR code');
1080
+ return null;
1081
+ }
1082
+
1083
+ // Se tem QR code armazenado, retorna
1084
+ if (this.currentQR) {
1085
+ this.logger.debug(`📱 QR code disponível (${this.currentQR.length} caracteres)`);
1086
+ return this.currentQR;
1087
+ }
1088
+
1089
+ // Se não está conectado e não tem QR, verifica estado
1090
+ this.logger.debug('🔄 Bot não conectado e sem QR code disponível');
1091
+
1092
+ // Verifica se a conexão está em andamento
1093
+ if (this.sock && this.sock.ws) {
1094
+ const state = this.sock.ws.readyState;
1095
+ if (state === 0) { // CONNECTING
1096
+ this.logger.debug('🔄 Conexão em andamento... aguarde QR code');
1097
+ return null;
1098
+ } else if (state === 1) { // OPEN
1099
+ this.logger.debug('✅ Conexão já aberta, mas isConnected não atualizado');
1100
+ return null;
1101
+ }
1102
+ }
1103
+
1104
+ // Se chegou aqui, precisa reconectar
1105
+ this.logger.debug('⚠️ Bot desconectado e sem QR. Talvez seja necessário reiniciar a conexão.');
1106
+ return null;
1107
+
1108
+ } catch (error) {
1109
+ this.logger.error('❌ Erro ao obter QR code:', error.message);
1110
+ return null;
1111
  }
 
1112
  }
1113
 
1114
  /**
 
1121
  botNumero: this.config.BOT_NUMERO_REAL,
1122
  botName: this.config.BOT_NAME,
1123
  version: this.config.BOT_VERSION,
1124
+ uptime: Math.floor(process.uptime()),
1125
+ hasQR: this.currentQR !== null && this.currentQR !== undefined,
1126
+ reconnectAttempts: this.reconnectAttempts,
1127
+ connectionStartTime: this.connectionStartTime
1128
  };
1129
  }
1130
 
 
1132
  * Retorna estatísticas
1133
  */
1134
  getStats() {
1135
+ const status = this.getStatus();
1136
+
1137
  return {
1138
+ ...status,
1139
+ api: this.apiClient ? this.apiClient.getStats() : { error: 'API não inicializada' },
1140
+ audio: this.audioProcessor ? this.audioProcessor.getStats() : { error: 'AudioProcessor não inicializado' },
1141
+ media: this.mediaProcessor ? this.mediaProcessor.getStats() : { error: 'MediaProcessor não inicializado' },
1142
+ message: this.messageProcessor ? this.messageProcessor.getStats() : { error: 'MessageProcessor não inicializado' },
1143
+ moderation: this.moderationSystem ? this.moderationSystem.getStats() : { error: 'ModerationSystem não inicializado' },
1144
+ leveling: this.levelSystem ? this.levelSystem.getStats() : { error: 'LevelSystem não inicializado' },
1145
+ componentsInitialized: {
1146
+ apiClient: !!this.apiClient,
1147
+ audioProcessor: !!this.audioProcessor,
1148
+ mediaProcessor: !!this.mediaProcessor,
1149
+ messageProcessor: !!this.messageProcessor,
1150
+ moderationSystem: !!this.moderationSystem,
1151
+ levelSystem: !!this.levelSystem,
1152
+ commandHandler: !!this.commandHandler
1153
+ }
1154
  };
1155
  }
1156
+
1157
+ /**
1158
+ * Registra event listeners
1159
+ */
1160
+ on(event, callback) {
1161
+ if (event === 'qr') {
1162
+ this.eventListeners.onQRGenerated = callback;
1163
+ } else if (event === 'connected') {
1164
+ this.eventListeners.onConnected = callback;
1165
+ } else if (event === 'disconnected') {
1166
+ this.eventListeners.onDisconnected = callback;
1167
+ } else {
1168
+ this.logger.warn(`⚠️ Evento não suportado: ${event}`);
1169
+ }
1170
+ }
1171
+
1172
+ /**
1173
+ * Limpa cache de componentes
1174
+ */
1175
+ clearCache() {
1176
+ try {
1177
+ if (this.audioProcessor && typeof this.audioProcessor.clearCache === 'function') {
1178
+ this.audioProcessor.clearCache();
1179
+ }
1180
+ if (this.mediaProcessor && typeof this.mediaProcessor.clearCache === 'function') {
1181
+ this.mediaProcessor.clearCache();
1182
+ }
1183
+ if (this.messageProcessor && typeof this.messageProcessor.clearCache === 'function') {
1184
+ this.messageProcessor.clearCache();
1185
+ }
1186
+ this.logger.info('✅ Cache limpo');
1187
+ } catch (error) {
1188
+ this.logger.error('❌ Erro ao limpar cache:', error.message);
1189
+ }
1190
+ }
1191
+
1192
+ /**
1193
+ * Verifica se o bot está pronto
1194
+ */
1195
+ isReady() {
1196
+ return this.isConnected && this.sock && this.sock.ws && this.sock.ws.readyState === 1;
1197
+ }
1198
+
1199
+ /**
1200
+ * Desconecta o bot
1201
+ */
1202
+ async disconnect() {
1203
+ try {
1204
+ this.logger.info('🔴 Desconectando bot...');
1205
+
1206
+ if (this.sock) {
1207
+ try {
1208
+ this.sock.ev.removeAllListeners();
1209
+ if (this.sock.ws) {
1210
+ this.sock.ws.close();
1211
+ }
1212
+ } catch (e) {
1213
+ this.logger.warn('Erro ao fechar socket:', e.message);
1214
+ }
1215
+ this.sock = null;
1216
+ }
1217
+
1218
+ this.isConnected = false;
1219
+ this.currentQR = null;
1220
+ this.BOT_JID = null;
1221
+
1222
+ this.logger.info('✅ Bot desconectado');
1223
+ } catch (error) {
1224
+ this.logger.error('❌ Erro ao desconectar:', error.message);
1225
+ }
1226
+ }
1227
  }
1228
 
1229
+ module.exports = BotCore;