Deploy: 2026-05-23 23:12:12

This commit is contained in:
Hamza-Ayed
2026-05-23 23:12:12 +03:00
parent 5eb848064d
commit bae23e8da7
2 changed files with 102 additions and 66 deletions

View File

@@ -5,6 +5,7 @@ namespace App\Controllers;
use App\Core\Request; use App\Core\Request;
use App\Core\Response; use App\Core\Response;
use App\Core\Database; use App\Core\Database;
use App\Core\Security;
use App\Models\CompanySubscription; use App\Models\CompanySubscription;
class SuperAdminController extends BaseController class SuperAdminController extends BaseController
@@ -206,50 +207,107 @@ class SuperAdminController extends BaseController
return; 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); $session = \App\Models\WhatsAppSession::findByCompany(1);
if (!$session || $session['status'] !== 'connected') { if (!$session) {
$response->status(404)->json(['error' => 'Super Admin WhatsApp session not active or connected']); $response->status(404)->json(['error' => 'Super Admin WhatsApp session not found']);
return; return;
} }
// Send request to the WhatsApp gateway to export chats try {
$gatewayUrl = rtrim(getenv('WHATSAPP_GATEWAY_URL') ?: 'http://localhost:3722', '/'); $sessionId = $session['id'];
if (substr($gatewayUrl, -4) === '/api') {
$exportUrl = substr($gatewayUrl, 0, -4) . '/api/chats/export'; // 1. Fetch all logged messages for this session
} else { $messages = Database::select(
$exportUrl = $gatewayUrl . '/api/chats/export'; "SELECT * FROM messages_log WHERE session_id = ? ORDER BY id ASC",
[$sessionId]
);
// 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
}
} }
$payload = json_encode([ // 3. Construct the formatted chat log
'session_key' => $session['session_key'] $outputText = "==================================================\n";
]); $outputText .= "سجل محادثات منصة نبيه - جلسة: " . $session['session_key'] . "\n";
$outputText .= "تاريخ التصدير: " . date('Y-m-d H:i:s') . "\n";
$outputText .= "==================================================\n\n";
$ch = curl_init($exportUrl); if (empty($messages)) {
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $outputText .= "لا يوجد سجل رسائل متاح في قاعدة البيانات لهذه الجلسة.\n";
curl_setopt($ch, CURLOPT_POST, true); } else {
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); // Group messages by decrypted contact phone number
curl_setopt($ch, CURLOPT_HTTPHEADER, [ $chats = [];
'Content-Type: application/json', foreach ($messages as $msg) {
'X-Webhook-Secret: ' . getenv('WEBHOOK_SECRET') try {
]); $phone = Security::decrypt($msg['contact_phone']);
curl_setopt($ch, CURLOPT_TIMEOUT, 30); } catch (\Exception $e) {
$phone = 'unknown_' . ($msg['contact_phone_hash'] ?? $msg['id']);
}
if (!isset($chats[$phone])) {
$chats[$phone] = [];
}
$chats[$phone][] = $msg;
}
$result = curl_exec($ch); // Format each chat group
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); foreach ($chats as $phone => $chatMsgs) {
curl_close($ch); $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([ $response->json([
'status' => 'success', 'status' => 'success',
'message' => 'Chat history exported successfully', 'message' => 'Chat history exported successfully from database',
'download_url' => '/whatsapp_chats_history.txt' 'download_url' => '/whatsapp_chats_history.txt'
]); ]);
} else { } catch (\Exception $e) {
$err = json_decode($result, true); error_log("[SuperAdminController Export Error] " . $e->getMessage());
$errMsg = $err['error'] ?? 'HTTP Code ' . $httpCode; $response->status(500)->json([
$response->status(500)->json(['error' => 'Failed to export chats: ' . $errMsg]); 'error' => 'Failed to export chats from database: ' . $e->getMessage()
]);
} }
} }
} }

View File

@@ -59,28 +59,6 @@ async function startSession(session_key, webhook_url) {
const sessionFolder = path.join(SESSIONS_DIR, session_key); const sessionFolder = path.join(SESSIONS_DIR, session_key);
const { state, saveCreds } = await useMultiFileAuthState(sessionFolder); 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 // Fetch the latest WhatsApp Web version to avoid 405 rejection
let version; let version;
try { try {
@@ -361,7 +339,7 @@ async function disconnectSession(session_key) {
const storeFile = path.join(SESSIONS_DIR, `${session_key}_store.json`); const storeFile = path.join(SESSIONS_DIR, `${session_key}_store.json`);
if (fs.existsSync(storeFile)) { if (fs.existsSync(storeFile)) {
try { fs.unlinkSync(storeFile); } catch (e) {} try { fs.unlinkSync(storeFile); } catch (e) { }
} }
const sock = sessions.get(session_key); const sock = sessions.get(session_key);
@@ -391,10 +369,10 @@ function convertToOggOpus(base64Audio) {
fs.writeFile(inputPath, Buffer.from(base64Audio, 'base64'), (err) => { fs.writeFile(inputPath, Buffer.from(base64Audio, 'base64'), (err) => {
if (err) return reject(err); if (err) return reject(err);
exec(`ffmpeg -i ${inputPath} -c:a libopus -y ${outputPath}`, (execErr) => { exec(`ffmpeg -i ${inputPath} -c:a libopus -y ${outputPath}`, (execErr) => {
fs.unlink(inputPath, () => {}); fs.unlink(inputPath, () => { });
if (execErr) return reject(execErr); if (execErr) return reject(execErr);
fs.readFile(outputPath, (readErr, data) => { fs.readFile(outputPath, (readErr, data) => {
fs.unlink(outputPath, () => {}); fs.unlink(outputPath, () => { });
if (readErr) return reject(readErr); if (readErr) return reject(readErr);
resolve(data.toString('base64')); resolve(data.toString('base64'));
}); });