const fs = require("fs");
const XLSX = require("xlsx");
const moment = require("moment");
const { google } = require("googleapis");
const { Email } = require("../utils/email");
const { AppError } = require("../utils/appError");
const { MessageMedia } = require("whatsapp-web.js");
const { createLog } = require("../utils/createLog");
const { allSessionsObject } = require("../client/client");
const { sendResponses } = require("../utils/sendResponses");
const { phoneNumberFormatter } = require("../utils/formatter");
const { codStatus, codStatusHttp } = require("../utils/dictionaries");
const { indexRepository } = require("../repositories/index.repository");
const { greetings, dates, emojis, farewells } = require("../utils/whatsappMiscellany");
const today = new Date();
today.setHours(0, 0, 0, 0); // * Set the time to 00:00:00:0000
moment.locale("es");
const maxCharMessages = 180;
class WhastappsServices {
static async controlSubscription(countryCode, phone, clientId) {
try {
const { whatsappRepository } = indexRepository();
const findSuscription = await whatsappRepository.findSuscriptionByCountryCodeClientAndPhone(
countryCode,
phone,
clientId
);
if (findSuscription) {
if (findSuscription.statusId.toString() === codStatus.canceled) {
return false;
} else {
return true;
}
} else {
await whatsappRepository.createSuscription(
countryCode,
phone,
`${countryCode}${phone}`,
clientId
);
return true;
}
} catch (error) {
throw error;
}
}
static async randomCharacters(key) {
try {
const result = Math.floor(Math.random() * key.length);
return result;
} catch (error) {
throw error;
}
}
static async sendMessage(client, countryCode, number, message, file, currentUser, workspaces) {
try {
const { whatsappRepository } = indexRepository();
const phone = await phoneNumberFormatter(countryCode, number);
const randomIndexGreeting = await WhastappsServices.randomCharacters(greetings);
const randomIndexFarewells = await WhastappsServices.randomCharacters(farewells);
const randomIndexEmojis = await WhastappsServices.randomCharacters(emojis);
const randomIndexMessages = await WhastappsServices.randomCharacters(message);
const randomIndexDates = await WhastappsServices.randomCharacters(dates);
const newMessage = `${greetings[randomIndexGreeting]},\n${emojis[randomIndexEmojis]}${emojis[randomIndexEmojis]} ${message[randomIndexMessages]} ${emojis[randomIndexEmojis]}${emojis[randomIndexEmojis]}\n${farewells[randomIndexFarewells]}\n${dates[randomIndexDates]}\nSi desea desuscribirse, por favor escriba: *Desuscribir*`;
const chat = await client.getChatById(phone);
await chat.sendStateTyping();
const wpm = (newMessage.split(" ").length / 90) * 60000; // ^ 90 words per minute
if (wpm < 25000) {
// ^ 25 seconds because it is the default time of the sendStateTyping()
await new Promise((r) => setTimeout(r, wpm));
} else {
await new Promise((r) => setTimeout(r, 25000));
}
let media;
if (file) {
const { mimetype, destination, filename } = file;
const base64Document = fs.readFileSync(`${destination}${filename}`, {
encoding: "base64"
});
media = new MessageMedia(mimetype, base64Document, filename);
}
await chat
.sendMessage(newMessage, { media })
.then(async () => {
await chat.clearState();
await whatsappRepository.createStat(
client.info.me.user,
`(+${countryCode})${number}`,
newMessage,
false, // ! It's pending to do
currentUser.id
);
})
.catch(async (err) => {
console.log(err);
let messageDescription = `${workspaces} Ocurrió un error inesperado en el sistema: ${phone}`;
await createLog(messageDescription, true, err, currentUser.id, codStatus.error);
});
} catch (error) {
throw error;
}
}
static async excelToJson(file) {
try {
const workbook = await XLSX.readFile(file.path);
const workbookSheets = workbook.SheetNames;
const sheet = workbookSheets[0];
const dataExcel = await XLSX.utils.sheet_to_json(workbook.Sheets[sheet]);
return dataExcel;
} catch (error) {
throw error;
}
}
static async contactBook(countryCode, phone, clientId, workspaces) {
try {
let messageDescription;
const { whatsappRepository } = indexRepository();
const googleCredentials = await whatsappRepository.findGoogleCredentialsByClientId(clientId);
const auth = new google.auth.OAuth2(
googleCredentials.googleClientId,
googleCredentials.googleClientSecret,
googleCredentials.googleClientUri
);
auth.setCredentials({
refresh_token: googleCredentials.googleRefreshToken
});
const service = google.people({ version: "v1", auth });
let phoneNumber = `${countryCode}${phone}`;
await service.people.searchContacts(
{
query: phoneNumber,
readMask: "phoneNumbers"
},
async (err, res) => {
if (err) {
console.error("Error al buscar el número:", err);
await new Email("inclub.dev@gmail.com")
.contactBookError(JSON.stringify(googleCredentials), JSON.stringify(err))
.then(async () => {
messageDescription = `${workspaces} Envio exitoso del correo para el desarrollador: inclub.dev@gmail.com`;
await createLog(messageDescription, false, null, null, codStatus.active);
})
.catch(async (err) => {
messageDescription = `${workspaces} Ocurrio algo inesperado enviando el correo para el desarrollador: inclub.dev@gmail.com`;
await createLog(messageDescription, true, err, null, codStatus.error);
});
}
if (res) {
const contacts = await res.data.results;
if (!contacts || contacts.length === 0) {
phoneNumber = "+" + phoneNumber;
const contact = {
phoneNumbers: [{ value: phoneNumber }]
};
await service.people.createContact({ requestBody: contact }, (err, res) => {
if (err) {
console.error("Error al crear el contacto:", err);
}
console.log("Contacto creado:", res.data);
});
} else {
console.log("Contacto encontrado:", contacts[0]);
}
}
}
);
} catch (error) {
throw error;
}
}
static async validatePhone(phoneClientQr, phoneClientDb, workspaces, currentUser, next) {
try {
if (phoneClientQr !== phoneClientDb) {
let messageDescription = `${workspaces} El número registrado en DB debe ser el mismo al que escanea el QR -- QR: ${phoneClientQr} -- DB: ${phoneClientDb}`;
await createLog(messageDescription, false, null, currentUser.id, codStatus.active);
return next(
new AppError(
codStatusHttp.forbidden,
"The registered number does not correspond to the one used in the QR"
)
);
}
} catch (error) {
throw error;
}
}
static async validateSize(file, workspaces, currentUser, next) {
try {
let size = file.size;
if (size >= 1000000) {
let messageDescription = `${workspaces} El documento cargado supera 1MB`;
await createLog(messageDescription, true, null, currentUser.id, codStatus.error);
return next(
new AppError(
codStatusHttp.badRequest,
"You must provide a document with a weight of less than 1mb"
)
);
}
} catch (error) {
throw error;
}
}
static async validateLimitPerDay(user, currentUser, next) {
try {
const { whatsappRepository } = indexRepository();
const stats = await whatsappRepository.findAllStatsAndCount(user, today);
if (stats.count >= 901) {
let messageDescription = `${workspaces} El cliente con numero ${user} llego al limite de mensajes diarios (900)`;
await createLog(messageDescription, true, null, currentUser.id, codStatus.error);
return next(
new AppError(
codStatusHttp.manyRequest,
"You exceeded the limit of conservations in the day"
)
);
}
} catch (error) {
throw error;
}
}
static async validateSuscription(
client,
countryCode,
phone,
arrayMessages,
file,
clientId,
counterRow,
i,
currentUser,
workspaces
) {
try {
let suscription = await WhastappsServices.controlSubscription(countryCode, phone, clientId);
if (suscription) {
await WhastappsServices.contactBook(countryCode, phone, currentUser.clientId, workspaces);
// * This is the delay
if (i % 10 === 0 && i !== 0) {
await new Promise((resolve) => setTimeout(resolve, 100000)); // * 100 Seg
}
await WhastappsServices.sendMessage(
client.client,
Number(countryCode),
`0${phone}`,
arrayMessages,
file,
currentUser,
workspaces
);
counterRow += 1;
} else {
let messageDescription = `${workspaces} No se envio mensaje al numero (+${countryCode}) ${phone} - Motivo: Desuscrito`;
await createLog(messageDescription, true, null, currentUser.id, codStatus.error);
}
} catch (error) {
throw error;
}
}
static async validateClient(req, res, next, workspaces) {
try {
const { id } = req.params;
console.log(id)
let messageDescription;
messageDescription = `${workspaces} Validando existencia de la sesion de WSP: ${id}`;
await createLog(messageDescription, false, null, null, codStatus.active);
let client = allSessionsObject[id];
if (client && client.client) {
messageDescription = `${workspaces} Existe la sesion de WSP: ${id}!!!`;
await createLog(messageDescription, false, null, null, codStatus.active);
sendResponses(res, codStatusHttp.ok, null, "Information processed successfully");
} else {
messageDescription = `${workspaces} No existe la sesion de WSP: ${id}`;
await createLog(messageDescription, false, null, null, codStatus.active);
sendResponses(res, codStatusHttp.notFound, null, "Information processed successfully");
}
messageDescription = `${workspaces} Finaliza exitosamente la validacion de la existencia de la sesion de WSP: ${id}`;
await createLog(messageDescription, false, null, null, codStatus.active);
} catch (error) {
throw error;
}
}
static async singleMessage(req, res, next, workspaces) {
try {
// ! Its pending to add validation that que user will be autorizated
const { currentUser, body, file } = req;
let messageDescription;
messageDescription = `${workspaces} Inicia envio de mensajes individuales`;
await createLog(messageDescription, false, null, currentUser.id, codStatus.active);
const { id, countryCode, number, messages } = body;
const client = allSessionsObject[id];
if (!client || !client.client || client.client.info === undefined) {
messageDescription = `${workspaces} Falla el envio de mensajes porque el sistema no está listo`;
let info = "The system is not ready yet";
await createLog(messageDescription, true, info, currentUser.id, codStatus.error);
sendResponses(res, codStatusHttp.serviceUnavailable, null, info);
} else {
await WhastappsServices.validatePhone(
client.client.info.me.user,
`${currentUser.brand.countryCode}${currentUser.brand.phone}`,
workspaces,
currentUser,
next
);
if (file) {
await WhastappsServices.validateSize(file, workspaces, currentUser, next);
}
await WhastappsServices.validateLimitPerDay(client.client.info.me.user, currentUser, next);
if (countryCode == 0 || countryCode == " " || number == 0 || number == " ") {
messageDescription = `${workspaces} Debe proporcionar un indicativo y/o numero diferente a espacio o cero`;
await createLog(messageDescription, true, null, currentUser.id, codStatus.error);
return next(
new AppError(
codStatusHttp.badRequest,
"You must provide a different country code than empty or zero"
)
);
}
if (messages.length > maxCharMessages) {
messageDescription = `${workspaces} Debe proporcionar un mensaje con caracteres inferiores a ${maxCharMessages}`;
await createLog(messageDescription, true, null, currentUser.id, codStatus.error);
return next(
new AppError(codStatusHttp.badRequest, "The message has more than 180 characters")
);
}
let counterRow;
let i = 0;
await WhastappsServices.validateSuscription(
client,
countryCode,
number,
[messages],
file,
currentUser.clientId,
counterRow,
i,
currentUser,
workspaces
);
messageDescription = `${workspaces} Finaliza exitosamente el envio de mensajes individuales`;
await createLog(messageDescription, false, null, currentUser.id, codStatus.active);
sendResponses(res, codStatusHttp.ok, null, "Information processed successfully");
}
} catch (error) {
throw error;
}
}
static async masiveMessage(req, res, next, workspaces) {
try {
// ! Its pending to add validation that que user will be autorizated
req.setTimeout(0); // ^ This is because I want the execution time of this function to be unlimited.
const { currentUser, body, files } = req;
let messageDescription;
messageDescription = `${workspaces} Inicia envio de mensajes masivos`;
await createLog(messageDescription, false, null, currentUser.id, codStatus.active);
const { id, numbers, messages, flag } = body;
// * This is because it will sometimes receive a booleans or strings (forms datas), so we need to format the data
let validate = flag === "true" || flag === true ? true : false;
const client = allSessionsObject[id];
if (!client || !client.client || client.client.info === undefined) {
messageDescription = `${workspaces} Falla el envio de mensajes porque el sistema no está listo`;
let info = "The system is not ready yet";
await createLog(messageDescription, true, info, currentUser.id, codStatus.error);
sendResponses(res, codStatusHttp.serviceUnavailable, null, info);
} else {
let arrayMessages = messages.split("%-%");
if (arrayMessages.length !== 5) {
messageDescription = `${workspaces} La cantidad de mensajes diferentes debe ser 5`;
await createLog(messageDescription, false, null, currentUser.id, codStatus.active);
return next(
new AppError(codStatusHttp.badRequest, "You should have 5 different message types")
);
}
let flag = false;
for (let index = 0; index < arrayMessages.length; index++) {
const element = arrayMessages[index];
if (element.length > maxCharMessages) {
flag = true;
}
}
if (flag) {
messageDescription = `${workspaces} Debe proporcionar mensajes con caracteres inferiores a ${maxCharMessages}`;
await createLog(messageDescription, true, null, currentUser.id, codStatus.error);
return next(
new AppError(codStatusHttp.badRequest, "The message has more than 180 characters")
);
}
await WhastappsServices.validatePhone(
client.client.info.me.user,
`${currentUser.brand.countryCode}${currentUser.brand.phone}`,
workspaces,
currentUser,
next
);
let array = [];
let file;
if (validate) {
// * Manual loading
if (!numbers) {
messageDescription = `${workspaces} Si desea utilizar este metodo de envio debe proporcionar un arreglo`;
await createLog(messageDescription, true, null, currentUser.id, codStatus.error);
return next(new AppError(codStatusHttp.badRequest, "Must provide a valid numbers"));
} else if (!Array.isArray(numbers)) {
messageDescription = `${workspaces} Si desea utilizar este metodo de envio debe proporcionar un arreglo`;
await createLog(messageDescription, true, null, currentUser.id, codStatus.error);
return next(new AppError(codStatusHttp.badRequest, "Numbers must be an Array"));
}
array = numbers;
} else {
// * Bulk upload per document
let excel;
files.forEach((element) => {
let name = element.originalname;
if (name.includes("listUsers")) {
excel = element;
}
if (name.includes("attachedDocument")) {
file = element;
}
});
if (excel) {
array = await WhastappsServices.excelToJson(excel);
} else if (typeof numbers === "string") {
array = JSON.parse(numbers.trim());
} else {
messageDescription = `${workspaces} Debe adjuntar un numeros para enviar mensajes`;
await createLog(messageDescription, true, null, currentUser.id, codStatus.error);
return next(
new AppError(codStatusHttp.badRequest, "You must attach a number to send messages")
);
}
}
if (file) {
await WhastappsServices.validateSize(file, workspaces, currentUser, next);
}
if (array.length === 0) {
messageDescription = `${workspaces} Debe agregar minimo un numero para enviar el mensaje`;
await createLog(messageDescription, true, null, currentUser.id, codStatus.error);
return next(
new AppError(
codStatusHttp.badRequest,
"You must add at least 1 number to send messages"
)
);
}
if (array.length >= 901) {
messageDescription = `${workspaces} La cantidad maxima para enviar mensajes son a 900 contactos`;
await createLog(messageDescription, true, null, currentUser.id, codStatus.error);
return next(
new AppError(
codStatusHttp.badRequest,
"It is only allowed to send a maximum of 900 messages"
)
);
}
let counterRow;
if (!validate) {
counterRow = 2;
} else {
counterRow = 0;
}
for (let i = 0; i < array.length; i++) {
if (array[i].phone || array[i].countryCode) {
array[i].Telefono = array[i].phone;
array[i].Pais = array[i].countryCode;
}
const numberRandom = (Math.random() * 10 + 4) * 1000;
await new Promise((res) => setTimeout(res, numberRandom));
await WhastappsServices.validateLimitPerDay(
client.client.info.me.user,
currentUser,
next
);
let counterFlag = true;
if (array[i].Telefono && array[i].Pais) {
if (
!isNaN(array[i].Telefono) &&
!isNaN(array[i].Pais) &&
array[i].Telefono !== " " &&
array[i].Pais !== " "
) {
await WhastappsServices.validateSuscription(
client,
array[i].Pais.toString(),
array[i].Telefono.toString(),
arrayMessages,
file,
currentUser.clientId,
counterRow,
i,
currentUser,
workspaces
);
} else {
messageDescription = `${workspaces} No se envio mensaje en ${
validate ? "el index" : "la fila"
} ${counterRow}`;
await createLog(messageDescription, true, null, currentUser.id, codStatus.error);
counterRow += 1;
counterFlag = false;
}
} else {
if (counterFlag) {
messageDescription = `${workspaces} No se envio mensaje en ${
validate ? "el index" : "la fila"
} ${counterRow}`;
await createLog(messageDescription, true, null, currentUser.id, codStatus.error);
counterRow += 1;
}
}
}
messageDescription = `${workspaces} Finaliza exitosamente el envio de mensajes masivos`;
await createLog(messageDescription, false, null, currentUser.id, codStatus.active);
if (!validate) {
files.forEach((element) => {
fs.unlink(`./${element.path}`, function (err) {
if (err) {
console.log(err);
} else {
console.log("Archivo eliminado exitosamente");
}
});
});
}
sendResponses(res, codStatusHttp.ok, null, "Information processed successfully");
}
} catch (error) {
throw error;
}
}
}
module.exports = { WhastappsServices };