// index.js const { default: makeWASocket, useMultiFileAuthState, DisconnectReason, fetchLatestBaileysVersion, proto, generateWAMessageFromContent } = require('@fizzxydev/baileys-pro') const express = require('express') const { Boom } = require('@hapi/boom') const qrcode = require('qrcode') const path = require('path') const fs = require('fs') const axios = require('axios') const app = express() app.use(express.json()) app.use(express.static('public')) let sock let qrData = null let isConnected = false // Dictionnaire des URLs PDF const PDF_LINKS = { prop: 'https://merlo-ch.com/uploads/proposition/f_p_250505_0000136_00008_EB00001909.pdf', spec: 'https://merlo-ch.com/uploads/proposition/d_p_250505_0000136_00008_EB00001909.pdf' } const initBaileys = async () => { const { version } = await fetchLatestBaileysVersion() const { state, saveCreds } = await useMultiFileAuthState('auth') sock = makeWASocket({ version, auth: state, printQRInTerminal: false }) sock.ev.on('connection.update', async update => { const { connection, lastDisconnect, qr } = update if (qr) { try { qrData = await qrcode.toDataURL(qr) isConnected = false } catch (e) { console.error('❌ Erreur génération QR :', e) } } 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) // Gestion du clic sur les boutons pour envoyer le PDF sock.ev.on('messages.upsert', async ({ messages }) => { const msg = messages[0] if (!msg.key.fromMe && msg.message?.buttonsResponseMessage) { const btnId = msg.message.buttonsResponseMessage.selectedButtonId const pdfUrl = PDF_LINKS[btnId] if (!pdfUrl) return try { const resp = await axios.get(pdfUrl, { responseType: 'arraybuffer' }) const pdfBuffer = Buffer.from(resp.data) await sock.sendMessage(msg.key.remoteJid, { document: pdfBuffer, fileName: btnId === 'prop' ? 'Proposition.pdf' : 'Spec_machine.pdf', mimetype: 'application/pdf' }) } catch (e) { console.error('❌ Erreur envoi PDF :', e) } } }) } initBaileys().catch(console.error) // Affichage du QR code pour login 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 }) }) // Envoi d'un simple texte 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 }) } }) // Envoi de boutons sans image app.post('/sendButtons', async (req, res) => { const { phone } = req.body if (!sock || !isConnected) return res.status(400).json({ error: 'Non connecté' }) try { const content = { message: { interactiveMessage: proto.Message.InteractiveMessage.create({ header: proto.Message.InteractiveMessage.Header.create({ title: "Menu principal", hasMediaAttachment: false }), body: proto.Message.InteractiveMessage.Body.create({ text: "Bienvenue sur notre service !" }), footer: proto.Message.InteractiveMessage.Footer.create({ text: "Choisis une action ci-dessous" }), nativeFlowMessage: proto.Message.InteractiveMessage.NativeFlowMessage.create({ buttons: [ { name: "cta_reply", buttonParamsJson: JSON.stringify({ display_text: "📩 Contacter support", id: "support_action" }) }, { name: "cta_url", buttonParamsJson: JSON.stringify({ display_text: "🌐 Voir notre site", url: "https://canguidev.fr", merchant_url: "https://canguidev.fr" }) }, { name: "cta_call", buttonParamsJson: JSON.stringify({ display_text: "📞 Appeler le support", id: "+33612345678" }) } ] }) }) } } const msg = generateWAMessageFromContent( `${phone}@s.whatsapp.net`, content, {} ) await sock.relayMessage( msg.key.remoteJid, msg.message, { messageId: msg.key.id } ) res.json({ success: true }) } catch (e) { console.error('❌ Erreur sendButtons :', e) res.status(500).json({ error: e.message }) } }) // Envoi de l'image interactive avec CTA URL app.post('/sendInteractiveImage', async (req, res) => { const { phone, caption = 'Description par défaut', title = 'Titre par défaut', footer = 'Pied de page', propositionUrl, specUrl } = req.body if (!sock || !isConnected) { return res.status(400).json({ error: 'Non connecté' }) } try { const jid = `${phone}@s.whatsapp.net` const imagePath = path.join(__dirname, 'public', 'logo-merlo-cs-FR.jpg') const imgBuffer = fs.readFileSync(imagePath) // Construction du native interactive message const interactiveContent = proto.Message.InteractiveMessage.create({ header: proto.Message.InteractiveMessage.Header.create({ title, hasMediaAttachment: true }), body: proto.Message.InteractiveMessage.Body.create({ text: caption }), footer: proto.Message.InteractiveMessage.Footer.create({ text: footer }), nativeFlowMessage: proto.Message.InteractiveMessage.NativeFlowMessage.create({ buttons: [ { name: 'prop', buttonParamsJson: JSON.stringify({ display_text: 'Proposition', url: propositionUrl, merchant_url: propositionUrl }) }, { name: 'spec', buttonParamsJson: JSON.stringify({ display_text: 'Spec machine', url: specUrl, merchant_url: specUrl }) } ] }) }) // On emballe l’image en tant que miniature (viewOnce) const messageContent = { viewOnceMessage: { message: { messageContextInfo: { deviceListMetadata: {}, deviceListMetadataVersion: 2 }, viewOnceMessage: { message: { imageMessage: { mimetype: 'image/jpeg', jpegThumbnail: imgBuffer } } }, interactiveMessage: interactiveContent } } } const waMsg = generateWAMessageFromContent(jid, messageContent, {}) await sock.relayMessage( waMsg.key.remoteJid, waMsg.message, { messageId: waMsg.key.id } ) res.json({ success: true }) } catch (err) { console.error('❌ Erreur sendInteractiveImage:', err) res.status(500).json({ error: err.message }) } }) const PORT = process.env.PORT || 3001 app.listen(PORT, () => { console.log(`🚀 Serveur Baileys démarré sur http://localhost:${PORT}`) })