From 4b98309309f6f92a222b0a21e4e48c3ff41382fb Mon Sep 17 00:00:00 2001 From: Leifer Mendez Date: Thu, 1 Jun 2023 21:50:04 +0200 Subject: [PATCH 01/17] docs: :zap: improve docs wit Ozzy --- packages/docs/src/root.tsx | 46 ++++++++++--- .../docs/src/routes/docs/add-action/index.mdx | 35 ++++++++++ .../src/routes/docs/add-answers/index.mdx | 36 ++++++++++ .../src/routes/docs/add-keyword/index.mdx | 28 +++++--- .../docs/src/routes/docs/end-flow/index.mdx | 69 +++++++++++++++++++ .../docs/src/routes/docs/example/index.mdx | 2 +- .../docs/src/routes/docs/fall-back/index.mdx | 28 ++++++++ .../src/routes/docs/flow-dynamic/index.mdx | 26 +++++++ .../docs/src/routes/docs/goto-flow/index.mdx | 17 +++++ packages/docs/src/routes/docs/index.mdx | 15 +--- .../docs/src/routes/docs/install/index.mdx | 14 ++-- 11 files changed, 278 insertions(+), 38 deletions(-) create mode 100644 packages/docs/src/routes/docs/add-action/index.mdx create mode 100644 packages/docs/src/routes/docs/add-answers/index.mdx create mode 100644 packages/docs/src/routes/docs/end-flow/index.mdx create mode 100644 packages/docs/src/routes/docs/fall-back/index.mdx create mode 100644 packages/docs/src/routes/docs/flow-dynamic/index.mdx create mode 100644 packages/docs/src/routes/docs/goto-flow/index.mdx diff --git a/packages/docs/src/root.tsx b/packages/docs/src/root.tsx index 639f06ce8..94c3639dc 100644 --- a/packages/docs/src/root.tsx +++ b/packages/docs/src/root.tsx @@ -32,22 +32,39 @@ export default component$(() => { { name: 'Requerimientos', link: '/docs/requirements' }, { name: 'Instalación', link: '/docs/install' }, { name: 'Pruebalo', link: '/docs/example' }, + { name: 'Conceptos', link: '/docs/essential' }, ], }, { - title: 'Esenciales', + title: '@bot/core', list: [ - { name: 'Conceptos', link: '/docs/essential' }, - { name: 'Conversaciones', link: '/docs/flows' }, - { name: 'Proveedores', link: '/docs/providers' }, - { name: 'Base de datos', link: '/docs/database' }, + { name: 'addKeyword', link: '/docs/add-keyword' }, + { name: 'addAnswers', link: '/docs/add-answers' }, + { name: 'addAction', link: '/docs/add-action' }, + { name: 'flowDynamic', link: '/docs/flow-dynamic' }, + { name: 'fallBack', link: '/docs/fall-back' }, + { name: 'endFlow', link: '/docs/end-flow' }, + { name: 'gotoFlow', link: '/docs/goto-flow' }, ], }, { - title: 'Avanzado', + title: '@bot/provider', list: [ - { name: 'Migración', link: '/docs/migration' }, - { name: 'MasterClass', link: '/docs/masterclass' }, + { name: 'Meta', link: '/docs/provider-meta' }, + { name: 'Twilio', link: '/docs/provider-twilio' }, + { name: 'Baileys', link: '/docs/provider-baileys' }, + { name: 'Venom', link: '/docs/provider-venom' }, + { name: 'WPPConnect', link: '/docs/provider-wppconnect' }, + { name: 'Whatsapp-web.js', link: '/docs/provider-wweb' } + ], + }, + { + title: '@bot/database', + list: [ + { name: 'Memory', link: '/docs/database-memory' }, + { name: 'Json', link: '/docs/database-json' }, + { name: 'Mongo', link: '/docs/database-mongo' }, + { name: 'MySQL', link: '/docs/database-mysql' } ], }, { @@ -58,6 +75,19 @@ export default component$(() => { { name: 'Cloud', link: '/docs/deploy/cloud' }, ], }, + { + title: 'Casos de Uso', + list: [ + { name: 'Manejo de estado', link: '/docs/migration' }, + ], + }, + { + title: 'Avanzado', + list: [ + { name: 'Migración', link: '/docs/migration' }, + { name: 'MasterClass', link: '/docs/masterclass' }, + ], + }, { title: 'Comunidad', list: [ diff --git a/packages/docs/src/routes/docs/add-action/index.mdx b/packages/docs/src/routes/docs/add-action/index.mdx new file mode 100644 index 000000000..3f45f5604 --- /dev/null +++ b/packages/docs/src/routes/docs/add-action/index.mdx @@ -0,0 +1,35 @@ +import Navigation from '../../../components/widgets/Navigation' + +# addAction + +Este fragmento de código define una constante llamada **`flujoBienvenida`**, que utiliza la función **`addKeyword()`** para agregar la palabra clave "hola" al Chat Bot de WhatsApp. Cuando el Bot recibe un mensaje que contiene la palabra "hola", se activa la acción definida en la función **`addAction()`**. + +La acción en sí es asíncrona y toma dos argumentos: **`ctx`** y **`{provider}`**. **`ctx`** es un objeto que contiene información sobre el mensaje recibido, como el remitente, el destinatario y el contenido del mensaje. **`{provider}`** es un objeto que se utiliza para enviar mensajes de respuesta. + +Dentro de la función **`addAction()`**, primero se extrae el ID del remitente del mensaje utilizando **`ctx.key.remoteJid`**. Luego, se define un conjunto de botones que se incluirán en un mensaje de plantilla. Cada botón tiene un índice, un texto de visualización y una acción. Los botones incluidos son un botón de enlace, un botón de llamada y un botón de respuesta rápida. + +Después de definir los botones, se crea un objeto de mensaje de plantilla que contiene un texto de saludo y un pie de página, así como los botones definidos anteriormente. + +A continuación, se utiliza el objeto **`{provider}`** para enviar el mensaje de plantilla al remitente del mensaje original utilizando el método **`sendMessage()`**. + +Por último, se imprime un mensaje de registro en la consola que indica que se ha enviado el mensaje, y luego se retorna. + +En resumen, **`addAction()`**es una función útil para definir acciones que se activan cuando se recibe un mensaje de WhatsApp específico. La función **`action`** puede realizar diversas tareas y acceder a los métodos y propiedades. En general, **`addAction()`**te permite crear Chat Bots de WhatsApp personalizados que pueden interactuar con los usuarios de una manera programática. Puedes definir acciones específicas que se activan cuando se recibe un mensaje específico, lo que te permite crear una variedad de flujos de conversación y respuestas automatizadas. Con **`addAction()`**y otras funciones en la biblioteca puedes personalizar completamente la funcionalidad de tu Bot de WhatsApp para satisfacer las necesidades de tu proyecto o negocio. + +Esta funcion se importa desde `@bot-whatsapp/bot` + +```js +const { addKeyword } = require('@bot-whatsapp/bot') +const flowPrincipal = addKeyword(['hola', 'alo']) + .addAnswer(['Hola, bienvenido a mi tienda', '¿Como puedo ayudarte?']) + .addAnswer(['Tengo:', 'Zapatos', 'Bolsos', 'etc ...']) +``` + +--- + + diff --git a/packages/docs/src/routes/docs/add-answers/index.mdx b/packages/docs/src/routes/docs/add-answers/index.mdx new file mode 100644 index 000000000..23dcd3f64 --- /dev/null +++ b/packages/docs/src/routes/docs/add-answers/index.mdx @@ -0,0 +1,36 @@ +import Navigation from '../../../components/widgets/Navigation' + +# addAnswer + +# ctx + +Esta funcion se importa desde `@bot-whatsapp/bot` + +```js + const { addKeyword } = require('@bot-whatsapp/bot') + + const flowString = addKeyword('hola').addAnswer('Este mensaje se enviara 1 segundo despues', { + delay: 1000, + }) + + const flowString = addKeyword('hola').addAnswer('Este mensaje envia una imagen', { + media: 'https://i.imgur.com/0HpzsEm.png', + }) + + const flowString = addKeyword('hola').addAnswer('Este mensaje envia tres botones', { + buttons: [{ body: 'Boton 1' }, { body: 'Boton 2' }, { body: 'Boton 3' }], + }) + + const flowString = addKeyword('hola').addAnswer('Este mensaje espera una respueta del usuario', { + capture: true, + }) +``` + +--- + + diff --git a/packages/docs/src/routes/docs/add-keyword/index.mdx b/packages/docs/src/routes/docs/add-keyword/index.mdx index 258eaf7ef..9443df78b 100644 --- a/packages/docs/src/routes/docs/add-keyword/index.mdx +++ b/packages/docs/src/routes/docs/add-keyword/index.mdx @@ -1,13 +1,21 @@ ---- -title: Overview -contributors: - - adamdbradley - - steve8708 - - manucorporat - - gabrielgrant ---- +import Navigation from '../../../components/widgets/Navigation' + +# addKeyword + +Esta funcion se importa desde `@bot-whatsapp/bot` -# AddKeyword ```js -const a = 1 +const { addKeyword } = require('@bot-whatsapp/bot') +const flowPrincipal = addKeyword(['hola', 'alo']) + .addAnswer(['Hola, bienvenido a mi tienda', '¿Como puedo ayudarte?']) + .addAnswer(['Tengo:', 'Zapatos', 'Bolsos', 'etc ...']) ``` + +--- + + diff --git a/packages/docs/src/routes/docs/end-flow/index.mdx b/packages/docs/src/routes/docs/end-flow/index.mdx new file mode 100644 index 000000000..bc256c509 --- /dev/null +++ b/packages/docs/src/routes/docs/end-flow/index.mdx @@ -0,0 +1,69 @@ +import Navigation from '../../../components/widgets/Navigation' + +# endFlow + +Esta función se utiliza para finalizar un flujo con dos o más addAnswer. Un ejemplo de uso sería registrar 3 datos de un usuario en 3 preguntas destinas y que el usuario pueda finalizar por él mismo flujo. Como podrás comprobar en el ejemplo siguiente, se puede vincular flowDynamic y todas sus funciones; como por ejemplo botones. + +```js +let nombre; +let apellidos; +let telefono; + +const flowFormulario = addKeyword(['Hola','⬅️ Volver al Inicio']) + .addAnswer( + ['Hola!','Para enviar el formulario necesito unos datos...' ,'Escriba su *Nombre*'], + { capture: true, buttons: [{ body: '❌ Cancelar solicitud' }] }, + + async (ctx, { flowDynamic, endFlow }) => { + if (ctx.body == '❌ Cancelar solicitud') + return endFlow({body: '❌ Su solicitud ha sido cancelada ❌', // Aquí terminamos el flow si la condicion se comple + buttons:[{body:'⬅️ Volver al Inicio' }] // Y además, añadimos un botón por si necesitas derivarlo a otro flow + + + }) + nombre = ctx.body; + return flowDynamic(`Encantado *${nombre}*, continuamos...`) + } + ) + .addAnswer( + ['También necesito tus dos apellidos'], + { capture: true, buttons: [{ body: '❌ Cancelar solicitud' }] }, + + async (ctx, { flowDynamic, endFlow }) => { + if (ctx.body == '❌ Cancelar solicitud') + return endFlow({body: '❌ Su solicitud ha sido cancelada ❌', + buttons:[{body:'⬅️ Volver al Inicio' }] + + + }) + apellidos = ctx.body; + return flowDynamic(`Perfecto *${nombre}*, por último...`) + } + ) + .addAnswer( + ['Dejeme su número de teléfono y le llamaré lo antes posible.'], + { capture: true, buttons: [{ body: '❌ Cancelar solicitud' }] }, + + async (ctx, { flowDynamic, endFlow }) => { + if (ctx.body == '❌ Cancelar solicitud') + return endFlow({body: '❌ Su solicitud ha sido cancelada ❌', + buttons:[{body:'⬅️ Volver al Inicio' }] + }) + + + telefono = ctx.body; + return flowDynamic(`Estupendo *${nombre}*! te dejo el resumen de tu formulario + \n- Nombre y apellidos: *${nombre} ${apellidos}* + \n- Telefono: *${telefono}*`) + } + ) +``` + +--- + + diff --git a/packages/docs/src/routes/docs/example/index.mdx b/packages/docs/src/routes/docs/example/index.mdx index 057fcc617..4a468ebbb 100644 --- a/packages/docs/src/routes/docs/example/index.mdx +++ b/packages/docs/src/routes/docs/example/index.mdx @@ -32,7 +32,7 @@ const main = async () => { main() ``` -## Explicando código +## Explicando En esta parte solo estamos declaramos las dependencias que vamos a utilizar. Si quieres saber a fondo cada una de las funciones te recomiendo pasarte por la seccion de **[conceptos](/docs/concepts)** diff --git a/packages/docs/src/routes/docs/fall-back/index.mdx b/packages/docs/src/routes/docs/fall-back/index.mdx new file mode 100644 index 000000000..42a689826 --- /dev/null +++ b/packages/docs/src/routes/docs/fall-back/index.mdx @@ -0,0 +1,28 @@ +import Navigation from '../../../components/widgets/Navigation' + +# fallBack + +La función **FallBack()** es una función que se utiliza para repetir el último mensaje del flujo en caso de que el usuario no proporcione una respuesta válida. Es decir, si el usuario ingresa un mensaje que no coincide con ninguna palabra clave o respuesta esperada, el Bot puede llamar a la función **FallBack()** para volver a enviar el último mensaje y esperar una respuesta válida. + +La función **FallBack()** se puede llamar dentro del método **addAnswer()** del flujo. Para ello, simplemente se llama a la función **FallBack()** dentro de una condición que comprueba si la respuesta del usuario es válida o no. Por ejemplo: +```js +const { addKeyword } = require('@bot-whatsapp/bot') + +const flowString = addKeyword('hola') + .addAnswer('Indica cual es tu email', null, (ctx, { fallBack }) => { + if (!ctx.body.includes('@')) { + return fallBack() + } else { + // Lógica para procesar el correo electrónico del usuario + } + }) +``` + +--- + + diff --git a/packages/docs/src/routes/docs/flow-dynamic/index.mdx b/packages/docs/src/routes/docs/flow-dynamic/index.mdx new file mode 100644 index 000000000..e678481ba --- /dev/null +++ b/packages/docs/src/routes/docs/flow-dynamic/index.mdx @@ -0,0 +1,26 @@ +import Navigation from '../../../components/widgets/Navigation' + +# flowDynamic + +La función flowDynamic + se utiliza para devolver mensajes dinámicos que pueden venir de una API o Base de datos. La función recibe un array que debe contener la siguiente estructura: + +```js +const { addKeyword } = require('@bot-whatsapp/bot') + +const flowString = addKeyword('ver categorias') + .addAnswer('Estas son las categorías disponibles:', null, async (ctx, {flowDynamic}) => { + const categories = await db.getCategories() + const messages = categories.map((c) => ({body: c.name})) + await flowDynamic(messages) + }) +``` + +--- + + diff --git a/packages/docs/src/routes/docs/goto-flow/index.mdx b/packages/docs/src/routes/docs/goto-flow/index.mdx new file mode 100644 index 000000000..6a2f2699b --- /dev/null +++ b/packages/docs/src/routes/docs/goto-flow/index.mdx @@ -0,0 +1,17 @@ +import Navigation from '../../../components/widgets/Navigation' + +# gotoFlow + +GotoFlow +```js + +``` + +--- + + diff --git a/packages/docs/src/routes/docs/index.mdx b/packages/docs/src/routes/docs/index.mdx index ab6d5f112..9813ca5c6 100644 --- a/packages/docs/src/routes/docs/index.mdx +++ b/packages/docs/src/routes/docs/index.mdx @@ -1,20 +1,11 @@ import Alert from '../../components/widgets/Alert' import Navigation from '../../components/widgets/Navigation' -# Introducción +# Vista Rápida - - **Atención** estás leyendo la documentación de la **versión v2** de esta librería, si vienes de la versión anterior - te recomendamos pasarte por la sección de **[migración](/docs/migration/)** para que puedas disfrutar de las nuevas - características. - +**@bot-whatsapp** es una herramienta que te ayuda a crear un tipo especial de programa llamado chatbot para WhatsApp. Con esta herramienta, podrás hacer tu propio chatbot en WhatsApp en muy poco tiempo y de forma rápida. En esta guía, encontrarás ejemplos y recursos que te ayudarán. -## ¿Qué es esto? - -**@bot-whatsapp** es una librería que te permitirá **crear chatbot para WhatsApp** en tan solo minutos de una manera ágil y rápida. A lo largo de esta documentación encontrarás ejemplos y material de ayuda. - -Si eres una persona con **poco tiempo y gran capacidad** de comprensión, con conocimientos ejecutando proyectos en Node.js y manejo de terminal. -Esta documentación te ayudará a instalar tu bot de WhatsApp en simples pasos; con el propósito de que tengas un chatbot funcional **en solo minutos.** +Si eres alguien que tiene poco tiempo pero entiende bien las cosas, y además sabes cómo trabajar con Node.js y usar la terminal, esta guía te será de mucha utilidad. Te ayudará a instalar tu chatbot de WhatsApp en unos simples pasos, para que puedas tenerlo funcionando en solo minutos. --- diff --git a/packages/docs/src/routes/docs/install/index.mdx b/packages/docs/src/routes/docs/install/index.mdx index 4b64e1169..74c0961c0 100644 --- a/packages/docs/src/routes/docs/install/index.mdx +++ b/packages/docs/src/routes/docs/install/index.mdx @@ -3,11 +3,6 @@ import Navigation from '../../../components/widgets/Navigation' # Instalación -**Con esta librería, puedes construir flujos automatizados de conversación de manera agnóstica al proveedor de WhatsApp,** configurar respuestas automatizadas para preguntas frecuentes, recibir y responder mensajes de manera automatizada, y hacer un seguimiento de las interacciones con los clientes. Además, puedes configurar fácilmente disparadores que te ayudaran a expandir las funcionalidades sin límites. - ---- - -### Comenzamos Crear un bot es tan sencillo como ejecutar el siguiente comando y seguir las instrucciones @@ -19,6 +14,11 @@ _Para seleccionar usa la tecla de **espacio** y para confirmar la tecla **enter* El **CLI** te hace una revisión previa, de versión de Node y sistema operativo, con la finalidad de informarte si cumples los requisitos o mostrarte información de interés. + + + **¿No te funciona las flechas?** Intenta con otro terminal, powershell, cmd, gitbash + +
-### Plantilla +### ¿Ahora qué? -Luego de seleccionar las opciones de tu preferencia se creara una carpeta con una plantilla de un flujo de un bot listo para ejecutar y que puedes modificar a tu gusto. +Luego de seleccionar las opciones de tu preferencia se creara una carpeta con un bot listo para ejecutar y que puedes modificar a tu gusto. **[Ver más plantillas](https://github.com/codigoencasa/bot-whatsapp/tree/dev/starters/apps)** Cada plantilla tiene sus dependencias necesarias basadas en tu previa selección. **Ejemplo**, si seleccionas el proveedor de MySQL, la plantilla incorpora lo necesario para que tu conexión con la base de datos sea exitosa. From 50effc1a2fc58593182e09a44f179ccba7fe518e Mon Sep 17 00:00:00 2001 From: Leifer Mendez Date: Fri, 2 Jun 2023 15:14:30 +0200 Subject: [PATCH 02/17] docs: :memo: page `addKeyword` --- packages/docs/src/assets/styles/global.css | 2 +- packages/docs/src/root.tsx | 2 + .../src/routes/docs/add-keyword/index.mdx | 87 +++++++++++++++++++ .../docs/src/routes/docs/essential/index.mdx | 10 +-- 4 files changed, 95 insertions(+), 6 deletions(-) diff --git a/packages/docs/src/assets/styles/global.css b/packages/docs/src/assets/styles/global.css index 86f3be101..d49703957 100644 --- a/packages/docs/src/assets/styles/global.css +++ b/packages/docs/src/assets/styles/global.css @@ -48,7 +48,7 @@ } .slot pre code { - @apply p-3 rounded md:w-full max-w-screen-md overflow-x-auto w-fit bg-gray-800 dark:bg-slate-800 ease-in duration-75 text-gray-100 text-xs shadow-xl; + @apply p-3 rounded md:w-full max-w-screen-lg overflow-x-auto w-fit bg-gray-800 dark:bg-slate-800 ease-in duration-75 text-gray-100 text-xs shadow-xl; } .slot iframe { diff --git a/packages/docs/src/root.tsx b/packages/docs/src/root.tsx index 94c3639dc..b6319653e 100644 --- a/packages/docs/src/root.tsx +++ b/packages/docs/src/root.tsx @@ -40,6 +40,7 @@ export default component$(() => { list: [ { name: 'addKeyword', link: '/docs/add-keyword' }, { name: 'addAnswers', link: '/docs/add-answers' }, + { name: 'ctx', link: '/docs/ctx' }, { name: 'addAction', link: '/docs/add-action' }, { name: 'flowDynamic', link: '/docs/flow-dynamic' }, { name: 'fallBack', link: '/docs/fall-back' }, @@ -79,6 +80,7 @@ export default component$(() => { title: 'Casos de Uso', list: [ { name: 'Manejo de estado', link: '/docs/migration' }, + { name: 'API', link: '/docs/api' }, ], }, { diff --git a/packages/docs/src/routes/docs/add-keyword/index.mdx b/packages/docs/src/routes/docs/add-keyword/index.mdx index 9443df78b..94f9b3921 100644 --- a/packages/docs/src/routes/docs/add-keyword/index.mdx +++ b/packages/docs/src/routes/docs/add-keyword/index.mdx @@ -4,8 +4,14 @@ import Navigation from '../../../components/widgets/Navigation' Esta funcion se importa desde `@bot-whatsapp/bot` +Se encarga de iniciar un [flow](/docs/essential/) dependiendo de la configuracio que se le ha indicado. + +--- + +En este ejemplo puedes ver como definimos un conversacion que el bot va a responder cuando alguien escribe `hola` o `alo` . ```js const { addKeyword } = require('@bot-whatsapp/bot') + const flowPrincipal = addKeyword(['hola', 'alo']) .addAnswer(['Hola, bienvenido a mi tienda', '¿Como puedo ayudarte?']) .addAnswer(['Tengo:', 'Zapatos', 'Bolsos', 'etc ...']) @@ -13,6 +19,87 @@ const flowPrincipal = addKeyword(['hola', 'alo']) --- +Tambien puedes definir solo una palabra ejemplo si alguien escribe `comprar` +```js +const { addKeyword } = require('@bot-whatsapp/bot') + +const flowPrincipal = addKeyword('comprar') + .addAnswer(['¿Como puedo ayudarte?']) + +``` + +--- + +### Regex + +En algunos casos necesitaras activar un flujo basado en una expression regular para esas situaciones tenemos. + + +```js +const { addKeyword } = require('@bot-whatsapp/bot') + +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 flow = addKeyword(REGEX_CREDIT_NUMBER, { regex: true }) + .addAnswer(`Gracias por proporcionar un numero de tarjeta valido`) + .addAnswer('Fin!') + +``` + +--- + +### Sensitive + +El comportamiento del addKeyword por defecto detecta un palabra entre el mensaje ejemplo si alguien escribe `hola como estas` y tienes `addKeyword('hola')` el flow se activara. +Si no quieres este comportamiento y quieres afinar solo a que la persona escriba `hola` + + +```js +const { addKeyword } = require('@bot-whatsapp/bot') + +const flowBienvenida = addKeyword('hola', { sensitive: true }) + .addAnswer('Bienvenido a este chatbot') + +``` +--- + +### Eventos + +En muchos casos necesitamos responder cuando no es una palabra exactamente. +Ejemplo que pasa si alguien envia una nota de voz, imagen, ubicacion, imagen o video. + +En este caso puedes usar los **eventos** + +- **WELCOME**: Se dispara cuando una persona escribe un palabra que no esta en ningun flow. Util para mensajes de bienvenida +- **MEDIA**: Util para detectar cuando la persona te envia imagen o video +- **LOCATION**: Util para detectar cuando la persona te envia coordenadas de ubicacion +- **VOICE_NOTE**: Util para detectar cuando la persona te envia nota de voz +- **DOCUMENT**: Util para detectar cuando la persona te envia alguno archivo como pdf, html o otro tipo que no sea los anteriores + +--- + +```js +const { EVENTS } = require('@bot-whatsapp/bot') + +const flowBienvenida = addKeyword(EVENTS.WELCOME) + .addAnswer('Bienvenido a este chatbot') + +const flowRecibirMedia = addKeyword(EVENTS.MEDIA) + .addAnswer('He recibido tu foto o video') + +const flowLocation = addKeyword(EVENTS.LOCATION) + .addAnswer('Ohh ya veo donde estas') + +const flowNotaDeVoz = addKeyword(EVENTS.VOICE_NOTE) + .addAnswer('Dame un momento para escuchar la nota de voz') + +const flowDocumento = addKeyword(EVENTS.DOCUMENT) + .addAnswer('Documento PDF recibido') + +``` + +--- + ⚡ Dependiendo del tipo de proveedor que utlices puede que necesites pasar algunas configuracion adicional como @@ -71,7 +71,7 @@ Los proveedores disponibles hasta el momento son los siguientes: documentación para ir agregando más info -Es la pieza encargada de mantener el **"estado"** de una conversación, para mayor facilidad la libreria te proporcia diferentes conectores que se de adapten mejor a tu desarrollo +Es la pieza encargada de mantener el **"historial"** de una conversación, para mayor facilidad la libreria te proporcia diferentes conectores que se de adapten mejor a tu desarrollo ```js const MongoAdapter = require('@bot-whatsapp/database/mongo') From 25b803149d95756b41996d468427e7a8ef0f13c8 Mon Sep 17 00:00:00 2001 From: Leifer Mendez Date: Fri, 2 Jun 2023 18:27:53 +0200 Subject: [PATCH 03/17] docs: :memo: page `addAnswer` --- .../src/routes/docs/add-answers/index.mdx | 106 +++++++++++++++++- 1 file changed, 101 insertions(+), 5 deletions(-) diff --git a/packages/docs/src/routes/docs/add-answers/index.mdx b/packages/docs/src/routes/docs/add-answers/index.mdx index 23dcd3f64..54f44f12b 100644 --- a/packages/docs/src/routes/docs/add-answers/index.mdx +++ b/packages/docs/src/routes/docs/add-answers/index.mdx @@ -2,30 +2,126 @@ import Navigation from '../../../components/widgets/Navigation' # addAnswer -# ctx - Esta funcion se importa desde `@bot-whatsapp/bot` +Se encarga de responder al usuario, puedes responder un texto o archivo adjunto. + +```js + const { addKeyword } = require('@bot-whatsapp/bot') + + const flowWelcome = addKeyword('hola').addAnswer('Hola y bievendido! como puedo ayudarte') + +``` +El mensaje`'Hola y bievendido! como puedo ayudarte'` se enviara como un mensaje de whatsapp + + +### Enviar más de un mensaje + +```js + const { addKeyword } = require('@bot-whatsapp/bot') + + const flowWelcome = addKeyword('hola') + .addAnswer('Hola!') + .addAnswer('Bienvenido') + .addAnswer('¿Como puedo ayudarte?') + +``` +Se enviaran (3) tres mensajes por separados consecutivamente. `Hola` , `Bienvenido`, `¿Como puedo ayudarte?` + +--- + +### Enviar mensaje con salto de linea + ```js const { addKeyword } = require('@bot-whatsapp/bot') - const flowString = addKeyword('hola').addAnswer('Este mensaje se enviara 1 segundo despues', { + const flowWelcome = addKeyword('hola') + .addAnswer(['Hola!','Bienvenido','¿Como puedo ayudarte?']) + + +``` +Se enviara (1) un mensaje pero con saltos de linea + +--- + +### Retardo + +Enviar un mensaje con `1000ms = 1segundo` de espera + +```js + const { addKeyword } = require('@bot-whatsapp/bot') + + const flowString = addKeyword('hola').addAnswer('Este mensaje se enviara 1 segundo despues', { delay: 1000, }) + +``` + +--- + +### Enviar imagen, video, pdf: Url externa + +Puedes enviar archivos desde url externas publicas o desde archivos locales. + +```js + const { addKeyword } = require('@bot-whatsapp/bot') + + const flowString = addKeyword('hola').addAnswer('Este mensaje envia una imagen', { + media: 'https://i.imgur.com/0HpzsEm.png', + }) + + +``` +Esta imagen `https://i.imgur.com/0HpzsEm.png` se envia desde una URL externa. + +--- + +### Enviar imagen, video, pdf: locales + +Dependiendo de tus sistema operativo asegurate de colocar bien la ruta absoluta + +```js + const { addKeyword } = require('@bot-whatsapp/bot') + const flowString = addKeyword('hola').addAnswer('Este mensaje envia una imagen', { - media: 'https://i.imgur.com/0HpzsEm.png', + media: 'c:/ruta/imagen.png', //'c:\ruta\imagen.png' }) + +``` + +--- + +### Botones + +**NOTA** Los botones funcionan bien en Meta o Twilio. **En los proveedores gratuitos son inestables.** + +```js + const { addKeyword } = require('@bot-whatsapp/bot') + const flowString = addKeyword('hola').addAnswer('Este mensaje envia tres botones', { buttons: [{ body: 'Boton 1' }, { body: 'Boton 2' }, { body: 'Boton 3' }], }) - const flowString = addKeyword('hola').addAnswer('Este mensaje espera una respueta del usuario', { +``` + +--- + +### Capture + +En algunas ocaciones necesitamos esperar por la respuesta del usuario para ello usamos `capture` + +```js + const { addKeyword } = require('@bot-whatsapp/bot') + + const flowString = addKeyword('hola').addAnswer('¿Cual es tu email?', { capture: true, }) + ``` + --- Date: Sun, 4 Jun 2023 21:53:30 +0200 Subject: [PATCH 04/17] docs: :memo: gotoFlow doc --- .../docs/src/routes/docs/add-action/index.mdx | 23 ++++----- packages/docs/src/routes/docs/ctx/index.mdx | 45 ++++++++++++++++++ .../docs/src/routes/docs/fall-back/index.mdx | 4 +- .../src/routes/docs/flow-dynamic/index.mdx | 34 ++++++++++++++ .../docs/src/routes/docs/goto-flow/index.mdx | 47 ++++++++++++++++++- 5 files changed, 135 insertions(+), 18 deletions(-) create mode 100644 packages/docs/src/routes/docs/ctx/index.mdx diff --git a/packages/docs/src/routes/docs/add-action/index.mdx b/packages/docs/src/routes/docs/add-action/index.mdx index 3f45f5604..050aaefdf 100644 --- a/packages/docs/src/routes/docs/add-action/index.mdx +++ b/packages/docs/src/routes/docs/add-action/index.mdx @@ -2,26 +2,19 @@ import Navigation from '../../../components/widgets/Navigation' # addAction -Este fragmento de código define una constante llamada **`flujoBienvenida`**, que utiliza la función **`addKeyword()`** para agregar la palabra clave "hola" al Chat Bot de WhatsApp. Cuando el Bot recibe un mensaje que contiene la palabra "hola", se activa la acción definida en la función **`addAction()`**. - -La acción en sí es asíncrona y toma dos argumentos: **`ctx`** y **`{provider}`**. **`ctx`** es un objeto que contiene información sobre el mensaje recibido, como el remitente, el destinatario y el contenido del mensaje. **`{provider}`** es un objeto que se utiliza para enviar mensajes de respuesta. - -Dentro de la función **`addAction()`**, primero se extrae el ID del remitente del mensaje utilizando **`ctx.key.remoteJid`**. Luego, se define un conjunto de botones que se incluirán en un mensaje de plantilla. Cada botón tiene un índice, un texto de visualización y una acción. Los botones incluidos son un botón de enlace, un botón de llamada y un botón de respuesta rápida. - -Después de definir los botones, se crea un objeto de mensaje de plantilla que contiene un texto de saludo y un pie de página, así como los botones definidos anteriormente. - -A continuación, se utiliza el objeto **`{provider}`** para enviar el mensaje de plantilla al remitente del mensaje original utilizando el método **`sendMessage()`**. - -Por último, se imprime un mensaje de registro en la consola que indica que se ha enviado el mensaje, y luego se retorna. - -En resumen, **`addAction()`**es una función útil para definir acciones que se activan cuando se recibe un mensaje de WhatsApp específico. La función **`action`** puede realizar diversas tareas y acceder a los métodos y propiedades. En general, **`addAction()`**te permite crear Chat Bots de WhatsApp personalizados que pueden interactuar con los usuarios de una manera programática. Puedes definir acciones específicas que se activan cuando se recibe un mensaje específico, lo que te permite crear una variedad de flujos de conversación y respuestas automatizadas. Con **`addAction()`**y otras funciones en la biblioteca puedes personalizar completamente la funcionalidad de tu Bot de WhatsApp para satisfacer las necesidades de tu proyecto o negocio. - -Esta funcion se importa desde `@bot-whatsapp/bot` +Es una función útil para definir acciones que se activan cuando se recibe un mensaje de WhatsApp específico. +La función action puede realizar diversas tareas y acceder a los métodos y propiedades. En general, **addAction()** te permite crear Chat Bots de WhatsApp personalizados que pueden interactuar con los usuarios de una manera programática. +Puedes definir acciones específicas que se activan cuando se recibe un mensaje específico, lo que te permite crear una variedad de flujos de conversación y respuestas automatizadas. ```js const { addKeyword } = require('@bot-whatsapp/bot') const flowPrincipal = addKeyword(['hola', 'alo']) .addAnswer(['Hola, bienvenido a mi tienda', '¿Como puedo ayudarte?']) + .addAction(async(ctx) => { + + console.log(`Enviar un mail con el con el numero de la persona: ${ctx.from}`) + + }) .addAnswer(['Tengo:', 'Zapatos', 'Bolsos', 'etc ...']) ``` diff --git a/packages/docs/src/routes/docs/ctx/index.mdx b/packages/docs/src/routes/docs/ctx/index.mdx new file mode 100644 index 000000000..580512d3b --- /dev/null +++ b/packages/docs/src/routes/docs/ctx/index.mdx @@ -0,0 +1,45 @@ +import Navigation from '../../../components/widgets/Navigation' + +# ctx + +Esta funcion se encuentra dentro de `addAnswer` o `addAction` + +Se encarga de responder al usuario, puedes responder un texto o archivo adjunto. + +```js + const { addKeyword } = require('@bot-whatsapp/bot') + + const flowWelcome = addKeyword('hola').addAnswer('¿Como es tu mail?',null, async (ctx) => { + console.log(ctx) + }) + +``` + +### ¿Que cosas tiene el ctx? + +Dentro del ctx puedes conseguir algunas de las siguientes propiedades + +```js + const { addKeyword } = require('@bot-whatsapp/bot') + + const flowWelcome = addKeyword('hola').addAnswer('¿Como es tu mail?',null, async (ctx) => { + console.log(ctx) + + const numeroDeWhatsapp = ctx.from + const mensajeRecibido = ctx.body + + }) + +``` + +Tambien recomiendao que con `console.log(ctx)` puedes ver todas las propiedades que vienen incluidas, muchas de ellas pueden ser propiedades propias del `provider` + + +--- + + diff --git a/packages/docs/src/routes/docs/fall-back/index.mdx b/packages/docs/src/routes/docs/fall-back/index.mdx index 42a689826..2e93574d6 100644 --- a/packages/docs/src/routes/docs/fall-back/index.mdx +++ b/packages/docs/src/routes/docs/fall-back/index.mdx @@ -2,9 +2,9 @@ import Navigation from '../../../components/widgets/Navigation' # fallBack -La función **FallBack()** es una función que se utiliza para repetir el último mensaje del flujo en caso de que el usuario no proporcione una respuesta válida. Es decir, si el usuario ingresa un mensaje que no coincide con ninguna palabra clave o respuesta esperada, el Bot puede llamar a la función **FallBack()** para volver a enviar el último mensaje y esperar una respuesta válida. +La función **fallBack()** es una función que se utiliza para repetir el último mensaje del flujo en caso de que el usuario no proporcione una respuesta válida. Es decir, si el usuario ingresa un mensaje que no coincide con ninguna palabra clave o respuesta esperada, el Bot puede llamar a la función **fallBack()** para volver a enviar el último mensaje y esperar una respuesta válida. -La función **FallBack()** se puede llamar dentro del método **addAnswer()** del flujo. Para ello, simplemente se llama a la función **FallBack()** dentro de una condición que comprueba si la respuesta del usuario es válida o no. Por ejemplo: +La función **fallBack()** se puede llamar dentro del método **addAnswer()** del flujo. Para ello, simplemente se llama a la función **fallBack()** dentro de una condición que comprueba si la respuesta del usuario es válida o no. Por ejemplo: ```js const { addKeyword } = require('@bot-whatsapp/bot') diff --git a/packages/docs/src/routes/docs/flow-dynamic/index.mdx b/packages/docs/src/routes/docs/flow-dynamic/index.mdx index e678481ba..4094c8271 100644 --- a/packages/docs/src/routes/docs/flow-dynamic/index.mdx +++ b/packages/docs/src/routes/docs/flow-dynamic/index.mdx @@ -16,6 +16,40 @@ const flowString = addKeyword('ver categorias') }) ``` +### Más ejemplos de flowDynamic + +```js +const { addKeyword } = require('@bot-whatsapp/bot') + +const flowString = addKeyword('ver categorias') + .addAnswer('Estas son las categorías disponibles:', null, async (ctx, {flowDynamic}) => { + await flowDynamic('Enviar un mensaje text') + + const listaDeArticulos = [ + { + name:'Item 1' + }, + { + name:'Item 2' + }, + { + name:'Item 3' + } + ] + + const mapeoDeLista = listaDeArticulos.map((item) => item.name).join(', ') //Item 1, Item 2, Item 3 + + await flowDynamic(mapeoDeLista) + + await flowDynamic({body:'Tambien puedes enviar un mensaje de esta manera'}) + + // Enviar una imagen o pdf o etc + + await flowDynamic({media:'https://i.imgur.com/0HpzsEm.png'}) + + }) +``` + --- { + + const numero = ctx.from + + console.log('consultando en base de datos nombre gracias al numero...') + const ejemploDB = { + name:'Leifer' + } + + await flowDynamic(`Como estas ${ejemploDB.name}, un gusto tenerte nuvamente`) +}) + +const flujoUsuariosNORegistrados = addKeyword('USUARIOS_NO_REGISTRADOS') +.addAnswer('Veo que es tu primera vez por aqui') +.addAnswer('¿Cual es tu email?',{capture:true},async(ctx, {flowDynamic, gotoFlow}) => { + + const numero = ctx.from + + console.log('registramos en base de datos el numero...') + + await flowDynamic(`Ya te registramos..`) + await gotoFlow(flujoUsuariosRegistrados) +}) + +const flowBienvenida = addKeyword('hola') +.addAnswer('Bievenido!', null, async (ctx,{gotoFlow}) => { + + const numero = ctx.from + console.log('consultando en base de datos si existe el numero registrado....') + + const ifExist = true + if(ifExist){ + // Si existe lo enviamos al flujo de regostrados.. + gotoFlow(flujoUsuariosRegistrados) + }else{ + // Si NO existe lo enviamos al flujo de NO registrados.. + gotoFlow(flujoUsuariosNORegistrados) + } + +}) + ``` From fff26d5634f7a57b5993fcfa907ea4a698776fed Mon Sep 17 00:00:00 2001 From: ozzyoss77 Date: Wed, 21 Jun 2023 22:04:19 -0400 Subject: [PATCH 05/17] Adding @bot/provider documentation: meta/twilio/baileys/venom/wppconnect/wweb --- packages/docs/package.json | 7 +- .../routes/docs/provider-baileys/index.mdx | 148 +++++++++++++ .../src/routes/docs/provider-meta/index.mdx | 202 ++++++++++++++++++ .../src/routes/docs/provider-twilio/index.mdx | 142 ++++++++++++ .../src/routes/docs/provider-venom/index.mdx | 61 ++++++ .../routes/docs/provider-wppconnect/index.mdx | 56 +++++ .../src/routes/docs/provider-wweb/index.mdx | 56 +++++ .../src/routes/docs/providers/meta/index.mdx | 4 +- packages/docs/src/services/github.ts | 19 +- packages/docs/src/services/opencollective.ts | 13 ++ 10 files changed, 697 insertions(+), 11 deletions(-) create mode 100644 packages/docs/src/routes/docs/provider-baileys/index.mdx create mode 100644 packages/docs/src/routes/docs/provider-meta/index.mdx create mode 100644 packages/docs/src/routes/docs/provider-twilio/index.mdx create mode 100644 packages/docs/src/routes/docs/provider-venom/index.mdx create mode 100644 packages/docs/src/routes/docs/provider-wppconnect/index.mdx create mode 100644 packages/docs/src/routes/docs/provider-wweb/index.mdx diff --git a/packages/docs/package.json b/packages/docs/package.json index 311902b21..7c38118e0 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -23,6 +23,7 @@ "@builder.io/qwik": "0.16.1", "@builder.io/qwik-city": "0.0.128", "@builder.io/qwik-react": "0.2.1", + "@docsearch/react": "^3.3.3", "@emotion/react": "11.10.4", "@emotion/styled": "11.10.4", "@fontsource/inter": "^4.5.14", @@ -53,11 +54,9 @@ "subfont": "^6.12.2", "tailwindcss": "^3.1.8", "typescript": "4.8.4", - "vite": "3.2.4", + "vite": "^3.2.4", "vite-imagetools": "^4.0.11", - "vite-tsconfig-paths": "3.6.0", - "@docsearch/react": "^3.3.3" - + "vite-tsconfig-paths": "3.6.0" }, "engines": { "node": ">=17.0.0" diff --git a/packages/docs/src/routes/docs/provider-baileys/index.mdx b/packages/docs/src/routes/docs/provider-baileys/index.mdx new file mode 100644 index 000000000..2d1650cfc --- /dev/null +++ b/packages/docs/src/routes/docs/provider-baileys/index.mdx @@ -0,0 +1,148 @@ +import Alert from '../../../components/widgets/Alert' +import Navigation from '../../../components/widgets/Navigation' + +# Baileys + + + ⚡ Baileys es un proveedor gratuito de WhatsApp que se ejecuta mediante Whatsapp Web. No es necesario un registro para usarlo, pero tiene algunas limitaciones. + - El uso de botones y listas no es compatible (en algunos dispositivos puede llegar a visualizarse pero en general falla). + - En el caso de mensajeria masiva debe utilizarse con precaución ya que puede ser bloqueado por WhatsApp. + + +--- + +## Documentación Baileys + +`require('@bot-whatsapp/provider/baileys')` + +Aca encontrarás la documentación en el repositorio oficial de [Baileys](https://github.com/whiskeysockets/Baileys). + +--- + +## Funciones usando el proveedor Baileys + +### Baileys: +### Enviar mensaje de texto + +```js +const flowPrincipal = addKeyword('hola') + .addAnswer( + 'Aqui va un mensaje', + { capture: true}, + async (ctx, {provider}) => { + await provider.sendText(ctx.from@s.whatsapp.net, 'mensaje de texto') + // el número de telefono se envía en este formato 12345678901@s.whatsapp.net + } + ) +``` +--- + +## Funciones usando la librería + +### Baileys: +### Enviar archivo media + +Uso de tipo genérico + +```js +const flowPrincipal = addKeyword('hola') + .addAnswer( + 'Aqui va un mensaje', + { capture: true}, + async (ctx, {provider}) => { + await provider.sendMedia(ctx.from@s.whatsapp.net, 'media url' 'mensaje de texto') + // el número de telefono se envía en este formato 12345678901@s.whatsapp.net + } + ) +``` +--- + +### Baileys: +### Enviar imagen + +```js +const flowPrincipal = addKeyword('hola') + .addAnswer( + 'Aqui va un mensaje', + { capture: true}, + async (ctx, {provider}) => { + await provider.sendImage(ctx.from@s.whatsapp.net, 'imagen url' 'mensaje de texto') + // el número de telefono se envía en este formato 12345678901@s.whatsapp.net + } + ) +``` +--- + +### Baileys: +### Enviar video + +```js +const flowPrincipal = addKeyword('hola') + .addAnswer( + 'Aqui va un mensaje', + { capture: true}, + async (ctx, {provider}) => { + await provider.sendVideo(ctx.from@s.whatsapp.net, 'video url' 'mensaje de texto') + // el número de telefono se envía en este formato 12345678901@s.whatsapp.net + } + ) +``` +--- + +### Baileys: +### Enviar audio + +```js +const flowPrincipal = addKeyword('hola') + .addAnswer( + 'Aqui va un mensaje', + { capture: true}, + async (ctx, {provider}) => { + await provider.sendAudio(ctx.from@s.whatsapp.net, 'audio url') + // el número de telefono se envía en este formato 12345678901@s.whatsapp.net + } + ) +``` +--- + +### Baileys: +### Enviar ubicación + +```js +const flowPrincipal = addKeyword('hola') + .addAnswer( + 'Aqui va un mensaje', + { capture: true}, + async (ctx, {provider}) => { + await provider.sendLocation(ctx.from@s.whatsapp.net, 'audio url') + // el número de telefono se envía en este formato 12345678901@s.whatsapp.net + } + ) +``` +--- + +## Captar eventos + +La librería tiene implementado un sistema de eventos que permite captar los mensajes que envía el usuario, para ello se debe usar el método `EVENTS` de la siguiente manera: + +### EVENTS.WELCOME + +```js +const flowPrincipal = addKeyword(EVENTS.WELCOME) + addAnswer( + 'Aqui va un mensaje', + { capture: true}, + null, + null + ) + +``` + + + + diff --git a/packages/docs/src/routes/docs/provider-meta/index.mdx b/packages/docs/src/routes/docs/provider-meta/index.mdx new file mode 100644 index 000000000..0112d3856 --- /dev/null +++ b/packages/docs/src/routes/docs/provider-meta/index.mdx @@ -0,0 +1,202 @@ +import Alert from '../../../components/widgets/Alert' +import Navigation from '../../../components/widgets/Navigation' + +# Meta + + + ⚡ Para poder usar la API oficial de Meta de manera profesional debes tener en cuenta lo siguiente: + + - Deberas verificar tu negocio y tu aplicación con documentos oficiales dependiendo de tu pais. + - Deberas registrar un número de teléfono oficial de tu negocio que no este vinculado con ninguna cuenta de WhatsApp ya se normal o Bussines (Meta proporciona un número de pruebas gratuito). + - Meta brinda 1000 conversaciones gratis iniciadas por el cliente hacia el bot y 250 conversaciones gratis iniciadas por el bot hacia el cliente, después de eso deberás pagar por cada conversación (los costos varian según tu país o region). + + +--- +## Meta: Configuración + +En este apartado esta una guía de como iniciar la configuración de Meta. Puedes encontrar los [detalles aquí](/docs/providers/meta) + +--- +## Funciones usando el provider + +### Meta: Enviar mensaje + +```js +const flowMensaje = addKeyword('hola') + .addAnswer( + 'Aqui va un mensaje', + { + capture: true, + }, + async (ctx, {provider}) => { + await provider.sendtext(ctx.from, 'mensaje') + //==> ctx.from puede ser reemplazado por un número de teléfono + //ej: 59170000000, donde el 591 es el código de país y el 70000000 es el número de teléfono + } + ) +``` +### Meta: Enviar media (imagen, pdf, audio, video) + +```js +const flowMensaje = addKeyword('hola') + .addAnswer( + 'Aqui va un mensaje', + { + capture: true, + }, + async (ctx, {provider}) => { + await sendMedia = async (ctx.from, 'mensaje', 'url de la media') + } + ) +``` +### Meta: Enviar botones sin provider (maximo de 3 botones) + +```js +// metodo sin uso del provider +const flowMensaje = addKeyword('hola') + .addAnswer( + 'Aqui va un mensaje', + { + capture: true, + buttons: [ + {body: 'opcion 1'}, + {body: 'opcion 2'}, + {body: 'opcion 3'}, + ] + }, + ) +``` +Para casos específicos donde se necesite enviar botones desde el provider revisar la ruta "node_modules/@bot-whatsapp/provider/meta/index.cjs", dentro el proyecto. + +### Meta: Enviar lista metodo 1 +```js +const flowMensaje = addKeyword('hola') + .addAnswer( + 'Aqui va un mensaje', + { + capture: true + }, + async (ctx, {provider}) => { + const list = { + "header": { + "type": "text", + "text": "" + }, + "body": { + "text": "" + }, + "footer": { + "text": "" + }, + "action": { + "button": "", + "sections": [ + { + "title": "", + "rows": [ + { + "id": "", + "title": "", + "description": "" + }, + { + "id": "", + "title": "", + "description": "" + } + ] + }, + { + "title": "", + "rows": [ + { + "id": "", + "title": "", + "description": "" + }, + { + "id": "", + "title": "", + "description": "" + } + ] + } + ] + } + } + await provider.sendLists(ctx.from, list) + } + ) +``` +### Meta: Enviar lista metodo 2 +```js +const flowServices = addKeyword('hola') + .addAnswer( + 'Aqui va un mensaje', + { + capture: true + }, + async (ctx, {provider}) => { + const headerText = 'HEADER_TEXT + const bodyText = 'BODY_TEXT' + const footerText = 'FOOTER_TEXT' + const buttonList = 'BUTTON_LIST' + const listParams = [ + { + title: 'TITLE_1', + rows: [ + { + id: 'ID_1', + title: 'TITLE_1', + description: 'DESCRIPTION_1' + }, + { + id: 'ID_2', + title: 'TITLE_2', + description: 'DESCRIPTION_2' + }, + { + id: 'ID_3', + title: 'TITLE_3', + description: 'DESCRIPTION_3' + } + ] + }, + { + title: 'TITLE_2', + rows: [ + { + id: 'ID_1', + title: 'TITLE_1', + description: 'DESCRIPTION_1' + }, + { + id: 'ID_2', + title: 'TITLE_2', + description: 'DESCRIPTION_2' + }, + { + id: 'ID_3', + title: 'TITLE_3', + description: 'DESCRIPTION_3' + } + ] + } + ] + await provider.sendList(ctx.from, headerText, bodyText, footerText, buttonList ,listParams) + } + ) +``` +--- +## Meta Deploy + +En este apartado esta una guía de como iniciar el deploy de Meta. Puedes encontrar los [detalles aquí](https://crhistianriverin.notion.site/Deploy-de-tu-chatbot-con-la-API-de-Meta-9ba0f1a800aa4df381c87a79dbf5a42a?pvs=4) + +--- + + diff --git a/packages/docs/src/routes/docs/provider-twilio/index.mdx b/packages/docs/src/routes/docs/provider-twilio/index.mdx new file mode 100644 index 000000000..f9e412d71 --- /dev/null +++ b/packages/docs/src/routes/docs/provider-twilio/index.mdx @@ -0,0 +1,142 @@ +import Alert from '../../../components/widgets/Alert' +import Navigation from '../../../components/widgets/Navigation' + +# Twilio + + + ⚡ Twilio te brinda un número de teléfono y funciona en base a créditos en su plataforma, ten en consideración que Twilio al ser un partner de Meta supondra costos un poco mas elevados en comparación. + + +--- + +## Twilio: Configuración + +En este apartado esta una guía de como iniciar la configuración de Twilio. Puedes encontrar los [detalles aquí](/docs/providers/twilio) + +--- + +## Funciones usando el provider + +### Twilio: Enviar mensaje + +```js +const flowMensaje = addKeyword('hola') + .addAnswer( + 'Aqui va un mensaje', + { + capture: true, + }, + async (ctx, {provider}) => { + await provider.sendtext(ctx.from, 'mensaje') + //==> ctx.from puede ser reemplazado por un número de teléfono + //ej: 59170000000, donde el 591 es el código de país y el 70000000 es el número de teléfono + } + ) +``` +### Twilio: Enviar media (imagen, pdf, audio, video) + +```js +const flowMensaje = addKeyword('hola') + .addAnswer( + 'Aqui va un mensaje', + { + capture: true, + }, + async (ctx, {provider}) => { + await sendMedia = async (ctx.from, 'mensaje', 'url de la media') + } + ) +``` +### Twilio: Enviar botones sin provider (maximo de 3 botones) + +```js +// metodo sin uso del provider +const flowMensaje = addKeyword('hola') + .addAnswer( + 'Aqui va un mensaje', + { + capture: true, + buttons: [ + {body: 'opcion 1'}, + {body: 'opcion 2'}, + {body: 'opcion 3'}, + ] + }, + ) +``` +Para casos específicos donde se necesite enviar botones desde el provider revisar la ruta "node_modules/@bot-whatsapp/provider/meta/index.cjs", dentro el proyecto. + +### Twilio: Enviar lista metodo 1 +```js +const flowMensaje = addKeyword('hola') + .addAnswer( + 'Aqui va un mensaje', + { + capture: true + }, + async (ctx, {provider}) => { + const list = { + "header": { + "type": "text", + "text": "" + }, + "body": { + "text": "" + }, + "footer": { + "text": "" + }, + "action": { + "button": "", + "sections": [ + { + "title": "", + "rows": [ + { + "id": "", + "title": "", + "description": "" + }, + { + "id": "", + "title": "", + "description": "" + } + ] + }, + { + "title": "", + "rows": [ + { + "id": "", + "title": "", + "description": "" + }, + { + "id": "", + "title": "", + "description": "" + } + ] + } + ] + } + } + await provider.sendLists(ctx.from, list) + } + ) +``` +--- + +## Funciones de la librería + +Puede usarse las mismas funciones que usan los proveedores [gratuitos](/docs/provider-baileys). + +--- + + diff --git a/packages/docs/src/routes/docs/provider-venom/index.mdx b/packages/docs/src/routes/docs/provider-venom/index.mdx new file mode 100644 index 000000000..5526f3d55 --- /dev/null +++ b/packages/docs/src/routes/docs/provider-venom/index.mdx @@ -0,0 +1,61 @@ +import Alert from '../../../components/widgets/Alert' +import Navigation from '../../../components/widgets/Navigation' + +# Venom + + + ⚡ Venom es un proveedor gratuito de WhatsApp que se ejecuta mediante Whatsapp Web. No es necesario un registro para usarlo, pero tiene algunas limitaciones. + - El uso de botones y listas no es compatible (en algunos dispositivos puede llegar a visualizarse pero en general falla). + - En el caso de mensajeria masiva debe utilizarse con precaución ya que puede ser bloqueado por WhatsApp. + - Venom tiene una capa de pago que cuesta $ 50 USD por mes, entre algunas de las opciones mas llamativas esta la multisesión, es decir, poder levantar varias instancias del bot, la capa de pago tiene funciones que no estan contempladas en esta librería. + - ⚠️ Venom hace uso de puppeter para levantar instancias de WhatsApp Web, por lo que es necesario tener instalado Google Chrome en el servidor donde se ejecutará el bot en caso de hacer deploy del proyecto. + + +--- + +## Documentación Venom + +`require('@bot-whatsapp/provider/venom')` + +Aca encontrarás la documentación en el repositorio oficial de [Venom](https://github.com/orkestral/venom). + +--- + +## Funciones usando el proveedor Venom + +### Venom: +### Enviar mensaje de texto + +```js +const flowPrincipal = addKeyword('hola') + .addAnswer( + 'Aqui va un mensaje', + { capture: true}, + async (ctx, {provider}) => { + await provider.sendText(ctx.from@c.us, 'mensaje de texto') + // el número de telefono se envía en este formato 12345678901@c.us + } + ) +``` +--- + +### Venom: +### Enviar encuesta (poll) + +Pendiente de implementación, puedes ver el ejemplo de la documentación de Venom e implementar la función por ti mismo [click aqui](https://github.com/orkestral/venom#here-chatid-could-be-phonenumbercus-or-phonenumber-groupidgus). + + + ⚠️ Venom tiene una gran variedad de funciones que aún no estan implementadas en la librería, puedes realizar tu aporte al proyecto mapeando las funciones que aún no estan implementadas. + + +--- +## Funciones usando la librería + +Puede usarse las mismas funciones que se usan con el proveedor de [Baileys](/docs/provider-baileys). + + diff --git a/packages/docs/src/routes/docs/provider-wppconnect/index.mdx b/packages/docs/src/routes/docs/provider-wppconnect/index.mdx new file mode 100644 index 000000000..ec4ba2591 --- /dev/null +++ b/packages/docs/src/routes/docs/provider-wppconnect/index.mdx @@ -0,0 +1,56 @@ +import Alert from '../../../components/widgets/Alert' +import Navigation from '../../../components/widgets/Navigation' + +# WPPConnect + + + ⚡ WPPConnect es un proveedor gratuito de WhatsApp que se ejecuta mediante Whatsapp Web. No es necesario un registro para usarlo, pero tiene algunas limitaciones. + - El uso de botones y listas no es compatible (en algunos dispositivos puede llegar a visualizarse pero en general falla). + - En el caso de mensajeria masiva debe utilizarse con precaución ya que puede ser bloqueado por WhatsApp. + - ⚠️ WPPConnect hace uso de puppeter para levantar instancias de WhatsApp Web, por lo que es necesario tener instalado Google Chrome en el servidor donde se ejecutará el bot en caso de hacer deploy del proyecto. + + +--- + +## Documentación WPPConnect + +`require('@bot-whatsapp/provider/wppconnect')` + +Aca encontrarás la documentación en el repositorio oficial de [WPPConnect](https://github.com/wppconnect-team/wppconnect). + +--- + +## Funciones usando el proveedor WPPConnect + +### WPPConnect: +### Enviar mensaje de texto + +```js +const flowPrincipal = addKeyword('hola') + .addAnswer( + 'Aqui va un mensaje', + { capture: true}, + async (ctx, {provider}) => { + await provider.sendText(ctx.from@c.us, 'mensaje de texto') + // el número de telefono se envía en este formato 12345678901@c.us + } + ) +``` +--- + + + ⚠️ WPPConnect tiene una gran variedad de funciones que aún no estan implementadas en la librería, puedes realizar tu aporte al proyecto mapeando las funciones que aún no estan implementadas [click aqui](https://wppconnect.io/wppconnect/pages/getting-started/basic-functions.html). + + +--- +## Funciones usando la librería + +Puede usarse las mismas funciones que se usan con el proveedor de [Baileys](/docs/provider-baileys). + + + diff --git a/packages/docs/src/routes/docs/provider-wweb/index.mdx b/packages/docs/src/routes/docs/provider-wweb/index.mdx new file mode 100644 index 000000000..0458c52cf --- /dev/null +++ b/packages/docs/src/routes/docs/provider-wweb/index.mdx @@ -0,0 +1,56 @@ +import Alert from '../../../components/widgets/Alert' +import Navigation from '../../../components/widgets/Navigation' + +# Whatsapp-web.js + + + ⚡ Whatsapp-web.js es un proveedor gratuito de WhatsApp que se ejecuta mediante Whatsapp Web. No es necesario un registro para usarlo, pero tiene algunas limitaciones. + - El uso de botones y listas no es compatible (en algunos dispositivos puede llegar a visualizarse pero en general falla). + - En el caso de mensajeria masiva debe utilizarse con precaución ya que puede ser bloqueado por WhatsApp. + - ⚠️ Whatsapp-web.js hace uso de puppeter para levantar instancias de WhatsApp Web, por lo que es necesario tener instalado Google Chrome en el servidor donde se ejecutará el bot en caso de hacer deploy del proyecto. + + +--- + +## Documentación Whatsapp-web.js + +`require('@bot-whatsapp/provider/web-whatsapp')` + +Aca encontrarás la documentación en el repositorio oficial de [Whatsapp-web.js](https://github.com/pedroslopez/whatsapp-web.js). + +--- + +## Funciones usando el proveedor Whatsapp-web.js + +### WPPConnect: +### Enviar mensaje de texto + +```js +const flowPrincipal = addKeyword('hola') + .addAnswer( + 'Aqui va un mensaje', + { capture: true}, + async (ctx, {provider}) => { + await provider.sendText(ctx.from@c.us, 'mensaje de texto') + // el número de telefono se envía en este formato 12345678901@c.us + } + ) +``` +--- + + + ⚠️ Whatsapp-web.js tiene una gran variedad de funciones que aún no estan implementadas en la librería, puedes realizar tu aporte al proyecto mapeando las funciones que aún no estan implementadas [click aqui](https://github.com/pedroslopez/whatsapp-web.js/blob/main/example.js). + + +--- +## Funciones usando la librería + +Puede usarse las mismas funciones que se usan con el proveedor de [Baileys](/docs/provider-baileys). + + + diff --git a/packages/docs/src/routes/docs/providers/meta/index.mdx b/packages/docs/src/routes/docs/providers/meta/index.mdx index c0051fe34..51714d2a0 100644 --- a/packages/docs/src/routes/docs/providers/meta/index.mdx +++ b/packages/docs/src/routes/docs/providers/meta/index.mdx @@ -151,7 +151,7 @@ Si ya tienes desplegado tu bot en un servidor tienes que obtener la IP publica o diff --git a/packages/docs/src/services/github.ts b/packages/docs/src/services/github.ts index 6ef11864b..1a06380fb 100644 --- a/packages/docs/src/services/github.ts +++ b/packages/docs/src/services/github.ts @@ -12,8 +12,17 @@ export const fetchGithub = async (token: string) => { }, }) const listUsers = await data.json() - return listUsers.map((u: any) => ({ - ...u, - avatar_url: `${u.avatar_url}&s=80`, - })) -} + return listUsers.map((u: any) => ({ + ...u, + avatar_url: `${u.avatar_url}&s=80`, + })) + // try { + // const listUsers = await data.json() + // return listUsers.map((u: any) => ({ + // ...u, + // avatar_url: `${u.avatar_url}&s=80`, + // })) + // } catch (error) { + // return [] + // } +} \ No newline at end of file diff --git a/packages/docs/src/services/opencollective.ts b/packages/docs/src/services/opencollective.ts index 55e7e758d..867a94e26 100644 --- a/packages/docs/src/services/opencollective.ts +++ b/packages/docs/src/services/opencollective.ts @@ -2,6 +2,8 @@ * GET API from OpenCollective * @returns */ +// envolver listUsers en un try catch y devolver un array vacio en caso de error + export const fetchOpenCollective = async () => { const data = await fetch(`https://opencollective.com/bot-whatsapp/members/users.json?limit=22&offset=0`, { method: 'GET', @@ -13,4 +15,15 @@ export const fetchOpenCollective = async () => { login: u.name, id: u.MemberId, })) + // try { + // const listUsers = await data.json() + // return listUsers.map((u: any) => ({ + // html_url: u.profile, + // avatar_url: u.image ?? 'https://i.imgur.com/HhiYKwN.png', + // login: u.name, + // id: u.MemberId, + // })) + // } catch (error) { + // return [] + // } } From 260c2406fc31fe8bacaa542dbc959e4618b82273 Mon Sep 17 00:00:00 2001 From: Andres Aya Date: Sun, 13 Aug 2023 11:50:06 -0500 Subject: [PATCH 06/17] =?UTF-8?q?fix(provider):=20=F0=9F=90=9BFixed=20get?= =?UTF-8?q?=20state=20message=20poll?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/provider/src/baileys/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/provider/src/baileys/index.js b/packages/provider/src/baileys/index.js index d0dd7d596..1d6e318e1 100644 --- a/packages/provider/src/baileys/index.js +++ b/packages/provider/src/baileys/index.js @@ -58,8 +58,8 @@ class BaileysProvider extends ProviderClass { this.store = makeInMemoryStore({ loggerBaileys }) this.store.readFromFile(`${NAME_DIR_SESSION}/baileys_store.json`) setInterval(() => { - const path = `${this.NAME_DIR_SESSION}/baileys_store.json` - if (existsSync(path)) { + const path = `${NAME_DIR_SESSION}/baileys_store.json` + if (existsSync(NAME_DIR_SESSION)) { this.store.writeToFile(path) } }, 10_000) From 8f90ee8f2d8ccb396423af375819431ca772e486 Mon Sep 17 00:00:00 2001 From: Andres Aya Date: Sun, 13 Aug 2023 11:58:35 -0500 Subject: [PATCH 07/17] =?UTF-8?q?fix(provider):=20=F0=9F=90=9BFixed=20=20B?= =?UTF-8?q?aileys=20body=20Undefined=20sometimes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/provider/src/baileys/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/provider/src/baileys/index.js b/packages/provider/src/baileys/index.js index d0dd7d596..3607dcead 100644 --- a/packages/provider/src/baileys/index.js +++ b/packages/provider/src/baileys/index.js @@ -168,6 +168,9 @@ class BaileysProvider extends ProviderClass { func: ({ messages, type }) => { if (type !== 'notify') return const [messageCtx] = messages + + if (messageCtx?.message?.protocolMessage?.type === 'EPHEMERAL_SETTING') return + let payload = { ...messageCtx, body: messageCtx?.message?.extendedTextMessage?.text ?? messageCtx?.message?.conversation, From 0f792da5bdc668e82b31a061204ca1756614196d Mon Sep 17 00:00:00 2001 From: Andres Aya Date: Sat, 19 Aug 2023 07:47:57 -0500 Subject: [PATCH 08/17] =?UTF-8?q?fix(provider):=20=F0=9F=90=9BFixed=20get?= =?UTF-8?q?=20data=20mysql=20for=20baileys?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/database/src/mysql/index.js | 2 +- packages/provider/src/baileys/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/database/src/mysql/index.js b/packages/database/src/mysql/index.js index 2c681e1cb..dceadb4c9 100644 --- a/packages/database/src/mysql/index.js +++ b/packages/database/src/mysql/index.js @@ -27,7 +27,7 @@ class MyslAdapter { getPrevByNumber = (from) => new Promise((resolve, reject) => { - const sql = `SELECT * FROM history WHERE phone=${from} ORDER BY id DESC` + const sql = `SELECT * FROM history WHERE phone='${from}' ORDER BY id DESC` this.db.query(sql, (error, rows) => { if (error) { reject(error) diff --git a/packages/provider/src/baileys/index.js b/packages/provider/src/baileys/index.js index 1d6e318e1..52c4e7cc3 100644 --- a/packages/provider/src/baileys/index.js +++ b/packages/provider/src/baileys/index.js @@ -238,7 +238,7 @@ class BaileysProvider extends ProviderClass { let payload = { ...messageCtx, body: pollMessage.find((poll) => poll.voters.length > 0)?.name || '', - from: baileyCleanNumber(key.remoteJid), + from: baileyCleanNumber(key.remoteJid, true), pushName: messageOriginal?.pushName, broadcast: messageOriginal?.broadcast, messageTimestamp: messageOriginal?.messageTimestamp, From e5d5f366fad42a2a46dafcc466fcd249ebdc82fb Mon Sep 17 00:00:00 2001 From: Leifer Mendez Date: Sun, 27 Aug 2023 12:14:52 +0200 Subject: [PATCH 09/17] chore: bug leak --- __test__/1.1.6-case.test.js | 2 +- package.json | 2 +- packages/bot/core/core.class.js | 26 +++++++++++------ packages/bot/package.json | 2 +- packages/bot/utils/hash.js | 10 ++++++- packages/bot/utils/queue.js | 51 +++++++++++++++++++++++++++++---- 6 files changed, 76 insertions(+), 17 deletions(-) diff --git a/__test__/1.1.6-case.test.js b/__test__/1.1.6-case.test.js index 1a29c9d85..f9615377b 100644 --- a/__test__/1.1.6-case.test.js +++ b/__test__/1.1.6-case.test.js @@ -87,7 +87,7 @@ suiteCase(`Debe retornar un mensaje resumen`, async ({ database, provider }) => assert.is('Tu datos son:', getHistory[7]) assert.is('Nombre: Leifer Edad: 90', getHistory[8]) assert.is('🤖🤖 Gracias por tu participacion', getHistory[9]) - assert.is('Maria', getHistory[10]) + assert.is('Maria', getHistory) assert.is('Gracias por tu nombre!', getHistory[11]) assert.is('¿Cual es tu edad?', getHistory[12]) assert.is('100', getHistory[13]) diff --git a/package.json b/package.json index 56ba04801..e2ed13077 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "build": "pnpm run cli:rollup && pnpm run bot:rollup && pnpm run provider:rollup && pnpm run database:rollup && pnpm run contexts:rollup && pnpm run create-bot-whatsapp:rollup && pnpm run portal:rollup", "copy.lib": "node ./scripts/move.js", "test.unit": "node ./node_modules/uvu/bin.js packages test", - "test.e2e": "node ./node_modules/uvu/bin.js __test__ ", + "test.e2e": "node ./node_modules/uvu/bin.js __test__ 1.1.6-case.test.js", "test.coverage": "node ./node_modules/c8/bin/c8.js npm run test.unit", "test": "npm run test.coverage", "cli": "node ./packages/cli/bin/cli.js", diff --git a/packages/bot/core/core.class.js b/packages/bot/core/core.class.js index 9a32c9459..895ae732a 100644 --- a/packages/bot/core/core.class.js +++ b/packages/bot/core/core.class.js @@ -8,6 +8,7 @@ const Queue = require('../utils/queue') const { LIST_REGEX } = require('../io/events') const SingleState = require('../context/state.class') const GlobalState = require('../context/globalState.class') +const { generateTime } = require('../utils/hash') const logger = new Console({ stdout: createWriteStream(`${process.cwd()}/core.class.log`), @@ -185,16 +186,20 @@ class CoreClass { if (delayMs) { await delay(delayMs) // Esperar según el retraso configurado } - + this.queuePrincipal.setFingerTime(from, generateTime()) logger.log(`[sendQueue_A]: `, ctxMessage) try { - await this.queuePrincipal.enqueue(from, async () => { - // Usar async en la función pasada a enqueue - await this.sendProviderAndSave(numberOrId, ctxMessage) - logger.log(`[QUEUE_SE_ENVIO]: `, ctxMessage) - await resolveCbEveryCtx(ctxMessage) - }) + await this.queuePrincipal.enqueue( + from, + async () => { + // Usar async en la función pasada a enqueue + await this.sendProviderAndSave(numberOrId, ctxMessage) + logger.log(`[QUEUE_SE_ENVIO]: `, ctxMessage) + await resolveCbEveryCtx(ctxMessage) + }, + generateTime() + ) } catch (error) { logger.error(`Error al encolar: ${error.message}`) return Promise.reject @@ -269,6 +274,7 @@ class CoreClass { const parseListMsg = listMsg.map((opt, index) => createCtxMessage(opt, index)) if (endFlowFlag) return + this.queuePrincipal.setFingerTime(from, generateTime()) for (const msg of parseListMsg) { const delayMs = msg?.options?.delay ?? this.generalArgs.delay ?? 0 if (delayMs) await delay(delayMs) @@ -419,7 +425,11 @@ class CoreClass { for (const ctxMessage of messageToSend) { const delayMs = ctxMessage?.options?.delay ?? this.generalArgs.delay ?? 0 if (delayMs) await delay(delayMs) - await this.queuePrincipal.enqueue(numberOrId, () => this.sendProviderAndSave(numberOrId, ctxMessage)) + await this.queuePrincipal.enqueue( + numberOrId, + () => this.sendProviderAndSave(numberOrId, ctxMessage), + generateTime() + ) // await queuePromises.dequeue() } return Promise.resolve diff --git a/packages/bot/package.json b/packages/bot/package.json index 0124e2f54..1a2a8aefe 100644 --- a/packages/bot/package.json +++ b/packages/bot/package.json @@ -1,6 +1,6 @@ { "name": "@bot-whatsapp/bot", - "version": "0.0.156-alpha.0", + "version": "0.0.165-alpha.0", "description": "", "main": "./lib/bundle.bot.cjs", "scripts": { diff --git a/packages/bot/utils/hash.js b/packages/bot/utils/hash.js index 6902d5884..cf6f40db8 100644 --- a/packages/bot/utils/hash.js +++ b/packages/bot/utils/hash.js @@ -10,6 +10,14 @@ const generateRef = (prefix = false) => { return prefix ? `${prefix}_${id}` : id } +/** + * Generar randon sin prefijos hex + * @returns + */ +const generateTime = () => { + return Date.now() +} + /** * Genera un HASH MD5 * @param {*} param0 @@ -18,4 +26,4 @@ const generateRef = (prefix = false) => { const generateRefSerialize = ({ index, answer, keyword }) => crypto.createHash('md5').update(JSON.stringify({ index, answer, keyword })).digest('hex') -module.exports = { generateRef, generateRefSerialize } +module.exports = { generateRef, generateRefSerialize, generateTime } diff --git a/packages/bot/utils/queue.js b/packages/bot/utils/queue.js index 1a054a0f5..c29cf2da5 100644 --- a/packages/bot/utils/queue.js +++ b/packages/bot/utils/queue.js @@ -1,7 +1,9 @@ class Queue { constructor(logger, concurrencyLimit = 15, timeout = 20000) { this.queue = new Map() + this.queueTime = new Map() this.workingOnPromise = new Map() + this.listFingers = new Map() this.logger = logger this.timeout = timeout this.concurrencyLimit = concurrencyLimit @@ -13,8 +15,8 @@ class Queue { * @param {*} promiseFunc * @returns */ - async enqueue(from, promiseFunc) { - this.logger.log(`${from}:ENCOLADO`) + async enqueue(from, promiseFunc, fingerTime) { + this.logger.log(`${from}:ENCOLADO ${fingerTime}`) if (!this.queue.has(from)) { this.queue.set(from, []) @@ -27,6 +29,8 @@ class Queue { return new Promise((resolve, reject) => { queueByFrom.push({ promiseFunc, + fingerTime, + cancelled: false, resolve, reject, }) @@ -46,26 +50,42 @@ class Queue { async processQueue(from) { const queueByFrom = this.queue.get(from) - const promise1 = () => new Promise((_, reject) => setTimeout(() => reject('timeout'), this.timeout)) + const timeOutFn = (item) => { + return new Promise((_, reject) => { + if (item.cancelled) { + reject('cancelled') + } + + const fingerTimeByFrom = this.queueTime.get(from) + + if (fingerTimeByFrom > item.fingerTime) { + reject('overtime') + } + + setTimeout(() => reject('timeout'), this.timeout) + }) + } while (queueByFrom.length > 0) { const tasksToProcess = queueByFrom.splice(0, this.concurrencyLimit) const promises = tasksToProcess.map(async (item) => { try { - const value = await Promise.race([promise1(), item.promiseFunc()]) + const value = await Promise.race([timeOutFn(item), item.promiseFunc()]) item.resolve(value) this.logger.log(`${from}:SUCCESS`) } catch (err) { this.logger.error(`${from}:ERROR: ${JSON.stringify(err)}`) item.reject(err) } + await this.clearQueue(from) }) await Promise.allSettled(promises) } this.workingOnPromise.set(from, false) + await this.clearQueue(from) } /** @@ -74,14 +94,35 @@ class Queue { */ async clearQueue(from) { if (this.queue.has(from)) { - this.queue.set(from, []) + const queueByFrom = this.queue.get(from) const workingByFrom = this.workingOnPromise.get(from) + // Marca todas las promesas como canceladas + queueByFrom.forEach((item) => { + item.cancelled = true + item.reject('Queue cleared') + }) + + // Limpia la cola + this.queue.set(from, []) + + // Si hay un proceso en ejecución, también deberías cancelarlo if (workingByFrom) { this.workingOnPromise.set(from, false) } } } + + /** + * Establecer una marca de tiempo de ejecuccion de promeses + * esto evita resolver promesas que yo no necesita + * @param {*} from + * @param {*} fingerTime + */ + setFingerTime = (from, fingerTime) => { + console.log(`Se seteo ${fingerTime}`) + this.queueTime.set(from, fingerTime) + } } module.exports = Queue From 8c58147741f3f4b977667ed19ddba7ecc577df8d Mon Sep 17 00:00:00 2001 From: Leifer Mendez Date: Sun, 27 Aug 2023 14:49:40 +0200 Subject: [PATCH 10/17] chore: :zap: fix --- __test__/1.1.6-case.test.js | 2 +- package.json | 2 +- packages/bot/core/core.class.js | 36 ++++++++++++++++++-------- packages/bot/package.json | 2 +- packages/bot/utils/queue.js | 45 +++++++++++++++++++++++++-------- 5 files changed, 62 insertions(+), 25 deletions(-) diff --git a/__test__/1.1.6-case.test.js b/__test__/1.1.6-case.test.js index f9615377b..1a29c9d85 100644 --- a/__test__/1.1.6-case.test.js +++ b/__test__/1.1.6-case.test.js @@ -87,7 +87,7 @@ suiteCase(`Debe retornar un mensaje resumen`, async ({ database, provider }) => assert.is('Tu datos son:', getHistory[7]) assert.is('Nombre: Leifer Edad: 90', getHistory[8]) assert.is('🤖🤖 Gracias por tu participacion', getHistory[9]) - assert.is('Maria', getHistory) + assert.is('Maria', getHistory[10]) assert.is('Gracias por tu nombre!', getHistory[11]) assert.is('¿Cual es tu edad?', getHistory[12]) assert.is('100', getHistory[13]) diff --git a/package.json b/package.json index e2ed13077..56ba04801 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "build": "pnpm run cli:rollup && pnpm run bot:rollup && pnpm run provider:rollup && pnpm run database:rollup && pnpm run contexts:rollup && pnpm run create-bot-whatsapp:rollup && pnpm run portal:rollup", "copy.lib": "node ./scripts/move.js", "test.unit": "node ./node_modules/uvu/bin.js packages test", - "test.e2e": "node ./node_modules/uvu/bin.js __test__ 1.1.6-case.test.js", + "test.e2e": "node ./node_modules/uvu/bin.js __test__ ", "test.coverage": "node ./node_modules/c8/bin/c8.js npm run test.unit", "test": "npm run test.coverage", "cli": "node ./packages/cli/bin/cli.js", diff --git a/packages/bot/core/core.class.js b/packages/bot/core/core.class.js index 895ae732a..a0a399878 100644 --- a/packages/bot/core/core.class.js +++ b/packages/bot/core/core.class.js @@ -172,7 +172,9 @@ class CoreClass { } // 📄 Esta funcion se encarga de enviar un array de mensajes dentro de este ctx - const sendFlow = async (messageToSend, numberOrId, options = { prev: prevMsg }) => { + const sendFlow = async (messageToSend, numberOrId, options = {}) => { + options = { prev: prevMsg, forceQueue: false, ...options } + if (options.prev?.options?.capture) { await cbEveryCtx(options.prev?.ref) } @@ -186,8 +188,22 @@ class CoreClass { if (delayMs) { await delay(delayMs) // Esperar según el retraso configurado } - this.queuePrincipal.setFingerTime(from, generateTime()) - logger.log(`[sendQueue_A]: `, ctxMessage) + + //TODO el proceso de forzar cola de procsos + if (options?.forceQueue) { + const listIdsRefCallbacks = messageToSend.map((i) => i.ref) + + const listProcessWait = this.queuePrincipal.getIdsCallbacs(from) + if (!listProcessWait.length) { + this.queuePrincipal.setIdsCallbacks(from, listIdsRefCallbacks) + } else { + const lastMessage = messageToSend[messageToSend.length - 1] + await this.databaseClass.save({ ...lastMessage, from: numberOrId }) + if (listProcessWait.includes(lastMessage.ref)) { + this.queuePrincipal.clearQueue(from) + } + } + } try { await this.queuePrincipal.enqueue( @@ -198,7 +214,7 @@ class CoreClass { logger.log(`[QUEUE_SE_ENVIO]: `, ctxMessage) await resolveCbEveryCtx(ctxMessage) }, - generateTime() + ctxMessage.ref ) } catch (error) { logger.error(`Error al encolar: ${error.message}`) @@ -209,7 +225,7 @@ class CoreClass { } } - const continueFlow = async () => { + const continueFlow = async (_) => { 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) @@ -221,9 +237,7 @@ class CoreClass { const nextChildMessages = (await this.flowClass.find(refToContinueChild?.ref, true, flowStandaloneChild)) || [] if (nextChildMessages?.length) return await sendFlow(nextChildMessages, from, { prev: undefined }) - } - if (!isContinueFlow) { await sendFlow(filterNextFlow, from, { prev: undefined }) return } @@ -274,14 +288,14 @@ class CoreClass { const parseListMsg = listMsg.map((opt, index) => createCtxMessage(opt, index)) if (endFlowFlag) return - this.queuePrincipal.setFingerTime(from, generateTime()) + this.queuePrincipal.setFingerTime(from, generateTime()) //aqui debeo decirle al sistema como que finalizo el flujo for (const msg of parseListMsg) { const delayMs = msg?.options?.delay ?? this.generalArgs.delay ?? 0 if (delayMs) await delay(delayMs) await this.sendProviderAndSave(from, msg) } - if (options?.continue) await continueFlow() + if (options?.continue) await continueFlow(generateTime()) return } @@ -319,7 +333,7 @@ class CoreClass { await this.flowClass.allCallbacks[inRef](messageCtxInComming, argsCb) //Si no hay llamado de fallaback y no hay llamado de flowDynamic y no hay llamado de enflow EL flujo continua const ifContinue = !flags.endFlow && !flags.fallBack && !flags.flowDynamic - if (ifContinue) await continueFlow() + if (ifContinue) await continueFlow(prevMsg?.options?.nested?.length) return } @@ -371,7 +385,7 @@ class CoreClass { msgToSend = this.flowClass.find(this.generalArgs.listEvents.VOICE_NOTE) || [] } } - return sendFlow(msgToSend, from) + return sendFlow(msgToSend, from, { forceQueue: true }) } /** diff --git a/packages/bot/package.json b/packages/bot/package.json index 1a2a8aefe..7f59d959c 100644 --- a/packages/bot/package.json +++ b/packages/bot/package.json @@ -1,6 +1,6 @@ { "name": "@bot-whatsapp/bot", - "version": "0.0.165-alpha.0", + "version": "0.0.167-alpha.0", "description": "", "main": "./lib/bundle.bot.cjs", "scripts": { diff --git a/packages/bot/utils/queue.js b/packages/bot/utils/queue.js index c29cf2da5..ccabd2204 100644 --- a/packages/bot/utils/queue.js +++ b/packages/bot/utils/queue.js @@ -2,8 +2,8 @@ class Queue { constructor(logger, concurrencyLimit = 15, timeout = 20000) { this.queue = new Map() this.queueTime = new Map() + this.idsCallbacks = new Map() this.workingOnPromise = new Map() - this.listFingers = new Map() this.logger = logger this.timeout = timeout this.concurrencyLimit = concurrencyLimit @@ -15,8 +15,8 @@ class Queue { * @param {*} promiseFunc * @returns */ - async enqueue(from, promiseFunc, fingerTime) { - this.logger.log(`${from}:ENCOLADO ${fingerTime}`) + async enqueue(from, promiseFunc, fingerIdRef) { + this.logger.log(`${from}:ENCOLADO ${fingerIdRef}`) if (!this.queue.has(from)) { this.queue.set(from, []) @@ -29,7 +29,7 @@ class Queue { return new Promise((resolve, reject) => { queueByFrom.push({ promiseFunc, - fingerTime, + fingerIdRef, cancelled: false, resolve, reject, @@ -56,11 +56,11 @@ class Queue { reject('cancelled') } - const fingerTimeByFrom = this.queueTime.get(from) - - if (fingerTimeByFrom > item.fingerTime) { - reject('overtime') - } + // const fingerTimeByFrom = this.queueTime.get(from) + // if (fingerTimeByFrom > item.fingerTime) { + // console.log(`🚀🚀 ${fingerTimeByFrom}------${item.fingerTime}`) + // reject('overtime') + // } setTimeout(() => reject('timeout'), this.timeout) }) @@ -78,7 +78,7 @@ class Queue { this.logger.error(`${from}:ERROR: ${JSON.stringify(err)}`) item.reject(err) } - await this.clearQueue(from) + this.clearIdFromCallback(from, item.fingerIdRef) }) await Promise.allSettled(promises) @@ -104,6 +104,7 @@ class Queue { }) // Limpia la cola + this.queue.set(from, []) // Si hay un proceso en ejecución, también deberías cancelarlo @@ -120,9 +121,31 @@ class Queue { * @param {*} fingerTime */ setFingerTime = (from, fingerTime) => { - console.log(`Se seteo ${fingerTime}`) this.queueTime.set(from, fingerTime) } + + setIdsCallbacks = (from, ids = []) => { + this.idsCallbacks.set(from, ids) + } + + getIdsCallbacs = (from) => { + if (this.idsCallbacks.has(from)) { + return this.idsCallbacks.get(from) + } else { + return [] + } + } + + clearIdFromCallback = (from, id) => { + if (this.idsCallbacks.has(from)) { + const ids = this.idsCallbacks.get(from) + const index = ids.indexOf(id) + + if (index !== -1) { + ids.splice(index, 1) + } + } + } } module.exports = Queue From dd82adc05024b12a7e9a0458a710dda45ea1f7e8 Mon Sep 17 00:00:00 2001 From: Leifer Mendez Date: Sun, 27 Aug 2023 14:49:54 +0200 Subject: [PATCH 11/17] chore: :zap: fix --- packages/bot/core/core.class.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bot/core/core.class.js b/packages/bot/core/core.class.js index a0a399878..f70f9486a 100644 --- a/packages/bot/core/core.class.js +++ b/packages/bot/core/core.class.js @@ -225,7 +225,7 @@ class CoreClass { } } - const continueFlow = async (_) => { + const continueFlow = async () => { 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) From d7bb1611ae717d42ac8fb529dc9371245d0b31be Mon Sep 17 00:00:00 2001 From: Leifer Mendez Date: Sun, 27 Aug 2023 20:14:16 +0200 Subject: [PATCH 12/17] test: more test unit --- __test__/0.1.6-case.test.js | 114 +++++++++++++++++++++++++++ packages/bot/core/core.class.js | 35 +++++--- packages/bot/tests/bot.class.test.js | 71 +++++++++++++++++ 3 files changed, 211 insertions(+), 9 deletions(-) create mode 100644 __test__/0.1.6-case.test.js diff --git a/__test__/0.1.6-case.test.js b/__test__/0.1.6-case.test.js new file mode 100644 index 000000000..e78b4bff3 --- /dev/null +++ b/__test__/0.1.6-case.test.js @@ -0,0 +1,114 @@ +const { suite } = require('uvu') +const assert = require('uvu/assert') +const { addKeyword, createBot, createFlow, EVENTS } = require('../packages/bot/index') +const { setup, clear, delay } = require('../__mocks__/env') +const { generateRefprovider } = require('../packages/provider/common/hash') + +const fakeHTTP = async (fakeData = []) => { + await delay(50) + const data = fakeData.map((u) => ({ body: `${u}` })) + return Promise.resolve(data) +} + +const suiteCase = suite('EVENTS:') + +suiteCase.before.each(setup) +suiteCase.after.each(clear) + +suiteCase(`WELCOME`, async ({ database, provider }) => { + const flow = addKeyword(EVENTS.WELCOME).addAnswer('Bievenido') + + createBot({ + database, + provider, + flow: createFlow([flow]), + }) + + await provider.delaySendMessage(0, 'message', { + from: '000', + body: 'hola', + }) + + await delay(100) + const getHistory = database.listHistory.map((i) => i.answer) + + assert.is('Bievenido', getHistory[0]) +}) + +suiteCase(`MEDIA`, async ({ database, provider }) => { + const flow = addKeyword(EVENTS.MEDIA).addAnswer('media recibido') + + createBot({ + database, + provider, + flow: createFlow([flow]), + }) + + await provider.delaySendMessage(0, 'message', { + from: '000', + body: generateRefprovider('_event_media_'), + }) + await delay(100) + const getHistory = database.listHistory.map((i) => i.answer) + + assert.is('media recibido', getHistory[0]) +}) + +suiteCase(`LOCATION`, async ({ database, provider }) => { + const flow = addKeyword(EVENTS.LOCATION).addAnswer('location recibido') + + createBot({ + database, + provider, + flow: createFlow([flow]), + }) + + await provider.delaySendMessage(0, 'message', { + from: '000', + body: generateRefprovider('_event_location_'), + }) + await delay(100) + const getHistory = database.listHistory.map((i) => i.answer) + + assert.is('location recibido', getHistory[0]) +}) + +suiteCase(`DOCUMENT`, async ({ database, provider }) => { + const flow = addKeyword(EVENTS.DOCUMENT).addAnswer('document recibido') + + createBot({ + database, + provider, + flow: createFlow([flow]), + }) + + await provider.delaySendMessage(0, 'message', { + from: '000', + body: generateRefprovider('_event_document_'), + }) + await delay(100) + const getHistory = database.listHistory.map((i) => i.answer) + + assert.is('document recibido', getHistory[0]) +}) + +suiteCase(`VOICE_NOTE`, async ({ database, provider }) => { + const flow = addKeyword(EVENTS.VOICE_NOTE).addAnswer('voice recibido') + + createBot({ + database, + provider, + flow: createFlow([flow]), + }) + + await provider.delaySendMessage(0, 'message', { + from: '000', + body: generateRefprovider('_event_voice_note_'), + }) + await delay(100) + const getHistory = database.listHistory.map((i) => i.answer) + + assert.is('voice recibido', getHistory[0]) +}) + +suiteCase.run() diff --git a/packages/bot/core/core.class.js b/packages/bot/core/core.class.js index f70f9486a..ee1b8c153 100644 --- a/packages/bot/core/core.class.js +++ b/packages/bot/core/core.class.js @@ -158,6 +158,7 @@ class CoreClass { // 📄 Limpiar cola de procesos const clearQueue = () => { this.queuePrincipal.clearQueue(from) + return } // 📄 Finalizar flujo @@ -236,10 +237,10 @@ class CoreClass { const flowStandaloneChild = this.flowClass.getFlowsChild() const nextChildMessages = (await this.flowClass.find(refToContinueChild?.ref, true, flowStandaloneChild)) || [] - if (nextChildMessages?.length) return await sendFlow(nextChildMessages, from, { prev: undefined }) + if (nextChildMessages?.length) + return exportFunctionsSend(() => sendFlow(nextChildMessages, from, { prev: undefined })) - await sendFlow(filterNextFlow, from, { prev: undefined }) - return + return exportFunctionsSend(() => sendFlow(filterNextFlow, from, { prev: undefined })) } } // 📄 [options: fallBack]: esta funcion se encarga de repetir el ultimo mensaje @@ -338,6 +339,22 @@ class CoreClass { return } + const exportFunctionsSend = async (cb = () => Promise.resolve()) => { + await cb() + return { + createCtxMessage, + clearQueue, + endFlow, + sendFlow, + continueFlow, + fallBack, + gotoFlow, + flowDynamic, + resolveCbEveryCtx, + cbEveryCtx, + } + } + // 📄🤘(tiene return) [options: nested(array)]: Si se tiene flujos hijos los implementa if (!endFlowFlag && prevMsg?.options?.nested?.length) { const nestedRef = prevMsg.options.nested @@ -347,8 +364,7 @@ class CoreClass { msgToSend = this.flowClass.find(body, false, flowStandalone) || [] - await sendFlow(msgToSend, from) - return + return exportFunctionsSend(() => sendFlow(msgToSend, from)) } // 📄🤘(tiene return) Si el mensaje previo implementa capture @@ -357,14 +373,15 @@ class CoreClass { if (typeCapture === 'boolean' && fallBackFlag) { msgToSend = this.flowClass.find(refToContinue?.ref, true) || [] - await sendFlow(msgToSend, from) - return + return exportFunctionsSend(() => sendFlow(msgToSend, from, { forceQueue: true })) } } msgToSend = this.flowClass.find(body) || [] - if (msgToSend.length) return sendFlow(msgToSend, from) + if (msgToSend.length) { + return exportFunctionsSend(() => sendFlow(msgToSend, from)) + } if (!prevMsg?.options?.capture) { msgToSend = this.flowClass.find(this.generalArgs.listEvents.WELCOME) || [] @@ -385,7 +402,7 @@ class CoreClass { msgToSend = this.flowClass.find(this.generalArgs.listEvents.VOICE_NOTE) || [] } } - return sendFlow(msgToSend, from, { forceQueue: true }) + return exportFunctionsSend(() => sendFlow(msgToSend, from, { forceQueue: true })) } /** diff --git a/packages/bot/tests/bot.class.test.js b/packages/bot/tests/bot.class.test.js index d94127cb3..c89c43b6f 100644 --- a/packages/bot/tests/bot.class.test.js +++ b/packages/bot/tests/bot.class.test.js @@ -220,6 +220,77 @@ test(`[Bot] Probando Flujos Nested`, async () => { assert.is(JSON.stringify(responseEvents.message), JSON.stringify(MOCK_EVENTS.message)) }) +test(`[Bot] Probando createCtxMessage `, async () => { + const mockProvider = new MockProvider() + + const setting = { + flow: new MockFlow(), + database: new MockDBB(), + provider: mockProvider, + } + + const bot = await createBot(setting) + + const messageCtxInComming = { + body: 'Hola', + from: '123456789', + } + + const botHandler = await bot.handleMsg(messageCtxInComming) + const result = botHandler.createCtxMessage(`Hola`) + + assert.is(result.answer, 'Hola') + assert.is(result.options.media, null) + assert.is(result.options.buttons.length, 0) + assert.is(result.options.capture, false) + assert.is(result.options.delay, 0) + assert.is(result.from, '123456789') +}) + +test(`[Bot] Probando clearQueue `, async () => { + const mockProvider = new MockProvider() + + const setting = { + flow: new MockFlow(), + database: new MockDBB(), + provider: mockProvider, + } + + const bot = await createBot(setting) + + const messageCtxInComming = { + body: 'Hola', + from: '123456789', + } + + const botHandler = await bot.handleMsg(messageCtxInComming) + const result = botHandler.clearQueue() + + assert.is(result, undefined) +}) + +test(`[Bot] Probando endFlow `, async () => { + const mockProvider = new MockProvider() + + const setting = { + flow: new MockFlow(), + database: new MockDBB(), + provider: mockProvider, + } + + const bot = await createBot(setting) + + const messageCtxInComming = { + body: 'Hola', + from: '123456789', + } + + const botHandler = await bot.handleMsg(messageCtxInComming) + const result = botHandler.endFlow({ endFlow: false })('hola') + + assert.is(Object.values(result).length, 0) +}) + test.run() function delay(ms) { From f0e28a58e41b05309698411b5cc09a3a97faa63f Mon Sep 17 00:00:00 2001 From: Trystan4861 Date: Tue, 29 Aug 2023 14:26:33 +0200 Subject: [PATCH 13/17] =?UTF-8?q?=F0=9F=9A=91=EF=B8=8F=20Now=20field=20key?= =?UTF-8?q?word=20at=20adapterMySQL/history=20can=20be=20NULL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/database/src/mysql/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/database/src/mysql/index.js b/packages/database/src/mysql/index.js index 2c681e1cb..483c735bf 100644 --- a/packages/database/src/mysql/index.js +++ b/packages/database/src/mysql/index.js @@ -64,7 +64,7 @@ class MyslAdapter { const sql = `CREATE TABLE ${tableName} (id INT AUTO_INCREMENT PRIMARY KEY, ref varchar(255) NOT NULL, - keyword varchar(255) NOT NULL, + keyword varchar(255) NULL, answer longtext NOT NULL, refSerialize varchar(255) NOT NULL, phone varchar(255) NOT NULL, From e298b546f4e91c2142ca09f4281a0faf694bfa7b Mon Sep 17 00:00:00 2001 From: Gerardo Castillo Date: Tue, 29 Aug 2023 11:54:02 -0400 Subject: [PATCH 14/17] feat: Integrate 'capture_only_intended' flag for silent execution in 'addAnswer' function --- packages/bot/core/core.class.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/bot/core/core.class.js b/packages/bot/core/core.class.js index 9a32c9459..9eb3d69a9 100644 --- a/packages/bot/core/core.class.js +++ b/packages/bot/core/core.class.js @@ -379,8 +379,10 @@ class CoreClass { const { answer } = ctxMessage logger.log(`[sendProviderAndSave]: `, ctxMessage) if (answer && answer.length && answer !== '__call_action__') { - await this.providerClass.sendMessage(numberOrId, answer, ctxMessage) - logger.log(`[providerClass.sendMessage]: `, ctxMessage) + if (answer !== '__capture_only_intended__') { + await this.providerClass.sendMessage(numberOrId, answer, ctxMessage) + logger.log(`[providerClass.sendMessage]: `, ctxMessage) + } await this.databaseClass.save({ ...ctxMessage, from: numberOrId }) logger.log(`[databaseClass.save]: `, ctxMessage) } From dba52ad6faea63e178a6b3cfb731b494c144e278 Mon Sep 17 00:00:00 2001 From: Leifer Mendez Date: Thu, 31 Aug 2023 13:37:19 +0200 Subject: [PATCH 15/17] chore: new addAction capture --- ...{1.1.6-case.test.js => 0.1.7-case.test.js} | 0 __test__/0.1.8-case.test.js | 46 +++++++++++++++++++ packages/bot/core/core.class.js | 4 -- packages/bot/io/methods/addAnswer.js | 5 +- 4 files changed, 50 insertions(+), 5 deletions(-) rename __test__/{1.1.6-case.test.js => 0.1.7-case.test.js} (100%) create mode 100644 __test__/0.1.8-case.test.js diff --git a/__test__/1.1.6-case.test.js b/__test__/0.1.7-case.test.js similarity index 100% rename from __test__/1.1.6-case.test.js rename to __test__/0.1.7-case.test.js diff --git a/__test__/0.1.8-case.test.js b/__test__/0.1.8-case.test.js new file mode 100644 index 000000000..91b9b107f --- /dev/null +++ b/__test__/0.1.8-case.test.js @@ -0,0 +1,46 @@ +const { suite } = require('uvu') +const assert = require('uvu/assert') +const { addKeyword, createBot, createFlow } = require('../packages/bot/index') +const { setup, clear, delay } = require('../__mocks__/env') + +const suiteCase = suite('Flujo: addAction (capture)') + +suiteCase.before.each(setup) +suiteCase.after.each(clear) + +suiteCase(`Debe ejecutar accion con captura`, async ({ database, provider }) => { + const flujoPrincipal = addKeyword(['hola']) + .addAction(async (_, { flowDynamic }) => { + return flowDynamic('Buenas! ¿Cual es tu nombre?') + }) + .addAction({ capture: true }, async (ctx, { flowDynamic, state }) => { + state.update({ name: ctx.body }) + return flowDynamic(`Gracias por tu nombre!: ${ctx.body}`) + }) + .addAnswer('Chao!') + + await createBot({ + database, + flow: createFlow([flujoPrincipal]), + provider, + }) + + await provider.delaySendMessage(0, 'message', { + from: '000', + body: 'hola', + }) + + await provider.delaySendMessage(50, 'message', { + from: '000', + body: 'Leifer', + }) + + await delay(1000) + const getHistory = database.listHistory.map((i) => i.answer) + assert.is('Buenas! ¿Cual es tu nombre?', getHistory[0]) + assert.is('Gracias por tu nombre!: Leifer', getHistory[3]) + assert.is('Chao!', getHistory[4]) + assert.is(undefined, getHistory[5]) +}) + +suiteCase.run() diff --git a/packages/bot/core/core.class.js b/packages/bot/core/core.class.js index d5831b9be..7321c8978 100644 --- a/packages/bot/core/core.class.js +++ b/packages/bot/core/core.class.js @@ -414,20 +414,16 @@ class CoreClass { sendProviderAndSave = async (numberOrId, ctxMessage) => { try { const { answer } = ctxMessage - logger.log(`[sendProviderAndSave]: `, ctxMessage) if (answer && answer.length && answer !== '__call_action__') { if (answer !== '__capture_only_intended__') { await this.providerClass.sendMessage(numberOrId, answer, ctxMessage) - logger.log(`[providerClass.sendMessage]: `, ctxMessage) } await this.databaseClass.save({ ...ctxMessage, from: numberOrId }) - logger.log(`[databaseClass.save]: `, ctxMessage) } return Promise.resolve } catch (err) { logger.log(`[ERROR.save]: `, ctxMessage) - console.log('ERROR:Enviando') return Promise.reject } } diff --git a/packages/bot/io/methods/addAnswer.js b/packages/bot/io/methods/addAnswer.js index 37adf088e..82451e2b3 100644 --- a/packages/bot/io/methods/addAnswer.js +++ b/packages/bot/io/methods/addAnswer.js @@ -97,7 +97,10 @@ const addAnswer = ctx, ref: ctx.ref, addAnswer: addAnswer(ctx), - addAction: (cb = () => null) => addAnswer(ctx)('__call_action__', null, cb), + addAction: (cb = () => null, flagCb = () => null) => { + if (typeof cb === 'object') return addAnswer(ctx)('__capture_only_intended__', cb, flagCb) + return addAnswer(ctx)('__call_action__', null, cb) + }, toJson: toJson(ctx), } } From cd68e70f92dd0f1a12d870db132c4abccf720866 Mon Sep 17 00:00:00 2001 From: Leifer Mendez Date: Thu, 31 Aug 2023 14:07:59 +0200 Subject: [PATCH 16/17] docs: update --- packages/docs/src/root.tsx | 28 +- .../docs/src/routes/docs/add-action/index.mdx | 15 + .../src/routes/docs/requirements/index.mdx | 2 +- packages/docs/src/routes/docs/state/index.mdx | 94 ++++++ pnpm-lock.yaml | 286 +++++++++--------- 5 files changed, 264 insertions(+), 161 deletions(-) create mode 100644 packages/docs/src/routes/docs/state/index.mdx diff --git a/packages/docs/src/root.tsx b/packages/docs/src/root.tsx index b6319653e..0becc0ca8 100644 --- a/packages/docs/src/root.tsx +++ b/packages/docs/src/root.tsx @@ -40,8 +40,9 @@ export default component$(() => { list: [ { name: 'addKeyword', link: '/docs/add-keyword' }, { name: 'addAnswers', link: '/docs/add-answers' }, - { name: 'ctx', link: '/docs/ctx' }, { name: 'addAction', link: '/docs/add-action' }, + { name: 'ctx', link: '/docs/ctx' }, + { name: 'state', link: '/docs/state' }, { name: 'flowDynamic', link: '/docs/flow-dynamic' }, { name: 'fallBack', link: '/docs/fall-back' }, { name: 'endFlow', link: '/docs/end-flow' }, @@ -59,15 +60,15 @@ export default component$(() => { { name: 'Whatsapp-web.js', link: '/docs/provider-wweb' } ], }, - { - title: '@bot/database', - list: [ - { name: 'Memory', link: '/docs/database-memory' }, - { name: 'Json', link: '/docs/database-json' }, - { name: 'Mongo', link: '/docs/database-mongo' }, - { name: 'MySQL', link: '/docs/database-mysql' } - ], - }, + // { + // title: '@bot/database', + // list: [ + // { name: 'Memory', link: '/docs/database-memory' }, + // { name: 'Json', link: '/docs/database-json' }, + // { name: 'Mongo', link: '/docs/database-mongo' }, + // { name: 'MySQL', link: '/docs/database-mysql' } + // ], + // }, { title: 'Despliegue', list: [ @@ -76,13 +77,6 @@ export default component$(() => { { name: 'Cloud', link: '/docs/deploy/cloud' }, ], }, - { - title: 'Casos de Uso', - list: [ - { name: 'Manejo de estado', link: '/docs/migration' }, - { name: 'API', link: '/docs/api' }, - ], - }, { title: 'Avanzado', list: [ diff --git a/packages/docs/src/routes/docs/add-action/index.mdx b/packages/docs/src/routes/docs/add-action/index.mdx index 050aaefdf..bc5163693 100644 --- a/packages/docs/src/routes/docs/add-action/index.mdx +++ b/packages/docs/src/routes/docs/add-action/index.mdx @@ -18,6 +18,21 @@ const flowPrincipal = addKeyword(['hola', 'alo']) .addAnswer(['Tengo:', 'Zapatos', 'Bolsos', 'etc ...']) ``` +Tambien puedes tener un addAction con `capture` + +```js +const { addKeyword } = require('@bot-whatsapp/bot') + const flujoPrincipal = addKeyword(['hola']) + .addAction(async (_, { flowDynamic }) => { + return flowDynamic('Buenas! ¿Cual es tu nombre?') + }) + .addAction({ capture: true }, async (ctx, { flowDynamic, state }) => { + state.update({ name: ctx.body }) + return flowDynamic(`Gracias por tu nombre!: ${ctx.body}`) + }) + .addAnswer('Chao!') +``` + --- { + state.update({ name: ctx.body }) + flowDynamic('Gracias por tu nombre!') + } + ) + .addAnswer( + '¿Cual es tu edad?', + { + capture: true, + }, + async (ctx, { flowDynamic, state }) => { + state.update({ age: ctx.body }) + const myState = state.getMyState() + await flowDynamic(`Gracias por tu edad! ${myState.name}`) + } + ) + .addAnswer('Tus datos son:', null, async (_, { flowDynamic, state }) => { + const myState = state.getMyState() + flowDynamic(`Nombre: ${myState.name} Edad: ${myState.age}`) + }) + .addAnswer('🤖🤖 Gracias por tu participacion') + +``` + +## GlobalState + +De igual manera que el `state` anterior nosotros podemos usar `globalState` para tener un esta general de la app (NO es por usuario), esto es muy util si quieres ejemplo apagar o prener el bot + + +```js + + const flowOnOff = addKeyword(['onoff']) + .addAction(async (_, { flowDynamic, globalState }) => { + const currentGlobalState = globalState.getMyState(); + if(currentGlobalState.encendido){ + globalState.update({encendido:false}) + }else{ + globalState.update({encendido:true}) + } + }) + .addAnswer('🤖🤖 Gracias por tu participacion') + + ... + + + const flowWelcome = addKeyword(EVENTS.WELCOME) + .addAction((_, { endFlow, globalState }) => { + + const currentGlobalState = globalState.getMyState(); + if (!currentGlobalState.encendido) { + return endFlow(); + } + }) + + ... + + createBot( + { + flow: adapterFlow, + provider: adapterProvider, + database: adapterDB, + }, + { + globalState: { + encendido: true, + } + } + ); + +``` + +--- + + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dabb5805e..3f72bc570 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -203,7 +203,7 @@ importers: version: 8.4.10 '@types/node': specifier: latest - version: 20.5.4 + version: 20.5.7 '@types/react': specifier: 18.0.14 version: 18.0.14 @@ -233,7 +233,7 @@ importers: version: 3.2.3 netlify-cli: specifier: ^12.0.11 - version: 12.0.11(@types/node@20.5.4) + version: 12.0.11(@types/node@20.5.7) node-fetch: specifier: ^3.3.0 version: 3.3.0 @@ -262,8 +262,8 @@ importers: specifier: 4.8.4 version: 4.8.4 vite: - specifier: 3.2.4 - version: 3.2.4(@types/node@20.5.4) + specifier: ^3.2.4 + version: 3.2.4(@types/node@20.5.7) vite-imagetools: specifier: ^4.0.11 version: 4.0.11(rollup@3.17.2) @@ -2437,7 +2437,7 @@ packages: dependencies: '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 20.5.4 + '@types/node': 20.5.7 '@types/yargs': 16.0.5 chalk: 4.1.2 dev: true @@ -2552,7 +2552,7 @@ packages: } dev: true - /@netlify/build@28.4.5(@types/node@20.5.4): + /@netlify/build@28.4.5(@types/node@20.5.7): resolution: { integrity: sha512-5ciyNoF0SH+1DlDLUzgtLqPJJXh6QW1bDkq8H0S44uOy1VxJpbvFat3asa0BspoB61CYpqzyzmpfIQKw+voySA==, @@ -2608,7 +2608,7 @@ packages: supports-color: 9.4.0 terminal-link: 3.0.0 tmp-promise: 3.0.3 - ts-node: 10.9.1(@types/node@20.5.4)(typescript@4.9.4) + ts-node: 10.9.1(@types/node@20.5.7)(typescript@4.9.4) typescript: 4.9.4 update-notifier: 5.1.0 uuid: 8.3.2 @@ -4766,7 +4766,7 @@ packages: } dependencies: '@types/connect': 3.4.35 - '@types/node': 20.5.4 + '@types/node': 20.5.7 dev: true /@types/cacheable-request@6.0.3: @@ -4777,7 +4777,7 @@ packages: dependencies: '@types/http-cache-semantics': 4.0.1 '@types/keyv': 3.1.4 - '@types/node': 20.5.4 + '@types/node': 20.5.7 '@types/responselike': 1.0.0 dev: true @@ -4796,7 +4796,7 @@ packages: integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==, } dependencies: - '@types/node': 20.5.4 + '@types/node': 20.5.7 dev: true /@types/debug@4.1.8: @@ -4814,7 +4814,7 @@ packages: integrity: sha512-/C8kTMRTNiNuWGl5nEyKbPiMv6HA+0RbEXzFhFBEzASM6+oa4tJro9b8nj7eRlOFfuLdzUU+DS/GPDlvvzMOhA==, } dependencies: - '@types/node': 20.5.4 + '@types/node': 20.5.7 dev: true /@types/download@8.0.2: @@ -4825,7 +4825,7 @@ packages: dependencies: '@types/decompress': 4.2.4 '@types/got': 9.6.12 - '@types/node': 20.5.4 + '@types/node': 20.5.7 dev: true /@types/eslint@8.4.10: @@ -4860,7 +4860,7 @@ packages: integrity: sha512-zbivROJ0ZqLAtMzgzIUC4oNqDG9iF0lSsAqpOD9kbs5xcIM3dTiyuHvBc7R8MtWBp3AAWGaovJa+wzWPjLYW7Q==, } dependencies: - '@types/node': 20.5.4 + '@types/node': 20.5.7 '@types/qs': 6.9.7 '@types/range-parser': 1.2.4 '@types/send': 0.17.1 @@ -4884,7 +4884,7 @@ packages: integrity: sha512-SvSrYXfWSc7R4eqnOzbQF4TZmfpNSM9FrSWLU3EUnWBuyZqNBOrv1B1JA3byUDPUl9z4Ab3jeZG2eDdySlgNMg==, } dependencies: - '@types/node': 20.5.4 + '@types/node': 18.11.18 dev: true /@types/glob@7.2.0: @@ -4894,7 +4894,7 @@ packages: } dependencies: '@types/minimatch': 5.1.2 - '@types/node': 20.5.4 + '@types/node': 18.11.18 dev: true /@types/got@9.6.12: @@ -4903,7 +4903,7 @@ packages: integrity: sha512-X4pj/HGHbXVLqTpKjA2ahI4rV/nNBc9mGO2I/0CgAra+F2dKgMXnENv2SRpemScBzBAI4vMelIVYViQxlSE6xA==, } dependencies: - '@types/node': 20.5.4 + '@types/node': 20.5.7 '@types/tough-cookie': 4.0.2 form-data: 2.5.1 dev: true @@ -4937,7 +4937,7 @@ packages: integrity: sha512-HC8G7c1WmaF2ekqpnFq626xd3Zz0uvaqFmBJNRZCGEZCXkvSdJoNFn/8Ygbd9fKNQj8UzLdCETaI0UWPAjK7IA==, } dependencies: - '@types/node': 20.5.4 + '@types/node': 20.5.7 dev: true /@types/istanbul-lib-coverage@2.0.4: @@ -4988,7 +4988,7 @@ packages: integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==, } dependencies: - '@types/node': 20.5.4 + '@types/node': 20.5.7 dev: true /@types/mdast@3.0.12: @@ -5064,7 +5064,6 @@ packages: { integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==, } - dev: true /@types/node@20.4.7: resolution: @@ -5073,11 +5072,12 @@ packages: } dev: true - /@types/node@20.5.4: + /@types/node@20.5.7: resolution: { - integrity: sha512-Y9vbIAoM31djQZrPYjpTLo0XlaSwOIsrlfE3LpulZeRblttsLQRFRlBAppW0LOxyT3ALj2M5vU1ucQQayQH3jA==, + integrity: sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==, } + dev: true /@types/normalize-package-data@2.4.1: resolution: @@ -5147,7 +5147,7 @@ packages: integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==, } dependencies: - '@types/node': 20.5.4 + '@types/node': 20.5.7 dev: true /@types/retry@0.12.1: @@ -5178,7 +5178,7 @@ packages: } dependencies: '@types/mime': 1.3.2 - '@types/node': 20.5.4 + '@types/node': 20.5.7 dev: true /@types/serve-static@1.15.2: @@ -5189,7 +5189,7 @@ packages: dependencies: '@types/http-errors': 2.0.1 '@types/mime': 3.0.1 - '@types/node': 20.5.4 + '@types/node': 20.5.7 dev: true /@types/tough-cookie@4.0.2: @@ -5226,7 +5226,7 @@ packages: integrity: sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==, } dependencies: - '@types/node': 20.5.4 + '@types/node': 18.11.18 '@types/webidl-conversions': 7.0.0 dev: false @@ -5262,7 +5262,7 @@ packages: } requiresBuild: true dependencies: - '@types/node': 20.5.4 + '@types/node': 20.5.7 dev: true optional: true @@ -6318,7 +6318,7 @@ packages: chalk: 2.4.2 common-path-prefix: 1.0.0 createerror: 1.3.0 - cssnano: 5.1.15(postcss@8.4.19) + cssnano: 5.1.15(postcss@8.4.28) data-urls: 1.1.0 domspace: 1.2.2 esanimate: 1.1.1 @@ -6336,8 +6336,8 @@ packages: memoizesync: 1.1.1 mkdirp: 0.5.6 normalizeurl: 1.0.0 - perfectionist-dfd: 3.0.2(postcss@8.4.19) - postcss: 8.4.19 + perfectionist-dfd: 3.0.2(postcss@8.4.28) + postcss: 8.4.28 qs: 6.11.2 read-pkg-up: 6.0.0 repeat-string: 1.6.1 @@ -8504,7 +8504,7 @@ packages: type-fest: 1.4.0 dev: true - /css-declaration-sorter@6.4.1(postcss@8.4.19): + /css-declaration-sorter@6.4.1(postcss@8.4.28): resolution: { integrity: sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==, @@ -8513,7 +8513,7 @@ packages: peerDependencies: postcss: ^8.0.9 dependencies: - postcss: 8.4.19 + postcss: 8.4.28 dev: true /css-font-parser-papandreou@0.2.3-patch1: @@ -8592,7 +8592,7 @@ packages: hasBin: true dev: true - /cssnano-preset-default@5.2.14(postcss@8.4.19): + /cssnano-preset-default@5.2.14(postcss@8.4.28): resolution: { integrity: sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==, @@ -8601,39 +8601,39 @@ packages: peerDependencies: postcss: ^8.2.15 dependencies: - css-declaration-sorter: 6.4.1(postcss@8.4.19) - cssnano-utils: 3.1.0(postcss@8.4.19) - postcss: 8.4.19 - postcss-calc: 8.2.4(postcss@8.4.19) - postcss-colormin: 5.3.1(postcss@8.4.19) - postcss-convert-values: 5.1.3(postcss@8.4.19) - postcss-discard-comments: 5.1.2(postcss@8.4.19) - postcss-discard-duplicates: 5.1.0(postcss@8.4.19) - postcss-discard-empty: 5.1.1(postcss@8.4.19) - postcss-discard-overridden: 5.1.0(postcss@8.4.19) - postcss-merge-longhand: 5.1.7(postcss@8.4.19) - postcss-merge-rules: 5.1.4(postcss@8.4.19) - postcss-minify-font-values: 5.1.0(postcss@8.4.19) - postcss-minify-gradients: 5.1.1(postcss@8.4.19) - postcss-minify-params: 5.1.4(postcss@8.4.19) - postcss-minify-selectors: 5.2.1(postcss@8.4.19) - postcss-normalize-charset: 5.1.0(postcss@8.4.19) - postcss-normalize-display-values: 5.1.0(postcss@8.4.19) - postcss-normalize-positions: 5.1.1(postcss@8.4.19) - postcss-normalize-repeat-style: 5.1.1(postcss@8.4.19) - postcss-normalize-string: 5.1.0(postcss@8.4.19) - postcss-normalize-timing-functions: 5.1.0(postcss@8.4.19) - postcss-normalize-unicode: 5.1.1(postcss@8.4.19) - postcss-normalize-url: 5.1.0(postcss@8.4.19) - postcss-normalize-whitespace: 5.1.1(postcss@8.4.19) - postcss-ordered-values: 5.1.3(postcss@8.4.19) - postcss-reduce-initial: 5.1.2(postcss@8.4.19) - postcss-reduce-transforms: 5.1.0(postcss@8.4.19) - postcss-svgo: 5.1.0(postcss@8.4.19) - postcss-unique-selectors: 5.1.1(postcss@8.4.19) - dev: true - - /cssnano-utils@3.1.0(postcss@8.4.19): + css-declaration-sorter: 6.4.1(postcss@8.4.28) + cssnano-utils: 3.1.0(postcss@8.4.28) + postcss: 8.4.28 + postcss-calc: 8.2.4(postcss@8.4.28) + postcss-colormin: 5.3.1(postcss@8.4.28) + postcss-convert-values: 5.1.3(postcss@8.4.28) + postcss-discard-comments: 5.1.2(postcss@8.4.28) + postcss-discard-duplicates: 5.1.0(postcss@8.4.28) + postcss-discard-empty: 5.1.1(postcss@8.4.28) + postcss-discard-overridden: 5.1.0(postcss@8.4.28) + postcss-merge-longhand: 5.1.7(postcss@8.4.28) + postcss-merge-rules: 5.1.4(postcss@8.4.28) + postcss-minify-font-values: 5.1.0(postcss@8.4.28) + postcss-minify-gradients: 5.1.1(postcss@8.4.28) + postcss-minify-params: 5.1.4(postcss@8.4.28) + postcss-minify-selectors: 5.2.1(postcss@8.4.28) + postcss-normalize-charset: 5.1.0(postcss@8.4.28) + postcss-normalize-display-values: 5.1.0(postcss@8.4.28) + postcss-normalize-positions: 5.1.1(postcss@8.4.28) + postcss-normalize-repeat-style: 5.1.1(postcss@8.4.28) + postcss-normalize-string: 5.1.0(postcss@8.4.28) + postcss-normalize-timing-functions: 5.1.0(postcss@8.4.28) + postcss-normalize-unicode: 5.1.1(postcss@8.4.28) + postcss-normalize-url: 5.1.0(postcss@8.4.28) + postcss-normalize-whitespace: 5.1.1(postcss@8.4.28) + postcss-ordered-values: 5.1.3(postcss@8.4.28) + postcss-reduce-initial: 5.1.2(postcss@8.4.28) + postcss-reduce-transforms: 5.1.0(postcss@8.4.28) + postcss-svgo: 5.1.0(postcss@8.4.28) + postcss-unique-selectors: 5.1.1(postcss@8.4.28) + dev: true + + /cssnano-utils@3.1.0(postcss@8.4.28): resolution: { integrity: sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==, @@ -8642,10 +8642,10 @@ packages: peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.19 + postcss: 8.4.28 dev: true - /cssnano@5.1.15(postcss@8.4.19): + /cssnano@5.1.15(postcss@8.4.28): resolution: { integrity: sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==, @@ -8654,9 +8654,9 @@ packages: peerDependencies: postcss: ^8.2.15 dependencies: - cssnano-preset-default: 5.2.14(postcss@8.4.19) + cssnano-preset-default: 5.2.14(postcss@8.4.28) lilconfig: 2.1.0 - postcss: 8.4.19 + postcss: 8.4.28 yaml: 1.10.2 dev: true @@ -16412,7 +16412,7 @@ packages: } dev: true - /netlify-cli@12.0.11(@types/node@20.5.4): + /netlify-cli@12.0.11(@types/node@20.5.7): resolution: { integrity: sha512-Y6p5/Ro/dPxsRHct52qgfrW4XmHwwdN9iAHDe4PUICV5I3u6a9CR2AzOMBCQPgJBy2RQoB3ctGSloO58Z0VNVA==, @@ -16421,7 +16421,7 @@ packages: hasBin: true requiresBuild: true dependencies: - '@netlify/build': 28.4.5(@types/node@20.5.4) + '@netlify/build': 28.4.5(@types/node@20.5.7) '@netlify/config': 19.1.2 '@netlify/edge-bundler': 2.9.0 '@netlify/framework-info': 9.8.10 @@ -17809,7 +17809,7 @@ packages: engines: { node: '>=6.14' } dev: true - /perfectionist-dfd@3.0.2(postcss@8.4.19): + /perfectionist-dfd@3.0.2(postcss@8.4.28): resolution: { integrity: sha512-FqWkKOprgY/uMgzj8ITNVXsuwcv2X+gJkNQGoK9u9iEh5vqZD0eSZ+p9IXc9KoEr4oUiYDOiYgOXZCSW3NSYOA==, @@ -17821,8 +17821,8 @@ packages: dependencies: defined: 1.0.1 minimist: 1.2.8 - postcss: 8.4.19 - postcss-scss: 4.0.7(postcss@8.4.19) + postcss: 8.4.28 + postcss-scss: 4.0.7(postcss@8.4.28) postcss-value-parser: 4.2.0 read-file-stdin: 0.2.1 semver: 7.5.4 @@ -18063,7 +18063,7 @@ packages: engines: { node: '>=0.10.0' } dev: true - /postcss-calc@8.2.4(postcss@8.4.19): + /postcss-calc@8.2.4(postcss@8.4.28): resolution: { integrity: sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==, @@ -18071,12 +18071,12 @@ packages: peerDependencies: postcss: ^8.2.2 dependencies: - postcss: 8.4.19 + postcss: 8.4.28 postcss-selector-parser: 6.0.13 postcss-value-parser: 4.2.0 dev: true - /postcss-colormin@5.3.1(postcss@8.4.19): + /postcss-colormin@5.3.1(postcss@8.4.28): resolution: { integrity: sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==, @@ -18088,11 +18088,11 @@ packages: browserslist: 4.21.10 caniuse-api: 3.0.0 colord: 2.9.3 - postcss: 8.4.19 + postcss: 8.4.28 postcss-value-parser: 4.2.0 dev: true - /postcss-convert-values@5.1.3(postcss@8.4.19): + /postcss-convert-values@5.1.3(postcss@8.4.28): resolution: { integrity: sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==, @@ -18102,11 +18102,11 @@ packages: postcss: ^8.2.15 dependencies: browserslist: 4.21.10 - postcss: 8.4.19 + postcss: 8.4.28 postcss-value-parser: 4.2.0 dev: true - /postcss-discard-comments@5.1.2(postcss@8.4.19): + /postcss-discard-comments@5.1.2(postcss@8.4.28): resolution: { integrity: sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==, @@ -18115,10 +18115,10 @@ packages: peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.19 + postcss: 8.4.28 dev: true - /postcss-discard-duplicates@5.1.0(postcss@8.4.19): + /postcss-discard-duplicates@5.1.0(postcss@8.4.28): resolution: { integrity: sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==, @@ -18127,10 +18127,10 @@ packages: peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.19 + postcss: 8.4.28 dev: true - /postcss-discard-empty@5.1.1(postcss@8.4.19): + /postcss-discard-empty@5.1.1(postcss@8.4.28): resolution: { integrity: sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==, @@ -18139,10 +18139,10 @@ packages: peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.19 + postcss: 8.4.28 dev: true - /postcss-discard-overridden@5.1.0(postcss@8.4.19): + /postcss-discard-overridden@5.1.0(postcss@8.4.28): resolution: { integrity: sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==, @@ -18151,7 +18151,7 @@ packages: peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.19 + postcss: 8.4.28 dev: true /postcss-import@14.1.0(postcss@8.4.19): @@ -18203,7 +18203,7 @@ packages: yaml: 1.10.2 dev: true - /postcss-merge-longhand@5.1.7(postcss@8.4.19): + /postcss-merge-longhand@5.1.7(postcss@8.4.28): resolution: { integrity: sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==, @@ -18212,12 +18212,12 @@ packages: peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.19 + postcss: 8.4.28 postcss-value-parser: 4.2.0 - stylehacks: 5.1.1(postcss@8.4.19) + stylehacks: 5.1.1(postcss@8.4.28) dev: true - /postcss-merge-rules@5.1.4(postcss@8.4.19): + /postcss-merge-rules@5.1.4(postcss@8.4.28): resolution: { integrity: sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==, @@ -18228,12 +18228,12 @@ packages: dependencies: browserslist: 4.21.10 caniuse-api: 3.0.0 - cssnano-utils: 3.1.0(postcss@8.4.19) - postcss: 8.4.19 + cssnano-utils: 3.1.0(postcss@8.4.28) + postcss: 8.4.28 postcss-selector-parser: 6.0.13 dev: true - /postcss-minify-font-values@5.1.0(postcss@8.4.19): + /postcss-minify-font-values@5.1.0(postcss@8.4.28): resolution: { integrity: sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==, @@ -18242,11 +18242,11 @@ packages: peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.19 + postcss: 8.4.28 postcss-value-parser: 4.2.0 dev: true - /postcss-minify-gradients@5.1.1(postcss@8.4.19): + /postcss-minify-gradients@5.1.1(postcss@8.4.28): resolution: { integrity: sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==, @@ -18256,12 +18256,12 @@ packages: postcss: ^8.2.15 dependencies: colord: 2.9.3 - cssnano-utils: 3.1.0(postcss@8.4.19) - postcss: 8.4.19 + cssnano-utils: 3.1.0(postcss@8.4.28) + postcss: 8.4.28 postcss-value-parser: 4.2.0 dev: true - /postcss-minify-params@5.1.4(postcss@8.4.19): + /postcss-minify-params@5.1.4(postcss@8.4.28): resolution: { integrity: sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==, @@ -18271,12 +18271,12 @@ packages: postcss: ^8.2.15 dependencies: browserslist: 4.21.10 - cssnano-utils: 3.1.0(postcss@8.4.19) - postcss: 8.4.19 + cssnano-utils: 3.1.0(postcss@8.4.28) + postcss: 8.4.28 postcss-value-parser: 4.2.0 dev: true - /postcss-minify-selectors@5.2.1(postcss@8.4.19): + /postcss-minify-selectors@5.2.1(postcss@8.4.28): resolution: { integrity: sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==, @@ -18285,7 +18285,7 @@ packages: peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.19 + postcss: 8.4.28 postcss-selector-parser: 6.0.13 dev: true @@ -18302,7 +18302,7 @@ packages: postcss-selector-parser: 6.0.13 dev: true - /postcss-normalize-charset@5.1.0(postcss@8.4.19): + /postcss-normalize-charset@5.1.0(postcss@8.4.28): resolution: { integrity: sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==, @@ -18311,10 +18311,10 @@ packages: peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.19 + postcss: 8.4.28 dev: true - /postcss-normalize-display-values@5.1.0(postcss@8.4.19): + /postcss-normalize-display-values@5.1.0(postcss@8.4.28): resolution: { integrity: sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==, @@ -18323,11 +18323,11 @@ packages: peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.19 + postcss: 8.4.28 postcss-value-parser: 4.2.0 dev: true - /postcss-normalize-positions@5.1.1(postcss@8.4.19): + /postcss-normalize-positions@5.1.1(postcss@8.4.28): resolution: { integrity: sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==, @@ -18336,11 +18336,11 @@ packages: peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.19 + postcss: 8.4.28 postcss-value-parser: 4.2.0 dev: true - /postcss-normalize-repeat-style@5.1.1(postcss@8.4.19): + /postcss-normalize-repeat-style@5.1.1(postcss@8.4.28): resolution: { integrity: sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==, @@ -18349,11 +18349,11 @@ packages: peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.19 + postcss: 8.4.28 postcss-value-parser: 4.2.0 dev: true - /postcss-normalize-string@5.1.0(postcss@8.4.19): + /postcss-normalize-string@5.1.0(postcss@8.4.28): resolution: { integrity: sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==, @@ -18362,11 +18362,11 @@ packages: peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.19 + postcss: 8.4.28 postcss-value-parser: 4.2.0 dev: true - /postcss-normalize-timing-functions@5.1.0(postcss@8.4.19): + /postcss-normalize-timing-functions@5.1.0(postcss@8.4.28): resolution: { integrity: sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==, @@ -18375,11 +18375,11 @@ packages: peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.19 + postcss: 8.4.28 postcss-value-parser: 4.2.0 dev: true - /postcss-normalize-unicode@5.1.1(postcss@8.4.19): + /postcss-normalize-unicode@5.1.1(postcss@8.4.28): resolution: { integrity: sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==, @@ -18389,11 +18389,11 @@ packages: postcss: ^8.2.15 dependencies: browserslist: 4.21.10 - postcss: 8.4.19 + postcss: 8.4.28 postcss-value-parser: 4.2.0 dev: true - /postcss-normalize-url@5.1.0(postcss@8.4.19): + /postcss-normalize-url@5.1.0(postcss@8.4.28): resolution: { integrity: sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==, @@ -18403,11 +18403,11 @@ packages: postcss: ^8.2.15 dependencies: normalize-url: 6.1.0 - postcss: 8.4.19 + postcss: 8.4.28 postcss-value-parser: 4.2.0 dev: true - /postcss-normalize-whitespace@5.1.1(postcss@8.4.19): + /postcss-normalize-whitespace@5.1.1(postcss@8.4.28): resolution: { integrity: sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==, @@ -18416,11 +18416,11 @@ packages: peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.19 + postcss: 8.4.28 postcss-value-parser: 4.2.0 dev: true - /postcss-ordered-values@5.1.3(postcss@8.4.19): + /postcss-ordered-values@5.1.3(postcss@8.4.28): resolution: { integrity: sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==, @@ -18429,12 +18429,12 @@ packages: peerDependencies: postcss: ^8.2.15 dependencies: - cssnano-utils: 3.1.0(postcss@8.4.19) - postcss: 8.4.19 + cssnano-utils: 3.1.0(postcss@8.4.28) + postcss: 8.4.28 postcss-value-parser: 4.2.0 dev: true - /postcss-reduce-initial@5.1.2(postcss@8.4.19): + /postcss-reduce-initial@5.1.2(postcss@8.4.28): resolution: { integrity: sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==, @@ -18445,10 +18445,10 @@ packages: dependencies: browserslist: 4.21.10 caniuse-api: 3.0.0 - postcss: 8.4.19 + postcss: 8.4.28 dev: true - /postcss-reduce-transforms@5.1.0(postcss@8.4.19): + /postcss-reduce-transforms@5.1.0(postcss@8.4.28): resolution: { integrity: sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==, @@ -18457,11 +18457,11 @@ packages: peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.19 + postcss: 8.4.28 postcss-value-parser: 4.2.0 dev: true - /postcss-scss@4.0.7(postcss@8.4.19): + /postcss-scss@4.0.7(postcss@8.4.28): resolution: { integrity: sha512-xPv2GseoyXPa58Nro7M73ZntttusuCmZdeOojUFR5PZDz2BR62vfYx1w9TyOnp1+nYFowgOMipsCBhxzVkAEPw==, @@ -18470,7 +18470,7 @@ packages: peerDependencies: postcss: ^8.4.19 dependencies: - postcss: 8.4.19 + postcss: 8.4.28 dev: true /postcss-selector-parser@6.0.13: @@ -18484,7 +18484,7 @@ packages: util-deprecate: 1.0.2 dev: true - /postcss-svgo@5.1.0(postcss@8.4.19): + /postcss-svgo@5.1.0(postcss@8.4.28): resolution: { integrity: sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==, @@ -18493,12 +18493,12 @@ packages: peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.19 + postcss: 8.4.28 postcss-value-parser: 4.2.0 svgo: 2.8.0 dev: true - /postcss-unique-selectors@5.1.1(postcss@8.4.19): + /postcss-unique-selectors@5.1.1(postcss@8.4.28): resolution: { integrity: sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==, @@ -18507,7 +18507,7 @@ packages: peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.19 + postcss: 8.4.28 postcss-selector-parser: 6.0.13 dev: true @@ -21109,7 +21109,7 @@ packages: inline-style-parser: 0.1.1 dev: true - /stylehacks@5.1.1(postcss@8.4.19): + /stylehacks@5.1.1(postcss@8.4.28): resolution: { integrity: sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==, @@ -21119,7 +21119,7 @@ packages: postcss: ^8.2.15 dependencies: browserslist: 4.21.10 - postcss: 8.4.19 + postcss: 8.4.28 postcss-selector-parser: 6.0.13 dev: true @@ -21893,7 +21893,7 @@ packages: yn: 3.1.1 dev: true - /ts-node@10.9.1(@types/node@20.5.4)(typescript@4.9.4): + /ts-node@10.9.1(@types/node@20.5.7)(typescript@4.9.4): resolution: { integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==, @@ -21915,7 +21915,7 @@ packages: '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.5.4 + '@types/node': 20.5.7 acorn: 8.10.0 acorn-walk: 8.2.0 arg: 4.1.3 @@ -22841,12 +22841,12 @@ packages: globrex: 0.1.2 recrawl-sync: 2.2.3 tsconfig-paths: 4.2.0 - vite: 3.2.4(@types/node@20.5.4) + vite: 3.2.4(@types/node@20.5.7) transitivePeerDependencies: - supports-color dev: true - /vite@3.2.4(@types/node@20.5.4): + /vite@3.2.4(@types/node@20.5.7): resolution: { integrity: sha512-Z2X6SRAffOUYTa+sLy3NQ7nlHFU100xwanq1WDwqaiFiCe+25zdxP1TfCS5ojPV2oDDcXudHIoPnI1Z/66B7Yw==, @@ -22874,9 +22874,9 @@ packages: terser: optional: true dependencies: - '@types/node': 20.5.4 + '@types/node': 20.5.7 esbuild: 0.15.18 - postcss: 8.4.19 + postcss: 8.4.28 resolve: 1.22.4 rollup: 2.79.1 optionalDependencies: From ed0be7ba1dfee8fb222af1b21203199a6ccdc7bf Mon Sep 17 00:00:00 2001 From: Leifer Mendez Date: Thu, 31 Aug 2023 14:09:48 +0200 Subject: [PATCH 17/17] chore: next version 0.1.32 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 56ba04801..79ab47f30 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bot-whatsapp/root", - "version": "0.1.30", + "version": "0.1.31", "description": "Bot de wahtsapp open source para MVP o pequeños negocios", "main": "app.js", "private": true,