Deploy: 2026-06-24 15:26:15
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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 (_) {}
|
||||||
|
|||||||
@@ -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}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user