From 8007d90652563ea62ce0103bb9befe41a97218a0 Mon Sep 17 00:00:00 2001 From: Jeanluca Date: Wed, 10 Jul 2024 18:59:31 -0300 Subject: [PATCH 1/3] crud ok, implementacao logica --- README.md | 3 + package.json | 2 +- src/api/class/instance.js | 35 +++- src/api/class/session.js | 6 + src/api/class/typebot.js | 228 +++++++++++++++++++++ src/api/controllers/instance.controller.js | 31 ++- src/api/helper/mongoAuthState.js | 1 + src/api/routes/instance.route.js | 1 + 8 files changed, 298 insertions(+), 9 deletions(-) create mode 100644 src/api/class/typebot.js diff --git a/README.md b/README.md index f747ea932..c8329a46b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +# Maintained by Jeanlucafp +This API is a forked from @salman0ansari and maintained by me. + # Archive Notice 🔒 After three years, I've decided to archive this open-source WhatsApp API project. Your support and contributions have been incredible! diff --git a/package.json b/package.json index 06d4ecd9d..91f3cd1cf 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "husky": "^8.0.2", "lint-staged": "^13.0.4", "mocha": "^10.1.0", - "nodemon": "^2.0.20", + "nodemon": "^3.1.4", "prettier": "^2.8.0", "supertest": "^6.3.1" } diff --git a/src/api/class/instance.js b/src/api/class/instance.js index e887df1d6..ef77fef21 100644 --- a/src/api/class/instance.js +++ b/src/api/class/instance.js @@ -6,9 +6,7 @@ const { DisconnectReason, PHONENUMBER_MCC } = require('@whiskeysockets/baileys') -const { unlinkSync } = require('fs') const { v4: uuidv4 } = require('uuid') -const path = require('path') const processButton = require('../helper/processbtn') const generateVC = require('../helper/genVc') const Chat = require('../models/chat.model') @@ -19,6 +17,7 @@ const logger = require('pino')() const useMongoDBAuthState = require('../helper/mongoAuthState') const { AuditMessages } = require('./audit') const libPhonenumber = require('libphonenumber-js') +const TypeBot = require('./typebot') class WhatsAppInstance { socketConfig = { @@ -41,6 +40,7 @@ class WhatsAppInstance { messages: [], qrRetry: 0, customWebhook: '', + typebot: null } axiosInstance = axios.create({ @@ -75,8 +75,8 @@ class WhatsAppInstance { async init() { this.collection = mongoClient.db('whatsapp-api').collection(this.key) - const { state, saveCreds } = await useMongoDBAuthState(this.collection) - this.authState = { state: state, saveCreds: saveCreds } + const { state, saveCreds, writeData } = await useMongoDBAuthState(this.collection) + this.authState = { state: state, saveCreds: saveCreds, writeData } this.socketConfig.auth = this.authState.state this.socketConfig.browser = Object.values(config.browser) this.instance.sock = makeWASocket(this.socketConfig) @@ -84,6 +84,13 @@ class WhatsAppInstance { return this } + async activeTypeBot(apiHost, typebotname, saveData = true) { + const typebot = new TypeBot(apiHost, typebotname, this) + this.instance.typebot = typebot + if (saveData) + await this.authState.writeData(typebot.getObjectToSave(), 'typebot') + } + setHandler() { const sock = this.instance.sock // on credentials update save state @@ -306,6 +313,22 @@ class WhatsAppInstance { ) ) await this.SendWebhook('message', webhookData, this.key) + + if (this.instance.typebot) { + if (messageType === 'extendedTextMessage') { + await this.instance.typebot.startTypebot({ + message: msg.message.extendedTextMessage.text, + remoteJid: msg.key.remoteJid + }) + } + + if (messageType === 'templateButtonReplyMessage') { + await this.instance.typebot.startTypebot({ + message: TypeBot.giveMeTextButtonAndIGiveUId(msg.message.templateButtonReplyMessage.selectedDisplayText), + remoteJid: msg.key.remoteJid + }) + } + } }) }) @@ -435,6 +458,10 @@ class WhatsAppInstance { phone_connected: this.instance?.online, webhookUrl: this.instance.customWebhook, user: this.instance?.online ? this.instance.sock?.user : {}, + typebot: this.instance?.typebot ? { + apiHost: this.instance.typebot.apiHost, + typebotName: this.instance.typebot.typebotName, + } : {}, } } diff --git a/src/api/class/session.js b/src/api/class/session.js index 9e93fe140..1d7a16eee 100644 --- a/src/api/class/session.js +++ b/src/api/class/session.js @@ -31,6 +31,12 @@ class Session { webhook, webhookUrl ) + + const typebotItem = result.find(items => items._id === 'typebot') + if (typebotItem) { + await instance.activeTypeBot(typebotItem.apiHost, typebotItem.typebotName, false) + } + await instance.init() WhatsAppInstances[key] = instance }) diff --git a/src/api/class/typebot.js b/src/api/class/typebot.js new file mode 100644 index 000000000..9ba9d42ac --- /dev/null +++ b/src/api/class/typebot.js @@ -0,0 +1,228 @@ +const { default: axios } = require("axios"); +const logger = require('pino')() + +const mapButtonsAndIds = [] + +function giveMeTextButtonAndIGiveUId(textButton) { + return mapButtonsAndIds.find((item) => item.formattedText === textButton)?.id +} + +function addMapButton(id, formattedText) { + const finded = mapButtonsAndIds.find((item) => item.formattedText === formattedText) + if (finded) return + mapButtonsAndIds.push({ id, formattedText }) +} + +function applyFormatting(element) { + let text = ''; + + if (element.text) { + text += element.text; + } + + if (element.children && element.type !== 'a') { + for (const child of element.children) { + text += applyFormatting(child); + } + } + + if (element.type === 'p' && element.type !== 'inline-variable') { + text = text.trim() + '\n'; + } + + if (element.type === 'inline-variable') { + text = text.trim(); + } + + if (element.type === 'ol') { + text = + '\n' + + text + .split('\n') + .map((line, index) => (line ? `${index + 1}. ${line}` : '')) + .join('\n'); + } + + if (element.type === 'li') { + text = text + .split('\n') + .map((line) => (line ? ` ${line}` : '')) + .join('\n'); + } + + let formats = ''; + + if (element.bold) { + formats += '*'; + } + + if (element.italic) { + formats += '_'; + } + + if (element.underline) { + formats += '~'; + } + + let formattedText = `${formats}${text}${formats.split('').reverse().join('')}`; + + if (element.url) { + formattedText = element.children[0]?.text ? `[${formattedText}]\n(${element.url})` : `${element.url}`; + } + + return formattedText; +} + +class TypeBot { + + constructor(apiHost, typebotName, parent) { + this.apiHost = apiHost; + this.typebotName = typebotName; + this.sessions = []; + this.parent = parent; + } + + static giveMeTextButtonAndIGiveUId(textButton) { + return giveMeTextButtonAndIGiveUId(textButton) + } + + getObjectToSave() { + return { + apiHost: this.apiHost, + typebotName: this.typebotName, + } + } + + async startTypebot(payload) { + const { message, remoteJid } = payload + if (remoteJid === 'status@broadcast') return; + + const session = this.findSession(remoteJid) + if (!session) { + await this.createNewSession(remoteJid) + } else { + await this.continueChat(session, message) + } + } + + findSession(remoteJid) { + return this.sessions.find((session) => session.remoteJid === remoteJid) + } + + async continueChat(sessionData, message) { + const urlTypebot = `${this.apiHost}/api/v1/sessions/${sessionData.sessionId}/continueChat`; + const content = message + const reqData = { + message: content, + }; + + try { + const request = await axios.post(urlTypebot, reqData, { timeout: 5000 }) + + await this.sendWAMessage( + sessionData.remoteJid, + request.data.messages, + request.data.input, + request.data.clientSideActions, + ) + } catch (error) { + logger.error(error) + } + } + + async createNewSession(remoteJid) { + const url = `${this.apiHost}/api/v1/typebots/${this.typebotName}/startChat`; + const reqBody = { + prefilledVariables: {} + } + + try { + const request = await axios.post(url, reqBody, { timeout: 5000 }) + + if (request?.data?.sessionId) { + const id = Math.floor(Math.random() * 10000000000).toString(); + this.sessions.push({ + sessionId: request.data.sessionId, + customSessionId: `${id}-${request.data.sessionId}`, + status: 'opened', + createdAt: Date.now(), + remoteJid + }) + + await this.sendWAMessage( + remoteJid, + request.data.messages, + request.data.input, + request.data.clientSideActions, + ) + + return request.data.sessionId + } + } catch (error) { + logger.error(error) + } + + return null + } + + async sendWAMessage(remoteJid, messages, input, clientSideActions) { + this.processMessages(remoteJid, this.parent, messages, input, clientSideActions, applyFormatting) + .catch((err) => { + logger.error(err) + }) + } + + async processMessages(remoteJid, instance, messages, input, clientSideActions, applyFormatting) { + for (const message of messages) { + + if (message.type === 'text') { + let formattedText = ''; + for (const richText of message.content.richText) { + for (const element of richText.children) { + formattedText += applyFormatting(element); + } + formattedText += '\n'; + } + + formattedText = formattedText.replace(/\*\*/g, '').replace(/__/, '').replace(/~~/, '').replace(/\n$/, ''); + + formattedText = formattedText.replace(/\n$/, ''); + logger.info(`typebot text response ${formattedText}`) + + await instance.sendTextMessage(remoteJid, formattedText) + } + + if (message.type === 'image') { + await instance.sendMediaFile('to', message.content, 'image', message.caption) + } + } + + if (input) { + if (input.type === 'choice input') { + + const items = input.items; + + for (const item of items) { + const formattedText = `▶️ ${item.content}\n`.replace(/\n$/, '') + addMapButton(item.id, formattedText) + + await instance.sendButtonMessage( + remoteJid, + { + //text: '', + buttons: [ + { + type: "replyButton", + title: formattedText + }, + ] + } + ) + } + + } + } + } +} + +module.exports = TypeBot; \ No newline at end of file diff --git a/src/api/controllers/instance.controller.js b/src/api/controllers/instance.controller.js index 0fc6c4af8..c6c0a5f5f 100644 --- a/src/api/controllers/instance.controller.js +++ b/src/api/controllers/instance.controller.js @@ -27,6 +27,29 @@ exports.init = async (req, res) => { }) } +exports.addTypeBot = async (req, res) => { + const key = req.body.key + const { name, apiHost } = req.body.typebot + const instance = WhatsAppInstances[key] + if (!instance) { + return res.json({ + error: true, + message: 'Instance not found', + }) + } + + await instance.activeTypeBot(apiHost, name) + + res.json({ + error: false, + message: 'Typebot added successfully', + typebot: { + name, + apiHost, + }, + }) +} + exports.qr = async (req, res) => { try { const qrcode = await WhatsAppInstances[req.query.key]?.instance.qr @@ -65,10 +88,10 @@ exports.qrbase64 = async (req, res) => { } exports.requestMobileCode = async (req, res) => { - try{ + try { const phone = req.query.phone - const instance = WhatsAppInstances[req.query.key] - const code = await instance.requestMobileAuthCode(`+${phone}`) + const instance = WhatsAppInstances[req.query.key] + const code = await instance.requestMobileAuthCode(`+${phone}`) res.json({ error: false, code, @@ -159,7 +182,7 @@ exports.list = async (req, res) => { WhatsAppInstances[key].getInstanceDetail(key) ) let data = await Promise.all(instance) - + return res.json({ error: false, message: 'All instance listed', diff --git a/src/api/helper/mongoAuthState.js b/src/api/helper/mongoAuthState.js index b4311e9b5..0c4f2077c 100644 --- a/src/api/helper/mongoAuthState.js +++ b/src/api/helper/mongoAuthState.js @@ -140,6 +140,7 @@ module.exports = useMongoDBAuthState = async (collection) => { saveCreds: () => { return writeData(creds, 'creds') }, + writeData, insertData, find, updateOne diff --git a/src/api/routes/instance.route.js b/src/api/routes/instance.route.js index 74f4dd837..171b1d10a 100644 --- a/src/api/routes/instance.route.js +++ b/src/api/routes/instance.route.js @@ -5,6 +5,7 @@ const loginVerify = require('../middlewares/loginCheck') const router = express.Router() router.route('/init').get(controller.init) +router.route('/typebot').post(controller.addTypeBot) router.route('/qr').get(keyVerify, controller.qr) router.route('/qrbase64').get(keyVerify, controller.qrbase64) router.route('/phone-number-code').get(keyVerify, controller.requestMobileCode) From ce990eac7ffeef6d00e7f9835abbb357ef703f44 Mon Sep 17 00:00:00 2001 From: Jeanluca Date: Thu, 11 Jul 2024 19:00:48 -0300 Subject: [PATCH 2/3] mais typebot types --- src/api/class/instance.js | 12 +++++++- src/api/class/session.js | 8 +++-- src/api/class/typebot.js | 37 +++++++++++++++++++++-- src/api/controllers/message.controller.js | 1 - 4 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/api/class/instance.js b/src/api/class/instance.js index ef77fef21..8d160992d 100644 --- a/src/api/class/instance.js +++ b/src/api/class/instance.js @@ -315,12 +315,21 @@ class WhatsAppInstance { await this.SendWebhook('message', webhookData, this.key) if (this.instance.typebot) { + // extendedTextMessage quando recebe mensagem do web-whatsapp if (messageType === 'extendedTextMessage') { await this.instance.typebot.startTypebot({ message: msg.message.extendedTextMessage.text, remoteJid: msg.key.remoteJid }) } + + // conversation quando recebe mensagem do celular + if (messageType === 'conversation') { + await this.instance.typebot.startTypebot({ + message: msg.message.conversation, + remoteJid: msg.key.remoteJid + }) + } if (messageType === 'templateButtonReplyMessage') { await this.instance.typebot.startTypebot({ @@ -504,8 +513,9 @@ class WhatsAppInstance { async sendUrlMediaFile(to, url, type, mimeType, caption = '') { await this.verifyId(this.getWhatsAppId(to)) + const receiver = this.getWhatsAppId(to) const data = await this.instance.sock?.sendMessage( - this.getWhatsAppId(to), + receiver, { [type]: { url: url, diff --git a/src/api/class/session.js b/src/api/class/session.js index 1d7a16eee..afda69d51 100644 --- a/src/api/class/session.js +++ b/src/api/class/session.js @@ -36,11 +36,15 @@ class Session { if (typebotItem) { await instance.activeTypeBot(typebotItem.apiHost, typebotItem.typebotName, false) } - + await instance.init() WhatsAppInstances[key] = instance }) - restoredSessions.push(key) + + // TODO:: remover essa gambiarra + if (key !== 'audit_messages') { + restoredSessions.push(key) + } }) } catch (e) { logger.error('Error restoring sessions') diff --git a/src/api/class/typebot.js b/src/api/class/typebot.js index 9ba9d42ac..0148a69ef 100644 --- a/src/api/class/typebot.js +++ b/src/api/class/typebot.js @@ -97,6 +97,7 @@ class TypeBot { const { message, remoteJid } = payload if (remoteJid === 'status@broadcast') return; + logger.info(`startTypebot ${remoteJid}`) const session = this.findSession(remoteJid) if (!session) { await this.createNewSession(remoteJid) @@ -105,6 +106,10 @@ class TypeBot { } } + clearSessions() { + this.sessions = [] + } + findSession(remoteJid) { return this.sessions.find((session) => session.remoteJid === remoteJid) } @@ -126,7 +131,8 @@ class TypeBot { request.data.clientSideActions, ) } catch (error) { - logger.error(error) + logger.error(error?.data) + this.clearSessions() } } @@ -193,7 +199,24 @@ class TypeBot { } if (message.type === 'image') { - await instance.sendMediaFile('to', message.content, 'image', message.caption) + await instance.sendUrlMediaFile( + remoteJid, + message.content.url, + 'image', + '', + 'teste' + ) + } + + if (message.type === 'video') { + + console.log(message) + // url youtube + console.log(message.content.type) + } + + if (message.type === 'audio') { + console.log(message) } } @@ -221,6 +244,16 @@ class TypeBot { } } + + if (input.type === 'text input') { + const label = input?.options?.labels?.placeholder || 'Sem titulo para o input definido' + await instance.sendTextMessage( + remoteJid, + label + ) + } + // email input + console.log('input type', input.type) } } } diff --git a/src/api/controllers/message.controller.js b/src/api/controllers/message.controller.js index 2938c248a..d356cef9d 100644 --- a/src/api/controllers/message.controller.js +++ b/src/api/controllers/message.controller.js @@ -67,7 +67,6 @@ exports.Mediaurl = async (req, res) => { } exports.Button = async (req, res) => { - // console.log(res.body) const data = await WhatsAppInstances[req.query.key].sendButtonMessage( req.body.id, req.body.btndata From 55302d3a22cd1fd1ff9de239b893b7f144c8f513 Mon Sep 17 00:00:00 2001 From: Jeanluca Date: Fri, 12 Jul 2024 15:30:17 -0300 Subject: [PATCH 3/3] Update instance.controller.js --- src/api/controllers/instance.controller.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/api/controllers/instance.controller.js b/src/api/controllers/instance.controller.js index c6c0a5f5f..59f902237 100644 --- a/src/api/controllers/instance.controller.js +++ b/src/api/controllers/instance.controller.js @@ -32,14 +32,16 @@ exports.addTypeBot = async (req, res) => { const { name, apiHost } = req.body.typebot const instance = WhatsAppInstances[key] if (!instance) { - return res.json({ - error: true, - message: 'Instance not found', - }) + return res + .status(422) + .json({ + error: true, + message: 'Instance not found', + }) } await instance.activeTypeBot(apiHost, name) - + res.json({ error: false, message: 'Typebot added successfully',