move io into bot

This commit is contained in:
Leifer Mendez
2022-11-13 14:41:25 +01:00
parent 3946c88ed7
commit 03eed5131a
19 changed files with 44 additions and 49 deletions

110
packages/bot/USES_CASES.md Normal file
View File

@@ -0,0 +1,110 @@
# @bot-whatsapp/io
### Caso de uso
> Una persona escribe `hola`
**addKeyword** recibe `string | string[]`
> `sensitive` false _default_
- [x] addKeyword
- [x] addAnswer
- [x] addKeyword: Opciones
- [x] addAnswer: Opciones, media, buttons
- [x] Retornar JSON (options)
- [ ] Recibir JSON
```js
// bootstrap.js Como iniciar el provider
const { inout, provider, database } = require('@bot-whatsapp')
/**
* async whatsapp-web, twilio, meta
* */
const bootstrap = async () => {
console.log(`Iniciando....`)
const client = await provider.start()
/**
* - QR
* - Endpoint
* - Check Token Meta, Twilio
* - Return events? on message
* */
console.log(`Fin...`)
// Esto es opcional ? no deberia ser necesario
client.on('message', ({number, body,...}) => {
// Incoming message
})
}
```
```js
// flow.js Como agregar keywords y respuestas
const { inout, provider, database } = require('@bot-whatsapp')
await inout
.addKeyword('hola')
.addAnswer('Bienvenido a tu tienda 🥲')
.addAnswer('escribe *catalogo* o *ofertas*')
await inout
.addKeyword(['catalogo', 'ofertas'])
.addAnswer('Este es nuestro CATALOGO mas reciente!', {
buttons: [{ body: 'Xiaomi' }, { body: 'Samsung' }],
})
await inout
.addKeyword('Xiaomi')
.addAnswer('Estos son nuestro productos XIAOMI ....', {
media: 'https://....',
})
.addAnswer('Si quieres mas info escrbie *info*')
await inout
.addKeyword('chao!')
.addAnswer('bye!')
.addAnswer('Recuerda que tengo esta promo', {
media: 'https://media2.giphy.com/media/VQJu0IeULuAmCwf5SL/giphy.gif',
})
await inout
.addKeyword('Modelo C', { sensitive: false })
.addAnswer('100USD', { media: 'http//:...' })
await inout
.addKeyword('hola!', { sensitive: false })
.addAnswer('Bievenido Escribe *productos*')
await inout
.addKeyword('productos', { sensitive: false })
.addAnswer('Esto son los mas vendidos')
.addAnswer('*PC1* Precio 10USD', { media: 'https://....' })
.addAnswer('*PC2* Precio 10USD', { media: 'https://....' })
await inout
.addKeyword('PC1', { sensitive: false })
.addAnswer('Bievenido Escribe *productos*')
const answerOne = await inout.addAnswer({
message: 'Como estas!',
media: 'https://media2.giphy.com/media/VQJu0IeULuAmCwf5SL/giphy.gif',
})
const otherAnswer = await inout.addAnswer('Aprovecho para decirte!')
answerOne.push(otherAnswer)
inout.addKeywords(['hola', 'hi', 'ola'])
```
**Comunidad**
> Forma parte de este proyecto.
- [Discord](https://link.codigoencasa.com/DISCORD)
- [Twitter](https://twitter.com/leifermendez)
- [Youtube](https://www.youtube.com/watch?v=5lEMCeWEJ8o&list=PL_WGMLcL4jzWPhdhcUyhbFU6bC0oJd2BR)
- [Telegram](https://t.me/leifermendez)

View File

@@ -6,7 +6,7 @@ const { printer } = require('../utils/interactive')
* [ ] Buscar mensaje en flow
*
*/
class BotClass {
class CoreClass {
flowClass
databaseClass
providerClass
@@ -75,4 +75,4 @@ class BotClass {
}
}
}
module.exports = BotClass
module.exports = CoreClass

View File

@@ -1,12 +1,30 @@
const BotClass = require('./classes/bot.class')
const ProviderClass = require('./classes/provider.class')
const CoreClass = require('./core/core.class')
const ProviderClass = require('./provider/provider.class')
const FlowClass = require('./io/flow.class')
const { addKeyword, addAnswer } = require('./io/methods')
/**
* Crear instancia de clase
* @param {*} args
* @returns
*/
const create = async ({ flow, database, provider }) =>
new BotClass(flow, database, provider)
const createBot = async ({ flow, database, provider }) =>
new CoreClass(flow, database, provider)
module.exports = { create, ProviderClass }
/**
* Crear instancia de clase
* @param {*} args
* @returns
*/
const createFlow = (args) => {
return new FlowClass(args)
}
module.exports = {
createBot,
createFlow,
addKeyword,
addAnswer,
ProviderClass,
CoreClass,
}

View File

@@ -0,0 +1,30 @@
class FlowClass {
flow
constructor(_flow) {
this.flow = _flow
}
find = (keyOrWord, symbol = false) => {
let messages = []
const findIn = (keyOrWord, symbol = false, flow = this.flow) => {
if (symbol) {
const refSymbol = flow.find((c) => c.keyword === keyOrWord)
if (refSymbol && refSymbol.answer)
messages.push(refSymbol.answer)
if (refSymbol && refSymbol.ref) findIn(refSymbol.ref, true)
} else {
const refSymbolByKeyworkd = flow.find((c) =>
c.keyword.includes(keyOrWord)
)
if (refSymbolByKeyworkd && refSymbolByKeyworkd.ref)
findIn(refSymbolByKeyworkd.ref, true)
return messages
}
}
findIn(keyOrWord, symbol)
return messages
}
}
module.exports = FlowClass

View File

@@ -0,0 +1,48 @@
const { generateRef } = require('../../utils/hash')
const { toJson } = require('./toJson')
/**
*
* @param answer string
* @param options {media:string, buttons:[], capture:true default false}
* @returns
*/
const addAnswer = (inCtx) => (answer, options) => {
const getAnswerOptions = () => ({
media: typeof options?.media === 'string' ? `${options?.media}` : null,
buttons: Array.isArray(options?.buttons) ? options.buttons : [],
capture:
typeof options?.capture === 'boolean' ? options?.capture : false,
})
const lastCtx = inCtx.hasOwnProperty('ctx') ? inCtx.ctx : inCtx
const ctxAnswer = () => {
const ref = `ans_${generateRef()}`
const options = {
...getAnswerOptions(),
keyword: {},
}
const json = [].concat(inCtx.json).concat([
{
ref,
keyword: lastCtx.ref,
answer,
options,
},
])
return { ...lastCtx, ref, answer, json, options }
}
const ctx = ctxAnswer()
return {
ctx,
ref: ctx.ref,
addAnswer: addAnswer(ctx),
toJson: toJson(ctx),
}
}
module.exports = { addAnswer }

View File

@@ -0,0 +1,58 @@
const { generateRef } = require('../../utils/hash')
const { addAnswer } = require('./addAnswer')
const { toJson } = require('./toJson')
/**
* addKeyword:
* Es necesario que genere id|hash
*/
/**
*
* @param {*} message `string | string[]`
* @param {*} options {sensitive:boolean} default false
*/
const addKeyword = (keyword, options) => {
/**
* Esta funcion deberia parsear y validar las opciones
* del keyword
* @returns
*/
const parseOptions = () => {
const defaultProperties = {
sensitive:
typeof options?.sensitive === 'boolean'
? options?.sensitive
: false,
}
return defaultProperties
}
const ctxAddKeyword = () => {
const ref = `key_${generateRef()}`
const options = parseOptions()
const json = [
{
ref,
keyword,
options,
},
]
/**
* Se guarda en db
*/
return { ref, keyword, options, json }
}
const ctx = ctxAddKeyword()
return {
ctx,
ref: ctx.ref,
addAnswer: addAnswer(ctx),
toJson: toJson(ctx),
}
}
module.exports = { addKeyword }

View File

@@ -0,0 +1,5 @@
const { addAnswer } = require('./addAnswer')
const { addKeyword } = require('./addKeyword')
const { toJson } = require('./toJson')
module.exports = { addAnswer, addKeyword, toJson }

View File

@@ -0,0 +1,6 @@
const toJson = (inCtx) => () => {
const lastCtx = inCtx.hasOwnProperty('ctx') ? inCtx.ctx : inCtx
return lastCtx.json
}
module.exports = { toJson }

View File

@@ -0,0 +1,14 @@
const commonjs = require('@rollup/plugin-commonjs')
const { nodeResolve } = require('@rollup/plugin-node-resolve')
const { join } = require('path')
const PATH = join(__dirname, 'lib', 'io', 'bundle.io.cjs')
module.exports = {
input: 'index.js',
output: {
file: PATH,
format: 'cjs',
},
plugins: [commonjs(), nodeResolve()],
}

View File

@@ -2,11 +2,10 @@
"name": "@bot-whatsapp/bot",
"version": "0.0.1",
"description": "",
"main": "index.js",
"main": "./lib/bundle.bot.cjs",
"private": true,
"scripts": {
"core:dev": "node ./index.js",
"core:rollup": "node ../../node_modules/.bin/rollup index.js --config ./rollup-cli.config.js",
"bot:rollup": "node ../../node_modules/.bin/rollup index.js --config ./rollup-cli.config.js",
"format:check": "prettier --check .",
"format:write": "prettier --write .",
"lint:check": "eslint .",
@@ -14,6 +13,12 @@
"test.unit": "cross-env NODE_ENV=test node ../../node_modules/uvu/bin.js tests"
},
"keywords": [],
"files": [
"./lib/bundle.bot.cjs",
"./provider/*",
"./core/*",
"./io/*"
],
"author": "",
"license": "ISC",
"devDependencies": {

View File

@@ -2,7 +2,7 @@ const commonjs = require('@rollup/plugin-commonjs')
const { nodeResolve } = require('@rollup/plugin-node-resolve')
const { join } = require('path')
const PATH = join(__dirname, 'lib', 'bot', 'bundle.bot.cjs')
const PATH = join(__dirname, 'lib', 'bundle.bot.cjs')
module.exports = {
input: 'index.js',

View File

@@ -1,8 +1,7 @@
const { test } = require('uvu')
const assert = require('uvu/assert')
const MockProvider = require('../../../__mocks__/mock.provider')
const { create } = require('@bot-whatsapp/bot')
const BotClass = require('@bot-whatsapp/bot/classes/bot.class')
const { createBot, CoreClass } = require('@bot-whatsapp/bot')
class MockFlow {
find = () => {}
@@ -12,17 +11,17 @@ class MockDB {
save = () => {}
}
test(`[BotClass] Probando instanciamiento de clase`, async () => {
test(`[CoreClass] Probando instanciamiento de clase`, async () => {
const setting = {
flow: new MockFlow(),
database: new MockDB(),
provider: new MockProvider(),
}
const bot = await create(setting)
assert.is(bot instanceof BotClass, true)
const bot = await createBot(setting)
assert.is(bot instanceof CoreClass, true)
})
test(`[BotClass] Eventos 'require_action,ready,auth_failure,message '`, async () => {
test(`[Bot] Eventos 'require_action,ready,auth_failure,message '`, async () => {
let responseEvents = {}
const MOCK_EVENTS = {
@@ -47,7 +46,7 @@ test(`[BotClass] Eventos 'require_action,ready,auth_failure,message '`, async ()
database: new MockDB(),
provider: mockProvider,
}
await create(setting)
await createBot(setting)
/// Escuchamos eventos
mockProvider.on(

View File

@@ -0,0 +1,130 @@
const { test } = require('uvu')
const assert = require('uvu/assert')
const { addKeyword, addAnswer } = require('@bot-whatsapp/bot')
test('Debere probar las propeidades', () => {
const ARRANGE = {
keyword: 'hola!',
}
const MAIN_CTX = addKeyword(ARRANGE.keyword)
assert.type(MAIN_CTX.addAnswer, 'function')
assert.is(MAIN_CTX.ctx.keyword, ARRANGE.keyword)
})
test('Debere probar las propeidades array', () => {
const ARRANGE = {
keyword: ['hola!', 'ole'],
}
const MAIN_CTX = addKeyword(ARRANGE.keyword)
assert.is(MAIN_CTX.ctx.keyword, ARRANGE.keyword)
})
test('Debere probar el paso de contexto', () => {
const ARRANGE = {
keyword: 'hola!',
answer: 'Bienvenido',
}
const CTX_A = addKeyword(ARRANGE.keyword)
const CTX_B = addAnswer(CTX_A)(ARRANGE.answer)
assert.is(CTX_A.ctx.keyword, ARRANGE.keyword)
assert.is(CTX_B.ctx.keyword, ARRANGE.keyword)
assert.is(CTX_B.ctx.answer, ARRANGE.answer)
})
test('Debere probar la anidación', () => {
const ARRANGE = {
keyword: 'hola!',
answer_A: 'Bienvenido',
answer_B: 'Continuar',
}
const MAIN_CTX = addKeyword(ARRANGE.keyword)
.addAnswer(ARRANGE.answer_A)
.addAnswer(ARRANGE.answer_B)
assert.is(MAIN_CTX.ctx.answer, ARRANGE.answer_B)
})
test('Debere probar las poptions', () => {
const MAIN_CTX = addKeyword('etc', { sensitive: false })
assert.is(MAIN_CTX.ctx.options.sensitive, false)
})
test('Debere probar las addAnswer', () => {
const MOCK_OPT = {
media: 'http://image.mock/mock.png',
buttons: [1],
}
const MAIN_CTX = addKeyword('hola').addAnswer('etc', MOCK_OPT)
assert.is(MAIN_CTX.ctx.options.media, MOCK_OPT.media)
assert.is(MAIN_CTX.ctx.options.buttons.length, 1)
})
test('Debere probar error las addAnswer', () => {
const MOCK_OPT = {
media: { a: 1, b: [] },
buttons: 'test',
}
const MAIN_CTX = addKeyword('hola').addAnswer('etc', MOCK_OPT)
assert.is(MAIN_CTX.ctx.options.media, null)
assert.is(MAIN_CTX.ctx.options.buttons.length, 0)
})
test('Obtener toJson', () => {
const [ctxA, ctxB, ctxC] = addKeyword('hola')
.addAnswer('pera!')
.addAnswer('chao')
.toJson()
assert.is(ctxA.keyword, 'hola')
assert.match(ctxA.ref, /^key_/)
assert.is(ctxB.answer, 'pera!')
assert.match(ctxB.ref, /^ans_/)
assert.is(ctxC.answer, 'chao')
assert.match(ctxC.ref, /^ans_/)
})
test('addKeyword toJson con sensitive', () => {
const [ctxA] = addKeyword('hola').toJson()
assert.is(ctxA.options.sensitive, false)
const [ctxB] = addKeyword('hola', { sensitive: true }).toJson()
assert.is(ctxB.options.sensitive, true)
})
test('addAnswer toJson con IMG', () => {
const [, ctxB, ctxC] = addKeyword('hola')
.addAnswer('bye!', {
media: 'http://mock.img/file-a.png',
})
.addAnswer('otro!', {
media: 'http://mock.img/file-b.png',
})
.toJson()
assert.is(ctxB.options.media, 'http://mock.img/file-a.png')
assert.is(ctxC.options.media, 'http://mock.img/file-b.png')
})
test('addAnswer toJson con BUTTONS', () => {
const [, ctxB] = addKeyword('hola')
.addAnswer('mis opciones!', {
buttons: [{ body: 'BTN_1' }, { body: 'BTN_2' }],
})
.toJson()
assert.is(ctxB.options.buttons.length, 2)
const [btnA, btnB] = ctxB.options.buttons
assert.is(btnA.body, 'BTN_1')
assert.is(btnB.body, 'BTN_2')
})
test.run()

View File

@@ -0,0 +1,7 @@
const crypto = require('crypto')
const generateRef = () => {
return crypto.randomUUID()
}
module.exports = { generateRef }