Deploy: 2026-06-24 14:31:54
This commit is contained in:
224
whatsapp-gateway/puppeteer-client.js
Normal file
224
whatsapp-gateway/puppeteer-client.js
Normal file
@@ -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
|
||||
};
|
||||
Reference in New Issue
Block a user