diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b39939..d0bdbfa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. +### [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) diff --git a/__test__/05-case.test.js b/__test__/05-case.test.js new file mode 100644 index 0000000..6ca65d0 --- /dev/null +++ b/__test__/05-case.test.js @@ -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)) +} diff --git a/__test__/06-case.test.js b/__test__/06-case.test.js new file mode 100644 index 0000000..c68679e --- /dev/null +++ b/__test__/06-case.test.js @@ -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)) +} diff --git a/package.json b/package.json index fda898a..30e3cf4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bot-whatsapp/root", - "version": "0.1.18", + "version": "0.1.19", "description": "Bot de wahtsapp open source para MVP o pequeños negocios", "main": "app.js", "private": true, @@ -81,6 +81,7 @@ "fs-extra": "^11.1.0", "git-cz": "^4.9.0", "husky": "^8.0.2", + "mime-types": "^2.1.35", "only-allow": "^1.1.1", "prettier": "^2.8.0", "pretty-quick": "^3.1.3", diff --git a/packages/bot/core/core.class.js b/packages/bot/core/core.class.js index f7051b3..9b329cc 100644 --- a/packages/bot/core/core.class.js +++ b/packages/bot/core/core.class.js @@ -71,8 +71,8 @@ class CoreClass { logger.log(`[handleMsg]: `, messageCtxInComming) const { body, from } = messageCtxInComming let msgToSend = [] - let fallBackFlag = false let endFlowFlag = false + let fallBackFlag = false if (this.generalArgs.blackList.includes(from)) return if (!body) return if (!body.length) return @@ -91,6 +91,25 @@ class CoreClass { 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 const clearQueue = () => { QueuePrincipal.pendingPromise = false @@ -98,18 +117,32 @@ class CoreClass { } // 📄 Finalizar flujo - const endFlow = async () => { + const endFlow = async (message = null) => { prevMsg = null endFlowFlag = true + if (message) + this.sendProviderAndSave(from, createCtxMessage(message)) clearQueue() return } - // 📄 Esta funcion se encarga de enviar un array de mensajes dentro de este ctx - const sendFlow = async (messageToSend, numberOrId) => { - // [1 Paso] esto esta bien! + // 📄 Continuar con el siguiente flujo + const continueFlow = async () => { + 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 = [] for (const ctxMessage of messageToSend) { if (endFlowFlag) return @@ -127,45 +160,37 @@ class CoreClass { } // 📄 [options: fallBack]: esta funcion se encarga de repetir el ultimo mensaje - const fallBack = async () => { - fallBackFlag = true - await this.sendProviderAndSave(from, refToContinue) + const fallBack = async (next = false, message = null) => { 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 // para evitar bloque de whatsapp - const flowDynamic = async ( - listMsg = [], - optListMsg = { limit: 5, fallback: false } - ) => { - if (!Array.isArray(listMsg)) - throw new Error('Esto debe ser un ARRAY') + const flowDynamic = async (listMsg = []) => { + if (!Array.isArray(listMsg)) listMsg = [listMsg] - fallBackFlag = optListMsg.fallback - const parseListMsg = listMsg - .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) + const parseListMsg = listMsg.map((opt, index) => + createCtxMessage(opt, index) + ) if (endFlowFlag) return for (const msg of parseListMsg) { await this.sendProviderAndSave(from, msg) } - return + return continueFlow() } // 📄 Se encarga de revisar si el contexto del mensaje tiene callback o fallback @@ -181,11 +206,12 @@ class CoreClass { fallBack, flowDynamic, endFlow, + continueFlow, }) } // 📄🤘(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 flowStandalone = nestedRef.map((f) => ({ ...nestedRef.find((r) => r.refSerialize === f.refSerialize), @@ -197,12 +223,11 @@ class CoreClass { return } - // 📄🤘(tiene return) [options: capture (boolean)]: Si se tiene option boolean - if (!fallBackFlag && !prevMsg?.options?.nested?.length) { + // 📄🤘(tiene return) Si el mensaje previo implementa capture + if (!endFlowFlag && !prevMsg?.options?.nested?.length) { 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) || [] sendFlow(msgToSend, from) return @@ -228,6 +253,7 @@ class CoreClass { } /** + * @deprecated * @private * @param {*} message * @param {*} ref diff --git a/packages/bot/package.json b/packages/bot/package.json index fe542c1..3d4acf3 100644 --- a/packages/bot/package.json +++ b/packages/bot/package.json @@ -1,6 +1,6 @@ { "name": "@bot-whatsapp/bot", - "version": "0.0.73-alpha.0", + "version": "0.0.91-alpha.0", "description": "", "main": "./lib/bundle.bot.cjs", "scripts": { diff --git a/packages/docs/src/routes/docs/flows/index.mdx b/packages/docs/src/routes/docs/flows/index.mdx index 0cb1222..94b1f59 100644 --- a/packages/docs/src/routes/docs/flows/index.mdx +++ b/packages/docs/src/routes/docs/flows/index.mdx @@ -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". ```js -createBot({ +createBot( + { flow: adapterFlow, provider: adapterProvider, database: adapterDB, - },{ - blackList:['34XXXXXXXXX','34XXXXXXXXX','34XXXXXXXXX','34XXXXXXXXX'] - }) + }, + { + blackList: ['34XXXXXXXXX', '34XXXXXXXXX', '34XXXXXXXXX', '34XXXXXXXXX'], + } +) ``` --- @@ -175,65 +178,75 @@ const flowString = addKeyword('hola') ``` --- + ## 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. Como podrás comprobar en el ejemplo siguiente, se puede vincular flowDynamic y todas sus funciones; como por ejemplo botones. - - ```js const flowFormulario = addKeyword(['Hola']) - -.addAnswer(['Hola!','Escriba su *Nombre* para generar su solicitud'], -{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() - } - }) - .addAnswer(['También necesito tus dos apellidos'], - {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() - } - }) - .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() - } - }) - - - + .addAnswer( + ['Hola!', 'Escriba su *Nombre* para generar su solicitud'], + { 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() + } + } + ) + .addAnswer( + ['También necesito tus dos apellidos'], + { 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() + } + } + ) + .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 Argumento para asignar nombre y puerto al BOT ```js -QRPortalWeb({name:BOTNAME, port:3005 }); - +const BOTNAME = 'bot' +QRPortalWeb({ name: BOTNAME, port: 3005 }) ``` --- - - { return ( <> - + {/* */}
diff --git a/packages/provider/common/download.js b/packages/provider/common/download.js new file mode 100644 index 0000000..76e23a0 --- /dev/null +++ b/packages/provider/common/download.js @@ -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 } diff --git a/packages/provider/src/baileys/index.js b/packages/provider/src/baileys/index.js index de6c66d..9361731 100644 --- a/packages/provider/src/baileys/index.js +++ b/packages/provider/src/baileys/index.js @@ -4,7 +4,7 @@ const pino = require('pino') const rimraf = require('rimraf') const mime = require('mime-types') const { join } = require('path') -const { existsSync, createWriteStream, readFileSync } = require('fs') +const { createWriteStream, readFileSync } = require('fs') const { Console } = require('console') const { @@ -13,13 +13,15 @@ const { Browsers, DisconnectReason, } = require('@adiwajshing/baileys') + const { baileyGenerateImage, baileyCleanNumber, baileyIsValidNumber, - baileyDownloadMedia, } = require('./utils') +const { generalDownload } = require('../../common/download') + const logger = new Console({ stdout: createWriteStream(`${process.cwd()}/baileys.log`), }) @@ -169,14 +171,50 @@ class BaileysProvider extends ProviderClass { */ 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, { - image: readFileSync(fileDownloaded), + image: readFileSync(filePath), 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 * @param {string} number * @param {string} message @@ -185,8 +223,7 @@ class BaileysProvider extends ProviderClass { */ sendAudio = async (number, audioUrl, voiceNote = false) => { - const numberClean = number.replace('+', '') - await this.vendor.sendMessage(`${numberClean}@c.us`, { + return this.vendor.sendMessage(number, { audio: { url: audioUrl }, ptt: voiceNote, }) @@ -210,17 +247,13 @@ class BaileysProvider extends ProviderClass { */ sendFile = async (number, filePath) => { - if (existsSync(filePath)) { - const mimeType = mime.lookup(filePath) - const numberClean = number.replace('+', '') - const fileName = filePath.split('/').pop() - - await this.vendor.sendMessage(`${numberClean}@c.us`, { - document: { url: filePath }, - mimetype: mimeType, - fileName: fileName, - }) - } + const mimeType = mime.lookup(filePath) + const fileName = filePath.split('/').pop() + return this.vendor.sendMessage(number, { + document: { url: filePath }, + mimetype: mimeType, + fileName: fileName, + }) } /** diff --git a/packages/provider/src/baileys/package.json b/packages/provider/src/baileys/package.json index da529d3..918a8e9 100644 --- a/packages/provider/src/baileys/package.json +++ b/packages/provider/src/baileys/package.json @@ -1,7 +1,6 @@ { "dependencies": { "@adiwajshing/baileys": "4.4.0", - "mime-types": "2.1.35", "wa-sticker-formatter": "4.3.2" } } diff --git a/packages/provider/src/baileys/utils.js b/packages/provider/src/baileys/utils.js index 433ddee..0b2a886 100644 --- a/packages/provider/src/baileys/utils.js +++ b/packages/provider/src/baileys/utils.js @@ -1,9 +1,6 @@ const { createWriteStream } = require('fs') const combineImage = require('combine-image') const qr = require('qr-image') -const { tmpdir } = require('os') -const http = require('http') -const https = require('https') const baileyCleanNumber = (number, full = false) => { number = number.replace('@s.whatsapp.net', '') @@ -41,38 +38,8 @@ const baileyIsValidNumber = (rawNumber) => { 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 = { baileyCleanNumber, baileyGenerateImage, baileyIsValidNumber, - baileyDownloadMedia, } diff --git a/packages/provider/src/venom/index.js b/packages/provider/src/venom/index.js index 4b7c231..3e1b630 100644 --- a/packages/provider/src/venom/index.js +++ b/packages/provider/src/venom/index.js @@ -2,18 +2,20 @@ const { ProviderClass } = require('@bot-whatsapp/bot') const venom = require('venom-bot') const { createWriteStream } = require('fs') const { Console } = require('console') +const mime = require('mime-types') const { venomCleanNumber, venomGenerateImage, venomisValidNumber, - venomDownloadMedia, } = require('./utils') const logger = new Console({ stdout: createWriteStream(`${process.cwd()}/venom.log`), }) +const { generalDownload } = require('../../common/download') + /** * ⚙️ VenomProvider: Es una clase tipo adaptor * 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"); } + /** + * 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 * @param {*} number @@ -141,10 +190,18 @@ class VenomProvider extends ProviderClass { * @param {*} message * @returns */ - sendMedia = async (number, mediaInput, message) => { - if (!mediaInput) throw new Error(`NO_SE_ENCONTRO: ${mediaInput}`) - const fileDownloaded = await venomDownloadMedia(mediaInput) - return this.vendor.sendImage(number, fileDownloaded, '.', message) + 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, text) + if (mimeType.includes('audio')) + return this.sendAudio(number, fileDownloaded) + + return this.sendFile(number, fileDownloaded, text) } /** diff --git a/packages/provider/src/web-whatsapp/index.js b/packages/provider/src/web-whatsapp/index.js index f7f8bd0..693d1b9 100644 --- a/packages/provider/src/web-whatsapp/index.js +++ b/packages/provider/src/web-whatsapp/index.js @@ -1,10 +1,9 @@ const { Client, LocalAuth, MessageMedia, Buttons } = require('whatsapp-web.js') const { ProviderClass } = require('@bot-whatsapp/bot') const { Console } = require('console') -const { createWriteStream } = require('fs') +const { createWriteStream, readFileSync } = require('fs') const { wwebCleanNumber, - wwebDownloadMedia, wwebGenerateImage, wwebIsValidNumber, } = require('./utils') @@ -13,6 +12,9 @@ const logger = new Console({ stdout: createWriteStream('./log'), }) +const { generalDownload } = require('../../common/download') +const mime = require('mime-types') + /** * ⚙️ WebWhatsappProvider: Es una clase tipo adaptor * 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', '--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 * https://docs.wwebjs.dev/Buttons.html @@ -170,6 +156,90 @@ class WebWhatsappProvider extends ProviderClass { 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 diff --git a/packages/provider/src/web-whatsapp/package.json b/packages/provider/src/web-whatsapp/package.json index d15dbe0..2fc0278 100644 --- a/packages/provider/src/web-whatsapp/package.json +++ b/packages/provider/src/web-whatsapp/package.json @@ -1,5 +1,5 @@ { "dependencies": { - "whatsapp-web.js": "1.19.2" + "whatsapp-web.js": "1.19.3" } } diff --git a/yarn.lock b/yarn.lock index cf802d5..9ab59a0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1113,6 +1113,7 @@ __metadata: fs-extra: ^11.1.0 git-cz: ^4.9.0 husky: ^8.0.2 + mime-types: ^2.1.35 only-allow: ^1.1.1 prettier: ^2.8.0 pretty-quick: ^3.1.3 @@ -13443,7 +13444,7 @@ __metadata: languageName: node 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 resolution: "mime-types@npm:2.1.35" dependencies: