const express = require('express'); const qrcode = require('qrcode'); const path = require('path'); const fs = require('fs'); const NodeCache = require('node-cache'); const mime = require('mime-types'); const fetch = require('node-fetch'); // en haut si pas encore ajouté const { default: makeWASocket, useMultiFileAuthState, DisconnectReason, fetchLatestBaileysVersion, proto, generateWAMessageFromContent, prepareWAMessageMedia, generateWAMessageContent } = require('@fizzxydev/baileys-pro'); const app = express(); const http = require('http'); const WebSocket = require('ws'); const server = http.createServer(app); const wss = new WebSocket.Server({ server }); function broadcastToClients(data) { const message = JSON.stringify(data); wss.clients.forEach(client => { if (client.readyState === WebSocket.OPEN) { client.send(message); } }); } app.use(express.json()); app.use(express.static('public')); let sock; let qrData = null; let isConnected = false; // 💾 Group Metadata Cache (5 minutes) const groupCache = new NodeCache({ stdTTL: 300, useClones: false }); // Simule une récupération de message depuis un store pour getMessage async function getMessageFromStore(key) { // À adapter selon ta logique réelle return { conversation: "Message temporaire pour retry" }; } app.post('/webhook/status', (req, res) => { console.log('📩 Webhook reçu :', req.body); res.sendStatus(200); }); const initBaileys = async () => { const { version } = await fetchLatestBaileysVersion(); const { state, saveCreds } = await useMultiFileAuthState('auth'); sock = makeWASocket({ version, auth: state, markOnlineOnConnect: false, getMessage: async (key) => await getMessageFromStore(key), cachedGroupMetadata: async (jid) => groupCache.get(jid) }); sock.ev.on('connection.update', async ({ connection, lastDisconnect, qr }) => { if (qr) { qrData = await qrcode.toDataURL(qr); isConnected = false; } if (connection === 'close') { const shouldReconnect = lastDisconnect?.error?.output?.statusCode !== DisconnectReason.loggedOut; console.log(shouldReconnect ? '🔁 Reconnexion...' : '❌ Déconnecté.'); if (shouldReconnect) initBaileys(); } else if (connection === 'open') { console.log('✅ Connecté à WhatsApp'); isConnected = true; } }); sock.ev.on('creds.update', saveCreds); // 📌 Caching des groupes sock.ev.on('groups.update', async ([event]) => { const metadata = await sock.groupMetadata(event.id); groupCache.set(event.id, metadata); }); sock.ev.on('group-participants.update', async (event) => { const metadata = await sock.groupMetadata(event.id); groupCache.set(event.id, metadata); }); // (Facultatif) Gestion des messages reçus sock.ev.on('messages.upsert', async ({ messages }) => { const msg = messages[0]; if (!msg.key.fromMe && msg.message?.conversation) { console.log('💬 Message reçu de', msg.key.remoteJid, ':', msg.message.conversation); } if (!msg.message || !msg.key.fromMe) { const jid = msg.key.remoteJid; const buttonId = msg.message.buttonsResponseMessage?.selectedButtonId; if (buttonId === 'doc_1') { await sock.sendMessage(jid, { document: { url: 'https://merlo-ch.com/uploads/proposition/f_p_250505_0000136_00008_EB00001909.pdf', }, mimetype: 'application/pdf', fileName: 'Document_1.pdf', caption: 'Voici votre *Document 1* 📄', footer: '© Fizzxy Dev', }); } else if (buttonId === 'doc_2') { await sock.sendMessage(jid, { document: { url: 'https://merlo-ch.com/uploads/proposition/d_p_250505_0000136_00008_EB00001909.pdf', }, mimetype: 'application/pdf', fileName: 'Document_2.pdf', caption: 'Voici votre *Document 2* 📄', footer: '© Fizzxy Dev', }); } } }); sock.ev.on('messages.update', async (updates) => { broadcastToClients({ messageId, status, jid }); }); }; initBaileys(); app.use('/static', express.static(path.join(__dirname, 'public'))); app.get('/login', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'login.html')); }); app.get('/api/qrcode', (req, res) => { res.json({ qr: qrData, connected: isConnected }); }); app.post('/sendText', async (req, res) => { const { phone, message } = req.body; if (!sock || !isConnected) return res.status(400).json({ error: 'Non connecté' }); try { await sock.sendMessage(`${phone}@s.whatsapp.net`, { text: message }); res.json({ success: true }); } catch (e) { res.status(500).json({ error: e.message }); } }); //// ***** bonne route ****** /// /* exemple de json *{ "phone": "33617452235", "imageUrl": "https://wa.canguidev.fr/static/logo-merlo-cs-FR.jpg", "caption": "🟢 Offre spéciale !\n📅 Merlo2025\n\nBonjour M. Jérôme VANDEMEULEBROUCK,\n\nSuite à nos échanges, je vous transmets l'offre que je vous propose.\nVous trouverez ci-joint votre offre au format PDF.\n\n📄 Proposition : https://pvmel.fr/V5Pi\n🛠️ Descriptif machine : \n\n—\nCanguilième Julien\n📎 Votre offre en PDF ci-joint." } * */ app.post('/sendImageWithBaileysPro', async (req, res) => { if (!sock || !isConnected) { return res.status(400).json({ error: 'Non connecté à WhatsApp' }); } const { phone, imageUrl, caption, quoted, } = req.body; if (!phone || !imageUrl) { return res.status(400).json({ error: 'Paramètres requis manquants (phone, imageUrl)' }); } const jid = `${phone}@s.whatsapp.net`; try { await sock.sendMessage( jid, { image: { url: imageUrl }, caption: caption || '', }, quoted ? { quoted } : {} ); res.json({ success: true, to: jid }); } catch (e) { console.error('❌ Erreur /sendImageWithBaileysPro :', e); res.status(500).json({ error: e.message }); } }); app.use((req, res) => res.status(404).json({ error: 'Ressource introuvable' })); app.use((err, req, res, next) => { console.error('Middleware d’erreur :', err); res.status(500).json({ error: err.message }); }); app.listen(3001, () => console.log('🚀 Serveur Baileys démarré sur http://localhost:3001'));