Skip to content

Commit

Permalink
Merge pull request #800 from codigoencasa/dev
Browse files Browse the repository at this point in the history
0.1.30
  • Loading branch information
leifermendez authored Aug 7, 2023
2 parents 5601d2e + 5dce017 commit 10d680d
Show file tree
Hide file tree
Showing 16 changed files with 208 additions and 31 deletions.
19 changes: 9 additions & 10 deletions __test__/0.1.6-case.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ suiteCase.before.each(setup)
suiteCase.after.each(clear)

suiteCase(`Debe retornar un mensaje resumen`, async ({ database, provider }) => {
let STATE_APP = {}
const MOCK_VALUES = ['¿Cual es tu nombre?', '¿Cual es tu edad?', 'Tu datos son:']

const flujoPrincipal = addKeyword(['hola'])
Expand All @@ -18,9 +17,8 @@ suiteCase(`Debe retornar un mensaje resumen`, async ({ database, provider }) =>
{
capture: true,
},
async (ctx, { flowDynamic }) => {
STATE_APP[ctx.from] = { ...STATE_APP[ctx.from], name: ctx.body }

async (ctx, { flowDynamic, state }) => {
state.update({ name: ctx.body })
flowDynamic('Gracias por tu nombre!')
}
)
Expand All @@ -29,14 +27,15 @@ suiteCase(`Debe retornar un mensaje resumen`, async ({ database, provider }) =>
{
capture: true,
},
async (ctx, { flowDynamic }) => {
STATE_APP[ctx.from] = { ...STATE_APP[ctx.from], age: ctx.body }

await flowDynamic(`Gracias por tu edad! ${STATE_APP[ctx.from].name}`)
async (ctx, { flowDynamic, state }) => {
state.update({ age: ctx.body })
const myState = state.getMyState()
await flowDynamic(`Gracias por tu edad! ${myState.name}`)
}
)
.addAnswer(MOCK_VALUES[2], null, async (ctx, { flowDynamic }) => {
flowDynamic(`Nombre: ${STATE_APP[ctx.from].name} Edad: ${STATE_APP[ctx.from].age}`)
.addAnswer(MOCK_VALUES[2], null, async (_, { flowDynamic, state }) => {
const myState = state.getMyState()
flowDynamic(`Nombre: ${myState.name} Edad: ${myState.age}`)
})
.addAnswer('🤖🤖 Gracias por tu participacion')

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@bot-whatsapp/root",
"version": "0.1.28",
"version": "0.1.29",
"description": "Bot de wahtsapp open source para MVP o pequeños negocios",
"main": "app.js",
"private": true,
Expand Down
39 changes: 39 additions & 0 deletions packages/bot/context/state.class.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
class GlobalState {
STATE = new Map()
constructor() {}

/**
*
* @param {*} ctx
* @returns
*/
updateState = (ctx = {}) => {
const currentStateByFrom = this.STATE.get(ctx.from)
return (keyValue) => this.STATE.set(ctx.from, { ...currentStateByFrom, ...keyValue })
}

/**
*
* @returns
*/
getMyState = (from) => {
return () => this.STATE.get(from)
}

/**
*
* @returns
*/
getAllState = () => this.STATE.values()

/**
*
* @param {*} from
* @returns
*/
clear = (from) => {
return () => this.STATE.delete(from)
}
}

module.exports = GlobalState
11 changes: 11 additions & 0 deletions packages/bot/core/core.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ const Queue = require('../utils/queue')
const { Console } = require('console')
const { createWriteStream } = require('fs')
const { LIST_REGEX } = require('../io/events')
const GlobalState = require('../context/state.class')

const logger = new Console({
stdout: createWriteStream(`${process.cwd()}/core.class.log`),
})

const QueuePrincipal = new Queue()
const StateHandler = new GlobalState()

/**
* [ ] Escuchar eventos del provider asegurarte que los provider emitan eventos
Expand Down Expand Up @@ -91,6 +93,14 @@ class CoreClass {
await this.databaseClass.save(ctxByNumber)
}

// 📄 Mantener estado de conversacion por numero
const state = {
getMyState: StateHandler.getMyState(messageCtxInComming.from),
getAllState: StateHandler.getAllState,
update: StateHandler.updateState(messageCtxInComming),
clear: StateHandler.clear(messageCtxInComming.from),
}

// 📄 Crar CTX de mensaje (uso private)
const createCtxMessage = (payload = {}, index = 0) => {
const body = typeof payload === 'string' ? payload : payload?.body ?? payload?.answer
Expand Down Expand Up @@ -232,6 +242,7 @@ class CoreClass {

const argsCb = {
provider,
state,
fallBack: fallBack(flags),
flowDynamic: flowDynamic(flags),
endFlow: endFlow(flags),
Expand Down
7 changes: 7 additions & 0 deletions packages/bot/io/events/eventAction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const { generateRef } = require('../../utils/hash')

const eventAction = () => {
return generateRef('_event_action_')
}

module.exports = { eventAction }
2 changes: 2 additions & 0 deletions packages/bot/io/events/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ const { eventLocation, REGEX_EVENT_LOCATION } = require('./eventLocation')
const { eventMedia, REGEX_EVENT_MEDIA } = require('./eventMedia')
const { eventVoiceNote, REGEX_EVENT_VOICE_NOTE } = require('./eventVoiceNote')
const { eventWelcome } = require('./eventWelcome')
const { eventAction } = require('./eventAction')

const LIST_ALL = {
WELCOME: eventWelcome(),
MEDIA: eventMedia(),
LOCATION: eventLocation(),
DOCUMENT: eventDocument(),
VOICE_NOTE: eventVoiceNote(),
ACTION: eventAction(),
}

const LIST_REGEX = {
Expand Down
6 changes: 4 additions & 2 deletions packages/bot/io/flow.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ class FlowClass {
overFlow = overFlow ?? this.flowSerialize

const mapSensitive = (str, mapOptions = { sensitive: false, regex: false }) => {
if (mapOptions.regex) return new RegExp(str)
const regexSensitive = mapOptions.sensitive ? 'g' : 'gi'

if (mapOptions.regex) return new Function(`return ${str}`)();
const regexSensitive = mapOptions.sensitive ? 'g' : 'i'

if (Array.isArray(str)) {
const patterns = mapOptions.sensitive ? str.map((item) => `\\b${item}\\b`) : str
return new RegExp(patterns.join('|'), regexSensitive)
Expand Down
2 changes: 1 addition & 1 deletion packages/docs/src/routes/docs/install/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Cada plantilla tiene sus dependencias necesarias basadas en tu previa selección
"@bot-whatsapp/cli": "latest",
"@bot-whatsapp/database": "latest",
"@bot-whatsapp/provider": "latest",
"@adiwajshing/baileys": "github:WhiskeySockets/Baileys",
"@whiskeysockets/baileys": "^6.4.0",
"mysql2": "^2.3.3", 👈
},
```
Expand Down
2 changes: 1 addition & 1 deletion packages/docs/src/routes/docs/providers/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Los proveedores disponibles hasta el momento son los siguientes:

[Meta Official](https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages) `require('@bot-whatsapp/provider/meta')`

[Twilio Official](https://www.twilio.com/es-mx/messaging/whatsapp) `require('@bot-whatsapp/provider/twilio')`
[Twilio Official](https://www.twilio.com/es-mx/messaging/channels/whatsapp) `require('@bot-whatsapp/provider/twilio')`

---

Expand Down
130 changes: 124 additions & 6 deletions packages/provider/src/baileys/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,19 @@ const pino = require('pino')
const rimraf = require('rimraf')
const mime = require('mime-types')
const { join } = require('path')
const { createWriteStream, readFileSync } = require('fs')
const { createWriteStream, readFileSync, existsSync } = require('fs')
const { Console } = require('console')

const { default: makeWASocket, useMultiFileAuthState, Browsers, DisconnectReason } = require('@adiwajshing/baileys')
const {
default: makeWASocket,
useMultiFileAuthState,
Browsers,
DisconnectReason,
proto,
makeInMemoryStore,
makeCacheableSignalKeyStore,
getAggregateVotesInPollMessage,
} = require('@whiskeysockets/baileys')

const { baileyGenerateImage, baileyCleanNumber, baileyIsValidNumber } = require('./utils')

Expand All @@ -25,11 +34,13 @@ const logger = new Console({
* https://github.com/whiskeysockets/Baileys
*/
class BaileysProvider extends ProviderClass {
globalVendorArgs = { name: `bot`, gifPlayback: false }
globalVendorArgs = { name: `bot`, gifPlayback: false, usePairingCode: false, phoneNumber: null }
vendor
store
saveCredsGlobal = null
constructor(args) {
super()
this.store = null
this.globalVendorArgs = { ...this.globalVendorArgs, ...args }
this.initBailey().then()
}
Expand All @@ -40,17 +51,57 @@ class BaileysProvider extends ProviderClass {
initBailey = async () => {
const NAME_DIR_SESSION = `${this.globalVendorArgs.name}_sessions`
const { state, saveCreds } = await useMultiFileAuthState(NAME_DIR_SESSION)
const loggerBaileys = pino({ level: 'fatal' })

this.saveCredsGlobal = saveCreds

this.store = makeInMemoryStore({ loggerBaileys })
this.store.readFromFile(`${NAME_DIR_SESSION}/baileys_store.json`)
setInterval(() => {
const path = `${this.NAME_DIR_SESSION}/baileys_store.json`
if (existsSync(path)) {
this.store.writeToFile(path)
}
}, 10_000)

try {
const sock = makeWASocket({
logger: loggerBaileys,
printQRInTerminal: false,
auth: state,
auth: {
creds: state.creds,
keys: makeCacheableSignalKeyStore(state.keys, loggerBaileys),
},
browser: Browsers.macOS('Desktop'),
syncFullHistory: false,
logger: pino({ level: 'fatal' }),
generateHighQualityLinkPreview: true,
getMessage: this.getMessage,
})

this.store?.bind(sock.ev)

if (this.globalVendorArgs.usePairingCode && !sock.authState.creds.registered) {
if (this.globalVendorArgs.phoneNumber) {
await sock.waitForConnectionUpdate((update) => !!update.qr)
const code = await sock.requestPairingCode(this.globalVendorArgs.phoneNumber)
this.emit('require_action', {
instructions: [
`Acepta la notificación del WhatsApp ${this.globalVendorArgs.phoneNumber} en tu celular 👌`,
`El token para la vinculación es: ${code}`,
`Necesitas ayuda: https://link.codigoencasa.com/DISCORD`,
],
})
} else {
this.emit('auth_failure', [
`No se ha definido el numero de telefono agregalo`,
`Reinicia el BOT`,
`Tambien puedes mirar un log que se ha creado baileys.log`,
`Necesitas ayuda: https://link.codigoencasa.com/DISCORD`,
`(Puedes abrir un ISSUE) https://github.com/codigoencasa/bot-whatsapp/issues/new/choose`,
])
}
}

sock.ev.on('connection.update', async (update) => {
const { connection, lastDisconnect, qr } = update

Expand Down Expand Up @@ -79,7 +130,7 @@ class BaileysProvider extends ProviderClass {
}

/** QR Code */
if (qr) {
if (qr && !this.globalVendorArgs.usePairingCode) {
this.emit('require_action', {
instructions: [
`Debes escanear el QR Code 👌 ${this.globalVendorArgs.name}.qr.png`,
Expand Down Expand Up @@ -165,6 +216,41 @@ class BaileysProvider extends ProviderClass {
this.emit('message', payload)
},
},
{
event: 'messages.update',
func: async (message) => {
for (const { key, update } of message) {
if (update.pollUpdates) {
const pollCreation = await this.getMessage(key)
if (pollCreation) {
const pollMessage = await getAggregateVotesInPollMessage({
message: pollCreation,
pollUpdates: update.pollUpdates,
})
const [messageCtx] = message

const messageOriginalKey = messageCtx?.update?.pollUpdates[0]?.pollUpdateMessageKey
const messageOriginal = await this.store.loadMessage(
messageOriginalKey.remoteJid,
messageOriginalKey.id
)

let payload = {
...messageCtx,
body: pollMessage.find((poll) => poll.voters.length > 0)?.name || '',
from: baileyCleanNumber(key.remoteJid),
pushName: messageOriginal?.pushName,
broadcast: messageOriginal?.broadcast,
messageTimestamp: messageOriginal?.messageTimestamp,
voters: pollCreation,
type: 'poll',
}
this.emit('message', payload)
}
}
}
},
},
]

initBusEvents = (_sock) => {
Expand All @@ -176,6 +262,15 @@ class BaileysProvider extends ProviderClass {
}
}

getMessage = async (key) => {
if (this.store) {
const msg = await this.store.loadMessage(key.remoteJid, key.id)
return msg?.message || undefined
}
// only if store is present
return proto.Message.fromObject({})
}

/**
* Funcion SendRaw envia opciones directamente del proveedor
* @example await sendMessage('+XXXXXXXXXXX', 'Hello World')
Expand Down Expand Up @@ -302,6 +397,29 @@ class BaileysProvider extends ProviderClass {
return this.vendor.sendMessage(numberClean, buttonMessage)
}

/**
*
* @param {string} number
* @param {string} text
* @param {string} footer
* @param {Array} poll
* @example await sendMessage("+XXXXXXXXXXX", { poll: { "name": "You accept terms", "values": [ "Yes", "Not"], "selectableCount": 1 })
*/

sendPoll = async (numberIn, text, poll) => {
const numberClean = baileyCleanNumber(numberIn)

if (poll.options.length < 2) return false

const pollMessage = {
name: text,
values: poll.options,
selectableCount: poll?.multiselect === undefined ? 1 : poll?.multiselect ? 1 : 0,
}

return this.vendor.sendMessage(numberClean, { poll: pollMessage })
}

/**
* TODO: Necesita terminar de implementar el sendMedia y sendButton guiarse:
* https://github.com/leifermendez/bot-whatsapp/blob/4e0fcbd8347f8a430adb43351b5415098a5d10df/packages/provider/src/web-whatsapp/index.js#L165
Expand Down
3 changes: 1 addition & 2 deletions packages/provider/src/baileys/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"dependencies": {
"@adiwajshing/baileys": "github:WhiskeySockets/Baileys",
"@adiwajshing/keyed-db": "^0.2.4",
"@whiskeysockets/baileys": "^6.4.0",
"wa-sticker-formatter": "4.3.2"
}
}
4 changes: 4 additions & 0 deletions packages/provider/src/wppconnect/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ class WPPConnectProviderClass extends ProviderClass {
})
WppConnectGenerateImage(base64Qrimg, `${this.globalVendorArgs.name}.qr.png`)
},
puppeteerOptions: {
headless: true,
args: ['--no-sandbox'],
},
})

this.vendor = session
Expand Down
Loading

0 comments on commit 10d680d

Please sign in to comment.