Compare commits

..

1 Commits

Author SHA1 Message Date
github-actions[bot]
3c64a34d77 docs(contributor): contrib-readme-action has updated readme 2023-01-27 09:15:21 +00:00
107 changed files with 1077 additions and 1457 deletions

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env sh #!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh" . "$(dirname -- "$0")/_/husky.sh"
npm run lint:fix && npx --no -- commitlint --edit npx --no -- commitlint --edit

View File

@@ -1,5 +1,5 @@
packages/**/lib packages/**/lib
packages/docs/ packages/docs/*.json
**/.git **/.git
**/.svn **/.svn
**/.hg **/.hg

View File

@@ -2,6 +2,5 @@
"trailingComma": "es5", "trailingComma": "es5",
"tabWidth": 4, "tabWidth": 4,
"semi": false, "semi": false,
"singleQuote": true, "singleQuote": true
"printWidth": 120
} }

View File

@@ -1,3 +1,3 @@
{ {
"recommendations": ["xyc.vscode-mdx-preview", "vivaxy.vscode-conventional-commits", "mhutchie.git-graph"] "recommendations": ["xyc.vscode-mdx-preview"]
} }

View File

@@ -2,57 +2,6 @@
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. 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.20](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.19...v0.1.20) (2023-02-05)
### Features
* **cli:** :fire: add regex expression in addKeyworkd ([e34560c](https://github.com/leifermendez/bot-whatsapp/commit/e34560c77d4852d2e90930f0858e51aa67d4eeab))
* **provider:** :zap: possible get class provider ([76ba717](https://github.com/leifermendez/bot-whatsapp/commit/76ba717927a75b3d6299206aa0b8aee2bc25b726))
### Bug Fixes
* **cli:** :zap: working flowDynamic test ([c0113ca](https://github.com/leifermendez/bot-whatsapp/commit/c0113ca49295aff220d8defcb53f2ba7f2872d75))
* **cli:** :zap: working flowDynamic test ([aef52d2](https://github.com/leifermendez/bot-whatsapp/commit/aef52d2694fa6616d614338643db198b4f7f1fe8))
* **cli:** :zap: working flowDynamic test ([f769320](https://github.com/leifermendez/bot-whatsapp/commit/f76932021ce968d93241b55cfcdb8ae0e0e6c934))
* **cli:** :zap: working flowDynamic test ([23e09ef](https://github.com/leifermendez/bot-whatsapp/commit/23e09efaeccaf51018c55da492edff45b625f0a9))
* **database:** add support emoji in mysql ([9311aa0](https://github.com/leifermendez/bot-whatsapp/commit/9311aa0a65623a1bf40e96207a281625154dae90))
* **database:** fix naming ([cd082f2](https://github.com/leifermendez/bot-whatsapp/commit/cd082f235012cd5f5844c6437f51711beee0c865))
* **database:** fix naming ([1afc3ba](https://github.com/leifermendez/bot-whatsapp/commit/1afc3ba182070713b5bec40eaab0fa1f680830cd))
* **database:** fix naming ([c9831d2](https://github.com/leifermendez/bot-whatsapp/commit/c9831d202ab2c85f15a0247cd2a2426bc435270c))
* **provider:** :zap: baily wa.link ([96c2bff](https://github.com/leifermendez/bot-whatsapp/commit/96c2bffd093269be8e39474a84c156938504a6cb))
### [0.1.19](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.18...v0.1.19) (2023-01-29)
### Features
* :fire: bailey add media ([eab39e4](https://github.com/leifermendez/bot-whatsapp/commit/eab39e4ac06fd46f1a4671f8c15d1456b4400b97))
* :zap: more feature ([e19c3a2](https://github.com/leifermendez/bot-whatsapp/commit/e19c3a25a40259c74b4add9635af4844907eed26))
* **provider:** :rocket: fix issues in providers venom and wwebjs ([cbe438b](https://github.com/leifermendez/bot-whatsapp/commit/cbe438b77854e8df48b9dafaf7a837d21124ac5f))
* **provider:** :rocket: fix provider ([0ad4c58](https://github.com/leifermendez/bot-whatsapp/commit/0ad4c58457b548dc41c0f9e8470d59c48de7b95a))
* **provider:** :rocket: fix provider ([f8c7184](https://github.com/leifermendez/bot-whatsapp/commit/f8c7184487065443ab10f77aaf585e8bd63ca441))
* **provider:** :rocket: fix provider ([b2afa45](https://github.com/leifermendez/bot-whatsapp/commit/b2afa45352a7ab1f5d9775f3c1fde475bd8ca204))
* **provider:** :rocket: fix provider venom and wwebjs ([dcb0566](https://github.com/leifermendez/bot-whatsapp/commit/dcb0566d2bc3da40cd0c71554bb5ea0ec115d9ca))
* **provider:** :rocket: implements all send media to venom provider ([9dd7c02](https://github.com/leifermendez/bot-whatsapp/commit/9dd7c02b6a5474aff063f7d6be0ca8519504b93c))
* **provider:** :rocket: send file wwebjs ([6ff1a3a](https://github.com/leifermendez/bot-whatsapp/commit/6ff1a3a980196c01c66ed04ee07d0e7e57256504))
* **provider:** :zap: bailey add send file video audio ([14d1a61](https://github.com/leifermendez/bot-whatsapp/commit/14d1a61fa259c09135c37c55bd79e97c9c8367e4))
* **provider:** :zap: venom wweb ([fd2847a](https://github.com/leifermendez/bot-whatsapp/commit/fd2847aea0db17a0bdf33b5bca67a4cb8db2da16))
* **provider:** :zap: venom wweb ([f95331d](https://github.com/leifermendez/bot-whatsapp/commit/f95331d3dc70e76a3dfbe4c8d24059f0e7a164ef))
* **provider:** 🚀 implements all send media to venom provider ([bd7d150](https://github.com/leifermendez/bot-whatsapp/commit/bd7d150c047af41fdbb47f0a50a21e82cd79ee85))
### Bug Fixes
* **bot:** :fire: endFlow with ctx ([f6114af](https://github.com/leifermendez/bot-whatsapp/commit/f6114affadfbc324536a86167d1fdfe8da3c8de6))
* **bot:** :fire: endFlow with ctx ([b655ae4](https://github.com/leifermendez/bot-whatsapp/commit/b655ae449e7958ea940d8cc3c678fd66f60b6385))
* **bot:** :zap: endFlow butons ([87a4203](https://github.com/leifermendez/bot-whatsapp/commit/87a4203cd5b88f566387a76d586248e4265d6e4e))
* **bot:** :zap: fix fallback refactor ([e22780d](https://github.com/leifermendez/bot-whatsapp/commit/e22780d3faba94f71a70f1f201a20690608fa5bf))
* **cli:** :zap: endflow ([1c66f17](https://github.com/leifermendez/bot-whatsapp/commit/1c66f178a56d284bb8cb9df5ca17685c7e5d1ddd))
* **cli:** :zap: refactor fallback in child flow ([b33e346](https://github.com/leifermendez/bot-whatsapp/commit/b33e34692d3abcb6874308a9be79f74be4a2c3a8))
* **cli:** :zap: refactor fallback in child flow ([8da4b20](https://github.com/leifermendez/bot-whatsapp/commit/8da4b204b41125b5d0fa0aee4fa87c1f5faf5568))
### [0.1.18](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.17...v0.1.18) (2023-01-24) ### [0.1.18](https://github.com/leifermendez/bot-whatsapp/compare/v0.1.17...v0.1.18) (2023-01-24)

View File

@@ -1,7 +1,8 @@
# Chatbot Library # Chatbot Library
![](https://img.shields.io/npm/v/@bot-whatsapp/bot?color=%2300c200&label=%40bot-whatsapp) ![](https://img.shields.io/npm/v/@bot-whatsapp/bot?color=%2300c200&label=%40bot-whatsapp)
[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)
[![](https://img.shields.io/discord/915193197645402142?logo=discord)](https://link.codigoencasa.com/DISCORD) [![BotWhatsapp Releases(Prod)](https://github.com/codigoencasa/bot-whatsapp/actions/workflows/releases.yml/badge.svg)](https://github.com/codigoencasa/bot-whatsapp/actions/workflows/releases.yml)
<p align="center"> <p align="center">
<img width="300" src="https://i.imgur.com/Oauef6t.png"> <img width="300" src="https://i.imgur.com/Oauef6t.png">
@@ -68,21 +69,14 @@ Entiende más a fondo sus funcionalidades explicadas en nuestra documentación.
<sub><b>Juan Daniel Castaño</b></sub> <sub><b>Juan Daniel Castaño</b></sub>
</a> </a>
</td> </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></tr>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/marianarolfo"> <a href="https://github.com/marianarolfo">
<img src="https://avatars.githubusercontent.com/u/68322254?v=4" width="50;" alt="marianarolfo"/> <img src="https://avatars.githubusercontent.com/u/68322254?v=4" width="50;" alt="marianarolfo"/>
<br /> <br />
<sub><b>Null</b></sub> <sub><b>Null</b></sub>
</a> </a>
</td> </td></tr>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/HKong31"> <a href="https://github.com/HKong31">
<img src="https://avatars.githubusercontent.com/u/113340082?v=4" width="50;" alt="HKong31"/> <img src="https://avatars.githubusercontent.com/u/113340082?v=4" width="50;" alt="HKong31"/>
@@ -117,14 +111,21 @@ Entiende más a fondo sus funcionalidades explicadas en nuestra documentación.
<br /> <br />
<sub><b>Developer RL Business</b></sub> <sub><b>Developer RL Business</b></sub>
</a> </a>
</td></tr> </td>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/Gregoriotecnico"> <a href="https://github.com/Gregoriotecnico">
<img src="https://avatars.githubusercontent.com/u/118696506?v=4" width="50;" alt="Gregoriotecnico"/> <img src="https://avatars.githubusercontent.com/u/118696506?v=4" width="50;" alt="Gregoriotecnico"/>
<br /> <br />
<sub><b>Null</b></sub> <sub><b>Null</b></sub>
</a> </a>
</td></tr>
<tr>
<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>
<td align="center"> <td align="center">
<a href="https://github.com/jlferrete"> <a href="https://github.com/jlferrete">
@@ -133,13 +134,6 @@ Entiende más a fondo sus funcionalidades explicadas en nuestra documentación.
<sub><b>Jose Luis Ferrete Olarte</b></sub> <sub><b>Jose Luis Ferrete Olarte</b></sub>
</a> </a>
</td> </td>
<td align="center">
<a href="https://github.com/lisandroprada">
<img src="https://avatars.githubusercontent.com/u/7232326?v=4" width="50;" alt="lisandroprada"/>
<br />
<sub><b>Null</b></sub>
</a>
</td>
<td align="center"> <td align="center">
<a href="https://github.com/6rak0"> <a href="https://github.com/6rak0">
<img src="https://avatars.githubusercontent.com/u/12260031?v=4" width="50;" alt="6rak0"/> <img src="https://avatars.githubusercontent.com/u/12260031?v=4" width="50;" alt="6rak0"/>
@@ -147,26 +141,18 @@ Entiende más a fondo sus funcionalidades explicadas en nuestra documentación.
<sub><b>Null</b></sub> <sub><b>Null</b></sub>
</a> </a>
</td> </td>
<td align="center">
<a href="https://github.com/Jhonarias13">
<img src="https://avatars.githubusercontent.com/u/19483021?v=4" width="50;" alt="Jhonarias13"/>
<br />
<sub><b>Jhon Freiman Arias</b></sub>
</a>
</td>
<td align="center"> <td align="center">
<a href="https://github.com/tonyvazgar"> <a href="https://github.com/tonyvazgar">
<img src="https://avatars.githubusercontent.com/u/21047090?v=4" width="50;" alt="tonyvazgar"/> <img src="https://avatars.githubusercontent.com/u/21047090?v=4" width="50;" alt="tonyvazgar"/>
<br /> <br />
<sub><b>Luis Antonio Vázquez García</b></sub> <sub><b>Luis Antonio Vázquez García</b></sub>
</a> </a>
</td></tr> </td>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/Zamphiropolos"> <a href="https://github.com/lisandroprada">
<img src="https://avatars.githubusercontent.com/u/40876040?v=4" width="50;" alt="Zamphiropolos"/> <img src="https://avatars.githubusercontent.com/u/7232326?v=4" width="50;" alt="lisandroprada"/>
<br /> <br />
<sub><b>Zamphi</b></sub> <sub><b>Null</b></sub>
</a> </a>
</td> </td>
<td align="center"> <td align="center">
@@ -175,7 +161,8 @@ Entiende más a fondo sus funcionalidades explicadas en nuestra documentación.
<br /> <br />
<sub><b>Rodrigo Mendoza Cabrera</b></sub> <sub><b>Rodrigo Mendoza Cabrera</b></sub>
</a> </a>
</td> </td></tr>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/yond1994"> <a href="https://github.com/yond1994">
<img src="https://avatars.githubusercontent.com/u/47557263?v=4" width="50;" alt="yond1994"/> <img src="https://avatars.githubusercontent.com/u/47557263?v=4" width="50;" alt="yond1994"/>

View File

@@ -2,7 +2,12 @@ const { test } = require('uvu')
const assert = require('uvu/assert') const assert = require('uvu/assert')
const MOCK_DB = require('../packages/database/src/mock') const MOCK_DB = require('../packages/database/src/mock')
const PROVIDER_DB = require('../packages/provider/src/mock') const PROVIDER_DB = require('../packages/provider/src/mock')
const { addKeyword, createBot, createFlow, createProvider } = require('../packages/bot/index') const {
addKeyword,
createBot,
createFlow,
createProvider,
} = require('../packages/bot')
test(`[Caso - 01] Flow Basico`, async () => { test(`[Caso - 01] Flow Basico`, async () => {
const [VALUE_A, VALUE_B] = ['hola', 'buenas'] const [VALUE_A, VALUE_B] = ['hola', 'buenas']

View File

@@ -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))
}

View File

@@ -1,111 +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))
}

View File

@@ -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))
}

View File

@@ -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))
}

View File

@@ -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))
}

View File

@@ -1,6 +1,6 @@
{ {
"name": "@bot-whatsapp/root", "name": "@bot-whatsapp/root",
"version": "0.1.20", "version": "0.1.18",
"description": "Bot de wahtsapp open source para MVP o pequeños negocios", "description": "Bot de wahtsapp open source para MVP o pequeños negocios",
"main": "app.js", "main": "app.js",
"private": true, "private": true,
@@ -81,7 +81,6 @@
"fs-extra": "^11.1.0", "fs-extra": "^11.1.0",
"git-cz": "^4.9.0", "git-cz": "^4.9.0",
"husky": "^8.0.2", "husky": "^8.0.2",
"mime-types": "^2.1.35",
"only-allow": "^1.1.1", "only-allow": "^1.1.1",
"prettier": "^2.8.0", "prettier": "^2.8.0",
"pretty-quick": "^3.1.3", "pretty-quick": "^3.1.3",

View File

@@ -43,7 +43,8 @@ class CoreClass {
}, },
{ {
event: 'require_action', event: 'require_action',
func: ({ instructions, title = '⚡⚡ ACCIÓN REQUERIDA ⚡⚡' }) => printer(instructions, title), func: ({ instructions, title = '⚡⚡ ACCIÓN REQUERIDA ⚡⚡' }) =>
printer(instructions, title),
}, },
{ {
event: 'ready', event: 'ready',
@@ -51,7 +52,8 @@ class CoreClass {
}, },
{ {
event: 'auth_failure', event: 'auth_failure',
func: ({ instructions }) => printer(instructions, '⚡⚡ ERROR AUTH ⚡⚡'), func: ({ instructions }) =>
printer(instructions, '⚡⚡ ERROR AUTH ⚡⚡'),
}, },
{ {
@@ -69,14 +71,16 @@ class CoreClass {
logger.log(`[handleMsg]: `, messageCtxInComming) logger.log(`[handleMsg]: `, messageCtxInComming)
const { body, from } = messageCtxInComming const { body, from } = messageCtxInComming
let msgToSend = [] let msgToSend = []
let endFlowFlag = false
let fallBackFlag = false let fallBackFlag = false
let endFlowFlag = false
if (this.generalArgs.blackList.includes(from)) return if (this.generalArgs.blackList.includes(from)) return
if (!body) return if (!body) return
if (!body.length) return if (!body.length) return
let prevMsg = await this.databaseClass.getPrevByNumber(from) let prevMsg = await this.databaseClass.getPrevByNumber(from)
const refToContinue = this.flowClass.findBySerialize(prevMsg?.refSerialize) const refToContinue = this.flowClass.findBySerialize(
prevMsg?.refSerialize
)
if (prevMsg?.ref) { if (prevMsg?.ref) {
const ctxByNumber = toCtx({ const ctxByNumber = toCtx({
@@ -87,22 +91,6 @@ class CoreClass {
this.databaseClass.save(ctxByNumber) this.databaseClass.save(ctxByNumber)
} }
// 📄 Crar CTX de mensaje (uso private)
const createCtxMessage = (payload = {}, index = 0) => {
const body = typeof payload === 'string' ? payload : payload?.body ?? payload?.answer
const media = payload?.media ?? null
const buttons = payload?.buttons ?? []
const capture = payload?.capture ?? false
return toCtx({
body,
from,
keyword: null,
index,
options: { media, buttons, capture },
})
}
// 📄 Limpiar cola de procesos // 📄 Limpiar cola de procesos
const clearQueue = () => { const clearQueue = () => {
QueuePrincipal.pendingPromise = false QueuePrincipal.pendingPromise = false
@@ -110,18 +98,18 @@ class CoreClass {
} }
// 📄 Finalizar flujo // 📄 Finalizar flujo
const endFlow = async (message = null) => { const endFlow = async () => {
prevMsg = null
endFlowFlag = true endFlowFlag = true
if (message) this.sendProviderAndSave(from, createCtxMessage(message))
clearQueue() clearQueue()
sendFlow([])
return return
} }
// 📄 Esta funcion se encarga de enviar un array de mensajes dentro de este ctx // 📄 Esta funcion se encarga de enviar un array de mensajes dentro de este ctx
const sendFlow = async (messageToSend, numberOrId, options = { prev: prevMsg }) => { const sendFlow = async (messageToSend, numberOrId) => {
if (options.prev?.options?.capture) await cbEveryCtx(options.prev?.ref) // [1 Paso] esto esta bien!
if (prevMsg?.options?.capture) await cbEveryCtx(prevMsg?.ref)
const queue = [] const queue = []
for (const ctxMessage of messageToSend) { for (const ctxMessage of messageToSend) {
if (endFlowFlag) return if (endFlowFlag) return
@@ -129,7 +117,9 @@ class CoreClass {
if (delayMs) await delay(delayMs) if (delayMs) await delay(delayMs)
QueuePrincipal.enqueue(() => QueuePrincipal.enqueue(() =>
Promise.all([ Promise.all([
this.sendProviderAndSave(numberOrId, ctxMessage).then(() => resolveCbEveryCtx(ctxMessage)), this.sendProviderAndSave(numberOrId, ctxMessage).then(
() => resolveCbEveryCtx(ctxMessage)
),
]) ])
) )
} }
@@ -137,70 +127,57 @@ class CoreClass {
} }
// 📄 [options: fallBack]: esta funcion se encarga de repetir el ultimo mensaje // 📄 [options: fallBack]: esta funcion se encarga de repetir el ultimo mensaje
const fallBack = async (validation = false, message = null) => { const fallBack = async () => {
fallBackFlag = true
await this.sendProviderAndSave(from, refToContinue)
QueuePrincipal.queue = [] QueuePrincipal.queue = []
return refToContinue
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 })
}
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 // 📄 [options: flowDynamic]: esta funcion se encarga de responder un array de respuesta esta limitado a 5 mensajes
// para evitar bloque de whatsapp // para evitar bloque de whatsapp
const flowDynamic = async (listMsg = []) => { const flowDynamic = async (
if (!Array.isArray(listMsg)) listMsg = [listMsg] listMsg = [],
optListMsg = { limit: 5, fallback: false }
) => {
if (!Array.isArray(listMsg))
throw new Error('Esto debe ser un ARRAY')
const parseListMsg = listMsg.map((opt, index) => createCtxMessage(opt, index)) fallBackFlag = optListMsg.fallback
const currentPrev = await this.databaseClass.getPrevByNumber(from) const parseListMsg = listMsg
.map((opt, index) => {
const body = typeof opt === 'string' ? opt : opt.body
const media = opt?.media ?? null
const buttons = opt?.buttons ?? []
const skipContinueFlow = async () => { return toCtx({
const nextFlow = await this.flowClass.find(refToContinue?.ref, true) body,
const filterNextFlow = nextFlow.filter((msg) => msg.refSerialize !== currentPrev?.refSerialize) from,
const isContinueFlow = filterNextFlow.map((i) => i.keyword).includes(currentPrev?.ref) keyword: null,
return { index,
continue: !isContinueFlow, options: { media, buttons },
contexts: filterNextFlow, })
} })
} .slice(0, optListMsg.limit)
if (endFlowFlag) return if (endFlowFlag) return
for (const msg of parseListMsg) { for (const msg of parseListMsg) {
await this.sendProviderAndSave(from, msg) await this.sendProviderAndSave(from, msg)
} }
const continueFlowData = await skipContinueFlow()
if (continueFlowData.continue) return sendFlow(continueFlowData.contexts, from, { prev: undefined })
return return
} }
// 📄 Se encarga de revisar si el contexto del mensaje tiene callback o fallback // 📄 Se encarga de revisar si el contexto del mensaje tiene callback o fallback
const resolveCbEveryCtx = async (ctxMessage) => { const resolveCbEveryCtx = async (ctxMessage) => {
if (!ctxMessage?.options?.capture) return await cbEveryCtx(ctxMessage?.ref) if (!ctxMessage?.options?.capture)
return await cbEveryCtx(ctxMessage?.ref)
} }
// 📄 Se encarga de revisar si el contexto del mensaje tiene callback y ejecutarlo // 📄 Se encarga de revisar si el contexto del mensaje tiene callback y ejecutarlo
const cbEveryCtx = async (inRef) => { const cbEveryCtx = async (inRef) => {
const provider = this.providerClass
if (!this.flowClass.allCallbacks[inRef]) return Promise.resolve() if (!this.flowClass.allCallbacks[inRef]) return Promise.resolve()
return this.flowClass.allCallbacks[inRef](messageCtxInComming, { return this.flowClass.allCallbacks[inRef](messageCtxInComming, {
provider,
fallBack, fallBack,
flowDynamic, flowDynamic,
endFlow, endFlow,
@@ -208,7 +185,7 @@ class CoreClass {
} }
// 📄🤘(tiene return) [options: nested(array)]: Si se tiene flujos hijos los implementa // 📄🤘(tiene return) [options: nested(array)]: Si se tiene flujos hijos los implementa
if (!endFlowFlag && prevMsg?.options?.nested?.length) { if (!fallBackFlag && prevMsg?.options?.nested?.length) {
const nestedRef = prevMsg.options.nested const nestedRef = prevMsg.options.nested
const flowStandalone = nestedRef.map((f) => ({ const flowStandalone = nestedRef.map((f) => ({
...nestedRef.find((r) => r.refSerialize === f.refSerialize), ...nestedRef.find((r) => r.refSerialize === f.refSerialize),
@@ -220,11 +197,12 @@ class CoreClass {
return return
} }
// 📄🤘(tiene return) Si el mensaje previo implementa capture // 📄🤘(tiene return) [options: capture (boolean)]: Si se tiene option boolean
if (!endFlowFlag && !prevMsg?.options?.nested?.length) { if (!fallBackFlag && !prevMsg?.options?.nested?.length) {
const typeCapture = typeof prevMsg?.options?.capture const typeCapture = typeof prevMsg?.options?.capture
const valueCapture = prevMsg?.options?.capture
if (typeCapture === 'boolean' && fallBackFlag) { if (['string', 'boolean'].includes(typeCapture) && valueCapture) {
msgToSend = this.flowClass.find(refToContinue?.ref, true) || [] msgToSend = this.flowClass.find(refToContinue?.ref, true) || []
sendFlow(msgToSend, from) sendFlow(msgToSend, from)
return return
@@ -243,13 +221,13 @@ class CoreClass {
*/ */
sendProviderAndSave = (numberOrId, ctxMessage) => { sendProviderAndSave = (numberOrId, ctxMessage) => {
const { answer } = ctxMessage const { answer } = ctxMessage
return this.providerClass return Promise.all([
.sendMessage(numberOrId, answer, ctxMessage) this.providerClass.sendMessage(numberOrId, answer, ctxMessage),
.then(() => this.databaseClass.save({ ...ctxMessage, from: numberOrId })) this.databaseClass.save({ ...ctxMessage, from: numberOrId }),
])
} }
/** /**
* @deprecated
* @private * @private
* @param {*} message * @param {*} message
* @param {*} ref * @param {*} ref
@@ -275,7 +253,9 @@ class CoreClass {
for (const ctxMessage of messageToSend) { for (const ctxMessage of messageToSend) {
const delayMs = ctxMessage?.options?.delay || 0 const delayMs = ctxMessage?.options?.delay || 0
if (delayMs) await delay(delayMs) if (delayMs) await delay(delayMs)
QueuePrincipal.enqueue(() => this.sendProviderAndSave(numberOrId, ctxMessage)) QueuePrincipal.enqueue(() =>
this.sendProviderAndSave(numberOrId, ctxMessage)
)
} }
return Promise.all(queue) return Promise.all(queue)
} }

View File

@@ -8,7 +8,8 @@ const { addKeyword, addAnswer, addChild, toSerialize } = require('./io/methods')
* @param {*} args * @param {*} args
* @returns * @returns
*/ */
const createBot = async ({ flow, database, provider }, args = {}) => new CoreClass(flow, database, provider, args) const createBot = async ({ flow, database, provider }, args = {}) =>
new CoreClass(flow, database, provider, args)
/** /**
* Crear instancia de clase Io (Flow) * Crear instancia de clase Io (Flow)
@@ -28,7 +29,8 @@ const createFlow = (args) => {
*/ */
const createProvider = (providerClass = class {}, args = null) => { const createProvider = (providerClass = class {}, args = null) => {
const providerInstance = new providerClass(args) const providerInstance = new providerClass(args)
if (!providerClass.prototype instanceof ProviderClass) throw new Error('El provider no implementa ProviderClass') if (!providerClass.prototype instanceof ProviderClass)
throw new Error('El provider no implementa ProviderClass')
return providerInstance return providerInstance
} }

View File

@@ -25,17 +25,9 @@ class FlowClass {
let refSymbol = null let refSymbol = null
overFlow = overFlow ?? this.flowSerialize 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 */ /** Retornar expresion regular para buscar coincidencia */
const mapSensitive = (str, mapOptions = { sensitive: false, regex: false }) => { const mapSensitive = (str, flag = false) => {
if (mapOptions.regex) return customRegex(str) const regexSensitive = flag ? 'g' : 'i'
const regexSensitive = mapOptions.sensitive ? 'g' : 'i'
if (Array.isArray(str)) { if (Array.isArray(str)) {
return new RegExp(str.join('|'), regexSensitive) return new RegExp(str.join('|'), regexSensitive)
} }
@@ -44,7 +36,6 @@ class FlowClass {
const findIn = (keyOrWord, symbol = false, flow = overFlow) => { const findIn = (keyOrWord, symbol = false, flow = overFlow) => {
const sensitive = refSymbol?.options?.sensitive || false const sensitive = refSymbol?.options?.sensitive || false
const regex = refSymbol?.options?.regex || false
capture = refSymbol?.options?.capture || false capture = refSymbol?.options?.capture || false
if (capture) return messages if (capture) return messages
@@ -55,7 +46,7 @@ class FlowClass {
if (refSymbol?.ref) findIn(refSymbol.ref, true) if (refSymbol?.ref) findIn(refSymbol.ref, true)
} else { } else {
refSymbol = flow.find((c) => { refSymbol = flow.find((c) => {
return mapSensitive(c.keyword, { sensitive, regex }).test(keyOrWord) return mapSensitive(c.keyword, sensitive).test(keyOrWord)
}) })
if (refSymbol?.ref) findIn(refSymbol.ref, true) if (refSymbol?.ref) findIn(refSymbol.ref, true)
return messages return messages
@@ -65,7 +56,8 @@ class FlowClass {
return messages return messages
} }
findBySerialize = (refSerialize) => this.flowSerialize.find((r) => r.refSerialize === refSerialize) findBySerialize = (refSerialize) =>
this.flowSerialize.find((r) => r.refSerialize === refSerialize)
findIndexByRef = (ref) => this.flowSerialize.findIndex((r) => r.ref === ref) findIndexByRef = (ref) => this.flowSerialize.findIndex((r) => r.ref === ref)
} }

View File

@@ -17,10 +17,15 @@ const addAnswer =
* @returns * @returns
*/ */
const getAnswerOptions = () => ({ const getAnswerOptions = () => ({
media: typeof options?.media === 'string' ? `${options?.media}` : null, media:
typeof options?.media === 'string' ? `${options?.media}` : null,
buttons: Array.isArray(options?.buttons) ? options.buttons : [], buttons: Array.isArray(options?.buttons) ? options.buttons : [],
capture: typeof options?.capture === 'boolean' ? options?.capture : false, capture:
child: typeof options?.child === 'string' ? `${options?.child}` : null, typeof options?.capture === 'boolean'
? options?.capture
: false,
child:
typeof options?.child === 'string' ? `${options?.child}` : null,
delay: typeof options?.delay === 'number' ? options?.delay : 0, delay: typeof options?.delay === 'number' ? options?.delay : 0,
}) })
@@ -44,7 +49,8 @@ const addAnswer =
* Esta funcion aplana y busca los callback anidados de los hijos * Esta funcion aplana y busca los callback anidados de los hijos
* @returns * @returns
*/ */
const getCbFromNested = () => flatObject(Array.isArray(nested) ? nested : [nested]) const getCbFromNested = () =>
flatObject(Array.isArray(nested) ? nested : [nested])
const callback = typeof cb === 'function' ? cb : () => null const callback = typeof cb === 'function' ? cb : () => null

View File

@@ -8,14 +8,12 @@ const { toJson } = require('./toJson')
* @param {*} options {sensitive:boolean} default false * @param {*} options {sensitive:boolean} default false
*/ */
const addKeyword = (keyword, options) => { const addKeyword = (keyword, options) => {
if (typeof keyword !== 'string' && !Array.isArray(keyword)) {
throw new Error('DEBE_SER_STRING_ARRAY_REGEX')
}
const parseOptions = () => { const parseOptions = () => {
const defaultProperties = { const defaultProperties = {
sensitive: typeof options?.sensitive === 'boolean' ? options?.sensitive : false, sensitive:
regex: typeof options?.regex === 'boolean' ? options?.regex : false, typeof options?.sensitive === 'boolean'
? options?.sensitive
: false,
} }
return defaultProperties return defaultProperties

View File

@@ -1,6 +1,6 @@
{ {
"name": "@bot-whatsapp/bot", "name": "@bot-whatsapp/bot",
"version": "0.0.96-alpha.0", "version": "0.0.73-alpha.0",
"description": "", "description": "",
"main": "./lib/bundle.bot.cjs", "main": "./lib/bundle.bot.cjs",
"scripts": { "scripts": {

View File

@@ -20,7 +20,8 @@ class ProviderClass extends EventEmitter {
*/ */
sendMessage = async (userId, message) => { sendMessage = async (userId, message) => {
if (NODE_ENV !== 'production') console.log('[sendMessage]', { userId, message }) if (NODE_ENV !== 'production')
console.log('[sendMessage]', { userId, message })
return message return message
} }
} }

View File

@@ -2,7 +2,13 @@ const { test } = require('uvu')
const assert = require('uvu/assert') const assert = require('uvu/assert')
const FlowClass = require('../io/flow.class') const FlowClass = require('../io/flow.class')
const MockProvider = require('../../../__mocks__/mock.provider') const MockProvider = require('../../../__mocks__/mock.provider')
const { createBot, CoreClass, createFlow, createProvider, ProviderClass } = require('../index') const {
createBot,
CoreClass,
createFlow,
createProvider,
ProviderClass,
} = require('../index')
class MockFlow { class MockFlow {
allCallbacks = { ref: () => 1 } allCallbacks = { ref: () => 1 }
@@ -94,13 +100,20 @@ test(`[Bot] Eventos 'require_action,ready,auth_failure,message '`, async () => {
await createBot(setting) await createBot(setting)
/// Escuchamos eventos /// Escuchamos eventos
mockProvider.on('require_action', (r) => (responseEvents['require_action'] = r)) mockProvider.on(
'require_action',
(r) => (responseEvents['require_action'] = r)
)
mockProvider.on('ready', (r) => (responseEvents['ready'] = r)) mockProvider.on('ready', (r) => (responseEvents['ready'] = r))
mockProvider.on('auth_failure', (r) => (responseEvents['auth_failure'] = r)) mockProvider.on('auth_failure', (r) => (responseEvents['auth_failure'] = r))
mockProvider.on('message', (r) => (responseEvents['message'] = r)) mockProvider.on('message', (r) => (responseEvents['message'] = r))
/// Emitimos eventos /// Emitimos eventos
mockProvider.delaySendMessage(0, 'require_action', MOCK_EVENTS.require_action) mockProvider.delaySendMessage(
0,
'require_action',
MOCK_EVENTS.require_action
)
mockProvider.delaySendMessage(0, 'ready', MOCK_EVENTS.ready) mockProvider.delaySendMessage(0, 'ready', MOCK_EVENTS.ready)
mockProvider.delaySendMessage(0, 'auth_failure', MOCK_EVENTS.auth_failure) mockProvider.delaySendMessage(0, 'auth_failure', MOCK_EVENTS.auth_failure)
mockProvider.delaySendMessage(0, 'message', MOCK_EVENTS.message) mockProvider.delaySendMessage(0, 'message', MOCK_EVENTS.message)
@@ -108,12 +121,21 @@ test(`[Bot] Eventos 'require_action,ready,auth_failure,message '`, async () => {
await delay(0) await delay(0)
/// Testeamos eventos /// Testeamos eventos
assert.is(JSON.stringify(responseEvents.require_action), JSON.stringify(MOCK_EVENTS.require_action)) assert.is(
JSON.stringify(responseEvents.require_action),
JSON.stringify(MOCK_EVENTS.require_action)
)
assert.is(responseEvents.ready, MOCK_EVENTS.ready) assert.is(responseEvents.ready, MOCK_EVENTS.ready)
assert.is(JSON.stringify(responseEvents.auth_failure), JSON.stringify(MOCK_EVENTS.auth_failure)) assert.is(
JSON.stringify(responseEvents.auth_failure),
JSON.stringify(MOCK_EVENTS.auth_failure)
)
assert.is(JSON.stringify(responseEvents.message), JSON.stringify(MOCK_EVENTS.message)) assert.is(
JSON.stringify(responseEvents.message),
JSON.stringify(MOCK_EVENTS.message)
)
}) })
test(`[Bot] Probando Flujos Internos`, async () => { test(`[Bot] Probando Flujos Internos`, async () => {
@@ -144,13 +166,20 @@ test(`[Bot] Probando Flujos Internos`, async () => {
await createBot(setting) await createBot(setting)
/// Escuchamos eventos /// Escuchamos eventos
mockProvider.on('require_action', (r) => (responseEvents['require_action'] = r)) mockProvider.on(
'require_action',
(r) => (responseEvents['require_action'] = r)
)
mockProvider.on('ready', (r) => (responseEvents['ready'] = r)) mockProvider.on('ready', (r) => (responseEvents['ready'] = r))
mockProvider.on('auth_failure', (r) => (responseEvents['auth_failure'] = r)) mockProvider.on('auth_failure', (r) => (responseEvents['auth_failure'] = r))
mockProvider.on('message', (r) => (responseEvents['message'] = r)) mockProvider.on('message', (r) => (responseEvents['message'] = r))
/// Emitimos eventos /// Emitimos eventos
mockProvider.delaySendMessage(0, 'require_action', MOCK_EVENTS.require_action) mockProvider.delaySendMessage(
0,
'require_action',
MOCK_EVENTS.require_action
)
mockProvider.delaySendMessage(0, 'ready', MOCK_EVENTS.ready) mockProvider.delaySendMessage(0, 'ready', MOCK_EVENTS.ready)
mockProvider.delaySendMessage(0, 'auth_failure', MOCK_EVENTS.auth_failure) mockProvider.delaySendMessage(0, 'auth_failure', MOCK_EVENTS.auth_failure)
mockProvider.delaySendMessage(0, 'message', MOCK_EVENTS.message) mockProvider.delaySendMessage(0, 'message', MOCK_EVENTS.message)
@@ -158,12 +187,21 @@ test(`[Bot] Probando Flujos Internos`, async () => {
await delay(0) await delay(0)
/// Testeamos eventos /// Testeamos eventos
assert.is(JSON.stringify(responseEvents.require_action), JSON.stringify(MOCK_EVENTS.require_action)) assert.is(
JSON.stringify(responseEvents.require_action),
JSON.stringify(MOCK_EVENTS.require_action)
)
assert.is(responseEvents.ready, MOCK_EVENTS.ready) assert.is(responseEvents.ready, MOCK_EVENTS.ready)
assert.is(JSON.stringify(responseEvents.auth_failure), JSON.stringify(MOCK_EVENTS.auth_failure)) assert.is(
JSON.stringify(responseEvents.auth_failure),
JSON.stringify(MOCK_EVENTS.auth_failure)
)
assert.is(JSON.stringify(responseEvents.message), JSON.stringify(MOCK_EVENTS.message)) assert.is(
JSON.stringify(responseEvents.message),
JSON.stringify(MOCK_EVENTS.message)
)
}) })
test(`[Bot] Probando Flujos Nested`, async () => { test(`[Bot] Probando Flujos Nested`, async () => {
@@ -196,13 +234,20 @@ test(`[Bot] Probando Flujos Nested`, async () => {
botInstance.sendProviderAndSave('xxxxx', 'xxxxx') botInstance.sendProviderAndSave('xxxxx', 'xxxxx')
botInstance.continue('xxxxx', 'xxxxx') botInstance.continue('xxxxx', 'xxxxx')
/// Escuchamos eventos /// Escuchamos eventos
mockProvider.on('require_action', (r) => (responseEvents['require_action'] = r)) mockProvider.on(
'require_action',
(r) => (responseEvents['require_action'] = r)
)
mockProvider.on('ready', (r) => (responseEvents['ready'] = r)) mockProvider.on('ready', (r) => (responseEvents['ready'] = r))
mockProvider.on('auth_failure', (r) => (responseEvents['auth_failure'] = r)) mockProvider.on('auth_failure', (r) => (responseEvents['auth_failure'] = r))
mockProvider.on('message', (r) => (responseEvents['message'] = r)) mockProvider.on('message', (r) => (responseEvents['message'] = r))
/// Emitimos eventos /// Emitimos eventos
mockProvider.delaySendMessage(0, 'require_action', MOCK_EVENTS.require_action) mockProvider.delaySendMessage(
0,
'require_action',
MOCK_EVENTS.require_action
)
mockProvider.delaySendMessage(0, 'ready', MOCK_EVENTS.ready) mockProvider.delaySendMessage(0, 'ready', MOCK_EVENTS.ready)
mockProvider.delaySendMessage(0, 'auth_failure', MOCK_EVENTS.auth_failure) mockProvider.delaySendMessage(0, 'auth_failure', MOCK_EVENTS.auth_failure)
mockProvider.delaySendMessage(0, 'message', MOCK_EVENTS.message) mockProvider.delaySendMessage(0, 'message', MOCK_EVENTS.message)
@@ -210,12 +255,21 @@ test(`[Bot] Probando Flujos Nested`, async () => {
await delay(0) await delay(0)
/// Testeamos eventos /// Testeamos eventos
assert.is(JSON.stringify(responseEvents.require_action), JSON.stringify(MOCK_EVENTS.require_action)) assert.is(
JSON.stringify(responseEvents.require_action),
JSON.stringify(MOCK_EVENTS.require_action)
)
assert.is(responseEvents.ready, MOCK_EVENTS.ready) assert.is(responseEvents.ready, MOCK_EVENTS.ready)
assert.is(JSON.stringify(responseEvents.auth_failure), JSON.stringify(MOCK_EVENTS.auth_failure)) assert.is(
JSON.stringify(responseEvents.auth_failure),
JSON.stringify(MOCK_EVENTS.auth_failure)
)
assert.is(JSON.stringify(responseEvents.message), JSON.stringify(MOCK_EVENTS.message)) assert.is(
JSON.stringify(responseEvents.message),
JSON.stringify(MOCK_EVENTS.message)
)
}) })
test.run() test.run()

View File

@@ -35,7 +35,10 @@ test('Debere probar toSerialize', () => {
const ARRANGE = { const ARRANGE = {
keyword: ['hola!', 'ole'], keyword: ['hola!', 'ole'],
} }
const MAIN_CTX = addKeyword(ARRANGE.keyword).addAnswer('Segundo!').addAnswer('Segundo!').toJson() const MAIN_CTX = addKeyword(ARRANGE.keyword)
.addAnswer('Segundo!')
.addAnswer('Segundo!')
.toJson()
const [ANSWER_A] = MAIN_CTX const [ANSWER_A] = MAIN_CTX
@@ -68,7 +71,9 @@ test('Debere probar la anidación', () => {
answer_A: 'Bienvenido', answer_A: 'Bienvenido',
answer_B: 'Continuar', answer_B: 'Continuar',
} }
const MAIN_CTX = addKeyword(ARRANGE.keyword).addAnswer(ARRANGE.answer_A).addAnswer(ARRANGE.answer_B) const MAIN_CTX = addKeyword(ARRANGE.keyword)
.addAnswer(ARRANGE.answer_A)
.addAnswer(ARRANGE.answer_B)
assert.is(MAIN_CTX.ctx.answer, ARRANGE.answer_B) assert.is(MAIN_CTX.ctx.answer, ARRANGE.answer_B)
}) })
@@ -102,7 +107,10 @@ test('Debere probar error las addAnswer', () => {
}) })
test('Obtener toJson', () => { test('Obtener toJson', () => {
const [ctxA, ctxB, ctxC] = addKeyword('hola').addAnswer('pera!').addAnswer('chao').toJson() const [ctxA, ctxB, ctxC] = addKeyword('hola')
.addAnswer('pera!')
.addAnswer('chao')
.toJson()
assert.is(ctxA.keyword, 'hola') assert.is(ctxA.keyword, 'hola')
assert.match(ctxA.ref, /^key_/) assert.match(ctxA.ref, /^key_/)

View File

@@ -1,3 +1,4 @@
const delay = (miliseconds) => new Promise((res) => setTimeout(res, miliseconds)) const delay = (miliseconds) =>
new Promise((res) => setTimeout(res, miliseconds))
module.exports = { delay } module.exports = { delay }

View File

@@ -3,7 +3,9 @@ const flatObject = (listArray = []) => {
if (!listArray.length) return {} if (!listArray.length) return {}
const cbNestedObj = cbNestedList.map(({ ctx }) => ctx?.callbacks).filter((i) => !!i) const cbNestedObj = cbNestedList
.map(({ ctx }) => ctx?.callbacks)
.filter((i) => !!i)
const queueCb = cbNestedObj.reduce((acc, current) => { const queueCb = cbNestedObj.reduce((acc, current) => {
const getKeys = Object.keys(current) const getKeys = Object.keys(current)
const parse = getKeys.map((icb, i) => ({ const parse = getKeys.map((icb, i) => ({

View File

@@ -16,6 +16,9 @@ const generateRef = (prefix = false) => {
* @returns * @returns
*/ */
const generateRefSerialize = ({ index, answer, keyword }) => const generateRefSerialize = ({ index, answer, keyword }) =>
crypto.createHash('md5').update(JSON.stringify({ index, answer, keyword })).digest('hex') crypto
.createHash('md5')
.update(JSON.stringify({ index, answer, keyword }))
.digest('hex')
module.exports = { generateRef, generateRefSerialize } module.exports = { generateRef, generateRefSerialize }

View File

@@ -4,7 +4,9 @@ const printer = (message, title) => {
if (NODE_ENV !== 'test') { if (NODE_ENV !== 'test') {
// console.clear() // console.clear()
if (title) console.log(bgRed(`${title}`)) if (title) console.log(bgRed(`${title}`))
console.log(yellow(Array.isArray(message) ? message.join('\n') : message)) console.log(
yellow(Array.isArray(message) ? message.join('\n') : message)
)
console.log(``) console.log(``)
} }
} }

View File

@@ -5,9 +5,15 @@ const checkNodeVersion = () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
console.log(bgCyan('🚀 Revisando tu Node.js')) console.log(bgCyan('🚀 Revisando tu Node.js'))
const version = process.version const version = process.version
const majorVersion = parseInt(version.replace('v', '').split('.').shift()) const majorVersion = parseInt(
version.replace('v', '').split('.').shift()
)
if (majorVersion < 16) { if (majorVersion < 16) {
console.error(red(`🔴 Se require Node.js 16 o superior. Actualmente esta ejecutando Node.js ${version}`)) console.error(
red(
`🔴 Se require Node.js 16 o superior. Actualmente esta ejecutando Node.js ${version}`
)
)
console.log(``) console.log(``)
reject('ERROR_NODE') reject('ERROR_NODE')
} }

View File

@@ -2,7 +2,10 @@ const rimraf = require('rimraf')
const { yellow } = require('kleur') const { yellow } = require('kleur')
const { join } = require('path') const { join } = require('path')
const PATH_WW = [join(process.cwd(), '.wwebjs_auth'), join(process.cwd(), 'session.json')] const PATH_WW = [
join(process.cwd(), '.wwebjs_auth'),
join(process.cwd(), 'session.json'),
]
const cleanSession = () => { const cleanSession = () => {
const queue = [] const queue = []

View File

@@ -23,7 +23,11 @@ const JSON_TEMPLATE = {
const PATH_CONFIG = join(process.cwd(), 'config.json') const PATH_CONFIG = join(process.cwd(), 'config.json')
const jsonConfig = () => { const jsonConfig = () => {
return writeFile(PATH_CONFIG, JSON.stringify(JSON_TEMPLATE, null, 2), 'utf-8') return writeFile(
PATH_CONFIG,
JSON.stringify(JSON_TEMPLATE, null, 2),
'utf-8'
)
} }
module.exports = { jsonConfig } module.exports = { jsonConfig }

View File

@@ -20,9 +20,13 @@ const installDeps = (pkgManager, packageList) => {
const installSingle = (pkgInstall) => () => { const installSingle = (pkgInstall) => () => {
new Promise((resolve) => { new Promise((resolve) => {
try { try {
childProcess = spawn(pkgManager, [PKG_OPTION[pkgManager], pkgInstall], { childProcess = spawn(
stdio: 'inherit', pkgManager,
}) [PKG_OPTION[pkgManager], pkgInstall],
{
stdio: 'inherit',
}
)
childProcess.on('error', (e) => { childProcess.on('error', (e) => {
console.error(e) console.error(e)

View File

@@ -30,7 +30,9 @@ const startInteractive = async () => {
await nextSteps() await nextSteps()
} catch (e) { } catch (e) {
console.error(bgRed(`Ups! 🙄 algo no va bien.`)) console.error(bgRed(`Ups! 🙄 algo no va bien.`))
console.error(bgRed(`Revisa los requerimientos minimos en la documentacion`)) console.error(
bgRed(`Revisa los requerimientos minimos en la documentacion`)
)
} }
} }
@@ -80,7 +82,8 @@ const nextSteps = async () => {
const { outDir = '', providerDb = [], providerWs = [] } = response const { outDir = '', providerDb = [], providerWs = [] } = response
const createApp = async (templateName = null) => { const createApp = async (templateName = null) => {
if (!templateName) throw new Error('TEMPLATE_NAME_INVALID: ', templateName) if (!templateName)
throw new Error('TEMPLATE_NAME_INVALID: ', templateName)
const possiblesPath = [ const possiblesPath = [
join(__dirname, '..', '..', 'starters', 'apps', templateName), join(__dirname, '..', '..', 'starters', 'apps', templateName),
@@ -112,7 +115,11 @@ const nextSteps = async () => {
const vendorProvider = async () => { const vendorProvider = async () => {
const [answer] = providerWs const [answer] = providerWs
if (!providerWs.length) { if (!providerWs.length) {
console.log(red(`Debes seleccionar un proveedor de whatsapp. Tecla [Space] para seleccionar`)) console.log(
red(
`Debes seleccionar un proveedor de whatsapp. Tecla [Space] para seleccionar`
)
)
process.exit(1) process.exit(1)
} }
return answer return answer
@@ -125,7 +132,11 @@ const nextSteps = async () => {
const dbProvider = async () => { const dbProvider = async () => {
const [answer] = providerDb const [answer] = providerDb
if (!providerDb.length) { if (!providerDb.length) {
console.log(red(`Debes seleccionar un proveedor de base de datos. Tecla [Space] para seleccionar`)) console.log(
red(
`Debes seleccionar un proveedor de base de datos. Tecla [Space] para seleccionar`
)
)
process.exit(1) process.exit(1)
} }
return answer return answer

View File

@@ -38,8 +38,10 @@ class DialogFlowCXContext extends CoreClass {
* */ * */
} }
if (!this.optionsDX.location.length) throw new Error('LOCATION_NO_ENCONTRADO') if (!this.optionsDX.location.length)
if (!this.optionsDX.agentId.length) throw new Error('AGENTID_NO_ENCONTRADO') throw new Error('LOCATION_NO_ENCONTRADO')
if (!this.optionsDX.agentId.length)
throw new Error('AGENTID_NO_ENCONTRADO')
const rawJson = readFileSync(GOOGLE_ACCOUNT_PATH, 'utf-8') const rawJson = readFileSync(GOOGLE_ACCOUNT_PATH, 'utf-8')
const { project_id, private_key, client_email } = JSON.parse(rawJson) const { project_id, private_key, client_email } = JSON.parse(rawJson)
@@ -84,7 +86,9 @@ class DialogFlowCXContext extends CoreClass {
}, },
} }
const [single] = (await this.sessionClient.detectIntent(reqDialog)) || [null] const [single] = (await this.sessionClient.detectIntent(reqDialog)) || [
null,
]
const listMessages = single.queryResult.responseMessages.map((res) => { const listMessages = single.queryResult.responseMessages.map((res) => {
if (res.message == 'text') { if (res.message == 'text') {
@@ -92,11 +96,17 @@ class DialogFlowCXContext extends CoreClass {
} }
if (res.message == 'payload') { if (res.message == 'payload') {
const { media = null, buttons = [], answer = '' } = res.payload.fields const {
const buttonsArray = buttons?.listValue?.values?.map((btnValue) => { media = null,
const { stringValue } = btnValue.structValue.fields.body buttons = [],
return { body: stringValue } answer = '',
}) } = res.payload.fields
const buttonsArray = buttons?.listValue?.values?.map(
(btnValue) => {
const { stringValue } = btnValue.structValue.fields.body
return { body: stringValue }
}
)
return { return {
answer: answer?.stringValue, answer: answer?.stringValue,
options: { options: {

View File

@@ -5,7 +5,8 @@ const DialogCXFlowClass = require('./dialogflow-cx.class')
* @param {*} args * @param {*} args
* @returns * @returns
*/ */
const createBotDialog = async ({ database, provider }, _options) => new DialogCXFlowClass(database, provider, _options) const createBotDialog = async ({ database, provider }, _options) =>
new DialogCXFlowClass(database, provider, _options)
module.exports = { module.exports = {
createBotDialog, createBotDialog,

View File

@@ -65,7 +65,10 @@ class DialogFlowContext extends CoreClass {
* para evitar este problema. * para evitar este problema.
* https://github.com/codigoencasa/bot-whatsapp/pull/140 * https://github.com/codigoencasa/bot-whatsapp/pull/140
*/ */
const session = this.sessionClient.projectAgentSessionPath(this.projectId, from) const session = this.sessionClient.projectAgentSessionPath(
this.projectId,
from
)
const reqDialog = { const reqDialog = {
session, session,
queryInput: { queryInput: {
@@ -76,11 +79,15 @@ class DialogFlowContext extends CoreClass {
}, },
} }
const [single] = (await this.sessionClient.detectIntent(reqDialog)) || [null] const [single] = (await this.sessionClient.detectIntent(reqDialog)) || [
null,
]
const { queryResult } = single const { queryResult } = single
const msgPayload = queryResult?.fulfillmentMessages?.find((a) => a.message === 'payload') const msgPayload = queryResult?.fulfillmentMessages?.find(
(a) => a.message === 'payload'
)
// Revisamos si el dialogFlow tiene multimedia // Revisamos si el dialogFlow tiene multimedia
if (msgPayload && msgPayload?.payload) { if (msgPayload && msgPayload?.payload) {

View File

@@ -5,7 +5,8 @@ const DialogFlowClass = require('./dialogflow.class')
* @param {*} args * @param {*} args
* @returns * @returns
*/ */
const createBotDialog = async ({ database, provider }) => new DialogFlowClass(database, provider) const createBotDialog = async ({ database, provider }) =>
new DialogFlowClass(database, provider)
module.exports = { module.exports = {
createBotDialog, createBotDialog,

View File

@@ -5,7 +5,8 @@ const MockClass = require('./mock.class')
* @param {*} args * @param {*} args
* @returns * @returns
*/ */
const createBotMock = async ({ database, provider }) => new MockClass(database, provider) const createBotMock = async ({ database, provider }) =>
new MockClass(database, provider)
module.exports = { module.exports = {
createBotMock, createBotMock,

View File

@@ -10,10 +10,7 @@ class MockDatabase {
constructor() {} constructor() {}
getPrevByNumber = (from) => { getPrevByNumber = (from) => {
const history = this.listHistory const history = this.listHistory.slice().reverse()
.slice()
.reverse()
.filter((i) => !!i.keyword)
return history.find((a) => a.from === from) return history.find((a) => a.from === from)
} }

View File

@@ -24,7 +24,12 @@ class MongoAdapter {
} }
getPrevByNumber = async (from) => { getPrevByNumber = async (from) => {
const result = await this.db.collection('history').find({ from }).sort({ _id: -1 }).limit(1).toArray() const result = await this.db
.collection('history')
.find({ from })
.sort({ _id: -1 })
.limit(1)
.toArray()
return result[0] return result[0]
} }

View File

@@ -46,8 +46,18 @@ class MyslAdapter {
}) })
save = (ctx) => { save = (ctx) => {
const values = [[ctx.ref, ctx.keyword, ctx.answer, ctx.refSerialize, ctx.from, JSON.stringify(ctx.options)]] const values = [
const sql = 'INSERT INTO history (ref, keyword, answer, refSerialize, phone, options ) values ?' [
ctx.ref,
ctx.keyword,
ctx.answer,
ctx.refSerialize,
ctx.from,
JSON.stringify(ctx.options),
],
]
const sql =
'INSERT INTO history (ref, keyword, answer, refSerialize, phone, options ) values ?'
this.db.query(sql, [values], (err) => { this.db.query(sql, [values], (err) => {
if (err) throw err if (err) throw err
@@ -61,14 +71,14 @@ class MyslAdapter {
const tableName = 'history' const tableName = 'history'
const sql = `CREATE TABLE ${tableName} const sql = `CREATE TABLE ${tableName}
(id INT AUTO_INCREMENT PRIMARY KEY, (id INT AUTO_INCREMENT PRIMARY KEY,
ref varchar(255) NOT NULL, ref varchar(255) NOT NULL,
keyword varchar(255) NOT NULL, keyword varchar(255) NOT NULL,
answer longtext NOT NULL, answer longtext NOT NULL,
refSerialize varchar(255) NOT NULL, refSerialize varchar(255) NOT NULL,
phone varchar(255) NOT NULL, phone varchar(255) NOT NULL,
options longtext NOT NULL) options longtext NOT NULL
CHARACTER SET utf8mb4 COLLATE utf8mb4_General_ci` )`
this.db.query(sql, (err) => { this.db.query(sql, (err) => {
if (err) throw err if (err) throw err

View File

@@ -8,5 +8,6 @@
font-family: IBMPlexMono-Regular; font-family: IBMPlexMono-Regular;
src: url(IBMPlexMono-Regular-subset.woff2) format('woff2'), src: url(IBMPlexMono-Regular-subset.woff2) format('woff2'),
url(IBMPlexMono-Regular-subset.zopfli.woff) format('woff'); url(IBMPlexMono-Regular-subset.zopfli.woff) format('woff');
unicode-range: U+20, U+2C, U+2E, U+41-43, U+46, U+49, U+4B-4F, U+53-55, U+58, U+61-65, U+67-69, U+6C-76; unicode-range: U+20, U+2C, U+2E, U+41-43, U+46, U+49, U+4B-4F, U+53-55, U+58,
U+61-65, U+67-69, U+6C-76;
} }

View File

@@ -8,6 +8,6 @@
font-family: IBMPlexMono-SemiBold; font-family: IBMPlexMono-SemiBold;
src: url(IBMPlexMono-SemiBold-subset.woff2) format('woff2'), src: url(IBMPlexMono-SemiBold-subset.woff2) format('woff2'),
url(IBMPlexMono-SemiBold-subset.zopfli.woff) format('woff'); url(IBMPlexMono-SemiBold-subset.zopfli.woff) format('woff');
unicode-range: U+20, U+24, U+2E, U+30, U+38, U+39, U+41, U+42, U+44, U+47, U+4E, U+4F, U+52-55, U+57, U+59, U+65, unicode-range: U+20, U+24, U+2E, U+30, U+38, U+39, U+41, U+42, U+44, U+47,
U+68, U+6F, U+72, U+74; U+4E, U+4F, U+52-55, U+57, U+59, U+65, U+68, U+6F, U+72, U+74;
} }

View File

@@ -6,8 +6,9 @@
@font-face { @font-face {
font-family: Pally-Variable; font-family: Pally-Variable;
src: url(Pally-Variable-subset.woff2) format('woff2'), url(Pally-Variable-subset.zopfli.woff) format('woff'); src: url(Pally-Variable-subset.woff2) format('woff2'),
unicode-range: U+20, U+24, U+2C, U+2E, U+30, U+33, U+39, U+41-43, U+46, U+49-4D, U+53, U+55, U+58, U+61-65, U+67-69, url(Pally-Variable-subset.zopfli.woff) format('woff');
U+6B-77, U+79; unicode-range: U+20, U+24, U+2C, U+2E, U+30, U+33, U+39, U+41-43, U+46,
U+49-4D, U+53, U+55, U+58, U+61-65, U+67-69, U+6B-77, U+79;
font-weight: 400 700; font-weight: 400 700;
} }

View File

@@ -8,5 +8,6 @@
font-family: SourceSerifPro-Regular; font-family: SourceSerifPro-Regular;
src: url(SourceSerifPro-Regular-subset.woff2) format('woff2'), src: url(SourceSerifPro-Regular-subset.woff2) format('woff2'),
url(SourceSerifPro-Regular-subset.zopfli.woff) format('woff'); url(SourceSerifPro-Regular-subset.zopfli.woff) format('woff');
unicode-range: U+20, U+2C, U+2E, U+41-44, U+49, U+4A, U+4C, U+53, U+55, U+61-65, U+67-69, U+6B-76, U+79; unicode-range: U+20, U+2C, U+2E, U+41-44, U+49, U+4A, U+4C, U+53, U+55,
U+61-65, U+67-69, U+6B-76, U+79;
} }

View File

@@ -6,8 +6,9 @@
@font-face { @font-face {
font-family: Synonym-Variable; font-family: Synonym-Variable;
src: url(Synonym-Variable-subset.woff2) format('woff2'), url(Synonym-Variable-subset.zopfli.woff) format('woff'); src: url(Synonym-Variable-subset.woff2) format('woff2'),
unicode-range: U+20, U+24, U+2C, U+2E, U+30, U+33, U+35, U+41-44, U+46, U+47, U+49, U+4B-4F, U+53-55, U+57-59, U+61, url(Synonym-Variable-subset.zopfli.woff) format('woff');
U+63-65, U+67-69, U+6C-76; unicode-range: U+20, U+24, U+2C, U+2E, U+30, U+33, U+35, U+41-44, U+46, U+47,
U+49, U+4B-4F, U+53-55, U+57-59, U+61, U+63-65, U+67-69, U+6C-76;
font-weight: 400 700; font-weight: 400 700;
} }

View File

@@ -6,7 +6,8 @@
@font-face { @font-face {
font-family: TenorSans-Regular; font-family: TenorSans-Regular;
src: url(TenorSans-Regular-subset.woff2) format('woff2'), url(TenorSans-Regular-subset.zopfli.woff) format('woff'); src: url(TenorSans-Regular-subset.woff2) format('woff2'),
unicode-range: U+20, U+24, U+2E, U+30, U+36, U+46, U+49, U+4A, U+53, U+54, U+61, U+63, U+65, U+69, U+6B, U+6E, U+6F, url(TenorSans-Regular-subset.zopfli.woff) format('woff');
U+72-75, U+79; unicode-range: U+20, U+24, U+2E, U+30, U+36, U+46, U+49, U+4A, U+53, U+54,
U+61, U+63, U+65, U+69, U+6B, U+6E, U+6F, U+72-75, U+79;
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

View File

@@ -1,189 +0,0 @@
export const DigitalOcean = () => (
<svg
version="1.1"
id="Layer_1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 604 129"
style="enable-background:new 0 0 604 129;"
xml:space="preserve"
>
<g>
<g>
<g>
<path
class="st0"
d="M174.3,3c4.9,0,8.7,2.9,8.7,8.6c0,5.6-3.8,8.5-8.7,8.5h-7.6v11.1h-3.5V3H174.3z M166.7,17.1h7.2
c3,0,5.6-1.8,5.6-5.5c0-3.8-2.5-5.5-5.6-5.5h-7.2V17.1z"
/>
<path
class="st0"
d="M208.8,21.7c0,6.1-4.3,10-9.9,10c-5.6,0-9.9-3.9-9.9-10c0-6.1,4.3-10,9.9-10
C204.5,11.7,208.8,15.6,208.8,21.7z M192.3,21.7c0,4.5,2.9,7.2,6.6,7.2c3.7,0,6.6-2.7,6.6-7.2c0-4.5-2.9-7.1-6.6-7.1
C195.2,14.5,192.3,17.2,192.3,21.7z"
/>
<path
class="st0"
d="M234.4,31.3l-5.2-13.8L224,31.3h-2.6L214.1,12h3.6l5.2,14l5.2-14h2.3l5.3,14l5.2-14h3.5L237,31.3H234.4z"
/>
<path
class="st0"
d="M253,22.9c0.2,3.7,2.6,5.9,6,5.9c2.8,0,4.8-1.3,5.4-3.4l3.2,0.2c-0.8,3.5-4.1,6.1-8.6,6.1
c-5.5,0-9.6-3.7-9.6-10c0-6.3,4-10,9.5-10c5.5,0,8.8,3.7,8.8,9.4v1.8H253z M253,20.3h11.6c-0.1-3.4-2-5.7-5.6-5.7
C255.6,14.5,253.2,16.5,253,20.3z"
/>
<path
class="st0"
d="M285.4,14.9c-3.4,0-5.6,2.3-5.6,5.3v11.1h-3.2V12h3.2v2.9c0.7-1.6,2.5-3.1,5.7-3.1V14.9z"
/>
<path
class="st0"
d="M294.7,22.9c0.2,3.7,2.6,5.9,6,5.9c2.8,0,4.8-1.3,5.4-3.4l3.2,0.2c-0.8,3.5-4.1,6.1-8.6,6.1
c-5.5,0-9.6-3.7-9.6-10c0-6.3,4-10,9.5-10c5.5,0,8.8,3.7,8.8,9.4v1.8H294.7z M294.7,20.3h11.6c-0.1-3.4-2-5.7-5.6-5.7
C297.4,14.5,294.9,16.5,294.7,20.3z"
/>
<path
class="st0"
d="M333.1,31.3v-3.1c-1.1,2-3.6,3.5-6.8,3.5c-5.3,0-9.3-3.8-9.3-10c0-6.2,4-10,9.3-10c3.2,0,5.6,1.4,6.6,3.2V2
h3.2v29.4H333.1z M320.3,21.7c0,4.6,2.8,7.2,6.5,7.2c3.6,0,6.2-2.2,6.2-6.6v-1.1c0-4.3-2.6-6.6-6.2-6.6
C323.1,14.5,320.3,17.1,320.3,21.7z"
/>
<path
class="st0"
d="M361.8,14.9c1.1-1.9,3.4-3.2,6.7-3.2c5.3,0,9.3,3.8,9.3,10c0,6.2-4,10-9.3,10c-3.3,0-5.7-1.5-6.8-3.5v3.1
h-3.1V2h3.2V14.9z M361.9,21.1v1.1c0,4.4,2.6,6.6,6.2,6.6c3.7,0,6.5-2.5,6.5-7.2c0-4.6-2.8-7.1-6.5-7.1
C364.5,14.5,361.9,16.8,361.9,21.1z"
/>
<path class="st0" d="M386.3,40.9l4.6-10.7L383.2,12h3.6l5.8,14.5l5.8-14.5h3.6l-12.2,28.9H386.3z" />
</g>
</g>
<g id="XMLID_2369_">
<g>
<g id="XMLID_281_">
<g id="XMLID_282_">
<g>
<g id="XMLID_283_">
<g id="XMLID_287_">
<path
id="XMLID_288_"
class="st0"
d="M64.4,127l0-24.2c25.6,0,45.5-25.4,35.7-52.3c-3.6-10-11.6-17.9-21.6-21.6
c-27-9.8-52.3,10-52.3,35.7c0,0,0,0,0,0L2,64.7C2,23.8,41.5-8,84.3,5.4c18.7,5.8,33.6,20.7,39.4,39.4
C137,87.6,105.2,127,64.4,127z"
/>
</g>
<polygon
id="XMLID_286_"
class="st1"
points="64.4,102.9 40.4,102.9 40.4,78.9 40.4,78.9 64.4,78.9 64.4,78.9 "
/>
<polygon
id="XMLID_285_"
class="st1"
points="40.3,121.5 21.8,121.5 21.8,121.5 21.8,102.9 40.4,102.9 40.4,121.5 "
/>
<path
id="XMLID_284_"
class="st1"
d="M21.9,102.9H6.3c0,0,0,0,0,0V87.4c0,0,0,0,0,0h15.5c0,0,0,0,0,0V102.9z"
/>
</g>
</g>
</g>
</g>
<g id="XMLID_254_">
<path
id="XMLID_278_"
class="st0"
d="M200.9,52.4c-5.5-3.8-12.4-5.8-20.5-5.8h-17.5v55.5h17.5c8,0,14.9-2.1,20.5-6.1
c3-2.1,5.4-5.1,7.1-8.9c1.7-3.7,2.5-8.2,2.5-13.1c0-4.9-0.8-9.3-2.5-13C206.3,57.4,203.9,54.4,200.9,52.4z M173.1,56h5.5
c6.1,0,11.1,1.2,15,3.6c4.2,2.6,6.4,7.4,6.4,14.4c0,7.2-2.2,12.3-6.4,15.1h0c-3.7,2.4-8.7,3.6-14.9,3.6h-5.6V56z"
/>
<path
id="XMLID_277_"
class="st0"
d="M222.6,45.9c-1.7,0-3.1,0.6-4.3,1.8c-1.2,1.1-1.8,2.6-1.8,4.2c0,1.7,0.6,3.1,1.8,4.3
c1.2,1.2,2.6,1.8,4.3,1.8c1.7,0,3.1-0.6,4.3-1.8c1.2-1.2,1.8-2.6,1.8-4.3c0-1.7-0.6-3.1-1.8-4.2
C225.7,46.5,224.3,45.9,222.6,45.9z"
/>
<rect id="XMLID_276_" x="217.6" y="63" class="st0" width="9.8" height="39.1" />
<path
id="XMLID_273_"
class="st0"
d="M263.2,66.3c-3-2.6-6.3-4.2-9.9-4.2c-5.4,0-9.9,1.9-13.4,5.6c-3.5,3.7-5.3,8.4-5.3,14.1
c0,5.5,1.8,10.2,5.2,14c3.5,3.7,8,5.5,13.5,5.5c3.8,0,7.1-1.1,9.7-3.1V99c0,3.2-0.9,5.8-2.6,7.5c-1.7,1.7-4.1,2.6-7.1,2.6
c-4.5,0-7.4-1.8-10.9-6.5l-6.7,6.4l0.2,0.3c1.4,2,3.7,4,6.6,5.9c2.9,1.9,6.6,2.8,10.9,2.8c5.8,0,10.6-1.8,14.1-5.4
c3.5-3.6,5.3-8.4,5.3-14.2V63h-9.7V66.3z M260.6,89.4c-1.7,2-3.9,2.9-6.8,2.9c-2.8,0-5-0.9-6.7-2.9c-1.7-1.9-2.5-4.5-2.5-7.7
c0-3.2,0.9-5.8,2.5-7.7c1.7-1.9,3.9-2.9,6.7-2.9c2.8,0,5,1,6.8,2.9c1.7,2,2.6,4.6,2.6,7.7C263.2,84.9,262.3,87.5,260.6,89.4z"
/>
<rect id="XMLID_272_" x="281.3" y="63" class="st0" width="9.8" height="39.1" />
<path
id="XMLID_271_"
class="st0"
d="M286.3,45.9c-1.7,0-3.1,0.6-4.3,1.8c-1.2,1.1-1.8,2.6-1.8,4.2c0,1.7,0.6,3.1,1.8,4.3
c1.2,1.2,2.6,1.8,4.3,1.8c1.7,0,3.1-0.6,4.3-1.8c1.2-1.2,1.8-2.6,1.8-4.3c0-1.7-0.6-3.1-1.8-4.2C289.4,46.5,288,45.9,286.3,45.9
z"
/>
<path
id="XMLID_270_"
class="st0"
d="M312.7,52.5H303V63h-5.6v9h5.6v16.2c0,5.1,1,8.7,3,10.8c2,2.1,5.6,3.2,10.6,3.2
c1.6,0,3.2-0.1,4.8-0.2l0.4,0v-9l-3.4,0.2c-2.3,0-3.9-0.4-4.7-1.2c-0.8-0.8-1.1-2.6-1.1-5.2V72h9.2v-9h-9.2V52.5z"
/>
<rect id="XMLID_269_" x="368" y="46.6" class="st0" width="9.8" height="55.5" />
<path
id="XMLID_268_"
class="st0"
d="M477.3,88.2c-1.8,2-3.6,3.7-4.9,4.6v0c-1.4,0.9-3.1,1.3-5.1,1.3c-2.9,0-5.2-1.1-7.1-3.2
c-1.9-2.2-2.8-4.9-2.8-8.3s0.9-6.1,2.8-8.2c1.9-2.2,4.2-3.2,7.1-3.2c3.2,0,6.5,2,9.4,5.4l6.5-6.2l0,0c-4.2-5.5-9.7-8.1-16.1-8.1
c-5.4,0-10.1,2-13.9,5.8c-3.8,3.9-5.7,8.8-5.7,14.6s1.9,10.7,5.7,14.6c3.8,3.9,8.5,5.9,13.9,5.9c7.1,0,12.9-3.1,16.8-8.7
L477.3,88.2z"
/>
<path
id="XMLID_265_"
class="st0"
d="M517.7,68.5c-1.4-1.9-3.3-3.5-5.7-4.7c-2.3-1.1-5.1-1.7-8.1-1.7c-5.5,0-10,2-13.4,6
c-3.3,4-4.9,8.9-4.9,14.7c0,5.9,1.8,10.8,5.4,14.6c3.6,3.7,8.4,5.6,14.2,5.6c6.6,0,12.1-2.7,16.2-8l0.2-0.3l-6.4-6.2l0,0
c-0.6,0.7-1.4,1.5-2.2,2.3c-1,0.9-1.9,1.6-2.9,2.1c-1.5,0.7-3.1,1.1-5,1.1c-2.7,0-5-0.8-6.7-2.4c-1.6-1.5-2.6-3.5-2.8-5.9h26.1
l0.1-3.6c0-2.5-0.3-5-1-7.3C520.1,72.6,519.1,70.4,517.7,68.5z M496.2,77.7c0.5-1.9,1.3-3.4,2.6-4.6c1.3-1.3,3.1-2,5.2-2
c2.4,0,4.2,0.7,5.5,2c1.2,1.2,1.8,2.8,2,4.6H496.2z"
/>
<path
id="XMLID_262_"
class="st0"
d="M555.5,66L555.5,66c-3-2.5-7.1-3.8-12.3-3.8c-3.3,0-6.3,0.7-9.1,2.1
c-2.6,1.3-5.1,3.5-6.7,6.3l0.1,0.1l6.3,6c2.6-4.1,5.5-5.6,9.3-5.6c2.1,0,3.8,0.6,5.1,1.6c1.3,1.1,1.9,2.5,1.9,4.2v1.9
c-2.4-0.7-4.9-1.1-7.2-1.1c-4.9,0-8.9,1.2-11.8,3.4c-3,2.3-4.5,5.6-4.5,9.8c0,3.7,1.3,6.7,3.8,8.9c2.6,2.1,5.8,3.2,9.5,3.2
c3.7,0,7.3-1.5,10.4-4.1v3.2h9.7V77C560,72.2,558.5,68.5,555.5,66z M538,87.2c1.1-0.8,2.7-1.2,4.7-1.2c2.4,0,4.9,0.5,7.5,1.4
v3.8c-2.1,2-5,3-8.5,3c-1.7,0-3-0.4-3.9-1.1c-0.9-0.7-1.3-1.7-1.3-2.8C536.4,89,536.9,88,538,87.2z"
/>
<path
id="XMLID_261_"
class="st0"
d="M597.9,66.7c-2.7-3.1-6.6-4.6-11.5-4.6c-3.9,0-7.1,1.1-9.4,3.3V63h-9.7v39.1h9.8V80.6
c0-3,0.7-5.3,2.1-7c1.4-1.7,3.3-2.5,5.8-2.5c2.2,0,3.9,0.7,5.2,2.2c1.3,1.5,1.9,3.6,1.9,6.2v22.7h9.8V79.5
C602,74.1,600.6,69.8,597.9,66.7z"
/>
<path
id="XMLID_258_"
class="st0"
d="M355.6,66L355.6,66c-3-2.5-7.1-3.8-12.3-3.8c-3.3,0-6.3,0.7-9.1,2.1
c-2.6,1.3-5.1,3.5-6.7,6.3l0.1,0.1l6.3,6c2.6-4.1,5.5-5.6,9.3-5.6c2.1,0,3.8,0.6,5.1,1.6c1.3,1.1,1.9,2.5,1.9,4.2v1.9
c-2.4-0.7-4.9-1.1-7.2-1.1c-4.9,0-8.9,1.2-11.8,3.4c-3,2.3-4.5,5.6-4.5,9.8c0,3.7,1.3,6.7,3.8,8.9c2.6,2.1,5.8,3.2,9.5,3.2
c3.7,0,7.3-1.5,10.4-4.1v3.2h9.7V77C360.2,72.2,358.7,68.5,355.6,66z M338.2,87.2c1.1-0.8,2.7-1.2,4.7-1.2
c2.4,0,4.9,0.5,7.5,1.4v3.8c-2.1,2-5,3-8.5,3c-1.7,0-3-0.4-3.9-1.1c-0.9-0.7-1.3-1.7-1.3-2.8C336.6,89,337.1,88,338.2,87.2z"
/>
<path
id="XMLID_255_"
class="st0"
d="M413.6,103c-15.8,0-28.6-12.8-28.6-28.6s12.8-28.6,28.6-28.6s28.6,12.8,28.6,28.6
S429.4,103,413.6,103z M413.6,55.8c-10.2,0-18.5,8.3-18.5,18.5s8.3,18.5,18.5,18.5s18.5-8.3,18.5-18.5S423.8,55.8,413.6,55.8z"
/>
</g>
</g>
</g>
</g>
</svg>
)

View File

@@ -5,7 +5,14 @@ import logoSrc from '~/assets/images/chatbot-whatsapp.png?width=64&height=64&png
export default component$(() => ( export default component$(() => (
<span class="self-center ml-2 text-2xl md:text-xl font-bold text-gray-900 whitespace-nowrap dark:text-white flex items-center"> <span class="self-center ml-2 text-2xl md:text-xl font-bold text-gray-900 whitespace-nowrap dark:text-white flex items-center">
<img src={logoSrc} class="inline-block mr-1" width={32} height={32} alt="Qwind Logo" loading="lazy" /> <img
src={logoSrc}
class="inline-block mr-1"
width={32}
height={32}
alt="Qwind Logo"
loading="lazy"
/>
Chatbot Chatbot
</span> </span>
)) ))

View File

@@ -1,5 +1,11 @@
export const Netlify = () => ( export const Netlify = () => (
<svg xmlns="http://www.w3.org/2000/svg" width={147} height={40} role="img" fill="currentColor"> <svg
xmlns="http://www.w3.org/2000/svg"
width={147}
height={40}
role="img"
fill="currentColor"
>
<g fill-rule="evenodd"> <g fill-rule="evenodd">
<path d="M53.37 12.978l.123 2.198c1.403-1.7 3.245-2.55 5.525-2.55 3.951 0 5.962 2.268 6.032 6.804v12.568H60.79V19.676c0-1.207-.26-2.1-.78-2.681-.52-.58-1.371-.87-2.552-.87-1.719 0-3 .78-3.84 2.338v13.535h-4.262v-19.02h4.016zM77.748 32.35c-2.7 0-4.89-.852-6.567-2.557-1.678-1.705-2.517-3.976-2.517-6.812v-.527c0-1.898.365-3.595 1.096-5.089.73-1.494 1.757-2.657 3.078-3.49 1.321-.831 2.794-1.247 4.42-1.247 2.583 0 4.58.826 5.988 2.478 1.41 1.653 2.114 3.99 2.114 7.014v1.723h-12.4c.13 1.57.652 2.812 1.57 3.726.918.914 2.073 1.371 3.464 1.371 1.952 0 3.542-.79 4.77-2.373l2.297 2.198c-.76 1.136-1.774 2.018-3.042 2.645-1.269.627-2.692.94-4.27.94zm-.508-16.294c-1.17 0-2.113.41-2.832 1.23-.72.82-1.178 1.963-1.377 3.428h8.12v-.317c-.094-1.43-.474-2.51-1.14-3.243-.667-.732-1.59-1.098-2.771-1.098zm16.765-7.7v4.623h3.35v3.164h-3.35V26.76c0 .726.144 1.25.43 1.573.286.322.798.483 1.535.483a6.55 6.55 0 0 0 1.49-.176v3.305c-.97.27-1.905.404-2.806.404-3.273 0-4.91-1.81-4.91-5.431V16.142H86.62v-3.164h3.122V8.355h4.261zm11.137 23.643h-4.262v-27h4.262v27zm9.172 0h-4.262v-19.02h4.262v19.02zm-4.525-23.96c0-.655.207-1.2.622-1.634.416-.433 1.009-.65 1.78-.65.772 0 1.368.217 1.79.65.42.434.63.979.63 1.635 0 .644-.21 1.18-.63 1.608-.422.428-1.018.642-1.79.642-.771 0-1.364-.214-1.78-.642-.415-.427-.622-.964-.622-1.608zm10.663 23.96V16.142h-2.894v-3.164h2.894v-1.74c0-2.11.584-3.738 1.753-4.887 1.17-1.148 2.806-1.722 4.91-1.722.749 0 1.544.105 2.386.316l-.105 3.34a8.375 8.375 0 0 0-1.631-.14c-2.035 0-3.052 1.048-3.052 3.146v1.687h3.858v3.164h-3.858v15.856h-4.261zm17.87-6.117l3.858-12.903h4.542l-7.54 21.903c-1.158 3.199-3.122 4.799-5.893 4.799-.62 0-1.304-.106-2.052-.317v-3.305l.807.053c1.075 0 1.885-.196 2.429-.589.543-.392.973-1.051 1.289-1.977l.613-1.635-6.664-18.932h4.595l4.016 12.903z" /> <path d="M53.37 12.978l.123 2.198c1.403-1.7 3.245-2.55 5.525-2.55 3.951 0 5.962 2.268 6.032 6.804v12.568H60.79V19.676c0-1.207-.26-2.1-.78-2.681-.52-.58-1.371-.87-2.552-.87-1.719 0-3 .78-3.84 2.338v13.535h-4.262v-19.02h4.016zM77.748 32.35c-2.7 0-4.89-.852-6.567-2.557-1.678-1.705-2.517-3.976-2.517-6.812v-.527c0-1.898.365-3.595 1.096-5.089.73-1.494 1.757-2.657 3.078-3.49 1.321-.831 2.794-1.247 4.42-1.247 2.583 0 4.58.826 5.988 2.478 1.41 1.653 2.114 3.99 2.114 7.014v1.723h-12.4c.13 1.57.652 2.812 1.57 3.726.918.914 2.073 1.371 3.464 1.371 1.952 0 3.542-.79 4.77-2.373l2.297 2.198c-.76 1.136-1.774 2.018-3.042 2.645-1.269.627-2.692.94-4.27.94zm-.508-16.294c-1.17 0-2.113.41-2.832 1.23-.72.82-1.178 1.963-1.377 3.428h8.12v-.317c-.094-1.43-.474-2.51-1.14-3.243-.667-.732-1.59-1.098-2.771-1.098zm16.765-7.7v4.623h3.35v3.164h-3.35V26.76c0 .726.144 1.25.43 1.573.286.322.798.483 1.535.483a6.55 6.55 0 0 0 1.49-.176v3.305c-.97.27-1.905.404-2.806.404-3.273 0-4.91-1.81-4.91-5.431V16.142H86.62v-3.164h3.122V8.355h4.261zm11.137 23.643h-4.262v-27h4.262v27zm9.172 0h-4.262v-19.02h4.262v19.02zm-4.525-23.96c0-.655.207-1.2.622-1.634.416-.433 1.009-.65 1.78-.65.772 0 1.368.217 1.79.65.42.434.63.979.63 1.635 0 .644-.21 1.18-.63 1.608-.422.428-1.018.642-1.79.642-.771 0-1.364-.214-1.78-.642-.415-.427-.622-.964-.622-1.608zm10.663 23.96V16.142h-2.894v-3.164h2.894v-1.74c0-2.11.584-3.738 1.753-4.887 1.17-1.148 2.806-1.722 4.91-1.722.749 0 1.544.105 2.386.316l-.105 3.34a8.375 8.375 0 0 0-1.631-.14c-2.035 0-3.052 1.048-3.052 3.146v1.687h3.858v3.164h-3.858v15.856h-4.261zm17.87-6.117l3.858-12.903h4.542l-7.54 21.903c-1.158 3.199-3.122 4.799-5.893 4.799-.62 0-1.304-.106-2.052-.317v-3.305l.807.053c1.075 0 1.885-.196 2.429-.589.543-.392.973-1.051 1.289-1.977l.613-1.635-6.664-18.932h4.595l4.016 12.903z" />
<path <path

View File

@@ -13,7 +13,10 @@ export const RouterHead = component$(() => {
<title>{head.title}</title> <title>{head.title}</title>
<link rel="canonical" href={loc.href} /> <link rel="canonical" href={loc.href} />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
{head.meta.map((m) => ( {head.meta.map((m) => (

View File

@@ -34,8 +34,14 @@ export const Social = () => {
content="https://campaign.codigoencasa.com" content="https://campaign.codigoencasa.com"
/> />
<meta property="og:site_name" content="campaign.codigoencasa.com" /> */} <meta property="og:site_name" content="campaign.codigoencasa.com" /> */}
<meta property="og:image" content="https://i.imgur.com/0HpzsEm.png"></meta> <meta
<meta property="og:image:secure_url" content="https://i.imgur.com/0HpzsEm.png" /> property="og:image"
content="https://i.imgur.com/0HpzsEm.png"
></meta>
<meta
property="og:image:secure_url"
content="https://i.imgur.com/0HpzsEm.png"
/>
<meta property="og:image:type" content="image/png"></meta> <meta property="og:image:type" content="image/png"></meta>
<meta property="og:image:width" content="1200"></meta> <meta property="og:image:width" content="1200"></meta>
<meta property="og:image:height" content="630"></meta> <meta property="og:image:height" content="630"></meta>
@@ -46,7 +52,10 @@ export const Social = () => {
name="twitter:title" name="twitter:title"
content="💻 Conviértete en un Programador Backend aprendiendo todo de Cloud y Nodejs" content="💻 Conviértete en un Programador Backend aprendiendo todo de Cloud y Nodejs"
/> />
<meta name="twitter:image" content="https://i.imgur.com/0HpzsEm.png" /> <meta
name="twitter:image"
content="https://i.imgur.com/0HpzsEm.png"
/>
</> </>
) )
} }

View File

@@ -27,7 +27,9 @@ export default component$((props: ItemProps) => {
// TODO: // TODO:
document.body.classList.toggle('overflow-hidden') document.body.classList.toggle('overflow-hidden')
document.getElementById('header')?.classList.toggle('h-screen') document.getElementById('header')?.classList.toggle('h-screen')
document.querySelector('#header nav')?.classList.toggle('hidden') document
.querySelector('#header nav')
?.classList.toggle('hidden')
}} }}
> >
<IconMenu class={iconClass} /> <IconMenu class={iconClass} />

View File

@@ -10,13 +10,16 @@ interface ItemProps {
export default component$((props: ItemProps) => { export default component$((props: ItemProps) => {
const { iconClass } = props const { iconClass } = props
const store = useStore({ const store = useStore({
theme: (typeof window !== 'undefined' && window?.localStorage?.theme) || undefined, theme:
(typeof window !== 'undefined' && window?.localStorage?.theme) ||
undefined,
}) })
useClientEffect$(() => { useClientEffect$(() => {
store.theme = store.theme =
window.localStorage.theme === 'dark' || window.localStorage.theme === 'dark' ||
(!('theme' in window.localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches) (!('theme' in window.localStorage) &&
window.matchMedia('(prefers-color-scheme: dark)').matches)
? 'dark' ? 'dark'
: 'light' : 'light'
}) })
@@ -39,7 +42,11 @@ export default component$((props: ItemProps) => {
} }
}} }}
> >
{store.theme == 'dark' ? <IconMoon class={iconClass} /> : <IconSun class={iconClass} />} {store.theme == 'dark' ? (
<IconMoon class={iconClass} />
) : (
<IconSun class={iconClass} />
)}
</button> </button>
) )
}) })

View File

@@ -7,7 +7,9 @@ export const IconArrowDownRight = (props: ItemProps) => {
return ( return (
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class={`icon icon-tabler icon-tabler-arrow-down-right ${className || 'w-5 h-5'}`} class={`icon icon-tabler icon-tabler-arrow-down-right ${
className || 'w-5 h-5'
}`}
width="24" width="24"
height="24" height="24"
viewBox="0 0 24 24" viewBox="0 0 24 24"

View File

@@ -8,7 +8,9 @@ export const IconMenu = (props: ItemProps) => {
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xlink="http://www.w3.org/1999/xlink"
class={`icon icon-tabler icon-tabler-menu ${className || 'w-5 h-5'}`} class={`icon icon-tabler icon-tabler-menu ${
className || 'w-5 h-5'
}`}
preserveAspectRatio="xMidYMid meet" preserveAspectRatio="xMidYMid meet"
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >

View File

@@ -7,7 +7,9 @@ export const IconMoon = (props: ItemProps) => {
return ( return (
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class={`icon icon-tabler icon-tabler-moon ${className || 'w-5 h-5'}`} class={`icon icon-tabler icon-tabler-moon ${
className || 'w-5 h-5'
}`}
width="24" width="24"
height="24" height="24"
viewBox="0 0 24 24" viewBox="0 0 24 24"

View File

@@ -23,7 +23,9 @@ export default component$(
<div class="pt-2 space-y-4 justify-center flex"> <div class="pt-2 space-y-4 justify-center flex">
<figcaption class="text-sm"> <figcaption class="text-sm">
<div class={'font-semibold truncate'}>{props.user.login}</div> <div class={'font-semibold truncate'}>
{props.user.login}
</div>
</figcaption> </figcaption>
</div> </div>
</figure> </figure>

View File

@@ -32,7 +32,8 @@ export default component$((props: { users: User[] }) => {
Super estrellas Super estrellas
</h2> </h2>
<p class="max-w-3xl mx-auto sm:text-center text-xl text-gray-600 dark:text-slate-400"> <p class="max-w-3xl mx-auto sm:text-center text-xl text-gray-600 dark:text-slate-400">
Todo es posible gracias a el mayor recursos de todos, el recurso humano. Tu tambien puedes{' '} Todo es posible gracias a el mayor recursos de todos, el
recurso humano. Tu tambien puedes{' '}
<a class={'font-semibold'} href="/docs/contributing"> <a class={'font-semibold'} href="/docs/contributing">
formar parte formar parte
</a> </a>

View File

@@ -15,8 +15,8 @@ export default component$(() => {
📄 Editar esta pagina 📄 Editar esta pagina
</a> </a>
<p class={'text-xs'}> <p class={'text-xs'}>
Forma parte de esta comunidad mejorando la documentación siente libre de poder agregar o editar Forma parte de esta comunidad mejorando la documentación
lo que quieras siente libre de poder agregar o editar lo que quieras
</p> </p>
</li> </li>
</ul> </ul>

View File

@@ -56,9 +56,13 @@ export default component$(() => {
<IconArrowDownRight class="w-7 h-7 text-primary-600 inline-block" /> <IconArrowDownRight class="w-7 h-7 text-primary-600 inline-block" />
{question} {question}
</div> </div>
{answer.split('\n\n').map((paragraph) => ( {answer
<p class="text-gray-700 dark:text-gray-400 mb-2">{paragraph}</p> .split('\n\n')
))} .map((paragraph) => (
<p class="text-gray-700 dark:text-gray-400 mb-2">
{paragraph}
</p>
))}
</div> </div>
))} ))}
</div> </div>

View File

@@ -50,11 +50,13 @@ export default component$(() => {
Caracteristicas Caracteristicas
</p> </p>
<h2 class="text-4xl md:text-5xl font-bold leading-tighter tracking-tighter mb-4 font-heading"> <h2 class="text-4xl md:text-5xl font-bold leading-tighter tracking-tighter mb-4 font-heading">
Nuestras principales <span class="whitespace-nowrap">funciones</span> Nuestras principales{' '}
<span class="whitespace-nowrap">funciones</span>
</h2> </h2>
<p class="max-w-3xl mx-auto sm:text-center text-xl text-gray-600 dark:text-slate-400"> <p class="max-w-3xl mx-auto sm:text-center text-xl text-gray-600 dark:text-slate-400">
El secreto es mantener los procesos repetitivos en procesos automatizados simples, por eso te El secreto es mantener los procesos repetitivos en
mostramos en que destacamos. procesos automatizados simples, por eso te mostramos en
que destacamos.
</p> </p>
</div> </div>
<div class="grid mx-auto space-y-6 md:grid-cols-2 md:space-y-0"> <div class="grid mx-auto space-y-6 md:grid-cols-2 md:space-y-0">
@@ -68,8 +70,12 @@ export default component$(() => {
</div> </div>
</div> </div>
<div> <div>
<h3 class="mb-3 text-xl font-bold">{title}</h3> <h3 class="mb-3 text-xl font-bold">
<p class="text-gray-600 dark:text-slate-400">{description}</p> {title}
</h3>
<p class="text-gray-600 dark:text-slate-400">
{description}
</p>
</div> </div>
</div> </div>
))} ))}

View File

@@ -9,24 +9,36 @@ import { src as placeholder } from '~/assets/images/chatbot-whatsapp.png?width=4
export default component$(() => { export default component$(() => {
return ( return (
<section class={` from-white via-purple-50 to-sky-100 dark:bg-none mt-[-95px]`}> <section
class={` from-white via-purple-50 to-sky-100 dark:bg-none mt-[-95px]`}
>
<div class="max-w-6xl mx-auto px-4 sm:px-6 md:flex md:h-screen 2xl:h-auto pt-[72px]"> <div class="max-w-6xl mx-auto px-4 sm:px-6 md:flex md:h-screen 2xl:h-auto pt-[72px]">
<div class="py-12 md:py-12 lg:py-16 block md:flex text-center md:text-left"> <div class="py-12 md:py-12 lg:py-16 block md:flex text-center md:text-left">
<div class="pb-12 md:pb-0 md:py-0 max-w-5xl mx-auto md:pr-16 flex items-center basis-[56%]"> <div class="pb-12 md:pb-0 md:py-0 max-w-5xl mx-auto md:pr-16 flex items-center basis-[56%]">
<div> <div>
<h1 class="text-5xl md:text-[3.48rem] font-bold leading-tighter tracking-tighter mb-4 font-heading px-4 md:px-0"> <h1 class="text-5xl md:text-[3.48rem] font-bold leading-tighter tracking-tighter mb-4 font-heading px-4 md:px-0">
Crear chatbot <span class="sm:whitespace-nowrap text-[#25b637]">WhatsApp</span> Crear chatbot{' '}
<br class="hidden lg:block" /> <span class="lg:inline">en minutos</span> <span class="sm:whitespace-nowrap text-[#25b637]">
WhatsApp
</span>
<br class="hidden lg:block" />{' '}
<span class="lg:inline">en minutos</span>
</h1> </h1>
<div class="max-w-3xl mx-auto"> <div class="max-w-3xl mx-auto">
<p class="text-xl text-gray-600 mb-8 dark:text-slate-400"> <p class="text-xl text-gray-600 mb-8 dark:text-slate-400">
<span class="font-semibold ">Con esta libreria, </span> <span class="font-semibold ">
Con esta libreria,{' '}
</span>
<span class="font-semibold "> <span class="font-semibold ">
puedes configurar respuestas automatizadas para preguntas frecuentes puedes configurar respuestas
automatizadas para preguntas frecuentes
</span>{' '} </span>{' '}
, recibir y responder mensajes de manera automatizada, y hacer un seguimiento de las , recibir y responder mensajes de manera
interacciones con los clientes. Además, nuestro Chatbot se integra fácilmente con automatizada, y hacer un seguimiento de las
otros sistemas y herramientas que ya esté utilizando en su negocio. interacciones con los clientes. Además,
nuestro Chatbot se integra fácilmente con
otros sistemas y herramientas que ya esté
utilizando en su negocio.
</p> </p>
<div class="max-w-xs sm:max-w-md flex flex-nowrap flex-col sm:flex-col gap-4 m-auto md:m-0 justify-center md:justify-start"> <div class="max-w-xs sm:max-w-md flex flex-nowrap flex-col sm:flex-col gap-4 m-auto md:m-0 justify-center md:justify-start">
@@ -36,7 +48,10 @@ export default component$(() => {
</code> </code>
</div> </div>
<div class="flex w-full sm:w-auto gap-3"> <div class="flex w-full sm:w-auto gap-3">
<a href="/docs" class="btn bg-gray-50 dark:bg-transparent"> <a
href="/docs"
class="btn bg-gray-50 dark:bg-transparent"
>
Ver documentación Ver documentación
</a> </a>
<a <a

View File

@@ -32,9 +32,14 @@ export default component$((props: { users: User[] }) => {
Miembros Miembros
</h2> </h2>
<p class="max-w-3xl mx-auto sm:text-center text-xl text-gray-600 dark:text-slate-400"> <p class="max-w-3xl mx-auto sm:text-center text-xl text-gray-600 dark:text-slate-400">
Conviértete en un miembro destacado y forma parte del proyecto y disfruta de manera adelantada Conviértete en un miembro destacado y forma parte del
de las actualizaciones{' '} proyecto y disfruta de manera adelantada de las
<a class={'font-semibold'} target={'_blank'} href="https://opencollective.com/bot-whatsapp"> actualizaciones{' '}
<a
class={'font-semibold'}
target={'_blank'}
href="https://opencollective.com/bot-whatsapp"
>
Únete Únete
</a> </a>
</p> </p>

View File

@@ -5,45 +5,55 @@ import { DocumentationCtx } from '~/contexts'
/** /**
* options = [] array con la lista de opciones de la documentacion * options = [] array con la lista de opciones de la documentacion
*/ */
export default component$(({ options = [] }: { options: DocumentationCtx[] }) => { export default component$(
return ( ({ options = [] }: { options: DocumentationCtx[] }) => {
<div> return (
{options.map((item, i) => ( <div>
<UlCompoent key={i} title={item.title} list={item.list} /> {options.map((item, i) => (
))} <UlCompoent key={i} title={item.title} list={item.list} />
</div> ))}
) </div>
}) )
}
)
export const UlCompoent = component$((porps: { title: string; list: { link: string; name: string }[] }) => { export const UlCompoent = component$(
return ( (porps: { title: string; list: { link: string; name: string }[] }) => {
<ul> return (
<li class="mt-2 lg:mt-2"> <ul>
<h5 class="mb-8 lg:mb-3 font-semibold text-slate-900 dark:text-slate-200">{porps.title}</h5> <li class="mt-2 lg:mt-2">
<LiComponent list={porps.list} /> <h5 class="mb-8 lg:mb-3 font-semibold text-slate-900 dark:text-slate-200">
</li> {porps.title}
</ul> </h5>
) <LiComponent list={porps.list} />
})
export const LiComponent = component$((porps: { list: { link: string; name: string }[] }) => {
const location = useLocation()
const currentPage = location.pathname
return (
<ul class="space-y-6 lg:space-y-2 border-l border-slate-100 dark:border-slate-800">
{porps.list.map((opt) => (
<li>
<Link
class={[
currentPage === `${opt.link}/` ? 'font-semibold' : '',
'block border-l pl-4 -ml-px border-transparent hover:border-slate-400 dark:hover:border-slate-500 text-slate-700 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-300 ',
]}
href={opt.link}
>
{opt.name}
</Link>
</li> </li>
))} </ul>
</ul> )
) }
}) )
export const LiComponent = component$(
(porps: { list: { link: string; name: string }[] }) => {
const location = useLocation()
const currentPage = location.pathname
return (
<ul class="space-y-6 lg:space-y-2 border-l border-slate-100 dark:border-slate-800">
{porps.list.map((opt) => (
<li>
<Link
class={[
currentPage === `${opt.link}/`
? 'font-semibold'
: '',
'block border-l pl-4 -ml-px border-transparent hover:border-slate-400 dark:hover:border-slate-500 text-slate-700 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-300 ',
]}
href={opt.link}
>
{opt.name}
</Link>
</li>
))}
</ul>
)
}
)

View File

@@ -1,63 +1,74 @@
import { component$ } from '@builder.io/qwik' import { component$ } from '@builder.io/qwik'
export const ButtonLink = component$((props: { name: string; link: string; direction: 'left' | 'right' }) => { export const ButtonLink = component$(
const ArrowRight = () => ( (props: { name: string; link: string; direction: 'left' | 'right' }) => {
<svg const ArrowRight = () => (
viewBox="0 0 3 6" <svg
class="ml-3 w-auto h-1.5 text-slate-400 overflow-visible group-hover:text-slate-600 dark:group-hover:text-slate-300" viewBox="0 0 3 6"
> class="ml-3 w-auto h-1.5 text-slate-400 overflow-visible group-hover:text-slate-600 dark:group-hover:text-slate-300"
<path >
d="M0 0L3 3L0 6" <path
fill="none" d="M0 0L3 3L0 6"
stroke="currentColor" fill="none"
stroke-width="2" stroke="currentColor"
stroke-linecap="round" stroke-width="2"
stroke-linejoin="round" stroke-linecap="round"
></path> stroke-linejoin="round"
</svg> ></path>
) </svg>
)
const ArrowLeft = () => ( const ArrowLeft = () => (
<svg <svg
viewBox="0 0 3 6" viewBox="0 0 3 6"
class="mr-3 w-auto h-1.5 text-slate-400 overflow-visible group-hover:text-slate-600 dark:group-hover:text-slate-300" class="mr-3 w-auto h-1.5 text-slate-400 overflow-visible group-hover:text-slate-600 dark:group-hover:text-slate-300"
> >
<path <path
d="M3 0L0 3L3 6" d="M3 0L0 3L3 6"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
stroke-width="2" stroke-width="2"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
></path> ></path>
</svg> </svg>
) )
return ( return (
<a class="group flex items-center hover:text-slate-900 dark:hover:text-white" href={props.link}> <a
{props.direction === 'left' ? ( class="group flex items-center hover:text-slate-900 dark:hover:text-white"
<> href={props.link}
<ArrowLeft /> >
{props.name} {props.direction === 'left' ? (
</> <>
) : ( <ArrowLeft />
<> {props.name}
{props.name} </>
<ArrowRight /> ) : (
</> <>
)} {props.name}
</a> <ArrowRight />
) </>
}) )}
</a>
)
}
)
export default component$((props: { pages: ({ name: string; link: string } | null)[] }) => { export default component$(
const { pages } = props (props: { pages: ({ name: string; link: string } | null)[] }) => {
return ( const { pages } = props
<div class="text-sm leading-6 mt-12"> return (
<div class="mb-10 text-slate-700 font-semibold flex justify-between items-center dark:text-slate-200"> <div class="text-sm leading-6 mt-12">
{pages[0] ? <ButtonLink direction="left" {...pages[0]} /> : null} <div class="mb-10 text-slate-700 font-semibold flex justify-between items-center dark:text-slate-200">
{pages[1] ? <ButtonLink direction="right" {...pages[1]} /> : null} {pages[0] ? (
<ButtonLink direction="left" {...pages[0]} />
) : null}
{pages[1] ? (
<ButtonLink direction="right" {...pages[1]} />
) : null}
</div>
</div> </div>
</div> )
) }
}) )

View File

@@ -7,8 +7,6 @@ import { src as qwik } from '~/assets/images/qwik.png?width=100&metadata'
import { src as leanga } from '~/assets/images/leanga.png?width=40&metadata' import { src as leanga } from '~/assets/images/leanga.png?width=40&metadata'
// @ts-ignore // @ts-ignore
import { src as netlify } from '~/assets/images/full-logo-light.png?width=100&metadata' import { src as netlify } from '~/assets/images/full-logo-light.png?width=100&metadata'
// @ts-ignore
import { src as digitalOcean } from '~/assets/images/digital-ocean.png?width=100&metadata'
/** /**
* options = [] array con la lista de opciones de la documentacion * options = [] array con la lista de opciones de la documentacion
@@ -43,20 +41,7 @@ export default component$(() => {
<img <img
src={netlify} src={netlify}
class="border border-slate-200 rounded my-2 p-1 bg-gray-50 dark:border-gray-600 dark:bg-gray-700" class="border border-slate-200 rounded my-2 p-1 bg-gray-50 dark:border-gray-600 dark:bg-gray-700"
alt="Netlify" alt="Qwind Hero Image (Cool dog)"
loading="eager"
decoding="async"
/>
</picture>
</a>
</li>
<li>
<a target={'_blank'} href="https://m.do.co/c/140291d21736">
<picture>
<img
src={digitalOcean}
class="border border-slate-200 rounded my-2 p-1 bg-gray-50 dark:border-gray-600 dark:bg-gray-700"
alt="DigitalOcean"
loading="eager" loading="eager"
decoding="async" decoding="async"
/> />

View File

@@ -5,25 +5,33 @@ export default component$(() => {
<div class="px-4 py-8 md:py-16 sm:px-6 mx-auto md:px-24 lg:px-8 lg:py-20 max-w-6xl"> <div class="px-4 py-8 md:py-16 sm:px-6 mx-auto md:px-24 lg:px-8 lg:py-20 max-w-6xl">
<div class="grid grid-cols-2 row-gap-8 md:grid-cols-4"> <div class="grid grid-cols-2 row-gap-8 md:grid-cols-4">
<div class="text-center md:border-r dark:md:border-slate-500 mb-10 md:mb-0"> <div class="text-center md:border-r dark:md:border-slate-500 mb-10 md:mb-0">
<div class="text-4xl font-bold lg:text-5xl xl:text-6xl text-[#039de1] font-heading">132K</div> <div class="text-4xl font-bold lg:text-5xl xl:text-6xl text-[#039de1] font-heading">
132K
</div>
<p class="text-sm font-medium tracking-widest text-gray-800 dark:text-slate-400 uppercase lg:text-base"> <p class="text-sm font-medium tracking-widest text-gray-800 dark:text-slate-400 uppercase lg:text-base">
Downloads Downloads
</p> </p>
</div> </div>
<div class="text-center md:border-r dark:md:border-slate-500 mb-10 md:mb-0"> <div class="text-center md:border-r dark:md:border-slate-500 mb-10 md:mb-0">
<div class="text-4xl font-bold lg:text-5xl xl:text-6xl text-[#039de1] font-heading">24.8K</div> <div class="text-4xl font-bold lg:text-5xl xl:text-6xl text-[#039de1] font-heading">
24.8K
</div>
<p class="text-sm font-medium tracking-widest text-gray-800 dark:text-slate-400 uppercase lg:text-base"> <p class="text-sm font-medium tracking-widest text-gray-800 dark:text-slate-400 uppercase lg:text-base">
Stars Stars
</p> </p>
</div> </div>
<div class="text-center md:border-r dark:md:border-slate-500 font-heading"> <div class="text-center md:border-r dark:md:border-slate-500 font-heading">
<div class="text-4xl font-bold lg:text-5xl xl:text-6xl text-[#039de1]">10.3K</div> <div class="text-4xl font-bold lg:text-5xl xl:text-6xl text-[#039de1]">
10.3K
</div>
<p class="text-sm font-medium tracking-widest text-gray-800 dark:text-slate-400 uppercase lg:text-base"> <p class="text-sm font-medium tracking-widest text-gray-800 dark:text-slate-400 uppercase lg:text-base">
Forks Forks
</p> </p>
</div> </div>
<div class="text-center"> <div class="text-center">
<div class="text-4xl font-bold lg:text-5xl xl:text-6xl text-[#039de1] font-heading">48.4K</div> <div class="text-4xl font-bold lg:text-5xl xl:text-6xl text-[#039de1] font-heading">
48.4K
</div>
<p class="text-sm font-medium tracking-widest text-gray-800 dark:text-slate-400 uppercase lg:text-base"> <p class="text-sm font-medium tracking-widest text-gray-800 dark:text-slate-400 uppercase lg:text-base">
Users Users
</p> </p>

View File

@@ -13,4 +13,5 @@ export interface User {
avatar_url: string avatar_url: string
} }
export const GlobalStore = createContext<DocumentationCtx[]>('documentation-site') export const GlobalStore =
createContext<DocumentationCtx[]>('documentation-site')

View File

@@ -1,5 +1,14 @@
import { component$, useContextProvider, useStore, useStyles$ } from '@builder.io/qwik' import {
import { QwikCityProvider, RouterOutlet, ServiceWorkerRegister } from '@builder.io/qwik-city' component$,
useContextProvider,
useStore,
useStyles$,
} from '@builder.io/qwik'
import {
QwikCityProvider,
RouterOutlet,
ServiceWorkerRegister,
} from '@builder.io/qwik-city'
import { RouterHead } from '~/components/core/RouterHead' import { RouterHead } from '~/components/core/RouterHead'
import { DarkThemeLauncher } from '~/components/core/DarkThemeLauncher' import { DarkThemeLauncher } from '~/components/core/DarkThemeLauncher'
@@ -69,7 +78,10 @@ export default component$(() => {
<QwikCityProvider> <QwikCityProvider>
<head> <head>
<meta charSet="utf-8" /> <meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta
name="viewport"
content="width=device-width, initial-scale=1"
/>
<link rel="manifest" href="/manifest.json" /> <link rel="manifest" href="/manifest.json" />
<RouterHead /> <RouterHead />

View File

@@ -4,10 +4,11 @@ import Navigation from '../../../components/widgets/Navigation'
# DataBase (Base de datos) # DataBase (Base de datos)
<Alert> <Alert>
⚡ Dependiendo del tipo de conector que utlices puede que necesites pasar algunas configuracion adicional como ⚡ Dependiendo del tipo de conector que utlices puede que necesites pasar
**user, host, password** para esos casos te recomendamos guiarte de los algunas configuracion adicional como **user, host, password** para esos
**[starters](https://github.com/codigoencasa/bot-whatsapp/tree/dev/starters/apps)** o si gustas puedes editar esta casos te recomendamos guiarte de los
documentación para ir agregando más info **[starters](https://github.com/codigoencasa/bot-whatsapp/tree/dev/starters/apps)**
o si gustas puedes editar esta documentación para ir agregando más info
</Alert> </Alert>
Es la pieza encargada de mantener el **"estado"** de una conversación, para mayor facilidad la libreria te proporcia diferentes conectores que se de adapten mejor a tu desarrollo Es la pieza encargada de mantener el **"estado"** de una conversación, para mayor facilidad la libreria te proporcia diferentes conectores que se de adapten mejor a tu desarrollo

View File

@@ -22,7 +22,12 @@ Tan sencillo como decir **palabra/s clave** y **mensaje a responder**
Ambos metodos **[addKeyword](https://github.com/codigoencasa/bot-whatsapp/blob/dev/packages/bot/io/methods/addKeyword.js)** y el **[addAnswer](https://github.com/codigoencasa/bot-whatsapp/blob/dev/packages/bot/io/methods/addAnswer.js)** tienen una serie opciones disponibles Ambos metodos **[addKeyword](https://github.com/codigoencasa/bot-whatsapp/blob/dev/packages/bot/io/methods/addKeyword.js)** y el **[addAnswer](https://github.com/codigoencasa/bot-whatsapp/blob/dev/packages/bot/io/methods/addAnswer.js)** tienen una serie opciones disponibles
```js ```js
const { createBot, createProvider, createFlow, addKeyword } = require('@bot-whatsapp/bot') const {
createBot,
createProvider,
createFlow,
addKeyword,
} = require('@bot-whatsapp/bot')
const flowPrincipal = addKeyword(['hola', 'alo']) const flowPrincipal = addKeyword(['hola', 'alo'])
.addAnswer(['Hola, bienvenido a mi tienda', '¿Como puedo ayudarte?']) .addAnswer(['Hola, bienvenido a mi tienda', '¿Como puedo ayudarte?'])
@@ -34,10 +39,11 @@ const flowPrincipal = addKeyword(['hola', 'alo'])
## Provider (Proveedor) ## Provider (Proveedor)
<Alert> <Alert>
⚡ Dependiendo del tipo de proveedor que utlices puede que necesites pasar algunas configuracion adicional como ⚡ Dependiendo del tipo de proveedor que utlices puede que necesites pasar
**token, api, etc.** para esos casos te recomendamos guiarte de los algunas configuracion adicional como **token, api, etc.** para esos casos te
**[starters](https://github.com/codigoencasa/bot-whatsapp/tree/dev/starters/apps)** o si gustas puedes editar esta recomendamos guiarte de los
documentación para ir agregando más info **[starters](https://github.com/codigoencasa/bot-whatsapp/tree/dev/starters/apps)**
o si gustas puedes editar esta documentación para ir agregando más info
</Alert> </Alert>
Es la pieza que conectara tu flujo con Whatsapp. En este chatbot tenemos varios proveedores disponibles la mayoria gratis pero tambien tenemos integracion la api oficial de whatsapp o twilio Es la pieza que conectara tu flujo con Whatsapp. En este chatbot tenemos varios proveedores disponibles la mayoria gratis pero tambien tenemos integracion la api oficial de whatsapp o twilio
@@ -65,10 +71,11 @@ Los proveedores disponibles hasta el momento son los siguientes:
## DataBase (Base de datos) ## DataBase (Base de datos)
<Alert> <Alert>
⚡ Dependiendo del tipo de conector que utlices puede que necesites pasar algunas configuracion adicional como ⚡ Dependiendo del tipo de conector que utlices puede que necesites pasar
**user, host, password** para esos casos te recomendamos guiarte de los algunas configuracion adicional como **user, host, password** para esos
**[starters](https://github.com/codigoencasa/bot-whatsapp/tree/dev/starters/apps)** o si gustas puedes editar esta casos te recomendamos guiarte de los
documentación para ir agregando más info **[starters](https://github.com/codigoencasa/bot-whatsapp/tree/dev/starters/apps)**
o si gustas puedes editar esta documentación para ir agregando más info
</Alert> </Alert>
Es la pieza encargada de mantener el **"estado"** de una conversación, para mayor facilidad la libreria te proporcia diferentes conectores que se de adapten mejor a tu desarrollo Es la pieza encargada de mantener el **"estado"** de una conversación, para mayor facilidad la libreria te proporcia diferentes conectores que se de adapten mejor a tu desarrollo

View File

@@ -5,7 +5,12 @@ import Navigation from '../../../components/widgets/Navigation'
Si copias y pegas este codigo y tu entorno de trabajo cumple con todos los requesitos te debe funcionar abajo explico muy por encima Si copias y pegas este codigo y tu entorno de trabajo cumple con todos los requesitos te debe funcionar abajo explico muy por encima
```js ```js
const { createBot, createProvider, createFlow, addKeyword } = require('@bot-whatsapp/bot') const {
createBot,
createProvider,
createFlow,
addKeyword,
} = require('@bot-whatsapp/bot')
const WebWhatsappProvider = require('@bot-whatsapp/provider/web-whatsapp') const WebWhatsappProvider = require('@bot-whatsapp/provider/web-whatsapp')
const MockAdapter = require('@bot-whatsapp/database/mock') const MockAdapter = require('@bot-whatsapp/database/mock')
@@ -37,7 +42,12 @@ main()
En esta parte solo estamos declaramos las dependencias que vamos a utilizar. Si quieres saber a fondo cada una de las funciones te recomiendo pasarte por la seccion de **[conceptos](/docs/concepts)** En esta parte solo estamos declaramos las dependencias que vamos a utilizar. Si quieres saber a fondo cada una de las funciones te recomiendo pasarte por la seccion de **[conceptos](/docs/concepts)**
```js ```js
const { createBot, createProvider, createFlow, addKeyword } = require('@bot-whatsapp/bot') const {
createBot,
createProvider,
createFlow,
addKeyword,
} = require('@bot-whatsapp/bot')
const WebWhatsappProvider = require('@bot-whatsapp/provider/web-whatsapp') const WebWhatsappProvider = require('@bot-whatsapp/provider/web-whatsapp')
const MockAdapter = require('@bot-whatsapp/database/mock') const MockAdapter = require('@bot-whatsapp/database/mock')

View File

@@ -9,7 +9,12 @@ Tan sencillo como decir **palabra/s clave** y **mensaje a responder**
Ambos metodos **[addKeyword](https://github.com/codigoencasa/bot-whatsapp/blob/dev/packages/bot/io/methods/addKeyword.js)** y el **[addAnswer](https://github.com/codigoencasa/bot-whatsapp/blob/dev/packages/bot/io/methods/addAnswer.js)** tienen una serie opciones disponibles Ambos metodos **[addKeyword](https://github.com/codigoencasa/bot-whatsapp/blob/dev/packages/bot/io/methods/addKeyword.js)** y el **[addAnswer](https://github.com/codigoencasa/bot-whatsapp/blob/dev/packages/bot/io/methods/addAnswer.js)** tienen una serie opciones disponibles
```js ```js
const { createBot, createProvider, createFlow, addKeyword } = require('@bot-whatsapp/bot') const {
createBot,
createProvider,
createFlow,
addKeyword,
} = require('@bot-whatsapp/bot')
const flowPrincipal = addKeyword(['hola', 'alo']) const flowPrincipal = addKeyword(['hola', 'alo'])
.addAnswer(['Hola, bienvenido a mi tienda', '¿Como puedo ayudarte?']) .addAnswer(['Hola, bienvenido a mi tienda', '¿Como puedo ayudarte?'])
@@ -24,16 +29,13 @@ const flowPrincipal = addKeyword(['hola', 'alo'])
Es importante que el número **vaya acompañado de su prefijo**, en el caso de España "34". Es importante que el número **vaya acompañado de su prefijo**, en el caso de España "34".
```js ```js
createBot( createBot({
{
flow: adapterFlow, flow: adapterFlow,
provider: adapterProvider, provider: adapterProvider,
database: adapterDB, database: adapterDB,
}, },{
{ blackList:['34XXXXXXXXX','34XXXXXXXXX','34XXXXXXXXX','34XXXXXXXXX']
blackList: ['34XXXXXXXXX', '34XXXXXXXXX', '34XXXXXXXXX', '34XXXXXXXXX'], })
}
)
``` ```
--- ---
@@ -76,21 +78,37 @@ Esta funcion se utliza para responder un mensaje despues del `addKeyword()`
```js ```js
const { addKeyword } = require('@bot-whatsapp/bot') const { addKeyword } = require('@bot-whatsapp/bot')
const flowString = addKeyword('hola').addAnswer('Este mensaje se enviara 1 segundo despues', { const flowString = addKeyword('hola').addAnswer(
delay: 1000, 'Este mensaje se enviara 1 segundo despues',
}) {
delay: 1000,
}
)
const flowString = addKeyword('hola').addAnswer('Este mensaje envia una imagen', { const flowString = addKeyword('hola').addAnswer(
media: 'https://i.imgur.com/0HpzsEm.png', 'Este mensaje envia una imagen',
}) {
media: 'https://i.imgur.com/0HpzsEm.png',
}
)
const flowString = addKeyword('hola').addAnswer('Este mensaje envia tres botones', { const flowString = addKeyword('hola').addAnswer(
buttons: [{ body: 'Boton 1' }, { body: 'Boton 2' }, { body: 'Boton 3' }], 'Este mensaje envia tres botones',
}) {
buttons: [
{ body: 'Boton 1' },
{ body: 'Boton 2' },
{ body: 'Boton 3' },
],
}
)
const flowString = addKeyword('hola').addAnswer('Este mensaje espera una respueta del usuario', { const flowString = addKeyword('hola').addAnswer(
capture: true, 'Este mensaje espera una respueta del usuario',
}) {
capture: true,
}
)
``` ```
--- ---
@@ -102,9 +120,13 @@ Este argumento se utiliza para obtener el contexto de la conversación
```js ```js
const { addKeyword } = require('@bot-whatsapp/bot') const { addKeyword } = require('@bot-whatsapp/bot')
const flowString = addKeyword('hola').addAnswer('Indica cual es tu email', null, (ctx) => { const flowString = addKeyword('hola').addAnswer(
console.log('👉 Informacion del contexto: ', ctx) 'Indica cual es tu email',
}) null,
(ctx) => {
console.log('👉 Informacion del contexto: ', ctx)
}
)
``` ```
--- ---
@@ -118,9 +140,13 @@ se repetira el mensaje `Indica cual es tu email`
```js ```js
const { addKeyword } = require('@bot-whatsapp/bot') const { addKeyword } = require('@bot-whatsapp/bot')
const flowString = addKeyword('hola').addAnswer('Indica cual es tu email', null, (ctx, { fallBack }) => { const flowString = addKeyword('hola').addAnswer(
if (!ctx.body.includes('@')) return fallBack() 'Indica cual es tu email',
}) null,
(ctx, { fallBack }) => {
if (!ctx.body.includes('@')) return fallBack()
}
)
``` ```
--- ---
@@ -149,75 +175,65 @@ const flowString = addKeyword('hola')
``` ```
--- ---
## endFlow() ## 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 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. 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. Como podrás comprobar en el ejemplo siguiente, se puede vincular flowDynamic y todas sus funciones; como por ejemplo botones.
```js ```js
const flowFormulario = addKeyword(['Hola']) const flowFormulario = addKeyword(['Hola'])
.addAnswer(
['Hola!', 'Escriba su *Nombre* para generar su solicitud'], .addAnswer(['Hola!','Escriba su *Nombre* para generar su solicitud'],
{ capture: true, buttons: [{ body: '❌ Cancelar solicitud' }] }, {capture: true,buttons:[{body:'❌ Cancelar solicitud'}]},
async (ctx, { flowDynamic, endFlow }) => { async (ctx,{flowDynamic, endFlow})=>{
if (ctx.body == '❌ Cancelar solicitud') { if(ctx.body == '❌ Cancelar solicitud'){
await flowDynamic([ await flowDynamic([{body: "❌ *Su solicitud de cita ha sido cancelada* ❌", buttons:[{body:'⬅️ Volver al Inicio'}]}])
{ return endFlow()
body: '❌ *Su solicitud de cita ha sido cancelada* ❌', }
buttons: [{ body: '⬅️ Volver al Inicio' }], })
}, .addAnswer(['También necesito tus dos apellidos'],
]) {capture: true,buttons:[{body:'❌ Cancelar solicitud'}]},
return endFlow() 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()
.addAnswer( }
['También necesito tus dos apellidos'], })
{ capture: true, buttons: [{ body: '❌ Cancelar solicitud' }] }, .addAnswer(['Dejeme su número de teléfono y le llamaré lo antes posible.'],
async (ctx, { flowDynamic, endFlow }) => { {capture: true,buttons:[{body:'❌ Cancelar solicitud'}]},
if (ctx.body == '❌ Cancelar solicitud') { async (ctx,{flowDynamic, endFlow})=>{
await flowDynamic([ if(ctx.body == '❌ Cancelar solicitud'){
{ await flowDynamic([{body: "❌ *Su solicitud de cita ha sido cancelada* ❌", buttons:[{body:'⬅️ Volver al Inicio'}]}])
body: '❌ *Su solicitud de cita ha sido cancelada* ❌', return endFlow()
buttons: [{ body: '⬅️ Volver al Inicio' }], }
}, })
])
return endFlow()
}
}
)
.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()
}
}
)
``` ```
--- ---
# QRPortalWeb # QRPortalWeb
Argumento para asignar nombre y puerto al BOT Argumento para asignar nombre y puerto al BOT
```js ```js
const BOTNAME = 'bot' QRPortalWeb({name:BOTNAME, port:3005 });
QRPortalWeb({ name: BOTNAME, port: 3005 })
``` ```
--- ---
<Navigation <Navigation
pages={[ pages={[
{ name: 'Conceptos', link: '/docs/essential' }, { name: 'Conceptos', link: '/docs/essential' },

View File

@@ -4,9 +4,10 @@ import Navigation from '../../components/widgets/Navigation'
# Introducción # Introducción
<Alert> <Alert>
**Atención** estás leyendo la documentación de la **versión v2** de esta librería, si vienes de la versión anterior **Atención** estás leyendo la documentación de la **versión v2** de esta
te recomendamos pasarte por la sección de **[migración](/docs/migration/)** para que puedas disfrutar de las nuevas librería, si vienes de la versión anterior te recomendamos pasarte por la
características. sección de **[migración](/docs/migration/)** para que puedas disfrutar de
las nuevas características.
</Alert> </Alert>
## ¿Qué es esto? ## ¿Qué es esto?
@@ -33,7 +34,10 @@ npm create bot-whatsapp@latest
muted muted
playsinline playsinline
> >
<source src="https://leifer-landing-page.s3.us-east-2.amazonaws.com/console.webm" type="video/webm" /> <source
src="https://leifer-landing-page.s3.us-east-2.amazonaws.com/console.webm"
type="video/webm"
/>
</video> </video>
</div> </div>

View File

@@ -28,7 +28,10 @@ El **CLI** te hace una revisión previa, de versión de Node y sistema operativo
muted muted
playsinline playsinline
> >
<source src="https://leifer-landing-page.s3.us-east-2.amazonaws.com/console.webm" type="video/webm" /> <source
src="https://leifer-landing-page.s3.us-east-2.amazonaws.com/console.webm"
type="video/webm"
/>
</video> </video>
</div> </div>
@@ -51,8 +54,10 @@ Cada plantilla tiene sus dependencias necesarias basadas en tu previa selección
``` ```
<Alert> <Alert>
📄 Si deseas cambiar tu **proveedor o tu motor** de base de datos no es necesario volver ejecutar el CLI (lo puedes 📄 Si deseas cambiar tu **proveedor o tu motor** de base de datos no es
hacer sin problema) aunque tambien basta con solo modificar un par de lineas. [Ver explicación](/docs/essential) necesario volver ejecutar el CLI (lo puedes hacer sin problema) aunque
tambien basta con solo modificar un par de lineas. [Ver
explicación](/docs/essential)
</Alert> </Alert>
--- ---

View File

@@ -3,7 +3,7 @@ import type { DocumentHead } from '@builder.io/qwik-city'
import ExtraBar from '~/components/widgets/ExtraBar' import ExtraBar from '~/components/widgets/ExtraBar'
import Header from '~/components/widgets/Header' import Header from '~/components/widgets/Header'
import NavBar from '~/components/widgets/NavBar' import NavBar from '~/components/widgets/NavBar'
// import { SearchModal } from '~/components/widgets/SearchModal' import { SearchModal } from '~/components/widgets/SearchModal'
import SponsorBar from '~/components/widgets/SponsorBar' import SponsorBar from '~/components/widgets/SponsorBar'
import { GlobalStore } from '~/contexts' import { GlobalStore } from '~/contexts'
// import Navigation from '~/components/widgets/Navigation' // import Navigation from '~/components/widgets/Navigation'
@@ -15,7 +15,7 @@ export default component$(() => {
return ( return (
<> <>
{/* <SearchModal /> */} <SearchModal />
<Header /> <Header />
<main class={'overflow-hidden'}> <main class={'overflow-hidden'}>
<div class={'max-w-8xl'}> <div class={'max-w-8xl'}>
@@ -27,7 +27,11 @@ export default component$(() => {
<NavBar options={store} /> <NavBar options={store} />
</div> </div>
<div class={'lg:pl-[14.5rem] lg:pr-[14.5rem]'}> <div class={'lg:pl-[14.5rem] lg:pr-[14.5rem]'}>
<div class={'slot max-w-3xl mx-auto relative z-20 p-5 xl:max-w-none'}> <div
class={
'slot max-w-3xl mx-auto relative z-20 p-5 xl:max-w-none'
}
>
<Slot /> <Slot />
</div> </div>
</div> </div>
@@ -50,7 +54,8 @@ export const head: DocumentHead = {
meta: [ meta: [
{ {
name: 'description', name: 'description',
content: 'Crear chatbot WhatsApp en minutos — Servicio de chatbot para whatspp gratis proyecto OpenSource', content:
'Crear chatbot WhatsApp en minutos — Servicio de chatbot para whatspp gratis proyecto OpenSource',
}, },
], ],
} }

View File

@@ -49,7 +49,11 @@ En la **_versión (legacy)_** se implementas los flujos de esta manera, en dos a
"title": "¿Que te interesa ver?", "title": "¿Que te interesa ver?",
"message": "Abajo unos botons", "message": "Abajo unos botons",
"footer": "", "footer": "",
"buttons": [{ "body": "Telefonos" }, { "body": "Computadoras" }, { "body": "Otros" }] "buttons": [
{ "body": "Telefonos" },
{ "body": "Computadoras" },
{ "body": "Otros" }
]
} }
}, },
"catalogo": { "catalogo": {
@@ -66,28 +70,51 @@ En esta versión es mucho más sencillo, abajo encontrarás un ejemplo del mismo
```js ```js
//app.js //app.js
const { createBot, createProvider, createFlow, addKeyword, addChild } = require('@bot-whatsapp/bot') const {
createBot,
createProvider,
createFlow,
addKeyword,
addChild,
} = require('@bot-whatsapp/bot')
const BaileysProvider = require('@bot-whatsapp/provider/baileys') //Provider const BaileysProvider = require('@bot-whatsapp/provider/baileys') //Provider
const MockAdapter = require('@bot-whatsapp/database/mock') //Base de datos const MockAdapter = require('@bot-whatsapp/database/mock') //Base de datos
/** /**
* Declarando flujos principales. * Declarando flujos principales.
*/ */
const flowHola = addKeyword(['hola', 'ola', 'alo']).addAnswer('Bienvenido a tu tienda online!') const flowHola = addKeyword(['hola', 'ola', 'alo']).addAnswer(
'Bienvenido a tu tienda online!'
)
const flowAdios = addKeyword(['adios', 'bye']).addAnswer('Que te vaya bien!!').addAnswer('Hasta luego!') const flowAdios = addKeyword(['adios', 'bye'])
.addAnswer('Que te vaya bien!!')
.addAnswer('Hasta luego!')
const flowProductos = addKeyword(['productos', 'info']).addAnswer('Te envio una imagen', { const flowProductos = addKeyword(['productos', 'info']).addAnswer(
buttons: [{ body: 'Telefonos' }, { body: 'Computadoras' }, { body: 'Otros' }], 'Te envio una imagen',
}) {
buttons: [
{ body: 'Telefonos' },
{ body: 'Computadoras' },
{ body: 'Otros' },
],
}
)
const flowCatalogo = addKeyword(['imagen', 'foto']).addAnswer('Te envio una imagen', { const flowCatalogo = addKeyword(['imagen', 'foto']).addAnswer(
media: 'https://media2.giphy.com/media/VQJu0IeULuAmCwf5SL/giphy.gif', 'Te envio una imagen',
}) { media: 'https://media2.giphy.com/media/VQJu0IeULuAmCwf5SL/giphy.gif' }
)
const main = async () => { const main = async () => {
const adapterDB = new MockAdapter() const adapterDB = new MockAdapter()
const adapterFlow = createFlow([flowHola, flowAdios, flowProductos, flowCatalogo]) //Se crean los flujos. const adapterFlow = createFlow([
flowHola,
flowAdios,
flowProductos,
flowCatalogo,
]) //Se crean los flujos.
const adapterProvider = createProvider(BaileysProvider) const adapterProvider = createProvider(BaileysProvider)
createBot({ createBot({
flow: adapterFlow, flow: adapterFlow,

View File

@@ -49,15 +49,17 @@ Qwik is a new kind of web framework that can deliver instant loading web applica
<div class="card"> <div class="card">
<h3>Instant-on</h3> <h3>Instant-on</h3>
<p> <p>
Unlike other frameworks, Qwik is resumable which means Qwik applications require 0 hydration. This allows Unlike other frameworks, Qwik is resumable which means Qwik
Qwik apps to have instant-on interactivity, regardless of size or complexity applications require 0 hydration. This allows Qwik apps to have
instant-on interactivity, regardless of size or complexity
</p> </p>
</div> </div>
<div class="card"> <div class="card">
<h3>Optimized for speed</h3> <h3>Optimized for speed</h3>
<p> <p>
Qwik has unprecedented performance, offering sub-second full page loads even on mobile devices. Qwik Qwik has unprecedented performance, offering sub-second full page
achieves this by delivering pure HTML, and incrementally loading JS only as-needed. loads even on mobile devices. Qwik achieves this by delivering pure
HTML, and incrementally loading JS only as-needed.
</p> </p>
</div> </div>
</div> </div>

View File

@@ -4,10 +4,11 @@ import Navigation from '../../../components/widgets/Navigation'
# Proveedores # Proveedores
<Alert> <Alert>
⚡ Dependiendo del tipo de proveedor que utlices puede que necesites pasar algunas configuracion adicional como ⚡ Dependiendo del tipo de proveedor que utlices puede que necesites pasar
**token, api, etc.** para esos casos te recomendamos guiarte de los algunas configuracion adicional como **token, api, etc.** para esos casos te
**[starters](https://github.com/codigoencasa/bot-whatsapp/tree/dev/starters/apps)** o si gustas puedes editar esta recomendamos guiarte de los
documentación para ir agregando más info **[starters](https://github.com/codigoencasa/bot-whatsapp/tree/dev/starters/apps)**
o si gustas puedes editar esta documentación para ir agregando más info
</Alert> </Alert>
Es la pieza que conectara tu flujo con Whatsapp. En este chatbot tenemos varios proveedores disponibles la mayoria gratis pero tambien tenemos integracion la api oficial de whatsapp o twilio Es la pieza que conectara tu flujo con Whatsapp. En este chatbot tenemos varios proveedores disponibles la mayoria gratis pero tambien tenemos integracion la api oficial de whatsapp o twilio

View File

@@ -59,7 +59,10 @@ En el **archivo principal** del bot donde estás implementando la función del a
- **jwtToken:** Lo puedes encontrar en la pagina anterior - **jwtToken:** Lo puedes encontrar en la pagina anterior
- **verifyToken:** Puedes escribir lo que quieras es como una palabra clave - **verifyToken:** Puedes escribir lo que quieras es como una palabra clave
<Alert>En el ejemplo de abajo puedes ver como una sugerencia de como puede ser utilizando variables de entorno</Alert> <Alert>
En el ejemplo de abajo puedes ver como una sugerencia de como puede ser
utilizando variables de entorno
</Alert>
```js ```js
const main = async () => { const main = async () => {

View File

@@ -5,7 +5,10 @@ import Navigation from '../../../../components/widgets/Navigation'
Twilio es una plataforma de desarrollo que permite a los desarrolladores construir aplicaciones de comunicación en la nube y sistemas web. Las API de comunicaciones de Twilio permiten a las empresas proporcionar la experiencia de comunicación adecuada para sus clientes dentro de la web y las aplicaciones móviles. Al usar las API de Twilio, los desarrolladores pueden agregar rápidamente esta funcionalidad a una aplicación, como mensajes de voz, videollamadas, mensajes de texto y más. Twilio es una plataforma de desarrollo que permite a los desarrolladores construir aplicaciones de comunicación en la nube y sistemas web. Las API de comunicaciones de Twilio permiten a las empresas proporcionar la experiencia de comunicación adecuada para sus clientes dentro de la web y las aplicaciones móviles. Al usar las API de Twilio, los desarrolladores pueden agregar rápidamente esta funcionalidad a una aplicación, como mensajes de voz, videollamadas, mensajes de texto y más.
<Alert>Twilio te proporciona una cuenta **Sandbox** para que puedas probar gratuitamente el servicio</Alert> <Alert>
Twilio te proporciona una cuenta **Sandbox** para que puedas probar
gratuitamente el servicio
</Alert>
### Requerimientos ### Requerimientos
@@ -63,7 +66,10 @@ En el **archivo principal** del bot donde estás implementando la función del a
- **ACC_VENDOR:** Es el numero de whatsapp (si ya tienes el plan de pago de Twilio usa el numero que compraste), si aun estas en modo - **ACC_VENDOR:** Es el numero de whatsapp (si ya tienes el plan de pago de Twilio usa el numero que compraste), si aun estas en modo
sandbox utliza el numero proporcionado en el paso numero 2 sandbox utliza el numero proporcionado en el paso numero 2
<Alert>En el ejemplo de abajo puedes ver como una sugerencia de como puede ser utilizando variables de entorno</Alert> <Alert>
En el ejemplo de abajo puedes ver como una sugerencia de como puede ser
utilizando variables de entorno
</Alert>
```js ```js
const main = async () => { const main = async () => {

View File

@@ -13,7 +13,8 @@ import { GITHUB_TOKEN } from './docs/constant'
// import { SearchModal } from '~/components/widgets/SearchModal' // import { SearchModal } from '~/components/widgets/SearchModal'
export const onGet: RequestHandlerNetlify = async ({ platform }) => { export const onGet: RequestHandlerNetlify = async ({ platform }) => {
const CHECK_GITHUB_TOKEN = (platform as any)?.['GITHUB_TOKEN'] ?? GITHUB_TOKEN const CHECK_GITHUB_TOKEN =
(platform as any)?.['GITHUB_TOKEN'] ?? GITHUB_TOKEN
const dataGithub = await fetchGithub(CHECK_GITHUB_TOKEN) const dataGithub = await fetchGithub(CHECK_GITHUB_TOKEN)
const dataOpenCollective = await fetchOpenCollective() const dataOpenCollective = await fetchOpenCollective()
return { return {
@@ -51,7 +52,8 @@ export const head: DocumentHead = {
meta: [ meta: [
{ {
name: 'description', name: 'description',
content: 'Crear chatbot WhatsApp en minutos — Servicio de chatbot para whatspp gratis proyecto OpenSource', content:
'Crear chatbot WhatsApp en minutos — Servicio de chatbot para whatspp gratis proyecto OpenSource',
}, },
], ],
} }

View File

@@ -3,14 +3,17 @@
* @returns * @returns
*/ */
export const fetchGithub = async (token: string) => { export const fetchGithub = async (token: string) => {
const data = await fetch(`https://api.github.com/repos/codigoencasa/bot-whatsapp/contributors`, { const data = await fetch(
method: 'GET', `https://api.github.com/repos/codigoencasa/bot-whatsapp/contributors`,
headers: { {
Accept: 'application/vnd.github+json', method: 'GET',
'X-GitHub-Api-Version': '2022-11-28', headers: {
Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json',
}, 'X-GitHub-Api-Version': '2022-11-28',
}) Authorization: `Bearer ${token}`,
},
}
)
const listUsers = await data.json() const listUsers = await data.json()
return listUsers.map((u: any) => ({ return listUsers.map((u: any) => ({
...u, ...u,

View File

@@ -3,9 +3,12 @@
* @returns * @returns
*/ */
export const fetchOpenCollective = async () => { export const fetchOpenCollective = async () => {
const data = await fetch(`https://opencollective.com/bot-whatsapp/members/users.json?limit=22&offset=0`, { const data = await fetch(
method: 'GET', `https://opencollective.com/bot-whatsapp/members/users.json?limit=22&offset=0`,
}) {
method: 'GET',
}
)
const listUsers = await data.json() const listUsers = await data.json()
return listUsers.map((u: any) => ({ return listUsers.map((u: any) => ({
html_url: u.profile, html_url: u.profile,

View File

@@ -76,7 +76,9 @@ module.exports = {
a: { a: {
fontWeight: theme('fontWeight.semibold'), fontWeight: theme('fontWeight.semibold'),
textDecoration: 'none', textDecoration: 'none',
borderBottom: `1px solid ${theme('colors.sky.300')}`, borderBottom: `1px solid ${theme(
'colors.sky.300'
)}`,
}, },
'a:hover': { 'a:hover': {
borderBottomWidth: '2px', borderBottomWidth: '2px',

View File

@@ -3,7 +3,11 @@
"scope": "javascriptreact,typescriptreact", "scope": "javascriptreact,typescriptreact",
"prefix": "q:onGet", "prefix": "q:onGet",
"description": "onGet function for a route index", "description": "onGet function for a route index",
"body": ["export const onGet: RequestHandler = (request) => {", " $0", "};"] "body": [
"export const onGet: RequestHandler = (request) => {",
" $0",
"};"
]
}, },
"onGet (typed)": { "onGet (typed)": {
"scope": "javascriptreact,typescriptreact", "scope": "javascriptreact,typescriptreact",

View File

@@ -56,13 +56,24 @@
"scope": "javascriptreact,typescriptreact", "scope": "javascriptreact,typescriptreact",
"prefix": "q:useTask", "prefix": "q:useTask",
"description": "useTask$() function hook", "description": "useTask$() function hook",
"body": ["useTask$(({ track }) => {", " track(() => $1);", " $0", "});", ""] "body": [
"useTask$(({ track }) => {",
" track(() => $1);",
" $0",
"});",
""
]
}, },
"useResource": { "useResource": {
"scope": "javascriptreact,typescriptreact", "scope": "javascriptreact,typescriptreact",
"prefix": "q:useResource", "prefix": "q:useResource",
"description": "useResource$() declaration", "description": "useResource$() declaration",
"body": ["const $1 = useResource$(({ track, cleanup }) => {", " $0", "});", ""] "body": [
"const $1 = useResource$(({ track, cleanup }) => {",
" $0",
"});",
""
]
}, },
"useServerMount": { "useServerMount": {
"scope": "javascriptreact,typescriptreact", "scope": "javascriptreact,typescriptreact",

View File

@@ -5,9 +5,14 @@ const polka = require('polka')
const HTTP_PORT = process.env.PORT || 3000 const HTTP_PORT = process.env.PORT || 3000
const QR_FILE = process.env.QR_FILE ?? 'bot' const QR_FILE = process.env.QR_FILE ?? 'bot'
const PUBLIC_URL = process.env.PUBLIC_URL ?? process.env.RAILWAY_STATIC_URL ?? 'http://localhost' const PUBLIC_URL =
process.env.PUBLIC_URL ??
process.env.RAILWAY_STATIC_URL ??
'http://localhost'
const dir = [join(__dirname, 'dist'), join(__dirname, '..', 'dist')].find((i) => existsSync(i)) const dir = [join(__dirname, 'dist'), join(__dirname, '..', 'dist')].find((i) =>
existsSync(i)
)
const serve = require('serve-static')(dir) const serve = require('serve-static')(dir)
/** /**
@@ -27,8 +32,14 @@ const start = (args) => {
console.log(``) console.log(``)
console.log(bgYellow(`🚩 ESCANEAR QR 🚩`)) console.log(bgYellow(`🚩 ESCANEAR QR 🚩`))
console.log(cyan(`Existen varias maneras de escanear el QR code`)) console.log(cyan(`Existen varias maneras de escanear el QR code`))
console.log(cyan(`- Tambien puedes visitar `), yellow(`${publicSite}:${port}`)) console.log(
console.log(cyan(`- Se ha creado un archivo que finaliza `), yellow('qr.png')) cyan(`- Tambien puedes visitar `),
yellow(`${publicSite}:${port}`)
)
console.log(
cyan(`- Se ha creado un archivo que finaliza `),
yellow('qr.png')
)
console.log(``) console.log(``)
} }

View File

@@ -8,18 +8,28 @@ export default component$(() => {
return ( return (
<header> <header>
<div class="logo"> <div class="logo">
<a href="https://github.com/codigoencasa/bot-whatsapp" target="_blank" title="qwik"> <a
href="https://github.com/codigoencasa/bot-whatsapp"
target="_blank"
title="qwik"
>
<BotLogo /> <BotLogo />
</a> </a>
</div> </div>
<ul> <ul>
<li> <li>
<a href="https://github.com/codigoencasa/bot-whatsapp" target="_blank"> <a
href="https://github.com/codigoencasa/bot-whatsapp"
target="_blank"
>
Docs Docs
</a> </a>
</li> </li>
<li> <li>
<a href="https://github.com/codigoencasa/bot-whatsapp/tree/main/starters/apps" target="_blank"> <a
href="https://github.com/codigoencasa/bot-whatsapp/tree/main/starters/apps"
target="_blank"
>
Examples Examples
</a> </a>
</li> </li>

View File

@@ -1,5 +1,11 @@
export const QwikLogo = () => ( export const QwikLogo = () => (
<svg width="100" height="35" viewBox="0 0 167 53" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg
width="100"
height="35"
viewBox="0 0 167 53"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path <path
d="M81.9545 46.5859H75.5513V35.4045C73.4363 36.8579 71.0496 37.5749 68.4884 37.5749C65.0151 37.5749 62.4344 36.6253 60.8239 34.6487C59.2134 32.6915 58.3984 29.2034 58.3984 24.2231C58.3984 19.1266 59.3492 15.5997 61.2702 13.5456C63.23 11.4721 66.3734 10.4644 70.7004 10.4644C74.7946 10.4644 78.5201 11.0264 81.9545 12.131V46.5859ZM75.5513 16.278C74.096 15.8323 72.4661 15.6191 70.7004 15.6191C68.5272 15.6191 66.9749 16.1811 66.1017 17.3244C65.2479 18.4871 64.7823 20.6962 64.7823 23.9712C64.7823 27.0524 65.1897 29.1065 66.0435 30.2304C66.8973 31.335 68.3719 31.897 70.5452 31.897C73.3781 31.897 75.5513 30.7343 75.5513 29.2809V16.278Z" d="M81.9545 46.5859H75.5513V35.4045C73.4363 36.8579 71.0496 37.5749 68.4884 37.5749C65.0151 37.5749 62.4344 36.6253 60.8239 34.6487C59.2134 32.6915 58.3984 29.2034 58.3984 24.2231C58.3984 19.1266 59.3492 15.5997 61.2702 13.5456C63.23 11.4721 66.3734 10.4644 70.7004 10.4644C74.7946 10.4644 78.5201 11.0264 81.9545 12.131V46.5859ZM75.5513 16.278C74.096 15.8323 72.4661 15.6191 70.7004 15.6191C68.5272 15.6191 66.9749 16.1811 66.1017 17.3244C65.2479 18.4871 64.7823 20.6962 64.7823 23.9712C64.7823 27.0524 65.1897 29.1065 66.0435 30.2304C66.8973 31.335 68.3719 31.897 70.5452 31.897C73.3781 31.897 75.5513 30.7343 75.5513 29.2809V16.278Z"
fill="black" fill="black"

View File

@@ -1,4 +1,9 @@
import { component$, useClientEffect$, useStore, useStylesScoped$ } from '@builder.io/qwik' import {
component$,
useClientEffect$,
useStore,
useStylesScoped$,
} from '@builder.io/qwik'
import style from './qr.css?inline' import style from './qr.css?inline'
export const QR = component$(() => { export const QR = component$(() => {
@@ -15,7 +20,12 @@ export const QR = component$(() => {
return ( return (
<div> <div>
<img width={350} height={350} src={'qr.png?time=' + state.count} alt="QR" /> <img
width={350}
height={350}
src={'qr.png?time=' + state.count}
alt="QR"
/>
</div> </div>
) )
}) })

View File

@@ -10,7 +10,10 @@ export const RouterHead = component$(() => {
<title>{head.title}</title> <title>{head.title}</title>
<link rel="canonical" href={loc.href} /> <link rel="canonical" href={loc.href} />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
{head.meta.map((m) => ( {head.meta.map((m) => (

View File

@@ -15,7 +15,8 @@
body { body {
background-color: #fafafa; background-color: #fafafa;
font-family: 'Inter', sans-serif, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif; font-family: 'Inter', sans-serif, ui-sans-serif, system-ui, -apple-system,
BlinkMacSystemFont, sans-serif;
padding: 20px 20px 40px 20px; padding: 20px 20px 40px 20px;
} }

View File

@@ -12,7 +12,8 @@
.page .btn-link { .page .btn-link {
background: white; background: white;
box-shadow: rgb(0 0 0 / 16%) 0px 10px 36px 0px, rgb(0 0 0 / 6%) 0px 0px 0px 1px; box-shadow: rgb(0 0 0 / 16%) 0px 10px 36px 0px,
rgb(0 0 0 / 6%) 0px 0px 0px 1px;
padding: 10px; padding: 10px;
border-radius: 5px; border-radius: 5px;
font-weight: 600; font-weight: 600;

View File

@@ -15,13 +15,19 @@ export default component$(() => {
<div class={'qr-section intructions'}> <div class={'qr-section intructions'}>
<h1>Whatsapp QR</h1> <h1>Whatsapp QR</h1>
<p> <p>
Con esta libreria, puedes configurar respuestas automatizadas para preguntas frecuentes, recibir y Con esta libreria, puedes configurar respuestas
responder mensajes de manera automatizada, y hacer un seguimiento de las interacciones con los automatizadas para preguntas frecuentes, recibir y responder
clientes. <br /> Además, nuestro Chatbot se integra fácilmente con otros sistemas y herramientas que mensajes de manera automatizada, y hacer un seguimiento de
ya esté utilizando en su negocio. las interacciones con los clientes. <br /> Además, nuestro
Chatbot se integra fácilmente con otros sistemas y
herramientas que ya esté utilizando en su negocio.
</p> </p>
<div class={'qr-section links'}> <div class={'qr-section links'}>
<a class={'btn-link '} target="_blank" href="https://bot-whatsapp.netlify.app/"> <a
class={'btn-link '}
target="_blank"
href="https://bot-whatsapp.netlify.app/"
>
Ver documentación Ver documentación
</a> </a>
<a <a
@@ -31,7 +37,11 @@ export default component$(() => {
> >
Ver videos Ver videos
</a> </a>
<a class={'btn-link '} target="_blank" href="https://opencollective.com/bot-whatsapp"> <a
class={'btn-link '}
target="_blank"
href="https://opencollective.com/bot-whatsapp"
>
Comprar café Comprar café
</a> </a>
</div> </div>

View File

@@ -1,65 +0,0 @@
const mimeDep = require('mime-types')
const { tmpdir } = require('os')
const http = require('http')
const https = require('https')
const { rename, createWriteStream } = require('fs')
/**
* Extrar el mimetype from buffer
* @param {string} response
* @returns
*/
const fileTypeFromFile = async (response) => {
const type = response.headers['content-type'] ?? null
const ext = mimeDep.extension(type)
return {
type,
ext,
}
}
/**
* Descargar archivo binay en tmp
* @param {*} url
* @returns
*/
const generalDownload = async (url) => {
const handleDownload = () => {
const checkProtocol = url.includes('https:')
const handleHttp = checkProtocol ? https : http
const name = `tmp-${Date.now()}-dat`
const fullPath = `${tmpdir()}/${name}`
const file = createWriteStream(fullPath)
return new Promise((res, rej) => {
handleHttp.get(url, function (response) {
response.pipe(file)
file.on('finish', async function () {
file.close()
res({ response, fullPath })
})
file.on('error', function () {
file.close()
rej(null)
})
})
})
}
const handleFile = (pathInput, ext) =>
new Promise((resolve, reject) => {
const fullPath = `${pathInput}.${ext}`
rename(pathInput, fullPath, (err) => {
if (err) reject(null)
resolve(fullPath)
})
})
const httpResponse = await handleDownload()
const { ext } = await fileTypeFromFile(httpResponse.response)
const getPath = await handleFile(httpResponse.fullPath, ext)
return getPath
}
module.exports = { generalDownload }

View File

@@ -4,14 +4,21 @@ const pino = require('pino')
const rimraf = require('rimraf') const rimraf = require('rimraf')
const mime = require('mime-types') const mime = require('mime-types')
const { join } = require('path') const { join } = require('path')
const { createWriteStream, readFileSync } = require('fs') const { existsSync, createWriteStream, readFileSync } = require('fs')
const { Console } = require('console') const { Console } = require('console')
const { default: makeWASocket, useMultiFileAuthState, Browsers, DisconnectReason } = require('@adiwajshing/baileys') const {
default: makeWASocket,
const { baileyGenerateImage, baileyCleanNumber, baileyIsValidNumber } = require('./utils') useMultiFileAuthState,
Browsers,
const { generalDownload } = require('../../common/download') DisconnectReason,
} = require('@adiwajshing/baileys')
const {
baileyGenerateImage,
baileyCleanNumber,
baileyIsValidNumber,
baileyDownloadMedia,
} = require('./utils')
const logger = new Console({ const logger = new Console({
stdout: createWriteStream(`${process.cwd()}/baileys.log`), stdout: createWriteStream(`${process.cwd()}/baileys.log`),
@@ -37,7 +44,9 @@ class BaileysProvider extends ProviderClass {
*/ */
initBailey = async () => { initBailey = async () => {
const NAME_DIR_SESSION = `${this.globalVendorArgs.name}_sessions` const NAME_DIR_SESSION = `${this.globalVendorArgs.name}_sessions`
const { state, saveCreds } = await useMultiFileAuthState(NAME_DIR_SESSION) const { state, saveCreds } = await useMultiFileAuthState(
NAME_DIR_SESSION
)
this.saveCredsGlobal = saveCreds this.saveCredsGlobal = saveCreds
try { try {
@@ -46,7 +55,7 @@ class BaileysProvider extends ProviderClass {
auth: state, auth: state,
browser: Browsers.macOS('Desktop'), browser: Browsers.macOS('Desktop'),
syncFullHistory: false, syncFullHistory: false,
logger: pino({ level: 'fatal' }), logger: pino({ level: 'error' }),
}) })
sock.ev.on('connection.update', async (update) => { sock.ev.on('connection.update', async (update) => {
@@ -85,7 +94,10 @@ class BaileysProvider extends ProviderClass {
`Necesitas ayuda: https://link.codigoencasa.com/DISCORD`, `Necesitas ayuda: https://link.codigoencasa.com/DISCORD`,
], ],
}) })
await baileyGenerateImage(qr, `${this.globalVendorArgs.name}.qr.png`) await baileyGenerateImage(
qr,
`${this.globalVendorArgs.name}.qr.png`
)
} }
}) })
@@ -117,10 +129,9 @@ class BaileysProvider extends ProviderClass {
const [messageCtx] = messages const [messageCtx] = messages
let payload = { let payload = {
...messageCtx, ...messageCtx,
body: messageCtx?.message?.extendedTextMessage?.text ?? messageCtx?.message?.conversation, body: messageCtx?.message?.conversation,
from: messageCtx?.key?.remoteJid, from: messageCtx?.key?.remoteJid,
} }
if (payload.from === 'status@broadcast') return if (payload.from === 'status@broadcast') return
if (payload?.key?.fromMe) return if (payload?.key?.fromMe) return
@@ -129,7 +140,9 @@ class BaileysProvider extends ProviderClass {
return return
} }
const btnCtx = payload?.message?.buttonsResponseMessage?.selectedDisplayText const btnCtx =
payload?.message?.buttonsResponseMessage
?.selectedDisplayText
if (btnCtx) payload.body = btnCtx if (btnCtx) payload.body = btnCtx
@@ -156,47 +169,14 @@ class BaileysProvider extends ProviderClass {
*/ */
sendMedia = async (number, imageUrl, text) => { sendMedia = async (number, imageUrl, text) => {
const fileDownloaded = await generalDownload(imageUrl) const fileDownloaded = await baileyDownloadMedia(imageUrl)
const mimeType = mime.lookup(fileDownloaded)
if (mimeType.includes('image')) return this.sendImage(number, fileDownloaded, text)
if (mimeType.includes('video')) return this.sendVideo(number, fileDownloaded, text)
if (mimeType.includes('audio')) return this.sendAudio(number, fileDownloaded, text)
return this.sendFile(number, fileDownloaded)
}
/**
* Enviar imagen
* @param {*} number
* @param {*} imageUrl
* @param {*} text
* @returns
*/
sendImage = async (number, filePath, text) => {
return this.vendor.sendMessage(number, { return this.vendor.sendMessage(number, {
image: readFileSync(filePath), image: readFileSync(fileDownloaded),
caption: text, caption: text,
}) })
} }
/** /**
* Enviar video
* @param {*} number
* @param {*} imageUrl
* @param {*} text
* @returns
*/
sendVideo = async (number, filePath, text) => {
return this.vendor.sendMessage(number, {
video: readFileSync(filePath),
caption: text,
gifPlayback: true,
})
}
/**
* Enviar audio
* @alpha * @alpha
* @param {string} number * @param {string} number
* @param {string} message * @param {string} message
@@ -205,7 +185,8 @@ class BaileysProvider extends ProviderClass {
*/ */
sendAudio = async (number, audioUrl, voiceNote = false) => { sendAudio = async (number, audioUrl, voiceNote = false) => {
return this.vendor.sendMessage(number, { const numberClean = number.replace('+', '')
await this.vendor.sendMessage(`${numberClean}@c.us`, {
audio: { url: audioUrl }, audio: { url: audioUrl },
ptt: voiceNote, ptt: voiceNote,
}) })
@@ -229,13 +210,17 @@ class BaileysProvider extends ProviderClass {
*/ */
sendFile = async (number, filePath) => { sendFile = async (number, filePath) => {
const mimeType = mime.lookup(filePath) if (existsSync(filePath)) {
const fileName = filePath.split('/').pop() const mimeType = mime.lookup(filePath)
return this.vendor.sendMessage(number, { const numberClean = number.replace('+', '')
document: { url: filePath }, const fileName = filePath.split('/').pop()
mimetype: mimeType,
fileName: fileName, await this.vendor.sendMessage(`${numberClean}@c.us`, {
}) document: { url: filePath },
mimetype: mimeType,
fileName: fileName,
})
}
} }
/** /**
@@ -276,8 +261,10 @@ class BaileysProvider extends ProviderClass {
sendMessage = async (numberIn, message, { options }) => { sendMessage = async (numberIn, message, { options }) => {
const number = baileyCleanNumber(numberIn) const number = baileyCleanNumber(numberIn)
if (options?.buttons?.length) return this.sendButtons(number, message, options.buttons) if (options?.buttons?.length)
if (options?.media) return this.sendMedia(number, options.media, message) return this.sendButtons(number, message, options.buttons)
if (options?.media)
return this.sendMedia(number, options.media, message)
return this.sendText(number, message) return this.sendText(number, message)
} }
@@ -312,7 +299,12 @@ class BaileysProvider extends ProviderClass {
* @example await sendContact("xxxxxxxxxxx@c.us" || "xxxxxxxxxxxxxxxxxx@g.us", "+xxxxxxxxxxx", "Robin Smith", messages) * @example await sendContact("xxxxxxxxxxx@c.us" || "xxxxxxxxxxxxxxxxxx@g.us", "+xxxxxxxxxxx", "Robin Smith", messages)
*/ */
sendContact = async (remoteJid, contactNumber, displayName, messages = null) => { sendContact = async (
remoteJid,
contactNumber,
displayName,
messages = null
) => {
const cleanContactNumber = contactNumber.replaceAll(' ', '') const cleanContactNumber = contactNumber.replaceAll(' ', '')
const waid = cleanContactNumber.replace('+', '') const waid = cleanContactNumber.replace('+', '')

View File

@@ -1,6 +1,7 @@
{ {
"dependencies": { "dependencies": {
"@adiwajshing/baileys": "4.4.0", "@adiwajshing/baileys": "4.4.0",
"mime-types": "2.1.35",
"wa-sticker-formatter": "4.3.2" "wa-sticker-formatter": "4.3.2"
} }
} }

View File

@@ -1,6 +1,9 @@
const { createWriteStream } = require('fs') const { createWriteStream } = require('fs')
const combineImage = require('combine-image') const combineImage = require('combine-image')
const qr = require('qr-image') const qr = require('qr-image')
const { tmpdir } = require('os')
const http = require('http')
const https = require('https')
const baileyCleanNumber = (number, full = false) => { const baileyCleanNumber = (number, full = false) => {
number = number.replace('@s.whatsapp.net', '') number = number.replace('@s.whatsapp.net', '')
@@ -38,8 +41,38 @@ const baileyIsValidNumber = (rawNumber) => {
return !exist return !exist
} }
/**
* Incompleta
* Descargar archivo multimedia para enviar
* @param {*} url
* @returns
*/
const baileyDownloadMedia = (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 = { module.exports = {
baileyCleanNumber, baileyCleanNumber,
baileyGenerateImage, baileyGenerateImage,
baileyIsValidNumber, baileyIsValidNumber,
baileyDownloadMedia,
} }

View File

@@ -56,11 +56,15 @@ class MetaProvider extends ProviderClass {
sendMessageMeta = async (body) => { sendMessageMeta = async (body) => {
try { try {
const response = await axios.post(`${URL}/${this.numberId}/messages`, body, { const response = await axios.post(
headers: { `${URL}/${this.numberId}/messages`,
Authorization: `Bearer ${this.jwtToken}`, body,
}, {
}) headers: {
Authorization: `Bearer ${this.jwtToken}`,
},
}
)
return response.data return response.data
} catch (error) { } catch (error) {
return Promise.resolve(error) return Promise.resolve(error)
@@ -102,7 +106,8 @@ class MetaProvider extends ProviderClass {
*/ */
sendMessage = async (number, message, { options }) => { sendMessage = async (number, message, { options }) => {
if (options?.buttons?.length) return console.log('Envio de botones') if (options?.buttons?.length) return console.log('Envio de botones')
if (options?.media) return this.sendMedia(number, message, options.media) if (options?.media)
return this.sendMedia(number, message, options.media)
this.sendtext(number, message) this.sendtext(number, message)
} }

Some files were not shown because too many files have changed in this diff Show More