diff --git a/.github/workflows/check-provider-major.yml b/.github/workflows/check-provider-major.yml deleted file mode 100644 index ea91659..0000000 --- a/.github/workflows/check-provider-major.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: Rev Major Providers - -on: - schedule: - - cron: '0 9 * * *' - -jobs: - check-npm: - name: Install Dependencies - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Setup Node - uses: actions/setup-node@v3 - with: - node-version: 16.x - cache: 'yarn' - registry-url: https://registry.npmjs.org/ - - - run: corepack enable - - - name: Install NPM Dependencies - run: yarn install --immutable --network-timeout 300000 - - - name: Check Baileys - run: yarn node ./scripts/checker.js --name=baileys --stable=true - - - name: Check Venom - run: yarn node ./scripts/checker.js --name=venom --stable=true - - - name: Check web-whatsapp - run: yarn node ./scripts/checker.js --name=web-whatsapp --stable=true - - - name: Check Meta - run: yarn node ./scripts/checker.js --name=meta --stable=true - - - name: Check Twilio - run: yarn node ./scripts/checker.js --name=twilio --stable=true - - - name: Add and commit changes to gh-pages branch - run: | - git config --local user.email 'action@github.com' - git config --local user.name 'GitHub Action' - git add . - - - uses: stefanzweifel/git-auto-commit-action@v4 - with: - commit_message: 'ci(providers): check provider versions' - create_branch: true - branch: feature/providers-major diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 87e42a6..6c0935f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,6 +12,7 @@ jobs: runs-on: ubuntu-latest needs: - test-unit + - test-e2e steps: - name: Checkout uses: actions/checkout@v3 @@ -56,20 +57,15 @@ jobs: - name: Unit Tests run: yarn test - ############ UNIT TEST ############ - check-providers: - name: Check Providers Versions + test-e2e: + name: e2e Tests runs-on: ubuntu-latest - outputs: - commit: ${{ steps.vars.outputs.commit }} + needs: + - test-unit steps: - name: Checkout uses: actions/checkout@v3 - with: - fetch-depth: 0 - ref: ${{github.event.after}} - persist-credentials: false - name: Setup Node uses: actions/setup-node@v3 @@ -83,28 +79,5 @@ jobs: - name: Install NPM Dependencies run: yarn install --immutable --network-timeout 300000 - - name: Check Baileys - run: yarn node ./scripts/checker.js --name=baileys --stable=true - - - name: Check Venom - run: yarn node ./scripts/checker.js --name=venom --stable=true - - - name: Check web-whatsapp - run: yarn node ./scripts/checker.js --name=web-whatsapp --stable=true - - - name: Check Meta - run: yarn node ./scripts/checker.js --name=meta --stable=true - - - name: Check Twilio - run: yarn node ./scripts/checker.js --name=twilio --stable=true - - - name: Set output - id: vars - run: echo "commit=$(git log --format=%B -n 1 ${{github.event.after}})" >> $GITHUB_OUTPUT - - - name: Commit & Push changes - uses: actions-js/push@master - with: - branch: feature/providers-major - github_token: ${{ secrets.GITHUB_TOKEN }} - force: true + - name: e2e Tests + run: yarn test.e2e diff --git a/.github/workflows/contributors.yml b/.github/workflows/contributors.yml deleted file mode 100644 index b126e98..0000000 --- a/.github/workflows/contributors.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: Revisando Colaboradores - -on: - schedule: - - cron: '0 9 * * *' - -jobs: - contrib-readme-job: - runs-on: ubuntu-latest - name: A job to automate contrib in readme - steps: - - name: Contribute List - uses: akhilmhdh/contributors-readme-action@v2.3.6 - with: - image_size: 50 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 9f6dbbc..e129511 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ tmp/ .fleet/ example-app*/ base-*/ +test-*.json !starters/apps/base-*/ qr.svg package-lock.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 01adb7f..eae56f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,23 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### [0.1.21](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.20...v0.1.21) (2023-02-12) + + +### Features + +* **provider:** :bug: add_location ([a46a9ef](https://github.com/leifermendez/bot-whatsapp/commit/a46a9efd8dbd921c773395d331154dc9a8aae783)) +* **provider:** :rocket: feat Instance provider ([2278149](https://github.com/leifermendez/bot-whatsapp/commit/227814929561cedc11a1f69c8029515a7f47c9ff)) +* **provider:** :rocket: provider raw ([2d44a00](https://github.com/leifermendez/bot-whatsapp/commit/2d44a002ff226fb0eb7362ad49936f1e00b84242)) +* **provider:** :zap: add location provider ([c7de860](https://github.com/leifermendez/bot-whatsapp/commit/c7de860803fb362f5afe06cc38ad71b2c316d524)) +* **provider:** :zap: add location provider ([c0ece6f](https://github.com/leifermendez/bot-whatsapp/commit/c0ece6feb2b0325476880a604c32de341eb60544)) +* **provider:** :zap: support location � ([a147677](https://github.com/leifermendez/bot-whatsapp/commit/a147677a26b36bba429c3dd3c2c81a44a7a4d2b6)) + + +### Bug Fixes + +* arreglando dir y varios mensajes en dialog flow essential ([01c7db8](https://github.com/leifermendez/bot-whatsapp/commit/01c7db8fe7dda2482eb0aa1fd7b3469b6ae8eae1)) + ### [0.1.20](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.19...v0.1.20) (2023-02-05) diff --git a/__mocks__/env.js b/__mocks__/env.js new file mode 100644 index 0000000..815807a --- /dev/null +++ b/__mocks__/env.js @@ -0,0 +1,50 @@ +const MOCK_DB = require('../packages/database/src/mock') +const PROVIDER_DB = require('../packages/provider/src/mock') + +class MOCK_FLOW { + allCallbacks = { ref: () => 1 } + flowSerialize = [] + flowRaw = [] + find = (arg) => { + if (arg) { + return [{ answer: 'answer', ref: 'ref' }] + } else { + return null + } + } + findBySerialize = () => ({}) + findIndexByRef = () => 0 +} + +const cleaName = (name) => { + name = name.toLowerCase() + name = name.replaceAll(' ', '-') + name = name.replaceAll(':', '-') + name = name.replaceAll('"', '-') + return name +} + +/** + * Preparar env para el test + * @param {*} context + */ +const setup = async (context) => { + const name = cleaName(`${context.__suite__}-${context.__test__}`) + const filename = `test-${name}.json` + context.provider = new PROVIDER_DB() + context.database = new MOCK_DB({ filename }) + context.flow = new MOCK_FLOW() + await delay(10) +} + +const clear = async (context) => { + context.provider = null + context.database = null + context.flow = null +} + +function delay(ms) { + return new Promise((res) => setTimeout(res, ms)) +} + +module.exports = { setup, clear, delay } diff --git a/__mocks__/mobile.mock.js b/__mocks__/mobile.mock.js deleted file mode 100644 index 7f19f74..0000000 --- a/__mocks__/mobile.mock.js +++ /dev/null @@ -1,6 +0,0 @@ -const MOCK_MOBILE_WS = { - from: 'XXXXXX', - hasMedia: false, -} - -module.exports = { MOCK_MOBILE_WS } diff --git a/__test__/0.0.0-case.test.js b/__test__/0.0.0-case.test.js new file mode 100644 index 0000000..b234eb8 --- /dev/null +++ b/__test__/0.0.0-case.test.js @@ -0,0 +1,51 @@ +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: Simple') + +suiteCase.before.each(setup) +suiteCase.after.each(clear) + +suiteCase(`Responder a "hola"`, async ({ database, provider }) => { + const flow = addKeyword('hola').addAnswer('Buenas!').addAnswer('Como vamos!') + + createBot({ + database, + provider, + flow: createFlow([flow]), + }) + + await provider.delaySendMessage(0, 'message', { + from: '000', + body: 'hola', + }) + + await delay(10) + assert.is('Buenas!', database.listHistory[0].answer) + assert.is('Como vamos!', database.listHistory[1].answer) + assert.is(undefined, database.listHistory[2]) +}) + +suiteCase(`NO reponder a "pepe"`, async ({ database, provider }) => { + const flow = addKeyword('hola').addAnswer('Buenas!').addAnswer('Como vamos!') + + createBot({ + database, + provider, + flow: createFlow([flow]), + }) + + await provider.delaySendMessage(0, 'message', { + from: '000', + body: 'pepe', + }) + + await delay(100) + + assert.is(undefined, database.listHistory[0]) + assert.is(undefined, database.listHistory[1]) +}) + +suiteCase.run() diff --git a/__test__/0.0.1-case.test.js b/__test__/0.0.1-case.test.js new file mode 100644 index 0000000..1f71435 --- /dev/null +++ b/__test__/0.0.1-case.test.js @@ -0,0 +1,31 @@ +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: Provider envia un location') + +suiteCase.before.each(setup) +suiteCase.after.each(clear) + +suiteCase(`Responder a "CURRENT_LOCATION"`, async ({ database, provider }) => { + const flow = addKeyword('#CURRENT_LOCATION#').addAnswer('Gracias por tu location') + + createBot({ + database, + provider, + flow: createFlow([flow]), + }) + + await provider.delaySendMessage(0, 'message', { + from: '000', + body: '#CURRENT_LOCATION#', + }) + + await delay(200) + const getHistory = database.listHistory.map((i) => i.answer) + assert.is('Gracias por tu location', getHistory[0]) + assert.is(undefined, getHistory[1]) +}) + +suiteCase.run() diff --git a/__test__/0.1.0-case.test.js b/__test__/0.1.0-case.test.js new file mode 100644 index 0000000..2380ede --- /dev/null +++ b/__test__/0.1.0-case.test.js @@ -0,0 +1,51 @@ +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: sensitive') + +suiteCase.before.each(setup) +suiteCase.after.each(clear) + +suiteCase(`Responder a "ole" en minuscula`, async ({ database, provider }) => { + const flow = addKeyword(['ola', 'ole'], { sensitive: true }).addAnswer('Bienvenido a la OLA') + + createBot({ + database, + provider, + flow: createFlow([flow]), + }) + + await provider.delaySendMessage(0, 'message', { + from: '000', + body: 'ole', + }) + + await delay(100) + + assert.is('Bienvenido a la OLA', database.listHistory[0].answer) + assert.is(undefined, database.listHistory[1]) +}) + +suiteCase(`NO Responder a "ole" en minuscula`, async ({ database, provider }) => { + const flow = addKeyword(['ola', 'ole'], { sensitive: true }).addAnswer('Bienvenido a la OLA') + + createBot({ + database, + provider, + flow: createFlow([flow]), + }) + + await provider.delaySendMessage(0, 'message', { + from: '000', + body: 'OLE', + }) + + await delay(100) + + assert.is(undefined, database.listHistory[0]) + assert.is(undefined, database.listHistory[1]) +}) + +suiteCase.run() diff --git a/__test__/0.1.1-case.test.js b/__test__/0.1.1-case.test.js new file mode 100644 index 0000000..499d2ec --- /dev/null +++ b/__test__/0.1.1-case.test.js @@ -0,0 +1,89 @@ +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 fakeHTTP = async () => { + await delay(10) +} + +const suiteCase = suite('Flujo: hijos con callbacks') + +suiteCase.before.each(setup) +suiteCase.after.each(clear) + +suiteCase(`Debe continuar el flujo del hijo`, async ({ database, provider }) => { + const flowCash = addKeyword('cash').addAnswer('Traeme los billetes! 😎') + + const flowOnline = addKeyword('paypal') + .addAnswer('Voy generar un link de paypal *escribe algo*', { capture: true }, async (_, { flowDynamic }) => { + await fakeHTTP() + await flowDynamic('Esperate.... estoy generando esto toma su tiempo') + }) + .addAnswer('Aqui lo tienes 😎😎', null, async (_, { flowDynamic }) => { + await fakeHTTP() + await flowDynamic('http://paypal.com') + }) + .addAnswer('Apurate!') + + const flujoPrincipal = addKeyword('hola') + .addAnswer('¿Como estas todo bien?') + .addAnswer('Espero que si') + .addAnswer('¿Cual es tu email?', { capture: true }, async (ctx, { fallBack }) => { + if (!ctx.body.includes('@')) { + return fallBack('Veo que no es um mail *bien*') + } + }) + .addAnswer('Voy a validar tu email...', null, async (_, { flowDynamic }) => { + await fakeHTTP() + return flowDynamic('Email validado correctamten!') + }) + .addAnswer('¿Como vas a pagar *paypal* o *cash*?', { capture: true }, async () => {}, [flowCash, flowOnline]) + + createBot({ + database, + flow: createFlow([flujoPrincipal]), + provider, + }) + + await provider.delaySendMessage(0, 'message', { + from: '000', + body: 'hola', + }) + + await provider.delaySendMessage(30, 'message', { + from: '000', + body: 'test@test.com', + }) + + await provider.delaySendMessage(60, 'message', { + from: '000', + body: 'paypal', + }) + + await provider.delaySendMessage(90, 'message', { + from: '000', + body: 'continue!', + }) + + await delay(800) + const getHistory = database.listHistory.map((i) => i.answer) + + assert.is('¿Como estas todo bien?', getHistory[0]) + assert.is('Espero que si', getHistory[1]) + assert.is('¿Cual es tu email?', getHistory[2]) + assert.is('test@test.com', getHistory[3]) + assert.is('Voy a validar tu email...', getHistory[4]) + assert.is('Email validado correctamten!', getHistory[5]) + assert.is('¿Como vas a pagar *paypal* o *cash*?', getHistory[6]) + assert.is('paypal', getHistory[7]) + assert.is('Voy generar un link de paypal *escribe algo*', getHistory[8]) + assert.is('continue!', getHistory[9]) + assert.is('Esperate.... estoy generando esto toma su tiempo', getHistory[10]) + assert.is('Aqui lo tienes 😎😎', getHistory[11]) + assert.is('http://paypal.com', getHistory[12]) + assert.is('Apurate!', getHistory[13]) + assert.is(undefined, getHistory[14]) +}) + +suiteCase.run() diff --git a/__test__/0.1.2-case.test.js b/__test__/0.1.2-case.test.js new file mode 100644 index 0000000..cd0dbcd --- /dev/null +++ b/__test__/0.1.2-case.test.js @@ -0,0 +1,59 @@ +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: regex') + +suiteCase.before.each(setup) +suiteCase.after.each(clear) + +suiteCase(`Responder a una expresion regular`, async ({ database, provider }) => { + 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!') + + createBot({ + database, + provider, + flow: createFlow([flow]), + }) + + await provider.delaySendMessage(0, 'message', { + from: '000', + body: '374245455400126', + }) + + await delay(100) + + assert.is('Gracias por proporcionar un numero de tarjeta valido', database.listHistory[0].answer) + assert.is('Fin!', database.listHistory[1].answer) + assert.is(undefined, database.listHistory[2]) +}) + +suiteCase(`NO Responder a una expresion regular`, async ({ database, provider }) => { + 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!') + + createBot({ + database, + provider, + flow: createFlow([flow]), + }) + + await provider.delaySendMessage(0, 'message', { + from: '000', + body: 'hola', + }) + + await delay(100) + + assert.is(undefined, database.listHistory[0]) + assert.is(undefined, database.listHistory[1]) +}) + +suiteCase.run() diff --git a/__test__/0.1.3-case.test.js b/__test__/0.1.3-case.test.js new file mode 100644 index 0000000..1893583 --- /dev/null +++ b/__test__/0.1.3-case.test.js @@ -0,0 +1,40 @@ +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: capture') + +suiteCase.before.each(setup) +suiteCase.after.each(clear) + +suiteCase(`Responder a "pregunta"`, async ({ database, provider }) => { + const flow = addKeyword(['hola']) + .addAnswer(['Hola como estas?', '¿Cual es tu edad?'], { capture: true }) + .addAnswer('Gracias por tu respuesta') + + createBot({ + database, + provider, + flow: createFlow([flow]), + }) + + await provider.delaySendMessage(0, 'message', { + from: '000', + body: 'hola', + }) + + await provider.delaySendMessage(10, 'message', { + from: '000', + body: '90', + }) + + await delay(100) + + assert.is(['Hola como estas?', '¿Cual es tu edad?'].join('\n'), database.listHistory[0].answer) + assert.is('90', database.listHistory[1].answer) + assert.is('Gracias por tu respuesta', database.listHistory[2].answer) + assert.is(undefined, database.listHistory[3]) +}) + +suiteCase.run() diff --git a/__test__/0.1.4-case.test.js b/__test__/0.1.4-case.test.js new file mode 100644 index 0000000..c741c2c --- /dev/null +++ b/__test__/0.1.4-case.test.js @@ -0,0 +1,162 @@ +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 fakeHTTP = async (fakeData = []) => { + await delay(50) + const data = fakeData.map((u) => ({ body: `${u}` })) + return Promise.resolve(data) +} + +const suiteCase = suite('Flujo: flowDynamic') + +suiteCase.before.each(setup) +suiteCase.after.each(clear) + +suiteCase(`Responder con mensajes asyncronos`, async ({ database, provider }) => { + const MOCK_VALUES = [ + 'Bienvenido te envio muchas marcas (5510)', + 'Seleccione marca del auto a cotizar, con el *número* correspondiente', + 'Seleccione la sub marca del auto a cotizar, con el *número* correspondiente:', + 'Los precios rondan:', + ] + const flow = addKeyword(['hola']) + .addAnswer(MOCK_VALUES[0], null, async (_, { flowDynamic }) => { + const data = await fakeHTTP(['Ford', 'GM', 'BMW']) + return flowDynamic(data) + }) + .addAnswer(MOCK_VALUES[1], null, async (_, { flowDynamic }) => { + const data = await fakeHTTP(['Ranger', 'Explorer']) + return flowDynamic(data) + }) + .addAnswer(MOCK_VALUES[2], null, async (_, { flowDynamic }) => { + const data = await fakeHTTP(['Usado', 'Nuevos']) + return flowDynamic(data) + }) + .addAnswer(MOCK_VALUES[3], null, async (_, { flowDynamic }) => { + const data = await fakeHTTP(['1000', '2000', '3000']) + return flowDynamic(data) + }) + + createBot({ + database, + provider, + flow: createFlow([flow]), + }) + + await provider.delaySendMessage(0, 'message', { + from: '000', + body: 'hola', + }) + + await delay(1500) + const getHistory = database.listHistory.map((i) => i.answer) + assert.is(MOCK_VALUES[0], getHistory[0]) + + //FlowDynamic + assert.is('Ford', getHistory[1]) + assert.is('GM', getHistory[2]) + assert.is('BMW', getHistory[3]) + + assert.is(MOCK_VALUES[1], getHistory[4]) + + //FlowDynamic + assert.is('Ranger', getHistory[5]) + assert.is('Explorer', getHistory[6]) + + assert.is(MOCK_VALUES[2], getHistory[7]) + + //FlowDynamic + assert.is('Usado', getHistory[8]) + assert.is('Nuevos', getHistory[9]) + + assert.is(MOCK_VALUES[3], getHistory[10]) + + //FlowDynamic + assert.is('1000', getHistory[11]) + assert.is('2000', getHistory[12]) + assert.is('3000', getHistory[13]) + assert.is(undefined, getHistory[14]) +}) + +suiteCase(`Responder con un "string"`, async ({ database, provider }) => { + const flow = addKeyword(['hola']) + .addAnswer('Como vas?', null, async (_, { flowDynamic }) => { + return flowDynamic('Todo bien!') + }) + .addAnswer('y vos?') + + 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('Como vas?', getHistory[0]) + assert.is('Todo bien!', getHistory[1]) + assert.is('y vos?', getHistory[2]) + assert.is(undefined, getHistory[3]) +}) + +suiteCase(`Responder con un "array"`, async ({ database, provider }) => { + const flow = addKeyword(['hola']) + .addAnswer('Como vas?', null, async (_, { flowDynamic }) => { + return flowDynamic(['Todo bien!', 'trabajando']) + }) + .addAnswer('y vos?') + + 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('Como vas?', getHistory[0]) + assert.is('Todo bien!', getHistory[1]) + assert.is('trabajando', getHistory[2]) + assert.is('y vos?', getHistory[3]) + assert.is(undefined, getHistory[4]) +}) + +suiteCase(`Responder con un "object"`, async ({ database, provider }) => { + const flow = addKeyword(['hola']) + .addAnswer('Como vas?', null, async (_, { flowDynamic }) => { + return flowDynamic([{ body: 'Todo bien!' }]) + }) + .addAnswer('y vos?') + + 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('Como vas?', getHistory[0]) + assert.is('Todo bien!', getHistory[1]) + assert.is('y vos?', getHistory[2]) + assert.is(undefined, getHistory[3]) +}) + +suiteCase.run() diff --git a/__test__/0.1.5-case.test.js b/__test__/0.1.5-case.test.js new file mode 100644 index 0000000..8b9e310 --- /dev/null +++ b/__test__/0.1.5-case.test.js @@ -0,0 +1,167 @@ +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 fakeHTTP = async (fakeData = []) => { + await delay(50) + const data = fakeData.map((u) => ({ body: `${u}` })) + return Promise.resolve(data) +} + +const suiteCase = suite('Flujo: endFlow') + +suiteCase.before.each(setup) +suiteCase.after.each(clear) + +suiteCase(`Detener el flujo`, async ({ database, provider }) => { + const MOCK_VALUES = [ + 'Bienvenido te envio muchas marcas', + 'Seleccione marca del auto a cotizar, con el *número* correspondiente', + 'Seleccione la sub marca del auto a cotizar, con el *número* correspondiente:', + 'Los precios rondan:', + ] + const flow = addKeyword(['hola']) + .addAnswer(MOCK_VALUES[0], null, async (_, { flowDynamic }) => { + const data = await fakeHTTP(['Ford', 'GM', 'BMW']) + return flowDynamic(data) + }) + .addAnswer(MOCK_VALUES[1], null, async (_, { endFlow }) => { + return endFlow() + }) + .addAnswer(MOCK_VALUES[2]) + .addAnswer(MOCK_VALUES[3], null, async (_, { flowDynamic }) => { + const data = await fakeHTTP(['1000', '2000', '3000']) + return flowDynamic(data) + }) + + createBot({ + database, + provider, + flow: createFlow([flow]), + }) + + await provider.delaySendMessage(0, 'message', { + from: '000', + body: 'hola', + }) + + await delay(900) + const getHistory = database.listHistory.map((i) => i.answer) + assert.is(MOCK_VALUES[0], getHistory[0]) + + //FlowDynamic + assert.is('Ford', getHistory[1]) + assert.is('GM', getHistory[2]) + assert.is('BMW', getHistory[3]) + + assert.is(MOCK_VALUES[1], getHistory[4]) + + //FlowDynamic + assert.is(undefined, getHistory[5]) + assert.is(undefined, getHistory[6]) +}) + +suiteCase(`Detener el flujo flowDynamic`, async ({ database, provider }) => { + const flow = addKeyword(['hola']) + .addAnswer('Buenas!', null, async (_, { endFlow, flowDynamic }) => { + await flowDynamic('Continuamos...') + return endFlow() + }) + .addAnswer('Como estas!') + + 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('Buenas!', getHistory[0]) + assert.is('Continuamos...', getHistory[1]) + assert.is(undefined, getHistory[2]) +}) + +suiteCase(`flowDynamic con capture`, async ({ database, provider }) => { + const MOCK_VALUES = ['¿CUal es tu email?', 'Continuamos....', '¿Cual es tu edad?'] + + const flow = addKeyword(['hola']) + .addAnswer( + MOCK_VALUES[0], + { + capture: true, + }, + async (ctx, { flowDynamic, fallBack }) => { + const validation = ctx.body.includes('@') + + if (validation) { + const getDataFromApi = await fakeHTTP(['Gracias por tu email se ha validado de manera correcta']) + return flowDynamic(getDataFromApi) + } + return fallBack() + } + ) + .addAnswer(MOCK_VALUES[1]) + .addAnswer(MOCK_VALUES[2], { capture: true }, async (ctx, { flowDynamic, fallBack }) => { + if (ctx.body !== '18') { + await delay(20) + return fallBack('Ups creo que no eres mayor de edad') + } + return flowDynamic('Bien tu edad es correcta!') + }) + .addAnswer('Puedes pasar') + + createBot({ + database, + provider, + flow: createFlow([flow]), + }) + + await provider.delaySendMessage(0, 'message', { + from: '000', + body: 'hola', + }) + + await provider.delaySendMessage(10, 'message', { + from: '000', + body: 'this is not email value', + }) + + await provider.delaySendMessage(20, 'message', { + from: '000', + body: 'test@test.com', + }) + + await provider.delaySendMessage(90, 'message', { + from: '000', + body: '20', + }) + + await provider.delaySendMessage(200, 'message', { + from: '000', + body: '18', + }) + + await delay(900) + const getHistory = database.listHistory.map((i) => i.answer) + assert.is(MOCK_VALUES[0], getHistory[0]) + assert.is('this is not email value', getHistory[1]) + assert.is(MOCK_VALUES[0], getHistory[2]) + assert.is('test@test.com', getHistory[3]) + assert.is('Gracias por tu email se ha validado de manera correcta', getHistory[4]) + assert.is(MOCK_VALUES[1], getHistory[5]) + assert.is(MOCK_VALUES[2], getHistory[6]) + assert.is('20', getHistory[7]) + assert.is('Ups creo que no eres mayor de edad', getHistory[8]) + assert.is('18', getHistory[9]) + assert.is('Bien tu edad es correcta!', getHistory[10]) + assert.is('Puedes pasar', getHistory[11]) +}) + +suiteCase.run() diff --git a/__test__/0.1.6-case.test.js b/__test__/0.1.6-case.test.js new file mode 100644 index 0000000..3473aab --- /dev/null +++ b/__test__/0.1.6-case.test.js @@ -0,0 +1,97 @@ +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: manejo de estado') + +suiteCase.before.each(setup) +suiteCase.after.each(clear) + +suiteCase(`Debe retornar un mensaje resumen`, async ({ database, provider }) => { + let STATE_APP = {} + const MOCK_VALUES = ['¿Cual es tu nombre?', '¿Cual es tu edad?', 'Tu datos son:'] + + const flujoPrincipal = addKeyword(['hola']) + .addAnswer( + MOCK_VALUES[0], + { + capture: true, + }, + async (ctx, { flowDynamic }) => { + STATE_APP[ctx.from] = { ...STATE_APP[ctx.from], name: ctx.body } + + flowDynamic('Gracias por tu nombre!') + } + ) + .addAnswer( + MOCK_VALUES[1], + { + capture: true, + }, + async (ctx, { flowDynamic }) => { + STATE_APP[ctx.from] = { ...STATE_APP[ctx.from], age: ctx.body } + + await flowDynamic(`Gracias por tu edad! ${STATE_APP[ctx.from].name}`) + } + ) + .addAnswer(MOCK_VALUES[2], null, async (ctx, { flowDynamic }) => { + flowDynamic(`Nombre: ${STATE_APP[ctx.from].name} Edad: ${STATE_APP[ctx.from].age}`) + }) + .addAnswer('🤖🤖 Gracias por tu participacion') + + createBot({ + database, + flow: createFlow([flujoPrincipal]), + provider, + }) + + await provider.delaySendMessage(0, 'message', { + from: '000', + body: 'hola', + }) + + await provider.delaySendMessage(5, 'message', { + from: '001', + body: 'hola', + }) + + await provider.delaySendMessage(10, 'message', { + from: '000', + body: 'Leifer', + }) + + await provider.delaySendMessage(15, 'message', { + from: '000', + body: '90', + }) + + await provider.delaySendMessage(20, 'message', { + from: '001', + body: 'Maria', + }) + + await provider.delaySendMessage(25, 'message', { + from: '001', + body: '100', + }) + + await delay(1000) + const getHistory = database.listHistory.map((i) => i.answer) + assert.is(MOCK_VALUES[0], getHistory[0]) + assert.is('¿Cual es tu nombre?', getHistory[1]) + assert.is('Leifer', getHistory[2]) + assert.is('Gracias por tu nombre!', getHistory[3]) + assert.is('¿Cual es tu edad?', getHistory[4]) + assert.is('90', getHistory[5]) + assert.is('Gracias por tu edad! Leifer', getHistory[6]) + 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('Gracias por tu nombre!', getHistory[11]) + assert.is('100', getHistory[12]) + assert.is(undefined, getHistory[13]) +}) + +suiteCase.run() diff --git a/__test__/01-case.test.js b/__test__/01-case.test.js deleted file mode 100644 index 57ae08c..0000000 --- a/__test__/01-case.test.js +++ /dev/null @@ -1,36 +0,0 @@ -const { test } = require('uvu') -const assert = require('uvu/assert') -const MOCK_DB = require('../packages/database/src/mock') -const PROVIDER_DB = require('../packages/provider/src/mock') -const { addKeyword, createBot, createFlow, createProvider } = require('../packages/bot/index') - -test(`[Caso - 01] Flow Basico`, async () => { - const [VALUE_A, VALUE_B] = ['hola', 'buenas'] - - const flow = addKeyword(VALUE_A).addAnswer(VALUE_B) - const provider = createProvider(PROVIDER_DB) - const database = new MOCK_DB() - - createBot({ - database, - flow: createFlow([flow]), - provider, - }) - - provider.delaySendMessage(100, 'message', { - from: '000', - body: VALUE_A, - }) - - await delay(100) - - const prevMsg = database.getPrevByNumber('000') - - assert.is(prevMsg.answer, VALUE_B) -}) - -test.run() - -function delay(ms) { - return new Promise((res) => setTimeout(res, ms)) -} diff --git a/__test__/02-case.test.js b/__test__/02-case.test.js deleted file mode 100644 index 855a566..0000000 --- a/__test__/02-case.test.js +++ /dev/null @@ -1,94 +0,0 @@ -const { test } = require('uvu') -const assert = require('uvu/assert') -const MOCK_DB = require('../packages/database/src/mock') -const PROVIDER_DB = require('../packages/provider/src/mock') -const { addKeyword, createBot, createFlow, createProvider } = require('../packages/bot/index') - -/** - * Falsear peticion async - * @param {*} fakeData - * @returns - */ -const fakeHTTP = async (fakeData = []) => { - console.log('⚡ Server request!') - await delay(50) - console.log('⚡ Server return!') - const data = fakeData.map((u, i) => ({ body: `${i + 1} ${u}` })) - console.log(data) - return Promise.resolve(data) -} - -test(`[Caso - 02] Flow (flowDynamic)`, async () => { - const MOCK_VALUES = [ - 'Bienvenido te envio muchas marcas (5510)', - 'Seleccione marca del auto a cotizar, con el *número* correspondiente', - 'Seleccione la sub marca del auto a cotizar, con el *número* correspondiente:', - 'Los precios rondan:', - ] - const provider = createProvider(PROVIDER_DB) - const database = new MOCK_DB() - - const flujoPrincipal = addKeyword(['hola']) - .addAnswer(MOCK_VALUES[0], null, async (ctx, { flowDynamic }) => { - console.log('execute...') - const data = await fakeHTTP(['Ford', 'GM', 'BMW']) - return flowDynamic(data) - }) - .addAnswer(MOCK_VALUES[1], null, async (ctx, { flowDynamic }) => { - const data = await fakeHTTP(['Ranger', 'Explorer']) - return flowDynamic(data) - }) - .addAnswer(MOCK_VALUES[2], null, async (ctx, { flowDynamic }) => { - const data = await fakeHTTP(['Usado', 'Nuevos']) - return flowDynamic(data) - }) - .addAnswer(MOCK_VALUES[3], null, async (ctx, { flowDynamic }) => { - const data = await fakeHTTP(['1000', '2000', '3000']) - return flowDynamic(data) - }) - - createBot({ - database, - flow: createFlow([flujoPrincipal]), - provider, - }) - - provider.delaySendMessage(0, 'message', { - from: '000', - body: 'hola', - }) - - await delay(1200) - const getHistory = database.listHistory.map((i) => i.answer) - assert.is(MOCK_VALUES[0], getHistory[0]) - - //FlowDynamic - assert.is('1 Ford', getHistory[1]) - assert.is('2 GM', getHistory[2]) - assert.is('3 BMW', getHistory[3]) - - assert.is(MOCK_VALUES[1], getHistory[4]) - - //FlowDynamic - assert.is('1 Ranger', getHistory[5]) - assert.is('2 Explorer', getHistory[6]) - - assert.is(MOCK_VALUES[2], getHistory[7]) - - //FlowDynamic - assert.is('1 Usado', getHistory[8]) - assert.is('2 Nuevos', getHistory[9]) - - assert.is(MOCK_VALUES[3], getHistory[10]) - - //FlowDynamic - assert.is('1 1000', getHistory[11]) - assert.is('2 2000', getHistory[12]) - assert.is('3 3000', getHistory[13]) -}) - -test.run() - -function delay(ms) { - return new Promise((res) => setTimeout(res, ms)) -} diff --git a/__test__/03-case.test.js b/__test__/03-case.test.js deleted file mode 100644 index 646b5d5..0000000 --- a/__test__/03-case.test.js +++ /dev/null @@ -1,37 +0,0 @@ -const { test } = require('uvu') -const assert = require('uvu/assert') -const MOCK_DB = require('../packages/database/src/mock') -const PROVIDER_DB = require('../packages/provider/src/mock') -const { addKeyword, createBot, createFlow, createProvider } = require('../packages/bot/index') - -test(`[Caso - 03] Flow puro`, async () => { - const MOCK_VALUES = ['Bienvenido a mi tienda', 'Como estas?'] - - const provider = createProvider(PROVIDER_DB) - const database = new MOCK_DB() - - const flujoPrincipal = addKeyword(['hola']).addAnswer(MOCK_VALUES[0]).addAnswer(MOCK_VALUES[1]) - - createBot({ - database, - flow: createFlow([flujoPrincipal]), - provider, - }) - - provider.delaySendMessage(0, 'message', { - from: '000', - body: 'hola', - }) - - await delay(10) - const getHistory = database.listHistory.map((i) => i.answer) - - assert.is(MOCK_VALUES[0], getHistory[0]) - assert.is(MOCK_VALUES[1], getHistory[1]) -}) - -test.run() - -function delay(ms) { - return new Promise((res) => setTimeout(res, ms)) -} diff --git a/__test__/04-case.test.js b/__test__/04-case.test.js deleted file mode 100644 index 8fcaeda..0000000 --- a/__test__/04-case.test.js +++ /dev/null @@ -1,77 +0,0 @@ -const { test } = require('uvu') -const assert = require('uvu/assert') -const MOCK_DB = require('../packages/database/src/mock') -const PROVIDER_DB = require('../packages/provider/src/mock') -const { addKeyword, createBot, createFlow, createProvider } = require('../packages/bot/index') - -/** - * Falsear peticion async - * @param {*} fakeData - * @returns - */ -const fakeHTTP = async (fakeData = []) => { - console.log('⚡ Server request!') - await delay(50) - console.log('⚡ Server return!') - const data = fakeData.map((u, i) => ({ body: `${i + 1} ${u}` })) - console.log(data) - return Promise.resolve(data) -} - -test(`[Caso - 04] Romper flujo (endFlow)`, async () => { - const MOCK_VALUES = [ - 'Bienvenido te envio muchas marcas (5510)', - 'Seleccione marca del auto a cotizar, con el *número* correspondiente', - 'Seleccione la sub marca del auto a cotizar, con el *número* correspondiente:', - 'Los precios rondan:', - ] - const provider = createProvider(PROVIDER_DB) - const database = new MOCK_DB() - - const flujoPrincipal = addKeyword(['hola']) - .addAnswer(MOCK_VALUES[0], null, async (ctx, { flowDynamic }) => { - console.log('execute...') - const data = await fakeHTTP(['Ford', 'GM', 'BMW']) - return flowDynamic(data) - }) - .addAnswer(MOCK_VALUES[1], null, async (ctx, { endFlow }) => { - return endFlow() - }) - .addAnswer(MOCK_VALUES[2], null, async (ctx, { flowDynamic }) => { - const data = await fakeHTTP(['Usado', 'Nuevos']) - return flowDynamic(data) - }) - .addAnswer(MOCK_VALUES[3], null, async (ctx, { flowDynamic }) => { - const data = await fakeHTTP(['1000', '2000', '3000']) - return flowDynamic(data) - }) - - createBot({ - database, - flow: createFlow([flujoPrincipal]), - provider, - }) - - provider.delaySendMessage(0, 'message', { - from: '000', - body: 'hola', - }) - - await delay(1200) - const getHistory = database.listHistory.map((i) => i.answer) - assert.is(MOCK_VALUES[0], getHistory[0]) - - //FlowDynamic - assert.is('1 Ford', getHistory[1]) - assert.is('2 GM', getHistory[2]) - assert.is('3 BMW', getHistory[3]) - - assert.is(MOCK_VALUES[1], getHistory[4]) - assert.is(undefined, getHistory[5]) -}) - -test.run() - -function delay(ms) { - return new Promise((res) => setTimeout(res, ms)) -} diff --git a/__test__/05-case.test.js b/__test__/05-case.test.js deleted file mode 100644 index f61d114..0000000 --- a/__test__/05-case.test.js +++ /dev/null @@ -1,100 +0,0 @@ -const { test } = require('uvu') -const assert = require('uvu/assert') -const MOCK_DB = require('../packages/database/src/mock') -const PROVIDER_DB = require('../packages/provider/src/mock') -const { addKeyword, createBot, createFlow, createProvider } = require('../packages/bot/index') - -/** - * Falsear peticion async - * @param {*} fakeData - * @returns - */ -const fakeHTTP = async (fakeData = []) => { - await delay(5) - const data = fakeData.map((u, i) => ({ body: `${i + 1} ${u}` })) - return Promise.resolve(data) -} - -test(`[Caso - 05] Continuar Flujo (continueFlow)`, async () => { - const MOCK_VALUES = ['¿CUal es tu email?', 'Continuamos....', '¿Cual es tu edad?'] - const provider = createProvider(PROVIDER_DB) - const database = new MOCK_DB() - - const flujoPrincipal = addKeyword(['hola']) - .addAnswer( - MOCK_VALUES[0], - { - capture: true, - }, - async (ctx, { flowDynamic, fallBack }) => { - const validation = ctx.body.includes('@') - - if (validation) { - const getDataFromApi = await fakeHTTP(['Gracias por tu email se ha validado de manera correcta']) - return flowDynamic(getDataFromApi) - } - return fallBack(validation) - } - ) - .addAnswer(MOCK_VALUES[1]) - .addAnswer(MOCK_VALUES[2], { capture: true }, async (ctx, { flowDynamic, fallBack }) => { - if (ctx.body !== '18') { - await delay(50) - return fallBack(false, 'Ups creo que no eres mayor de edad') - } - return flowDynamic('Bien tu edad es correcta!') - }) - .addAnswer('Puedes pasar') - - createBot({ - database, - flow: createFlow([flujoPrincipal]), - provider, - }) - - provider.delaySendMessage(0, 'message', { - from: '000', - body: 'hola', - }) - - provider.delaySendMessage(10, 'message', { - from: '000', - body: 'this is not email value', - }) - - provider.delaySendMessage(20, 'message', { - from: '000', - body: 'test@test.com', - }) - - provider.delaySendMessage(90, 'message', { - from: '000', - body: '20', - }) - - provider.delaySendMessage(200, 'message', { - from: '000', - body: '18', - }) - - await delay(1200) - const getHistory = database.listHistory.map((i) => i.answer) - assert.is(MOCK_VALUES[0], getHistory[0]) - assert.is('this is not email value', getHistory[1]) - assert.is(MOCK_VALUES[0], getHistory[2]) - assert.is('test@test.com', getHistory[3]) - assert.is('1 Gracias por tu email se ha validado de manera correcta', getHistory[4]) - assert.is(MOCK_VALUES[1], getHistory[5]) - assert.is(MOCK_VALUES[2], getHistory[6]) - assert.is('20', getHistory[7]) - assert.is('Ups creo que no eres mayor de edad', getHistory[8]) - assert.is('18', getHistory[9]) - assert.is('Bien tu edad es correcta!', getHistory[10]) - assert.is('Puedes pasar', getHistory[11]) -}) - -test.run() - -function delay(ms) { - return new Promise((res) => setTimeout(res, ms)) -} diff --git a/__test__/06-case.test.js b/__test__/06-case.test.js deleted file mode 100644 index 355cbd4..0000000 --- a/__test__/06-case.test.js +++ /dev/null @@ -1,93 +0,0 @@ -const { test } = require('uvu') -const assert = require('uvu/assert') -const MOCK_DB = require('../packages/database/src/mock') -const PROVIDER_DB = require('../packages/provider/src/mock') -const { addKeyword, createBot, createFlow, createProvider } = require('../packages/bot/index') - -/** - * Falsear peticion async - * @param {*} fakeData - * @returns - */ -const fakeHTTP = async (fakeData = []) => { - await delay(5) - const data = fakeData.map((u, i) => ({ body: `${i + 1} ${u}` })) - return Promise.resolve(data) -} - -test(`[Caso - 06] Finalizar Flujo (endFlow)`, async () => { - const MOCK_VALUES = ['¿CUal es tu email?', 'Continuamos....', '¿Cual es tu edad?'] - const provider = createProvider(PROVIDER_DB) - const database = new MOCK_DB() - - const flujoPrincipal = addKeyword(['hola']) - .addAnswer( - MOCK_VALUES[0], - { - capture: true, - }, - async (ctx, { flowDynamic, fallBack }) => { - const validation = ctx.body.includes('@') - - if (validation) { - const getDataFromApi = await fakeHTTP(['Gracias por tu email se ha validado de manera correcta']) - return flowDynamic(getDataFromApi) - } - return fallBack(validation) - } - ) - .addAnswer(MOCK_VALUES[1], null, async (_, { endFlow }) => { - return endFlow() - }) - .addAnswer(MOCK_VALUES[2], { capture: true }, async (ctx, { flowDynamic, fallBack }) => { - if (ctx.body !== '18') { - await delay(50) - return fallBack(false, 'Ups creo que no eres mayor de edad') - } - return flowDynamic('Bien tu edad es correcta!') - }) - .addAnswer('Puedes pasar') - - createBot({ - database, - flow: createFlow([flujoPrincipal]), - provider, - }) - - provider.delaySendMessage(0, 'message', { - from: '000', - body: 'hola', - }) - - provider.delaySendMessage(10, 'message', { - from: '000', - body: 'this is not email value', - }) - - provider.delaySendMessage(20, 'message', { - from: '000', - body: 'test@test.com', - }) - - provider.delaySendMessage(90, 'message', { - from: '000', - body: '20', - }) - - await delay(1200) - const getHistory = database.listHistory.map((i) => i.answer) - assert.is(MOCK_VALUES[0], getHistory[0]) - assert.is('this is not email value', getHistory[1]) - assert.is(MOCK_VALUES[0], getHistory[2]) - assert.is('test@test.com', getHistory[3]) - assert.is('1 Gracias por tu email se ha validado de manera correcta', getHistory[4]) - assert.is(MOCK_VALUES[1], getHistory[5]) - assert.is('20', getHistory[6]) - assert.is(undefined, getHistory[7]) -}) - -test.run() - -function delay(ms) { - return new Promise((res) => setTimeout(res, ms)) -} diff --git a/__test__/07-case.test.js b/__test__/07-case.test.js deleted file mode 100644 index 6eb8e42..0000000 --- a/__test__/07-case.test.js +++ /dev/null @@ -1,92 +0,0 @@ -const { test } = require('uvu') -const assert = require('uvu/assert') -const MOCK_DB = require('../packages/database/src/mock') -const PROVIDER_DB = require('../packages/provider/src/mock') -const { addKeyword, createBot, createFlow, createProvider } = require('../packages/bot/index') - -/** - * Falsear peticion async - * @param {*} fakeData - * @returns - */ -const fakeHTTP = async (fakeData = []) => { - await delay(5) - const data = fakeData.map((u, i) => ({ body: `${i + 1} ${u}` })) - return Promise.resolve(data) -} - -let STATE_APP = {} - -test(`[Caso - 07] Retornar estado`, async () => { - const MOCK_VALUES = ['¿Cual es tu nombre?', '¿Cual es tu edad?', 'Tu datos son:'] - const provider = createProvider(PROVIDER_DB) - const database = new MOCK_DB() - - const flujoPrincipal = addKeyword(['hola']) - .addAnswer( - MOCK_VALUES[0], - { - capture: true, - }, - async (ctx, { flowDynamic, fallBack }) => { - STATE_APP[ctx.from] = { ...STATE_APP[ctx.from], name: ctx.body } - - flowDynamic('Gracias por tu nombre!') - } - ) - .addAnswer( - MOCK_VALUES[1], - { - capture: true, - }, - async (ctx, { flowDynamic, endFlow }) => { - STATE_APP[ctx.from] = { ...STATE_APP[ctx.from], age: ctx.body } - - await flowDynamic('Gracias por tu edad!') - } - ) - .addAnswer(MOCK_VALUES[2], null, async (ctx, { flowDynamic }) => { - flowDynamic(`Nombre: ${STATE_APP[ctx.from].name} Edad: ${STATE_APP[ctx.from].age}`) - }) - .addAnswer('🤖🤖 Gracias por tu participacion') - - createBot({ - database, - flow: createFlow([flujoPrincipal]), - provider, - }) - - provider.delaySendMessage(0, 'message', { - from: '000', - body: 'hola', - }) - - provider.delaySendMessage(20, 'message', { - from: '000', - body: 'Leifer', - }) - - provider.delaySendMessage(40, 'message', { - from: '000', - body: '90', - }) - - await delay(1200) - const getHistory = database.listHistory.map((i) => i.answer) - assert.is(MOCK_VALUES[0], getHistory[0]) - assert.is('Leifer', getHistory[1]) - assert.is('Gracias por tu nombre!', getHistory[2]) - assert.is('¿Cual es tu edad?', getHistory[3]) - assert.is('90', getHistory[4]) - assert.is('Gracias por tu edad!', getHistory[5]) - assert.is('Tu datos son:', getHistory[6]) - assert.is('Nombre: Leifer Edad: 90', getHistory[7]) - assert.is('🤖🤖 Gracias por tu participacion', getHistory[8]) - assert.is(undefined, getHistory[9]) -}) - -test.run() - -function delay(ms) { - return new Promise((res) => setTimeout(res, ms)) -} diff --git a/__test__/08-case.test.js b/__test__/08-case.test.js deleted file mode 100644 index 82fc46a..0000000 --- a/__test__/08-case.test.js +++ /dev/null @@ -1,43 +0,0 @@ -const { test } = require('uvu') -const assert = require('uvu/assert') -const MOCK_DB = require('../packages/database/src/mock') -const PROVIDER_DB = require('../packages/provider/src/mock') -const { addKeyword, createBot, createFlow, createProvider } = require('../packages/bot/index') - -test(`[Caso - 08] Regular expression on keyword`, async () => { - const provider = createProvider(PROVIDER_DB) - const database = new MOCK_DB() - - const REGEX_CREDIT_NUMBER = `/(^4[0-9]{12}(?:[0-9]{3})?$)|(^(?:5[1-5][0-9]{2}|222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}$)|(3[47][0-9]{13})|(^3(?:0[0-5]|[68][0-9])[0-9]{11}$)|(^6(?:011|5[0-9]{2})[0-9]{12}$)|(^(?:2131|1800|35\d{3})\d{11}$)/gm` - const flujoPrincipal = addKeyword(REGEX_CREDIT_NUMBER, { regex: true }) - .addAnswer(`Gracias por proporcionar un numero de tarjeta valido`) - .addAnswer('Fin!') - - createBot({ - database, - flow: createFlow([flujoPrincipal]), - provider, - }) - - provider.delaySendMessage(0, 'message', { - from: '000', - body: 'hola', - }) - - provider.delaySendMessage(20, 'message', { - from: '000', - body: '374245455400126', - }) - - await delay(40) - const getHistory = database.listHistory.map((i) => i.answer) - assert.is('Gracias por proporcionar un numero de tarjeta valido', getHistory[0]) - assert.is('Fin!', getHistory[1]) - assert.is(undefined, getHistory[2]) -}) - -test.run() - -function delay(ms) { - return new Promise((res) => setTimeout(res, ms)) -} diff --git a/__test__/09-case.test.js b/__test__/09-case.test.js deleted file mode 100644 index 417ce31..0000000 --- a/__test__/09-case.test.js +++ /dev/null @@ -1,41 +0,0 @@ -const { test } = require('uvu') -const assert = require('uvu/assert') -const MOCK_DB = require('../packages/database/src/mock') -const PROVIDER_MOCK = require('../packages/provider/src/mock') -const { addKeyword, createBot, createFlow, createProvider } = require('../packages/bot/index') - -let PROVIDER = undefined - -test(`[Caso - 09] Check provider WS`, async () => { - const [VALUE_A, VALUE_B] = ['hola', 'buenas'] - - const flow = addKeyword(VALUE_A).addAnswer(VALUE_B, null, async (_, { provider }) => { - PROVIDER = provider - }) - const provider = createProvider(PROVIDER_MOCK) - const database = new MOCK_DB() - - createBot({ - database, - flow: createFlow([flow]), - provider, - }) - - provider.delaySendMessage(100, 'message', { - from: '000', - body: VALUE_A, - }) - - await delay(100) - - const prevMsg = database.getPrevByNumber('000') - - assert.is(prevMsg.answer, VALUE_B) - assert.is(typeof PROVIDER.sendMessage, 'function') -}) - -test.run() - -function delay(ms) { - return new Promise((res) => setTimeout(res, ms)) -} diff --git a/package.json b/package.json index cc0b21a..8da3e44 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bot-whatsapp/root", - "version": "0.1.20", + "version": "0.1.21", "description": "Bot de wahtsapp open source para MVP o pequeños negocios", "main": "app.js", "private": true, @@ -25,7 +25,7 @@ "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.coverage": "node ./node_modules/c8/bin/c8.js npm run test.unit && npm run test.e2e", + "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", "create": "node ./packages/create-bot-whatsapp/bin/create.js", diff --git a/packages/bot/core/core.class.js b/packages/bot/core/core.class.js index 29cf5d1..09e926d 100644 --- a/packages/bot/core/core.class.js +++ b/packages/bot/core/core.class.js @@ -84,7 +84,7 @@ class CoreClass { from, prevRef: prevMsg.refSerialize, }) - this.databaseClass.save(ctxByNumber) + await this.databaseClass.save(ctxByNumber) } // 📄 Crar CTX de mensaje (uso private) @@ -110,13 +110,16 @@ class CoreClass { } // 📄 Finalizar flujo - const endFlow = async (message = null) => { - endFlowFlag = true - if (message) this.sendProviderAndSave(from, createCtxMessage(message)) - clearQueue() - sendFlow([]) - return - } + const endFlow = + (flag) => + async (message = null) => { + flag.endFlow = true + endFlowFlag = true + if (message) this.sendProviderAndSave(from, createCtxMessage(message)) + clearQueue() + sendFlow([]) + return + } // 📄 Esta funcion se encarga de enviar un array de mensajes dentro de este ctx const sendFlow = async (messageToSend, numberOrId, options = { prev: prevMsg }) => { @@ -127,68 +130,68 @@ class CoreClass { if (endFlowFlag) return const delayMs = ctxMessage?.options?.delay || 0 if (delayMs) await delay(delayMs) - QueuePrincipal.enqueue(() => - Promise.all([ - this.sendProviderAndSave(numberOrId, ctxMessage).then(() => resolveCbEveryCtx(ctxMessage)), - ]) + await QueuePrincipal.enqueue(() => + this.sendProviderAndSave(numberOrId, ctxMessage).then(() => resolveCbEveryCtx(ctxMessage)) ) } return Promise.all(queue) } - // 📄 [options: fallBack]: esta funcion se encarga de repetir el ultimo mensaje - const fallBack = async (validation = false, message = null) => { - QueuePrincipal.queue = [] + 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) + const isContinueFlow = filterNextFlow.map((i) => i.keyword).includes(currentPrev?.ref) - if (validation) { - const currentPrev = await this.databaseClass.getPrevByNumber(from) - const nextFlow = await this.flowClass.find(refToContinue?.ref, true) - const filterNextFlow = nextFlow.filter((msg) => msg.refSerialize !== currentPrev?.refSerialize) - - return sendFlow(filterNextFlow, from, { prev: undefined }) + if (!isContinueFlow) { + const refToContinueChild = this.flowClass.getRefToContinueChild(currentPrev?.keyword) + 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 }) } - await this.sendProviderAndSave(from, { - ...prevMsg, - answer: typeof message === 'string' ? message : message?.body ?? prevMsg.answer, - options: { - ...prevMsg.options, - buttons: prevMsg.options?.buttons, - }, - }) - return + if (!isContinueFlow) { + await sendFlow(filterNextFlow, from, { prev: undefined }) + return + } } + // 📄 [options: fallBack]: esta funcion se encarga de repetir el ultimo mensaje + const fallBack = + (flag) => + async (message = null) => { + QueuePrincipal.queue = [] + flag.fallBack = true + await this.sendProviderAndSave(from, { + ...prevMsg, + answer: typeof message === 'string' ? message : message?.body ?? prevMsg.answer, + options: { + ...prevMsg.options, + buttons: prevMsg.options?.buttons, + }, + }) + return + } // 📄 [options: flowDynamic]: esta funcion se encarga de responder un array de respuesta esta limitado a 5 mensajes // para evitar bloque de whatsapp - const flowDynamic = async (listMsg = []) => { - if (!Array.isArray(listMsg)) listMsg = [listMsg] + const flowDynamic = + (flag) => + async (listMsg = []) => { + flag.flowDynamic = true + if (!Array.isArray(listMsg)) listMsg = [listMsg] - const parseListMsg = listMsg.map((opt, index) => createCtxMessage(opt, index)) - const currentPrev = await this.databaseClass.getPrevByNumber(from) + const parseListMsg = listMsg.map((opt, index) => createCtxMessage(opt, index)) - const skipContinueFlow = async () => { - const nextFlow = await this.flowClass.find(refToContinue?.ref, true) - const filterNextFlow = nextFlow.filter((msg) => msg.refSerialize !== currentPrev?.refSerialize) - const isContinueFlow = filterNextFlow.map((i) => i.keyword).includes(currentPrev?.ref) - return { - continue: !isContinueFlow, - contexts: filterNextFlow, + if (endFlowFlag) return + for (const msg of parseListMsg) { + await this.sendProviderAndSave(from, msg) } + await continueFlow() + return } - if (endFlowFlag) return - for (const msg of parseListMsg) { - await this.sendProviderAndSave(from, msg) - } - - const continueFlowData = await skipContinueFlow() - - if (continueFlowData.continue) return sendFlow(continueFlowData.contexts, from, { prev: undefined }) - return - } - // 📄 Se encarga de revisar si el contexto del mensaje tiene callback o fallback const resolveCbEveryCtx = async (ctxMessage) => { if (!ctxMessage?.options?.capture) return await cbEveryCtx(ctxMessage?.ref) @@ -196,15 +199,29 @@ class CoreClass { // 📄 Se encarga de revisar si el contexto del mensaje tiene callback y ejecutarlo const cbEveryCtx = async (inRef) => { + let flags = { + endFlow: false, + fallBack: false, + flowDynamic: false, + wait: true, + } + const provider = this.providerClass if (!this.flowClass.allCallbacks[inRef]) return Promise.resolve() - return this.flowClass.allCallbacks[inRef](messageCtxInComming, { + + const argsCb = { provider, - fallBack, - flowDynamic, - endFlow, - }) + fallBack: fallBack(flags), + flowDynamic: flowDynamic(flags), + endFlow: endFlow(flags), + } + + await this.flowClass.allCallbacks[inRef](messageCtxInComming, argsCb) + const wait = !(!flags.endFlow && !flags.fallBack && !flags.flowDynamic) + if (!wait) await continueFlow() + + return } // 📄🤘(tiene return) [options: nested(array)]: Si se tiene flujos hijos los implementa @@ -216,7 +233,7 @@ class CoreClass { msgToSend = this.flowClass.find(body, false, flowStandalone) || [] - sendFlow(msgToSend, from) + await sendFlow(msgToSend, from) return } @@ -226,7 +243,7 @@ class CoreClass { if (typeCapture === 'boolean' && fallBackFlag) { msgToSend = this.flowClass.find(refToContinue?.ref, true) || [] - sendFlow(msgToSend, from) + await sendFlow(msgToSend, from) return } } @@ -241,11 +258,11 @@ class CoreClass { * @param {*} ctxMessage ver más en GLOSSARY.md * @returns */ - sendProviderAndSave = (numberOrId, ctxMessage) => { + sendProviderAndSave = async (numberOrId, ctxMessage) => { const { answer } = ctxMessage - return this.providerClass - .sendMessage(numberOrId, answer, ctxMessage) - .then(() => this.databaseClass.save({ ...ctxMessage, from: numberOrId })) + await this.providerClass.sendMessage(numberOrId, answer, ctxMessage) + await this.databaseClass.save({ ...ctxMessage, from: numberOrId }) + return } /** diff --git a/packages/bot/io/flow.class.js b/packages/bot/io/flow.class.js index 13de732..4e9932d 100644 --- a/packages/bot/io/flow.class.js +++ b/packages/bot/io/flow.class.js @@ -25,16 +25,8 @@ class FlowClass { let refSymbol = null overFlow = overFlow ?? this.flowSerialize - const customRegex = (str = null) => { - if (typeof str !== 'string') return - const instanceRegex = new RegExp(str) - return instanceRegex.test(str) - } - - /** Retornar expresion regular para buscar coincidencia */ const mapSensitive = (str, mapOptions = { sensitive: false, regex: false }) => { - if (mapOptions.regex) return customRegex(str) - + if (mapOptions.regex) return new RegExp(str) const regexSensitive = mapOptions.sensitive ? 'g' : 'i' if (Array.isArray(str)) { return new RegExp(str.join('|'), regexSensitive) @@ -43,10 +35,7 @@ class FlowClass { } const findIn = (keyOrWord, symbol = false, flow = overFlow) => { - const sensitive = refSymbol?.options?.sensitive || false - const regex = refSymbol?.options?.regex || false capture = refSymbol?.options?.capture || false - if (capture) return messages if (symbol) { @@ -55,6 +44,8 @@ class FlowClass { if (refSymbol?.ref) findIn(refSymbol.ref, true) } else { refSymbol = flow.find((c) => { + const sensitive = c?.options?.sensitive || false + const regex = c?.options?.regex || false return mapSensitive(c.keyword, { sensitive, regex }).test(keyOrWord) }) if (refSymbol?.ref) findIn(refSymbol.ref, true) @@ -68,6 +59,37 @@ class FlowClass { findBySerialize = (refSerialize) => this.flowSerialize.find((r) => r.refSerialize === refSerialize) findIndexByRef = (ref) => this.flowSerialize.findIndex((r) => r.ref === ref) + + getRefToContinueChild = (keyword) => { + try { + const flowChilds = this.flowSerialize + .reduce((acc, cur) => { + const merge = [...acc, cur?.options?.nested].flat(2) + return merge + }, []) + .filter((i) => !!i && i?.refSerialize === keyword) + .shift() + + return flowChilds + } catch (e) { + return undefined + } + } + + getFlowsChild = () => { + try { + const flowChilds = this.flowSerialize + .reduce((acc, cur) => { + const merge = [...acc, cur?.options?.nested].flat(2) + return merge + }, []) + .filter((i) => !!i) + + return flowChilds + } catch (e) { + return [] + } + } } module.exports = FlowClass diff --git a/packages/bot/package.json b/packages/bot/package.json index 563cd74..053a85d 100644 --- a/packages/bot/package.json +++ b/packages/bot/package.json @@ -1,6 +1,6 @@ { "name": "@bot-whatsapp/bot", - "version": "0.0.96-alpha.0", + "version": "0.0.100-alpha.0", "description": "", "main": "./lib/bundle.bot.cjs", "scripts": { diff --git a/packages/bot/tests/bot.class.test.js b/packages/bot/tests/bot.class.test.js index 4db4ca8..d94127c 100644 --- a/packages/bot/tests/bot.class.test.js +++ b/packages/bot/tests/bot.class.test.js @@ -17,6 +17,8 @@ class MockFlow { } findBySerialize = () => ({}) findIndexByRef = () => 0 + getRefToContinueChild = () => ({}) + getFlowsChild = () => [] } class MockDBA { diff --git a/packages/contexts/src/dialogflow/dialogflow.class.js b/packages/contexts/src/dialogflow/dialogflow.class.js index 33df66b..3575c36 100644 --- a/packages/contexts/src/dialogflow/dialogflow.class.js +++ b/packages/contexts/src/dialogflow/dialogflow.class.js @@ -104,11 +104,19 @@ class DialogFlowContext extends CoreClass { return } - const ctxFromDX = { + /* const ctxFromDX = { answer: queryResult?.fulfillmentText, - } + } */ - this.sendFlowSimple([ctxFromDX], from) + const messagesFromCX = queryResult['fulfillmentMessages'] + .map((a) => { + if (a.message === 'text') { + return { answer: a.text.text[0] } + } + }) + .filter((e) => e) + + this.sendFlowSimple(messagesFromCX, from) } } diff --git a/packages/database/src/json/index.js b/packages/database/src/json/index.js index 83450d5..edecd18 100644 --- a/packages/database/src/json/index.js +++ b/packages/database/src/json/index.js @@ -1,63 +1,79 @@ const { join } = require('path') -const { existsSync, writeFileSync, readFileSync } = require('fs') +const { existsSync } = require('fs') +const { writeFile, readFile } = require('fs').promises class JsonFileAdapter { db pathFile listHistory = [] + options = { filename: 'db.json' } - constructor() { - this.pathFile = join(process.cwd(), 'db.json') + constructor(options = {}) { + this.options = { ...this.options, ...options } + this.pathFile = join(process.cwd(), this.options.filename) this.init().then() } - databaseExists() { - return existsSync(this.pathFile) - } - - async init() { - const dbExists = await this.databaseExists() - - if (!dbExists) { - const data = { - history: [], - } - await this.saveData(data) + /** + * Revisamos si existe o no el json file + * @returns + */ + init = async () => { + if (existsSync(this.pathFile)) { + return Promise.resolve() + } + try { + const parseData = JSON.stringify([], null, 2) + return writeFile(this.pathFile, parseData, 'utf-8') + } catch (e) { + return Promise.reject(e.message) } } - readDatabase() { - const db = readFileSync(this.pathFile) - return JSON.parse(db) + validateJson = (raw) => { + try { + return JSON.parse(raw) + } catch (e) { + return {} + } } - saveData(data) { - writeFileSync(this.pathFile, JSON.stringify(data, null, 2)) + /** + * Leer archivo y parsear + * @returns + */ + readFileAndParse = async () => { + const data = await readFile(this.pathFile, 'utf-8') + const parseData = this.validateJson(data) + return parseData } + /** + * Buscamos el ultimo mensaje por numero + * @param {*} from + * @returns + */ getPrevByNumber = async (from) => { - const { history } = await this.readDatabase() - + const history = await this.readFileAndParse() if (!history.length) { - return null + return [] } - const result = history.filter((res) => res.from === from).pop() - - return { - ...result, - } + const result = history + .slice() + .reverse() + .filter((i) => !!i.keyword) + return result.find((a) => a.from === from) } + /** + * Guardar dato + * @param {*} ctx + */ save = async (ctx) => { - this.db = await this.readDatabase() - - this.db.history.push(ctx) - - await this.saveData(this.db) - this.listHistory.push(ctx) - console.log('Guardado en DB...', ctx) + const parseData = JSON.stringify(this.listHistory, null, 2) + await writeFile(this.pathFile, parseData, 'utf-8') } } diff --git a/packages/database/src/mongo/index.js b/packages/database/src/mongo/index.js index 9ea3fa5..cff9f34 100644 --- a/packages/database/src/mongo/index.js +++ b/packages/database/src/mongo/index.js @@ -30,7 +30,6 @@ class MongoAdapter { save = async (ctx) => { await this.db.collection('history').insert(ctx) - console.log('Guardando DB...', ctx) this.listHistory.push(ctx) } } diff --git a/packages/docs/src/routes/docs/flows/index.mdx b/packages/docs/src/routes/docs/flows/index.mdx index 0c2cf0b..432def3 100644 --- a/packages/docs/src/routes/docs/flows/index.mdx +++ b/packages/docs/src/routes/docs/flows/index.mdx @@ -152,57 +152,65 @@ const flowString = addKeyword('hola') ## endFlow() -Esta funcion se utliza 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 distinas y -que el usuario pueda finalizar por él mismo el flujo. +Esta funcion se utliza 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 distinas y +que el usuario **pueda finalizar por él mismo el flujo**. Como podrás comprobar en el ejemplo siguiente, se puede vincular flowDynamic y todas sus funciones; como por ejemplo botones. ```js -const flowFormulario = addKeyword(['Hola']) +let nombre; +let apellidos; +let telefono; + +const flowFormulario = addKeyword(['Hola','⬅️ Volver al Inicio']) .addAnswer( - ['Hola!', 'Escriba su *Nombre* para generar su solicitud'], + ['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') { - await flowDynamic([ - { - body: '❌ *Su solicitud de cita ha sido cancelada* ❌', - buttons: [{ body: '⬅️ Volver al Inicio' }], - }, - ]) - return 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') { - await flowDynamic([ - { - body: '❌ *Su solicitud de cita ha sido cancelada* ❌', - buttons: [{ body: '⬅️ Volver al Inicio' }], - }, - ]) - return 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') { - await flowDynamic([ - { - body: '❌ *Su solicitud de cita ha sido cancelada* ❌', - buttons: [{ body: '⬅️ Volver al Inicio' }], - }, - ]) - return endFlow() - } + if (ctx.body == '❌ Cancelar solicitud') + return endFlow({body: '❌ Su solicitud ha sido cancelada ❌', + buttons:[{body:'⬅️ Volver al Inicio' }] + }) + + + telefono = ctx.body + await delay(2000) + return flowDynamic(`Estupendo *${nombre}*! te dejo el resumen de tu formulario + \n- Nombre y apellidos: *${nombre} ${apellidos}* + \n- Telefono: *${telefono}*`) } ) + ``` --- diff --git a/packages/provider/src/baileys/index.js b/packages/provider/src/baileys/index.js index dd0e671..eaa6adb 100644 --- a/packages/provider/src/baileys/index.js +++ b/packages/provider/src/baileys/index.js @@ -118,9 +118,17 @@ class BaileysProvider extends ProviderClass { let payload = { ...messageCtx, body: messageCtx?.message?.extendedTextMessage?.text ?? messageCtx?.message?.conversation, + from: messageCtx?.key?.remoteJid, } + if (messageCtx.message.locationMessage) { + const { degreesLatitude, degreesLongitude } = messageCtx.message.locationMessage + if (typeof degreesLatitude === 'number' && typeof degreesLongitude === 'number') { + payload = { ...payload, body: `#CURRENT_LOCATION#` } + } + } + if (payload.from === 'status@broadcast') return if (payload?.key?.fromMe) return diff --git a/packages/provider/src/mock/index.js b/packages/provider/src/mock/index.js index 63034c0..9f47757 100644 --- a/packages/provider/src/mock/index.js +++ b/packages/provider/src/mock/index.js @@ -1,20 +1,20 @@ -const { ProviderClass } = require('@bot-whatsapp/bot') +const { ProviderClass } = require('../../../bot') + +function delay(ms) { + return new Promise((res) => setTimeout(res, ms)) +} class MockProvider extends ProviderClass { constructor() { super() } - delaySendMessage = (miliseconds, eventName, payload) => - new Promise((res) => - setTimeout(() => { - this.emit(eventName, payload) - res - }, miliseconds) - ) + delaySendMessage = async (miliseconds, eventName, payload) => { + await delay(miliseconds) + this.emit(eventName, payload) + } sendMessage = async (userId, message) => { - console.log(`Enviando... ${userId}, ${message}`) return Promise.resolve({ userId, message }) } } diff --git a/packages/provider/src/venom/index.js b/packages/provider/src/venom/index.js index 1369af2..2ab185e 100644 --- a/packages/provider/src/venom/index.js +++ b/packages/provider/src/venom/index.js @@ -85,11 +85,17 @@ class VenomProvider extends ProviderClass { if (payload.from === 'status@broadcast') { return } - if (!venomisValidNumber(payload.from)) { return } payload.from = venomCleanNumber(payload.from, true) + if (payload.hasOwnProperty('lat') && payload.hasOwnProperty('lng')) { + const lat = payload.lat + const lng = payload.lng + if (lat !== '' && lng !== '') { + payload = { ...payload, body: `#CURRENT_LOCATION#` } + } + } this.emit('message', payload) }, }, diff --git a/packages/provider/src/web-whatsapp/index.js b/packages/provider/src/web-whatsapp/index.js index 4d566ee..71c1bc4 100644 --- a/packages/provider/src/web-whatsapp/index.js +++ b/packages/provider/src/web-whatsapp/index.js @@ -90,6 +90,9 @@ class WebWhatsappProvider extends ProviderClass { return } payload.from = wwebCleanNumber(payload.from, true) + if (payload._data.lat && payload._data.lng) { + payload = { ...payload, body: `#CURRENT_LOCATION#` } + } this.emit('message', payload) }, }, diff --git a/packages/provider/tests/provider.class.test.js b/packages/provider/tests/provider.class.test.js index 3a892fd..dc3320a 100644 --- a/packages/provider/tests/provider.class.test.js +++ b/packages/provider/tests/provider.class.test.js @@ -1,6 +1,6 @@ const { test } = require('uvu') const assert = require('uvu/assert') -const MockProvider = require('../../../__mocks__/mock.provider') +const MockProvider = require('../../provider/src/mock') test(`ProviderClass`, async () => { const provider = new MockProvider() diff --git a/starters/apps/base-baileys-mysql/app.js b/starters/apps/base-baileys-mysql/app.js index 2b3a2fe..30ee231 100644 --- a/starters/apps/base-baileys-mysql/app.js +++ b/starters/apps/base-baileys-mysql/app.js @@ -11,6 +11,7 @@ const MYSQL_DB_HOST = 'localhost' const MYSQL_DB_USER = 'usr' const MYSQL_DB_PASSWORD = 'pass' const MYSQL_DB_NAME = 'bot' +const MYSQL_DB_PORT = '3306' /** * Aqui declaramos los flujos hijos, los flujos se declaran de atras para adelante, es decir que si tienes un flujo de este tipo: @@ -88,6 +89,7 @@ const main = async () => { user: MYSQL_DB_USER, database: MYSQL_DB_NAME, password: MYSQL_DB_PASSWORD, + port: MYSQL_DB_PORT, }) const adapterFlow = createFlow([flowPrincipal]) const adapterProvider = createProvider(BaileysProvider) diff --git a/starters/apps/base-meta-mysql/app.js b/starters/apps/base-meta-mysql/app.js index 1c2ff53..31f4f0d 100644 --- a/starters/apps/base-meta-mysql/app.js +++ b/starters/apps/base-meta-mysql/app.js @@ -10,6 +10,7 @@ const MYSQL_DB_HOST = 'localhost' const MYSQL_DB_USER = 'usr' const MYSQL_DB_PASSWORD = 'pass' const MYSQL_DB_NAME = 'bot' +const MYSQL_DB_PORT = '3306' /** * Aqui declaramos los flujos hijos, los flujos se declaran de atras para adelante, es decir que si tienes un flujo de este tipo: @@ -87,6 +88,7 @@ const main = async () => { user: MYSQL_DB_USER, database: MYSQL_DB_NAME, password: MYSQL_DB_PASSWORD, + port: MYSQL_DB_PORT, }) const adapterFlow = createFlow([flowPrincipal]) diff --git a/starters/apps/base-twilio-mysql/app.js b/starters/apps/base-twilio-mysql/app.js index 2a9eaa3..bf9a239 100644 --- a/starters/apps/base-twilio-mysql/app.js +++ b/starters/apps/base-twilio-mysql/app.js @@ -11,6 +11,7 @@ const MYSQL_DB_HOST = 'localhost' const MYSQL_DB_USER = 'user' const MYSQL_DB_PASSWORD = 'pass' const MYSQL_DB_NAME = 'bot' +const MYSQL_DB_PORT = '3306' /** * Aqui declaramos los flujos hijos, los flujos se declaran de atras para adelante, es decir que si tienes un flujo de este tipo: @@ -88,6 +89,7 @@ const main = async () => { user: MYSQL_DB_USER, database: MYSQL_DB_NAME, password: MYSQL_DB_PASSWORD, + port: MYSQL_DB_PORT, }) const adapterFlow = createFlow([flowPrincipal]) const adapterProvider = createProvider(TwilioProvider, { diff --git a/starters/apps/base-venom-mysql/app.js b/starters/apps/base-venom-mysql/app.js index 48a47c8..28e3399 100644 --- a/starters/apps/base-venom-mysql/app.js +++ b/starters/apps/base-venom-mysql/app.js @@ -12,6 +12,7 @@ const MYSQL_DB_HOST = 'localhost' const MYSQL_DB_USER = 'user' const MYSQL_DB_PASSWORD = 'pass' const MYSQL_DB_NAME = 'bot' +const MYSQL_DB_PORT = '3306' /** * Aqui declaramos los flujos hijos, los flujos se declaran de atras para adelante, es decir que si tienes un flujo de este tipo: @@ -89,6 +90,7 @@ const main = async () => { user: MYSQL_DB_USER, database: MYSQL_DB_NAME, password: MYSQL_DB_PASSWORD, + port: MYSQL_DB_PORT, }) const adapterFlow = createFlow([flowPrincipal]) const adapterProvider = createProvider(VenomProvider) diff --git a/starters/apps/base-wweb-mysql/app.js b/starters/apps/base-wweb-mysql/app.js index 97ceadc..46270a2 100644 --- a/starters/apps/base-wweb-mysql/app.js +++ b/starters/apps/base-wweb-mysql/app.js @@ -12,6 +12,7 @@ const MYSQL_DB_HOST = 'localhost' const MYSQL_DB_USER = 'user' const MYSQL_DB_PASSWORD = 'pass' const MYSQL_DB_NAME = 'bot' +const MYSQL_DB_PORT = '3306' /** * Aqui declaramos los flujos hijos, los flujos se declaran de atras para adelante, es decir que si tienes un flujo de este tipo: @@ -89,6 +90,7 @@ const main = async () => { user: MYSQL_DB_USER, database: MYSQL_DB_NAME, password: MYSQL_DB_PASSWORD, + port: MYSQL_DB_PORT, }) const adapterFlow = createFlow([flowPrincipal]) const adapterProvider = createProvider(WebWhatsappProvider)