From 0f8a33cc0ca33236da6c2b28d2d5859b22573409 Mon Sep 17 00:00:00 2001 From: Leifer Mendez Date: Wed, 1 Feb 2023 16:33:57 +0100 Subject: [PATCH 1/9] refactor(cli): :zap: flowDynamic --- __test__/05-case.test.js | 2 +- packages/bot/core/core.class.js | 40 ++++++++++++++--------------- packages/database/src/mock/index.js | 5 +++- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/__test__/05-case.test.js b/__test__/05-case.test.js index 6ca65d0..16b11f5 100644 --- a/__test__/05-case.test.js +++ b/__test__/05-case.test.js @@ -54,7 +54,7 @@ test(`[Caso - 05] Continuar Flujo (continueFlow)`, async () => { async (ctx, { flowDynamic, fallBack }) => { if (ctx.body !== '18') { await delay(50) - return fallBack(false, 'Ups creo que no eres mayor de edad') + return fallBack('Ups creo que no eres mayor de edad') } return flowDynamic('Bien tu edad es correcta!') } diff --git a/packages/bot/core/core.class.js b/packages/bot/core/core.class.js index 9b329cc..68fd4d5 100644 --- a/packages/bot/core/core.class.js +++ b/packages/bot/core/core.class.js @@ -118,19 +118,11 @@ class CoreClass { // 📄 Finalizar flujo const endFlow = async (message = null) => { - prevMsg = null endFlowFlag = true if (message) this.sendProviderAndSave(from, createCtxMessage(message)) clearQueue() - return - } - - // 📄 Continuar con el siguiente flujo - const continueFlow = async () => { - const cotinueMessage = - this.flowClass.find(refToContinue?.ref, true) || [] - sendFlow(cotinueMessage, from, { continue: true }) + sendFlow([]) return } @@ -138,10 +130,10 @@ class CoreClass { const sendFlow = async ( messageToSend, numberOrId, - options = { continue: false } + options = { prev: prevMsg } ) => { - if (!options.continue && prevMsg?.options?.capture) - await cbEveryCtx(prevMsg?.ref) + if (options.prev?.options?.capture) + await cbEveryCtx(options.prev?.ref) const queue = [] for (const ctxMessage of messageToSend) { @@ -160,9 +152,8 @@ class CoreClass { } // 📄 [options: fallBack]: esta funcion se encarga de repetir el ultimo mensaje - const fallBack = async (next = false, message = null) => { + const fallBack = async (message = null) => { QueuePrincipal.queue = [] - if (next) return continueFlow() return this.sendProviderAndSave(from, { ...prevMsg, answer: @@ -171,7 +162,7 @@ class CoreClass { : message?.body ?? prevMsg.answer, options: { ...prevMsg.options, - buttons: message?.buttons ?? prevMsg.options?.buttons, + buttons: prevMsg.options?.buttons, }, }) } @@ -185,12 +176,19 @@ class CoreClass { const parseListMsg = listMsg.map((opt, index) => createCtxMessage(opt, index) ) + const currentPrev = await this.databaseClass.getPrevByNumber(from) if (endFlowFlag) return for (const msg of parseListMsg) { await this.sendProviderAndSave(from, msg) } - return continueFlow() + + const nextFlow = await this.flowClass.find(refToContinue?.ref, true) + const filterNextFlow = nextFlow.filter( + (msg) => msg.refSerialize !== currentPrev?.refSerialize + ) + + return sendFlow(filterNextFlow, from, { prev: undefined }) } // 📄 Se encarga de revisar si el contexto del mensaje tiene callback o fallback @@ -206,7 +204,6 @@ class CoreClass { fallBack, flowDynamic, endFlow, - continueFlow, }) } @@ -246,10 +243,11 @@ class CoreClass { */ sendProviderAndSave = (numberOrId, ctxMessage) => { const { answer } = ctxMessage - return Promise.all([ - this.providerClass.sendMessage(numberOrId, answer, ctxMessage), - this.databaseClass.save({ ...ctxMessage, from: numberOrId }), - ]) + return this.providerClass + .sendMessage(numberOrId, answer, ctxMessage) + .then(() => + this.databaseClass.save({ ...ctxMessage, from: numberOrId }) + ) } /** diff --git a/packages/database/src/mock/index.js b/packages/database/src/mock/index.js index 4c07c03..2ab91a0 100644 --- a/packages/database/src/mock/index.js +++ b/packages/database/src/mock/index.js @@ -10,7 +10,10 @@ class MockDatabase { constructor() {} getPrevByNumber = (from) => { - const history = this.listHistory.slice().reverse() + const history = this.listHistory + .slice() + .reverse() + .filter((i) => !!i.keyword) return history.find((a) => a.from === from) } From ebd30ea1d78125d105d2785def8780a626305556 Mon Sep 17 00:00:00 2001 From: Leifer Mendez Date: Wed, 1 Feb 2023 16:34:24 +0100 Subject: [PATCH 2/9] refactor(cli): :zap: flowDynamic --- .prettierrc.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.prettierrc.json b/.prettierrc.json index e74ed9f..f0db82f 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -2,5 +2,6 @@ "trailingComma": "es5", "tabWidth": 4, "semi": false, - "singleQuote": true + "singleQuote": true, + "printWidth": 120 } From c655c480095bfb54b10f3bc6f7be4daaa545b70f Mon Sep 17 00:00:00 2001 From: Leifer Mendez Date: Wed, 1 Feb 2023 16:58:10 +0100 Subject: [PATCH 3/9] refactor(bot): :zap: working fallBack --- __test__/05-case.test.js | 38 +++++------------- packages/bot/core/core.class.js | 69 +++++++++++++-------------------- 2 files changed, 36 insertions(+), 71 deletions(-) diff --git a/__test__/05-case.test.js b/__test__/05-case.test.js index 16b11f5..f61d114 100644 --- a/__test__/05-case.test.js +++ b/__test__/05-case.test.js @@ -2,12 +2,7 @@ const { test } = require('uvu') const assert = require('uvu/assert') const MOCK_DB = require('../packages/database/src/mock') const PROVIDER_DB = require('../packages/provider/src/mock') -const { - addKeyword, - createBot, - createFlow, - createProvider, -} = require('../packages/bot/index') +const { addKeyword, createBot, createFlow, createProvider } = require('../packages/bot/index') /** * Falsear peticion async @@ -21,11 +16,7 @@ const fakeHTTP = async (fakeData = []) => { } test(`[Caso - 05] Continuar Flujo (continueFlow)`, async () => { - const MOCK_VALUES = [ - '¿CUal es tu email?', - 'Continuamos....', - '¿Cual es tu edad?', - ] + const MOCK_VALUES = ['¿CUal es tu email?', 'Continuamos....', '¿Cual es tu edad?'] const provider = createProvider(PROVIDER_DB) const database = new MOCK_DB() @@ -39,26 +30,20 @@ test(`[Caso - 05] Continuar Flujo (continueFlow)`, async () => { const validation = ctx.body.includes('@') if (validation) { - const getDataFromApi = await fakeHTTP([ - 'Gracias por tu email se ha validado de manera correcta', - ]) + const getDataFromApi = await fakeHTTP(['Gracias por tu email se ha validado de manera correcta']) return flowDynamic(getDataFromApi) } return fallBack(validation) } ) .addAnswer(MOCK_VALUES[1]) - .addAnswer( - MOCK_VALUES[2], - { capture: true }, - async (ctx, { flowDynamic, fallBack }) => { - if (ctx.body !== '18') { - await delay(50) - return fallBack('Ups creo que no eres mayor de edad') - } - return flowDynamic('Bien tu edad es correcta!') + .addAnswer(MOCK_VALUES[2], { capture: true }, async (ctx, { flowDynamic, fallBack }) => { + if (ctx.body !== '18') { + await delay(50) + return fallBack(false, 'Ups creo que no eres mayor de edad') } - ) + return flowDynamic('Bien tu edad es correcta!') + }) .addAnswer('Puedes pasar') createBot({ @@ -98,10 +83,7 @@ test(`[Caso - 05] Continuar Flujo (continueFlow)`, async () => { assert.is('this is not email value', getHistory[1]) assert.is(MOCK_VALUES[0], getHistory[2]) assert.is('test@test.com', getHistory[3]) - assert.is( - '1 Gracias por tu email se ha validado de manera correcta', - getHistory[4] - ) + assert.is('1 Gracias por tu email se ha validado de manera correcta', getHistory[4]) assert.is(MOCK_VALUES[1], getHistory[5]) assert.is(MOCK_VALUES[2], getHistory[6]) assert.is('20', getHistory[7]) diff --git a/packages/bot/core/core.class.js b/packages/bot/core/core.class.js index 68fd4d5..fae0ba4 100644 --- a/packages/bot/core/core.class.js +++ b/packages/bot/core/core.class.js @@ -43,8 +43,7 @@ class CoreClass { }, { event: 'require_action', - func: ({ instructions, title = '⚡⚡ ACCIÓN REQUERIDA ⚡⚡' }) => - printer(instructions, title), + func: ({ instructions, title = '⚡⚡ ACCIÓN REQUERIDA ⚡⚡' }) => printer(instructions, title), }, { event: 'ready', @@ -52,8 +51,7 @@ class CoreClass { }, { event: 'auth_failure', - func: ({ instructions }) => - printer(instructions, '⚡⚡ ERROR AUTH ⚡⚡'), + func: ({ instructions }) => printer(instructions, '⚡⚡ ERROR AUTH ⚡⚡'), }, { @@ -78,9 +76,7 @@ class CoreClass { if (!body.length) return let prevMsg = await this.databaseClass.getPrevByNumber(from) - const refToContinue = this.flowClass.findBySerialize( - prevMsg?.refSerialize - ) + const refToContinue = this.flowClass.findBySerialize(prevMsg?.refSerialize) if (prevMsg?.ref) { const ctxByNumber = toCtx({ @@ -93,10 +89,7 @@ class CoreClass { // 📄 Crar CTX de mensaje (uso private) const createCtxMessage = (payload = {}, index = 0) => { - const body = - typeof payload === 'string' - ? payload - : payload?.body ?? payload?.answer + const body = typeof payload === 'string' ? payload : payload?.body ?? payload?.answer const media = payload?.media ?? null const buttons = payload?.buttons ?? [] const capture = payload?.capture ?? false @@ -119,21 +112,15 @@ class CoreClass { // 📄 Finalizar flujo const endFlow = async (message = null) => { endFlowFlag = true - if (message) - this.sendProviderAndSave(from, createCtxMessage(message)) + if (message) this.sendProviderAndSave(from, createCtxMessage(message)) clearQueue() sendFlow([]) return } // 📄 Esta funcion se encarga de enviar un array de mensajes dentro de este ctx - const sendFlow = async ( - messageToSend, - numberOrId, - options = { prev: prevMsg } - ) => { - if (options.prev?.options?.capture) - await cbEveryCtx(options.prev?.ref) + const sendFlow = async (messageToSend, numberOrId, options = { prev: prevMsg }) => { + if (options.prev?.options?.capture) await cbEveryCtx(options.prev?.ref) const queue = [] for (const ctxMessage of messageToSend) { @@ -142,9 +129,7 @@ class CoreClass { if (delayMs) await delay(delayMs) QueuePrincipal.enqueue(() => Promise.all([ - this.sendProviderAndSave(numberOrId, ctxMessage).then( - () => resolveCbEveryCtx(ctxMessage) - ), + this.sendProviderAndSave(numberOrId, ctxMessage).then(() => resolveCbEveryCtx(ctxMessage)), ]) ) } @@ -152,19 +137,26 @@ class CoreClass { } // 📄 [options: fallBack]: esta funcion se encarga de repetir el ultimo mensaje - const fallBack = async (message = null) => { + const fallBack = async (validation = false, message = null) => { QueuePrincipal.queue = [] - return this.sendProviderAndSave(from, { + + if (validation) { + const currentPrev = await this.databaseClass.getPrevByNumber(from) + const nextFlow = await this.flowClass.find(refToContinue?.ref, true) + const filterNextFlow = nextFlow.filter((msg) => msg.refSerialize !== currentPrev?.refSerialize) + + return sendFlow(filterNextFlow, from, { prev: undefined }) + } + + await this.sendProviderAndSave(from, { ...prevMsg, - answer: - typeof message === 'string' - ? message - : message?.body ?? prevMsg.answer, + answer: typeof message === 'string' ? message : message?.body ?? prevMsg.answer, options: { ...prevMsg.options, buttons: prevMsg.options?.buttons, }, }) + return } // 📄 [options: flowDynamic]: esta funcion se encarga de responder un array de respuesta esta limitado a 5 mensajes @@ -173,9 +165,7 @@ class CoreClass { const flowDynamic = async (listMsg = []) => { if (!Array.isArray(listMsg)) listMsg = [listMsg] - const parseListMsg = listMsg.map((opt, index) => - createCtxMessage(opt, index) - ) + const parseListMsg = listMsg.map((opt, index) => createCtxMessage(opt, index)) const currentPrev = await this.databaseClass.getPrevByNumber(from) if (endFlowFlag) return @@ -184,17 +174,14 @@ class CoreClass { } const nextFlow = await this.flowClass.find(refToContinue?.ref, true) - const filterNextFlow = nextFlow.filter( - (msg) => msg.refSerialize !== currentPrev?.refSerialize - ) + const filterNextFlow = nextFlow.filter((msg) => msg.refSerialize !== currentPrev?.refSerialize) return sendFlow(filterNextFlow, from, { prev: undefined }) } // 📄 Se encarga de revisar si el contexto del mensaje tiene callback o fallback const resolveCbEveryCtx = async (ctxMessage) => { - if (!ctxMessage?.options?.capture) - return await cbEveryCtx(ctxMessage?.ref) + if (!ctxMessage?.options?.capture) return await cbEveryCtx(ctxMessage?.ref) } // 📄 Se encarga de revisar si el contexto del mensaje tiene callback y ejecutarlo @@ -245,9 +232,7 @@ class CoreClass { const { answer } = ctxMessage return this.providerClass .sendMessage(numberOrId, answer, ctxMessage) - .then(() => - this.databaseClass.save({ ...ctxMessage, from: numberOrId }) - ) + .then(() => this.databaseClass.save({ ...ctxMessage, from: numberOrId })) } /** @@ -277,9 +262,7 @@ class CoreClass { for (const ctxMessage of messageToSend) { const delayMs = ctxMessage?.options?.delay || 0 if (delayMs) await delay(delayMs) - QueuePrincipal.enqueue(() => - this.sendProviderAndSave(numberOrId, ctxMessage) - ) + QueuePrincipal.enqueue(() => this.sendProviderAndSave(numberOrId, ctxMessage)) } return Promise.all(queue) } From 96c2bffd093269be8e39474a84c156938504a6cb Mon Sep 17 00:00:00 2001 From: Leifer Mendez Date: Fri, 3 Feb 2023 13:37:21 +0100 Subject: [PATCH 4/9] fix(provider): :zap: baily wa.link --- packages/bot/package.json | 2 +- packages/provider/src/baileys/index.js | 53 +++++++------------------- 2 files changed, 15 insertions(+), 40 deletions(-) diff --git a/packages/bot/package.json b/packages/bot/package.json index 3d4acf3..563cd74 100644 --- a/packages/bot/package.json +++ b/packages/bot/package.json @@ -1,6 +1,6 @@ { "name": "@bot-whatsapp/bot", - "version": "0.0.91-alpha.0", + "version": "0.0.96-alpha.0", "description": "", "main": "./lib/bundle.bot.cjs", "scripts": { diff --git a/packages/provider/src/baileys/index.js b/packages/provider/src/baileys/index.js index 9361731..7a5de23 100644 --- a/packages/provider/src/baileys/index.js +++ b/packages/provider/src/baileys/index.js @@ -7,18 +7,9 @@ const { join } = require('path') const { createWriteStream, readFileSync } = require('fs') const { Console } = require('console') -const { - default: makeWASocket, - useMultiFileAuthState, - Browsers, - DisconnectReason, -} = require('@adiwajshing/baileys') +const { default: makeWASocket, useMultiFileAuthState, Browsers, DisconnectReason } = require('@adiwajshing/baileys') -const { - baileyGenerateImage, - baileyCleanNumber, - baileyIsValidNumber, -} = require('./utils') +const { baileyGenerateImage, baileyCleanNumber, baileyIsValidNumber } = require('./utils') const { generalDownload } = require('../../common/download') @@ -46,9 +37,7 @@ class BaileysProvider extends ProviderClass { */ initBailey = async () => { const NAME_DIR_SESSION = `${this.globalVendorArgs.name}_sessions` - const { state, saveCreds } = await useMultiFileAuthState( - NAME_DIR_SESSION - ) + const { state, saveCreds } = await useMultiFileAuthState(NAME_DIR_SESSION) this.saveCredsGlobal = saveCreds try { @@ -57,7 +46,7 @@ class BaileysProvider extends ProviderClass { auth: state, browser: Browsers.macOS('Desktop'), syncFullHistory: false, - logger: pino({ level: 'error' }), + logger: pino({ level: 'fatal' }), }) sock.ev.on('connection.update', async (update) => { @@ -96,10 +85,7 @@ class BaileysProvider extends ProviderClass { `Necesitas ayuda: https://link.codigoencasa.com/DISCORD`, ], }) - await baileyGenerateImage( - qr, - `${this.globalVendorArgs.name}.qr.png` - ) + await baileyGenerateImage(qr, `${this.globalVendorArgs.name}.qr.png`) } }) @@ -131,9 +117,10 @@ class BaileysProvider extends ProviderClass { const [messageCtx] = messages let payload = { ...messageCtx, - body: messageCtx?.message?.conversation, + body: messageCtx?.message?.extendedTextMessage?.text ?? messageCtx?.message?.conversation, from: messageCtx?.key?.remoteJid, } + if (payload.from === 'status@broadcast') return if (payload?.key?.fromMe) return @@ -142,9 +129,7 @@ class BaileysProvider extends ProviderClass { return } - const btnCtx = - payload?.message?.buttonsResponseMessage - ?.selectedDisplayText + const btnCtx = payload?.message?.buttonsResponseMessage?.selectedDisplayText if (btnCtx) payload.body = btnCtx @@ -174,12 +159,9 @@ class BaileysProvider extends ProviderClass { const fileDownloaded = await generalDownload(imageUrl) const mimeType = mime.lookup(fileDownloaded) - if (mimeType.includes('image')) - return this.sendImage(number, fileDownloaded, text) - if (mimeType.includes('video')) - return this.sendVideo(number, fileDownloaded, text) - if (mimeType.includes('audio')) - return this.sendAudio(number, fileDownloaded, text) + if (mimeType.includes('image')) return this.sendImage(number, fileDownloaded, text) + if (mimeType.includes('video')) return this.sendVideo(number, fileDownloaded, text) + if (mimeType.includes('audio')) return this.sendAudio(number, fileDownloaded, text) return this.sendFile(number, fileDownloaded) } @@ -294,10 +276,8 @@ class BaileysProvider extends ProviderClass { sendMessage = async (numberIn, message, { options }) => { const number = baileyCleanNumber(numberIn) - if (options?.buttons?.length) - return this.sendButtons(number, message, options.buttons) - if (options?.media) - return this.sendMedia(number, options.media, message) + if (options?.buttons?.length) return this.sendButtons(number, message, options.buttons) + if (options?.media) return this.sendMedia(number, options.media, message) return this.sendText(number, message) } @@ -332,12 +312,7 @@ class BaileysProvider extends ProviderClass { * @example await sendContact("xxxxxxxxxxx@c.us" || "xxxxxxxxxxxxxxxxxx@g.us", "+xxxxxxxxxxx", "Robin Smith", messages) */ - sendContact = async ( - remoteJid, - contactNumber, - displayName, - messages = null - ) => { + sendContact = async (remoteJid, contactNumber, displayName, messages = null) => { const cleanContactNumber = contactNumber.replaceAll(' ', '') const waid = cleanContactNumber.replace('+', '') From 23e09efaeccaf51018c55da492edff45b625f0a9 Mon Sep 17 00:00:00 2001 From: Leifer Mendez Date: Fri, 3 Feb 2023 15:46:07 +0100 Subject: [PATCH 5/9] fix(cli): :zap: working flowDynamic test --- .vscode/extensions.json | 2 +- __test__/07-case.test.js | 92 +++++++++++++++++++++ packages/bot/core/core.class.js | 2 +- packages/provider/src/web-whatsapp/index.js | 32 +++---- 4 files changed, 104 insertions(+), 24 deletions(-) create mode 100644 __test__/07-case.test.js diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 0d0e52b..1ace260 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,3 +1,3 @@ { - "recommendations": ["xyc.vscode-mdx-preview"] + "recommendations": ["xyc.vscode-mdx-preview", "vivaxy.vscode-conventional-commits", "mhutchie.git-graph"] } diff --git a/__test__/07-case.test.js b/__test__/07-case.test.js new file mode 100644 index 0000000..6eb8e42 --- /dev/null +++ b/__test__/07-case.test.js @@ -0,0 +1,92 @@ +const { test } = require('uvu') +const assert = require('uvu/assert') +const MOCK_DB = require('../packages/database/src/mock') +const PROVIDER_DB = require('../packages/provider/src/mock') +const { addKeyword, createBot, createFlow, createProvider } = require('../packages/bot/index') + +/** + * Falsear peticion async + * @param {*} fakeData + * @returns + */ +const fakeHTTP = async (fakeData = []) => { + await delay(5) + const data = fakeData.map((u, i) => ({ body: `${i + 1} ${u}` })) + return Promise.resolve(data) +} + +let STATE_APP = {} + +test(`[Caso - 07] Retornar estado`, async () => { + const MOCK_VALUES = ['¿Cual es tu nombre?', '¿Cual es tu edad?', 'Tu datos son:'] + const provider = createProvider(PROVIDER_DB) + const database = new MOCK_DB() + + const flujoPrincipal = addKeyword(['hola']) + .addAnswer( + MOCK_VALUES[0], + { + capture: true, + }, + async (ctx, { flowDynamic, fallBack }) => { + STATE_APP[ctx.from] = { ...STATE_APP[ctx.from], name: ctx.body } + + flowDynamic('Gracias por tu nombre!') + } + ) + .addAnswer( + MOCK_VALUES[1], + { + capture: true, + }, + async (ctx, { flowDynamic, endFlow }) => { + STATE_APP[ctx.from] = { ...STATE_APP[ctx.from], age: ctx.body } + + await flowDynamic('Gracias por tu edad!') + } + ) + .addAnswer(MOCK_VALUES[2], null, async (ctx, { flowDynamic }) => { + flowDynamic(`Nombre: ${STATE_APP[ctx.from].name} Edad: ${STATE_APP[ctx.from].age}`) + }) + .addAnswer('🤖🤖 Gracias por tu participacion') + + createBot({ + database, + flow: createFlow([flujoPrincipal]), + provider, + }) + + provider.delaySendMessage(0, 'message', { + from: '000', + body: 'hola', + }) + + provider.delaySendMessage(20, 'message', { + from: '000', + body: 'Leifer', + }) + + provider.delaySendMessage(40, 'message', { + from: '000', + body: '90', + }) + + await delay(1200) + const getHistory = database.listHistory.map((i) => i.answer) + assert.is(MOCK_VALUES[0], getHistory[0]) + assert.is('Leifer', getHistory[1]) + assert.is('Gracias por tu nombre!', getHistory[2]) + assert.is('¿Cual es tu edad?', getHistory[3]) + assert.is('90', getHistory[4]) + assert.is('Gracias por tu edad!', getHistory[5]) + assert.is('Tu datos son:', getHistory[6]) + assert.is('Nombre: Leifer Edad: 90', getHistory[7]) + assert.is('🤖🤖 Gracias por tu participacion', getHistory[8]) + assert.is(undefined, getHistory[9]) +}) + +test.run() + +function delay(ms) { + return new Promise((res) => setTimeout(res, ms)) +} diff --git a/packages/bot/core/core.class.js b/packages/bot/core/core.class.js index fae0ba4..1c15932 100644 --- a/packages/bot/core/core.class.js +++ b/packages/bot/core/core.class.js @@ -175,7 +175,7 @@ class CoreClass { const nextFlow = await this.flowClass.find(refToContinue?.ref, true) const filterNextFlow = nextFlow.filter((msg) => msg.refSerialize !== currentPrev?.refSerialize) - + console.log(`🚩🚩🚩`, filterNextFlow?.answer) return sendFlow(filterNextFlow, from, { prev: undefined }) } diff --git a/packages/provider/src/web-whatsapp/index.js b/packages/provider/src/web-whatsapp/index.js index 693d1b9..0e763c6 100644 --- a/packages/provider/src/web-whatsapp/index.js +++ b/packages/provider/src/web-whatsapp/index.js @@ -2,11 +2,7 @@ const { Client, LocalAuth, MessageMedia, Buttons } = require('whatsapp-web.js') const { ProviderClass } = require('@bot-whatsapp/bot') const { Console } = require('console') const { createWriteStream, readFileSync } = require('fs') -const { - wwebCleanNumber, - wwebGenerateImage, - wwebIsValidNumber, -} = require('./utils') +const { wwebCleanNumber, wwebGenerateImage, wwebIsValidNumber } = require('./utils') const logger = new Console({ stdout: createWriteStream('./log'), @@ -32,11 +28,7 @@ class WebWhatsappProvider extends ProviderClass { }), puppeteer: { headless: true, - args: [ - '--no-sandbox', - '--disable-setuid-sandbox', - '--unhandled-rejections=strict', - ], + args: ['--no-sandbox', '--disable-setuid-sandbox', '--unhandled-rejections=strict'], //executablePath: 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe', }, }) @@ -80,10 +72,7 @@ class WebWhatsappProvider extends ProviderClass { `Necesitas ayuda: https://link.codigoencasa.com/DISCORD`, ], }) - await wwebGenerateImage( - qr, - `${this.globalVendorArgs.name}.qr.png` - ) + await wwebGenerateImage(qr, `${this.globalVendorArgs.name}.qr.png`) }, }, { @@ -116,6 +105,9 @@ class WebWhatsappProvider extends ProviderClass { * @returns */ sendButtons = async (number, message, buttons = []) => { + console.log(`🚩 ¿No te funciona los botones? Intenta instalar`) + console.log(`npm i github:pedroslopez/whatsapp-web.js#fix-buttons-list`) + const buttonMessage = new Buttons(message, buttons, '', '') return this.vendor.sendMessage(number, buttonMessage) } @@ -230,12 +222,9 @@ class WebWhatsappProvider extends ProviderClass { const fileDownloaded = await generalDownload(mediaUrl) const mimeType = mime.lookup(fileDownloaded) - if (mimeType.includes('image')) - return this.sendImage(number, fileDownloaded, text) - if (mimeType.includes('video')) - return this.sendVideo(number, fileDownloaded) - if (mimeType.includes('audio')) - return this.sendAudio(number, fileDownloaded) + if (mimeType.includes('image')) return this.sendImage(number, fileDownloaded, text) + if (mimeType.includes('video')) return this.sendVideo(number, fileDownloaded) + if (mimeType.includes('audio')) return this.sendAudio(number, fileDownloaded) return this.sendFile(number, fileDownloaded) } @@ -249,8 +238,7 @@ class WebWhatsappProvider extends ProviderClass { */ sendMessage = async (userId, message, { options }) => { const number = wwebCleanNumber(userId) - if (options?.buttons?.length) - return this.sendButtons(number, message, options.buttons) + if (options?.buttons?.length) return this.sendButtons(number, message, options.buttons) if (options?.media) return this.sendMedia(number, options.media) return this.sendText(number, message) } From f76932021ce968d93241b55cfcdb8ae0e0e6c934 Mon Sep 17 00:00:00 2001 From: Leifer Mendez Date: Fri, 3 Feb 2023 18:17:46 +0100 Subject: [PATCH 6/9] fix(cli): :zap: working flowDynamic test --- __test__/07-case.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/__test__/07-case.test.js b/__test__/07-case.test.js index 6eb8e42..024fc60 100644 --- a/__test__/07-case.test.js +++ b/__test__/07-case.test.js @@ -82,7 +82,8 @@ test(`[Caso - 07] Retornar estado`, async () => { assert.is('Tu datos son:', getHistory[6]) assert.is('Nombre: Leifer Edad: 90', getHistory[7]) assert.is('🤖🤖 Gracias por tu participacion', getHistory[8]) - assert.is(undefined, getHistory[9]) + assert.is('🤖🤖 Gracias por tu participacion', getHistory[9]) + assert.is(undefined, getHistory[10]) }) test.run() From aef52d2694fa6616d614338643db198b4f7f1fe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifer=20Jes=C3=BAs=20Mendez?= Date: Fri, 3 Feb 2023 19:26:00 +0100 Subject: [PATCH 7/9] fix(cli): :zap: working flowDynamic test --- __test__/07-case.test.js | 3 +-- packages/bot/core/core.class.js | 5 +++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/__test__/07-case.test.js b/__test__/07-case.test.js index 024fc60..6eb8e42 100644 --- a/__test__/07-case.test.js +++ b/__test__/07-case.test.js @@ -82,8 +82,7 @@ test(`[Caso - 07] Retornar estado`, async () => { assert.is('Tu datos son:', getHistory[6]) assert.is('Nombre: Leifer Edad: 90', getHistory[7]) assert.is('🤖🤖 Gracias por tu participacion', getHistory[8]) - assert.is('🤖🤖 Gracias por tu participacion', getHistory[9]) - assert.is(undefined, getHistory[10]) + assert.is(undefined, getHistory[9]) }) test.run() diff --git a/packages/bot/core/core.class.js b/packages/bot/core/core.class.js index 1c15932..1258b5a 100644 --- a/packages/bot/core/core.class.js +++ b/packages/bot/core/core.class.js @@ -172,10 +172,11 @@ class CoreClass { for (const msg of parseListMsg) { await this.sendProviderAndSave(from, msg) } - const nextFlow = await this.flowClass.find(refToContinue?.ref, true) const filterNextFlow = nextFlow.filter((msg) => msg.refSerialize !== currentPrev?.refSerialize) - console.log(`🚩🚩🚩`, filterNextFlow?.answer) + + if (filterNextFlow.map((i) => i.keyword).includes(currentPrev?.ref)) return + return sendFlow(filterNextFlow, from, { prev: undefined }) } From c0113ca49295aff220d8defcb53f2ba7f2872d75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifer=20Jes=C3=BAs=20Mendez?= Date: Fri, 3 Feb 2023 19:32:04 +0100 Subject: [PATCH 8/9] fix(cli): :zap: working flowDynamic test --- packages/bot/core/core.class.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/bot/core/core.class.js b/packages/bot/core/core.class.js index 1258b5a..a4a0cd0 100644 --- a/packages/bot/core/core.class.js +++ b/packages/bot/core/core.class.js @@ -168,16 +168,25 @@ class CoreClass { const parseListMsg = listMsg.map((opt, index) => createCtxMessage(opt, index)) const currentPrev = await this.databaseClass.getPrevByNumber(from) + const skipContinueFlow = async () => { + const nextFlow = await this.flowClass.find(refToContinue?.ref, true) + const filterNextFlow = nextFlow.filter((msg) => msg.refSerialize !== currentPrev?.refSerialize) + const isContinueFlow = filterNextFlow.map((i) => i.keyword).includes(currentPrev?.ref) + return { + continue: !isContinueFlow, + contexts: filterNextFlow, + } + } + if (endFlowFlag) return for (const msg of parseListMsg) { await this.sendProviderAndSave(from, msg) } - const nextFlow = await this.flowClass.find(refToContinue?.ref, true) - const filterNextFlow = nextFlow.filter((msg) => msg.refSerialize !== currentPrev?.refSerialize) - if (filterNextFlow.map((i) => i.keyword).includes(currentPrev?.ref)) return + const continueFlowData = await skipContinueFlow() - return sendFlow(filterNextFlow, from, { prev: undefined }) + if (continueFlowData.continue) return sendFlow(continueFlowData.contexts, from, { prev: undefined }) + return } // 📄 Se encarga de revisar si el contexto del mensaje tiene callback o fallback From e34560c77d4852d2e90930f0858e51aa67d4eeab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leifer=20Jes=C3=BAs=20Mendez?= Date: Sat, 4 Feb 2023 17:01:10 +0100 Subject: [PATCH 9/9] feat(cli): :fire: add regex expression in addKeyworkd --- __test__/08-case.test.js | 43 +++++++++++++++++++++++++++ packages/bot/io/flow.class.js | 18 +++++++---- packages/bot/io/methods/addKeyword.js | 10 ++++--- 3 files changed, 62 insertions(+), 9 deletions(-) create mode 100644 __test__/08-case.test.js diff --git a/__test__/08-case.test.js b/__test__/08-case.test.js new file mode 100644 index 0000000..82fc46a --- /dev/null +++ b/__test__/08-case.test.js @@ -0,0 +1,43 @@ +const { test } = require('uvu') +const assert = require('uvu/assert') +const MOCK_DB = require('../packages/database/src/mock') +const PROVIDER_DB = require('../packages/provider/src/mock') +const { addKeyword, createBot, createFlow, createProvider } = require('../packages/bot/index') + +test(`[Caso - 08] Regular expression on keyword`, async () => { + const provider = createProvider(PROVIDER_DB) + const database = new MOCK_DB() + + const REGEX_CREDIT_NUMBER = `/(^4[0-9]{12}(?:[0-9]{3})?$)|(^(?:5[1-5][0-9]{2}|222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}$)|(3[47][0-9]{13})|(^3(?:0[0-5]|[68][0-9])[0-9]{11}$)|(^6(?:011|5[0-9]{2})[0-9]{12}$)|(^(?:2131|1800|35\d{3})\d{11}$)/gm` + const flujoPrincipal = addKeyword(REGEX_CREDIT_NUMBER, { regex: true }) + .addAnswer(`Gracias por proporcionar un numero de tarjeta valido`) + .addAnswer('Fin!') + + createBot({ + database, + flow: createFlow([flujoPrincipal]), + provider, + }) + + provider.delaySendMessage(0, 'message', { + from: '000', + body: 'hola', + }) + + provider.delaySendMessage(20, 'message', { + from: '000', + body: '374245455400126', + }) + + await delay(40) + const getHistory = database.listHistory.map((i) => i.answer) + assert.is('Gracias por proporcionar un numero de tarjeta valido', getHistory[0]) + assert.is('Fin!', getHistory[1]) + assert.is(undefined, getHistory[2]) +}) + +test.run() + +function delay(ms) { + return new Promise((res) => setTimeout(res, ms)) +} diff --git a/packages/bot/io/flow.class.js b/packages/bot/io/flow.class.js index 458c423..13de732 100644 --- a/packages/bot/io/flow.class.js +++ b/packages/bot/io/flow.class.js @@ -25,9 +25,17 @@ class FlowClass { let refSymbol = null overFlow = overFlow ?? this.flowSerialize + const customRegex = (str = null) => { + if (typeof str !== 'string') return + const instanceRegex = new RegExp(str) + return instanceRegex.test(str) + } + /** Retornar expresion regular para buscar coincidencia */ - const mapSensitive = (str, flag = false) => { - const regexSensitive = flag ? 'g' : 'i' + const mapSensitive = (str, mapOptions = { sensitive: false, regex: false }) => { + if (mapOptions.regex) return customRegex(str) + + const regexSensitive = mapOptions.sensitive ? 'g' : 'i' if (Array.isArray(str)) { return new RegExp(str.join('|'), regexSensitive) } @@ -36,6 +44,7 @@ class FlowClass { const findIn = (keyOrWord, symbol = false, flow = overFlow) => { const sensitive = refSymbol?.options?.sensitive || false + const regex = refSymbol?.options?.regex || false capture = refSymbol?.options?.capture || false if (capture) return messages @@ -46,7 +55,7 @@ class FlowClass { if (refSymbol?.ref) findIn(refSymbol.ref, true) } else { refSymbol = flow.find((c) => { - return mapSensitive(c.keyword, sensitive).test(keyOrWord) + return mapSensitive(c.keyword, { sensitive, regex }).test(keyOrWord) }) if (refSymbol?.ref) findIn(refSymbol.ref, true) return messages @@ -56,8 +65,7 @@ class FlowClass { return messages } - findBySerialize = (refSerialize) => - this.flowSerialize.find((r) => r.refSerialize === refSerialize) + findBySerialize = (refSerialize) => this.flowSerialize.find((r) => r.refSerialize === refSerialize) findIndexByRef = (ref) => this.flowSerialize.findIndex((r) => r.ref === ref) } diff --git a/packages/bot/io/methods/addKeyword.js b/packages/bot/io/methods/addKeyword.js index e17f9be..50a5c0c 100644 --- a/packages/bot/io/methods/addKeyword.js +++ b/packages/bot/io/methods/addKeyword.js @@ -8,12 +8,14 @@ const { toJson } = require('./toJson') * @param {*} options {sensitive:boolean} default false */ const addKeyword = (keyword, options) => { + if (typeof keyword !== 'string' && !Array.isArray(keyword)) { + throw new Error('DEBE_SER_STRING_ARRAY_REGEX') + } + const parseOptions = () => { const defaultProperties = { - sensitive: - typeof options?.sensitive === 'boolean' - ? options?.sensitive - : false, + sensitive: typeof options?.sensitive === 'boolean' ? options?.sensitive : false, + regex: typeof options?.regex === 'boolean' ? options?.regex : false, } return defaultProperties