diff --git a/receiver_app_new/devtools_options.yaml b/receiver_app_new/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/receiver_app_new/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/whatsapp-gateway/package.json b/whatsapp-gateway/package.json index 5837100..041f1be 100644 --- a/whatsapp-gateway/package.json +++ b/whatsapp-gateway/package.json @@ -7,7 +7,9 @@ "start": "node server.js" }, "dependencies": { - "@whiskeysockets/baileys": "^6.7.9", + "whatsapp-web.js": "^1.23.0", + "puppeteer": "^21.0.0", + "qrcode": "^1.5.3", "axios": "^1.7.2", "cors": "^2.8.5", "dotenv": "^16.4.5", diff --git a/whatsapp-gateway/puppeteer-client.js b/whatsapp-gateway/puppeteer-client.js new file mode 100644 index 0000000..3590b2b --- /dev/null +++ b/whatsapp-gateway/puppeteer-client.js @@ -0,0 +1,224 @@ +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}`); + const QRCode = require('qrcode'); + QRCode.toDataURL(qr, async (err, url) => { + if (!err) { + await sendWebhook(webhook_url, { + session_key, + state: 'waiting_qr', + qr_code: url + }); + } + }); + }); + + 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 +}; diff --git a/whatsapp-gateway/server.js b/whatsapp-gateway/server.js index 4178eeb..8c7323b 100644 --- a/whatsapp-gateway/server.js +++ b/whatsapp-gateway/server.js @@ -20,7 +20,7 @@ for (const p of envPaths) { const express = require('express'); const cors = require('cors'); -const { startSession, disconnectSession, sendMessage, getActiveSessions, checkContact } = require('./baileys-client'); +const { startSession, disconnectSession, sendMessage, getActiveSessions, checkContact } = require('./puppeteer-client'); const app = express(); app.use(cors());