chore(release): 0.1.20

chore(release): 0.1.20
This commit is contained in:
Leifer Mendez
2023-01-29 18:44:19 +01:00
committed by GitHub
16 changed files with 653 additions and 162 deletions

View File

@@ -2,6 +2,36 @@
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.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)

118
__test__/05-case.test.js Normal file
View File

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

111
__test__/06-case.test.js Normal file
View File

@@ -0,0 +1,111 @@
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,6 +1,6 @@
{ {
"name": "@bot-whatsapp/root", "name": "@bot-whatsapp/root",
"version": "0.1.18", "version": "0.1.19",
"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,6 +81,7 @@
"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

@@ -71,8 +71,8 @@ class CoreClass {
logger.log(`[handleMsg]: `, messageCtxInComming) logger.log(`[handleMsg]: `, messageCtxInComming)
const { body, from } = messageCtxInComming const { body, from } = messageCtxInComming
let msgToSend = [] let msgToSend = []
let fallBackFlag = false
let endFlowFlag = false let endFlowFlag = false
let fallBackFlag = 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
@@ -91,6 +91,25 @@ 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
@@ -98,18 +117,32 @@ class CoreClass {
} }
// 📄 Finalizar flujo // 📄 Finalizar flujo
const endFlow = async () => { const endFlow = async (message = null) => {
prevMsg = null prevMsg = null
endFlowFlag = true endFlowFlag = true
if (message)
this.sendProviderAndSave(from, createCtxMessage(message))
clearQueue() clearQueue()
return return
} }
// 📄 Esta funcion se encarga de enviar un array de mensajes dentro de este ctx // 📄 Continuar con el siguiente flujo
const sendFlow = async (messageToSend, numberOrId) => { const continueFlow = async () => {
// [1 Paso] esto esta bien! const cotinueMessage =
this.flowClass.find(refToContinue?.ref, true) || []
sendFlow(cotinueMessage, from, { continue: true })
return
}
// 📄 Esta funcion se encarga de enviar un array de mensajes dentro de este ctx
const sendFlow = async (
messageToSend,
numberOrId,
options = { continue: false }
) => {
if (!options.continue && prevMsg?.options?.capture)
await cbEveryCtx(prevMsg?.ref)
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
@@ -127,45 +160,37 @@ 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 () => { const fallBack = async (next = false, message = null) => {
fallBackFlag = true
await this.sendProviderAndSave(from, refToContinue)
QueuePrincipal.queue = [] QueuePrincipal.queue = []
return refToContinue if (next) return continueFlow()
return this.sendProviderAndSave(from, {
...prevMsg,
answer:
typeof message === 'string'
? message
: message?.body ?? prevMsg.answer,
options: {
...prevMsg.options,
buttons: message?.buttons ?? prevMsg.options?.buttons,
},
})
} }
// 📄 [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 ( const flowDynamic = async (listMsg = []) => {
listMsg = [], if (!Array.isArray(listMsg)) listMsg = [listMsg]
optListMsg = { limit: 5, fallback: false }
) => {
if (!Array.isArray(listMsg))
throw new Error('Esto debe ser un ARRAY')
fallBackFlag = optListMsg.fallback const parseListMsg = listMsg.map((opt, index) =>
const parseListMsg = listMsg createCtxMessage(opt, index)
.map((opt, index) => { )
const body = typeof opt === 'string' ? opt : opt.body
const media = opt?.media ?? null
const buttons = opt?.buttons ?? []
return toCtx({
body,
from,
keyword: null,
index,
options: { media, buttons },
})
})
.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)
} }
return return continueFlow()
} }
// 📄 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
@@ -181,11 +206,12 @@ class CoreClass {
fallBack, fallBack,
flowDynamic, flowDynamic,
endFlow, endFlow,
continueFlow,
}) })
} }
// 📄🤘(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 (!fallBackFlag && prevMsg?.options?.nested?.length) { if (!endFlowFlag && 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),
@@ -197,12 +223,11 @@ class CoreClass {
return return
} }
// 📄🤘(tiene return) [options: capture (boolean)]: Si se tiene option boolean // 📄🤘(tiene return) Si el mensaje previo implementa capture
if (!fallBackFlag && !prevMsg?.options?.nested?.length) { if (!endFlowFlag && !prevMsg?.options?.nested?.length) {
const typeCapture = typeof prevMsg?.options?.capture const typeCapture = typeof prevMsg?.options?.capture
const valueCapture = prevMsg?.options?.capture
if (['string', 'boolean'].includes(typeCapture) && valueCapture) { if (typeCapture === 'boolean' && fallBackFlag) {
msgToSend = this.flowClass.find(refToContinue?.ref, true) || [] msgToSend = this.flowClass.find(refToContinue?.ref, true) || []
sendFlow(msgToSend, from) sendFlow(msgToSend, from)
return return
@@ -228,6 +253,7 @@ class CoreClass {
} }
/** /**
* @deprecated
* @private * @private
* @param {*} message * @param {*} message
* @param {*} ref * @param {*} ref

View File

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

View File

@@ -29,13 +29,16 @@ 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'],
}
)
``` ```
--- ---
@@ -175,65 +178,75 @@ 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(
.addAnswer(['Hola!','Escriba su *Nombre* para generar su solicitud'], ['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([{body: "❌ *Su solicitud de cita ha sido cancelada* ❌", buttons:[{body:'⬅️ Volver al Inicio'}]}]) await flowDynamic([
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'}]}, ])
async (ctx,{flowDynamic, endFlow})=>{ return 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'],
.addAnswer(['Dejeme su número de teléfono y le llamaré lo antes posible.'], { 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' }],
}) },
])
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
QRPortalWeb({name:BOTNAME, port:3005 }); const BOTNAME = 'bot'
QRPortalWeb({ name: BOTNAME, port: 3005 })
``` ```
--- ---
<Navigation <Navigation
pages={[ pages={[
{ name: 'Conceptos', link: '/docs/essential' }, { name: 'Conceptos', link: '/docs/essential' },

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'}>

View File

@@ -0,0 +1,65 @@
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,7 +4,7 @@ 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 { existsSync, createWriteStream, readFileSync } = require('fs') const { createWriteStream, readFileSync } = require('fs')
const { Console } = require('console') const { Console } = require('console')
const { const {
@@ -13,13 +13,15 @@ const {
Browsers, Browsers,
DisconnectReason, DisconnectReason,
} = require('@adiwajshing/baileys') } = require('@adiwajshing/baileys')
const { const {
baileyGenerateImage, baileyGenerateImage,
baileyCleanNumber, baileyCleanNumber,
baileyIsValidNumber, baileyIsValidNumber,
baileyDownloadMedia,
} = require('./utils') } = require('./utils')
const { generalDownload } = require('../../common/download')
const logger = new Console({ const logger = new Console({
stdout: createWriteStream(`${process.cwd()}/baileys.log`), stdout: createWriteStream(`${process.cwd()}/baileys.log`),
}) })
@@ -169,14 +171,50 @@ class BaileysProvider extends ProviderClass {
*/ */
sendMedia = async (number, imageUrl, text) => { sendMedia = async (number, imageUrl, text) => {
const fileDownloaded = await baileyDownloadMedia(imageUrl) const fileDownloaded = await generalDownload(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(fileDownloaded), image: readFileSync(filePath),
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
@@ -185,8 +223,7 @@ class BaileysProvider extends ProviderClass {
*/ */
sendAudio = async (number, audioUrl, voiceNote = false) => { sendAudio = async (number, audioUrl, voiceNote = false) => {
const numberClean = number.replace('+', '') return this.vendor.sendMessage(number, {
await this.vendor.sendMessage(`${numberClean}@c.us`, {
audio: { url: audioUrl }, audio: { url: audioUrl },
ptt: voiceNote, ptt: voiceNote,
}) })
@@ -210,17 +247,13 @@ class BaileysProvider extends ProviderClass {
*/ */
sendFile = async (number, filePath) => { sendFile = async (number, filePath) => {
if (existsSync(filePath)) { const mimeType = mime.lookup(filePath)
const mimeType = mime.lookup(filePath) const fileName = filePath.split('/').pop()
const numberClean = number.replace('+', '') return this.vendor.sendMessage(number, {
const fileName = filePath.split('/').pop() document: { url: filePath },
mimetype: mimeType,
await this.vendor.sendMessage(`${numberClean}@c.us`, { fileName: fileName,
document: { url: filePath }, })
mimetype: mimeType,
fileName: fileName,
})
}
} }
/** /**

View File

@@ -1,7 +1,6 @@
{ {
"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,9 +1,6 @@
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', '')
@@ -41,38 +38,8 @@ 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

@@ -2,18 +2,20 @@ const { ProviderClass } = require('@bot-whatsapp/bot')
const venom = require('venom-bot') const venom = require('venom-bot')
const { createWriteStream } = require('fs') const { createWriteStream } = require('fs')
const { Console } = require('console') const { Console } = require('console')
const mime = require('mime-types')
const { const {
venomCleanNumber, venomCleanNumber,
venomGenerateImage, venomGenerateImage,
venomisValidNumber, venomisValidNumber,
venomDownloadMedia,
} = require('./utils') } = require('./utils')
const logger = new Console({ const logger = new Console({
stdout: createWriteStream(`${process.cwd()}/venom.log`), stdout: createWriteStream(`${process.cwd()}/venom.log`),
}) })
const { generalDownload } = require('../../common/download')
/** /**
* ⚙️ VenomProvider: Es una clase tipo adaptor * ⚙️ VenomProvider: Es una clase tipo adaptor
* que extiende clases de ProviderClass (la cual es como interfaz para sber que funciones rqueridas) * que extiende clases de ProviderClass (la cual es como interfaz para sber que funciones rqueridas)
@@ -134,6 +136,53 @@ class VenomProvider extends ProviderClass {
// return this.vendor.sendButtons(number, "Title", buttons1, "Description"); // return this.vendor.sendButtons(number, "Title", buttons1, "Description");
} }
/**
* Enviar audio
* @alpha
* @param {string} number
* @param {string} message
* @param {boolean} voiceNote optional
* @example await sendMessage('+XXXXXXXXXXX', 'audio.mp3')
*/
sendAudio = async (number, audioPath) => {
return this.vendor.sendVoice(number, audioPath)
}
/**
* Enviar imagen
* @param {*} number
* @param {*} imageUrl
* @param {*} text
* @returns
*/
sendImage = async (number, filePath, text) => {
return this.vendor.sendImage(number, filePath, 'image-name', text)
}
/**
*
* @param {string} number
* @param {string} filePath
* @example await sendMessage('+XXXXXXXXXXX', './document/file.pdf')
*/
sendFile = async (number, filePath, text) => {
const fileName = filePath.split('/').pop()
return this.vendor.sendFile(number, filePath, fileName, text)
}
/**
* Enviar video
* @param {*} number
* @param {*} imageUrl
* @param {*} text
* @returns
*/
sendVideo = async (number, filePath, text) => {
return this.vendor.sendVideoAsGif(number, filePath, 'video.gif', text)
}
/** /**
* Enviar imagen o multimedia * Enviar imagen o multimedia
* @param {*} number * @param {*} number
@@ -141,10 +190,18 @@ class VenomProvider extends ProviderClass {
* @param {*} message * @param {*} message
* @returns * @returns
*/ */
sendMedia = async (number, mediaInput, message) => { sendMedia = async (number, mediaUrl, text) => {
if (!mediaInput) throw new Error(`NO_SE_ENCONTRO: ${mediaInput}`) const fileDownloaded = await generalDownload(mediaUrl)
const fileDownloaded = await venomDownloadMedia(mediaInput) const mimeType = mime.lookup(fileDownloaded)
return this.vendor.sendImage(number, fileDownloaded, '.', message)
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)
return this.sendFile(number, fileDownloaded, text)
} }
/** /**

View File

@@ -1,10 +1,9 @@
const { Client, LocalAuth, MessageMedia, Buttons } = require('whatsapp-web.js') const { Client, LocalAuth, MessageMedia, Buttons } = require('whatsapp-web.js')
const { ProviderClass } = require('@bot-whatsapp/bot') const { ProviderClass } = require('@bot-whatsapp/bot')
const { Console } = require('console') const { Console } = require('console')
const { createWriteStream } = require('fs') const { createWriteStream, readFileSync } = require('fs')
const { const {
wwebCleanNumber, wwebCleanNumber,
wwebDownloadMedia,
wwebGenerateImage, wwebGenerateImage,
wwebIsValidNumber, wwebIsValidNumber,
} = require('./utils') } = require('./utils')
@@ -13,6 +12,9 @@ const logger = new Console({
stdout: createWriteStream('./log'), stdout: createWriteStream('./log'),
}) })
const { generalDownload } = require('../../common/download')
const mime = require('mime-types')
/** /**
* ⚙️ WebWhatsappProvider: Es una clase tipo adaptor * ⚙️ WebWhatsappProvider: Es una clase tipo adaptor
* que extiende clases de ProviderClass (la cual es como interfaz para sber que funciones rqueridas) * que extiende clases de ProviderClass (la cual es como interfaz para sber que funciones rqueridas)
@@ -35,6 +37,7 @@ class WebWhatsappProvider extends ProviderClass {
'--disable-setuid-sandbox', '--disable-setuid-sandbox',
'--unhandled-rejections=strict', '--unhandled-rejections=strict',
], ],
//executablePath: 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
}, },
}) })
@@ -103,23 +106,6 @@ class WebWhatsappProvider extends ProviderClass {
}, },
] ]
/**
* Enviar un archivo multimedia
* https://docs.wwebjs.dev/MessageMedia.html
* @private
* @param {*} number
* @param {*} mediaInput
* @returns
*/
sendMedia = async (number, mediaInput = null) => {
if (!mediaInput) throw new Error(`NO_SE_ENCONTRO: ${mediaInput}`)
const fileDownloaded = await wwebDownloadMedia(mediaInput)
const media = MessageMedia.fromFilePath(fileDownloaded)
return this.vendor.sendMessage(number, media, {
sendAudioAsVoice: true,
})
}
/** /**
* Enviar botones * Enviar botones
* https://docs.wwebjs.dev/Buttons.html * https://docs.wwebjs.dev/Buttons.html
@@ -170,6 +156,90 @@ class WebWhatsappProvider extends ProviderClass {
return this.vendor.sendMessage(number, message) return this.vendor.sendMessage(number, message)
} }
/**
* Enviar imagen
* @param {*} number
* @param {*} imageUrl
* @param {*} text
* @returns
*/
sendImage = async (number, filePath) => {
const base64 = readFileSync(filePath, { encoding: 'base64' })
const mimeType = mime.lookup(filePath)
const media = new MessageMedia(mimeType, base64)
return this.vendor.sendMessage(number, media, {
caption: 'soy una imagen',
})
}
/**
* Enviar audio
* @param {*} number
* @param {*} imageUrl
* @param {*} text
* @returns
*/
sendAudio = async (number, filePath) => {
const base64 = readFileSync(filePath, { encoding: 'base64' })
const mimeType = mime.lookup(filePath)
const media = new MessageMedia(mimeType, base64)
return this.vendor.sendMessage(number, media, {
caption: 'soy un audio',
})
}
/**
* Enviar video
* @param {*} number
* @param {*} imageUrl
* @param {*} text
* @returns
*/
sendVideo = async (number, filePath) => {
const base64 = readFileSync(filePath, { encoding: 'base64' })
const mimeType = mime.lookup(filePath)
const media = new MessageMedia(mimeType, base64)
return this.vendor.sendMessage(number, media, {
sendMediaAsDocument: true,
})
}
/**
* Enviar Arhivos/pdf
* @param {*} number
* @param {*} imageUrl
* @param {*} text
* @returns
*/
sendFile = async (number, filePath) => {
const base64 = readFileSync(filePath, { encoding: 'base64' })
const mimeType = mime.lookup(filePath)
const media = new MessageMedia(mimeType, base64)
return this.vendor.sendMessage(number, media)
}
/**
* Enviar imagen o multimedia
* @param {*} number
* @param {*} mediaInput
* @param {*} message
* @returns
*/
sendMedia = async (number, mediaUrl, text) => {
const fileDownloaded = await generalDownload(mediaUrl)
const mimeType = mime.lookup(fileDownloaded)
if (mimeType.includes('image'))
return this.sendImage(number, fileDownloaded, text)
if (mimeType.includes('video'))
return this.sendVideo(number, fileDownloaded)
if (mimeType.includes('audio'))
return this.sendAudio(number, fileDownloaded)
return this.sendFile(number, fileDownloaded)
}
/** /**
* *
* @param {*} userId * @param {*} userId

View File

@@ -1,5 +1,5 @@
{ {
"dependencies": { "dependencies": {
"whatsapp-web.js": "1.19.2" "whatsapp-web.js": "1.19.3"
} }
} }

View File

@@ -1113,6 +1113,7 @@ __metadata:
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
@@ -13443,7 +13444,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"mime-types@npm:^2.1.12, mime-types@npm:~2.1.19, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": "mime-types@npm:^2.1.12, mime-types@npm:^2.1.35, mime-types@npm:~2.1.19, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34":
version: 2.1.35 version: 2.1.35
resolution: "mime-types@npm:2.1.35" resolution: "mime-types@npm:2.1.35"
dependencies: dependencies: