Deploy: 2026-06-24 15:26:15

This commit is contained in:
Hamza-Ayed
2026-06-24 15:26:15 +03:00
parent 2562652e68
commit f565b211ee
3 changed files with 91 additions and 22 deletions

View File

@@ -1,6 +1,6 @@
<?php <?php
/** /**
* WhatsApp Gateway Webhook Receiver & QR Code Viewer (6 Slots) * WhatsApp Gateway Webhook Receiver & QR Code Viewer (3 Slots)
*/ */
require_once __DIR__ . '/../includes/Redis.php'; require_once __DIR__ . '/../includes/Redis.php';
@@ -45,7 +45,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Prepare slots data for UI // Prepare slots data for UI
$slots = []; $slots = [];
for ($i = 1; $i <= 6; $i++) { for ($i = 1; $i <= 3; $i++) {
$sk = "slot-{$i}"; $sk = "slot-{$i}";
$slots[$sk] = [ $slots[$sk] = [
'status' => $redis->get("whatsapp:{$sk}:status") ?: 'disconnected', 'status' => $redis->get("whatsapp:{$sk}:status") ?: 'disconnected',
@@ -59,7 +59,7 @@ for ($i = 1; $i <= 6; $i++) {
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>إدارة أرقام بوابة الواتساب (6 جلسات) — Flash Call OTP</title> <title>إدارة أرقام بوابة الواتساب (3 جلسات) — Flash Call OTP</title>
<link href="https://fonts.googleapis.com/css2?family=Cairo:wght@400;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Cairo:wght@400;600;700&display=swap" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
<style> <style>
@@ -140,7 +140,7 @@ for ($i = 1; $i <= 6; $i++) {
</head> </head>
<body> <body>
<div class="header"> <div class="header">
<h1>إدارة أرقام بوابة الواتساب (6 جلسات)</h1> <h1>إدارة أرقام بوابة الواتساب (3 جلسات)</h1>
<p>يتم توزيع رسائل الـ OTP تلقائياً على الأرقام المتصلة بالتناوب (Round-Robin).</p> <p>يتم توزيع رسائل الـ OTP تلقائياً على الأرقام المتصلة بالتناوب (Round-Robin).</p>
<button class="refresh-btn global-refresh" onclick="window.location.reload();">تحديث جميع الحالات 🔄</button> <button class="refresh-btn global-refresh" onclick="window.location.reload();">تحديث جميع الحالات 🔄</button>
</div> </div>

View File

@@ -1,9 +1,11 @@
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const axios = require('axios'); const axios = require('axios');
const { execSync } = require('child_process');
const { Client, LocalAuth, MessageMedia } = require('whatsapp-web.js'); const { Client, LocalAuth, MessageMedia } = require('whatsapp-web.js');
const sessions = new Map(); const sessions = new Map();
const readyTimers = new Map();
const SESSIONS_DIR = path.join(__dirname, 'sessions'); const SESSIONS_DIR = path.join(__dirname, 'sessions');
if (!fs.existsSync(SESSIONS_DIR)) { if (!fs.existsSync(SESSIONS_DIR)) {
@@ -25,6 +27,7 @@ const puppeteerConfig = {
'--disable-background-timer-throttling', '--disable-background-timer-throttling',
'--disable-backgrounding-occluded-windows', '--disable-backgrounding-occluded-windows',
'--disable-renderer-backgrounding', '--disable-renderer-backgrounding',
'--disable-session-crashed-bubble',
'--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36' '--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36'
] ]
}; };
@@ -48,7 +51,9 @@ function cleanChromeLocks(sessionKey) {
const sessionPath = path.join(SESSIONS_DIR, `session-${sessionKey}`); const sessionPath = path.join(SESSIONS_DIR, `session-${sessionKey}`);
const lockFiles = [ const lockFiles = [
path.join(sessionPath, 'SingletonLock'), path.join(sessionPath, 'SingletonLock'),
path.join(sessionPath, 'Default', 'SingletonLock') path.join(sessionPath, 'Default', 'SingletonLock'),
path.join(sessionPath, 'SingletonCookie'),
path.join(sessionPath, 'Default', 'SingletonCookie')
]; ];
for (const lockFile of lockFiles) { for (const lockFile of lockFiles) {
try { try {
@@ -62,16 +67,33 @@ function cleanChromeLocks(sessionKey) {
} }
} }
// Clear Session Folder recursively to prevent corruption on logouts // Kill any Chrome processes still holding this session folder
function killChromeProcessesForSession(sessionKey) {
try {
const sessionPath = path.join(SESSIONS_DIR, `session-${sessionKey}`);
execSync(
`lsof +D "${sessionPath}" 2>/dev/null | awk 'NR>1 {print $2}' | xargs -r kill -9 2>/dev/null; sleep 1`,
{ stdio: 'ignore', timeout: 5000 }
);
} catch (_) {}
}
// Clear Session Folder with retry mechanism
function clearSessionFolder(sessionKey) { function clearSessionFolder(sessionKey) {
const sessionPath = path.join(SESSIONS_DIR, `session-${sessionKey}`); const sessionPath = path.join(SESSIONS_DIR, `session-${sessionKey}`);
try { for (let attempt = 0; attempt < 3; attempt++) {
if (fs.existsSync(sessionPath)) { try {
console.log(`[CLEANUP] Recursively removing session folder for ${sessionKey}: ${sessionPath}`); if (fs.existsSync(sessionPath)) {
fs.rmSync(sessionPath, { recursive: true, force: true }); console.log(`[CLEANUP] Removing session folder for ${sessionKey} (attempt ${attempt + 1})`);
fs.rmSync(sessionPath, { recursive: true, force: true });
}
return;
} catch (err) {
console.warn(`[CLEANUP] Attempt ${attempt + 1} failed for ${sessionKey}: ${err.message}`);
if (attempt < 2) {
killChromeProcessesForSession(sessionKey);
}
} }
} catch (err) {
console.error(`[CLEANUP ERROR] Failed to delete session folder for ${sessionKey}:`, err.message);
} }
} }
@@ -124,8 +146,21 @@ async function startSession(session_key, webhook_url) {
console.log(`[Session] ${session_key} authenticated successfully.`); console.log(`[Session] ${session_key} authenticated successfully.`);
}); });
client.on('auth_failure', async (reason) => {
console.error(`[Session] ${session_key} auth failure:`, reason);
await sendWebhook(webhook_url, {
session_key,
state: 'auth_failure',
reason: String(reason)
});
});
client.on('ready', async () => { client.on('ready', async () => {
console.log(`[Session] ${session_key} is ready and connected.`); console.log(`[Session] ${session_key} is ready and connected.`);
if (readyTimers.has(session_key)) {
clearTimeout(readyTimers.get(session_key));
readyTimers.delete(session_key);
}
const phoneNumber = client.info.wid.user; const phoneNumber = client.info.wid.user;
await sendWebhook(webhook_url, { await sendWebhook(webhook_url, {
session_key, session_key,
@@ -142,11 +177,29 @@ async function startSession(session_key, webhook_url) {
reason: reason reason: reason
}); });
sessions.delete(session_key); sessions.delete(session_key);
if (readyTimers.has(session_key)) {
clearTimeout(readyTimers.get(session_key));
readyTimers.delete(session_key);
}
killChromeProcessesForSession(session_key);
try { client.destroy(); } catch (_) {} try { client.destroy(); } catch (_) {}
// Wipe session folder to prevent corrupted state loading on next boot
clearSessionFolder(session_key); clearSessionFolder(session_key);
}); });
// Ready timeout: if ready doesn't fire within 90s, destroy and restart
const readyTimeout = setTimeout(async () => {
if (sessions.get(session_key) === client && (!client.info || !client.info.wid)) {
console.warn(`[Session] ${session_key} timeout: ready not fired in 90s, restarting...`);
sessions.delete(session_key);
readyTimers.delete(session_key);
killChromeProcessesForSession(session_key);
try { client.destroy(); } catch (_) {}
clearSessionFolder(session_key);
await startSession(session_key, webhook_url);
}
}, 90000);
readyTimers.set(session_key, readyTimeout);
// Handle Incoming Messages to Webhook // Handle Incoming Messages to Webhook
client.on('message', async (msg) => { client.on('message', async (msg) => {
if (msg.fromMe) return; if (msg.fromMe) return;
@@ -206,9 +259,17 @@ async function startSession(session_key, webhook_url) {
async function disconnectSession(session_key) { async function disconnectSession(session_key) {
const client = sessions.get(session_key); const client = sessions.get(session_key);
if (client) { if (client) {
if (readyTimers.has(session_key)) {
clearTimeout(readyTimers.get(session_key));
readyTimers.delete(session_key);
}
// Kill Chrome processes first to release file locks
killChromeProcessesForSession(session_key);
try { try {
await client.logout(); await client.logout();
} catch (_) {} } catch (err) {
console.warn(`[Session] Logout error for ${session_key}: ${err.message}`);
}
try { try {
client.destroy(); client.destroy();
} catch (_) {} } catch (_) {}

View File

@@ -92,7 +92,7 @@ app.post('/api/contacts/check', async (req, res) => {
if (session_key === 'auto') { if (session_key === 'auto') {
const activeSlots = []; const activeSlots = [];
for (let i = 1; i <= 6; i++) { for (let i = 1; i <= 3; i++) {
if (isSessionReady(`slot-${i}`)) { if (isSessionReady(`slot-${i}`)) {
activeSlots.push(`slot-${i}`); activeSlots.push(`slot-${i}`);
} }
@@ -140,7 +140,7 @@ app.post('/api/messages/send', async (req, res) => {
try { try {
if (session_key === 'auto') { if (session_key === 'auto') {
let activeSlots = []; let activeSlots = [];
for (let i = 1; i <= 6; i++) { for (let i = 1; i <= 3; i++) {
if (isSessionReady(`slot-${i}`)) { if (isSessionReady(`slot-${i}`)) {
activeSlots.push(`slot-${i}`); activeSlots.push(`slot-${i}`);
} }
@@ -185,13 +185,21 @@ app.post('/api/messages/send', async (req, res) => {
} }
}); });
// Auto-start default sessions (6 slots) // Auto-start default sessions (3 slots, staggered every 20s)
setTimeout(() => { async function startSlotsSequentially() {
console.log('🔄 Auto-starting 6 WhatsApp slots...'); console.log('🔄 Auto-starting 3 WhatsApp slots sequentially...');
for (let i = 1; i <= 6; i++) { const WEBHOOK_URL = 'https://otp.intaleqapp.com/api/whatsapp-webhook.php';
startSession(`slot-${i}`, 'https://otp.intaleqapp.com/api/whatsapp-webhook.php').catch(console.error); for (let i = 1; i <= 3; i++) {
console.log(`[Sequential] Starting slot-${i}...`);
startSession(`slot-${i}`, WEBHOOK_URL).catch(err => {
console.error(`[Sequential] slot-${i} failed:`, err.message);
});
if (i < 3) {
await new Promise(resolve => setTimeout(resolve, 20000));
}
} }
}, 2000); }
setTimeout(startSlotsSequentially, 2000);
app.listen(PORT, () => { app.listen(PORT, () => {
console.log(`🚀 Flash Call OTP WhatsApp Gateway running on port ${PORT}`); console.log(`🚀 Flash Call OTP WhatsApp Gateway running on port ${PORT}`);