diff --git a/backend/.env_original b/backend/.env_original new file mode 100644 index 0000000..eac0b17 --- /dev/null +++ b/backend/.env_original @@ -0,0 +1,44 @@ +NODE_ENV= +BACKEND_URL=http://localhost +FRONTEND_URL=http://localhost:3000 +PROXY_PORT=8080 +PORT=8080 + +DB_DIALECT=mysql +DB_HOST=localhost +DB_USER=whaticket +DB_PASS=strongpassword +DB_NAME=whaticket + +# WHATSAPP OFFICIAL +VERIFY_TOKEN=HAPPY +TOKEN=EAAEPZBB2YqgwBOZBEAvPxYaO2nbPvuzU3ZBaZA6YF6tyWtjKom2yLxPxOm421njhbb1ZC2rOkyQyZCWpZBk9jXZCAaMLNY6SkNOrwPoRNaqO9Fbj31mZC8mxra08jIhBiziX7IZBFDWYbkcfw5cfLdTSys9ilfRlKsIZClOUlTiHnhSDkMvXY6rMFrvWswR2YVvJVH1qPvM7hGuuUqM +VERSION=v17.0 +URL_WHATSAPP_MEDIA=https://ccsm-api.omnihit.app.br/whatsapp/official/media +URL_WHATSAPP_API=https://graph.facebook.com + +JWT_SECRET=3123123213123 +JWT_REFRESH_SECRET=75756756756 + +SENTRY_DSN= + +CACHE= + +WHATS_NUMBER_VALIDATOR_URL=http://localhost:8021 + +TOKEN_REMOTE_TICKET_CREATION=emvd7UfskjstMC99mFqs2tEiNmn05PgsUVK06TZP9LfkyjxDrsKCxlVV5ApYM7hP +TOKEN_IAM_HORACIUS_EL=emvd7UfskjstMC99mFqs2tEiNmn05PgsUVK06TZP9LfkyjxDrsKCxlVV5ApYM7hP + +# omnihit da hit test bot +# APP_NAME=recrutamento_el +APP_NAME=test + +BACKEND_URL_RAW=http://localhost + +PUBLICKEY=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwOvEh4Qc0yeJidiX3YpDdOB/XuDeyNRaypmSyW5FjjIxBFaMiUmNSZ2X2m2LqyyrHJxQgRqwjifHUQ+ivmNBm4YFNSr05iFB/kgi/1Jmbst6h1MnmuD1YFSRnEsmdUXzhhgcj5btyjRfw6L2rGwOnqzMzS54seE1aAy+rtN82DW8wfbYU/IO83MAJiocthCBOev5MDUq6hdkGPPZ/kdFOLcQe+wt/suhmF4KRfq77X4GgLM5nAOMj7N7cJ6b97nB47krfPOMJissNzPDZ879BKeQX4t8TwJGUFNOvLd3UW3xVBTBz8pSS36VlCXjbYm44za8eTuBLDYYbGkUNEFYxwIDAQAB + +REDIS_URI=redis://127.0.0.1:6379 + +URL_DASHBOARD_SESSIONS=http://localhost:6002 +TOKEN_DASHBOARD_SESSIONS=8168dd72adb7bab7e8f54f9d022468ab + diff --git a/backend/package.json b/backend/package.json index 1e5bd13..748415c 100644 --- a/backend/package.json +++ b/backend/package.json @@ -50,6 +50,7 @@ "socket.io-client": "^4.5.4", "uuid": "^8.3.2", "whatsapp-web.js": "github:pedroslopez/whatsapp-web.js", + "xml2js": "^0.6.2", "yup": "^0.32.8" }, "devDependencies": { diff --git a/backend/src/controllers/QueueController.ts b/backend/src/controllers/QueueController.ts index 9ee3eaa..2912260 100644 --- a/backend/src/controllers/QueueController.ts +++ b/backend/src/controllers/QueueController.ts @@ -92,11 +92,21 @@ export const store = async (req: Request, res: Response): Promise => { return res.status(200).json(queue); }; +export const form = async (req: Request, res: Response): Promise => { + const { form, name } = req.body; + + if (!form || !name) throw new AppError("BAD REQUEST", 400); + + await set(name, form); + + return res.status(200).json({ [name]: form }); +}; + export const customization = async ( req: Request, res: Response ): Promise => { - const { ura } = req.body; + const { ura, number } = req.body; if (!ura) throw new AppError("BAD REQUEST", 400); @@ -184,10 +194,10 @@ export const customization = async ( } } - await set("ura", ura); + await set(`ura_${number}`, ura); - const _ura = await get({ key: "ura", parse: true }); - console.log("_URA: ", _ura); + const _ura = await get({ key: `ura_${number}`, parse: true }); + console.log(`ura_${number}`, _ura); return res.status(200).json({ new_queues }); }; diff --git a/backend/src/controllers/TicketController.ts b/backend/src/controllers/TicketController.ts index da1399d..284998d 100644 --- a/backend/src/controllers/TicketController.ts +++ b/backend/src/controllers/TicketController.ts @@ -127,7 +127,7 @@ export const remoteTicketCreation = async ( ): Promise => { let { queueId, contact_from, cc, contact_to, msg, contact_name }: any = req.body; - + let whatsappId: any; if (!queueId && !contact_from && !cc) { diff --git a/backend/src/helpers/CloseBotTickets.ts b/backend/src/helpers/CloseBotTickets.ts index 43cbe81..2276dad 100644 --- a/backend/src/helpers/CloseBotTickets.ts +++ b/backend/src/helpers/CloseBotTickets.ts @@ -1,54 +1,60 @@ import ListTicketTimeLife from "../services/TicketServices/ListTicketTimeLife"; import UpdateTicketService from "../services/TicketServices/UpdateTicketService"; +import { delForm } from "../services/WbotServices/wbotMessageListener"; import BotIsOnQueue from "./BotIsOnQueue"; +import { del } from "./RedisClient"; const fsPromises = require("fs/promises"); -const fs = require('fs') +const fs = require("fs"); -let timer: any +let timer: any; const CloseBotTickets = async () => { - try { + const botInfo = await BotIsOnQueue("botqueue"); - const botInfo = await BotIsOnQueue('botqueue') + if (!botInfo.userIdBot) return; - if (!botInfo.userIdBot) return + let tickets: any = await ListTicketTimeLife({ + timeseconds: 60, + status: "open", + userId: botInfo.userIdBot + }); - let tickets: any = await ListTicketTimeLife({ timeseconds: 60, status: 'open', userId: botInfo.userIdBot }) - - console.log('tickets: ', tickets) + console.log("tickets: ", tickets); for (let i = 0; i < tickets.length; i++) { - await UpdateTicketService({ - ticketData: { 'status': 'closed', 'userId': botInfo.userIdBot, 'statusChatEnd': 'FINALIZADO' }, + ticketData: { + status: "closed", + userId: botInfo.userIdBot, + statusChatEnd: "FINALIZADO" + }, ticketId: tickets[i].ticket_id }); + tickets[i].id = tickets[i].ticket_id; + delForm(tickets[i]); + del(`form:${tickets[i].id}:request`); + del(`form:${tickets[i].id}:confirmation`); } - } catch (error) { - console.log('There was an error on try close the bot tickets: ', error) + console.log("There was an error on try close the bot tickets: ", error); } - -} +}; const schedule = async () => { - try { clearInterval(timer); - await CloseBotTickets() - + await CloseBotTickets(); } catch (error) { - console.log('error on schedule: ', error) + console.log("error on schedule: ", error); + } finally { + timer = setInterval(schedule, 180000); } - finally { - timer = setInterval(schedule, 60000); - } -} +}; -timer = setInterval(schedule, 60000); +timer = setInterval(schedule, 180000); -export default schedule; \ No newline at end of file +export default schedule; diff --git a/backend/src/helpers/RedisClient.ts b/backend/src/helpers/RedisClient.ts index 2520588..5c7c6d0 100644 --- a/backend/src/helpers/RedisClient.ts +++ b/backend/src/helpers/RedisClient.ts @@ -44,7 +44,7 @@ export async function get({ key, value, parse }: getData) { for (const key of keys) { const val = await redis.get(key); if (parse) res.push(JSON.parse(val)); - res.push(val); + else res.push(val); } return res; } diff --git a/backend/src/routes/queueRoutes.ts b/backend/src/routes/queueRoutes.ts index 96bb509..5dfc452 100644 --- a/backend/src/routes/queueRoutes.ts +++ b/backend/src/routes/queueRoutes.ts @@ -11,6 +11,8 @@ queueRoutes.post("/queue", isAuth, QueueController.store); queueRoutes.post("/queue/customization", QueueController.customization); +queueRoutes.post("/queue/form", QueueController.form); + queueRoutes.get("/queue/remote/list", isAuth, QueueController.listQueues); queueRoutes.get("/queue/:queueId", isAuth, QueueController.show); diff --git a/backend/src/services/WbotServices/SendWhatsAppMessage.ts b/backend/src/services/WbotServices/SendWhatsAppMessage.ts index ec07cd8..88a133c 100644 --- a/backend/src/services/WbotServices/SendWhatsAppMessage.ts +++ b/backend/src/services/WbotServices/SendWhatsAppMessage.ts @@ -19,7 +19,7 @@ import { import ListWhatsAppsNumber from "../WhatsappService/ListWhatsAppsNumber"; import { getWbot } from "../../libs/wbot"; -import { json } from "sequelize/types"; +import { QueryTypes, json } from "sequelize/types"; import { restartWhatsSession } from "../../helpers/RestartWhatsSession"; // import { insertOrUpeateWhatsCache, searchWhatsappCache } from "../../helpers/WhatsCache"; @@ -56,14 +56,8 @@ const SendWhatsAppMessage = async ({ console.time(timetaken); - let quotedMsgSerializedId: string | undefined; - - if (quotedMsg) { - await GetWbotMessage(ticket, quotedMsg.id); - - quotedMsgSerializedId = SerializeWbotMsgId(ticket, quotedMsg); - } - + let quotedMsgSerializedId: string | undefined; + console.log("quotedMsgSerializedId: ", quotedMsgSerializedId); let whatsapps: any; diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts b/backend/src/services/WbotServices/wbotMessageListener.ts index 204b03b..4bb26b4 100644 --- a/backend/src/services/WbotServices/wbotMessageListener.ts +++ b/backend/src/services/WbotServices/wbotMessageListener.ts @@ -2,9 +2,11 @@ import { join } from "path"; import { promisify } from "util"; import { writeFile } from "fs"; import * as Sentry from "@sentry/node"; - +import axios from "axios"; import { copyFolder } from "../../helpers/CopyFolder"; import { removeDir } from "../../helpers/DeleteDirectory"; +const { parseString, parseStringPromise, xml2js } = require("xml2js"); + import path from "path"; import { @@ -91,6 +93,7 @@ import mostRepeatedPhrase from "../../helpers/MostRepeatedPhrase"; import ListWhatsAppsNumber from "../WhatsappService/ListWhatsAppsNumber"; import { createObject, + del, findByContain, findObject, get, @@ -105,6 +108,8 @@ import CreateContactService from "../ContactServices/CreateContactService"; import { number } from "yup"; import AssociateContatctQueue from "../ContactServices/AssociateContatctQueue"; import ContactQueue from "../../models/ContactQueues"; +import { encodeFileToBase64 } from "../../helpers/EncodeFileToBase64"; +import { Json } from "sequelize/types/lib/utils"; var lst: any[] = getWhatsappIds(); @@ -160,6 +165,295 @@ const verifyQuotedMessage = async ( return quotedMsg; }; +function debounce2 void>( + func: T, + wait: number +): (...args: Parameters) => void { + let timeoutId: NodeJS.Timeout; + + return function (...args: Parameters) { + clearTimeout(timeoutId); + + timeoutId = setTimeout(() => { + func(...args); + }, wait); + }; +} + +const handleFileChange = debounce2( + async (filename: string, ticket: any, msg: any, wbot: any, contact: any) => { + console.log(`File ${filename} has been changed.`); + + let fillingOutForm = await get({ + key: `form:${ticket.id}`, + parse: true + }); + del(`form:${ticket.id}:file`); + await question(fillingOutForm, msg, ticket, contact, wbot); + }, + 3000 +); + +const handleFileChange2 = debounce2( + async (ticket: any, ask: any, fillingOutForm: any, index) => { + del(`form:${ticket.id}:process`); + + const sendFile = await get({ + key: `form:${ticket.id}:file`, + parse: true + }); + + console.log("sendFile?.filePath: ", sendFile?.filePath); + + // ask.value = msg.body; + ask[index].value = sendFile?.filePath; + fillingOutForm = { ...fillingOutForm, ask }; + await set(`form:${ticket.id}`, JSON.stringify(fillingOutForm)); + + await del(`form:${ticket.id}:file`); + + let formData: any = await formDataMsg(ask, ticket); + + botSendMessage(ticket, formData); + + await del(`form:${ticket.id}:process`); + }, + 3000 +); + +const question = async ( + fillingOutForm: any, + msg: any, + ticket: any, + contact: any, + wbot: any, + ignore?: boolean +) => { + if (!msg.fromMe) { + const sendFile = await get({ + key: `form:${ticket.id}:file`, + parse: true + }); + + const process = await get({ + key: `form:${ticket.id}:process`, + parse: true + }); + + if (sendFile && !process && sendFile.status == "yes" && msg?.hasMedia) { + let currentId = await get({ + key: `form:${ticket.id}:current` + }); + + let ask = fillingOutForm.ask; + + let index = ask.findIndex((obj: any) => obj.id == currentId); + + if (index != -1) { + ask[index].value = sendFile?.filePath; + fillingOutForm = { ...fillingOutForm, ask }; + await set(`form:${ticket.id}`, JSON.stringify(fillingOutForm)); + + console.log(" interno kkkkkkkk"); + handleFileChange("TEST 123", ticket, msg, wbot, contact); + } + + return; + } + + console.log( + "sendFile?.status: ", + sendFile?.status, + " | sendFile?.filePath?.length: ", + sendFile?.filePath?.length, + " | msg?.body?.trim()?.length: ", + msg?.body?.trim()?.length + ); + + if ( + sendFile && + sendFile?.status == "yes" && + !sendFile?.filePath && + msg?.body?.trim()?.length > 0 + ) { + await botSendMessage( + ticket, + "¡Envía uno o más archivos simultáneamente!" + ); + return; + } + + fillingOutForm = await get({ + key: `form:${ticket.id}`, + parse: true + }); + + let ask = fillingOutForm.ask; + let currentId = await get({ key: `form:${ticket.id}:current` }); + + if (sendFile && !process && !sendFile?.filePath) { + if ( + msg?.body?.trim().toLowerCase() == "sim" || + msg?.body?.trim().toLowerCase() == "sí" || + msg?.body?.trim().toLowerCase() == "ok" || + msg?.body?.trim().toLowerCase() == "si" + ) { + set( + `form:${ticket.id}:file`, + JSON.stringify({ ...sendFile, status: "yes" }) + ); + } else if ( + msg?.body?.trim().toLowerCase() == "não" || + msg?.body?.trim().toLowerCase() == "nao" || + msg?.body?.trim().toLowerCase() == "no" + ) { + botSendMessage( + ticket, + "Te estamos transfiriendo para hablar con un asesor. Espera a que te atiendan." + ); + transferTicket("Asesor", wbot, ticket); + delForm(ticket); + del(`form:${ticket.id}:request`); + return; + + const index = ask.findIndex((q: any) => q?.item?.file); + + if (index != -1) { + ask.splice(index, 1); + } + } else { + botSendMessage(ticket, "¿Quieres enviar archivo? Ingrese *Sí* o *No*"); + return; + } + } + + // currentId = await get({ key: `form:${ticket.id}:current` }); + + let index = ask.findIndex((obj: any) => obj.id == currentId); + + if (index != -1) { + console.log( + "msg?.body: ", + msg?.body, + " | sendFile?.filePath: ", + sendFile?.filePath, + " | index: ", + index, + " | ask[index]?.item?.file: ", + ask[index]?.item?.file + ); + + if (!ask[index]?.item?.file && !sendFile) { + ask[index].value = msg?.body; + } + + fillingOutForm = { ...fillingOutForm, ask }; + await set(`form:${ticket.id}`, JSON.stringify(fillingOutForm)); + + if (index + 1 <= ask.length - 1) { + if (ask[index + 1]?.item?.file && !sendFile) { + await botSendMessage( + ticket, + "¿Quieres enviar archivo? Ingrese *Sí* o *No*" + ); + await set( + `form:${ticket.id}:file`, + JSON.stringify({ status: "waiting" }) + ); + return; + } + + // Next question + await botSendMessage(ticket, ask[index + 1].question); + await set(`form:${ticket.id}:current`, `${ask[index + 1].id}`); + } else { + await set(`form:${ticket.id}:current`, ``); + + let formData: any = await formDataMsg(ask, ticket); + + botSendMessage(ticket, formData); + } + } else { + console.log("---------> process: ", process); + + if (process) { + console.log("kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk"); + if (process?.questionId) { + const index = ask.findIndex((q: any) => q.id == process?.questionId); + + if (ask[index]?.item?.file) { + handleFileChange2(ticket, ask, fillingOutForm, index); + } else { + ask[index].value = msg.body; + fillingOutForm = { ...fillingOutForm, ask }; + await set(`form:${ticket.id}`, JSON.stringify(fillingOutForm)); + + let formData: any = await formDataMsg(ask, ticket); + + botSendMessage(ticket, formData); + + del(`form:${ticket.id}:process`); + } + + return; + } + + if (process?.option_process == "3") { + const index = ask.findIndex((q: any) => q.id == msg?.body?.trim()); + + if (index != -1) { + if (ask[index]?.item?.file) { + set( + `form:${ticket.id}:file`, + JSON.stringify({ ...sendFile, status: "yes" }) + ); + } + + await botSendMessage(ticket, ask[index].question); + await set( + `form:${ticket.id}:process`, + JSON.stringify({ + ...process, + questionId: msg?.body?.trim() + }) + ); + } else { + updateFormFields(ask, ticket); + } + } + return; + } + + if (["1", "2", "3"].includes(msg?.body?.trim())) { + if (msg?.body?.trim() == "3" || msg?.body?.trim() == "2") { + await set( + `form:${ticket.id}:process`, + JSON.stringify({ + option_process: msg?.body?.trim() + }) + ); + } + + if (msg?.body?.trim() == "3") { + await updateFormFields(ask, ticket); + } else if (msg?.body?.trim() == "2") { + delForm(ticket); + + await previousUra(wbot, contact, ticket); + // const menuMsg: any = await menu("#", wbot.id, contact.id); + // await botSendMessage(ticket, menuMsg.value); + } else if (msg?.body?.trim() == "1") { + console.log("ENVIANDO............"); + botSendMessage(ticket, "Enviando datos, por favor espere..."); + // return + await new Promise(f => setTimeout(f, 4000)); + setSoport(ticket, wbot, contact); + } + } + } + } +}; + const verifyMediaMessage = async ( msg: any, ticket: Ticket, @@ -184,7 +478,11 @@ const verifyMediaMessage = async ( phoneNumberId: msg?.phoneNumberId, fromAgent: false }; - if(messageData.mediaType === 'video' || messageData.mediaType === 'audio' && getSettingValue('blockAudioVideoMedia')?.value === 'enabled'){ + if ( + messageData.mediaType === "video" || + (messageData.mediaType === "audio" && + getSettingValue("blockAudioVideoMedia")?.value === "enabled") + ) { mediaAuthorized = false; } if (msg?.fromMe) { @@ -201,35 +499,76 @@ const verifyMediaMessage = async ( body: media.filename }; } - if(mediaAuthorized){ + if (mediaAuthorized) { try { await writeFileAsync( - join(__dirname, "..", "..", "..", "..", "..", "public", media.filename), + join( + __dirname, + "..", + "..", + "..", + "..", + "..", + "public", + media.filename + ), media.data, "base64" + ); + + const sendFile = await get({ + key: `form:${ticket.id}:file`, + parse: true + }); + + if (sendFile && sendFile.status == "yes") { + const _filePath = sendFile?.filePath || []; + set( + `form:${ticket.id}:file`, + JSON.stringify({ + ...sendFile, + filePath: [ + ..._filePath, + join( + __dirname, + "..", + "..", + "..", + "..", + "..", + "public", + media.filename + ) + ] + }) ); - } catch (err) { - Sentry.captureException(err); - logger.error(`There was an error: wbotMessageLitener.ts: ${err}`); } + } catch (err) { + Sentry.captureException(err); + logger.error(`There was an error: wbotMessageLitener.ts: ${err}`); + } } } - if(mediaAuthorized){ + if (mediaAuthorized) { await ticket.update({ lastMessage: msg.body || media.filename }); const newMessage = await CreateMessageService({ messageData }); return newMessage; - }else{ - if (ticket.status !== "queueChoice") { - botSendMessage( - ticket, - `Atenção! Mensagem ignorada, tipo de mídia não suportado.` + } else { + if (ticket.status !== "queueChoice") { + botSendMessage( + ticket, + `Atenção! Mensagem ignorada, tipo de mídia não suportado.` ); } - messageData.body = `Mensagem de *${messageData.mediaType}* ignorada, tipo de mídia não suportado.`; - messageData.mediaUrl = ''; - await ticket.update({ lastMessage: `Mensagem de *${messageData.mediaType}* ignorada, tipo de mídia não suportado.`}); - const newMessage = await CreateMessageService({ messageData }); - console.log(`--------->>> Mensagem do tipo: ${messageData.mediaType}, ignorada!`) + messageData.body = `Mensagem de *${messageData.mediaType}* ignorada, tipo de mídia não suportado.`; + messageData.mediaUrl = ""; + await ticket.update({ + lastMessage: `Mensagem de *${messageData.mediaType}* ignorada, tipo de mídia não suportado.` + }); + const newMessage = await CreateMessageService({ messageData }); + console.log( + `--------->>> Mensagem do tipo: ${messageData.mediaType}, ignorada!` + ); } }; @@ -323,7 +662,11 @@ const verifyMessage = async ( const botInfo = await BotIsOnQueue("botqueue"); if (botInfo.isOnQueue) { - const ura: any = await get({ key: "ura" }); + let whatsapp = await whatsappCache(ticket.whatsappId); + + const ura: any = await get({ + key: whatsapp?.number ? `ura_${whatsapp?.number}` : "ura" + }); if (ura && !ura.includes(JSON.stringify(msg?.body))) { messageData = { ...messageData, fromAgent: true }; @@ -341,13 +684,13 @@ const verifyMessage = async ( } else { messageData = { ...messageData, body: JSON.stringify(msg.vCards) }; } - } + } await CreateMessageService({ messageData }); }; const verifyQueue = async ( - wbot: Session, + wbot: any, msg: WbotMessage, ticket: Ticket, contact: Contact @@ -399,12 +742,17 @@ const verifyQueue = async ( ticketId: ticket.id }); - const data = await get({ key: "ura", parse: true }); + const whatsapp = await whatsappCache(ticket.whatsappId); + + const data = await get({ + key: whatsapp?.number ? `ura_${whatsapp?.number}` : "ura", + parse: true + }); await createObject({ whatsappId: `${ticket.whatsappId}`, contactId: `${ticket.contactId}`, - identifier: "ura", + identifier: whatsapp?.number ? `ura_${whatsapp?.number}` : "ura", value: data[1].id, history: `|${data[1].id}` }); @@ -435,7 +783,7 @@ const verifyQueue = async ( body = `\u200e${choosenQueue.greetingMessage}`; } - io.emit('notifyPeding', {data: {ticket, queue: choosenQueue}}); + io.emit("notifyPeding", { data: { ticket, queue: choosenQueue } }); sendWhatsAppMessageSocket(ticket, body); } else { @@ -621,12 +969,12 @@ const botTransferTicketToUser = async ( }); }; -const botSendMessage = (ticket: Ticket, msg: string) => { +const botSendMessage = (ticket: Ticket, msg: string, quotedMsg?: any) => { const { phoneNumberId } = ticket; const debouncedSentMessage = debounce( async () => { - await SendWhatsAppMessage({ body: msg, ticket }); + await SendWhatsAppMessage({ body: msg, ticket, quotedMsg }); }, phoneNumberId ? 0 : 3000, ticket.id @@ -779,7 +1127,7 @@ const handleMessage = async ( unreadMessages // groupContact ); - } + } if (getSettingValue("oneContactChatWithManyWhats")?.value == "disabled") { // Para responder para o cliente pelo mesmo whatsapp que ele enviou a mensagen @@ -826,10 +1174,16 @@ const handleMessage = async ( const botInfo = await BotIsOnQueue("botqueue"); + let fillingOutForm = await get({ + key: `form:${ticket.id}`, + parse: true + }); + // Transfer to agent // O bot interage com o cliente e encaminha o atendimento para fila de atendende quando o usuário escolhe a opção falar com atendente if ( !msg.fromMe && + !fillingOutForm && ((ticket.status == "open" && botInfo && ticket.userId == +botInfo.userIdBot) || @@ -868,6 +1222,7 @@ const handleMessage = async ( return; } } + // if ( @@ -891,13 +1246,13 @@ const handleMessage = async ( } else { console.log("MSG body: ", msg.body); - if (msg.type != "chat") { - botSendMessage( - ticket, - `Desculpe, nao compreendi!\nEnvie apenas texto quando estiver interagindo com o bot!\n _Digite *0* para voltar ao menu principal._` - ); - return; - } + // if (msg.type != "chat") { + // botSendMessage( + // ticket, + // `Desculpe, nao compreendi!\nEnvie apenas texto quando estiver interagindo com o bot!\n _Digite *0* para voltar ao menu principal._` + // ); + // return; + // } if (msg.type == "chat" && String(msg.body).length > 120) { botSendMessage( @@ -907,17 +1262,207 @@ const handleMessage = async ( return; } + if (msg?.body?.trim() == "0" || msg?.body?.trim() == "#") { + del(`form:${ticket.id}:request`); + del(`form:${ticket.id}:confirmation`); + delForm(ticket); + } + + let confirmation = await get({ + key: `form:${ticket.id}:confirmation`, + parse: true + }); + + if (confirmation && !msg?.fromMe && !confirmation?.done) { + if (msg?.hasMedia) { + return; + } + + if (!confirmation?.value) { + confirmation = { ...confirmation, value: msg?.body }; + set(`form:${ticket.id}:confirmation`, JSON.stringify(confirmation)); + } else if (confirmation?.value) { + set( + `form:${ticket.id}:confirmation`, + JSON.stringify({ ...confirmation, done: true }) + ); + + if (confirmation?.value?.trim() == msg?.body?.trim()) { + await set( + `form:${ticket.id}:request`, + JSON.stringify({ + id: ticket.id, + createdAt: ticket.createdAt, + updatedAt: ticket.updatedAt, + whatsappId: ticket.whatsappId, + status: ticket.status + }) + ); + } else { + botSendMessage( + ticket, + `El nit proporcionado no se corresponde con el anterior.` + ); + + await new Promise(f => setTimeout(f, 4000)); + previousUra(wbot, contact, ticket); + return; + } + } + } + + let request = await get({ + key: `form:${ticket.id}:request`, + parse: true + }); + + let fillingOutForm = await get({ + key: `form:${ticket.id}`, + parse: true + }); + + if (request && !request?.done) { + if (request?.product) { + if (request?.selecteProduct) { + } else { + const products = JSON.parse(request.product); + + console.log("PRODUCT: ", typeof products); + + const selectedProd = products.find( + (p: any, index: number) => msg?.body?.trim() == `${index + 1}` + ); + + if (selectedProd) { + console.log("PRODUTO SELECIONADO: ", selectedProd); + + request = { + ...request, + ...{ + selecteProduct: JSON.stringify(selectedProd), + done: true + } + }; + + await set(`form:${ticket.id}:request`, JSON.stringify(request)); + + botSendMessage( + ticket, + `*Producto seleccionado*\n${selectedProd.det}\n\nRellena el siguiente formulario para registrar la incidencia` + ); + await new Promise(r => setTimeout(r, 4000)); + + await set( + `form:${ticket.id}`, + JSON.stringify({ + id: ticket.id, + createdAt: ticket.createdAt, + updatedAt: ticket.updatedAt, + whatsappId: ticket.whatsappId, + status: ticket.status + }) + ); + + const ask = await get({ + key: `form_setSoport`, + parse: true + }); + + botSendMessage(ticket, ask[0].question); + fillingOutForm = { ...fillingOutForm, ask }; + await set(`form:${ticket.id}`, JSON.stringify(fillingOutForm)); + await set(`form:${ticket.id}:current`, `${ask[0].id}`); + } else { + productUra(products, ticket); + } + } + } else if (!request?.nit) { + await getProduct(ticket, msg?.body?.trim(), wbot); + } else if (request?.nit) { + botSendMessage(ticket, "Esperar consulta..."); + } + + return; + } + + if ( + fillingOutForm && + msg?.body?.trim() != "0" && + msg?.body?.trim() != "#" + ) { + await question(fillingOutForm, msg, ticket, contact, wbot); + return; + } + const menuMsg: any = await menu(msg.body, wbot.id, contact.id); console.log("menuMsg: ", menuMsg); - await botSendMessage(ticket, menuMsg.value); + await botSendMessage( + ticket, + menuMsg.value + + "\n\nSi desea volver al menú principal, escriba *0* o *#* para volver al menú anterior" + ); if ( menuMsg?.transferToQueue && menuMsg.transferToQueue.trim().length > 0 ) { transferTicket(menuMsg.transferToQueue.trim(), wbot, ticket); + } else if (menuMsg?.form) { + // console.log("FORMULARIO"); + // await set( + // `form:${ticket.id}`, + // JSON.stringify({ + // id: ticket.id, + // createdAt: ticket.createdAt, + // updatedAt: ticket.updatedAt, + // whatsappId: ticket.whatsappId, + // status: ticket.status + // }) + // ); + // const ask = await get({ + // key: `form_setSoport`, + // parse: true + // }); + // botSendMessage(ticket, ask[0].question); + // fillingOutForm = { ...fillingOutForm, ask }; + // await set(`form:${ticket.id}`, JSON.stringify(fillingOutForm)); + // await set(`form:${ticket.id}:current`, `${ask[0].id}`); + } else if (menuMsg?.request && menuMsg?.confirmation) { + confirmation = await get({ + key: `form:${ticket.id}:confirmation`, + parse: true + }); + + if (confirmation?.done) return; + + if (!confirmation?.value) { + botSendMessage(ticket, menuMsg.value); + set( + `form:${ticket.id}:confirmation`, + JSON.stringify({ ask: "1", type: "ura" }) + ); + } else { + botSendMessage(ticket, menuMsg.confirmation); + set( + `form:${ticket.id}:confirmation`, + JSON.stringify({ ...confirmation, ask: "2" }) + ); + } + } else if (menuMsg?.request) { + botSendMessage(ticket, menuMsg.value); + + await set( + `form:${ticket.id}:request`, + JSON.stringify({ + id: ticket.id, + createdAt: ticket.createdAt, + updatedAt: ticket.updatedAt, + whatsappId: ticket.whatsappId, + status: ticket.status + }) + ); } } @@ -1003,9 +1548,73 @@ const handleMessage = async ( } }; +async function previousUra(wbot: any, contact: Contact, ticket: any) { + const menuMsg: any = await menu("#", wbot.id, contact.id); + botSendMessage(ticket, menuMsg.value); +} + +async function formDataMsg(ask: any, ticket: any) { + let formData = ""; + + for (const q of ask) { + if (q?.item?.file && q?.value) { + let files = ""; + + for (const x of q?.value) { + files += x.split("/").pop() + "\n"; + } + + console.log("FILES: ", files); + + q.value = files; + } + + formData += `${q.question}:\n${q.value}\n\n`; + } + + formData = + "*Datos del formulario*\n" + + formData + + "*1* - Enviar datos\n*2* - Cancelar\n*3* - Editar datos"; + return formData; +} + +async function updateFormFields(ask: any, ticket: any) { + let formData = ""; + + for (const q of ask) { + formData += `*${q.id}* - ${q.question}:\n${q.value}\n\n`; + } + + formData = + "*Datos del formulario*\n" + + formData + + "Seleccione el número correspondiente a la opción que desea cambiar"; + + await botSendMessage(ticket, formData); +} + +function delForm(ticket: any) { + del(`form:${ticket.id}:current`); + del(`form:${ticket.id}:process`); + del(`form:${ticket.id}`); + del(`form:${ticket.id}:file`); + del(`form:${ticket.id}:confirmation`); +} + const menu = async (userTyped: string, whatsappId: any, contactId: any) => { - let lastId = await findObject(whatsappId, contactId, "ura"); - const data: any = await get({ key: "ura", parse: true }); + let whatsapp = await whatsappCache(whatsappId); + + let lastId = await findObject( + whatsappId, + contactId, + whatsapp?.number ? `ura_${whatsapp?.number}` : "ura" + ); + + const data: any = await get({ + key: whatsapp?.number ? `ura_${whatsapp?.number}` : "ura", + parse: true + }); console.log("lastId[0]: ", lastId[0]); @@ -1013,13 +1622,17 @@ const menu = async (userTyped: string, whatsappId: any, contactId: any) => { await createObject({ whatsappId, contactId, - identifier: "ura", + identifier: whatsapp?.number ? `ura_${whatsapp?.number}` : "ura", value: data[1].id, history: `|${data[1].id}` }); } - lastId = await findObject(whatsappId, contactId, "ura"); + lastId = await findObject( + whatsappId, + contactId, + whatsapp?.number ? `ura_${whatsapp?.number}` : "ura" + ); console.log("LAST ID: ", lastId); let option: any; @@ -1043,7 +1656,7 @@ const menu = async (userTyped: string, whatsappId: any, contactId: any) => { let uraOptionSelected = await findObject( whatsappId, contactId, - "ura" + whatsapp?.number ? `ura_${whatsapp?.number}` : "ura" ); uraOptionSelected = uraOptionSelected[4].split("|"); @@ -1052,7 +1665,7 @@ const menu = async (userTyped: string, whatsappId: any, contactId: any) => { await createObject({ whatsappId, contactId, - identifier: "ura", + identifier: whatsapp?.number ? `ura_${whatsapp?.number}` : "ura", value: data[1].id, history: `|${data[1].id}` }); @@ -1068,7 +1681,7 @@ const menu = async (userTyped: string, whatsappId: any, contactId: any) => { await createObject({ whatsappId, contactId, - identifier: "ura", + identifier: whatsapp?.number ? `ura_${whatsapp?.number}` : "ura", value: id, history }); @@ -1088,20 +1701,24 @@ const menu = async (userTyped: string, whatsappId: any, contactId: any) => { let history: any = await historyUra(whatsappId, contactId, response.id); - await createObject({ - whatsappId, - contactId, - identifier: "ura", - value: response.id, - history - }); + const _ura = data.find((ura: any) => ura.id == response.id); + + if (!_ura?.ignoreHistory) { + await createObject({ + whatsappId, + contactId, + identifier: whatsapp?.number ? `ura_${whatsapp?.number}` : "ura", + value: response.id, + history + }); + } return response; } else if (userTyped == "0") { await createObject({ whatsappId, contactId, - identifier: "ura", + identifier: whatsapp?.number ? `ura_${whatsapp?.number}` : "ura", value: data[1].id, history: `|${data[1].id}` }); @@ -1121,7 +1738,11 @@ const menu = async (userTyped: string, whatsappId: any, contactId: any) => { } async function mainOptionsMenu(userTyped: any) { - let currentMenu = await findObject(whatsappId, contactId, "ura"); + let currentMenu = await findObject( + whatsappId, + contactId, + whatsapp?.number ? `ura_${whatsapp?.number}` : "ura" + ); const menuValues = data .filter((m: any) => m.idmaster == currentMenu[3]) @@ -1141,7 +1762,7 @@ const menu = async (userTyped: string, whatsappId: any, contactId: any) => { await createObject({ whatsappId, contactId, - identifier: "ura", + identifier: whatsapp?.number ? `ura_${whatsapp?.number}` : "ura", value: response.id, history }); @@ -1257,8 +1878,21 @@ export { verifyContact, isValidMsg, mediaTypeWhatsappOfficial, - botSendMessage + botSendMessage, + delForm }; + +async function whatsappCache(whatsappId: any) { + let whatsapp = await get({ key: "whatsapp:*", parse: true }); + + let uraByNumber = await get({ key: "ura_*", parse: true }); + // console.log("------------------------> uraByNumber: ", uraByNumber); + + whatsapp = whatsapp.filter((w: any) => +w?.id == +whatsappId); + + if (whatsapp && whatsapp.length > 0 && uraByNumber) return whatsapp[0]; +} + async function assingContactByQueue(ticket: Ticket, queueId: any) { if (getSettingValue("contactByqueues")?.value == "enabled") { const contact = await Contact.findByPk(ticket.contact.id); @@ -1328,7 +1962,13 @@ async function vcard(msg: any) { } async function backUra(whatsappId: any, contactId: any, data: any) { - let uraOptionSelected = await findObject(whatsappId, contactId, "ura"); + const _whatsapp = await whatsappCache(whatsappId); + + let uraOptionSelected = await findObject( + whatsappId, + contactId, + _whatsapp?.number ? `ura_${_whatsapp?.number}` : "ura" + ); uraOptionSelected = uraOptionSelected[4].split("|").filter(Boolean); @@ -1346,10 +1986,12 @@ async function backUra(whatsappId: any, contactId: any, data: any) { id = lstIds[lstIds.length - 1]; } + const whatsapp = await whatsappCache(whatsappId); + await createObject({ whatsappId, contactId, - identifier: "ura", + identifier: whatsapp?.number ? `ura_${whatsapp?.number}` : "ura", value: id, history }); @@ -1365,7 +2007,13 @@ async function historyUra( id: any, remove?: boolean ) { - let uraOptionSelected = await findObject(whatsappId, contactId, "ura"); + const whatsapp = await whatsappCache(whatsappId); + + let uraOptionSelected = await findObject( + whatsappId, + contactId, + whatsapp?.number ? `ura_${whatsapp?.number}` : "ura" + ); let history = ""; console.log("SELECED OPTION uraOptionSelected: ", uraOptionSelected); @@ -1387,3 +2035,299 @@ async function historyUra( async function whatsappInfo(whatsappId: string | number) { return await Whatsapp.findByPk(whatsappId); } + +async function getProduct(ticket: any, nit: any, wbot: any) { + let request = await get({ + key: `form:${ticket.id}:request`, + parse: true + }); + + request = { ...request, nit }; + + await set(`form:${ticket.id}:request`, JSON.stringify(request)); + + var data = ` + + + + + ${process.env.LOGIN_FARO} + ${process.env.PASS_FARO} + ${nit} + + + `; + + // return; + var config = { + method: "post", + url: process.env.URL_FARO, + headers: { + "Content-Type": "text/xml", + SOAPAction: "urn:consult_base#getProduct" + }, + data: data + }; + + try { + botSendMessage(ticket, "Enviando datos, por favor espere..."); + await new Promise(f => setTimeout(f, 4000)); + const response = await axios(config); + // console.log( + // "response.data: ", + // response.data, + // " | typeof response.data: ", + // typeof response.data, + // " | removed pre tag: ", + // removePreTags(response.data) + // ); + + parseString( + response.data, + { explicitArray: false }, + async (err: any, result: any) => { + if (err) { + console.error("Error parsing XML:", err); + return; + } + + // Navigate through the parsed object to extract the values + const response = + result["SOAP-ENV:Envelope"]["SOAP-ENV:Body"][ + "ns1:getProductResponse" + ]["dat_respon"]; + const code_resp = response["code_resp"]; + // const msg_resp = response["msg_resp"]; + const dat_Product = response["dat_Product"]["item"]; + + // Display the extracted values + // console.log("code_resp:", code_resp); + // console.log("msg_resp:", msg_resp); + // console.log("dat_Product:", dat_Product); + + if (code_resp?._ != "1000") { + await nitNotFound( + `No te identificamos como cliente en nuestra base de datos, te transferimos con un asesor`, + ticket, + wbot, + "Asesor" + ); + return; + } + + if (!dat_Product || dat_Product.length == 0) { + await nitNotFound( + `No se encontraron productos para el nit ingresado. Trasladaremos tu servicio para hablar con un asesor. Espera a que te atiendan.`, + ticket, + wbot, + "Asesor" + ); + return; + } + + const products = dat_Product.map((p: any) => { + const { cod, det } = p; + return { cod: cod._, det: det._ }; + }); + + request = { + ...request, + product: JSON.stringify(products) + }; + + productUra(products, ticket); + + await set(`form:${ticket.id}:request`, JSON.stringify(request)); + } + ); + } catch (error) { + console.error(error); + botSendMessage(ticket, `Lo sentimos, hubo un error en tu solicitud.`); + delForm(ticket); + del(`form:${ticket.id}:request`); + } +} + +async function nitNotFound(msg: string, ticket: any, wbot: any, queue: string) { + botSendMessage(ticket, msg); + + await transferTicket(queue, wbot, ticket); + + delForm(ticket); + del(`form:${ticket.id}:request`); +} + +function productUra(products: { cod: number; det: string }[], ticket: any) { + let options = ""; + products.forEach((p, index) => { + options += `*${index + 1}* - ${p.det}\n`; + }); + + const body = `\u200e*Seleccionar una de las siguientes opciones:*\n${options}`; + + botSendMessage(ticket, body); +} + +function removePreTags(str: string) { + return str.replace(//gi, ""); +} + +// Function to extract [cod] and [det] values +function extractValues(data: any) { + // Regular expression to match [cod] and [det] values + const regex = /\[cod\] => (\d+)\n\s+\[det\] => ([^\n]+)/g; + + let match; + const result = []; + + // Executing regex on the data + while ((match = regex.exec(data)) !== null) { + // Extracting [cod] and [det] values + const cod = parseInt(match[1]); + const det = match[2]; + + // Adding extracted values to the result array + result.push({ cod, det }); + } + + return result; +} + +async function setSoport(ticket: any, wbot: any, contact: any) { + let fillingOutForm = await get({ + key: `form:${ticket.id}`, + parse: true + }); + + let payload = fillingOutForm.ask + .filter((a: any) => !a?.item?.file) + .map((a: any) => { + const { key, value } = a; + return `<${key}>${value}`; + }) + .join(""); + + let payloadFile = fillingOutForm.ask.find((a: any) => a?.item?.file); + + let files = ""; + if (payloadFile?.value?.length > 0) { + let _files = payloadFile.value; + for (const f of _files) { + files += ` + ${encodeFileToBase64(f)} + ${fs.statSync(f).size} + ${path.basename(f)} + ${path.extname(f)} + `; + } + + let fatherKey = payloadFile.key; + + files = `<${fatherKey}>${files}`; + } + + const searchString = ""; + const endIndex = payload.indexOf(searchString) + searchString.length; + let fields = ""; + + if (endIndex >= searchString.length) { + const beforeInsert = payload.substring(0, endIndex); + const afterInsert = payload.substring(endIndex); + + fields = beforeInsert + files + afterInsert; + } else { + console.log(`The string "${searchString}" was not found.`); + } + + var data = ` + + + + + ${process.env.LOGIN_FARO} + ${process.env.PASS_FARO} + + ${fields} + + + +`; + + // console.log("DATA: ", data); + + // return; + var config = { + method: "post", + url: process.env.URL_FARO, + headers: { + "Content-Type": "text/xml", + SOAPAction: "urn:consult_base#setSoport" + }, + data: data + }; + + try { + const response = await axios(config); + // console.log( + // "response.data: ", + // response.data, + // " | typeof response.data: ", + // typeof response.data, + // " | removed pre tag: ", + // removePreTags(response.data) + // ); + + // console.log("DATA: ", JSON.stringify(response.data, null, 6)); + + const xmlResponse = removePreTags(response.data); + + // Parse the XML response to JSON + parseString( + xmlResponse, + { explicitArray: false }, + async (err: any, result: any) => { + if (err) { + throw err; + } + const jsonData = result; + + const envelope = jsonData["SOAP-ENV:Envelope"]; + if (envelope) { + const body = envelope["SOAP-ENV:Body"]; + if (body) { + const setSoportResponse = body["ns1:setSoportResponse"]; + if (setSoportResponse) { + const dat_respon = setSoportResponse["dat_respon"]; + const code_resp = dat_respon["code_resp"]; + const msg_resp = dat_respon["msg_resp"]; + + console.log("Code Response:", code_resp._); + console.log("Message Response:", msg_resp._); + + botSendMessage( + ticket, + `_*Respuesta*_\n\n*code_resp*: ${code_resp._}\n*msg_resp*: ${msg_resp._}` + ); + } else { + console.error( + "setSoportResponse is missing in the response body" + ); + } + } else { + console.error("SOAP-ENV:Body is missing in the envelope"); + } + } else { + console.error("SOAP-ENV:Envelope is missing in the response"); + } + } + ); + } catch (error) { + console.error(error); + botSendMessage(ticket, `Lo sentimos, hubo un error en su solicitud.`); + } + + delForm(ticket); + del(`form:${ticket.id}:request`); + await new Promise(f => setTimeout(f, 4000)); + previousUra(wbot, contact, ticket); +}