From db94c8a111745f3a11aa1b03a55692bfbcf21241 Mon Sep 17 00:00:00 2001 From: cangui Date: Tue, 6 May 2025 22:25:52 +0200 Subject: [PATCH] ghghg --- index.js | 541 ++++++++++++++++++++++++++++++++++++++++++++------- package.json | 4 +- 2 files changed, 472 insertions(+), 73 deletions(-) diff --git a/index.js b/index.js index 9b078a4..9393e2f 100644 --- a/index.js +++ b/index.js @@ -1,51 +1,56 @@ const express = require('express'); const qrcode = require('qrcode'); +const path = require('path'); +const fs = require('fs'); +const NodeCache = require('node-cache'); const { default: makeWASocket, useMultiFileAuthState, DisconnectReason, fetchLatestBaileysVersion, - generateWAMessageFromContent, - proto -} = require('@whiskeysockets/baileys'); -const { Boom } = require('@hapi/boom'); + proto, + generateWAMessageFromContent +} = require('@fizzxydev/baileys-pro'); const app = express(); app.use(express.json()); -const path = require('path'); // à ajouter en haut si pas encore importé - -// Sert les fichiers statiques depuis le dossier "public" app.use(express.static('public')); -// Route pour afficher la page de login avec QR code -app.get('/login', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'login.html')); -}); - let sock; -let qrCodeData = null; +let qrData = null; let isConnected = false; -async function startSock() { - const { state, saveCreds } = await useMultiFileAuthState('auth_info'); +// 💾 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" }; +} + +const initBaileys = async () => { const { version } = await fetchLatestBaileysVersion(); + const { state, saveCreds } = await useMultiFileAuthState('auth'); sock = makeWASocket({ version, auth: state, - printQRInTerminal: true + 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) { - qrCodeData = await qrcode.toDataURL(qr); + qrData = await qrcode.toDataURL(qr); + isConnected = false; } if (connection === 'close') { - const shouldReconnect = (lastDisconnect?.error instanceof Boom) - && lastDisconnect.error.output.statusCode !== DisconnectReason.loggedOut; - console.log('❌ Déconnecté. Reconnexion :', shouldReconnect); - if (shouldReconnect) startSock(); + 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; @@ -53,17 +58,42 @@ async function startSock() { }); sock.ev.on('creds.update', saveCreds); -} -startSock(); + // 📌 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); + } + }); +}; + +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: qrCodeData, connected: isConnected }); + 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 }); @@ -71,43 +101,99 @@ app.post('/sendText', async (req, res) => { res.status(500).json({ error: e.message }); } }); - -app.post('/sendImage', async (req, res) => { - const { phone, imageUrl, caption } = req.body; - if (!sock || !isConnected) return res.status(400).json({ error: 'Non connecté' }); - - try { - await sock.sendMessage(`${phone}@s.whatsapp.net`, { - image: { url: imageUrl }, - caption: caption || '' - }); - res.json({ success: true }); - } catch (e) { - res.status(500).json({ error: e.message }); - } -}); - -app.post('/sendDocument', async (req, res) => { - const { phone, documentUrl, fileName, mimetype } = req.body; - if (!sock || !isConnected) return res.status(400).json({ error: 'Non connecté' }); - - try { - await sock.sendMessage(`${phone}@s.whatsapp.net`, { - document: { url: documentUrl }, - fileName: fileName || 'fichier.pdf', - mimetype: mimetype || 'application/pdf' - }); - res.json({ success: true }); - } catch (e) { - res.status(500).json({ error: e.message }); - } -}); -app.post('/sendInteractive', async (req, res) => { +app.post('/testButtons', async (req, res) => { const { phone } = req.body; - if (!sock || !isConnected) return res.status(400).json({ error: 'Non connecté' }); + + if (!sock || !isConnected) { + return res.status(400).json({ error: 'Non connecté' }); + } + + const jid = `${phone}@s.whatsapp.net`; + + try { + await sock.sendMessage(jid, { + image: { url : "https://wa.canguidev.fr/static/logo-merlo-cs-FR.jpg" }, // Can buffer + text: "Hello Wolrd !;", + caption: "Description Of Messages", //Additional information + footer: "© Fizzxy Dev", + media:true, + image: { + url: 'https://wa.canguidev.fr/static/logo-merlo-cs-FR.jpg' + }, + buttons: [ + { + buttonId: '.tes', + buttonText: { + displayText: 'TESTING BOT' + }, + type: 1 + }, + { + buttonId: ' ', + buttonText: { + displayText: 'PRIVATE SCRIPT' + }, + type: 1 + }, + { + buttonId: 'action', + buttonText: { + displayText: 'ini pesan interactiveMeta' + }, + type: 4, + nativeFlowInfo: { + name: 'single_select', + paramsJson: JSON.stringify({ + title: 'message', + sections: [ + { + title: 'FizzxyDev - 2025', + highlight_label: '😜', + rows: [ + { + header: 'HEADER', + title: 'TITLE', + description: 'DESCRIPTION', + id: 'YOUR ID 1' + }, + { + header: 'HEADER', + title: 'TITLE', + description: 'DESCRIPTION', + id: 'YOUR ID 2' + } + ] + } + ] + }) + } + } + ], + headerType: 1, + viewOnce: true + }, { + quoted: null // ou tu peux injecter un msg pour reply ici + }); + + res.json({ success: true }); + } catch (e) { + console.error('❌ Erreur testButtons :', e); + res.status(500).json({ error: e.message }); + } +}); + +app.post('/sendButtons', async (req, res) => { + const { phone } = req.body; + + if (!sock || !isConnected) { + return res.status(400).json({ error: 'Non connecté' }); + } + + const BASE_URL = process.env.BASE_URL || 'https://wa.canguidev.fr'; + const imageUrl = `${BASE_URL}/static/logo-merlo-cs-FR.jpg`; + const jid = `${phone}@s.whatsapp.net`; try { - const jid = `${phone}@s.whatsapp.net`; const content = { viewOnceMessage: { message: { @@ -116,22 +202,33 @@ app.post('/sendInteractive', async (req, res) => { deviceListMetadataVersion: 2 }, interactiveMessage: proto.Message.InteractiveMessage.create({ - body: { text: 'Bienvenue ! Choisissez une action :' }, - footer: { text: 'Service WhatsApp' }, + body: { text: "Bienvenue sur notre service !" }, + footer: { text: "Choisis une action ci-dessous" }, + header: { + hasMediaAttachment: true, + imageMessage: { url: imageUrl } + }, nativeFlowMessage: { buttons: [ { - name: 'cta_reply', + name: "cta_reply", buttonParamsJson: JSON.stringify({ - display_text: '✅ Confirmer', - id: 'confirm_action' + display_text: "📩 Contacter support", + id: "support_action" }) }, { - name: 'cta_url', + name: "cta_url", buttonParamsJson: JSON.stringify({ - display_text: '🌐 Site Web', - url: 'https://canguidev.fr' + display_text: "🌐 Voir notre site", + url: "https://canguidev.fr" + }) + }, + { + name: "cta_call", + buttonParamsJson: JSON.stringify({ + display_text: "📞 Appeler le support", + id: "+33612345678" }) } ] @@ -146,11 +243,313 @@ app.post('/sendInteractive', async (req, res) => { res.json({ success: true }); } catch (e) { - console.error('❌ Erreur interactive :', e); + console.error('❌ Erreur bouton actif :', e); res.status(500).json({ error: e.message }); } }); -const PORT = process.env.PORT || 3001; -app.listen(PORT, () => { - console.log(`🚀 Serveur prêt sur http://localhost:${PORT}`); + + +// Votre route POST +app.post('/sendInteractiveImage', async (req, res) => { + const { phone, caption, title, subtitle, footer } = req.body; + if (!sock || !isConnected) { + return res.status(400).json({ error: 'Non connecté à WhatsApp' }); + } + + try { + const BASE_URL = process.env.BASE_URL || 'https://wa.canguidev.fr'; + const imageUrl = `${BASE_URL}/static/logo-merlo-cs-FR.jpg`; + + // Construire le Header correct + const header = proto.Message.InteractiveMessage.create({ + // ici on injecte l'image dans le header sans besoin de type explicite + header: proto.Message.InteractiveMessage.Header.create({ + title: 'Igna', + subtitle: 'test' + }), + }) + // Body et Footer + const body = proto.Message.InteractiveMessage.Body.create({ + text: caption || 'Description par défaut' + }); + const foot = proto.Message.InteractiveMessage.Footer.create({ + text: footer || 'Pied de page' + }); + + // Vos boutons URL + const nativeFlow = proto.Message.InteractiveMessage.NativeFlowMessage.create({ + buttons: [ + { + name: 'cta_url', + buttonParamsJson: JSON.stringify({ + display_text: '📄 Voir proposition', + url: 'https://merlo-ch.com/uploads/proposition/f_p_250505_0000136_00008_EB00001909.pdf' + }) + }, + { + name: 'cta_url', + buttonParamsJson: JSON.stringify({ + display_text: '🔧 Spécifications', + url: 'https://merlo-ch.com/uploads/proposition/d_p_250505_0000136_00008_EB00001909.pdf' + }) + } + ] + }); + + // Construire l'InteractiveMessage complet + const interactiveMsg = proto.Message.InteractiveMessage.create({ + header, + body, + footer: foot, + nativeFlowMessage: nativeFlow + }); + + // Envelopper (ici dans viewOnceMessage, comme dans votre exemple) + const raw = { + viewOnceMessage: { + message: { + messageContextInfo: { + deviceListMetadata: {}, + deviceListMetadataVersion: 2 + }, + interactiveMessage: interactiveMsg + } + } + }; + + // Générer et relayer + const jid = `${phone}@s.whatsapp.net`; + const msg = generateWAMessageFromContent(jid, raw, {}); + await sock.relayMessage(jid, msg.message, { messageId: msg.key.id }); + + return res.json({ success: true }); + } catch (e) { + console.error('❌ Erreur /sendInteractiveImage :', e); + return res.status(500).json({ error: e.message }); + } }); + +// app.post('/sendInteractiveImage', async (req, res) => { +// const { phone, caption, title, subtitle, footer } = req.body; +// if (!sock || !isConnected) return res.status(400).json({ error: 'Non connecté' }); + +// try { +// const imagePath = path.join(__dirname, 'public', 'logo-merlo-cs-FR.jpg'); +// const resizedBuffer = fs.readFileSync(imagePath); + +// await sock.sendMessage(`${phone}@s.whatsapp.net`, { +// image: resizedBuffer, +// caption: caption || 'Description par défaut', +// title: title || 'Titre par défaut', +// subtitle: subtitle || 'Sous-titre', +// footer: footer || 'Pied de page', +// media: true, +// interactiveButtons: [ +// { +// name: 'cta_url', +// buttonParamsJson: JSON.stringify({ +// display_text: 'Proposition', +// url: 'https://merlo-ch.com/uploads/proposition/f_p_250505_0000136_00008_EB00001909.pdf' +// }) +// }, +// { +// name: 'cta_url', +// buttonParamsJson: JSON.stringify({ +// display_text: 'Spec machine', +// url: 'https://merlo-ch.com/uploads/proposition/d_p_250505_0000136_00008_EB00001909.pdf' +// }) +// } +// ] +// }); +// res.json({ success: true }); +// } catch (e) { +// console.error('❌ Erreur interactive image :', e); +// res.status(500).json({ error: e.message }); +// } +// }); +app.post('/sendProductMessage', async (req, res) => { + // On vérifie sock et isConnected, pas client + if (!sock || !isConnected) { + return res.status(400).json({ error: 'Non connecté à WhatsApp' }); + } + + const { + phone, + productImageUrl, + productImageCount, + productTitle, + productDescription, + priceAmount1000, + currencyCode, + retailerId, + productUrl, + businessOwnerJid, + caption, + messageTitle, + footer, + interactiveButtons = [], + quoted + } = req.body; + + try { + const jid = `${phone}@s.whatsapp.net`; + + // Build product payload exactly comme avant + const productPayload = { + productImage: { url: productImageUrl }, + productImageCount, + title: productTitle, // ici productTitle + description: productDescription, + priceAmount1000: priceAmount1000 * 1000, // ajustez si besoin + currencyCode, + retailerId, + url: productUrl + }; + + // Transformer les boutons + const buttons = interactiveButtons.map(btn => { + const params = {}; + if (btn.id) params.id = btn.id; + if (btn.url) params.url = btn.url; + if (btn.display_text) params.display_text = btn.display_text; + return { + name: btn.name, + buttonParamsJson: JSON.stringify(params) + }; + }); + + // **On utilise sock.sendMessage** et non client.sendMessage + await sock.sendMessage( + jid, + { + product: productPayload, + businessOwnerJid, + caption, + title: messageTitle, + footer, + media: true, + interactiveButtons: buttons + }, + quoted ? { quoted } : {} + ); + + return res.json({ success: true }); + } catch (e) { + console.error('❌ Erreur /sendProductMessage :', e); + return res.status(500).json({ error: e.message }); + } +}); +app.post('/testProductMessage', async (req, res) => { + const { phone } = req.body; + + if (!sock || !isConnected) { + return res.status(400).json({ error: 'Non connecté à WhatsApp' }); + } + + const jid = `${phone}@s.whatsapp.net`; + + try { + await sock.sendMessage( + jid, + { + product: { + productImage: { url: "https://wa.canguidev.fr/static/logo-merlo-cs-FR.jpg" }, // image du produit + productImageCount: 1, + title: "Tracteur Merlo TF38.10", + description: "Charge maximale 3.8T, hauteur 10m", + priceAmount1000: 49500 * 1000, // 49 500 € + currencyCode: "EUR", + retailerId: "MERLO-FR-001", + url: "https://example.com/product/tf38-10" + }, + businessOwnerJid: "1234@s.whatsapp.net", + caption: "🛒 Découvrez notre nouveau modèle Merlo TF38.10", + title: "Offre Spéciale Merlo", + footer: "MERLO France • Offre valable jusqu'au 30/06", + media: true, + interactiveButtons: [ + { + name: "quick_reply", + buttonParamsJson: JSON.stringify({ + display_text: "📩 Demander un devis", + id: "request_quote" + }) + }, + { + name: "cta_url", + buttonParamsJson: JSON.stringify({ + display_text: "🌐 Voir la fiche produit", + url: "https://example.com/product/tf38-10" + }) + } + ] + }, + { + quoted: null // ou remplace par un message existant si tu veux répondre à un msg + } + ); + + res.json({ success: true }); + } catch (e) { + console.error('❌ Erreur envoi produit :', e); + res.status(500).json({ error: e.message }); + } +}); + +app.post('/testInteractiveImage', async (req, res) => { + const { phone } = req.body; + + if (!sock || !isConnected) { + return res.status(400).json({ error: 'Non connecté à WhatsApp' }); + } + + const content = { + viewOnceMessage: { + message: { + messageContextInfo: { + deviceListMetadata: {}, + deviceListMetadataVersion: 2 + }, + interactiveMessage: proto.Message.InteractiveMessage.create({ + header: { + hasMediaAttachment: true, + imageMessage: { url: "https://canguidev.fr/static/logo-merlo-cs-FR.jpg" } + }, + body: { text: "Bienvenue chez Merlo France 🇫🇷\nChoisissez une action ci-dessous." }, + footer: { text: "MERLO - Support & Documentation" }, + nativeFlowMessage: { + buttons: [ + { + name: "cta_reply", + buttonParamsJson: JSON.stringify({ + display_text: "📩 Contacter support", + id: "support_action" + }) + }, + { + name: "cta_url", + buttonParamsJson: JSON.stringify({ + display_text: "🌐 Voir la fiche produit", + url: "https://example.com/product" + }) + } + ] + } + }) + } + } +}; + +const jid = `${phone}@s.whatsapp.net`; +const msg = generateWAMessageFromContent(jid, content, {}); +await sock.relayMessage(jid, msg.message, { messageId: msg.key.id }); + +}); + +// 5) 404 et gestion des erreurs +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')); diff --git a/package.json b/package.json index 93d2423..e9d8771 100644 --- a/package.json +++ b/package.json @@ -7,13 +7,13 @@ "start": "node index.js" }, "dependencies": { - "@whiskeysockets/baileys": "^6.7.16", + "@fizzxydev/baileys-pro": "latest", "@hapi/boom": "^10.0.1", "axios": "^1.4.0", "express": "^4.18.4", "qrcode": "^1.5.1", "sharp": "^0.33.0", - "node-cache": "^5.1.2" + "node-cache": "^5.1.2" } }