mirror of
https://github.com/cheveguerra/whatsapp-api-tutorial.git
synced 2026-04-17 11:26:13 +00:00
348 lines
8.5 KiB
JavaScript
348 lines
8.5 KiB
JavaScript
const { Client, MessageMedia, LocalAuth } = require('whatsapp-web.js');
|
|
const express = require('express');
|
|
const { body, validationResult } = require('express-validator');
|
|
const socketIO = require('socket.io');
|
|
const qrcode = require('qrcode');
|
|
const http = require('http');
|
|
const fs = require('fs');
|
|
const { phoneNumberFormatter } = require('./helpers/formatter');
|
|
const fileUpload = require('express-fileupload');
|
|
const axios = require('axios');
|
|
const mime = require('mime-types');
|
|
|
|
const port = process.env.PORT || 8000;
|
|
|
|
const app = express();
|
|
const server = http.createServer(app);
|
|
const io = socketIO(server);
|
|
|
|
app.use(express.json());
|
|
app.use(express.urlencoded({
|
|
extended: true
|
|
}));
|
|
|
|
/**
|
|
* BASED ON MANY QUESTIONS
|
|
* Actually ready mentioned on the tutorials
|
|
*
|
|
* Many people confused about the warning for file-upload
|
|
* So, we just disabling the debug for simplicity.
|
|
*/
|
|
app.use(fileUpload({
|
|
debug: false
|
|
}));
|
|
|
|
app.get('/', (req, res) => {
|
|
res.sendFile('index.html', {
|
|
root: __dirname
|
|
});
|
|
});
|
|
|
|
const client = new Client({
|
|
restartOnAuthFail: true,
|
|
puppeteer: {
|
|
headless: true,
|
|
args: [
|
|
'--no-sandbox',
|
|
'--disable-setuid-sandbox',
|
|
'--disable-dev-shm-usage',
|
|
'--disable-accelerated-2d-canvas',
|
|
'--no-first-run',
|
|
'--no-zygote',
|
|
'--single-process', // <- this one doesn't works in Windows
|
|
'--disable-gpu'
|
|
],
|
|
},
|
|
authStrategy: new LocalAuth()
|
|
});
|
|
|
|
client.on('message', msg => {
|
|
if (msg.body == '!ping') {
|
|
msg.reply('pong');
|
|
} else if (msg.body == 'good morning') {
|
|
msg.reply('selamat pagi');
|
|
} else if (msg.body == '!groups') {
|
|
client.getChats().then(chats => {
|
|
const groups = chats.filter(chat => chat.isGroup);
|
|
|
|
if (groups.length == 0) {
|
|
msg.reply('You have no group yet.');
|
|
} else {
|
|
let replyMsg = '*YOUR GROUPS*\n\n';
|
|
groups.forEach((group, i) => {
|
|
replyMsg += `ID: ${group.id._serialized}\nName: ${group.name}\n\n`;
|
|
});
|
|
replyMsg += '_You can use the group id to send a message to the group._'
|
|
msg.reply(replyMsg);
|
|
}
|
|
});
|
|
}
|
|
|
|
// NOTE!
|
|
// UNCOMMENT THE SCRIPT BELOW IF YOU WANT TO SAVE THE MESSAGE MEDIA FILES
|
|
// Downloading media
|
|
// if (msg.hasMedia) {
|
|
// msg.downloadMedia().then(media => {
|
|
// // To better understanding
|
|
// // Please look at the console what data we get
|
|
// console.log(media);
|
|
|
|
// if (media) {
|
|
// // The folder to store: change as you want!
|
|
// // Create if not exists
|
|
// const mediaPath = './downloaded-media/';
|
|
|
|
// if (!fs.existsSync(mediaPath)) {
|
|
// fs.mkdirSync(mediaPath);
|
|
// }
|
|
|
|
// // Get the file extension by mime-type
|
|
// const extension = mime.extension(media.mimetype);
|
|
|
|
// // Filename: change as you want!
|
|
// // I will use the time for this example
|
|
// // Why not use media.filename? Because the value is not certain exists
|
|
// const filename = new Date().getTime();
|
|
|
|
// const fullFilename = mediaPath + filename + '.' + extension;
|
|
|
|
// // Save to file
|
|
// try {
|
|
// fs.writeFileSync(fullFilename, media.data, { encoding: 'base64' });
|
|
// console.log('File downloaded successfully!', fullFilename);
|
|
// } catch (err) {
|
|
// console.log('Failed to save the file:', err);
|
|
// }
|
|
// }
|
|
// });
|
|
// }
|
|
});
|
|
|
|
client.initialize();
|
|
|
|
// Socket IO
|
|
io.on('connection', function(socket) {
|
|
socket.emit('message', 'Connecting...');
|
|
|
|
client.on('qr', (qr) => {
|
|
console.log('QR RECEIVED', qr);
|
|
qrcode.toDataURL(qr, (err, url) => {
|
|
socket.emit('qr', url);
|
|
socket.emit('message', 'QR Code received, scan please!');
|
|
});
|
|
});
|
|
|
|
client.on('ready', () => {
|
|
socket.emit('ready', 'Whatsapp is ready!');
|
|
socket.emit('message', 'Whatsapp is ready!');
|
|
});
|
|
|
|
client.on('authenticated', () => {
|
|
socket.emit('authenticated', 'Whatsapp is authenticated!');
|
|
socket.emit('message', 'Whatsapp is authenticated!');
|
|
console.log('AUTHENTICATED');
|
|
});
|
|
|
|
client.on('auth_failure', function(session) {
|
|
socket.emit('message', 'Auth failure, restarting...');
|
|
});
|
|
|
|
client.on('disconnected', (reason) => {
|
|
socket.emit('message', 'Whatsapp is disconnected!');
|
|
client.destroy();
|
|
client.initialize();
|
|
});
|
|
});
|
|
|
|
|
|
const checkRegisteredNumber = async function(number) {
|
|
const isRegistered = await client.isRegisteredUser(number);
|
|
return isRegistered;
|
|
}
|
|
|
|
// Send message
|
|
app.post('/send-message', [
|
|
body('number').notEmpty(),
|
|
body('message').notEmpty(),
|
|
], async (req, res) => {
|
|
const errors = validationResult(req).formatWith(({
|
|
msg
|
|
}) => {
|
|
return msg;
|
|
});
|
|
|
|
if (!errors.isEmpty()) {
|
|
return res.status(422).json({
|
|
status: false,
|
|
message: errors.mapped()
|
|
});
|
|
}
|
|
|
|
const number = phoneNumberFormatter(req.body.number);
|
|
const message = req.body.message;
|
|
|
|
const isRegisteredNumber = await checkRegisteredNumber(number);
|
|
|
|
if (!isRegisteredNumber) {
|
|
return res.status(422).json({
|
|
status: false,
|
|
message: 'The number is not registered'
|
|
});
|
|
}
|
|
|
|
client.sendMessage(number, message).then(response => {
|
|
res.status(200).json({
|
|
status: true,
|
|
response: response
|
|
});
|
|
}).catch(err => {
|
|
res.status(500).json({
|
|
status: false,
|
|
response: err
|
|
});
|
|
});
|
|
});
|
|
|
|
// Send media
|
|
app.post('/send-media', async (req, res) => {
|
|
const number = phoneNumberFormatter(req.body.number);
|
|
const caption = req.body.caption;
|
|
const fileUrl = req.body.file;
|
|
|
|
// const media = MessageMedia.fromFilePath('./image-example.png');
|
|
// const file = req.files.file;
|
|
// const media = new MessageMedia(file.mimetype, file.data.toString('base64'), file.name);
|
|
let mimetype;
|
|
const attachment = await axios.get(fileUrl, {
|
|
responseType: 'arraybuffer'
|
|
}).then(response => {
|
|
mimetype = response.headers['content-type'];
|
|
return response.data.toString('base64');
|
|
});
|
|
|
|
const media = new MessageMedia(mimetype, attachment, 'Media');
|
|
|
|
client.sendMessage(number, media, {
|
|
caption: caption
|
|
}).then(response => {
|
|
res.status(200).json({
|
|
status: true,
|
|
response: response
|
|
});
|
|
}).catch(err => {
|
|
res.status(500).json({
|
|
status: false,
|
|
response: err
|
|
});
|
|
});
|
|
});
|
|
|
|
const findGroupByName = async function(name) {
|
|
const group = await client.getChats().then(chats => {
|
|
return chats.find(chat =>
|
|
chat.isGroup && chat.name.toLowerCase() == name.toLowerCase()
|
|
);
|
|
});
|
|
return group;
|
|
}
|
|
|
|
// Send message to group
|
|
// You can use chatID or group name, yea!
|
|
app.post('/send-group-message', [
|
|
body('id').custom((value, { req }) => {
|
|
if (!value && !req.body.name) {
|
|
throw new Error('Invalid value, you can use `id` or `name`');
|
|
}
|
|
return true;
|
|
}),
|
|
body('message').notEmpty(),
|
|
], async (req, res) => {
|
|
const errors = validationResult(req).formatWith(({
|
|
msg
|
|
}) => {
|
|
return msg;
|
|
});
|
|
|
|
if (!errors.isEmpty()) {
|
|
return res.status(422).json({
|
|
status: false,
|
|
message: errors.mapped()
|
|
});
|
|
}
|
|
|
|
let chatId = req.body.id;
|
|
const groupName = req.body.name;
|
|
const message = req.body.message;
|
|
|
|
// Find the group by name
|
|
if (!chatId) {
|
|
const group = await findGroupByName(groupName);
|
|
if (!group) {
|
|
return res.status(422).json({
|
|
status: false,
|
|
message: 'No group found with name: ' + groupName
|
|
});
|
|
}
|
|
chatId = group.id._serialized;
|
|
}
|
|
|
|
client.sendMessage(chatId, message).then(response => {
|
|
res.status(200).json({
|
|
status: true,
|
|
response: response
|
|
});
|
|
}).catch(err => {
|
|
res.status(500).json({
|
|
status: false,
|
|
response: err
|
|
});
|
|
});
|
|
});
|
|
|
|
// Clearing message on spesific chat
|
|
app.post('/clear-message', [
|
|
body('number').notEmpty(),
|
|
], async (req, res) => {
|
|
const errors = validationResult(req).formatWith(({
|
|
msg
|
|
}) => {
|
|
return msg;
|
|
});
|
|
|
|
if (!errors.isEmpty()) {
|
|
return res.status(422).json({
|
|
status: false,
|
|
message: errors.mapped()
|
|
});
|
|
}
|
|
|
|
const number = phoneNumberFormatter(req.body.number);
|
|
|
|
const isRegisteredNumber = await checkRegisteredNumber(number);
|
|
|
|
if (!isRegisteredNumber) {
|
|
return res.status(422).json({
|
|
status: false,
|
|
message: 'The number is not registered'
|
|
});
|
|
}
|
|
|
|
const chat = await client.getChatById(number);
|
|
|
|
chat.clearMessages().then(status => {
|
|
res.status(200).json({
|
|
status: true,
|
|
response: status
|
|
});
|
|
}).catch(err => {
|
|
res.status(500).json({
|
|
status: false,
|
|
response: err
|
|
});
|
|
})
|
|
});
|
|
|
|
server.listen(port, function() {
|
|
console.log('App running on *: ' + port);
|
|
});
|