mirror of
https://github.com/cheveguerra/bot-whatsapp.git
synced 2026-04-20 20:49:15 +00:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9ed8e514e | ||
|
|
f36ddd3014 | ||
|
|
3fadaaaf13 | ||
|
|
dfa569f29d | ||
|
|
601508f379 | ||
|
|
e7ad205268 | ||
|
|
f62ba0a076 | ||
|
|
a9efa0aa58 | ||
|
|
3276c21bc3 | ||
|
|
1114f25a71 | ||
|
|
f13a34ff75 | ||
|
|
d45ea85e7d | ||
|
|
10e2b138d3 | ||
|
|
a1bf5ba5c2 | ||
|
|
19102b7b3a | ||
|
|
5efcc2a9a6 | ||
|
|
8279c07a88 | ||
|
|
02d7b3bd98 | ||
|
|
f8f6a3000d | ||
|
|
9a92b152a4 | ||
|
|
f86700deaf | ||
|
|
4ba259b46c | ||
|
|
cf459e94d2 | ||
|
|
4f8ed1361c | ||
|
|
bad8802241 | ||
|
|
f09ac862d5 | ||
|
|
fe7567e1a9 | ||
|
|
9b0b7f4d54 | ||
|
|
3ddbf462a8 | ||
|
|
e6043c99a7 | ||
|
|
b1daa0020e | ||
|
|
190d35c9a5 | ||
|
|
e4378fe848 | ||
|
|
981a6bd928 | ||
|
|
ba4f05ebb2 |
@@ -9,3 +9,5 @@ SQL_HOST=
|
||||
SQL_USER=
|
||||
SQL_PASS=
|
||||
SQL_DATABASE=
|
||||
KEEP_DIALOG_FLOW=false
|
||||
MULTI_DEVICE=true
|
||||
@@ -12,3 +12,7 @@
|
||||
- add support for ubuntu/linux
|
||||
|
||||
https://stackoverflow.com/questions/51855169/dialogflow-403-iam-permission-dialogflow-sessions-detectintent
|
||||
|
||||
#### Actualización 20 Oct 2022
|
||||
- npm update
|
||||
- use node crypto instead of nanoid
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
## Chatbot Whatsapp (OpenSource)
|
||||
#### Actualizado Enero 2022
|
||||
#### Actualizado Abril 2022
|
||||
|
||||
El siguiente proyecto se realizó con fines educativos para el canal de [Youtube (Leifer Mendez)](https://www.youtube.com/channel/UCgrIGp5QAnC0J8LfNJxDRDw?sub_confirmation=1) donde aprendemos a crear y implementar un chatbot increíble usando [node.js](https://codigoencasa.com/tag/nodejs/) además le agregamos inteligencia artificial gracias al servicio de __dialogflow__.
|
||||
|
||||
[](https://youtu.be/5lEMCeWEJ8o)
|
||||
|
||||
### ATENCION 1 🔴
|
||||
### ATENCION 🔴
|
||||
> 💥💥 Si te aparece el Error Multi-device es porque tienes la cuenta de whatsapp afiliada al modo "BETA de Multi dispositivo" por el momento no se tiene soporte para esas personas si tu quieres hacer uso de este __BOT__ debes de salir del modo BETA y intentarlo de la manera tradicional
|
||||
|
||||
### ATENCION 2 🔴
|
||||
> El core de whatsapp esta en constante actualizaciones por lo cual siempre revisa la ultima fecha de la actualizacion
|
||||
> [VER](https://github.com/leifermendez/bot-whatsapp/commits/main)
|
||||
|
||||
### Busco colaboradores ⭐
|
||||
Hola amigos me gusta mucho este proyecto pero por cuestiones de tiempo se me dificulta mantener las actualizaciones si alguno quieres participar en el proyecto escribeme a leifer.contacto@gmail.com
|
||||
|
||||
#### Acceso rápido
|
||||
> Si tienes una cuenta en __heroku__ puedes desplegar este proyecto con (1 click)
|
||||
@@ -31,7 +32,7 @@ El siguiente proyecto se realizó con fines educativos para el canal de [Youtube
|
||||
| JSON File | ✅ |
|
||||
| QR Scan (route) | ✅ |
|
||||
| Easy deploy heroku | ✅ |
|
||||
| Buttons | ✅ |
|
||||
| Buttons | ✅ℹ️ (No funciona en multi-device)|
|
||||
| Send Voice Note | ✅ |
|
||||
| Add support ubuntu/linux | ✅ |
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
const dialogflow = require('@google-cloud/dialogflow');
|
||||
const fs = require('fs')
|
||||
const nanoid = require('nanoid')
|
||||
const crypto = require('crypto');
|
||||
/**
|
||||
* Debes de tener tu archivo con el nombre "chatbot-account.json" en la raíz del proyecto
|
||||
*/
|
||||
|
||||
const KEEP_DIALOG_FLOW = (process.env.KEEP_DIALOG_FLOW === 'true')
|
||||
let PROJECID;
|
||||
let CONFIGURATION;
|
||||
let sessionClient;
|
||||
@@ -30,7 +32,7 @@ const checkFileCredentials = () => {
|
||||
// Detect intent method
|
||||
const detectIntent = async (queryText) => {
|
||||
let media = null;
|
||||
const sessionId = nanoid.nanoid()
|
||||
const sessionId = KEEP_DIALOG_FLOW ? 1 : crypto.randomUUID();
|
||||
const sessionPath = sessionClient.projectAgentSessionPath(PROJECID, sessionId);
|
||||
const languageCode = process.env.LANGUAGE
|
||||
const request = {
|
||||
@@ -54,7 +56,7 @@ const detectIntent = async (queryText) => {
|
||||
const { fields } = parsePayload.payload
|
||||
media = fields.media.stringValue || null
|
||||
}
|
||||
// const customPayload = parsePayload['payload']
|
||||
const customPayload = parsePayload['payload']
|
||||
|
||||
const parseData = {
|
||||
replyMessage: queryResult.fulfillmentText,
|
||||
|
||||
@@ -82,6 +82,7 @@ const saveMessage = ( message, trigger, number ) => new Promise( async (resolve
|
||||
resolve( await saveMessageJson( message, trigger, number ) )
|
||||
break;
|
||||
default:
|
||||
resolve(true)
|
||||
break;
|
||||
}
|
||||
})
|
||||
|
||||
@@ -21,6 +21,7 @@ getReply = (option_key = '', callback) => connection.query(
|
||||
replyMessage:response?.replyMessage || '',
|
||||
trigger:response?.trigger || '',
|
||||
media:response?.media || ''
|
||||
|
||||
}
|
||||
callback(value)
|
||||
});
|
||||
@@ -51,13 +52,15 @@ saveMessageMysql = ( message, date, trigger, number ) => new Promise((resolve,re
|
||||
connection.query(
|
||||
`INSERT INTO ${DATABASE_NAME}.messages `+"( `message`, `date`, `trigger`, `number`)"+` VALUES ('${message}','${date}','${trigger}', '${number}')` , (error, results) => {
|
||||
if(error) {
|
||||
if( error.code === 'ER_NO_SUCH_TABLE' ){
|
||||
connection.query( `CREATE TABLE ${DATABASE_NAME}.messages `+"( `date` DATE NOT NULL , `message` VARCHAR(450) NOT NULL , `trigger` VARCHAR(450) NOT NULL , `number` VARCHAR(50) NOT NULL ) ENGINE = InnoDB", async (error, results) => {
|
||||
setTimeout( async () => {
|
||||
return resolve( await this.saveMessageMysql( message, date, trigger, number ) )
|
||||
}, 150)
|
||||
})
|
||||
}
|
||||
//TODO esta parte es mejor incluirla directamente en el archivo .sql template
|
||||
console.log('DEBES DE CREAR LA TABLA DE MESSAGE')
|
||||
// if( error.code === 'ER_NO_SUCH_TABLE' ){
|
||||
// connection.query( `CREATE TABLE ${DATABASE_NAME}.messages `+"( `date` DATE NOT NULL , `message` VARCHAR(450) NOT NULL , `trigger` VARCHAR(450) NOT NULL , `number` VARCHAR(50) NOT NULL ) ENGINE = InnoDB", async (error, results) => {
|
||||
// setTimeout( async () => {
|
||||
// return resolve( await this.saveMessageMysql( message, date, trigger, number ) )
|
||||
// }, 150)
|
||||
// })
|
||||
// }
|
||||
}
|
||||
console.log('Saved')
|
||||
console.log( results )
|
||||
|
||||
109
app.js
109
app.js
@@ -6,10 +6,10 @@ const fs = require('fs');
|
||||
const express = require('express');
|
||||
const cors = require('cors')
|
||||
const qrcode = require('qrcode-terminal');
|
||||
const { Client, LegacySessionAuth } = require('whatsapp-web.js');
|
||||
const { Client,LocalAuth } = require('whatsapp-web.js');
|
||||
const mysqlConnection = require('./config/mysql')
|
||||
const { middlewareClient } = require('./middleware/client')
|
||||
const { generateImage, cleanNumber, checkEnvFile } = require('./controllers/handle')
|
||||
const { generateImage, cleanNumber, checkEnvFile, createClient, isValidNumber } = require('./controllers/handle')
|
||||
const { connectionReady, connectionLost } = require('./controllers/connection')
|
||||
const { saveMedia } = require('./controllers/save')
|
||||
const { getMessages, responseMessages, bothResponse } = require('./controllers/flows')
|
||||
@@ -17,35 +17,23 @@ const { sendMedia, sendMessage, lastTrigger, sendMessageButton, readChat } = req
|
||||
const app = express();
|
||||
app.use(cors())
|
||||
app.use(express.json())
|
||||
|
||||
const MULTI_DEVICE = process.env.MULTI_DEVICE || 'true';
|
||||
const server = require('http').Server(app)
|
||||
const io = require('socket.io')(server, {
|
||||
cors: {
|
||||
origins: ['http://localhost:4200']
|
||||
}
|
||||
})
|
||||
|
||||
let socketEvents = {sendQR:() => {} ,sendStatus:() => {}};
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
const CHANNEL = 'main-channel';
|
||||
socket.join(CHANNEL);
|
||||
socketEvents = require('./controllers/socket')(socket)
|
||||
console.log('Se conecto')
|
||||
})
|
||||
|
||||
app.use('/', require('./routes/web'))
|
||||
|
||||
const port = process.env.PORT || 3000
|
||||
const SESSION_FILE_PATH = './session.json';
|
||||
var client;
|
||||
var sessionData;
|
||||
app.use('/', require('./routes/web'))
|
||||
|
||||
/**
|
||||
* Escuchamos cuando entre un mensaje
|
||||
*/
|
||||
const listenMessage = () => client.on('message', async msg => {
|
||||
const { from, body, hasMedia } = msg;
|
||||
|
||||
if(!isValidNumber(from)){
|
||||
return
|
||||
}
|
||||
|
||||
// Este bug lo reporto Lucas Aldeco Brescia para evitar que se publiquen estados
|
||||
if (from === 'status@broadcast') {
|
||||
return
|
||||
@@ -68,6 +56,7 @@ const listenMessage = () => client.on('message', async msg => {
|
||||
*/
|
||||
|
||||
if (process.env.DATABASE === 'dialogflow') {
|
||||
if(!message.length) return;
|
||||
const response = await bothResponse(message);
|
||||
await sendMessage(client, from, response.replyMessage);
|
||||
if (response.media) {
|
||||
@@ -101,6 +90,7 @@ const listenMessage = () => client.on('message', async msg => {
|
||||
*/
|
||||
|
||||
await sendMessage(client, from, response.replyMessage, response.trigger);
|
||||
|
||||
if(response.hasOwnProperty('actions')){
|
||||
const { actions } = response;
|
||||
await sendMessageButton(client, from, null, actions);
|
||||
@@ -134,66 +124,16 @@ const listenMessage = () => client.on('message', async msg => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Revisamos si tenemos credenciales guardadas para inciar sessio
|
||||
* este paso evita volver a escanear el QRCODE
|
||||
*/
|
||||
const withSession = () => {
|
||||
// Si exsite cargamos el archivo con las credenciales
|
||||
console.log(`Validando session con Whatsapp...`)
|
||||
sessionData = require(SESSION_FILE_PATH);
|
||||
client = new Client({
|
||||
authStrategy: new LegacySessionAuth({
|
||||
session: sessionData // saved session object
|
||||
}),
|
||||
restartOnAuthFail: true,
|
||||
puppeteer: {
|
||||
args: [
|
||||
'--no-sandbox'
|
||||
],
|
||||
}
|
||||
});
|
||||
|
||||
client.on('ready', () => {
|
||||
connectionReady()
|
||||
listenMessage()
|
||||
loadRoutes(client);
|
||||
socketEvents.sendStatus()
|
||||
});
|
||||
|
||||
client.on('auth_failure', () => connectionLost())
|
||||
|
||||
client.initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generamos un QRCODE para iniciar sesion
|
||||
*/
|
||||
const withOutSession = () => {
|
||||
console.log('No tenemos session guardada');
|
||||
console.log([
|
||||
'🙌 El core de whatsapp se esta actualizando',
|
||||
'🙌 para proximamente dar paso al multi-device',
|
||||
'🙌 falta poco si quieres estar al pendiente unete',
|
||||
'🙌 http://t.me/leifermendez',
|
||||
'________________________',
|
||||
].join('\n'));
|
||||
|
||||
client = new Client({
|
||||
session: { },
|
||||
// authStrategy: new LegacySessionAuth({
|
||||
// session: { }
|
||||
// }),
|
||||
restartOnAuthFail: true,
|
||||
puppeteer: {
|
||||
args: [
|
||||
'--no-sandbox'
|
||||
],
|
||||
}
|
||||
authStrategy: new LocalAuth(),
|
||||
puppeteer: { headless: true }
|
||||
});
|
||||
|
||||
client.on('qr', qr => generateImage(qr, () => {
|
||||
qrcode.generate(qr, { small: true });
|
||||
|
||||
console.log(`Ver QR http://localhost:${port}/qr`)
|
||||
socketEvents.sendQR(qr)
|
||||
}))
|
||||
@@ -201,7 +141,6 @@ const withOutSession = () => {
|
||||
client.on('ready', (a) => {
|
||||
connectionReady()
|
||||
listenMessage()
|
||||
loadRoutes(client);
|
||||
// socketEvents.sendStatus(client)
|
||||
});
|
||||
|
||||
@@ -210,29 +149,13 @@ const withOutSession = () => {
|
||||
// connectionLost()
|
||||
});
|
||||
|
||||
client.on('authenticated', (session) => {
|
||||
sessionData = session;
|
||||
fs.writeFile(SESSION_FILE_PATH, JSON.stringify(session), function (err) {
|
||||
if (err) {
|
||||
console.log(`Ocurrio un error con el archivo: `, err);
|
||||
}
|
||||
});
|
||||
client.on('authenticated', () => {
|
||||
console.log('AUTHENTICATED');
|
||||
});
|
||||
|
||||
client.initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cargamos rutas de express
|
||||
*/
|
||||
|
||||
const loadRoutes = (client) => {
|
||||
app.use('/api/', middlewareClient(client), require('./routes/api'))
|
||||
}
|
||||
/**
|
||||
* Revisamos si existe archivo con credenciales!
|
||||
*/
|
||||
(fs.existsSync(SESSION_FILE_PATH)) ? withSession() : withOutSession();
|
||||
|
||||
/**
|
||||
* Verificamos si tienes un gesto de db
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
const mysql = require('mysql');
|
||||
const connection = mysql.createConnection({
|
||||
host : process.env.SQL_HOST || 'localhost',
|
||||
user : process.env.SQL_USER || 'me',
|
||||
user : process.env.SQL_USER || 'root',
|
||||
password : process.env.SQL_PASS || '',
|
||||
database : process.env.SQL_DATABASE || 'my_db'
|
||||
database : process.env.SQL_DATABASE || 'pruebas'
|
||||
});
|
||||
|
||||
const connect = () => connection.connect(function(err) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const connectionReady = (cb = () =>{}) => {
|
||||
console.log('Listo para escuchas mensajes')
|
||||
console.log('Client is ready!');
|
||||
console.log('🔴 escribe: hola');
|
||||
cb()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
const { Client, LegacySessionAuth, LocalAuth } = require('whatsapp-web.js');
|
||||
const http = require('http'); // or 'https' for https:// URLs
|
||||
const https = require('https'); // or 'https' for https:// URLs
|
||||
const fs = require('fs');
|
||||
const qr = require('qr-image')
|
||||
|
||||
const MULTI_DEVICE = process.env.MULTI_DEVICE || 'true';
|
||||
|
||||
const cleanNumber = (number) => {
|
||||
number = number.replace('@c.us', '');
|
||||
@@ -57,4 +59,25 @@ const checkEnvFile = () => {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {cleanNumber, saveExternalFile, generateImage, checkIsUrl, checkEnvFile}
|
||||
/**
|
||||
*
|
||||
* @param {*} session
|
||||
* @param {*} cb
|
||||
*/
|
||||
const createClient = () => {
|
||||
client = new Client({
|
||||
authStrategy: new LocalAuth(
|
||||
{dataPath: './sessions/',
|
||||
clientId: 'bot'}),
|
||||
puppeteer: { headless: false }
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
const isValidNumber = (rawNumber) => {
|
||||
const regexGroup = /\@g.us\b/gm;
|
||||
const exist = rawNumber.match(regexGroup);
|
||||
return !exist
|
||||
}
|
||||
|
||||
module.exports = {cleanNumber, saveExternalFile, generateImage, checkIsUrl, checkEnvFile, createClient, isValidNumber}
|
||||
@@ -15,13 +15,18 @@ const { saveMessage } = require('../adapter')
|
||||
* @param {*} fileName
|
||||
*/
|
||||
|
||||
const sendMedia = (client, number, fileName) => {
|
||||
number = cleanNumber(number)
|
||||
const sendMedia = (client, number = null, fileName = null) => {
|
||||
if(!client) return cosnole.error("El objeto cliente no está definido.");
|
||||
try {
|
||||
number = cleanNumber(number || 0)
|
||||
const file = `${DIR_MEDIA}/${fileName}`;
|
||||
if (fs.existsSync(file)) {
|
||||
const media = MessageMedia.fromFilePath(file);
|
||||
client.sendMessage(number, media, { sendAudioAsVoice: true });
|
||||
}
|
||||
} catch(e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -30,15 +35,21 @@ const sendMedia = (client, number, fileName) => {
|
||||
* @param {*} fileName
|
||||
*/
|
||||
|
||||
const sendMediaVoiceNote = (client, number, fileName) => {
|
||||
number = cleanNumber(number)
|
||||
const sendMediaVoiceNote = (client, number = null, fileName = null) => {
|
||||
if(!client) return cosnole.error("El objeto cliente no está definido.");
|
||||
try {
|
||||
number = cleanNumber(number || 0)
|
||||
const file = `${DIR_MEDIA}/${fileName}`;
|
||||
if (fs.existsSync(file)) {
|
||||
const media = MessageMedia.fromFilePath(file);
|
||||
client.sendMessage(number, media ,{ sendAudioAsVoice: true });
|
||||
|
||||
}
|
||||
}catch(e) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* Enviamos un mensaje simple (texto) a nuestro cliente
|
||||
* @param {*} number
|
||||
|
||||
@@ -2,6 +2,7 @@ const fs = require('fs')
|
||||
const { sendMessage } = require('../controllers/send')
|
||||
|
||||
const sendMessagePost = (req, res) => {
|
||||
console.log('asdasdasdasdasd')
|
||||
const { message, number } = req.body
|
||||
const client = req.clientWs || null;
|
||||
sendMessage(client, number, message)
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"\n Si necesitas ver más info sobre las capacitacion tecnicas ",
|
||||
"escribe *cursos* o *info* o escribe *audio*"
|
||||
],
|
||||
"media":null,
|
||||
"media":"https://media2.giphy.com/media/VQJu0IeULuAmCwf5SL/giphy.gif",
|
||||
"trigger":null,
|
||||
"actions":{
|
||||
"title":"¿Que te interesa ver?",
|
||||
|
||||
7191
package-lock.json
generated
7191
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
56
package.json
56
package.json
@@ -1,38 +1,58 @@
|
||||
{
|
||||
"name": "test-ws-bot",
|
||||
"name": "bot-whatsapp",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"description": "Bot de wahtsapp open source para MVP o pequeños negocios",
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"start": "node ./app.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"keywords": [
|
||||
"whatsapp",
|
||||
"bot-whatsapp",
|
||||
"node-bot-whatsapp"
|
||||
],
|
||||
|
||||
"contributors": [
|
||||
{
|
||||
"email": "leifer33@gmail.com",
|
||||
"name": "Leifer Mendez",
|
||||
"url": "https://leifermendez.github.io"
|
||||
},
|
||||
{
|
||||
"name": "aurik3",
|
||||
"email": "aurik3@aurik3.com",
|
||||
"url": "https://github.com/aurik3"
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/leifermendez/bot-whatsapp"
|
||||
},
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@google-cloud/dialogflow": "^4.6.0",
|
||||
"@google-cloud/dialogflow": "^5.2.0",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^11.0.0",
|
||||
"dotenv": "^16.0.1",
|
||||
"exceljs": "^4.3.0",
|
||||
"express": "^4.17.2",
|
||||
"file-type": "^16.5.3",
|
||||
"mime-db": "^1.51.0",
|
||||
"moment": "^2.29.1",
|
||||
"express": "^4.18.1",
|
||||
"file-type": "^17.1.6",
|
||||
"mime-db": "^1.52.0",
|
||||
"moment": "^2.29.4",
|
||||
"mysql": "^2.18.1",
|
||||
"nanoid": "^3.1.32",
|
||||
"qr-image": "^3.2.0",
|
||||
"qrcode-terminal": "^0.12.0",
|
||||
"socket.io": "^4.4.1",
|
||||
"stormdb": "^0.5.2",
|
||||
"whatsapp-web.js": "^1.16.4",
|
||||
"xlsx": "^0.16.9"
|
||||
"socket.io": "^4.5.1",
|
||||
"stormdb": "^0.6.0",
|
||||
"whatsapp-web.js": "^1.17.1",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"pm2": "^5.1.2",
|
||||
"prettier": "2.5.1"
|
||||
"pm2": "^5.2.0",
|
||||
"prettier": "2.7.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "14.x"
|
||||
"node": "16.x"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const express = require('express')
|
||||
const router = express.Router();
|
||||
const { sendMessagePost } = require('../controllers/web')
|
||||
const { sendMessagePost } = require('../controllers/web')|
|
||||
|
||||
router.post('/send', sendMessagePost)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user