whatsapp/index.js
2025-05-07 08:06:19 +02:00

702 lines
21 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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,
proto,
generateWAMessageFromContent
} = require('@fizzxydev/baileys-pro');
const app = express();
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" };
}
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',
});
}
}
});
};
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 });
}
});
app.post('/testButtons', async (req, res) => {
const { phone } = req.body;
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 content = {
viewOnceMessage: {
message: {
messageContextInfo: {
deviceListMetadata: {},
deviceListMetadataVersion: 2
},
interactiveMessage: proto.Message.InteractiveMessage.create({
body: { text: "Bienvenue sur notre service !" },
footer: { text: "Choisis une action ci-dessous" },
header: {
hasMediaAttachment: true,
imageMessage: { url: imageUrl }
},
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"
})
},
{
name: "cta_call",
buttonParamsJson: JSON.stringify({
display_text: "📞 Appeler le support",
id: "+33612345678"
})
}
]
}
})
}
}
};
const msg = generateWAMessageFromContent(jid, content, {});
await sock.relayMessage(jid, msg.message, { messageId: msg.key.id });
res.json({ success: true });
} catch (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({
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 });
});
app.post('/testButtons2', async (req, res) => {
const { phone } = req.body;
if (!sock || !isConnected) {
return res.status(400).json({ error: 'Non connecté' });
}
const jid = `${phone}@s.whatsapp.net`;
try {
await sock.sendMessage(jid, {
text: '📚 *Veuillez choisir le document à télécharger :*',
footer: '© Fizzxy Dev',
buttons: [
{
buttonId: 'doc_1',
buttonText: { displayText: 'Télécharger Document 1' },
type: 1,
},
{
buttonId: 'doc_2',
buttonText: { displayText: 'Télécharger Document 2' },
type: 1,
}
],
headerType: 1, // Texte seulement
});
res.json({ success: true });
} catch (e) {
console.error('❌ Erreur testButtons :', e);
res.status(500).json({ error: e.message });
}
});
app.post('/testButtons3', async (req, res) => {
const { phone } = req.body;
if (!sock || !isConnected) {
return res.status(400).json({ error: 'Non connecté' });
}
const jid = `${phone}@s.whatsapp.net`;
let msg = generateWAMessageFromContent(m.chat, {
viewOnceMessage: {
message: {
"messageContextInfo": {
"deviceListMetadata": {},
"deviceListMetadataVersion": 2
},
interactiveMessage: proto.Message.InteractiveMessage.create({
body: proto.Message.InteractiveMessage.Body.create({
text: "Fizzxy Dev"
}),
footer: proto.Message.InteractiveMessage.Footer.create({
text: "Bot"
}),
header: proto.Message.InteractiveMessage.Header.create({
title: "Igna",
subtitle: "test",
hasMediaAttachment: false
}),
nativeFlowMessage: proto.Message.InteractiveMessage.NativeFlowMessage.create({
buttons: [
{
"name": "single_select",
"buttonParamsJson": "{\"title\":\"title\",\"sections\":[{\".menu\":\".play dj webito\",\"highlight_label\":\"label\",\"rows\":[{\"header\":\"header\",\"title\":\"title\",\"description\":\"description\",\"id\":\"id\"},{\"header\":\"header\",\"title\":\"title\",\"description\":\"description\",\"id\":\"id\"}]}]}"
},
{
"name": "cta_reply",
"buttonParamsJson": "{\"display_text\":\"quick_reply\",\"id\":\"message\"}"
},
{
"name": "cta_url",
"buttonParamsJson": "{\"display_text\":\"url\",\"url\":\"https://www.google.com\",\"merchant_url\":\"https://www.google.com\"}"
},
{
"name": "cta_call",
"buttonParamsJson": "{\"display_text\":\"call\",\"id\":\"message\"}"
},
{
"name": "cta_copy",
"buttonParamsJson": "{\"display_text\":\"copy\",\"id\":\"123456789\",\"copy_code\":\"message\"}"
},
{
"name": "cta_reminder",
"buttonParamsJson": "{\"display_text\":\"Recordatorio\",\"id\":\"message\"}"
},
{
"name": "cta_cancel_reminder",
"buttonParamsJson": "{\"display_text\":\"cta_cancel_reminder\",\"id\":\"message\"}"
},
{
"name": "address_message",
"buttonParamsJson": "{\"display_text\":\"address_message\",\"id\":\"message\"}"
},
{
"name": "send_location",
"buttonParamsJson": ""
}
],
})
})
}
}
}, {})
try {
await sock.relayMessage(msg.key.remoteJid, msg.message, { messageId: msg.key.id })
res.json({ success: true });
} catch (e) {
console.error('❌ Erreur testButtons :', e);
res.status(500).json({ error: e.message });
}
});
// 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 derreur :', err);
res.status(500).json({ error: err.message });
});
app.listen(3001, () => console.log('🚀 Serveur Baileys démarré sur http://localhost:3001'));