From 30a6f2171d1fafc825084926b43fafc15e87e85d Mon Sep 17 00:00:00 2001 From: cangui Date: Tue, 6 May 2025 20:21:47 +0200 Subject: [PATCH] dsdsdds --- Dockerfile | 54 +++----- index.js | 355 +++++++++++++++++++++++++++++++++++++-------------- package.json | 44 ++----- 3 files changed, 286 insertions(+), 167 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8406d98..f2e1b24 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,46 +1,28 @@ -FROM node:20-slim +FROM node:20 -# Install system dependencies for Venom-bot, Puppeteer and Sharp +# Installation des dépendances système nécessaires à sharp (et puppeteer) RUN apt-get update && apt-get install -y \ - libvips-dev \ - libnss3 \ - libatk-bridge2.0-0 \ - libxss1 \ - libasound2 \ - libgtk-3-0 \ - libgbm1 \ - libx11-xcb1 \ - libxcomposite1 \ - libxdamage1 \ - libxrandr2 \ - xdg-utils \ - fonts-liberation \ - libappindicator3-1 \ - libnspr4 \ - lsb-release \ - wget \ - # Clean up - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* + libvips-dev \ + libnss3 \ + libatk-bridge2.0-0 \ + libxss1 \ + libasound2 \ + libgtk-3-0 \ + libgbm1 \ + libx11-xcb1 \ + libxcomposite1 \ + libxdamage1 \ + libxrandr2 \ + xdg-utils \ + --no-install-recommends && \ + apt-get clean && rm -rf /var/lib/apt/lists/* -# Set working directory WORKDIR /app -# Install Chromium manually (recommended approach for Venom-bot) -ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true -ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium - -# Copy package files first for better layer caching COPY package.json yarn.lock ./ +RUN yarn install -# Install project dependencies -RUN yarn install --frozen-lockfile --production=false - -# Copy application files COPY . . -# Expose the application port EXPOSE 3001 - -# Start command -CMD ["yarn", "start"] \ No newline at end of file +CMD ["yarn", "start"] diff --git a/index.js b/index.js index e62d585..cdafa74 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,6 @@ -const venom = require('venom-bot'); +const { default: makeWASocket, useMultiFileAuthState, DisconnectReason, fetchLatestBaileysVersion, proto, generateWAMessageFromContent, generateWAMessageContent ,prepareWAMessageMedia} = 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'); @@ -8,42 +9,44 @@ const app = express(); app.use(express.json()); app.use(express.static('public')); -let client; +let sock; let qrData = null; let isConnected = false; -// Initialisation Venom-bot -const initVenom = async () => { - client = await venom.create({ - session: 'whatsapp-session', - disableSpins: true, - logQR: false, - catchQR: (base64Qrimg) => { - qrcode.toDataURL(base64Qrimg, (err, url) => { - if (err) { - console.error('Erreur QR code:', err); - return; - } - qrData = url; - isConnected = false; - }); +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) { + qrData = await qrcode.toDataURL(qr); + isConnected = false; + } + if (connection === 'close') { + const shouldReconnect = lastDisconnect?.error?.output?.statusCode !== DisconnectReason.loggedOut; + if (shouldReconnect) { + console.log('🔁 Reconnexion...'); + initBaileys(); + } else { + console.log('❌ Déconnecté.'); + } + } else if (connection === 'open') { + console.log('✅ Connecté à WhatsApp'); + isConnected = true; } }); - client.onStateChange((state) => { - console.log('État de connexion:', state); - isConnected = state === 'CONNECTED'; - - if (state === 'DISCONNECTED') { - console.log('🔁 Tentative de reconnexion...'); - setTimeout(initVenom, 5000); - } - }); + sock.ev.on('creds.update', saveCreds); }; -initVenom(); - -// Routes Express +initBaileys(); app.use('/static', express.static(path.join(__dirname, 'public'))); app.get('/login', (req, res) => { @@ -56,10 +59,9 @@ app.get('/api/qrcode', (req, res) => { app.post('/sendText', async (req, res) => { const { phone, message } = req.body; - if (!client || !isConnected) return res.status(400).json({ error: 'Non connecté' }); - + if (!sock || !isConnected) return res.status(400).json({ error: 'Non connecté' }); try { - await client.sendText(`${phone}@c.us`, message); + await sock.sendMessage(`${phone}@s.whatsapp.net`, { text: message }); res.json({ success: true }); } catch (e) { res.status(500).json({ error: e.message }); @@ -68,100 +70,255 @@ app.post('/sendText', async (req, res) => { app.post('/sendButtons', async (req, res) => { const { phone } = req.body; - if (!client || !isConnected) return res.status(400).json({ error: 'Non connecté' }); + if (!sock || !isConnected) return res.status(400).json({ error: 'Non connecté' }); try { - const buttons = [ - { body: "📩 Contacter support" }, - { body: "🌐 Voir notre site" }, - { body: "📞 Appeler le support" } - ]; - - await client.sendButtons( - `${phone}@c.us`, - "Bienvenue sur notre service !", - buttons, - "Choisis une action ci-dessous" - ); - - res.json({ success: true }); - } catch (e) { - console.error('❌ Erreur boutons:', e); - res.status(500).json({ error: e.message }); - } -}); - -app.post('/sendInteractiveImage', async (req, res) => { - const { phone, caption, footer } = req.body; - if (!client || !isConnected) return res.status(400).json({ error: 'Non connecté' }); - - try { - const imagePath = path.join(__dirname, 'public', 'logo-merlo-cs-FR.jpg'); - const imageBuffer = fs.readFileSync(imagePath); - - await client.sendImage( - `${phone}@c.us`, - imageBuffer, - 'logo.jpg', - caption || 'Description par défaut', - { - footer: footer || 'Pied de page', - buttons: [ - { buttonId: 'id1', buttonText: { displayText: '📄 Voir proposition' }, type: 1 }, - { buttonId: 'id2', buttonText: { displayText: '🔧 Spécifications' }, type: 1 } - ] + const content = { + message: { + interactiveMessage: proto.Message.InteractiveMessage.create({ + body: { text: "Bienvenue sur notre service !" }, + footer: { text: "Choisis une action ci-dessous" }, + header: { + title: "Menu principal", + hasMediaAttachment: false + }, + nativeFlowMessage: { + 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(`${phone}@s.whatsapp.net`, msg.message, { messageId: msg.key.id }); + res.json({ success: true }); } catch (e) { - console.error('❌ Erreur image interactive:', e); + console.error('❌ Erreur bouton actif :', e); res.status(500).json({ error: e.message }); } }); +// 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({ + type: proto.Message.InteractiveMessage.HeaderType.IMAGE, + imageMessage: { url: imageUrl }, + 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) => { - if (!client || !isConnected) return res.status(400).json({ error: 'Non connecté' }); + // 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, - price, - productUrl + priceAmount1000, + currencyCode, + retailerId, + productUrl, + businessOwnerJid, + caption, + messageTitle, + footer, + interactiveButtons = [], + quoted } = req.body; try { - // Venom-bot n'a pas de méthode native pour les produits - // On simule avec une image et des boutons - const buttons = [ - { buttonId: 'buy', buttonText: { displayText: '🛒 Acheter' }, type: 1 }, - { buttonId: 'details', buttonText: { displayText: 'ℹ️ Détails' }, type: 1 } - ]; + const jid = `${phone}@s.whatsapp.net`; - const caption = `*${productTitle}*\n\n${productDescription}\n\nPrix: ${price}`; + // 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 + }; - await client.sendImage( - `${phone}@c.us`, - productImageUrl, - 'product.jpg', - caption, - { buttons } + // 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 } : {} ); - - res.json({ success: true }); + + return res.json({ success: true }); } catch (e) { - console.error('❌ Erreur produit:', e); - res.status(500).json({ error: e.message }); + console.error('❌ Erreur /sendProductMessage :', e); + return res.status(500).json({ error: e.message }); } }); -// Gestion des erreurs + +// 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('Erreur:', err); + console.error('Middleware d’erreur :', err); res.status(500).json({ error: err.message }); }); - -app.listen(3001, () => console.log('🚀 Serveur Venom démarré sur http://localhost:3001')); \ No newline at end of file +app.listen(3001, () => console.log('🚀 Serveur Baileys démarré sur http://localhost:3001')); diff --git a/package.json b/package.json index b45a530..9a7c66b 100644 --- a/package.json +++ b/package.json @@ -1,37 +1,17 @@ { - "name": "whatsapp-api-venom", - "version": "2.0.1", - "description": "API WhatsApp avec Venom-bot - Solution complète de messagerie", - "main": "server.js", + "name": "baileys-whatsapp-api", + "version": "1.0.0", + "description": "API WhatsApp avec Baileys Pro", + "main": "index.js", "scripts": { - "start": "node server.js", - "dev": "nodemon server.js", - "test": "jest --coverage", - "lint": "eslint .", - "format": "prettier --write .", - "postinstall": "node node_modules/venom-bot/dist/install/install-chromium.js" + "start": "node index.js" }, "dependencies": { - "venom-bot": "^4.3.7", - "express": "^4.18.2", - "express-rate-limit": "^6.8.1", - "helmet": "^7.1.0", - "morgan": "^1.10.0", - "qrcode": "^1.5.3", - "winston": "^3.11.0", - "axios": "^1.6.2", - "puppeteer": "^21.9.0" - }, - "devDependencies": { - "eslint": "^8.56.0", - "eslint-config-prettier": "^9.1.0", - "jest": "^29.7.0", - "nodemon": "^3.0.2", - "prettier": "^3.1.1", - "supertest": "^6.3.3" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=8.0.0" + "@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" } -} \ No newline at end of file +}