const fs = require('fs'); const path = require('path'); const dotenv = require('dotenv'); // Find .env file identically to how PHP bootstrap does it const envPaths = [ path.join(__dirname, '.env'), path.join(__dirname, '../.env'), path.join(__dirname, '../backend/.env'), path.join(__dirname, '../../../.env') ]; for (const p of envPaths) { if (fs.existsSync(p)) { dotenv.config({ path: p }); console.log(`Loaded environment from ${p}`); break; } } const express = require('express'); const cors = require('cors'); const { startSession, disconnectSession, sendMessage, getActiveSessions, checkContact, isSessionReady } = require('./baileys-client'); const app = express(); app.use(cors()); app.use(express.json({ limit: '50mb' })); app.use(express.urlencoded({ limit: '50mb', extended: true })); const PORT = process.env.PORT || 3722; // Health check endpoint (Public) app.get('/health', (req, res) => { res.json({ status: 'healthy', service: 'Flash Call OTP WhatsApp Gateway' }); }); // Security Middleware: Protect all /api/ routes app.use('/api', (req, res, next) => { const secret = req.header('X-Webhook-Secret'); if (!process.env.WEBHOOK_SECRET || secret !== process.env.WEBHOOK_SECRET) { return res.status(403).json({ error: 'Unauthorized gateway access' }); } next(); }); // Start or retrieve a session app.post('/api/sessions/start', async (req, res) => { const { session_key, webhook_url } = req.body; if (!session_key || !webhook_url) { return res.status(400).json({ error: 'Missing session_key or webhook_url' }); } try { await startSession(session_key, webhook_url); res.json({ status: 'success', message: 'Session started or retrieved' }); } catch (err) { console.error(`Error starting session ${session_key}:`, err); res.status(500).json({ error: 'Failed to start session' }); } }); // Disconnect and remove a session (e.g., when banned or logged out) app.post('/api/sessions/disconnect', async (req, res) => { const { session_key } = req.body; if (!session_key) { return res.status(400).json({ error: 'Missing session_key' }); } try { await disconnectSession(session_key); res.json({ status: 'success', message: 'Session disconnected and cleaned up' }); } catch (err) { console.error(`Error disconnecting session ${session_key}:`, err); res.status(500).json({ error: 'Failed to disconnect session' }); } }); // Get list of active session keys in memory app.get('/api/sessions/active', (req, res) => { res.json({ status: 'success', active_sessions: getActiveSessions() }); }); // Check if contact is on WhatsApp app.post('/api/contacts/check', async (req, res) => { let { session_key, phone } = req.body; if (!session_key || !phone) { return res.status(400).json({ error: 'Missing session_key or phone' }); } if (session_key === 'auto') { const activeSlots = []; for (let i = 1; i <= 3; i++) { if (isSessionReady(`slot-${i}`)) { activeSlots.push(`slot-${i}`); } } if (activeSlots.length === 0) { return res.status(503).json({ error: 'No WhatsApp slots are ready to check contacts' }); } let checkError = null; for (const slotKey of activeSlots) { try { const result = await checkContact(slotKey, phone); return res.json({ status: 'success', session_key: slotKey, data: result }); } catch (err) { console.error(`[ContactCheck] Failed check via ${slotKey}:`, err.message); checkError = err; } } return res.status(500).json({ error: `Failed to check contact via any active slot. Last error: ${checkError.message}` }); } try { const result = await checkContact(session_key, phone); res.json({ status: 'success', data: result }); } catch (err) { console.error(`Error checking contact ${phone} via ${session_key}:`, err); res.status(500).json({ error: err.message || 'Failed to check contact' }); } }); // Send outbound message let currentSlotIndex = 1; app.post('/api/messages/send', async (req, res) => { let { session_key, phone, message, media_url, audio, mimetype, image } = req.body; if (!session_key || !phone) { return res.status(400).json({ error: 'Missing session_key or phone' }); } if (!message && !audio && !media_url && !image) { return res.status(400).json({ error: 'Missing message, audio, media_url, or image' }); } try { if (session_key === 'auto') { let activeSlots = []; for (let i = 1; i <= 3; i++) { if (isSessionReady(`slot-${i}`)) { activeSlots.push(`slot-${i}`); } } if (activeSlots.length === 0) { return res.status(503).json({ error: 'No WhatsApp slots are currently ready to send messages' }); } let lastError = null; let attempts = 0; const maxAttempts = activeSlots.length; while (attempts < maxAttempts) { const selectedKey = activeSlots[currentSlotIndex % activeSlots.length]; currentSlotIndex++; attempts++; try { console.log(`[RoundRobin] Attempt ${attempts}/${maxAttempts}: Trying to send via ${selectedKey}`); const result = await sendMessage(selectedKey, phone, message, media_url, audio, mimetype, image); return res.json({ status: 'success', session_key: selectedKey, data: result }); } catch (err) { console.error(`[RoundRobin] Attempt ${attempts} failed via ${selectedKey}:`, err.message); lastError = err; activeSlots = activeSlots.filter(s => s !== selectedKey); if (activeSlots.length === 0) break; } } return res.status(500).json({ error: `Failed to send message after trying all active slots. Last error: ${lastError ? lastError.message : 'Unknown'}` }); } else { const result = await sendMessage(session_key, phone, message, media_url, audio, mimetype, image); res.json({ status: 'success', data: result }); } } catch (err) { console.error(`Error sending message via ${session_key} to ${phone}:`, err); res.status(500).json({ error: err.message || 'Failed to send message' }); } }); // Auto-start default sessions (3 slots, staggered every 20s) async function startSlotsSequentially() { console.log('🔄 Auto-starting 3 WhatsApp slots sequentially...'); const WEBHOOK_URL = 'https://otp.intaleqapp.com/api/whatsapp-webhook.php'; 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)); } } } setTimeout(startSlotsSequentially, 2000); app.listen(PORT, () => { console.log(`🚀 Flash Call OTP WhatsApp Gateway running on port ${PORT}`); });