mirror of
https://github.com/cheveguerra/bot-whatsapp.git
synced 2026-04-20 20:49:15 +00:00
move io into bot
This commit is contained in:
110
packages/bot/USES_CASES.md
Normal file
110
packages/bot/USES_CASES.md
Normal 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)
|
||||
@@ -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
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
30
packages/bot/io/flow.class.js
Normal file
30
packages/bot/io/flow.class.js
Normal 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
|
||||
48
packages/bot/io/methods/addAnswer.js
Normal file
48
packages/bot/io/methods/addAnswer.js
Normal 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 }
|
||||
58
packages/bot/io/methods/addKeyword.js
Normal file
58
packages/bot/io/methods/addKeyword.js
Normal 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 }
|
||||
5
packages/bot/io/methods/index.js
Normal file
5
packages/bot/io/methods/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
const { addAnswer } = require('./addAnswer')
|
||||
const { addKeyword } = require('./addKeyword')
|
||||
const { toJson } = require('./toJson')
|
||||
|
||||
module.exports = { addAnswer, addKeyword, toJson }
|
||||
6
packages/bot/io/methods/toJson.js
Normal file
6
packages/bot/io/methods/toJson.js
Normal file
@@ -0,0 +1,6 @@
|
||||
const toJson = (inCtx) => () => {
|
||||
const lastCtx = inCtx.hasOwnProperty('ctx') ? inCtx.ctx : inCtx
|
||||
return lastCtx.json
|
||||
}
|
||||
|
||||
module.exports = { toJson }
|
||||
14
packages/bot/io/rollup-cli.config.js
Normal file
14
packages/bot/io/rollup-cli.config.js
Normal 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()],
|
||||
}
|
||||
@@ -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": {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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(
|
||||
|
||||
130
packages/bot/tests/methods.test.js
Normal file
130
packages/bot/tests/methods.test.js
Normal 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()
|
||||
7
packages/bot/utils/hash.js
Normal file
7
packages/bot/utils/hash.js
Normal file
@@ -0,0 +1,7 @@
|
||||
const crypto = require('crypto')
|
||||
|
||||
const generateRef = () => {
|
||||
return crypto.randomUUID()
|
||||
}
|
||||
|
||||
module.exports = { generateRef }
|
||||
Reference in New Issue
Block a user