Files
flash-call-otp/whatsapp-gateway/server.js
2026-06-24 15:32:02 +03:00

207 lines
7.4 KiB
JavaScript

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}`);
});