const fs = require('fs'); const path = require('path'); const axios = require('axios'); const { Client, LocalAuth, MessageMedia } = require('whatsapp-web.js'); const sessions = new Map(); const SESSIONS_DIR = path.join(__dirname, 'sessions'); if (!fs.existsSync(SESSIONS_DIR)) { fs.mkdirSync(SESSIONS_DIR, { recursive: true }); } // Puppeteer / Chrome Config const puppeteerConfig = { headless: 'new', args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-accelerated-2d-canvas', '--no-first-run', '--disable-gpu', '--disable-web-security', '--disable-features=IsolateOrigins,site-per-process' ] }; const possiblePaths = [ process.env.CHROME_BIN, '/usr/bin/chromium', '/usr/bin/chromium-browser', '/usr/bin/google-chrome', '/usr/bin/google-chrome-stable' ]; for (const p of possiblePaths) { if (p && fs.existsSync(p)) { puppeteerConfig.executablePath = p; break; } } async function sendWebhook(webhook_url, payload) { try { console.log(`[Webhook] Sending to ${webhook_url} | state=${payload.state}`); const response = await axios.post(webhook_url, payload, { headers: { 'Content-Type': 'application/json', 'X-Webhook-Secret': process.env.WEBHOOK_SECRET || '' }, timeout: 10000 }); console.log(`[Webhook] ✅ Success | HTTP ${response.status}`); } catch (err) { console.error(`[Webhook] ❌ Failed: ${err.message}`); } } async function startSession(session_key, webhook_url) { if (sessions.has(session_key)) { return sessions.get(session_key); } console.log(`[Session] Starting ${session_key} → webhook: ${webhook_url}`); const client = new Client({ authStrategy: new LocalAuth({ clientId: session_key, dataPath: SESSIONS_DIR }), puppeteer: puppeteerConfig }); sessions.set(session_key, client); client.on('qr', async (qr) => { console.log(`[QR] Generated for ${session_key}`); await sendWebhook(webhook_url, { session_key, state: 'waiting_qr', qr_code: qr }); }); client.on('authenticated', () => { console.log(`[Session] ${session_key} authenticated successfully.`); }); client.on('ready', async () => { console.log(`[Session] ${session_key} is ready and connected.`); const phoneNumber = client.info.wid.user; await sendWebhook(webhook_url, { session_key, state: 'connected', phone: phoneNumber }); }); client.on('disconnected', async (reason) => { console.warn(`[Session] ${session_key} disconnected! Reason:`, reason); await sendWebhook(webhook_url, { session_key, state: 'disconnected', reason: reason }); sessions.delete(session_key); try { client.destroy(); } catch (_) {} }); // Handle Incoming Messages to Webhook client.on('message', async (msg) => { if (msg.fromMe) return; const senderPhone = msg.from.split('@')[0]; const contact = await msg.getContact(); const senderName = contact.name || contact.pushname || ''; let audioBase64 = null; let imageBase64 = null; let mimeType = null; let duration = null; if (msg.hasMedia) { try { const media = await msg.downloadMedia(); if (media) { if (media.mimetype.startsWith('audio/')) { audioBase64 = media.data; mimeType = media.mimetype; } else if (media.mimetype.startsWith('image/')) { imageBase64 = media.data; mimeType = media.mimetype; } } } catch (e) { console.error('Failed to download media:', e.message); } } await sendWebhook(webhook_url, { session_key, state: 'message_received', message: { id: msg.id.id, phone: senderPhone, name: senderName, body: msg.body, audio: audioBase64, mimeType: mimeType, duration: duration, image: imageBase64, imageMimeType: mimeType, timestamp: msg.timestamp } }); }); client.initialize().catch(err => { console.error(`[Session] ${session_key} failed to initialize:`, err.message); sessions.delete(session_key); }); return client; } async function disconnectSession(session_key) { const client = sessions.get(session_key); if (client) { await client.logout(); client.destroy(); sessions.delete(session_key); } } async function checkContact(session_key, phone) { const client = sessions.get(session_key); if (!client) throw new Error('Session not active'); const chatId = phone.includes('@') ? phone : `${phone}@c.us`; const isRegistered = await client.isRegisteredUser(chatId); return { exists: isRegistered }; } async function sendMessage(session_key, phone, message, media_url, audio, mimetype, image) { const client = sessions.get(session_key); if (!client) throw new Error('Session not active'); const chatId = phone.includes('@') ? phone : `${phone}@c.us`; let content = message || ''; let options = {}; if (audio || image) { const base64Data = audio || image; const mt = mimetype || (audio ? 'audio/ogg' : 'image/jpeg'); let cleanBase64 = base64Data.trim(); if (cleanBase64.includes(';base64,')) { cleanBase64 = cleanBase64.split(';base64,')[1]; } const media = new MessageMedia(mt, cleanBase64, 'file'); content = media; if (audio) { options.sendAudioAsVoice = true; } } const sentMsg = await client.sendMessage(chatId, content, options); return { messageId: sentMsg.id.id, status: 'sent' }; } function getActiveSessions() { return Array.from(sessions.keys()); } module.exports = { startSession, disconnectSession, sendMessage, getActiveSessions, checkContact };