Deploy: 2026-05-23 23:12:12
This commit is contained in:
@@ -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()
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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'));
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user