diff --git a/.vscode/settings.json b/.vscode/settings.json index 977ac5f..ffa60fd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,6 +7,7 @@ "provider", "adapter", "ci", - "starters" + "starters", + "conflict" ] } diff --git a/packages/bot/package.json b/packages/bot/package.json index 88a782b..ebd2d4b 100644 --- a/packages/bot/package.json +++ b/packages/bot/package.json @@ -1,6 +1,6 @@ { "name": "@bot-whatsapp/bot", - "version": "0.0.21-alpha.0", + "version": "0.0.22-alpha.0", "description": "", "main": "./lib/bundle.bot.cjs", "scripts": { diff --git a/packages/cli/interactive/index.js b/packages/cli/interactive/index.js index 7da0029..f7360ce 100644 --- a/packages/cli/interactive/index.js +++ b/packages/cli/interactive/index.js @@ -33,10 +33,10 @@ const startInteractive = async () => { message: '¿Cuál proveedor de whatsapp quieres utilizar?', choices: [ { title: 'whatsapp-web.js (gratis)', value: 'wweb' }, - { title: 'Twilio', value: 'twilio' }, { title: 'Venom (gratis)', value: 'venom' }, { title: 'Baileys (gratis)', value: 'bailey' }, - { title: 'API Oficial (Meta)', value: 'meta', disabled: true }, + { title: 'Twilio', value: 'twilio' }, + { title: 'API Oficial (Meta)', value: 'meta' }, ], max: 1, hint: 'Espacio para seleccionar', diff --git a/packages/cli/package.json b/packages/cli/package.json index 50aa41f..6030446 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@bot-whatsapp/cli", - "version": "0.0.28-alpha.0", + "version": "0.0.29-alpha.0", "description": "", "main": "index.js", "devDependencies": { diff --git a/packages/create-bot-whatsapp/package.json b/packages/create-bot-whatsapp/package.json index 9935c59..5de6a2b 100644 --- a/packages/create-bot-whatsapp/package.json +++ b/packages/create-bot-whatsapp/package.json @@ -1,6 +1,6 @@ { "name": "create-bot-whatsapp", - "version": "0.0.39-alpha.0", + "version": "0.0.40-alpha.0", "description": "", "main": "./lib/bundle.create-bot-whatsapp.cjs", "files": [ diff --git a/packages/database/package.json b/packages/database/package.json index 6dd7def..571d809 100644 --- a/packages/database/package.json +++ b/packages/database/package.json @@ -1,6 +1,6 @@ { "name": "@bot-whatsapp/database", - "version": "0.0.20-alpha.0", + "version": "0.0.21-alpha.0", "description": "Esto es el conector a mysql, pg, mongo", "main": "./lib/mock/index.cjs", "keywords": [], diff --git a/packages/provider/package.json b/packages/provider/package.json index 3262cdc..fbfd616 100644 --- a/packages/provider/package.json +++ b/packages/provider/package.json @@ -1,6 +1,6 @@ { "name": "@bot-whatsapp/provider", - "version": "0.0.26-alpha.0", + "version": "0.0.27-alpha.0", "description": "Esto es el conector a Twilio, Meta, etc...", "main": "./lib/mock/index.cjs", "keywords": [], @@ -18,6 +18,7 @@ "./twilio": "./lib/twilio/index.cjs", "./web-whatsapp": "./lib/web-whatsapp/index.cjs", "./venom": "./lib/venom/index.cjs", - "./baileys": "./lib/baileys/index.cjs" + "./baileys": "./lib/baileys/index.cjs", + "./meta": "./lib/meta/index.cjs" } } diff --git a/packages/provider/rollup-provider.config.js b/packages/provider/rollup-provider.config.js index 03aa368..73cfb4b 100644 --- a/packages/provider/rollup-provider.config.js +++ b/packages/provider/rollup-provider.config.js @@ -48,4 +48,13 @@ module.exports = [ }, plugins: [commonjs()], }, + { + input: join(__dirname, 'src', 'meta', 'index.js'), + output: { + banner: banner['banner.output'].join(''), + file: join(__dirname, 'lib', 'meta', 'index.cjs'), + format: 'cjs', + }, + plugins: [commonjs()], + }, ] diff --git a/packages/provider/src/baileys/index.js b/packages/provider/src/baileys/index.js index e985e4a..8bd860e 100644 --- a/packages/provider/src/baileys/index.js +++ b/packages/provider/src/baileys/index.js @@ -1,5 +1,7 @@ const { ProviderClass } = require('@bot-whatsapp/bot') const pino = require('pino') +const mime = require('mime-types') +const fs = require('fs') const { default: makeWASocket, useMultiFileAuthState, @@ -166,6 +168,49 @@ class BaileysProvider extends ProviderClass { if (options?.media) return this.sendMedia(number, options.media) return this.sendText(number, message) } + + /** + * + * @param {string} number + * @param {string} filePath + * @example await sendMessage('+XXXXXXXXXXX', './document/file.pdf') + */ + + sendFile = async (number, filePath) => { + if (fs.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, + }) + } + } + + /** + * + * @param {string} number + * @param {string} text + * @param {string} footer + * @param {Array} buttons + * @example await sendMessage("+XXXXXXXXXXX", "Your Text", "Your Footer", [{"buttonId": "id", "buttonText": {"displayText": "Button"}, "type": 1}]) + */ + + sendButtons = async (number, text, footer, buttons) => { + const numberClean = number.replace('+', '') + + const buttonMessage = { + text: text, + footer: footer, + buttons: buttons, + headerType: 1, + } + + await this.vendor.sendMessage(`${numberClean}@c.us`, buttonMessage) + } } module.exports = BaileysProvider diff --git a/packages/provider/src/meta/index.js b/packages/provider/src/meta/index.js new file mode 100644 index 0000000..f6b9ae6 --- /dev/null +++ b/packages/provider/src/meta/index.js @@ -0,0 +1,115 @@ +const { ProviderClass } = require('@bot-whatsapp/bot') +const axios = require('axios') +const MetaWebHookServer = require('./server') +const URL = `https://graph.facebook.com/v15.0` + +/** + * ⚙️MetaProvider: Es un provedor que te ofrece enviar + * mensaje a Whatsapp via API + * info: https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages + * + * + * Necesitas las siguientes tokens y valores + * { token, numberId, vendorNumber, verify_token } + */ + +class MetaProvider extends ProviderClass { + metHook + token + numberId + constructor({ token, numberId, verifyToken }, _port = 3000) { + super() + this.token = token + this.numberId = numberId + this.metHook = new MetaWebHookServer(verifyToken, _port) + this.metHook.start() + + const listEvents = this.busEvents() + + for (const { event, func } of listEvents) { + this.metHook.on(event, func) + } + } + + /** + * Mapeamos los eventos nativos a los que la clase Provider espera + * para tener un standar de eventos + * @returns + */ + busEvents = () => [ + { + event: 'auth_failure', + func: (payload) => this.emit('error', payload), + }, + { + event: 'ready', + func: () => this.emit('ready', true), + }, + { + event: 'message', + func: (payload) => { + this.emit('message', payload) + }, + }, + ] + + sendMessageMeta = async (body) => { + try { + const response = await axios.post( + `${URL}/${this.numberId}/messages`, + body, + { + headers: { + Authorization: `Bearer ${this.token}`, + }, + } + ) + return response.data + } catch (error) { + return Promise.resolve(error) + } + } + + sendtext = async (number, message) => { + const body = { + messaging_product: 'whatsapp', + to: number, + type: 'text', + text: { + preview_url: false, + body: message, + }, + } + await this.sendMessageMeta(body) + } + + sendMedia = async (number, _, mediaInput = null) => { + if (!mediaInput) throw new Error(`MEDIA_INPUT_NULL_: ${mediaInput}`) + const body = { + messaging_product: 'whatsapp', + to: number, + type: 'image', + image: { + link: mediaInput, + }, + } + await this.sendMessageMeta(body) + } + + /** + * + * @param {*} userId + * @param {*} message + * @param {*} param2 + * @returns + */ + sendMessage = async (number, message, { options }) => { + if (options?.buttons?.length) return console.log('Envio de botones') + if (options?.media) + return this.sendMedia(number, message, options.media) + + this.sendtext(number, message) + } +} + +module.exports = MetaProvider diff --git a/packages/provider/src/meta/server.js b/packages/provider/src/meta/server.js new file mode 100644 index 0000000..91ae8bc --- /dev/null +++ b/packages/provider/src/meta/server.js @@ -0,0 +1,63 @@ +const { EventEmitter } = require('node:events') +const polka = require('polka') +const { urlencoded } = require('body-parser') + +class MetaWebHookServer extends EventEmitter { + metaServer + metaPort + verifyToken + constructor(_verifyToken, _metaPort) { + super() + this.metaServer = this.buildHTTPServer() + this.metaPort = _metaPort + this.verifyToken = _verifyToken + } + + /** + * Mensaje entrante + * emit: 'message' + * @param {*} req + * @param {*} res + */ + incomingMsg = (req, res) => { + const { body } = req + const message = body.entry[0].changes[0].value.messages[0] + const to = body.entry[0].changes[0].value.metadata.display_phone_number + this.emit('message', { + from: message.from, + to, + body: message.text.body, + }) + const json = JSON.stringify({ body }) + res.end(json) + } + + /** + * Contruir HTTP Server + * @returns + */ + buildHTTPServer = () => { + return polka() + .use(urlencoded({ extended: true })) + .post('/meta-hook', this.incomingMsg) + } + + /** + * Puerto del HTTP + * @param {*} port default 3000 + */ + start = () => { + this.metaServer.listen(this.metaPort, () => { + console.log(``) + console.log(`[meta]: Agregar esta url "WHEN A MESSAGE COMES IN"`) + console.log( + `[meta]: POST http://localhost:${this.metaPort}/meta-hook` + ) + console.log(`[meta]: Más información en la documentacion`) + console.log(``) + }) + this.emit('ready') + } +} + +module.exports = MetaWebHookServer diff --git a/starters/apps/base-bailey-memory/package.json b/starters/apps/base-bailey-memory/package.json index 1efeb95..ef0e2b3 100644 --- a/starters/apps/base-bailey-memory/package.json +++ b/starters/apps/base-bailey-memory/package.json @@ -10,6 +10,7 @@ "keywords": [], "dependencies": { "@adiwajshing/baileys": "^4.4.0", + "mime-types": "^2.1.35", "@bot-whatsapp/bot": "latest", "@bot-whatsapp/cli": "latest", "@bot-whatsapp/database": "latest", diff --git a/starters/apps/base-bailey-mongo/package.json b/starters/apps/base-bailey-mongo/package.json index d2ffa4a..ad14f04 100644 --- a/starters/apps/base-bailey-mongo/package.json +++ b/starters/apps/base-bailey-mongo/package.json @@ -10,6 +10,7 @@ "keywords": [], "dependencies": { "@adiwajshing/baileys": "^4.4.0", + "mime-types": "^2.1.35", "@bot-whatsapp/bot": "latest", "@bot-whatsapp/cli": "latest", "@bot-whatsapp/database": "latest", diff --git a/starters/apps/base-bailey-mysql/package.json b/starters/apps/base-bailey-mysql/package.json index a52fd19..c8c66de 100644 --- a/starters/apps/base-bailey-mysql/package.json +++ b/starters/apps/base-bailey-mysql/package.json @@ -10,6 +10,7 @@ "keywords": [], "dependencies": { "@adiwajshing/baileys": "^4.4.0", + "mime-types": "^2.1.35", "@bot-whatsapp/bot": "latest", "@bot-whatsapp/cli": "latest", "@bot-whatsapp/database": "latest", diff --git a/starters/apps/base-meta-memory/README.md b/starters/apps/base-meta-memory/README.md new file mode 100644 index 0000000..f8e0e90 --- /dev/null +++ b/starters/apps/base-meta-memory/README.md @@ -0,0 +1,12 @@ +### BASE APP + +Este bot contiene un flujo basico en el cual una persona (cliente) escribe **"hola"** y el bot responde. +- Bienvenido a mi tienda +- Como puedo ayudarte? +- Tengo: Zapatos Bolsos etc.. + +------ +- [Discord](https://link.codigoencasa.com/DISCORD) +- [Twitter](https://twitter.com/leifermendez) +- [Youtube](https://www.youtube.com/watch?v=5lEMCeWEJ8o&list=PL_WGMLcL4jzWPhdhcUyhbFU6bC0oJd2BR) +- [Telegram](https://t.me/leifermendez) diff --git a/starters/apps/base-meta-memory/app.js b/starters/apps/base-meta-memory/app.js new file mode 100644 index 0000000..6f932f0 --- /dev/null +++ b/starters/apps/base-meta-memory/app.js @@ -0,0 +1,88 @@ +const { + createBot, + createProvider, + createFlow, + addKeyword, + addChild, +} = require('@bot-whatsapp/bot') + +const TwilioProvider = require('@bot-whatsapp/provider/twilio') +const MockAdapter = require('@bot-whatsapp/database/mock') + +/** + * Declarando flujo hijo + */ + +const flowBolsos2 = addKeyword(['bolsos2', '2']) + .addAnswer('🤯 *MUCHOS* bolsos ...') + .addAnswer('y mas bolsos... bla bla') + +const flowZapatos2 = addKeyword(['zapatos2', '2']) + .addAnswer('🤯 repito que tengo *MUCHOS* zapatos.') + .addAnswer('y algunas otras cosas.') + +const flowZapatos = addKeyword(['zapatos', 'ZAPATOS']) + .addAnswer('🤯 Veo que elegiste zapatos') + .addAnswer('Tengo muchos zapatos...bla bla') + .addAnswer( + ['Manda:', '*2*', 'o', '*zapatos2*', 'para mas información'], + { capture: true }, + (ctx) => { + console.log('Aqui puedes ver más info del usuario...') + console.log('Puedes enviar un mail, hook, etc..') + console.log(ctx) + }, + [...addChild(flowZapatos2)] + ) + +const flowBolsos = addKeyword(['bolsos', 'BOLSOS']) + .addAnswer('🙌 Veo que elegiste bolsos') + .addAnswer('Tengo muchos bolsos...bla bla') + .addAnswer( + ['Manda:', '*2*', 'o', '*bolsos2*', 'para mas información.'], + { capture: true }, + (ctx) => { + console.log('Aqui puedes ver más info del usuario...') + console.log('Puedes enviar un mail, hook, etc..') + console.log(ctx) + }, + [...addChild(flowBolsos2)] + ) + +/** + * Declarando flujo principal + */ +const flowPrincipal = addKeyword(['hola', 'ole', 'alo']) + .addAnswer('Hola, bienvenido a mi tienda') + .addAnswer('Como puedo ayudarte?') + .addAnswer(['Tengo:', 'Zapatos', 'Bolsos', 'etc..']) + .addAnswer( + ['Para continuar escribe:', '*Zapatos*', 'o', '*Bolsos*'], + { capture: true }, + (ctx) => { + console.log('Aqui puedes ver más info del usuario...') + console.log('Puedes enviar un mail, hook, etc..') + console.log(ctx) + console.log(ctx['_data']['notifyName']) + }, + [...addChild(flowBolsos), ...addChild(flowZapatos)] + ) + +const main = async () => { + const adapterDB = new MockAdapter() + const adapterFlow = createFlow([flowPrincipal]) + + const adapterProvider = createProvider(TwilioProvider, { + accountSid: 'YOUR_ACCOUNT_SID', + authToken: 'YOUR_ACCOUNT_TOKEN', + vendorNumber: '+14155238886', + }) + + createBot({ + flow: adapterFlow, + provider: adapterProvider, + database: adapterDB, + }) +} + +main() diff --git a/starters/apps/base-meta-memory/package.json b/starters/apps/base-meta-memory/package.json new file mode 100644 index 0000000..d0af65c --- /dev/null +++ b/starters/apps/base-meta-memory/package.json @@ -0,0 +1,22 @@ +{ + "name": "bot-whatsapp-base-meta-memory", + "version": "1.0.0", + "description": "", + "main": "app.js", + "scripts": { + "pre-copy": "cd .. && yarn run copy.lib base-meta-memory", + "start": "node app.js" + }, + "keywords": [], + "dependencies": { + "body-parser": "^1.20.1", + "polka": "^0.5.2", + "twilio": "^3.83.4", + "@bot-whatsapp/bot": "latest", + "@bot-whatsapp/cli": "latest", + "@bot-whatsapp/database": "latest", + "@bot-whatsapp/provider": "latest" + }, + "author": "", + "license": "ISC" +}