mirror of
https://github.com/cheveguerra/bot-whatsapp.git
synced 2026-04-21 04:59:15 +00:00
Compare commits
55 Commits
feature/mo
...
0.0.1-1-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5735b49e25 | ||
|
|
1988948c30 | ||
|
|
cb33c0df68 | ||
|
|
f4ad7040ab | ||
|
|
b115dc3654 | ||
|
|
5d7c297f2f | ||
|
|
04f99d5ed2 | ||
|
|
bb3f21b056 | ||
|
|
cf6188d860 | ||
|
|
294bfbb35f | ||
|
|
230538bcea | ||
|
|
5e7aa72494 | ||
|
|
3159ea5665 | ||
|
|
4b307efe79 | ||
|
|
0105dab2c4 | ||
|
|
6e8e16c9a4 | ||
|
|
8d73c86946 | ||
|
|
a7b19d9bff | ||
|
|
4b7de0f690 | ||
|
|
520145bf7d | ||
|
|
2253d57fed | ||
|
|
0fb93f66a3 | ||
|
|
501887300d | ||
|
|
14b6247106 | ||
|
|
eda8a67718 | ||
|
|
73caf090ba | ||
|
|
afa6771903 | ||
|
|
8dd3be909b | ||
|
|
88af2469cb | ||
|
|
999d6742b4 | ||
|
|
24ac9fbf48 | ||
|
|
4350dff22a | ||
|
|
30e3d443bb | ||
|
|
f5ea7fe2c4 | ||
|
|
94d139e484 | ||
|
|
8049241f3f | ||
|
|
28d308ed4b | ||
|
|
e0862053d0 | ||
|
|
e0e76d3a56 | ||
|
|
242e44315b | ||
|
|
6b53ed13e2 | ||
|
|
fafccbcecc | ||
|
|
49698bfda9 | ||
|
|
4154cc2230 | ||
|
|
ce8a96b958 | ||
|
|
f373a3abc7 | ||
|
|
371ee0a780 | ||
|
|
5e1a373730 | ||
|
|
aa2417af12 | ||
|
|
f2533f1ed5 | ||
|
|
f9ccfef8e0 | ||
|
|
08dbdcf4ae | ||
|
|
d7ed9ff592 | ||
|
|
a99f424901 | ||
|
|
f29ed6e29b |
13
.github/workflows/ci.yml
vendored
13
.github/workflows/ci.yml
vendored
@@ -1,8 +1,8 @@
|
||||
name: Test / Coverage
|
||||
name: Bot CI Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [feature/monorepo]
|
||||
branches: [dev]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
@@ -20,7 +20,8 @@ jobs:
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: npm install
|
||||
- run: npm run build --if-present
|
||||
- run: npm run test.unit
|
||||
- run: npm run test.coverage
|
||||
- run: corepack enable
|
||||
- name: Install NPM Dependencies
|
||||
run: yarn install --immutable --network-timeout 300000
|
||||
- name: Test
|
||||
run: yarn test
|
||||
|
||||
20
.github/workflows/contributors.yml
vendored
Normal file
20
.github/workflows/contributors.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: Add contributors
|
||||
on:
|
||||
schedule:
|
||||
- cron: '20 20 * * *'
|
||||
push:
|
||||
branches: [dev]
|
||||
pull_request:
|
||||
branches: [main, dev]
|
||||
|
||||
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 }}
|
||||
26
.github/workflows/relases.yml
vendored
Normal file
26
.github/workflows/relases.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: Bot Relases
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [released, published]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [16.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: corepack enable
|
||||
- name: Install NPM Dependencies
|
||||
run: yarn install --immutable --network-timeout 300000
|
||||
- name: Test
|
||||
run: yarn test
|
||||
- name: Build
|
||||
run: yarn build
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -18,6 +18,8 @@ config.json
|
||||
coverage/
|
||||
*.lcov
|
||||
log
|
||||
log/*
|
||||
*.log
|
||||
lib
|
||||
tmp/
|
||||
.yarn/*
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
packages/**/lib
|
||||
packages/docs
|
||||
packages/docs/*.json
|
||||
**/.git
|
||||
**/.svn
|
||||
**/.hg
|
||||
|
||||
10
.vscode/settings.json
vendored
10
.vscode/settings.json
vendored
@@ -1,3 +1,11 @@
|
||||
{
|
||||
"conventionalCommits.scopes": ["hook", "contributing", "cli"]
|
||||
"conventionalCommits.scopes": [
|
||||
"hook",
|
||||
"contributing",
|
||||
"cli",
|
||||
"bot",
|
||||
"provider",
|
||||
"adapter",
|
||||
"ci"
|
||||
]
|
||||
}
|
||||
|
||||
2
GLOSSARY.md
Normal file
2
GLOSSARY.md
Normal file
@@ -0,0 +1,2 @@
|
||||
CTX: Es el objeto que representa un mensaje, con opciones, id, ref
|
||||
messageInComming: Objeto entrante del provider {body, from,...}
|
||||
79
README.md
79
README.md
@@ -8,6 +8,82 @@ Video como hacer PR: https://youtu.be/Lxt8Acob6aU
|
||||
|
||||
|
||||
**Comunidad**
|
||||
<!-- readme: collaborators,contributors -start -->
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/leifermendez">
|
||||
<img src="https://avatars.githubusercontent.com/u/15802366?v=4" width="50;" alt="leifermendez"/>
|
||||
<br />
|
||||
<sub><b>Leifer Mendez</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/aurik3">
|
||||
<img src="https://avatars.githubusercontent.com/u/37228512?v=4" width="50;" alt="aurik3"/>
|
||||
<br />
|
||||
<sub><b>Null</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/vicente1992">
|
||||
<img src="https://avatars.githubusercontent.com/u/57806030?v=4" width="50;" alt="vicente1992"/>
|
||||
<br />
|
||||
<sub><b>Manuel Vicente Ortiz</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/leifermendezfroged">
|
||||
<img src="https://avatars.githubusercontent.com/u/97020486?v=4" width="50;" alt="leifermendezfroged"/>
|
||||
<br />
|
||||
<sub><b>Leifer Mendez</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/Gonzalito87">
|
||||
<img src="https://avatars.githubusercontent.com/u/100331586?v=4" width="50;" alt="Gonzalito87"/>
|
||||
<br />
|
||||
<sub><b>Null</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/jzvi12">
|
||||
<img src="https://avatars.githubusercontent.com/u/10729787?v=4" width="50;" alt="jzvi12"/>
|
||||
<br />
|
||||
<sub><b>Null</b></sub>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/tonyvazgar">
|
||||
<img src="https://avatars.githubusercontent.com/u/21047090?v=4" width="50;" alt="tonyvazgar"/>
|
||||
<br />
|
||||
<sub><b>Luis Antonio Vázquez García</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/ulisesvina">
|
||||
<img src="https://avatars.githubusercontent.com/u/20508563?v=4" width="50;" alt="ulisesvina"/>
|
||||
<br />
|
||||
<sub><b>Ulises Viña</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/rrruuuyyy">
|
||||
<img src="https://avatars.githubusercontent.com/u/33061671?v=4" width="50;" alt="rrruuuyyy"/>
|
||||
<br />
|
||||
<sub><b>Rodrigo Mendoza Cabrera</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/yond1994">
|
||||
<img src="https://avatars.githubusercontent.com/u/47557263?v=4" width="50;" alt="yond1994"/>
|
||||
<br />
|
||||
<sub><b>Yonathan Suarez</b></sub>
|
||||
</a>
|
||||
</td></tr>
|
||||
</table>
|
||||
<!-- readme: collaborators,contributors -end -->
|
||||
|
||||
> Forma parte de este proyecto.
|
||||
|
||||
@@ -15,6 +91,3 @@ Video como hacer PR: https://youtu.be/Lxt8Acob6aU
|
||||
- [Twitter](https://twitter.com/leifermendez)
|
||||
- [Youtube](https://www.youtube.com/watch?v=5lEMCeWEJ8o&list=PL_WGMLcL4jzWPhdhcUyhbFU6bC0oJd2BR)
|
||||
- [Telegram](https://t.me/leifermendez)
|
||||
|
||||
|
||||
|
||||
33
TODO.md
33
TODO.md
@@ -2,31 +2,50 @@
|
||||
- [X] __(doc)__ Video de como colaborar PR
|
||||
- [ ] __(doc)__ Video implementación de test y cobertura
|
||||
- [ ] __(doc)__ Video explicacion de github action
|
||||
- [ ] Crear packages list externas
|
||||
|
||||
### @bot-whatsapp/bot
|
||||
- [ ] agregar export package
|
||||
- [X] agregar export package
|
||||
- [X] Posibilidad de en el capture meter todo un nuevo CTX de FLOW .addAnswer('Marca la opcion',{capture:true, join:CTX})
|
||||
- [X] .addKeyword('1') no funciona con 1 caracter
|
||||
- [X] sensitivy viene activado por defecto
|
||||
- [ ] fallback respuesta en hijo: Se puede colocar en option el ref de la answer fallback
|
||||
- [X] fallback respuesta en hijo: Se puede colocar en option el ref de la answer fallback
|
||||
- [X] Cuando Envian Sticket devuelve mensaje raro
|
||||
- [x] addAnswer agregar delay
|
||||
- [ ] colocar mensaje esperando conectando whatsapp (provider)
|
||||
- [ ] Cuando Envian Sticket devuelve mensaje raro
|
||||
- [ ] createDatabase validar implementacion de funciones
|
||||
- [ ] limitar caracteres de mensajes 4000
|
||||
- [X] cuando envias numeros (5 o 1) se dispara el flujo
|
||||
|
||||
### @bot-whatsapp/database
|
||||
- [X] agregar export package
|
||||
- [X] __(doc):__ Video para explicar como implementar nuevos database
|
||||
- [X] Mongo adapter
|
||||
- [ ] MySQL adapter
|
||||
- [X] MySQL adapter
|
||||
- [ ] JsonFile adapter
|
||||
|
||||
### @bot-whatsapp/provider
|
||||
- [X] agregar export package
|
||||
- [ ] __(doc):__ Video para explicar como implementar nuevos providers
|
||||
- [ ] WhatsappWeb provider enviar imagenes
|
||||
- [ ] WhatsappWeb provider enviar audio
|
||||
- [X] WhatsappWeb provider enviar imagenes
|
||||
- [X] WhatsappWeb provider enviar audio
|
||||
- [X] WhatsappWeb botones (Tiene truco) github:leifermendez/whatsapp-web.js
|
||||
- [ ] Twilio adapter
|
||||
- [ ] Meta adapter
|
||||
|
||||
### @bot-whatsapp/cli
|
||||
- [ ] Hacer comando para crear `example-app`
|
||||
- [X] Hacer comando para crear `example-app`
|
||||
|
||||
|
||||
### @bot-whatsapp/create-bot
|
||||
- [ ]
|
||||
|
||||
### Starters
|
||||
- [X] Base
|
||||
- [X] Basico
|
||||
- [ ] Enviando Imagen
|
||||
- [ ] Enviando Botones
|
||||
- [ ] Mezclando flujos hijos
|
||||
|
||||
### Extra
|
||||
- [X] Crear CI mantener fork update https://stackoverflow.com/questions/23793062/can-forks-be-synced-automatically-in-github
|
||||
|
||||
3
core.class.log
Normal file
3
core.class.log
Normal file
@@ -0,0 +1,3 @@
|
||||
[handleMsg]: { from: 'XXXXXX', body: 'hola', hasMedia: false }
|
||||
[handleMsg]: { from: 'XXXXXX', body: 'hola', hasMedia: false }
|
||||
[handleMsg]: { from: 'XXXXXX', body: 'hola', hasMedia: false }
|
||||
@@ -7,6 +7,7 @@
|
||||
"scripts": {
|
||||
"commit": "git-cz",
|
||||
"cli:rollup": "rollup --config ./packages/cli/rollup-cli.config.js ",
|
||||
"create-bot:rollup": "rollup --config ./packages/create-bot-whatsapp/rollup-create.config.js ",
|
||||
"bot:rollup": "rollup --config ./packages/bot/rollup-bot.config.js",
|
||||
"provider:rollup": "rollup --config ./packages/provider/rollup-provider.config.js ",
|
||||
"database:rollup": "rollup --config ./packages/database/rollup-database.config.js",
|
||||
@@ -16,12 +17,12 @@
|
||||
"lint:check": "eslint ./packages",
|
||||
"lint:fix": "eslint --fix ./packages",
|
||||
"build": "yarn run cli:rollup && yarn run bot:rollup && yarn run provider:rollup && yarn run database:rollup",
|
||||
"link.dist": "cd packages/bot && npm link && cd ../provider && npm link && cd ../cli && npm link && cd ../database && npm link && cd ../provider && npm link",
|
||||
"copy.lib": "node ./scripts/move.js",
|
||||
"test.unit": "node ./node_modules/uvu/bin.js packages test",
|
||||
"test.coverage": "node ./node_modules/c8/bin/c8.js npm run test.unit",
|
||||
"test": "npm run test.coverage",
|
||||
"cli": "node ./packages/cli/bin/cli.js",
|
||||
"create": "node ./packages/create-bot-whatsapp/bin/create.js",
|
||||
"dev:debug": "node --inspect ./example-app/app.js",
|
||||
"dev": "node ./example-app/app.js",
|
||||
"prepare": "npx husky install",
|
||||
@@ -30,6 +31,7 @@
|
||||
"release": "standard-version"
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/create-bot-whatsapp",
|
||||
"packages/bot",
|
||||
"packages/cli",
|
||||
"packages/database",
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
const { toCtx } = require('../io/methods')
|
||||
const { printer } = require('../utils/interactive')
|
||||
const { delay } = require('../utils/delay')
|
||||
const Queue = require('../utils/queue')
|
||||
const { Console } = require('console')
|
||||
const { createWriteStream } = require('fs')
|
||||
|
||||
const logger = new Console({
|
||||
stdout: createWriteStream(`${process.cwd()}/core.class.log`),
|
||||
})
|
||||
/**
|
||||
* [ ] Escuchar eventos del provider asegurarte que los provider emitan eventos
|
||||
* [ ] Guardar historial en db
|
||||
@@ -21,7 +28,14 @@ class CoreClass {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manejador de eventos
|
||||
*/
|
||||
listenerBusEvents = () => [
|
||||
{
|
||||
event: 'preinit',
|
||||
func: () => printer('Iniciando provider espere...'),
|
||||
},
|
||||
{
|
||||
event: 'require_action',
|
||||
func: ({ instructions, title = '⚡⚡ ACCION REQUERIDA ⚡⚡' }) =>
|
||||
@@ -44,16 +58,19 @@ class CoreClass {
|
||||
]
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {*} ctxMessage
|
||||
*
|
||||
* @param {*} messageInComming
|
||||
* @returns
|
||||
*/
|
||||
handleMsg = async (messageInComming) => {
|
||||
logger.log(`[handleMsg]: `, messageInComming)
|
||||
const { body, from } = messageInComming
|
||||
let msgToSend = []
|
||||
let fallBackFlag = false
|
||||
|
||||
if (!body.length) return
|
||||
|
||||
//Consultamos mensaje previo en DB
|
||||
const prevMsg = await this.databaseClass.getPrevByNumber(from)
|
||||
//Consultamos for refSerializada en el flow actual
|
||||
const refToContinue = this.flowClass.findBySerialize(
|
||||
prevMsg?.refSerialize
|
||||
)
|
||||
@@ -67,14 +84,24 @@ class CoreClass {
|
||||
this.databaseClass.save(ctxByNumber)
|
||||
}
|
||||
|
||||
//Si se tiene un callback se ejecuta
|
||||
if (refToContinue && prevMsg?.options?.callback) {
|
||||
const indexFlow = this.flowClass.findIndexByRef(refToContinue?.ref)
|
||||
this.flowClass.allCallbacks[indexFlow].callback(messageInComming)
|
||||
// 📄 [options: fallback]: esta funcion se encarga de repetir el ultimo mensaje
|
||||
const fallBack = () => {
|
||||
fallBackFlag = true
|
||||
msgToSend = this.flowClass.find(refToContinue?.keyword, true) || []
|
||||
this.sendFlow(msgToSend, from)
|
||||
return refToContinue
|
||||
}
|
||||
|
||||
//Si se tiene anidaciones de flows, si tienes anidados obligatoriamente capture:true
|
||||
if (prevMsg?.options?.nested?.length) {
|
||||
// 📄 [options: callback]: Si se tiene un callback se ejecuta
|
||||
if (!fallBackFlag && refToContinue && prevMsg?.options?.callback) {
|
||||
const indexFlow = this.flowClass.findIndexByRef(refToContinue?.ref)
|
||||
this.flowClass.allCallbacks[indexFlow].callback(messageInComming, {
|
||||
fallBack,
|
||||
})
|
||||
}
|
||||
|
||||
// 📄🤘(tiene return) [options: nested(array)]: Si se tiene flujos hijos los implementa
|
||||
if (!fallBackFlag && prevMsg?.options?.nested?.length) {
|
||||
const nestedRef = prevMsg.options.nested
|
||||
const flowStandalone = nestedRef.map((f) => ({
|
||||
...nestedRef.find((r) => r.refSerialize === f.refSerialize),
|
||||
@@ -85,28 +112,44 @@ class CoreClass {
|
||||
return
|
||||
}
|
||||
|
||||
//Consultamos si se espera respuesta por parte de cliente "Ejemplo: Dime tu nombre"
|
||||
if (!prevMsg?.options?.nested?.length && prevMsg?.options?.capture) {
|
||||
msgToSend = this.flowClass.find(refToContinue?.ref, true) || []
|
||||
} else {
|
||||
msgToSend = this.flowClass.find(body) || []
|
||||
// 📄🤘(tiene return) [options: capture (boolean)]: Si se tiene option boolean
|
||||
if (!fallBackFlag && !prevMsg?.options?.nested?.length) {
|
||||
const typeCapture = typeof prevMsg?.options?.capture
|
||||
const valueCapture = prevMsg?.options?.capture
|
||||
|
||||
if (['string', 'boolean'].includes(typeCapture) && valueCapture) {
|
||||
msgToSend = this.flowClass.find(refToContinue?.ref, true) || []
|
||||
this.sendFlow(msgToSend, from)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
msgToSend = this.flowClass.find(body) || []
|
||||
this.sendFlow(msgToSend, from)
|
||||
}
|
||||
|
||||
/**
|
||||
* Enviar mensaje con contexto atraves del proveedor de whatsapp
|
||||
* @param {*} numberOrId
|
||||
* @param {*} ctxMessage ver más en GLOSSARY.md
|
||||
* @returns
|
||||
*/
|
||||
sendProviderAndSave = (numberOrId, ctxMessage) => {
|
||||
const { answer } = ctxMessage
|
||||
return Promise.all([
|
||||
this.providerClass.sendMessage(numberOrId, answer),
|
||||
this.providerClass.sendMessage(numberOrId, answer, ctxMessage),
|
||||
this.databaseClass.save({ ...ctxMessage, from: numberOrId }),
|
||||
])
|
||||
}
|
||||
|
||||
sendFlow = (messageToSend, numberOrId) => {
|
||||
sendFlow = async (messageToSend, numberOrId) => {
|
||||
const queue = []
|
||||
for (const ctxMessage of messageToSend) {
|
||||
queue.push(this.sendProviderAndSave(numberOrId, ctxMessage))
|
||||
const delayMs = ctxMessage?.options?.delay || 0
|
||||
if (delayMs) await delay(delayMs)
|
||||
Queue.enqueue(() =>
|
||||
this.sendProviderAndSave(numberOrId, ctxMessage)
|
||||
)
|
||||
}
|
||||
return Promise.all(queue)
|
||||
}
|
||||
|
||||
@@ -22,11 +22,13 @@ const createFlow = (args) => {
|
||||
|
||||
/**
|
||||
* Crear instancia de clase Provider
|
||||
* Depdendiendo del Provider puedes pasar argumentos
|
||||
* Ver Documentacion
|
||||
* @param {*} args
|
||||
* @returns
|
||||
*/
|
||||
const createProvider = (providerClass = class {}) => {
|
||||
const providerInstance = new providerClass()
|
||||
const createProvider = (providerClass = class {}, args = null) => {
|
||||
const providerInstance = new providerClass(args)
|
||||
if (!providerClass.prototype instanceof ProviderClass)
|
||||
throw new Error('El provider no implementa ProviderClass')
|
||||
return providerInstance
|
||||
|
||||
@@ -21,29 +21,25 @@ class FlowClass {
|
||||
}
|
||||
|
||||
find = (keyOrWord, symbol = false, overFlow = null) => {
|
||||
keyOrWord = `${keyOrWord}`
|
||||
let capture = false
|
||||
let messages = []
|
||||
let refSymbol = null
|
||||
overFlow = overFlow ?? this.flowSerialize
|
||||
|
||||
const mapSensitiveString = (str, flag = false) => {
|
||||
if (!flag && Array.isArray(str)) {
|
||||
return str.map((c) => c.toLowerCase())
|
||||
/** Retornar expresion regular para buscar coincidencia */
|
||||
const mapSensitive = (str, flag = false) => {
|
||||
const regexSensitive = flag ? 'g' : 'i'
|
||||
if (Array.isArray(str)) {
|
||||
return new RegExp(str.join('|'), regexSensitive)
|
||||
}
|
||||
|
||||
if (!flag && typeof str === 'string') {
|
||||
return str.toLowerCase()
|
||||
}
|
||||
|
||||
return str
|
||||
return new RegExp(str, regexSensitive)
|
||||
}
|
||||
|
||||
const findIn = (keyOrWord, symbol = false, flow = overFlow) => {
|
||||
const sensitive = refSymbol?.options?.sensitive || false
|
||||
capture = refSymbol?.options?.capture || false
|
||||
|
||||
keyOrWord = mapSensitiveString(keyOrWord, sensitive)
|
||||
|
||||
if (capture) return messages
|
||||
|
||||
if (symbol) {
|
||||
@@ -51,9 +47,9 @@ class FlowClass {
|
||||
if (refSymbol?.answer) messages.push(refSymbol)
|
||||
if (refSymbol?.ref) findIn(refSymbol.ref, true)
|
||||
} else {
|
||||
refSymbol = flow.find((c) =>
|
||||
mapSensitiveString(c.keyword, sensitive).includes(keyOrWord)
|
||||
)
|
||||
refSymbol = flow.find((c) => {
|
||||
return mapSensitive(c.keyword, sensitive).test(keyOrWord)
|
||||
})
|
||||
if (refSymbol?.ref) findIn(refSymbol.ref, true)
|
||||
return messages
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
const { generateRef } = require('../../utils/hash')
|
||||
const { toJson } = require('./toJson')
|
||||
const { toSerialize } = require('./toSerialize')
|
||||
/**
|
||||
*
|
||||
* @param answer string
|
||||
* @param options {media:string, buttons:[], capture:true default false}
|
||||
* @param options {media:string, buttons:[{"body":"😎 Cursos"}], delay:ms, capture:true default false}
|
||||
* @returns
|
||||
*/
|
||||
const addAnswer =
|
||||
@@ -25,6 +24,7 @@ const addAnswer =
|
||||
: false,
|
||||
child:
|
||||
typeof options?.child === 'string' ? `${options?.child}` : null,
|
||||
delay: typeof options?.delay === 'number' ? options?.delay : 0,
|
||||
})
|
||||
|
||||
const getNested = () => ({
|
||||
@@ -79,6 +79,7 @@ const addAnswer =
|
||||
}
|
||||
}
|
||||
|
||||
/// Retornar contexto no colocar nada más abajo de esto
|
||||
const ctx = ctxAnswer()
|
||||
|
||||
return {
|
||||
|
||||
@@ -19,7 +19,7 @@ class ProviderClass extends EventEmitter {
|
||||
*
|
||||
*/
|
||||
|
||||
sendMessage = async (userId, message) => {
|
||||
sendMessage = async (userId, message, sendMessage) => {
|
||||
if (NODE_ENV !== 'production')
|
||||
console.log('[sendMessage]', { userId, message })
|
||||
return message
|
||||
|
||||
4
packages/bot/utils/delay.js
Normal file
4
packages/bot/utils/delay.js
Normal file
@@ -0,0 +1,4 @@
|
||||
const delay = (miliseconds) =>
|
||||
new Promise((res) => setTimeout(res, miliseconds))
|
||||
|
||||
module.exports = { delay }
|
||||
46
packages/bot/utils/queue.js
Normal file
46
packages/bot/utils/queue.js
Normal file
@@ -0,0 +1,46 @@
|
||||
class Queue {
|
||||
static queue = []
|
||||
static pendingPromise = false
|
||||
|
||||
static enqueue(promise) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.queue.push({
|
||||
promise,
|
||||
resolve,
|
||||
reject,
|
||||
})
|
||||
this.dequeue()
|
||||
})
|
||||
}
|
||||
|
||||
static dequeue() {
|
||||
if (this.workingOnPromise) {
|
||||
return false
|
||||
}
|
||||
const item = this.queue.shift()
|
||||
if (!item) {
|
||||
return false
|
||||
}
|
||||
try {
|
||||
this.workingOnPromise = true
|
||||
item.promise()
|
||||
.then((value) => {
|
||||
this.workingOnPromise = false
|
||||
item.resolve(value)
|
||||
this.dequeue()
|
||||
})
|
||||
.catch((err) => {
|
||||
this.workingOnPromise = false
|
||||
item.reject(err)
|
||||
this.dequeue()
|
||||
})
|
||||
} catch (err) {
|
||||
this.workingOnPromise = false
|
||||
item.reject(err)
|
||||
this.dequeue()
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Queue
|
||||
3
packages/create-bot-whatsapp/bin/create.js
Normal file
3
packages/create-bot-whatsapp/bin/create.js
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env node
|
||||
const main = require('../lib/bin/bundle.create.cjs')
|
||||
main()
|
||||
12
packages/create-bot-whatsapp/index.js
Normal file
12
packages/create-bot-whatsapp/index.js
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Main function
|
||||
*/
|
||||
const main = () => {
|
||||
console.clear()
|
||||
console.log(``)
|
||||
console.log(`[PostInstall]: Este es el main function.`)
|
||||
console.log(`[PostInstall]: 👌 Aqui podrias instalar cosas`)
|
||||
console.log(``)
|
||||
}
|
||||
|
||||
module.exports = main
|
||||
13
packages/create-bot-whatsapp/package.json
Normal file
13
packages/create-bot-whatsapp/package.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "create-bot-whatsapp",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "./lib/bin/bundle.create.cjs",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@bot-whatsapp/cli": "*"
|
||||
},
|
||||
"bin": {
|
||||
"bot": "./lib/bin/bundle.create.cjs"
|
||||
}
|
||||
}
|
||||
16
packages/create-bot-whatsapp/rollup-create.config.js
Normal file
16
packages/create-bot-whatsapp/rollup-create.config.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const banner = require('../../config/banner.rollup.json')
|
||||
const commonjs = require('@rollup/plugin-commonjs')
|
||||
const { nodeResolve } = require('@rollup/plugin-node-resolve')
|
||||
const { join } = require('path')
|
||||
|
||||
const PATH = join(__dirname, 'lib', 'bin', 'bundle.create.cjs')
|
||||
|
||||
module.exports = {
|
||||
input: join(__dirname, 'index.js'),
|
||||
output: {
|
||||
banner: banner['banner.output'].join(''),
|
||||
file: PATH,
|
||||
format: 'cjs',
|
||||
},
|
||||
plugins: [commonjs(), nodeResolve()],
|
||||
}
|
||||
@@ -10,11 +10,13 @@
|
||||
"dependencies": {
|
||||
"dotenv": "^16.0.3",
|
||||
"mongodb": "^4.11.0",
|
||||
"mysql2": "^2.3.3"
|
||||
"mysql2": "^2.3.3",
|
||||
"stormdb": "^0.6.0"
|
||||
},
|
||||
"exports": {
|
||||
"./mock": "./lib/mock/index.cjs",
|
||||
"./mongo": "./lib/mongo/index.cjs",
|
||||
"./json-file": "./lib/json-file/index.cjs",
|
||||
"./mysql": "./lib/mysql/index.cjs"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,4 +30,12 @@ module.exports = [
|
||||
},
|
||||
plugins: [commonjs()],
|
||||
},
|
||||
{
|
||||
input: join(__dirname, 'src', 'json-file', 'index.js'),
|
||||
output: {
|
||||
banner: banner['banner.output'].join(''),
|
||||
file: join(__dirname, 'lib', 'json-file', 'index.cjs'),
|
||||
},
|
||||
plugins: [commonjs()],
|
||||
},
|
||||
]
|
||||
|
||||
48
packages/database/src/json-file/index.js
Normal file
48
packages/database/src/json-file/index.js
Normal file
@@ -0,0 +1,48 @@
|
||||
const path = require('path')
|
||||
const StormDB = require('stormdb')
|
||||
const engine = new StormDB.localFileEngine(
|
||||
path.join(process.cwd(), './db.stormdb')
|
||||
)
|
||||
|
||||
class JsonFileAdapter {
|
||||
db
|
||||
listHistory = []
|
||||
|
||||
constructor() {
|
||||
this.init().then()
|
||||
}
|
||||
|
||||
init() {
|
||||
return new Promise((resolve) => {
|
||||
this.db = new StormDB(engine)
|
||||
this.db.default({ history: [] })
|
||||
resolve(this.db)
|
||||
})
|
||||
}
|
||||
|
||||
getPrevByNumber = async (from) => {
|
||||
const response = await this.db.get('history')
|
||||
const { history } = response.state
|
||||
|
||||
if (!history.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
const result = history.filter((res) => res.from === from).pop()
|
||||
|
||||
return {
|
||||
...result,
|
||||
}
|
||||
}
|
||||
|
||||
save = async (ctx) => {
|
||||
await this.db
|
||||
.get('history')
|
||||
.push({ ...ctx })
|
||||
.save()
|
||||
console.log('Guardado en DB...', ctx)
|
||||
this.listHistory.push(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = JsonFileAdapter
|
||||
@@ -1,28 +1,22 @@
|
||||
require('dotenv').config()
|
||||
const mysql = require('mysql2')
|
||||
|
||||
const DB_NAME = process.env.DB_NAME || 'db_bot'
|
||||
const DB_HOST = process.env.DB_HOST || 'localhost'
|
||||
const DB_USER = process.env.DB_USER || 'root'
|
||||
|
||||
class MyslAdapter {
|
||||
db
|
||||
listHistory = []
|
||||
credentials = { host: null, user: null, database: null }
|
||||
|
||||
constructor() {
|
||||
constructor(_credentials) {
|
||||
this.credentials = _credentials
|
||||
this.init().then()
|
||||
}
|
||||
|
||||
async init() {
|
||||
this.db = mysql.createConnection({
|
||||
host: DB_HOST,
|
||||
user: DB_USER,
|
||||
database: DB_NAME,
|
||||
})
|
||||
this.db = mysql.createConnection(this.credentials)
|
||||
|
||||
await this.db.connect((error) => {
|
||||
await this.db.connect(async (error) => {
|
||||
if (!error) {
|
||||
console.log(`Solicitud de conexión a base de datos exitosa`)
|
||||
await this.checkTableExists()
|
||||
}
|
||||
|
||||
if (error) {
|
||||
@@ -71,6 +65,42 @@ class MyslAdapter {
|
||||
})
|
||||
this.listHistory.push(ctx)
|
||||
}
|
||||
|
||||
createTable = () =>
|
||||
new Promise((resolve) => {
|
||||
const tableName = 'history'
|
||||
|
||||
const sql = `CREATE TABLE ${tableName}
|
||||
(id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
ref varchar(255) NOT NULL,
|
||||
keyword varchar(255) NOT NULL,
|
||||
answer longtext NOT NULL,
|
||||
refSerialize varchar(255) NOT NULL,
|
||||
phone varchar(255) NOT NULL,
|
||||
options longtext NOT NULL
|
||||
)`
|
||||
|
||||
this.db.query(sql, (err) => {
|
||||
if (err) throw err
|
||||
console.log(`Tabla ${tableName} creada correctamente `)
|
||||
resolve(true)
|
||||
})
|
||||
})
|
||||
|
||||
checkTableExists = () =>
|
||||
new Promise((resolve) => {
|
||||
const sql = "SHOW TABLES LIKE 'history'"
|
||||
|
||||
this.db.query(sql, (err, rows) => {
|
||||
if (err) throw err
|
||||
|
||||
if (!rows.length) {
|
||||
this.createTable()
|
||||
}
|
||||
|
||||
resolve(!!rows.length)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = MyslAdapter
|
||||
|
||||
@@ -1,41 +1,44 @@
|
||||
{
|
||||
"name": "bot-whatsapp-docs",
|
||||
"version": "0.0.1",
|
||||
"description": "Basic start point to build a docs site with Qwik",
|
||||
"engines": {
|
||||
"node": ">=15.0.0"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "qwik build",
|
||||
"build.client": "vite build",
|
||||
"build.preview": "vite build --ssr src/entry.preview.tsx",
|
||||
"build.server": "vite build -c adaptors/cloudflare-pages/vite.config.ts",
|
||||
"build.types": "tsc --incremental --noEmit",
|
||||
"deploy": "wrangler pages dev ./dist",
|
||||
"dev": "vite --mode ssr",
|
||||
"dev.debug": "node --inspect-brk ./node_modules/vite/bin/vite.js --mode ssr --force",
|
||||
"fmt": "prettier --write .",
|
||||
"fmt.check": "prettier --check .",
|
||||
"lint": "eslint \"src/**/*.ts*\"",
|
||||
"preview": "qwik build preview && vite preview --open",
|
||||
"start": "vite --open --mode ssr",
|
||||
"qwik": "qwik"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@builder.io/qwik": "0.14.1",
|
||||
"@builder.io/qwik-city": "0.0.127",
|
||||
"@types/eslint": "8.4.10",
|
||||
"@types/node": "latest",
|
||||
"@typescript-eslint/eslint-plugin": "5.43.0",
|
||||
"@typescript-eslint/parser": "5.43.0",
|
||||
"eslint": "8.28.0",
|
||||
"eslint-plugin-qwik": "0.14.1",
|
||||
"node-fetch": "3.3.0",
|
||||
"prettier": "2.7.1",
|
||||
"typescript": "4.9.3",
|
||||
"vite": "3.2.4",
|
||||
"vite-tsconfig-paths": "3.5.0",
|
||||
"wrangler": "latest"
|
||||
}
|
||||
"name": "bot-whatsapp-docs",
|
||||
"version": "0.0.1",
|
||||
"description": "Basic start point to build a docs site with Qwik",
|
||||
"engines": {
|
||||
"node": ">=15.0.0"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "qwik build",
|
||||
"build.client": "vite build",
|
||||
"build.preview": "vite build --ssr src/entry.preview.tsx",
|
||||
"build.server": "vite build -c adaptors/cloudflare-pages/vite.config.ts",
|
||||
"build.types": "tsc --incremental --noEmit",
|
||||
"deploy": "wrangler pages dev ./dist",
|
||||
"dev": "vite --mode ssr",
|
||||
"dev.debug": "node --inspect-brk ./node_modules/vite/bin/vite.js --mode ssr --force",
|
||||
"fmt": "prettier --write .",
|
||||
"fmt.check": "prettier --check .",
|
||||
"lint": "eslint \"src/**/*.ts*\"",
|
||||
"preview": "qwik build preview && vite preview --open",
|
||||
"start": "vite --open --mode ssr",
|
||||
"qwik": "qwik"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@builder.io/qwik": "0.14.1",
|
||||
"@builder.io/qwik-city": "0.0.127",
|
||||
"@types/eslint": "8.4.10",
|
||||
"@types/node": "latest",
|
||||
"@typescript-eslint/eslint-plugin": "5.43.0",
|
||||
"@typescript-eslint/parser": "5.43.0",
|
||||
"autoprefixer": "10.4.11",
|
||||
"eslint": "8.28.0",
|
||||
"eslint-plugin-qwik": "0.14.1",
|
||||
"node-fetch": "3.3.0",
|
||||
"postcss": "^8.4.16",
|
||||
"prettier": "2.7.1",
|
||||
"tailwindcss": "^3.1.8",
|
||||
"typescript": "4.9.3",
|
||||
"vite": "3.2.4",
|
||||
"vite-tsconfig-paths": "3.5.0",
|
||||
"wrangler": "latest"
|
||||
}
|
||||
}
|
||||
|
||||
6
packages/docs/postcss.config.js
Normal file
6
packages/docs/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
21
packages/docs/tailwind.config.js
Normal file
21
packages/docs/tailwind.config.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
|
||||
const defaultTheme = require("tailwindcss/defaultTheme");
|
||||
const colors = require("tailwindcss/colors");
|
||||
|
||||
module.exports = {
|
||||
content: ["./src/**/*.{js,ts,jsx,tsx,mdx}"],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: colors.purple,
|
||||
secondary: colors.sky,
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ["'Inter'", ...defaultTheme.fontFamily.sans],
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
darkMode: "class",
|
||||
};
|
||||
@@ -1,13 +0,0 @@
|
||||
# @bot-whatsapp/provider
|
||||
|
||||
```js
|
||||
// bootstrap.js Como iniciar el provider
|
||||
const { inout, provider, database } = require('@bot-whatsapp')
|
||||
|
||||
provider.start()
|
||||
provider.close()
|
||||
```
|
||||
|
||||
- [ ] whatsapp-web.js _verificar update_
|
||||
- [ ] Meta _verificar tokens_
|
||||
- [ ] Twilio _verificar tokens_
|
||||
@@ -1,19 +1,116 @@
|
||||
const twilio = require('twilio')
|
||||
const { ProviderClass } = require('@bot-whatsapp/bot')
|
||||
|
||||
const TwilioVendor = new twilio(accountSid, authToken)
|
||||
const TwilioWebHookServer = require('./server')
|
||||
const { parseNumber } = require('./utils')
|
||||
|
||||
/**
|
||||
* ⚙️TwilioProvider: Es un provedor que te ofrece enviar
|
||||
* mensaje a Whatsapp via API
|
||||
* info: https://www.twilio.com/es-mx/messaging/whatsapp
|
||||
* video: https://youtu.be/KoOmsHylxUw
|
||||
*
|
||||
* Necesitas las siguientes tokens y valores
|
||||
* { accountSid, authToken, vendorNumber }
|
||||
*/
|
||||
class TwilioProvider extends ProviderClass {
|
||||
constructor() {
|
||||
super(TwilioVendor)
|
||||
twilioHook
|
||||
vendor
|
||||
vendorNumber
|
||||
constructor({ accountSid, authToken, vendorNumber }, _port = 3000) {
|
||||
super()
|
||||
this.vendor = new twilio(accountSid, authToken)
|
||||
this.twilioHook = new TwilioWebHookServer(_port)
|
||||
this.vendorNumber = parseNumber(vendorNumber)
|
||||
|
||||
this.twilioHook.start()
|
||||
const listEvents = this.busEvents()
|
||||
|
||||
for (const { event, func } of listEvents) {
|
||||
this.twilioHook.on(event, func)
|
||||
}
|
||||
}
|
||||
|
||||
sendMessage = (message) =>
|
||||
this.vendor.messages.create({
|
||||
/**
|
||||
* Mapeamos los eventos nativos de whatsapp-web.js a los que la clase Provider espera
|
||||
* para tener un standar de eventos
|
||||
* @returns
|
||||
*/
|
||||
busEvents = () => [
|
||||
{
|
||||
event: 'auth_failure',
|
||||
func: (payload) => this.emit('error', payload),
|
||||
},
|
||||
{
|
||||
event: 'ready',
|
||||
func: () => this.emit('ready', true),
|
||||
},
|
||||
{
|
||||
event: 'message',
|
||||
func: (payload) => {
|
||||
this.emit('message', payload)
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
* Enviar un archivo multimedia
|
||||
* https://www.twilio.com/es-mx/docs/whatsapp/tutorial/send-and-receive-media-messages-whatsapp-nodejs
|
||||
* @private
|
||||
* @param {*} number
|
||||
* @param {*} mediaInput
|
||||
* @returns
|
||||
*/
|
||||
sendMedia = async (number, message, mediaInput = null) => {
|
||||
if (!mediaInput) throw new Error(`MEDIA_INPUT_NULL_: ${mediaInput}`)
|
||||
number = parseNumber(number)
|
||||
return this.vendor.messages.create({
|
||||
mediaUrl: [`${mediaInput}`],
|
||||
body: message,
|
||||
to: '+12345678901', // Text this number
|
||||
from: '+12345678901', // From a valid Twilio number
|
||||
from: `whatsapp:+${this.vendorNumber}`,
|
||||
to: `whatsapp:+${number}`,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Enviar botones
|
||||
* https://www.twilio.com/es-mx/docs/whatsapp/buttons
|
||||
* @private
|
||||
* @param {*} number
|
||||
* @param {*} message
|
||||
* @param {*} buttons []
|
||||
* @returns
|
||||
*/
|
||||
sendButtons = async (number, message, buttons = []) => {
|
||||
console.log(``)
|
||||
console.log(
|
||||
`[NOTA]: Actualmente enviar botons con Twilio esta en desarrollo`
|
||||
)
|
||||
console.log(
|
||||
`[NOTA]: https://www.twilio.com/es-mx/docs/whatsapp/buttons`
|
||||
)
|
||||
console.log(``)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} userId
|
||||
* @param {*} message
|
||||
* @param {*} param2
|
||||
* @returns
|
||||
*/
|
||||
sendMessage = async (number, message, { options }) => {
|
||||
number = parseNumber(number)
|
||||
if (options?.buttons?.length)
|
||||
this.sendButtons(number, message, options.buttons)
|
||||
if (options?.media)
|
||||
return this.sendMedia(number, message, options.media)
|
||||
return this.vendor.messages.create({
|
||||
body: message,
|
||||
from: `whatsapp:+${this.vendorNumber}`,
|
||||
to: `whatsapp:+${number}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TwilioProvider
|
||||
|
||||
64
packages/provider/src/twilio/server.js
Normal file
64
packages/provider/src/twilio/server.js
Normal file
@@ -0,0 +1,64 @@
|
||||
const { EventEmitter } = require('node:events')
|
||||
const polka = require('polka')
|
||||
const { urlencoded } = require('body-parser')
|
||||
const { parseNumber } = require('./utils')
|
||||
|
||||
/**
|
||||
* Encargado de levantar un servidor HTTP con una hook url
|
||||
* [POST] /twilio-hook
|
||||
*/
|
||||
class TwilioWebHookServer extends EventEmitter {
|
||||
twilioServer
|
||||
twilioPort
|
||||
constructor(_twilioPort) {
|
||||
super()
|
||||
this.twilioServer = this.buildHTTPServer()
|
||||
this.twilioPort = _twilioPort
|
||||
}
|
||||
|
||||
/**
|
||||
* Mensaje entrante
|
||||
* emit: 'message'
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
*/
|
||||
incomingMsg = (req, res) => {
|
||||
const { body } = req
|
||||
this.emit('message', {
|
||||
from: parseNumber(body.From),
|
||||
to: parseNumber(body.To),
|
||||
body: body.Body,
|
||||
})
|
||||
const json = JSON.stringify({ body })
|
||||
res.end(json)
|
||||
}
|
||||
|
||||
/**
|
||||
* Contruir HTTP Server
|
||||
* @returns
|
||||
*/
|
||||
buildHTTPServer = () => {
|
||||
return polka()
|
||||
.use(urlencoded({ extended: true }))
|
||||
.post('/twilio-hook', this.incomingMsg)
|
||||
}
|
||||
|
||||
/**
|
||||
* Puerto del HTTP
|
||||
* @param {*} port default 3000
|
||||
*/
|
||||
start = () => {
|
||||
this.twilioServer.listen(this.twilioPort, () => {
|
||||
console.log(``)
|
||||
console.log(`[Twilio]: Agregar esta url "WHEN A MESSAGE COMES IN"`)
|
||||
console.log(
|
||||
`[Twilio]: POST http://localhost:${this.twilioPort}/twilio-hook`
|
||||
)
|
||||
console.log(`[Twilio]: Más información en la documentacion`)
|
||||
console.log(``)
|
||||
})
|
||||
this.emit('ready')
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TwilioWebHookServer
|
||||
5
packages/provider/src/twilio/utils.js
Normal file
5
packages/provider/src/twilio/utils.js
Normal file
@@ -0,0 +1,5 @@
|
||||
const parseNumber = (number) => {
|
||||
return `${number}`.replace('whatsapp:', '').replace('+', '')
|
||||
}
|
||||
|
||||
module.exports = { parseNumber }
|
||||
@@ -1,13 +1,29 @@
|
||||
const { Client, LocalAuth } = require('whatsapp-web.js')
|
||||
const {
|
||||
Client,
|
||||
LocalAuth,
|
||||
MessageMedia,
|
||||
Buttons,
|
||||
List,
|
||||
} = require('whatsapp-web.js')
|
||||
const { ProviderClass } = require('@bot-whatsapp/bot')
|
||||
const { Console } = require('console')
|
||||
const { createWriteStream } = require('fs')
|
||||
const { cleanNumber, generateImage, isValidNumber } = require('./utils')
|
||||
const { createWriteStream, existsSync } = require('fs')
|
||||
const {
|
||||
cleanNumber,
|
||||
generateImage,
|
||||
isValidNumber,
|
||||
downloadMedia,
|
||||
} = require('./utils')
|
||||
|
||||
const logger = new Console({
|
||||
stdout: createWriteStream('./log'),
|
||||
})
|
||||
|
||||
/**
|
||||
* ⚙️ WebWhatsappProvider: Es una clase tipo adaptor
|
||||
* que extiende clases de ProviderClass (la cual es como interfaz para sber que funciones rqueridas)
|
||||
* https://github.com/pedroslopez/whatsapp-web.js
|
||||
*/
|
||||
class WebWhatsappProvider extends ProviderClass {
|
||||
vendor
|
||||
constructor() {
|
||||
@@ -21,13 +37,14 @@ class WebWhatsappProvider extends ProviderClass {
|
||||
for (const { event, func } of listEvents) {
|
||||
this.vendor.on(event, func)
|
||||
}
|
||||
|
||||
this.vendor.emit('preinit')
|
||||
this.vendor.initialize().catch((e) => {
|
||||
logger.log(e)
|
||||
this.emit('require_action', {
|
||||
instructions: [
|
||||
`Debes eliminar la carpeta .wwebjs_auth`,
|
||||
`y reiniciar nuevamente el bot `,
|
||||
`(Opcion 1): Debes eliminar la carpeta .wwebjs_auth y reiniciar nuevamente el bot. `,
|
||||
`(Opcion 2): Intenta actualizar el paquete [npm install whatsapp-web.js] `,
|
||||
`(Opcion 3): Ir FORO de discord https://link.codigoencasa.com/DISCORD `,
|
||||
],
|
||||
})
|
||||
})
|
||||
@@ -60,10 +77,6 @@ class WebWhatsappProvider extends ProviderClass {
|
||||
event: 'ready',
|
||||
func: () => this.emit('ready', true),
|
||||
},
|
||||
{
|
||||
event: 'authenticated',
|
||||
func: () => this.emit('ready', true),
|
||||
},
|
||||
{
|
||||
event: 'message',
|
||||
func: (payload) => {
|
||||
@@ -80,10 +93,87 @@ class WebWhatsappProvider extends ProviderClass {
|
||||
},
|
||||
]
|
||||
|
||||
sendMessage = async (userId, message) => {
|
||||
const number = cleanNumber(userId)
|
||||
/**
|
||||
* Enviar un archivo multimedia
|
||||
* https://docs.wwebjs.dev/MessageMedia.html
|
||||
* @private
|
||||
* @param {*} number
|
||||
* @param {*} mediaInput
|
||||
* @returns
|
||||
*/
|
||||
sendMedia = async (number, mediaInput = null) => {
|
||||
if (!mediaInput) throw new Error(`NO_SE_ENCONTRO: ${mediaInput}`)
|
||||
const fileDownloaded = await downloadMedia(mediaInput)
|
||||
const media = MessageMedia.fromFilePath(fileDownloaded)
|
||||
return this.vendor.sendMessage(number, media, {
|
||||
sendAudioAsVoice: true,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Enviar botones
|
||||
* https://docs.wwebjs.dev/Buttons.html
|
||||
* @private
|
||||
* @param {*} number
|
||||
* @param {*} message
|
||||
* @param {*} buttons []
|
||||
* @returns
|
||||
*/
|
||||
sendButtons = async (number, message, buttons = []) => {
|
||||
const buttonMessage = new Buttons(message, buttons, '', '')
|
||||
return this.vendor.sendMessage(number, buttonMessage)
|
||||
}
|
||||
|
||||
/**
|
||||
* Enviar lista
|
||||
* https://docs.wwebjs.dev/List.html
|
||||
* @private
|
||||
* @alpha No funciona en whatsapp bussines
|
||||
* @param {*} number
|
||||
* @param {*} message
|
||||
* @param {*} buttons []
|
||||
* @returns
|
||||
*/
|
||||
sendList = async (number, message, listInput = []) => {
|
||||
let sections = [
|
||||
{
|
||||
title: 'sectionTitle',
|
||||
rows: [
|
||||
{ title: 'ListItem1', description: 'desc' },
|
||||
{ title: 'ListItem2' },
|
||||
],
|
||||
},
|
||||
]
|
||||
let list = new List('List body', 'btnText', sections, 'Title', 'footer')
|
||||
return this.vendor.sendMessage(number, list)
|
||||
}
|
||||
|
||||
/**
|
||||
* Enviar un mensaje solo texto
|
||||
* https://docs.wwebjs.dev/Message.html
|
||||
* @private
|
||||
* @param {*} number
|
||||
* @param {*} message
|
||||
* @returns
|
||||
*/
|
||||
sendText = async (number, message) => {
|
||||
return this.vendor.sendMessage(number, message)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} userId
|
||||
* @param {*} message
|
||||
* @param {*} param2
|
||||
* @returns
|
||||
*/
|
||||
sendMessage = async (userId, message, { options }) => {
|
||||
const number = cleanNumber(userId)
|
||||
if (options?.buttons?.length)
|
||||
return this.sendButtons(number, message, options.buttons)
|
||||
if (options?.media) return this.sendMedia(number, options.media)
|
||||
return this.sendText(number, message)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WebWhatsappProvider
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
const { createWriteStream } = require('fs')
|
||||
const qr = require('qr-image')
|
||||
const { tmpdir } = require('os')
|
||||
const http = require('http')
|
||||
const https = require('https')
|
||||
|
||||
const cleanNumber = (number, full = false) => {
|
||||
number = number.replace('@c.us', '')
|
||||
@@ -18,4 +21,33 @@ const isValidNumber = (rawNumber) => {
|
||||
return !exist
|
||||
}
|
||||
|
||||
module.exports = { cleanNumber, generateImage, isValidNumber }
|
||||
/**
|
||||
* Incompleta
|
||||
* Descargar archivo multimedia para enviar
|
||||
* @param {*} url
|
||||
* @returns
|
||||
*/
|
||||
const downloadMedia = (url) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const ext = url.split('.').pop()
|
||||
const checkProtocol = url.includes('https:')
|
||||
const handleHttp = checkProtocol ? https : http
|
||||
const name = `tmp-${Date.now()}.${ext}`
|
||||
const fullPath = `${tmpdir()}/${name}`
|
||||
const file = createWriteStream(fullPath)
|
||||
handleHttp.get(url, function (response) {
|
||||
response.pipe(file)
|
||||
file.on('finish', function () {
|
||||
file.close()
|
||||
resolve(fullPath)
|
||||
})
|
||||
file.on('error', function () {
|
||||
console.log('errro')
|
||||
file.close()
|
||||
reject(null)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = { cleanNumber, generateImage, isValidNumber, downloadMedia }
|
||||
|
||||
@@ -11,6 +11,7 @@ const copyLibPkg = async (pkgName, to) => {
|
||||
}
|
||||
|
||||
Promise.all([
|
||||
copyLibPkg('create-bot-whatsapp', appDir),
|
||||
copyLibPkg('bot', appDir),
|
||||
copyLibPkg('database', appDir),
|
||||
copyLibPkg('provider', appDir),
|
||||
|
||||
33
scripts/release.js
Normal file
33
scripts/release.js
Normal file
@@ -0,0 +1,33 @@
|
||||
const { writeFile, readFileSync } = require('fs')
|
||||
const { join } = require('path')
|
||||
const PATH_PACKAGES = join(__dirname, '..', `packages`)
|
||||
const [PKG_ARG, PKG_ARG_VERSION] = process.argv.slice(2) || [null]
|
||||
|
||||
/**
|
||||
* Actualizar ramdon version de package
|
||||
* @param {*} packageName
|
||||
*/
|
||||
const updateVersion = (packageName = null, number = null) => {
|
||||
if (!packageName) throw new Error(`PATH_ERROR_PACKAGE: ${packageName}`)
|
||||
const pkgJson = join(PATH_PACKAGES, packageName, 'package.json')
|
||||
const rawFile = readFileSync(pkgJson, 'utf-8')
|
||||
|
||||
if (!rawFile) throw new Error(`ERROR_FILE_READ`)
|
||||
let pkgJsonObject = JSON.parse(rawFile)
|
||||
const build = !number ? Date.now() : number
|
||||
let [versionNumber] = pkgJsonObject.version.split('-')
|
||||
pkgJsonObject.version = !number ? `${versionNumber}-${build}` : `${number}`
|
||||
pkgJsonObject = JSON.stringify(pkgJsonObject)
|
||||
writeFile(pkgJson, pkgJsonObject, (err) => {
|
||||
if (err) throw err
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Recibe los argumentos entrantes
|
||||
*/
|
||||
if (PKG_ARG) {
|
||||
const pkgName = PKG_ARG ? PKG_ARG.split('=').at(1) : null
|
||||
const pkgNumber = PKG_ARG_VERSION ? PKG_ARG_VERSION.split('=').at(1) : null
|
||||
updateVersion(pkgName, pkgNumber)
|
||||
}
|
||||
@@ -5,11 +5,6 @@ const {
|
||||
addKeyword,
|
||||
} = require('@bot-whatsapp/bot')
|
||||
|
||||
/**
|
||||
* ATENCION: Si vas a usar el provider whatsapp-web.js
|
||||
* recuerda ejecutar npm i whatsapp-web.js@latest
|
||||
*/
|
||||
|
||||
const WebWhatsappProvider = require('@bot-whatsapp/provider/web-whatsapp')
|
||||
const MockAdapter = require('@bot-whatsapp/database/mock')
|
||||
|
||||
|
||||
@@ -6,11 +6,6 @@ const {
|
||||
addChild,
|
||||
} = require('@bot-whatsapp/bot')
|
||||
|
||||
/**
|
||||
* ATENCION: Si vas a usar el provider whatsapp-web.js
|
||||
* recuerda ejecutar npm i whatsapp-web.js@latest
|
||||
*/
|
||||
|
||||
const WebWhatsappProvider = require('@bot-whatsapp/provider/web-whatsapp')
|
||||
const MockAdapter = require('@bot-whatsapp/database/mock')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user