diff --git a/backend/app/Controllers/SuperAdminController.php b/backend/app/Controllers/SuperAdminController.php index 295668e..1d94be6 100644 --- a/backend/app/Controllers/SuperAdminController.php +++ b/backend/app/Controllers/SuperAdminController.php @@ -5,6 +5,7 @@ namespace App\Controllers; use App\Core\Request; use App\Core\Response; use App\Core\Database; +use App\Core\Security; use App\Models\CompanySubscription; class SuperAdminController extends BaseController @@ -206,50 +207,107 @@ class SuperAdminController extends BaseController return; } - // Get the active session for the Super Admin company (ID 1) + // Get the WhatsApp session for the Super Admin company (ID 1) $session = \App\Models\WhatsAppSession::findByCompany(1); - if (!$session || $session['status'] !== 'connected') { - $response->status(404)->json(['error' => 'Super Admin WhatsApp session not active or connected']); + if (!$session) { + $response->status(404)->json(['error' => 'Super Admin WhatsApp session not found']); return; } - // Send request to the WhatsApp gateway to export chats - $gatewayUrl = rtrim(getenv('WHATSAPP_GATEWAY_URL') ?: 'http://localhost:3722', '/'); - if (substr($gatewayUrl, -4) === '/api') { - $exportUrl = substr($gatewayUrl, 0, -4) . '/api/chats/export'; - } else { - $exportUrl = $gatewayUrl . '/api/chats/export'; - } + try { + $sessionId = $session['id']; + + // 1. Fetch all logged messages for this session + $messages = Database::select( + "SELECT * FROM messages_log WHERE session_id = ? ORDER BY id ASC", + [$sessionId] + ); - $payload = json_encode([ - 'session_key' => $session['session_key'] - ]); + // 2. Fetch all contacts for Company 1 to map phone numbers to names + $contactsList = Database::select( + "SELECT name, phone FROM contacts WHERE company_id = 1" + ); + $contactNames = []; + foreach ($contactsList as $c) { + try { + $decryptedPhone = Security::decrypt($c['phone']); + $contactNames[$decryptedPhone] = $c['name']; + } catch (\Exception $e) { + // Skip decryption failure for this specific contact + } + } - $ch = curl_init($exportUrl); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); - curl_setopt($ch, CURLOPT_HTTPHEADER, [ - 'Content-Type: application/json', - 'X-Webhook-Secret: ' . getenv('WEBHOOK_SECRET') - ]); - curl_setopt($ch, CURLOPT_TIMEOUT, 30); - - $result = curl_exec($ch); - $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); - curl_close($ch); + // 3. Construct the formatted chat log + $outputText = "==================================================\n"; + $outputText .= "سجل محادثات منصة نبيه - جلسة: " . $session['session_key'] . "\n"; + $outputText .= "تاريخ التصدير: " . date('Y-m-d H:i:s') . "\n"; + $outputText .= "==================================================\n\n"; + + if (empty($messages)) { + $outputText .= "لا يوجد سجل رسائل متاح في قاعدة البيانات لهذه الجلسة.\n"; + } else { + // Group messages by decrypted contact phone number + $chats = []; + foreach ($messages as $msg) { + try { + $phone = Security::decrypt($msg['contact_phone']); + } catch (\Exception $e) { + $phone = 'unknown_' . ($msg['contact_phone_hash'] ?? $msg['id']); + } + if (!isset($chats[$phone])) { + $chats[$phone] = []; + } + $chats[$phone][] = $msg; + } + + // Format each chat group + foreach ($chats as $phone => $chatMsgs) { + $name = isset($contactNames[$phone]) ? $contactNames[$phone] : 'عميل غير مسمى'; + $outputText .= "--------------------------------------------------\n"; + $outputText .= "المحادثة مع: {$name} ({$phone})\n"; + $outputText .= "--------------------------------------------------\n"; + + foreach ($chatMsgs as $msg) { + $fromMe = $msg['direction'] === 'outbound'; + $sender = $fromMe ? 'المنصة (نبيه)' : $name; + + try { + $body = Security::decrypt($msg['message_body']); + } catch (\Exception $e) { + $body = '[خطأ في فك تشفير الرسالة]'; + } + + $dateStr = $msg['created_at']; + + if ($msg['message_type'] === 'text') { + $outputText .= "[{$dateStr}] {$sender}: {$body}\n"; + } else if ($msg['message_type'] === 'audio') { + $outputText .= "[{$dateStr}] {$sender}: [رسالة صوتية] " . ($body ? "- {$body}" : "") . "\n"; + } else if ($msg['message_type'] === 'image') { + $outputText .= "[{$dateStr}] {$sender}: [صورة] " . ($body ? "- {$body}" : "") . "\n"; + } else { + $outputText .= "[{$dateStr}] {$sender}: [" . $msg['message_type'] . "] " . ($body ? "- {$body}" : "") . "\n"; + } + } + $outputText .= "\n\n"; + } + } + + // 4. Write the file to public directory + $publicDir = __DIR__ . '/../../public'; + $filePath = $publicDir . '/whatsapp_chats_history.txt'; + file_put_contents($filePath, $outputText); - if ($httpCode === 200) { - $data = json_decode($result, true); $response->json([ 'status' => 'success', - 'message' => 'Chat history exported successfully', + 'message' => 'Chat history exported successfully from database', 'download_url' => '/whatsapp_chats_history.txt' ]); - } else { - $err = json_decode($result, true); - $errMsg = $err['error'] ?? 'HTTP Code ' . $httpCode; - $response->status(500)->json(['error' => 'Failed to export chats: ' . $errMsg]); + } catch (\Exception $e) { + error_log("[SuperAdminController Export Error] " . $e->getMessage()); + $response->status(500)->json([ + 'error' => 'Failed to export chats from database: ' . $e->getMessage() + ]); } } } diff --git a/whatsapp-gateway/baileys-client.js b/whatsapp-gateway/baileys-client.js index 74120ad..b6cc98e 100644 --- a/whatsapp-gateway/baileys-client.js +++ b/whatsapp-gateway/baileys-client.js @@ -59,28 +59,6 @@ async function startSession(session_key, webhook_url) { const sessionFolder = path.join(SESSIONS_DIR, session_key); const { state, saveCreds } = await useMultiFileAuthState(sessionFolder); - // Initialize InMemoryStore for chat history - const store = makeInMemoryStore({ logger: pino({ level: 'silent' }) }); - const storeFile = path.join(SESSIONS_DIR, `${session_key}_store.json`); - if (fs.existsSync(storeFile)) { - try { - store.readFromFile(storeFile); - console.log(`[Store] Loaded store from file for ${session_key}`); - } catch (e) { - console.error(`[Store] Failed to load store file for ${session_key}:`, e.message); - } - } - - // Periodically save store to file every 10 seconds - const storeInterval = setInterval(() => { - try { - store.writeToFile(storeFile); - } catch (e) { - console.error(`[Store] Failed to write store file for ${session_key}:`, e.message); - } - }, 10000); - storeIntervals.set(session_key, storeInterval); - // Fetch the latest WhatsApp Web version to avoid 405 rejection let version; try { @@ -361,7 +339,7 @@ async function disconnectSession(session_key) { const storeFile = path.join(SESSIONS_DIR, `${session_key}_store.json`); if (fs.existsSync(storeFile)) { - try { fs.unlinkSync(storeFile); } catch (e) {} + try { fs.unlinkSync(storeFile); } catch (e) { } } const sock = sessions.get(session_key); @@ -391,10 +369,10 @@ function convertToOggOpus(base64Audio) { fs.writeFile(inputPath, Buffer.from(base64Audio, 'base64'), (err) => { if (err) return reject(err); exec(`ffmpeg -i ${inputPath} -c:a libopus -y ${outputPath}`, (execErr) => { - fs.unlink(inputPath, () => {}); + fs.unlink(inputPath, () => { }); if (execErr) return reject(execErr); fs.readFile(outputPath, (readErr, data) => { - fs.unlink(outputPath, () => {}); + fs.unlink(outputPath, () => { }); if (readErr) return reject(readErr); resolve(data.toString('base64')); }); @@ -436,7 +414,7 @@ async function sendMessage(session_key, phone, message, mediaUrl = null, audioBa } else if (audioBase64) { let finalAudioBase64 = audioBase64; let finalMime = mimetype || 'audio/mp4'; - + // If it's MP3, convert to OGG Opus to ensure iPhone PTT compatibility if (finalMime.includes('mpeg') || finalMime.includes('mp3')) { try { @@ -446,13 +424,13 @@ async function sendMessage(session_key, phone, message, mediaUrl = null, audioBa } catch (err) { console.error(`[Baileys] FFmpeg conversion failed:`, err.message); // Fallback to sending as normal audio if conversion fails - finalMime = 'audio/mp4'; + finalMime = 'audio/mp4'; } } - + const buffer = Buffer.from(finalAudioBase64, 'base64'); const isMp3 = finalMime.includes('mpeg') || finalMime.includes('mp3'); - + sentMsg = await sock.sendMessage(jid, { audio: buffer, mimetype: finalMime, @@ -533,11 +511,11 @@ async function exportChatHistory(session_key) { for (const msg of messages) { const fromMe = msg.key.fromMe; const sender = fromMe ? 'المنصة (نبيه)' : name; - + const body = msg.message?.conversation || - msg.message?.extendedTextMessage?.text || - msg.message?.imageMessage?.caption || - msg.message?.videoMessage?.caption || ''; + msg.message?.extendedTextMessage?.text || + msg.message?.imageMessage?.caption || + msg.message?.videoMessage?.caption || ''; let dateStr = 'تاريخ غير معروف'; if (msg.messageTimestamp) {