Compare commits

..

26 Commits

Author SHA1 Message Date
adriano 051e1b3b2a feat: Add code to enable sending of information via socket to application monitoring dashboard 2024-02-09 09:35:19 -03:00
adriano c444012963 feat: Uncomment code to allow backend to send disk space information to frontend 2024-02-08 09:13:19 -03:00
adriano 7bab4bf6b9 feat: Optimize database by reopening bot tickets instead of creating new ones 2024-02-07 17:52:07 -03:00
adriano 853f537361 fix: Update frontend to prevent React.js emoji-related errors 2024-02-07 17:07:06 -03:00
adriano ad89d3ccdb feat: Support IVR(ura) for bot interactions, optimize botqueue user checks, and allow supervisor to view all tickets 2024-02-05 12:29:49 -03:00
adriano dfa4be6253 chore: Update translation for email to login and change application label 2024-02-02 08:54:46 -03:00
gustavo.pinho 11e2dc1a50 Merge branch 'integracao_wa_oficial_el_editing' of https://github.com/AdrianoRobson/projeto-hit into integracao_wa_oficial_el_editing 2024-02-01 18:49:13 -03:00
gustavo.pinho c3ffab3819 style: update job column display and enhance job editing
Details:
- Improved the presentation of the job column for better clarity.
- Enhanced the functionality for editing job roles.
2024-02-01 18:49:06 -03:00
adriano 21b19fcfbd fix: Resolve report error at supervisor level in frontend 2024-02-01 18:38:30 -03:00
adriano 09a469e892 Merge branch 'integracao_wa_oficial_el_editing' of github.com:AdrianoRobson/projeto-hit into integracao_wa_oficial_el_editing 2024-02-01 18:30:19 -03:00
gustavo.pinho 6a5a51ff3f chore: update reports tab for supervisors, add reminders functionality, and include user roles
Details:
- Updated the reports tab to enhance functionality for supervisors.
- Implemented a reminders feature for improved user experience.
- Added user roles to enhance user management capabilities.
2024-02-01 17:46:23 -03:00
adriano a2f946d849 feat: Configure business hours for WhatsApp sessions and add setting to hide campaign module 2024-02-01 17:16:56 -03:00
adriano 0751374fb7 Merge branch 'integracao_wa_oficial_el_editing' of github.com:AdrianoRobson/projeto-hit into integracao_wa_oficial_el_editing 2024-01-30 09:52:49 -03:00
adriano 255b1bb47b feat: Allow supervisor users to view dashboard and reports 2024-01-30 09:50:37 -03:00
gustavo.pinho 763448fdab teste 2024-01-29 15:23:46 -03:00
adriano 5bfaa7f733 fix: Resolve duplicated display of users on online/offline dashboard 2024-01-29 14:17:42 -03:00
adriano 4edddd2dbb feat: Automatically add '55' to the beginning of phone numbers in ContactController 2024-01-29 11:34:56 -03:00
adriano bc134c827c feat: Enable users to transfer to other queues and users with the same WhatsApp connection. 2024-01-29 08:48:20 -03:00
adriano d12a47f062 chore: Update package-lock.json 2024-01-23 08:51:35 -03:00
adriano edbb565f62 refactor: Otimização do codigo responsavel por controlar o horario de expediente 2023-10-18 11:57:36 -03:00
adriano 8b6d7e2597 fix: Correção para considerar o id do numero que esta conectado na interface 2023-10-13 11:52:26 -03:00
adriano c151473d52 Atualização basica para envio de template do Whatsapp cloud API 2023-09-19 09:41:15 -03:00
adriano 72c489a27b remoçao de console.log 2023-09-16 11:46:42 -03:00
adriano 996e182fb6 Atualização para habilitar ou desabilitar whatsapp oficial 2023-09-16 11:45:44 -03:00
adriano 6d525e4224 estado funcional whatsapp api oficial 2023-09-08 16:50:51 -03:00
adriano 42535f2e6c whatsapp oficial em desenvolvimento 2023-08-28 14:05:53 -03:00
118 changed files with 5807 additions and 2370 deletions

View File

@ -30,6 +30,8 @@ app.get('/', function (req, res) {
app.post('/api/session', async function (req, res) { app.post('/api/session', async function (req, res) {
let { app_name, whatsappId, client_url, number } = req.body let { app_name, whatsappId, client_url, number } = req.body
let oldNumber = ''
if (app_name) { if (app_name) {
app_name = app_name.trim() app_name = app_name.trim()
} }
@ -67,6 +69,7 @@ app.post('/api/session', async function (req, res) {
} }
} }
let appPort = [] let appPort = []
let existSubDir = false let existSubDir = false
@ -98,7 +101,7 @@ app.post('/api/session', async function (req, res) {
path.join(sessionsPath, directoriesInDIrectory[i], subDir[x]) path.join(sessionsPath, directoriesInDIrectory[i], subDir[x])
) )
let oldNumber = subDir[x].split('_')[1] oldNumber = subDir[x].split('_')[1]
if (oldNumber != number) { if (oldNumber != number) {
deletePm2Process(subDir[x], currPath) deletePm2Process(subDir[x], currPath)
@ -197,7 +200,7 @@ app.post('/api/session', async function (req, res) {
) { ) {
const whatsapp_numbers = await new Promise((resolve, reject) => { const whatsapp_numbers = await new Promise((resolve, reject) => {
mysql_conn(db_credentials.db_conf).query( mysql_conn(db_credentials.db_conf).query(
'SELECT name FROM Whatsapps WHERE name LIKE ?', 'SELECT name, number FROM Whatsapps WHERE name LIKE ?',
[`%${number}%`], [`%${number}%`],
(err, result) => { (err, result) => {
if (err) { if (err) {
@ -209,8 +212,6 @@ app.post('/api/session', async function (req, res) {
) )
}) })
console.log('whatsapp_numbers: ', whatsapp_numbers)
let session_num = [] let session_num = []
if (whatsapp_numbers && whatsapp_numbers.length > 0) { if (whatsapp_numbers && whatsapp_numbers.length > 0) {
@ -289,7 +290,7 @@ app.post('/api/session', async function (req, res) {
) )
}) })
if (whatsapp[0]['name'].split('->').length > 0) { if (whatsapp[0]['name']?.split('->')?.length > 0) {
whatsName = `${whatsapp[0]['name'].split('->')[0]} -> S${numberSession}` whatsName = `${whatsapp[0]['name'].split('->')[0]} -> S${numberSession}`
} else { } else {
whatsName = `${whatsapp[0]['name']} -> S${numberSession}` whatsName = `${whatsapp[0]['name']} -> S${numberSession}`
@ -341,6 +342,7 @@ app.post('/api/session', async function (req, res) {
stream.write('# NUMBER AND NAME THAT WILL BE DISPLAYED ON CONSOLE\n') stream.write('# NUMBER AND NAME THAT WILL BE DISPLAYED ON CONSOLE\n')
stream.write(`MOBILEUID=${number}\n`) stream.write(`MOBILEUID=${number}\n`)
stream.write(`MOBILENAME=${whatsappName}\n`) stream.write(`MOBILENAME=${whatsappName}\n`)
stream.write(`OLD_MOBILEUID=${oldNumber}\n`)
stream.write('\n') stream.write('\n')
stream.write('# PORT NUMBER FOR THIS API\n') stream.write('# PORT NUMBER FOR THIS API\n')

View File

@ -288,8 +288,6 @@ client.on("ready", async () => {
let url = process.env.CLIENT_URL + '/whatsapp/connection/number' let url = process.env.CLIENT_URL + '/whatsapp/connection/number'
try { try {
await client.logout() await client.logout()
@ -304,6 +302,48 @@ client.on("ready", async () => {
} }
if (process.env.OLD_MOBILEUID) {
const ticketSettingsId = await new Promise((resolve, reject) => {
dbcc.query("select id from SettingTickets where number = ?", [process.env.OLD_MOBILEUID,], (err, result) => {
if (err) {
reject(err)
}
else {
// resolve(result)
const idArray = result.map(row => row.id)
resolve(idArray)
}
})
})
if (ticketSettingsId?.length > 0) {
await new Promise((resolve, reject) => {
const idsToUpdate = ticketSettingsId // Assuming ticketSettingsId is an array of IDs
// Create placeholders for the IN clause based on the number of elements in idsToUpdate
const placeholders = Array(idsToUpdate.length).fill('?').join(',')
dbcc.query(
`UPDATE SettingTickets SET number = ? WHERE id IN (${placeholders})`,
[client.info["wid"]["user"], ...idsToUpdate], // Spread the array to pass individual values
function (err, result) {
if (err) {
console.log("ERROR: " + err)
reject(err)
} else {
resolve(result)
}
}
)
})
}
}
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
@ -324,7 +364,6 @@ client.on("ready", async () => {
}) })
let url = process.env.CLIENT_URL + '/whatsapp/connection/qrcode' let url = process.env.CLIENT_URL + '/whatsapp/connection/qrcode'
try { try {
@ -1193,9 +1232,9 @@ function comercialBuss(until_hour) {
scheduler_monitor = setInterval(monitor, 10000) scheduler_monitor = setInterval(monitor, 10000)
scheduler_campaign_monitor = setInterval(sendCampaignMessage, 3000) // scheduler_campaign_monitor = setInterval(sendCampaignMessage, 3000)
scheduler_internet_conn = setInterval(internetMonitor, 60000) // scheduler_internet_conn = setInterval(internetMonitor, 60000)
app.listen(process.env.PORT || 8003, function () { app.listen(process.env.PORT || 8003, function () {
console.log("\u26A1[server]: Server is running at Port ::: " + process.env.PORT || 8003) console.log("\u26A1[server]: Server is running at Port ::: " + process.env.PORT || 8003)

View File

@ -1,14 +0,0 @@
NODE_ENV=
BACKEND_URL=http://localhost
FRONTEND_URL=http://localhost:3000
PROXY_PORT=8080
PORT=8080
DB_DIALECT=
DB_HOST=
DB_USER=
DB_PASS=
DB_NAME=
JWT_SECRET=
JWT_REFRESH_SECRET=

View File

@ -16,6 +16,7 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry/node": "^5.29.2", "@sentry/node": "^5.29.2",
"@types/fluent-ffmpeg": "^2.1.21",
"@types/pino": "^6.3.4", "@types/pino": "^6.3.4",
"axios": "^1.2.3", "axios": "^1.2.3",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
@ -28,6 +29,7 @@
"express-async-errors": "^3.1.1", "express-async-errors": "^3.1.1",
"fast-folder-size": "^1.7.0", "fast-folder-size": "^1.7.0",
"flat": "^5.0.2", "flat": "^5.0.2",
"fluent-ffmpeg": "^2.1.2",
"fs-extra": "^10.1.0", "fs-extra": "^10.1.0",
"http-graceful-shutdown": "^2.3.2", "http-graceful-shutdown": "^2.3.2",
"ioredis": "^5.2.3", "ioredis": "^5.2.3",
@ -42,6 +44,7 @@
"sequelize": "^5.22.3", "sequelize": "^5.22.3",
"sequelize-cli": "^5.5.1", "sequelize-cli": "^5.5.1",
"sequelize-typescript": "^1.1.0", "sequelize-typescript": "^1.1.0",
"sharp": "^0.32.5",
"socket.io": "^3.0.5", "socket.io": "^3.0.5",
"socket.io-client": "^4.5.4", "socket.io-client": "^4.5.4",
"uuid": "^8.3.2", "uuid": "^8.3.2",

View File

@ -22,6 +22,7 @@ app.use(
origin: process.env.FRONTEND_URL origin: process.env.FRONTEND_URL
}) })
); );
app.use(cookieParser()); app.use(cookieParser());
app.use(express.json()); app.use(express.json());
app.use(Sentry.Handlers.requestHandler()); app.use(Sentry.Handlers.requestHandler());

View File

@ -93,7 +93,7 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
number: Yup.string() number: Yup.string()
.required() .required()
.matches(/^\d+$/, "Invalid number format. Only numbers is allowed.") .matches(/^\d+$/, "Invalid number format. Only numbers is allowed.")
.matches(/^55\d+$/, "The number must start with 55.") // .matches(/^55\d+$/, "The number must start with 55.")
}); });
try { try {
@ -102,6 +102,8 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
throw new AppError(err.message); throw new AppError(err.message);
} }
newContact.number = addStartPhoneNumber(newContact.number);
const validNumber = await CheckIsValidContact(newContact.number); const validNumber = await CheckIsValidContact(newContact.number);
// const validNumber: any = await CheckContactNumber(newContact.number) // const validNumber: any = await CheckContactNumber(newContact.number)
@ -157,9 +159,11 @@ export const update = async (
const schema = Yup.object().shape({ const schema = Yup.object().shape({
name: Yup.string(), name: Yup.string(),
number: Yup.string() number: Yup.string().matches(
.matches(/^\d+$/, "Invalid number format. Only numbers is allowed.") /^\d+$/,
.matches(/^55\d+$/, "The number must start with 55.") "Invalid number format. Only numbers is allowed."
)
// .matches(/^55\d+$/, "The number must start with 55.")
}); });
try { try {
@ -168,6 +172,8 @@ export const update = async (
throw new AppError(err.message); throw new AppError(err.message);
} }
contactData.number = addStartPhoneNumber(contactData.number);
await CheckIsValidContact(contactData.number); await CheckIsValidContact(contactData.number);
const { contactId } = req.params; const { contactId } = req.params;
@ -233,3 +239,13 @@ export const contacsBulkInsertOnQueue = async (
return res.status(200).json({ message: "ok" }); return res.status(200).json({ message: "ok" });
}; };
function addStartPhoneNumber(phoneNumber: string) {
const regex = /^55/;
if (!regex.test(phoneNumber)) {
phoneNumber = "55" + phoneNumber;
}
return phoneNumber;
}

View File

@ -1,4 +1,5 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import whatsappOfficialAPI from "../helpers/WhatsappOfficialAPI";
import SetTicketMessagesAsRead from "../helpers/SetTicketMessagesAsRead"; import SetTicketMessagesAsRead from "../helpers/SetTicketMessagesAsRead";
import { getIO } from "../libs/socket"; import { getIO } from "../libs/socket";
@ -9,6 +10,17 @@ import ShowTicketService from "../services/TicketServices/ShowTicketService";
import DeleteWhatsAppMessage from "../services/WbotServices/DeleteWhatsAppMessage"; import DeleteWhatsAppMessage from "../services/WbotServices/DeleteWhatsAppMessage";
import SendWhatsAppMedia from "../services/WbotServices/SendWhatsAppMedia"; import SendWhatsAppMedia from "../services/WbotServices/SendWhatsAppMedia";
import SendWhatsAppMessage from "../services/WbotServices/SendWhatsAppMessage"; import SendWhatsAppMessage from "../services/WbotServices/SendWhatsAppMessage";
import axios from "axios";
import Contact from "../models/Contact";
import {
isValidMsg,
verifyMessage
} from "../services/WbotServices/wbotMessageListener";
import CreateOrUpdateContactService from "../services/ContactServices/CreateOrUpdateContactService";
import sendWhatsAppMessageOfficialAPI from "../helpers/sendWhatsAppMessageOfficialAPI";
import Whatsapp from "../models/Whatsapp";
import checkLastClientMsg24hs from "../helpers/CheckLastClientMsg24hs";
import AppError from "../errors/AppError";
type IndexQuery = { type IndexQuery = {
pageNumber: string; pageNumber: string;
@ -19,6 +31,8 @@ type MessageData = {
fromMe: boolean; fromMe: boolean;
read: boolean; read: boolean;
quotedMsg?: Message; quotedMsg?: Message;
mic_audio?: boolean;
params: any;
}; };
export const index = async (req: Request, res: Response): Promise<Response> => { export const index = async (req: Request, res: Response): Promise<Response> => {
@ -30,28 +44,103 @@ export const index = async (req: Request, res: Response): Promise<Response> => {
ticketId ticketId
}); });
// SetTicketMessagesAsRead(ticket);
return res.json({ count, messages, ticket, hasMore }); return res.json({ count, messages, ticket, hasMore });
}; };
export const store = async (req: Request, res: Response): Promise<Response> => { export const store = async (req: Request, res: Response): Promise<Response> => {
const { ticketId } = req.params; const { ticketId } = req.params;
const { body, quotedMsg }: MessageData = req.body; const { body, quotedMsg, mic_audio, params }: MessageData = req.body;
const medias = req.files as Express.Multer.File[]; const medias = req.files as Express.Multer.File[];
const ticket = await ShowTicketService(ticketId); const ticket = await ShowTicketService(ticketId);
console.log('TICKET ID: ', ticketId) const { queueId } = ticket;
console.log(
"-----------> queueId: ",
queueId,
" | quotedMsg: ",
quotedMsg,
" | params: ",
params
);
// SetTicketMessagesAsRead(ticket); const { phoneNumberId, whatsappId } = ticket;
if (phoneNumberId) {
const into24hs = await checkLastClientMsg24hs(ticket);
if (into24hs && into24hs.length == 0) {
if (params) {
console.log("SEND TEMPLATE PARAMS: ", params);
// return res.send()
let payloadComponents = [];
try {
for (let i in params) {
const { parameters, language, type } = params[i];
if (type == "BODY") {
if (parameters && parameters.length > 0) {
let components: any = [{ type: "body", parameters: [] }];
for (let x in parameters) {
const { type, text, index } = parameters[x];
console.log(text);
components[0].parameters.splice(index - 1, 0, {
type,
text
});
}
payloadComponents.push(components[0]);
}
} else if (type == "BUTTONS") {
}
}
const name = params.find((p: any) => p?.template_name);
const { language }: any = params.find((p: any) => p?.language);
const { template_name } = name;
if (template_name && language) {
const template: any = {
template: {
name: template_name,
language: { code: language },
components: payloadComponents
}
};
sendWhatsAppMessageOfficialAPI(ticket, body, null, template);
console.log("TEMPLATE: ", template);
return res.send();
}
} catch (error: any) {
throw new AppError(error.message);
}
} else {
try {
const { wabaId }: any = await Whatsapp.findByPk(whatsappId);
const { data } = await whatsappOfficialAPI.get(
`/${process.env.VERSION}/${wabaId}/message_templates?language=pt_BR`
);
return res.status(200).json(data);
} catch (error) {
return res
.status(500)
.json({ message: "Não foi possível baixar os templates!" });
}
}
}
}
if (medias) { if (medias) {
await Promise.all( await Promise.all(
medias.map(async (media: Express.Multer.File) => { medias.map(async (media: Express.Multer.File) => {
console.log(
console.log(`\n >>>>>>>>>> SENDING MESSAGE MEDIA `\n >>>>>>>>>> SENDING MESSAGE MEDIA
Parcial ticket info and media: Parcial ticket info and media:
ticket.id: ${ticket.id} ticket.id: ${ticket.id}
ticket.status: ${ticket.status} ticket.status: ${ticket.status}
@ -61,13 +150,14 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
ticket.contact.profilePicUrl: ${ticket.contact.profilePicUrl} ticket.contact.profilePicUrl: ${ticket.contact.profilePicUrl}
ticket.user.id: ${ticket.user.id} ticket.user.id: ${ticket.user.id}
ticket.user.name: ${ticket.user.name} ticket.user.name: ${ticket.user.name}
media:`, media,'\n') media:`,
media,
await SendWhatsAppMedia({ media, ticket }); "\n"
);
await SendWhatsAppMedia({ media, ticket, mic_audio });
}) })
); );
} else { } else {
console.log(`\n >>>>>>>>>> SENDING MESSAGE console.log(`\n >>>>>>>>>> SENDING MESSAGE
Parcial ticket info: Parcial ticket info:
ticket.id: ${ticket.id} ticket.id: ${ticket.id}
@ -78,8 +168,7 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
ticket.contact.name: ${ticket.contact.name} ticket.contact.name: ${ticket.contact.name}
ticket.contact.profilePicUrl: ${ticket.contact.profilePicUrl} ticket.contact.profilePicUrl: ${ticket.contact.profilePicUrl}
ticket.user.id: ${ticket.user.id} ticket.user.id: ${ticket.user.id}
ticket.user.name: ${ticket.user.name}\n`) ticket.user.name: ${ticket.user.name}\n`);
await SendWhatsAppMessage({ body, ticket, quotedMsg }); await SendWhatsAppMessage({ body, ticket, quotedMsg });
} }

View File

@ -5,6 +5,9 @@ import DeleteQueueService from "../services/QueueService/DeleteQueueService";
import ListQueuesService from "../services/QueueService/ListQueuesService"; import ListQueuesService from "../services/QueueService/ListQueuesService";
import ShowQueueService from "../services/QueueService/ShowQueueService"; import ShowQueueService from "../services/QueueService/ShowQueueService";
import UpdateQueueService from "../services/QueueService/UpdateQueueService"; import UpdateQueueService from "../services/QueueService/UpdateQueueService";
import Queue from "../models/Queue"
import AppError from "../errors/AppError"
import { get, set } from "../helpers/RedisClient";
@ -28,6 +31,106 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
return res.status(200).json(queue); return res.status(200).json(queue);
}; };
export const customization = async (
req: Request,
res: Response
): Promise<Response> => {
const { ura } = req.body;
if (!ura) throw new AppError("BAD REQUEST", 400);
let new_queues: any;
if (ura.length > 0) {
new_queues = ura
.filter(
(u: any) =>
u.idmaster === ura[1].id &&
u?.queueName &&
u?.color &&
u?.greetingMessage
)
.map((u: any) => {
const { queueName, color, greetingMessage } = u;
return {
queueName: queueName?.trim()?.replace(/\s+/g, " "),
color,
greetingMessage
};
});
if (new_queues && new_queues.length > 0) {
const db_queues: any = await Queue.findAll();
for (const i in new_queues) {
let { queueName: name, color, greetingMessage } = new_queues[i];
name = name?.trim()?.replace(/\s+/g, " ");
const update = db_queues.find(
(q: any) => q.name?.trim()?.replace(/\s+/g, " ") == name
);
if (update) {
const { id } = update;
// UPDATE
// const queue = await UpdateQueueService(id, {
// name,
// color,
// greetingMessage
// });
// const io = getIO();
// io.emit("queue", {
// action: "update",
// queue
// });
} else {
// CREATE
// const queue = await CreateQueueService({
// name,
// color,
// greetingMessage
// });
// const io = getIO();
// io.emit("queue", {
// action: "update",
// queue
// });
}
}
let remove_queues = db_queues.filter(
(q: any) =>
!new_queues
.map((nq: any) => nq.queueName)
.includes(q.name?.trim()?.replace(/\s+/g, " "))
);
for (const i in remove_queues) {
const { id, name } = remove_queues[i];
// await DeleteQueueService(id);
// const io = getIO();
// io.emit("queue", {
// action: "delete",
// queueId: +id
// });
}
// await set("ura", ura);
}
}
await set("ura", ura);
const _ura = await get("ura");
console.log("_URA: ", _ura);
return res.status(200).json({ new_queues });
};
export const show = async (req: Request, res: Response): Promise<Response> => { export const show = async (req: Request, res: Response): Promise<Response> => {
const { queueId } = req.params; const { queueId } = req.params;

View File

@ -34,6 +34,7 @@ type IndexQuery = {
startDate: string; startDate: string;
endDate: string; endDate: string;
pageNumber: string; pageNumber: string;
userQueues: [];
}; };
type ReportOnQueue = { type ReportOnQueue = {
@ -44,13 +45,20 @@ type ReportOnQueue = {
export const reportUserByDateStartDateEnd = async (req: Request, res: Response): Promise<Response> => { export const reportUserByDateStartDateEnd = async (req: Request, res: Response): Promise<Response> => {
if (req.user.profile !== "master" && req.user.profile !== "admin") { if (
req.user.profile !== "master" &&
req.user.profile !== "admin" &&
req.user.profile !== "supervisor"
) {
throw new AppError("ERR_NO_PERMISSION", 403); throw new AppError("ERR_NO_PERMISSION", 403);
} }
const { userId, startDate, endDate, pageNumber } = req.query as IndexQuery const { userId, startDate, endDate, pageNumber, userQueues } = req.query as IndexQuery
console.log("userId, startDate, endDate, pageNumber: ", userId, startDate, endDate, pageNumber);
const { tickets, count, hasMore } = await ShowTicketReport({ userId, startDate, endDate, pageNumber }); const { tickets, count, hasMore } = await ShowTicketReport({ userId, startDate, endDate, pageNumber });
// console.log('kkkkkkkkkkkkkkkkkk tickets: ', JSON.stringify(tickets, null, 6))
return res.status(200).json({ tickets, count, hasMore }); return res.status(200).json({ tickets, count, hasMore });
}; };
@ -58,13 +66,16 @@ export const reportUserByDateStartDateEnd = async (req: Request, res: Response):
export const reportUserService = async (req: Request, res: Response): Promise<Response> => { export const reportUserService = async (req: Request, res: Response): Promise<Response> => {
if (req.user.profile !== "master" && req.user.profile !== "admin") { if (req.user.profile !== "master" && req.user.profile !== "admin" && req.user.profile !=="supervisor") {
throw new AppError("ERR_NO_PERMISSION", 403); throw new AppError("ERR_NO_PERMISSION", 403);
} }
const { userId, startDate, endDate } = req.query as IndexQuery const { userId, startDate, endDate } = req.query as IndexQuery
// let usersProfile = await ListUserParamiterService({ profile: 'user' })
let usersProfile = await ListUserParamiterService({ profile: 'user', raw:true }) let usersProfile = await ListUserParamiterService({
profiles: ["user", "supervisor"],
raw: true
});
const sumUserOlineTime = await ShowUserServiceReport({ startDate, endDate, userId }); const sumUserOlineTime = await ShowUserServiceReport({ startDate, endDate, userId });
const closedByUser = await ShowUserServiceReport({ startDate, endDate, ticketStatus: 'closed', userId }); const closedByUser = await ShowUserServiceReport({ startDate, endDate, ticketStatus: 'closed', userId });
@ -184,7 +195,11 @@ export const reportUserService = async (req: Request, res: Response): Promise<Re
export const reportMessagesUserByDateStartDateEnd = async (req: Request, res: Response): Promise<Response> => { export const reportMessagesUserByDateStartDateEnd = async (req: Request, res: Response): Promise<Response> => {
if (req.user.profile !== "master" && req.user.profile !== "admin") { if (
req.user.profile !== "master" &&
req.user.profile !== "admin" &&
req.user.profile !== "supervisor"
) {
throw new AppError("ERR_NO_PERMISSION", 403); throw new AppError("ERR_NO_PERMISSION", 403);
} }

View File

@ -5,6 +5,12 @@ import AuthUserService from "../services/UserServices/AuthUserService";
import { SendRefreshToken } from "../helpers/SendRefreshToken"; import { SendRefreshToken } from "../helpers/SendRefreshToken";
import { RefreshTokenService } from "../services/AuthServices/RefreshTokenService"; import { RefreshTokenService } from "../services/AuthServices/RefreshTokenService";
import createOrUpdateOnlineUserService from "../services/UserServices/CreateOrUpdateOnlineUserService";
import { removeUserFromOlineList } from "../helpers/removeUserFromOnlineList";
// const usersSocket = require("./../libs/socket");
const usersSocket = require("../libs/socket");
export const store = async (req: Request, res: Response): Promise<Response> => { export const store = async (req: Request, res: Response): Promise<Response> => {
const { email, password } = req.body; const { email, password } = req.body;
@ -15,6 +21,11 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
SendRefreshToken(res, refreshToken); SendRefreshToken(res, refreshToken);
const userOnline = await createOrUpdateOnlineUserService({
userId: serializedUser.id,
status: "online"
});
return res.status(200).json({ return res.status(200).json({
token, token,
user: serializedUser user: serializedUser
@ -47,5 +58,14 @@ export const remove = async (
): Promise<Response> => { ): Promise<Response> => {
res.clearCookie("jrt"); res.clearCookie("jrt");
const { userId } = req.params;
removeUserFromOlineList(userId);
const userOnline = await createOrUpdateOnlineUserService({
userId,
status: "offline"
});
return res.send(); return res.send();
}; };

View File

@ -8,6 +8,8 @@ import ListSettingsService from "../services/SettingServices/ListSettingsService
import loadSettings from "../helpers/LoadSettings"; import loadSettings from "../helpers/LoadSettings";
import updateSettingTicket from "../services/SettingServices/UpdateSettingTicket"; import updateSettingTicket from "../services/SettingServices/UpdateSettingTicket";
import SettingTicket from "../models/SettingTicket"; import SettingTicket from "../models/SettingTicket";
import Whatsapp from "../models/Whatsapp";
import whatsappOfficialNumberInfo from "../helpers/WhatsappOfficialNumberInfo";
export const index = async (req: Request, res: Response): Promise<Response> => { export const index = async (req: Request, res: Response): Promise<Response> => {
// if (req.user.profile !== "master") { // if (req.user.profile !== "master") {
@ -16,9 +18,20 @@ export const index = async (req: Request, res: Response): Promise<Response> => {
const settings = await ListSettingsService(); const settings = await ListSettingsService();
const config = await SettingTicket.findAll(); // const config = await SettingTicket.findAll();
return res.status(200).json({ settings, config }); return res.status(200).json({ settings, });
};
export const ticketSettings = async (
req: Request,
res: Response
): Promise<Response> => {
const { number } = req.params;
const config = await SettingTicket.findAll({ where: { number } });
return res.status(200).json({ config });
}; };
export const updateTicketSettings = async ( export const updateTicketSettings = async (
@ -26,6 +39,7 @@ export const updateTicketSettings = async (
res: Response res: Response
): Promise<Response> => { ): Promise<Response> => {
const { const {
number,
outBusinessHours, outBusinessHours,
ticketExpiration, ticketExpiration,
weekend, weekend,
@ -34,58 +48,64 @@ export const updateTicketSettings = async (
holiday holiday
} = req.body; } = req.body;
if (!number) throw new AppError("No number selected", 400);
if (outBusinessHours && Object.keys(outBusinessHours).length > 0) { if (outBusinessHours && Object.keys(outBusinessHours).length > 0) {
await updateSettingTicket({ await updateSettingTicket({
...outBusinessHours, ...outBusinessHours,
key: "outBusinessHours" key: "outBusinessHours",
number
}); });
} }
if (ticketExpiration && Object.keys(ticketExpiration).length > 0) { if (ticketExpiration && Object.keys(ticketExpiration).length > 0) {
await updateSettingTicket({ await updateSettingTicket({
...ticketExpiration, ...ticketExpiration,
key: "ticketExpiration" key: "ticketExpiration",
number
}); });
} }
if (weekend && Object.keys(weekend).length > 0) { if (weekend && Object.keys(weekend).length > 0) {
await updateSettingTicket({ await updateSettingTicket({
...weekend, ...weekend,
key: "weekend" key: "weekend",
number
}); });
} }
if (saturday && Object.keys(saturday).length > 0) { if (saturday && Object.keys(saturday).length > 0) {
await updateSettingTicket({ await updateSettingTicket({
...saturday, ...saturday,
key: "saturday" key: "saturday",
number
}); });
} }
if (sunday && Object.keys(sunday).length > 0) { if (sunday && Object.keys(sunday).length > 0) {
await updateSettingTicket({ await updateSettingTicket({
...sunday, ...sunday,
key: "sunday" key: "sunday",
number
}); });
} }
if (holiday && Object.keys(holiday).length > 0) { if (holiday && Object.keys(holiday).length > 0) {
await updateSettingTicket({ await updateSettingTicket({
...holiday, ...holiday,
key: "holiday" key: "holiday",
number
}); });
} }
return res return res.status(200).json({
.status(200) outBusinessHours,
.json({ ticketExpiration,
outBusinessHours, weekend,
ticketExpiration, saturday,
weekend, sunday,
saturday, holiday
sunday, });
holiday
});
}; };
export const update = async ( export const update = async (
@ -103,6 +123,40 @@ export const update = async (
value value
}); });
if (key && key == "whatsaAppCloudApi") {
let whatsapps: any = await Whatsapp.findAll();
if (whatsapps) {
for (let i in whatsapps) {
const { id, wabaId, isOfficial } = whatsapps[i];
if (isOfficial && wabaId) {
try {
const whatsapp = await Whatsapp.findByPk(id);
if (whatsapp) {
if (value == "disabled") {
whatsapp.update({ status: "OPENING" });
} else if (value == "enabled") {
const info = await whatsappOfficialNumberInfo(wabaId);
if (info) {
whatsapp.update({
classification: info.quality_rating,
status: "CONNECTED"
});
}
}
}
} catch (error) {
console.log(
"error on try update classification number from oficial whatsapp in SettingControllers.ts: ",
error
);
}
}
}
}
}
loadSettings(); loadSettings();
const io = getIO(); const io = getIO();

View File

@ -64,6 +64,10 @@ import Contact from "../models/Contact";
import BotIsOnQueue from "../helpers/BotIsOnQueue"; import BotIsOnQueue from "../helpers/BotIsOnQueue";
import { setMessageAsRead } from "../helpers/SetMessageAsRead"; import { setMessageAsRead } from "../helpers/SetMessageAsRead";
import { getSettingValue } from "../helpers/WhaticketSettings"; import { getSettingValue } from "../helpers/WhaticketSettings";
import ListWhatsAppsForQueueService from "../services/WhatsappService/ListWhatsAppsForQueueService";
import ListWhatsAppsNumber from "../services/WhatsappService/ListWhatsAppsNumber";
import Whatsapp from "../models/Whatsapp";
import AppError from "../errors/AppError";
export const index = async (req: Request, res: Response): Promise<Response> => { export const index = async (req: Request, res: Response): Promise<Response> => {
const { const {
@ -103,26 +107,45 @@ export const index = async (req: Request, res: Response): Promise<Response> => {
}; };
export const store = async (req: Request, res: Response): Promise<Response> => { export const store = async (req: Request, res: Response): Promise<Response> => {
const { contactId, status, userId, msg, queueId }: TicketData = req.body; const { contactId, status, userId, msg, queueId, whatsappId }: TicketData =
req.body;
// const botInfo = await BotIsOnQueue("botqueue"); const botInfo = await BotIsOnQueue("botqueue");
let ticket = await Ticket.findOne({ let ticket = await Ticket.findOne({
where: { where: {
[Op.or]: [ [Op.or]: [
{ contactId, status: "queueChoice" } { contactId, status: "queueChoice" },
// { contactId, status: "open", userId: botInfo.userIdBot } { contactId, status: "open", userId: botInfo.userIdBot }
] ]
} }
}); });
if (ticket) { if (getSettingValue("whatsaAppCloudApi")?.value == "enabled") {
await UpdateTicketService({ if (ticket) {
ticketData: { status: "open", userId: userId, queueId }, await UpdateTicketService({
ticketId: ticket.id ticketData: { status: "closed" },
}); ticketId: ticket.id
});
ticket = null;
}
} else { } else {
ticket = await CreateTicketService({ contactId, status, userId, queueId }); if (ticket) {
await UpdateTicketService({
ticketData: { status: "open", userId: userId, queueId },
ticketId: ticket.id
});
}
}
if (!ticket) {
ticket = await CreateTicketService({
contactId,
status,
userId,
queueId,
whatsappId
});
} }
const io = getIO(); const io = getIO();
@ -130,15 +153,6 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
action: "update", action: "update",
ticket ticket
}); });
//
// const ticket = await CreateTicketService({ contactId, status, userId });
// const io = getIO();
// io.to(ticket.status).emit("ticket", {
// action: "update",
// ticket
// });
return res.status(200).json(ticket); return res.status(200).json(ticket);
}; };
@ -236,7 +250,6 @@ export const update = async (
ticket2 = ticket; ticket2 = ticket;
} else { } else {
// Para aparecer pendente para todos usuarios que estao na fila // Para aparecer pendente para todos usuarios que estao na fila
if (req.body.transfer) { if (req.body.transfer) {
req.body.userId = null; req.body.userId = null;
@ -246,20 +259,82 @@ export const update = async (
if (getSettingValue("oneContactChatWithManyWhats")?.value == "enabled") { if (getSettingValue("oneContactChatWithManyWhats")?.value == "enabled") {
if (ticketData.transfer) { if (ticketData.transfer) {
const defaultWhatsapp: any = await GetDefaultWhatsApp( const whatsappsByqueue = await ListWhatsAppsForQueueService(
ticketData.userId ticketData.queueId
); );
const _ticket: any = await Ticket.findByPk(ticketId); if (userOldInfo) {
let listTicketOpenPending: any = [];
if (defaultWhatsapp && ticketData.status != "open") { for (const w of whatsappsByqueue) {
await CheckContactOpenTickets( let whats = await ListWhatsAppsNumber(w.id);
_ticket.dataValues.contactId,
defaultWhatsapp.dataValues.id console.log("-------> WHATS: ", JSON.stringify(whats, null, 6));
); const ticket = await Ticket.findOne({
where: {
[Op.and]: [
{ contactId: userOldInfo.contactId },
{
whatsappId: {
[Op.in]: whats.whatsapps.map((w: any) => w.id)
}
},
{ status: { [Op.or]: ["open", "pending"] } }
]
}
});
if (ticket) {
listTicketOpenPending.push({
ticketId: ticket.id,
status: ticket.status,
userId: ticket.userId,
contactId: ticket.contactId,
whatsappId: ticket.whatsappId,
queueId: ticket.queueId
});
}
}
// console.log("userOldInfo: ", JSON.stringify(userOldInfo, null, 6));
// console.log("##########")
// console.log(
// "listTicketOpenPending: ",
// JSON.stringify(listTicketOpenPending)
// );
if (
listTicketOpenPending.filter(
(ob: any) => userOldInfo.whatsappId != ob.whatsappId
)?.length > 0
) {
throw new AppError("ERR_OTHER_OPEN_TICKET");
}
} }
ticketData.whatsappId = defaultWhatsapp.dataValues.id; //////////////////////////////////////////////
// const defaultWhatsapp: any = await GetDefaultWhatsApp({
// userId: ticketData.userId
// });
// console.log(
// "ticketData.userId: ",
// ticketData.userId,
// " | defaultWhatsapp: ",
// JSON.stringify(defaultWhatsapp, null, 6)
// );
// const _ticket: any = await Ticket.findByPk(ticketId);
// if (defaultWhatsapp && ticketData.status != "open") {
// await CheckContactOpenTickets(
// _ticket.dataValues.contactId,
// defaultWhatsapp.dataValues.id
// );
// }
// ticketData.whatsappId = defaultWhatsapp.dataValues.id;
} }
} }
@ -279,8 +354,6 @@ export const update = async (
await setMessageAsRead(ticket); await setMessageAsRead(ticket);
} }
console.log("ticket.unreadMessages: ", ticket.unreadMessages);
if (ticketData.userId) { if (ticketData.userId) {
const dateToday = splitDateTime( const dateToday = splitDateTime(
new Date(format(new Date(), "yyyy-MM-dd HH:mm:ss", { locale: ptBR })) new Date(format(new Date(), "yyyy-MM-dd HH:mm:ss", { locale: ptBR }))

View File

@ -10,20 +10,29 @@ import UpdateUserService from "../services/UserServices/UpdateUserService";
import ShowUserService from "../services/UserServices/ShowUserService"; import ShowUserService from "../services/UserServices/ShowUserService";
import DeleteUserService from "../services/UserServices/DeleteUserService"; import DeleteUserService from "../services/UserServices/DeleteUserService";
import ListUserParamiterService from "../services/UserServices/ListUserParamiterService"; import ListUser from "../services/UserServices/ListUserParamiterService";
import User from "../models/User"; import User from "../models/User";
import { get, set } from "../helpers/RedisClient";
import { import {
startWhoIsOnlineMonitor, startWhoIsOnlineMonitor,
stopWhoIsOnlineMonitor stopWhoIsOnlineMonitor
} from "../helpers/WhoIsOnlineMonitor"; } from "../helpers/WhoIsOnlineMonitor";
import UserOnlineTIme from "../models/UserOnlineTime"; import UserOnlineTIme from "../models/UserOnlineTime";
import ListUser from "../services/UserServices/ListUserParamiterService";
import { format, subMonths } from "date-fns";
import { ptBR } from "date-fns/locale";
import CountTicketsByUserQueue from "../services/UserServices/CountTicketsByUserQueue";
import { splitDateTime } from "../helpers/SplitDateTime";
import ListUserByWhatsappQueuesService from "../services/UserServices/ListUserByWhatsappQueuesService";
import { json } from "sequelize";
import { getSettingValue } from "../helpers/WhaticketSettings";
type IndexQuery = { type IndexQuery = {
searchParam: string; searchParam: string;
pageNumber: string; pageNumber: string;
profile?: string; profile?: string;
userId: string;
}; };
export const index = async (req: Request, res: Response): Promise<Response> => { export const index = async (req: Request, res: Response): Promise<Response> => {
@ -38,8 +47,17 @@ export const index = async (req: Request, res: Response): Promise<Response> => {
if (req.user.profile !== "master") { if (req.user.profile !== "master") {
let auxUsers: Array<object> = []; let auxUsers: Array<object> = [];
// for (var user of users) {
// if (user.profile !== 'master') {
// auxUsers.push(user)
// }
// }
for (var user of users) { for (var user of users) {
if (user.profile !== "master") { if (user.profile !== "master") {
if (req.user.profile == "supervisor" && user.profile == "admin")
continue;
auxUsers.push(user); auxUsers.push(user);
} }
} }
@ -70,25 +88,70 @@ export const index = async (req: Request, res: Response): Promise<Response> => {
// return res.json({ users, count, hasMore }); // return res.json({ users, count, hasMore });
}; };
// export const usersByWhatsappQueue = async (req: Request, res: Response): Promise<Response> => {
// const { profile } = req.query as IndexQuery;
// const users = await ListUser({
// profile
// });
// return res.json({ users });
// };
export const all = async (req: Request, res: Response): Promise<Response> => { export const all = async (req: Request, res: Response): Promise<Response> => {
const { profile } = req.query as IndexQuery; const { userId, profile } = req.query as IndexQuery;
const users = await ListUser({ console.log(
profile "userId: ",
}); userId,
" | profile: ",
profile,
' | getSettingValue("queueTransferByWhatsappScope")?.value: ',
getSettingValue("queueTransferByWhatsappScope")?.value
);
return res.json({ users }); if (getSettingValue("queueTransferByWhatsappScope")?.value == "enabled") {
const obj = await ListUserByWhatsappQueuesService(
userId,
'"admin", "user", "supervisor"'
);
const usersByWhatsqueue = obj.users;
const queues = obj.queues;
let userIds = usersByWhatsqueue.map((w: any) => w.userId);
const users = await ListUser({
userIds
});
return res.json({ users, queues });
} else {
const users = await ListUser({
profile
});
return res.json({ users });
}
}; };
export const store = async (req: Request, res: Response): Promise<Response> => { export const store = async (req: Request, res: Response): Promise<Response> => {
const { email, password, name, profile, queueIds } = req.body; const { email, password, name, profile, positionCompany, queueIds } =
req.body;
console.log("===========> req.url: ", req.url);
if ( if (
req.url === "/user" &&
getSettingValue("userCreation")?.value == "disabled" &&
req.user.profile == "admin"
) {
throw new AppError("ERR_NO_PERMISSION", 403);
} else if (
req.url === "/signup" && req.url === "/signup" &&
(await CheckSettingsHelper("userCreation")) === "disabled" getSettingValue("userCreation")?.value == "disabled"
) { ) {
throw new AppError("ERR_USER_CREATION_DISABLED", 403); throw new AppError("ERR_USER_CREATION_DISABLED", 403);
} else if (req.url !== "/signup" && req.user.profile !== "master") { } else if (req.user.profile !== "master") {
throw new AppError("ERR_NO_PERMISSION", 403); throw new AppError("ERR_NO_PERMISSION", 403);
} }
@ -96,6 +159,7 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
email, email,
password, password,
name, name,
positionCompany,
profile, profile,
queueIds queueIds
}); });
@ -149,14 +213,89 @@ export const update = async (
req: Request, req: Request,
res: Response res: Response
): Promise<Response> => { ): Promise<Response> => {
if (req.user.profile !== "admin" && req.user.profile !== "master") { if (
req.user.profile !== "admin" &&
req.user.profile !== "master" &&
req.user.profile !== "supervisor"
) {
throw new AppError("ERR_NO_PERMISSION", 403); throw new AppError("ERR_NO_PERMISSION", 403);
} }
const { userId } = req.params; const { userId } = req.params;
const userData = req.body; const userData = req.body;
const user = await UpdateUserService({ userData, userId }); const dateToday = splitDateTime(
new Date(format(new Date(), "yyyy-MM-dd HH:mm:ss", { locale: ptBR }))
);
const currentDate = new Date();
const tenMonthsAgo = subMonths(currentDate, 10);
const formattedDate = format(tenMonthsAgo, "yyyy-MM-dd");
console.log("dateToday.fullDate: ", dateToday.fullDate);
console.log("formattedDate 10 months ago: ", formattedDate);
const openByUserOnQueue: any[] = await CountTicketsByUserQueue({
startDate: formattedDate,
endDate: dateToday.fullDate,
status: "open",
clientChatStart: true,
userId: userId
});
// console.log('------> openByUserOnQueue: ', openByUserOnQueue)
// console.log()
// console.log('------> 1 userData.queueIds: ', userData.queueIds)
let userQueuesAttendance = [];
if ((openByUserOnQueue && openByUserOnQueue.length) > 0) {
userQueuesAttendance = openByUserOnQueue.filter(
(e: any) => !userData.queueIds.includes(e.queueId)
);
if (userQueuesAttendance && userQueuesAttendance.length > 0) {
const queueInAttendance = userQueuesAttendance.map(e => e.queueId);
const mergedSet = new Set([...userData.queueIds, ...queueInAttendance]);
// Convert the Set back to an array
userData.queueIds = Array.from(mergedSet);
// console.log('------> 2 userData.queueIds: ', userData.queueIds)
}
}
let user: any = await UpdateUserService({ userData, userId });
if (user?.name?.trim() == "botqueue") {
let botInfo;
if (
user?.queues?.length > 0 &&
user.queues[0]?.name?.trim() == "botqueue"
) {
botInfo = JSON.stringify({
userId: user.id,
queueId: user.queues[0].id,
botIsOnQueue: true
});
botInfo = JSON.parse(botInfo);
await set("botInfo", botInfo);
} else if (
user?.queues?.length == 0 ||
user.queues[0]?.name?.trim() != "botqueue"
) {
botInfo = JSON.stringify({
userId: user.id,
queueId: 0,
botIsOnQueue: false
});
botInfo = JSON.parse(botInfo);
await set("botInfo", botInfo);
}
}
const io = getIO(); const io = getIO();
io.emit("user", { io.emit("user", {
@ -164,6 +303,8 @@ export const update = async (
user user
}); });
user.userQueuesAttendance = userQueuesAttendance;
return res.status(200).json(user); return res.status(200).json(user);
}; };

View File

@ -4,76 +4,102 @@ import ShowWhatsAppService from "../services/WhatsappService/ShowWhatsAppService
import { logger } from "../utils/logger"; import { logger } from "../utils/logger";
import * as Sentry from "@sentry/node"; import * as Sentry from "@sentry/node";
import Whatsapp from "../models/Whatsapp"; import Whatsapp from "../models/Whatsapp";
import omnihitDashboardSession from "../helpers/OmnhitDashboardSession";
// type IndexQuery = { // type IndexQuery = {
// centro_custo: string; // centro_custo: string;
// }; // };
export const wbotMonitorRemote = async (req: Request, res: Response): Promise<Response> => { export const wbotMonitorRemote = async (
req: Request,
res: Response
): Promise<Response> => {
const { action, whatsappId, reason } = req.body;
const { action, whatsappId, reason } = req.body console.log(
"action: ",
action,
" | whatsappId: ",
whatsappId,
" | reason: ",
reason
);
console.log('-----------> ACTION: ', req.body['action']) console.log("-----------> ACTION: ", req.body["action"]);
const whatsapp: any = await Whatsapp.findByPk(whatsappId, { raw: true }) const whatsapp: any = await Whatsapp.findByPk(whatsappId, { raw: true });
if (whatsapp) {
if (action === 'disconnected') {
logger.info(`Disconnected session: ${whatsapp.name}, reason: ${reason}`);
}
const io = getIO();
io.emit("whatsappSession", {
action: "update",
session: whatsapp
});
if (whatsapp) {
if (action === "disconnected") {
logger.info(`Disconnected session: ${whatsapp.name}, reason: ${reason}`);
} }
return res.status(200).json({ "message": "Ok" }); let data: any = {};
}; data.whatsapp = { ...whatsapp, DB: process.env.DB_NAME };
data.action = "update";
await omnihitDashboardSession(data);
export const wbotMonitorQrcodeRemote = async (req: Request, res: Response): Promise<Response> => {
const { whatsappId } = req.body
console.log('-----------> QRCODE MONITOR whatsappId: ', req.body['whatsappId'])
const whatsapp: any = await Whatsapp.findByPk(whatsappId, { raw: true })
// let whatsapp = await ShowWhatsAppService(whatsappId)
if (whatsapp) {
const io = getIO();
io.emit("whatsappSession", {
action: "update",
session: whatsapp
});
}
return res.status(200).json({ "message": "Ok" });
};
export const wbotMonitorQrcodeNumberRead = async (req: Request, res: Response): Promise<Response> => {
const { number } = req.body
console.log('-----------> number read: ', number)
const io = getIO(); const io = getIO();
io.emit("whatsappSession", { io.emit("whatsappSession", {
action: "error", action: "update",
msg: `Numero lido: ${number} \nEssa sessão de whatsapp foi desconectada porque o numero que esta descrito no nome dessa sessão não corresponde ao numero lido!` session: whatsapp
}); });
}
return res.status(200).json({ "message": "Ok" }); return res.status(200).json({ message: "Ok" });
}; };
export const wbotMonitorQrcodeRemote = async (
req: Request,
res: Response
): Promise<Response> => {
const { whatsappId } = req.body;
console.log(
"-----------> QRCODE MONITOR whatsappId: ",
req.body["whatsappId"]
);
const whatsapp: any = await Whatsapp.findByPk(whatsappId, { raw: true });
// let whatsapp = await ShowWhatsAppService(whatsappId)
if (whatsapp) {
let data: any = {};
data.whatsapp = { ...whatsapp, DB: process.env.DB_NAME };
data.action = "update";
await omnihitDashboardSession(data);
}
if (whatsapp) {
const io = getIO();
io.emit("whatsappSession", {
action: "update",
session: whatsapp
});
}
return res.status(200).json({ message: "Ok" });
};
export const wbotMonitorQrcodeNumberRead = async (
req: Request,
res: Response
): Promise<Response> => {
const { number } = req.body;
console.log("-----------> number read: ", number);
const msg = `Numero lido: ${number} \nEssa sessão de whatsapp foi desconectada porque o numero que esta descrito no nome dessa sessão não corresponde ao numero lido!`;
let data: any = {};
data.msg = msg;
data.action = "error";
await omnihitDashboardSession(data);
const io = getIO();
io.emit("whatsappSession", {
action: "error",
msg: `Numero lido: ${number} \nEssa sessão de whatsapp foi desconectada porque o numero que esta descrito no nome dessa sessão não corresponde ao numero lido!`
});
return res.status(200).json({ message: "Ok" });
};

View File

@ -14,13 +14,34 @@ import UpdateWhatsAppService from "../services/WhatsappService/UpdateWhatsAppSer
import AppError from "../errors/AppError"; import AppError from "../errors/AppError";
import getNumberFromName from "../helpers/GetNumberSequence"; import getNumberFromName from "../helpers/GetNumberSequence";
import phoneNumberStart from "../helpers/PhoneNumberStatusCode" import phoneNumberStart from "../helpers/PhoneNumberStatusCode";
import path from 'path'; import path, { join } from "path";
import validatePhoneName from "../helpers/ValidatePhoneName"; import validatePhoneName from "../helpers/ValidatePhoneName";
import postData from "../helpers/AxiosPost"; import postData from "../helpers/AxiosPost";
import Whatsapp from "../models/Whatsapp"; import Whatsapp from "../models/Whatsapp";
import Message from "../models/Message";
import FindOrCreateTicketService from "../services/TicketServices/FindOrCreateTicketService";
import {
handleMessage,
handleMsgAck,
verifyContact,
verifyMessage
} from "../services/WbotServices/wbotMessageListener";
import Contact from "../models/Contact";
import CreateOrUpdateContactService from "../services/ContactServices/CreateOrUpdateContactService";
import GetDefaultWhatsApp from "../helpers/GetDefaultWhatsApp";
import ShowUserService from "../services/UserServices/ShowUserService";
import fs from "fs";
import receiveWhatsAppMediaOfficialAPI from "../helpers/ReceiveWhatsAppMediaOfficialAPI";
import whatsappOfficialAPI from "../helpers/WhatsappOfficialAPI";
import whatsappOfficialNumberInfo from "../helpers/WhatsappOfficialNumberInfo";
import { getSettingValue } from "../helpers/WhaticketSettings";
import ListWhatsAppsNumber from "../services/WhatsappService/ListWhatsAppsNumber";
import SettingTicket from "../models/SettingTicket";
import { Op } from "sequelize";
interface WhatsappData { interface WhatsappData {
name: string; name: string;
@ -31,16 +52,266 @@ interface WhatsappData {
farewellMessage?: string; farewellMessage?: string;
status?: string; status?: string;
isDefault?: boolean; isDefault?: boolean;
isOfficial?: boolean;
phoneNumberId?: string;
wabaId?: string;
} }
let count: number = 0;
export const index = async (req: Request, res: Response): Promise<Response> => { export const index = async (req: Request, res: Response): Promise<Response> => {
const whatsapps = await ListWhatsAppsService(); let whatsapps = await ListWhatsAppsService();
if (getSettingValue("whatsaAppCloudApi")?.value == "enabled") {
// Atualizar isso quando tiver tempo
if (count > 12) count = 0;
if (count == 0) {
for (let i in whatsapps) {
const { id, wabaId, isOfficial } = whatsapps[i];
if (isOfficial && wabaId) {
try {
const info = await whatsappOfficialNumberInfo(wabaId);
if (info) {
const whatsapp = await Whatsapp.findByPk(id);
if (whatsapp) {
whatsapp.update({
classification: info.quality_rating
});
whatsapps[i].classification = info.quality_rating;
}
}
} catch (error) {
console.log(
"error on try update classification number from oficial whatsapp in WhatsappController.ts: ",
error
);
}
}
}
}
console.log("count: ", count);
count++;
}
return res.status(200).json(whatsapps); return res.status(200).json(whatsapps);
}; };
export const whatsAppOfficialMatchQueue = async (
req: Request,
res: Response
): Promise<Response> => {
const { userId, queueId }: any = req.query;
let whatsapps = await GetDefaultWhatsApp({ userId, queueId });
if (whatsapps && Array.isArray(whatsapps)) {
const uniqueWhatsApps = whatsapps.filter(
(whatsapp, index, self) =>
index === self.findIndex(w => w.number === whatsapp.number)
);
whatsapps = uniqueWhatsApps;
}
return res.status(200).json(whatsapps);
};
export const whatsAppOfficialMatchQueueUser = async (
req: Request,
res: Response
): Promise<Response> => {
const { userId, queueId }: any = req.query;
let whatsApps: any = await ListWhatsAppsService();
let user: any = await ShowUserService(userId);
// console.log(JSON.stringify(user, null, 2));
let queuesConnected = whatsApps
.filter((w: any) => w.status === "CONNECTED")
.map((item: any) => {
const { queues } = item;
return {
queues: queues.map((q: any) => {
return { id: q.id };
})
};
})
.flatMap((item: any) => item.queues.map((queue: any) => queue.id));
queuesConnected = [...new Set(queuesConnected)].map(q => {
return { id: q };
});
const userQueues = user.queues.map((item: any) => {
const { id, name, color } = item;
return {
id,
name,
color,
disable: queuesConnected.find((queue: any) => queue.id === id)
? false
: true
};
});
return res.status(200).json(userQueues);
};
export const media = async (req: Request, res: Response) => {
const { filename } = req.params;
const filePath = join(__dirname, "..", "..", "..", "..", "public", filename);
console.log("filePath: ", filePath);
console.log(filename);
if (!fs.existsSync(filePath)) {
return res.status(404).json({ message: "File not folund!" });
}
// Set appropriate headers for the download.
res.setHeader("Content-Disposition", `attachment; filename=${filename}`);
res.sendFile(filePath);
};
export const weebhook = async (
req: Request,
res: Response
): Promise<Response> => {
// console.log(JSON.stringify(req.body, null, 2));
console.log("req.method: ", req.method);
if (req.method == "GET") {
/**
* UPDATE YOUR VERIFY TOKEN
*This will be the Verify Token value when you set up webhook
**/
const verify_token = process.env.VERIFY_TOKEN;
// Parse params from the webhook verification request
let mode = req.query["hub.mode"];
let token = req.query["hub.verify_token"];
let challenge = req.query["hub.challenge"];
// Check if a token and mode were sent
if (mode && token) {
// Check the mode and token sent are correct
if (mode === "subscribe" && token === verify_token) {
// Respond with 200 OK and challenge token from the request
console.log("WEBHOOK_VERIFIED");
return res.status(200).send(challenge);
} else {
// Responds with '403 Forbidden' if verify tokens do not match
return res.sendStatus(403);
}
}
return res.sendStatus(500);
}
// MESSAGE
if (req.body.object) {
if (
req.body.entry &&
req.body.entry[0].changes &&
req.body.entry[0].changes[0] &&
req.body.entry[0].changes[0].value.messages &&
req.body.entry[0].changes[0].value.messages[0]
) {
const message = req.body.entry[0].changes[0].value.messages[0];
const contact_from = message.from; // extract the phone number from the webhook payload
const contact_to =
req.body.entry[0].changes[0].value.metadata.display_phone_number;
let type = message.type;
let wbot = {};
let msg = {};
let contacts = req.body.entry[0].changes[0].value.contacts[0];
msg = {
...msg,
id: { id: message.id },
fromMe: false,
type: type,
read: false,
hasMedia: false
};
// NEW
const whatsapp = await ShowWhatsAppService(null, {
number: contact_to
});
if (type == "text") {
type = "chat";
msg = {
...msg,
body: message.text.body, // extract the message text from the webhook payload,
type
};
} else {
const mediaId = message[message.type].id;
const mimetype = message[message.type].mime_type;
let filename = await receiveWhatsAppMediaOfficialAPI(
mediaId,
whatsapp.phoneNumberId
);
if (!filename) throw new AppError("There was an error");
msg = {
...msg,
hasMedia: true
};
wbot = { ...wbot, media: { filename, mimetype } };
}
msg = { ...msg, phoneNumberId: whatsapp.phoneNumberId };
console.log("from: ", contact_from);
console.log("to: ", contact_to);
console.log("msg type: ", type);
wbot = {
...wbot,
id: whatsapp.id,
msgContact: {
number: contact_from,
name: contacts?.profile?.name
},
chat: { isGroup: false, unreadCount: 1 },
quotedMsg: message && message?.context ? message.context.id : undefined
};
handleMessage(msg, wbot, true);
return res.sendStatus(200);
}
// STATUS MESSAGE SENT
else if (
req.body.entry &&
req.body.entry[0].changes &&
req.body.entry[0].changes[0] &&
req.body.entry[0].changes[0].value.statuses &&
req.body.entry[0].changes[0].value.statuses[0]
) {
const id = req.body.entry[0].changes[0].value.statuses[0].id;
const ack = req.body.entry[0].changes[0].value.statuses[0].status;
handleMsgAck(id, ack, true);
}
}
return res.sendStatus(200);
};
export const store = async (req: Request, res: Response): Promise<Response> => { export const store = async (req: Request, res: Response): Promise<Response> => {
const { let {
name, name,
status, status,
isDefault, isDefault,
@ -48,31 +319,39 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
farewellMessage, farewellMessage,
queueIds, queueIds,
url, url,
urlApi urlApi,
phoneNumberId,
wabaId,
isOfficial
}: WhatsappData = req.body; }: WhatsappData = req.body;
// console.log( name,
// status,
// isDefault,
// greetingMessage,
// farewellMessage,
// queueIds,
// url,
// urlApi)
// console.log('getNumberFromName: ', getNumberFromName(name))
// return res.status(200);
if (req.user.profile !== "master") { if (req.user.profile !== "master") {
throw new AppError("ERR_NO_PERMISSION", 403); throw new AppError("ERR_NO_PERMISSION", 403);
} }
let validate = validatePhoneName(name) const invalid = checkWhatsAppData({
urlApi,
isOfficial,
phoneNumberId,
wabaId
});
if (validate) { if (invalid) {
return res.status(200).json({ message: validate }); return res.status(400).json(invalid);
}
if (isOfficial) {
urlApi = "";
url = "";
} else if (!isOfficial) {
phoneNumberId = "";
wabaId = "";
}
let invalidPhoneName = validatePhoneName(name);
if (invalidPhoneName) {
return res.status(200).json({ message: invalidPhoneName });
} }
const { whatsapp, oldDefaultWhatsapp } = await CreateWhatsAppService({ const { whatsapp, oldDefaultWhatsapp } = await CreateWhatsAppService({
@ -83,19 +362,22 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
isDefault, isDefault,
greetingMessage, greetingMessage,
farewellMessage, farewellMessage,
queueIds queueIds,
phoneNumberId,
wabaId,
isOfficial
}); });
console.log('whatsapp.id: ', whatsapp.id) console.log("whatsapp.id: ", whatsapp.id);
postData( `${whatsapp.urlApi}/api/session`, { if (!isOfficial) {
"app_name": process.env.APP_NAME, postData(`${whatsapp.urlApi}/api/session`, {
"whatsappId": whatsapp.id, app_name: process.env.APP_NAME,
"number": getNumberFromName(name), whatsappId: whatsapp.id,
"client_url": `${process.env.BACKEND_URL_RAW}:${process.env.PORT}` number: getNumberFromName(name),
}) client_url: `${process.env.BACKEND_URL_RAW}:${process.env.PORT}`
});
// StartWhatsAppSession(whatsapp); }
const io = getIO(); const io = getIO();
io.emit("whatsapp", { io.emit("whatsapp", {
@ -128,13 +410,31 @@ export const update = async (
const { whatsappId } = req.params; const { whatsappId } = req.params;
const whatsappData = req.body; const whatsappData = req.body;
let invalidPhoneName = validatePhoneName(whatsappData.name);
let validate = validatePhoneName(whatsappData.name) if (invalidPhoneName) {
return res.status(200).json({ message: invalidPhoneName });
}
console.log('validate', validate) const { urlApi, isOfficial, phoneNumberId, wabaId } = whatsappData;
if (validate) { const invalid = checkWhatsAppData({
return res.status(200).json({ message: validate }); urlApi,
isOfficial,
phoneNumberId,
wabaId
});
if (invalid) {
return res.status(400).json(invalid);
}
if (isOfficial) {
whatsappData.urlApi = "";
whatsappData.url = "";
} else if (!isOfficial) {
whatsappData.phoneNumberId = "";
whatsappData.wabaId = "";
} }
const { whatsapp, oldDefaultWhatsapp } = await UpdateWhatsAppService({ const { whatsapp, oldDefaultWhatsapp } = await UpdateWhatsAppService({
@ -142,13 +442,14 @@ export const update = async (
whatsappId whatsappId
}); });
if (!whatsappData?.isOfficial) {
postData( `${whatsapp.urlApi}/api/session`, { postData(`${whatsapp.urlApi}/api/session`, {
"app_name": process.env.APP_NAME, app_name: process.env.APP_NAME,
"whatsappId": whatsapp.id, whatsappId: whatsapp.id,
"number": getNumberFromName(whatsapp.name), number: getNumberFromName(whatsapp.name),
"client_url": `${process.env.BACKEND_URL_RAW}:${process.env.PORT}` client_url: `${process.env.BACKEND_URL_RAW}:${process.env.PORT}`
}) });
}
const io = getIO(); const io = getIO();
io.emit("whatsapp", { io.emit("whatsapp", {
@ -170,26 +471,67 @@ export const remove = async (
req: Request, req: Request,
res: Response res: Response
): Promise<Response> => { ): Promise<Response> => {
if (req.user.profile !== "master") { if (req.user.profile !== "master") {
throw new AppError("ERR_NO_PERMISSION", 403); throw new AppError("ERR_NO_PERMISSION", 403);
} }
const { whatsappId } = req.params; const { whatsappId } = req.params;
const whatsapp: any = await Whatsapp.findByPk(whatsappId, { raw: true }) const whatsapp: any = await Whatsapp.findByPk(whatsappId, { raw: true });
postData( `${whatsapp.urlApi}/api/session/del`, { if (!whatsapp?.isOfficial) {
"app_name": process.env.APP_NAME, postData(`${whatsapp.urlApi}/api/session/del`, {
"whatsappId": whatsappId app_name: process.env.APP_NAME,
}) whatsappId: whatsappId
});
}
let whats = await ListWhatsAppsNumber(whatsappId);
// Remove tickets business hours config
if (whats?.whatsapps?.length == 1) {
const configIds = await SettingTicket.findAll({
where: { number: whats?.whatsapps[0]?.number },
raw: true,
attributes: ["id"]
});
const whatsappTicketConfig = await SettingTicket.findOne({
where: { number: whats.whatsapps[0].number }
});
if (whatsappTicketConfig) {
try {
await SettingTicket.destroy({
where: {
id: {
[Op.in]: configIds.map(config => config.id)
}
}
});
} catch (error) {
console.log(
"Error on delete SettingTicket by number: ",
whats?.whatsapps[0]?.number
);
}
}
}
await DeleteWhatsAppService(whatsappId); await DeleteWhatsAppService(whatsappId);
removeDir(path.join(process.cwd(), '.wwebjs_auth', `session-bd_${whatsappId}`)) removeDir(
path.join(process.cwd(), ".wwebjs_auth", `session-bd_${whatsappId}`)
removeDir(path.join(process.cwd(), '.wwebjs_auth', 'sessions', `session-bd_${whatsappId}`)) );
removeDir(
path.join(
process.cwd(),
".wwebjs_auth",
"sessions",
`session-bd_${whatsappId}`
)
);
removeWbot(+whatsappId); removeWbot(+whatsappId);
@ -201,3 +543,25 @@ export const remove = async (
return res.status(200).json({ message: "Whatsapp deleted." }); return res.status(200).json({ message: "Whatsapp deleted." });
}; };
interface WhatsappDataValidate {
urlApi?: string;
isOfficial?: boolean;
phoneNumberId?: string;
wabaId?: string;
}
const checkWhatsAppData = ({
urlApi,
isOfficial,
phoneNumberId,
wabaId
}: WhatsappDataValidate) => {
if (isOfficial && (!phoneNumberId || phoneNumberId.trim() == "")) {
return { message: "Phone number Id is required!" };
} else if (isOfficial && (!wabaId || wabaId.trim() == "")) {
return { message: "WABA ID is required!" };
} else if (!isOfficial && (!urlApi || urlApi.trim() == "")) {
return { message: "urlApi is required!" };
}
};

View File

@ -0,0 +1,14 @@
import { QueryInterface, DataTypes } from "sequelize";
module.exports = {
up: (queryInterface: QueryInterface) => {
return queryInterface.addColumn("Whatsapps", "phoneNumberId", {
type: DataTypes.STRING,
allowNull: true,
});
},
down: (queryInterface: QueryInterface) => {
return queryInterface.removeColumn("Whatsapps", "phoneNumberId");
}
};

View File

@ -2,16 +2,13 @@ import { QueryInterface, DataTypes } from "sequelize";
module.exports = { module.exports = {
up: (queryInterface: QueryInterface) => { up: (queryInterface: QueryInterface) => {
return queryInterface.changeColumn("SettingTickets", "message", { return queryInterface.addColumn("Tickets", "phoneNumberId", {
type: DataTypes.STRING(3000), type: DataTypes.STRING,
allowNull: true allowNull: true
}); });
}, },
down: (queryInterface: QueryInterface) => { down: (queryInterface: QueryInterface) => {
return queryInterface.changeColumn("SettingTickets", "message", { return queryInterface.removeColumn("Tickets", "phoneNumberId");
type: DataTypes.STRING,
allowNull: true
});
} }
}; };

View File

@ -0,0 +1,14 @@
import { QueryInterface, DataTypes } from "sequelize";
module.exports = {
up: (queryInterface: QueryInterface) => {
return queryInterface.addColumn("Messages", "phoneNumberId", {
type: DataTypes.STRING,
allowNull: true
});
},
down: (queryInterface: QueryInterface) => {
return queryInterface.removeColumn("Messages", "phoneNumberId");
}
};

View File

@ -0,0 +1,15 @@
import { QueryInterface, DataTypes } from "sequelize";
module.exports = {
up: (queryInterface: QueryInterface) => {
return queryInterface.addColumn("Whatsapps", "isOfficial", {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
});
},
down: (queryInterface: QueryInterface) => {
return queryInterface.removeColumn("Whatsapps", "isOfficial");
}
};

View File

@ -0,0 +1,14 @@
import { QueryInterface, DataTypes } from "sequelize";
module.exports = {
up: (queryInterface: QueryInterface) => {
return queryInterface.addColumn("Whatsapps", "wabaId", {
type: DataTypes.STRING,
allowNull: true
});
},
down: (queryInterface: QueryInterface) => {
return queryInterface.removeColumn("Whatsapps", "wabaId");
}
};

View File

@ -0,0 +1,14 @@
import { QueryInterface, DataTypes } from "sequelize";
module.exports = {
up: (queryInterface: QueryInterface) => {
return queryInterface.addColumn("Whatsapps", "classification", {
type: DataTypes.STRING,
allowNull: true
});
},
down: (queryInterface: QueryInterface) => {
return queryInterface.removeColumn("Whatsapps", "classification");
}
};

View File

@ -0,0 +1,14 @@
import { QueryInterface, DataTypes } from "sequelize";
module.exports = {
up: (queryInterface: QueryInterface) => {
return queryInterface.addColumn("SettingTickets", "number", {
type: DataTypes.STRING,
allowNull: true
});
},
down: (queryInterface: QueryInterface) => {
return queryInterface.removeColumn("SettingTickets", "number");
}
};

View File

@ -0,0 +1,14 @@
import { QueryInterface, DataTypes } from "sequelize";
module.exports = {
up: (queryInterface: QueryInterface) => {
return queryInterface.addColumn("Users", "positionCompany", {
type: DataTypes.STRING,
allowNull: true
});
},
down: (queryInterface: QueryInterface) => {
return queryInterface.removeColumn("Users", "positionCompany");
}
};

View File

@ -0,0 +1,26 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
/*
Add altering commands here.
Return a promise to correctly handle asynchronicity.
Example:
return queryInterface.bulkInsert('People', [{
name: 'John Doe',
isBetaMember: false
}], {});
*/
},
down: (queryInterface, Sequelize) => {
/*
Add reverting commands here.
Return a promise to correctly handle asynchronicity.
Example:
return queryInterface.bulkDelete('People', null, {});
*/
}
};

View File

@ -0,0 +1,22 @@
import { QueryInterface } from "sequelize";
module.exports = {
up: (queryInterface: QueryInterface) => {
return queryInterface.bulkInsert(
"Settings",
[
{
key: "whatsaAppCloudApi",
value: "disabled",
createdAt: new Date(),
updatedAt: new Date()
}
],
{}
);
},
down: (queryInterface: QueryInterface) => {
return queryInterface.bulkDelete("Settings", {});
}
};

View File

@ -0,0 +1,22 @@
import { QueryInterface } from "sequelize";
module.exports = {
up: (queryInterface: QueryInterface) => {
return queryInterface.bulkInsert(
"Settings",
[
{
key: "queueTransferByWhatsappScope",
value: "disabled",
createdAt: new Date(),
updatedAt: new Date()
}
],
{}
);
},
down: (queryInterface: QueryInterface) => {
return queryInterface.bulkDelete("Settings", {});
}
};

View File

@ -0,0 +1,22 @@
import { QueryInterface } from "sequelize";
module.exports = {
up: (queryInterface: QueryInterface) => {
return queryInterface.bulkInsert(
"Settings",
[
{
key: "hasCampaign",
value: "disabled",
createdAt: new Date(),
updatedAt: new Date()
}
],
{}
);
},
down: (queryInterface: QueryInterface) => {
return queryInterface.bulkDelete("Settings", {});
}
};

View File

@ -12,6 +12,7 @@ import {
import ptBR from "date-fns/locale/pt-BR"; import ptBR from "date-fns/locale/pt-BR";
import { splitDateTime } from "./SplitDateTime"; import { splitDateTime } from "./SplitDateTime";
import Whatsapp from "../models/Whatsapp";
const fsPromises = require("fs/promises"); const fsPromises = require("fs/promises");
const fs = require("fs"); const fs = require("fs");
@ -24,37 +25,43 @@ const AutoCloseTickets = async () => {
// if (!botInfo.userIdBot) return // if (!botInfo.userIdBot) return
const ticketExpiration = await SettingTicket.findOne({ const whatsapps = await Whatsapp.findAll({ group: ["number"] });
where: { key: "ticketExpiration" }
});
if (ticketExpiration && ticketExpiration.value == "enabled") { for (const whatsapp of whatsapps) {
const startTime = splitDateTime( // console.log("-------> whatsapp: ", JSON.stringify(whatsapps, null, 6));
new Date(
_format(new Date(ticketExpiration.startTime), "yyyy-MM-dd HH:mm:ss", {
locale: ptBR
})
)
);
const seconds = timeStringToSeconds(startTime.fullTime); const ticketExpiration = await SettingTicket.findOne({
where: { key: "ticketExpiration", number: whatsapp.number }
// console.log("Ticket seconds: ", seconds);
let tickets: any = await ListTicketTimeLife({
timeseconds: seconds,
status: "open"
}); });
// console.log("tickets: ", tickets); if (ticketExpiration && ticketExpiration.value == "enabled") {
const startTime = splitDateTime(
new Date(
_format(
new Date(ticketExpiration.startTime),
"yyyy-MM-dd HH:mm:ss",
{
locale: ptBR
}
)
)
);
for (let i = 0; i < tickets.length; i++) { const seconds = timeStringToSeconds(startTime.fullTime);
await UpdateTicketService({ let tickets: any = await ListTicketTimeLife({
ticketData: { status: "closed", statusChatEnd: "FINALIZADO" }, timeseconds: seconds,
ticketId: tickets[i].ticket_id, status: "open",
msg: ticketExpiration.message number: whatsapp.number
}); });
for (let i = 0; i < tickets.length; i++) {
await UpdateTicketService({
ticketData: { status: "closed", statusChatEnd: "FINALIZADO" },
ticketId: tickets[i].ticket_id,
msg: ticketExpiration.message
});
}
} }
} }
} catch (error) { } catch (error) {

View File

@ -1,37 +1,26 @@
const fsPromises = require("fs/promises"); const fsPromises = require("fs/promises");
const fs = require('fs') const fs = require("fs");
import ListUsersService from "../services/UserServices/ListUsersService" import ListUsersService from "../services/UserServices/ListUsersService";
import { get } from "./RedisClient";
const _botIsOnQueue = async (botName: string) => { const _botIsOnQueue = async (botName: string) => {
const { users, count, hasMore } = await ListUsersService({searchParam:`${botName}`,pageNumber:1}); const botInfo = await get("botInfo");
let botIsOnQueue = false
let userIdBot = null
let queueId = null
if(users.length > 0){
try {
console.log('----------------- bot queue id: ', Object(users)[0]["queues"][0].id)
queueId = Object(users)[0]["queues"][0].id;
userIdBot = Object(users)[0].id
botIsOnQueue = true
}catch(err){
console.log('O usuário botqueue não está em nenhuma fila err: ',err)
}
}
else{
console.log('Usuário botqueue não existe!')
}
return { userIdBot: userIdBot, botQueueId: queueId, isOnQueue: botIsOnQueue }
if (
botInfo &&
botInfo?.userId &&
botInfo?.queueId &&
botInfo?.botIsOnQueue == true
) {
return {
userIdBot: botInfo.userId,
botQueueId: botInfo.queueId,
isOnQueue: botInfo.botIsOnQueue
};
} }
return { userIdBot: null, botQueueId: null, isOnQueue: false };
};
export default _botIsOnQueue; export default _botIsOnQueue;

View File

@ -0,0 +1,3 @@
export function bytesToMB(bytes: number | string) {
return (+bytes / (1024 * 1024)).toFixed(2);
}

View File

@ -3,16 +3,22 @@ import AppError from "../errors/AppError";
import Ticket from "../models/Ticket"; import Ticket from "../models/Ticket";
import ListWhatsAppsNumber from "../services/WhatsappService/ListWhatsAppsNumber"; import ListWhatsAppsNumber from "../services/WhatsappService/ListWhatsAppsNumber";
import { getSettingValue } from "./WhaticketSettings"; import { getSettingValue } from "./WhaticketSettings";
import ListWhatsAppsForQueueService from "../services/WhatsappService/ListWhatsAppsForQueueService";
const CheckContactOpenTickets = async ( const CheckContactOpenTickets = async (
contactId: number, contactId: number,
whatsappId: number | string whatsappId: number | string,
): Promise<void> => { handle?: boolean
): Promise<void | any> => {
let ticket; let ticket;
if (getSettingValue("oneContactChatWithManyWhats")?.value == "enabled") { if (getSettingValue("oneContactChatWithManyWhats")?.value == "enabled") {
let whats = await ListWhatsAppsNumber(whatsappId); let whats = await ListWhatsAppsNumber(whatsappId);
console.log("contactId: ", contactId, " | whatsappId: ", whatsappId);
console.log("WHATS: ", JSON.stringify(whats, null, 6));
ticket = await Ticket.findOne({ ticket = await Ticket.findOne({
where: { where: {
[Op.and]: [ [Op.and]: [
@ -22,6 +28,8 @@ const CheckContactOpenTickets = async (
] ]
} }
}); });
console.log("TICKET: ", JSON.stringify(ticket, null, 6));
} else { } else {
ticket = await Ticket.findOne({ ticket = await Ticket.findOne({
where: { contactId, status: { [Op.or]: ["open", "pending"] } } where: { contactId, status: { [Op.or]: ["open", "pending"] } }
@ -29,8 +37,12 @@ const CheckContactOpenTickets = async (
} }
if (ticket) { if (ticket) {
if (handle) return true;
throw new AppError("ERR_OTHER_OPEN_TICKET"); throw new AppError("ERR_OTHER_OPEN_TICKET");
} }
}; };
export default CheckContactOpenTickets; export default CheckContactOpenTickets;

View File

@ -0,0 +1,22 @@
import { Op } from "sequelize";
import { sub, subHours } from "date-fns";
import Message from "../models/Message";
import Ticket from "../models/Ticket";
async function checkLastClientMsg24hs(ticket: Ticket) {
return await Message.findAll({
attributes: ["createdAt", "body"],
where: {
contactId: ticket.contactId,
phoneNumberId: ticket.phoneNumberId,
fromMe: false,
createdAt: {
[Op.between]: [+subHours(new Date(), 24), +new Date()]
}
},
order: [["createdAt", "DESC"]],
limit: 1
});
}
export default checkLastClientMsg24hs;

View File

@ -0,0 +1,54 @@
import ListTicketTimeLife from "../services/TicketServices/ListTicketTimeLife";
import UpdateTicketService from "../services/TicketServices/UpdateTicketService";
import BotIsOnQueue from "./BotIsOnQueue";
const fsPromises = require("fs/promises");
const fs = require('fs')
let timer: any
const CloseBotTickets = async () => {
try {
const botInfo = await BotIsOnQueue('botqueue')
if (!botInfo.userIdBot) return
let tickets: any = await ListTicketTimeLife({ timeseconds: 60, status: 'open', userId: botInfo.userIdBot })
console.log('tickets: ', tickets)
for (let i = 0; i < tickets.length; i++) {
await UpdateTicketService({
ticketData: { 'status': 'closed', 'userId': botInfo.userIdBot, 'statusChatEnd': 'FINALIZADO' },
ticketId: tickets[i].ticket_id
});
}
} catch (error) {
console.log('There was an error on try close the bot tickets: ', error)
}
}
const schedule = async () => {
try {
clearInterval(timer);
await CloseBotTickets()
} catch (error) {
console.log('error on schedule: ', error)
}
finally {
timer = setInterval(schedule, 60000);
}
}
timer = setInterval(schedule, 60000);
export default schedule;

View File

@ -0,0 +1,48 @@
import ffmpeg from "fluent-ffmpeg";
import util from "util";
import { exec as execCallback } from "child_process";
const exec = util.promisify(execCallback);
async function convertAudioToOgg(
inputFile: string,
outputFile: string
): Promise<void> {
try {
const command = `ffmpeg -i ${inputFile} -c:a libopus ${outputFile}.ogg && rm ${inputFile}`;
const { stdout, stderr } = await exec(command);
console.log("Conversion finished");
console.log("stdout:", stdout);
console.error("stderr:", stderr);
} catch (error) {
console.error("Error:", error);
throw error;
}
}
function convertAudioToWav(
inputFile: string,
outputFile: string
): Promise<void> {
return new Promise<void>((resolve, reject) => {
const command = ffmpeg(inputFile)
.audioCodec("pcm_s16le") // Set the audio codec to libvorbis (OGG)
.format("wav") // Set the output format to OGG
.on("end", () => {
console.log("Conversion finished");
resolve(); // Resolve the promise when the conversion is successful
})
.on("error", (err: any) => {
console.error("Error:", err);
reject(err); // Reject the promise if there is an error
});
// Save the output to the specified file
command.save(outputFile);
});
}
export { convertAudioToWav, convertAudioToOgg };

View File

@ -1,55 +1,64 @@
import AppError from "../errors/AppError"; import AppError from "../errors/AppError";
import Whatsapp from "../models/Whatsapp"; import Whatsapp from "../models/Whatsapp";
import WhatsappQueue from "../models/WhatsappQueue" import WhatsappQueue from "../models/WhatsappQueue";
import UserQueue from "../models/UserQueue" import UserQueue from "../models/UserQueue";
import { Op, where } from "sequelize"; import { Op, where } from "sequelize";
import wbotByUserQueue from '../helpers/GetWbotByUserQueue' import wbotByUserQueue from "../helpers/GetWbotByUserQueue";
// import WhatsQueueIndex from "./WhatsQueueIndex";
import { WhatsIndex } from "./LoadBalanceWhatsSameQueue"; import { WhatsIndex } from "./LoadBalanceWhatsSameQueue";
const GetDefaultWhatsApp = async (userId?: string | number): Promise<Whatsapp> => { interface Request {
userId?: string | number;
queueId?: string | number;
}
//const GetDefaultWhatsApp = async (userId?: string | number): Promise<Whatsapp> => {
const GetDefaultWhatsApp = async ({
userId,
queueId
}: Request): Promise<any> => {
// test del // test del
let defaultWhatsapp = await Whatsapp.findOne({ let defaultWhatsapp = await Whatsapp.findOne({
where: { isDefault: true } where: { isDefault: true }
}); });
if (!defaultWhatsapp) { if (!defaultWhatsapp) {
if (userId) { if (userId) {
let whatsapps = await wbotByUserQueue({ userId, queueId });
let whatsapps = await wbotByUserQueue(userId) if (userId && queueId) {
if (whatsapps.length > 0) {
if (whatsapps.length > 1) { if (whatsapps.length > 1) {
let whatsAppOfficial: any = whatsapps.find(
(w: any) => w.phoneNumberId && w.phoneNumberId.trim().length > 0
);
defaultWhatsapp = whatsapps[+WhatsIndex(whatsapps)] if (whatsAppOfficial) {
return whatsapps;
}
} }
else {
defaultWhatsapp = whatsapps[0]
}
}// Quando o usuário não está em nenhuma fila
else {
defaultWhatsapp = await Whatsapp.findOne({ where: { status: 'CONNECTED' } });
} }
if (whatsapps.length > 0) {
if (whatsapps.length > 1) {
defaultWhatsapp = whatsapps[+WhatsIndex(whatsapps)];
} else {
defaultWhatsapp = whatsapps[0];
}
} // Quando o usuário não está em nenhuma fila
else {
defaultWhatsapp = await Whatsapp.findOne({
where: { status: "CONNECTED" }
});
}
} else {
defaultWhatsapp = await Whatsapp.findOne({
where: { status: "CONNECTED" }
});
} }
else {
defaultWhatsapp = await Whatsapp.findOne({ where: { status: 'CONNECTED' } });
}
} }
if (!defaultWhatsapp) { if (!defaultWhatsapp) {
@ -58,20 +67,6 @@ const GetDefaultWhatsApp = async (userId?: string | number): Promise<Whatsapp> =
return defaultWhatsapp; return defaultWhatsapp;
// //
// const defaultWhatsapp = await Whatsapp.findOne({
// where: { isDefault: true }
// });
// if (!defaultWhatsapp) {
// throw new AppError("ERR_NO_DEF_WAPP_FOUND");
// }
// return defaultWhatsapp;
}; };
export default GetDefaultWhatsApp; export default GetDefaultWhatsApp;

View File

@ -7,7 +7,7 @@ const GetTicketWbot = async (ticket: Ticket): Promise<Session> => {
if (!ticket.whatsappId) { if (!ticket.whatsappId) {
const defaultWhatsapp = await GetDefaultWhatsApp(); const defaultWhatsapp = await GetDefaultWhatsApp({});
await ticket.$set("whatsapp", defaultWhatsapp); await ticket.$set("whatsapp", defaultWhatsapp);
} }

View File

@ -1,50 +1,64 @@
import UserQueue from "../models/UserQueue"; import UserQueue from "../models/UserQueue";
import WhatsappQueue from "../models/WhatsappQueue"; import WhatsappQueue from "../models/WhatsappQueue";
import Whatsapp from "../models/Whatsapp"; import Whatsapp from "../models/Whatsapp";
import { Op, where } from "sequelize"; import { Op, where } from "sequelize";
const wbotByUserQueue = async (userId: string | number, status: string = 'CONNECTED') => { interface Request {
userId: string | number;
let defaultWhatsapp: Whatsapp[] = [] status?: string;
queueId?: string | number;
try{
const queue = await UserQueue.findOne(
{
where: { userId: userId },
raw:true,
attributes: ['queueId']
});
if(queue?.queueId){
// Pega todas conexões de whatsaap que estão adicionadas à uma fila
const whatsappQueues = await WhatsappQueue.findAll(
{
where: { queueId: `${queue?.queueId }`},
raw:true,
attributes: ['whatsappId']
});
defaultWhatsapp = await Whatsapp.findAll({
where: {
id: {[Op.in]: whatsappQueues.map((w) => { return w.whatsappId })},
status: status
}
});
}
}catch(err){
console.log('There was an error on select a whatsapp id by user queue: ', err)
}
return defaultWhatsapp;
} }
export default wbotByUserQueue; const wbotByUserQueue = async ({
userId,
status = "CONNECTED",
queueId
}: Request): Promise<any> => {
let defaultWhatsapp: Whatsapp[] = [];
try {
let query: any = {};
query = { userId };
if (queueId) {
query = { ...query, queueId };
}
const queue = await UserQueue.findOne({
where: query,
raw: true,
attributes: ["queueId"]
});
if (queue?.queueId) {
// Pega todas conexões de whatsaap que estão adicionadas à uma fila
const whatsappQueues = await WhatsappQueue.findAll({
where: { queueId: `${queue?.queueId}` },
raw: true,
attributes: ["whatsappId"]
});
defaultWhatsapp = await Whatsapp.findAll({
where: {
id: {
[Op.in]: whatsappQueues.map(w => {
return w.whatsappId;
})
},
status: status
}
});
}
} catch (err) {
console.log(
"There was an error on select a whatsapp id by user queue: ",
err
);
}
return defaultWhatsapp;
};
export default wbotByUserQueue;

View File

@ -0,0 +1,24 @@
import axios from "axios";
async function omnihitDashboardSession(data: any) {
// console.log('DATA: ', data)
try {
await axios.post(
`${process.env.URL_DASHBOARD_SESSIONS}/api/v1/omnihit/monitor`,
data,
{
headers: {
Authorization: `Bearer ${process.env.TOKEN_DASHBOARD_SESSIONS}`
}
}
);
} catch (error) {
console.log(
`Post request error to ${process.env.URL_DASHBOARD_SESSIONS}/api/v1/omnihit/monitor`
);
}
}
export default omnihitDashboardSession;

View File

@ -0,0 +1,73 @@
import axios from "axios";
import { getIO } from "../libs/socket";
import Contact from "../models/Contact";
import Ticket from "../models/Ticket";
import {
isValidMsg,
verifyMediaMessage,
verifyMessage
} from "../services/WbotServices/wbotMessageListener";
import { writeFile } from "fs";
import whatsappOfficialAPI from "./WhatsappOfficialAPI";
import path, { join } from "path";
import { promisify } from "util";
import mime from "mime";
import fs from "fs";
import { response } from "express";
const writeFileAsync = promisify(writeFile);
async function receiveWhatsAppMediaOfficialAPI(
mediaId: string,
phoneNumberId: string
) {
try {
const { data } = await whatsappOfficialAPI.get(
`/${process.env.VERSION}/${mediaId}?phone_number_id=${phoneNumberId}`
);
if (data && data?.url) {
const config: any = {
headers: {
Authorization: `Bearer ${process.env.TOKEN}`
},
responseType: "arraybuffer"
};
const filename = Date.now() + String(Math.floor(Math.random() * 1000));
const response = await axios.get(data.url, config);
const ext = response.headers["content-type"].split("/")[1];
const filename_ext = `${filename}.${ext}`;
const destPath = path.join(
__dirname,
`..`,
`..`,
`..`,
`..`,
`public`,
`${filename_ext}`
);
fs.writeFileSync(destPath, response.data);
return filename_ext;
}
} catch (error) {
console.log(
"There was an error on receiveWhatsAppMediaOfficialAPI: ",
error
);
}
return null;
}
export default receiveWhatsAppMediaOfficialAPI;

View File

@ -0,0 +1,130 @@
const Redis = require("ioredis");
const redis = new Redis(process.env.REDIS_URI);
type WhatsappData = {
whatsappId: string;
contactId: string;
identifier: string;
value?: string;
};
export async function set(key: string, value: string) {
await redis.set(key, JSON.stringify(value));
}
export async function get(key: string) {
const value: any = await redis.get(key);
return JSON.parse(value);
}
export async function createObject({
whatsappId,
contactId,
identifier,
value
}: WhatsappData) {
const key = `whatsappId:${whatsappId}:contactId:${contactId}:identifier:${identifier}`;
const result = await redis.hmset(
key,
"whatsappId",
whatsappId,
"contactId",
contactId,
"identifier",
identifier,
"value",
value
);
await redis.expire(key, 300);
}
export async function updateObject({
whatsappId,
contactId,
identifier,
value
}: WhatsappData) {
const key = `whatsappId:${whatsappId}:contactId:${contactId}:identifier:${identifier}`;
await redis.hset(key, "value", value);
}
export async function findObject(
whatsappId: string,
contactId: string,
identifier: string
) {
const key = `whatsappId:${whatsappId}:contactId:${contactId}:identifier:${identifier}`;
const result = await redis.hmget(
key,
"whatsappId",
"contactId",
"identifier",
"value"
);
return result;
}
export async function deleteObject(
whatsappId: string,
contactId: string,
identifier: string
) {
const key = `whatsappId:${whatsappId}:contactId:${contactId}:identifier:${identifier}`;
const deletedCount = await redis.del(key);
}
export async function deleteKeysWithPattern(
whatsappId: string,
contactId: string
) {
const pattern = `whatsappId:${whatsappId}:contactId:${contactId}:*`;
let cursor = "0";
do {
const [newCursor, keys] = await redis.scan(
cursor,
"MATCH",
pattern,
"COUNT",
"100"
);
for (const key of keys) {
await redis.del(key);
}
cursor = newCursor;
} while (cursor !== "0");
}
export async function getHashesWithPattern(
whatsappId: string,
contactId: string
) {
const pattern = `whatsappId:${whatsappId}:contactId:${contactId}:*`;
let cursor = "0";
const hashes = [];
do {
const [newCursor, keys] = await redis.scan(
cursor,
"MATCH",
pattern,
"COUNT",
"100"
);
for (const key of keys) {
const hash = await redis.hgetall(key);
hashes.push(hash);
}
cursor = newCursor;
} while (cursor !== "0");
return hashes;
}

View File

@ -11,179 +11,140 @@ import { convertBytes } from "./ConvertBytes";
import { deleteScheduleByTicketIdCache } from "./SchedulingNotifyCache"; import { deleteScheduleByTicketIdCache } from "./SchedulingNotifyCache";
import SchedulingNotify from "../models/SchedulingNotify"; import SchedulingNotify from "../models/SchedulingNotify";
import Ticket from "../models/Ticket"; import Ticket from "../models/Ticket";
import User from "../models/User";
import { Sequelize, Op } from "sequelize"; import { Sequelize, Op } from "sequelize";
import omnihitDashboardSession from "./OmnhitDashboardSession";
const fastFolderSize = require("fast-folder-size");
const fastFolderSize = require('fast-folder-size') const { promisify } = require("util");
const { promisify } = require('util') const fs = require("fs");
const fs = require('fs')
const { exec } = require("child_process"); const { exec } = require("child_process");
let _fifo: any let _fifo: any;
let scheduler_monitor: any; let scheduler_monitor: any;
let timeInterval = 5 let timeInterval = 5;
const monitor = async () => { const monitor = async () => {
let date = new Date();
let date = new Date() let day = date.getDate().toString().padStart(2, "0");
let month = (date.getMonth() + 1).toString().padStart(2, "0");
let year = date.getFullYear();
let day = date.getDate().toString().padStart(2, '0'); let hour = date.getHours();
let month = (date.getMonth() + 1).toString().padStart(2, '0'); let minute = date.getMinutes();
let year = date.getFullYear();
let hour = date.getHours() let fullDate = `${year}-${month}-${day}`;
let minute = date.getMinutes() let dateParm = `${fullDate} ${hour.toString().padStart(2, "0")}:${minute
.toString()
.padStart(2, "0")}:00`;
let fullDate = `${year}-${month}-${day}`; //console.log(dateParm);
let dateParm = `${fullDate} ${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}:00`
//console.log(dateParm); try {
const { schedulingNotifies, count, hasMore } =
await ListSchedulingNotifyService({
searchParam: dateParm,
pageNumber: "1"
});
try { if (schedulingNotifies && schedulingNotifies.length > 0) {
for (let i = 0; i < schedulingNotifies.length; i++) {
const { schedulingNotifies, count, hasMore } = await ListSchedulingNotifyService({ searchParam: dateParm, pageNumber: "1" }); const ticket: any = await ShowTicketService(
+schedulingNotifies[i].ticketId
if (schedulingNotifies && schedulingNotifies.length > 0) { );
for (let i = 0; i < schedulingNotifies.length; i++) {
const ticket: any = await ShowTicketService(+schedulingNotifies[i].ticketId);
let _ticket = await Ticket.findOne({
where: {
contactId: ticket.contactId,
status: { [Op.in]: ['open', 'pending'] }
}
})
await deleteScheduleByTicketIdCache(schedulingNotifies[i].ticketId)
await DeleteSchedulingNotifyService(schedulingNotifies[i].id)
if (_ticket) continue
if (ticket.dataValues.status == 'closed') {
await ticket.update({ status: 'pending' })
}
await new Promise(f => setTimeout(f, 3000));
await SendWhatsAppMessage({
body: schedulingNotifies[i].message, ticket
});
}
}
exec("df -h /", (error: any, stdout: any, stderr: any) => {
if (error) {
console.log(`exec error: ${error.message}`);
return;
}
if (stderr) {
console.log(`exec stderr: ${stderr}`);
return;
}
stdout = stdout.split(/\r?\n/)
stdout = stdout[1].trim().split(/\s+/)
// DISK SPACE MONITORING
const io = getIO();
io.emit("diskSpaceMonit", {
action: "update",
diskSpace: {
size: stdout[1],
used: stdout[2],
available: stdout[3],
use: stdout[4]
}
});
let _ticket = await Ticket.findOne({
where: {
contactId: ticket.contactId,
status: { [Op.in]: ["open", "pending"] }
}
}); });
await deleteScheduleByTicketIdCache(schedulingNotifies[i].ticketId);
await DeleteSchedulingNotifyService(schedulingNotifies[i].id);
// WHATS SESSION SIZE MONITORING if (_ticket) continue;
// const whatsapps = await ListWhatsAppsService();
// whatsapps.forEach(async whats => { if (ticket.dataValues.status == "closed") {
await ticket.update({ status: "open" });
}
const userId: number = ticket.getDataValue("userId");
let userN = "";
if (userId) {
const useer = await User.findByPk(userId);
if (useer) {
const userName: string = useer.getDataValue("name");
userN = `*${userName}:* \n`;
}
}
await new Promise(f => setTimeout(f, 3000));
await SendWhatsAppMessage({
// const sourcePath = path.join(__dirname, `../../.wwebjs_auth/`, `session-bd_${whats.id}`) body: userN + schedulingNotifies[i].message,
ticket
// if (fs.existsSync(sourcePath)) { });
}
// try {
// const fastFolderSizeAsync = promisify(fastFolderSize)
// let size = await fastFolderSizeAsync(sourcePath)
// size = convertBytes(size)
// // SESSION MONITORING
// const io = getIO();
// io.emit("whatsappSessionMonit", {
// action: "update",
// whatsappSessionSize: {
// id: whats.id,
// sessionSize: size
// }
// });
// }
// catch (err) {
// console.log('An error occurred while get info from the directory: ', err);
// }
// }
// else {
// // console.log('Directory not found to get size info: ', sourcePath)
// }
// });
} catch (error) {
console.log('>>> SchedulingNotifiySendMessage.ts error: ', error)
} }
exec("df -h /", (error: any, stdout: any, stderr: any) => {
if (error) {
console.log(`exec error: ${error.message}`);
return;
}
if (stderr) {
console.log(`exec stderr: ${stderr}`);
return;
}
stdout = stdout.split(/\r?\n/);
stdout = stdout[1].trim().split(/\s+/);
// DISK SPACE MONITORING
const io = getIO();
io.emit("diskSpaceMonit", {
action: "update",
diskSpace: {
size: stdout[1],
used: stdout[2],
available: stdout[3],
use: stdout[4]
}
});
let data: any = {};
data.diskSpace = {
DB: process.env.DB_NAME,
size: stdout[1],
used: stdout[2],
available: stdout[3],
use: stdout[4]
};
data.action = "update";
omnihitDashboardSession(data);
});
} catch (error) {
console.log(">>> SchedulingNotifiySendMessage.ts error: ", error);
}
}; };
const SchedulingNotifySendMessage = async () => { const SchedulingNotifySendMessage = async () => {
try {
clearInterval(_fifo);
try { await monitor();
clearInterval(_fifo); } catch (error) {
console.log("error on SchedulingNotifySendMessage: ", error);
await monitor() } finally {
_fifo = setInterval(SchedulingNotifySendMessage, 5000);
} catch (error) { }
console.log('error on SchedulingNotifySendMessage: ', error) };
}
finally {
_fifo = setInterval(SchedulingNotifySendMessage, 5000);
}
}
_fifo = setInterval(SchedulingNotifySendMessage, 5000); _fifo = setInterval(SchedulingNotifySendMessage, 5000);
module.exports = SchedulingNotifySendMessage;
module.exports = SchedulingNotifySendMessage

View File

@ -1,21 +1,33 @@
import { getIO } from "../libs/socket"; import { getIO } from "../libs/socket";
import Ticket from "../models/Ticket"; import Ticket from "../models/Ticket";
import sendWhatsAppMessageOfficialAPI from "./sendWhatsAppMessageOfficialAPI"
function sendWhatsAppMessageSocket(
ticket: Ticket,
body: string,
quotedMsgSerializedId?: string | undefined,
number?: string
) {
const { phoneNumberId } = ticket;
function sendWhatsAppMessageSocket(ticket: Ticket, body: string, quotedMsgSerializedId?: string | undefined, number?: string ) { if (phoneNumberId) {
sendWhatsAppMessageOfficialAPI(ticket, body, quotedMsgSerializedId);
return;
}
const io = getIO(); const io = getIO();
io.to(`session_${ticket.whatsappId.toString()}`).emit("send_message", {
action: "create",
msg: {
number: number ? number : `${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us`,
body: body,
quotedMessageId: quotedMsgSerializedId,
linkPreview: false
}
});
io.to(`session_${ticket.whatsappId.toString()}`).emit("send_message", {
action: "create",
msg: {
number: number
? number
: `${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us`,
body: body,
quotedMessageId: quotedMsgSerializedId,
linkPreview: false
}
});
} }
export default sendWhatsAppMessageSocket; export default sendWhatsAppMessageSocket;

View File

@ -4,6 +4,7 @@ import User from "../models/User";
interface SerializedUser { interface SerializedUser {
id: number; id: number;
name: string; name: string;
positionCompany: string;
email: string; email: string;
profile: string; profile: string;
queues: Queue[]; queues: Queue[];
@ -13,6 +14,7 @@ export const SerializeUser = (user: User): SerializedUser => {
return { return {
id: user.id, id: user.id,
name: user.name, name: user.name,
positionCompany: user.positionCompany,
email: user.email, email: user.email,
profile: user.profile, profile: user.profile,
queues: user.queues queues: user.queues

View File

@ -1,12 +1,27 @@
import { Op } from "sequelize"
import { getWbot } from "../libs/wbot"; import { getWbot } from "../libs/wbot";
import Message from "../models/Message"
import Ticket from "../models/Ticket"; import Ticket from "../models/Ticket";
import Whatsapp from "../models/Whatsapp";
import endPointQuery from "./old_EndPointQuery"; import endPointQuery from "./old_EndPointQuery";
import whatsappOfficialAPI from "./WhatsappOfficialAPI";
export async function setMessageAsRead(ticket: Ticket) { export async function setMessageAsRead(ticket: Ticket) {
if (ticket?.phoneNumberId) {
return;
}
const wbot_url = await getWbot(ticket.whatsappId); const wbot_url = await getWbot(ticket.whatsappId);
console.log('from wbotMessagelistener wbot_url: ', wbot_url, ' | ticket.contact.number: ', ticket.contact.number); console.log(
"from wbotMessagelistener wbot_url: ",
wbot_url,
" | ticket.contact.number: ",
ticket.contact.number
);
await endPointQuery(`${wbot_url}/api/sendSeen`, { number: `${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us` }); await endPointQuery(`${wbot_url}/api/sendSeen`, {
number: `${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us`
});
} }

View File

@ -13,11 +13,11 @@ import {
} from "date-fns"; } from "date-fns";
import ptBR from "date-fns/locale/pt-BR"; import ptBR from "date-fns/locale/pt-BR";
const isHoliday = async () => { const isHoliday = async (number: string | number) => {
let obj = { set: false, msg: "" }; let obj = { set: false, msg: "" };
const holiday = await SettingTicket.findOne({ const holiday = await SettingTicket.findOne({
where: { key: "holiday" } where: { key: "holiday", number }
}); });
if ( if (
@ -50,11 +50,11 @@ const isHoliday = async () => {
return obj; return obj;
}; };
const isWeekend = async () => { const isWeekend = async (number: string | number) => {
let obj = { set: false, msg: "" }; let obj = { set: false, msg: "" };
const weekend = await SettingTicket.findOne({ const weekend = await SettingTicket.findOne({
where: { key: "weekend" } where: { key: "weekend", number }
}); });
if ( if (
@ -62,7 +62,6 @@ const isWeekend = async () => {
weekend.value == "enabled" && weekend.value == "enabled" &&
weekend.message?.trim()?.length > 0 weekend.message?.trim()?.length > 0
) { ) {
// Specify your desired timezone // Specify your desired timezone
const brazilTimeZone = "America/Sao_Paulo"; const brazilTimeZone = "America/Sao_Paulo";
@ -100,8 +99,7 @@ const isWeekend = async () => {
obj.set = true; obj.set = true;
obj.msg = weekend.message; obj.msg = weekend.message;
} }
} } else {
else{
// obj.set = true; // obj.set = true;
// obj.msg = weekend.message; // obj.msg = weekend.message;
} }
@ -109,11 +107,11 @@ const isWeekend = async () => {
} }
}; };
async function isOutBusinessTime() { async function isOutBusinessTime(number: string | number) {
let obj = { set: false, msg: "" }; let obj = { set: false, msg: "" };
const outBusinessHours = await SettingTicket.findOne({ const outBusinessHours = await SettingTicket.findOne({
where: { key: "outBusinessHours" } where: { key: "outBusinessHours", number }
}); });
let isWithinRange = false; let isWithinRange = false;

View File

@ -1,49 +0,0 @@
import Ticket from "../models/Ticket";
import ListWhatsAppsNumber from "../services/WhatsappService/ListWhatsAppsNumber";
import GetTicketWbot from "./GetTicketWbot";
const sendMessageMultiSession = async (ticket: Ticket, body?: any, quotedMsgSerializedId?: any, sendSeen?: boolean) => {
let sentMessage: any = ''
const listWhatsapp: any = await ListWhatsAppsNumber(ticket.whatsappId, 'CONNECTED')
if (listWhatsapp.length > 0) {
for (let w = 0; w < listWhatsapp.length; w++) {
if(listWhatsapp[w].id == ticket.whatsappId) continue
try {
console.log('CHANGE THE WHATSAPP SESSION ID: ', listWhatsapp[w].id)
await ticket.update({ whatsappId: listWhatsapp[w].id })
const wbot = await GetTicketWbot(ticket);
if (sendSeen) {
await wbot.sendSeen(`${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us`);
}
else if (body) {
sentMessage = await wbot.sendMessage(`${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us`, body, { quotedMessageId: quotedMsgSerializedId, linkPreview: false });
}
break
} catch (error) {
console.log('Cannot send send the message using the whatsapp id: ', listWhatsapp[w].id, ' | error: ', error)
}
}
}
return sentMessage
};
export default sendMessageMultiSession;

View File

@ -0,0 +1,11 @@
import axios from "axios";
const api = axios.create({
baseURL: process.env.URL_WHATSAPP_API,
headers: {
Accept: "application/json",
Authorization: `Bearer ${process.env.TOKEN}`
}
});
export default api;

View File

@ -0,0 +1,21 @@
import whatsappOfficialAPI from "./WhatsappOfficialAPI";
async function whatsappOfficialNumberInfo(wabaId: string) {
try {
const { data } = await whatsappOfficialAPI.get(
`/${process.env.VERSION}/${wabaId}/phone_numbers`
);
console.log("data: ", data);
if (data && Object.keys(data).length > 0) {
return data.data[0];
}
} catch (error) {
console.log(
`There was an error into whatsappOfficialNumberInfo method : ${error}`
);
}
return null;
}
export default whatsappOfficialNumberInfo;

View File

@ -67,7 +67,7 @@ const monitor = async () => {
const usersSocket = require('./../libs/socket'); const usersSocket = require('./../libs/socket');
if (usersSocket.ob) { if (usersSocket?.ob) {
if (count > 1) { if (count > 1) {
count = 0 count = 0

View File

@ -1,39 +1,28 @@
const fsPromises = require("fs/promises"); const fsPromises = require("fs/promises");
const fs = require('fs') const fs = require("fs");
import axios from 'axios'; import axios from "axios";
import * as https from "https"; import * as https from "https";
const endPointQuery = async (url: string, data: any) => { const endPointQuery = async (url: string, data: any) => {
let response: any = null;
let response: any = null try {
response = await axios.post(url, data);
try { console.log(
`TEST URL CLIENT POST ROUTE: ${url} | STATUS CODE: ${response.status}`
response = await axios.post(url, data); );
} catch (err: any) {
console.log(`TEST URL CLIENT POST ROUTE: ${url} | STATUS CODE: ${response.status}`); if (err.response) {
console.log("err.response: ", err.response);
} else if (err.request) {
} catch (err: any) { console.log("err.request: ", err.request);
} else {
if (err.response) { console.error(`Erro ao consultar endpoint ${url}: ${err}`);
// The client was given an error response (5xx, 4xx)
// console.log('err.response: ', err.response)
console.log('err.response: ', err.response)
// return { data: err.response.data, status: err.response.status }
} else if (err.request) {
// The client never received a response, and the request was never left
console.log('err.request: ', err.request)
} else {
// Anything else
console.error(`Erro ao consultar endpoint ${url}: ${err}`);
}
} }
}
return response return response;
};
}
export default endPointQuery; export default endPointQuery;

View File

@ -0,0 +1,17 @@
const usersSocket = require("../libs/socket");
export const removeUserFromOlineList = (userId: any) => {
let index = usersSocket?.ob?.listOnline?.findIndex((o: any) => o?.id == userId);
if (index != -1) {
usersSocket?.ob?.listOnline?.splice(index, 1);
}
index = -1;
index = usersSocket?.ob?.listOnlineAux?.findIndex((o: any) => o?.id == userId);
if (index != -1) {
usersSocket?.ob?.listOnlineAux?.splice(index, 1);
}
};

View File

@ -0,0 +1,81 @@
import { getIO } from "../libs/socket";
import Contact from "../models/Contact";
import Ticket from "../models/Ticket";
import {
isValidMsg,
verifyMessage
} from "../services/WbotServices/wbotMessageListener";
import whatsappOfficialAPI from "./WhatsappOfficialAPI";
async function sendWhatsAppMessageOfficialAPI(
ticket: Ticket,
body: string,
quotedMsgSerializedId?: any | undefined,
_template?: any
) {
const { contactId, phoneNumberId } = ticket;
const contact: any = await Contact.findByPk(contactId);
const { number } = contact;
let data: any = {
messaging_product: "whatsapp",
recipient_type: "individual",
to: number
};
if (_template) {
const { template } = _template;
data = {
...data,
type: "template",
template
};
} else {
data = {
...data,
type: "text",
text: {
preview_url: true,
body
}
};
}
if (quotedMsgSerializedId) {
data = { ...data, context: { message_id: quotedMsgSerializedId.id } };
}
if (!isValidMsg({ type: data.type })) {
return;
}
console.log("SEND MESSAGE: ", JSON.stringify(data, null,2));
whatsappOfficialAPI
.post(`/${process.env.VERSION}/${phoneNumberId}/messages`, data)
.then(response => {
console.log("Response:", response.data);
let msg = {};
msg = {
...msg,
id: { id: response.data.messages[0].id },
fromMe: true,
type: "chat",
read: false,
phoneNumberId,
body
};
verifyMessage(msg, ticket, contact, quotedMsgSerializedId?.id);
})
.catch(error => {
console.log("Error on try request: ", error.response.data.error.message);
});
}
export default sendWhatsAppMessageOfficialAPI;

View File

@ -0,0 +1,106 @@
import { MessageMedia } from "whatsapp-web.js";
import { getIO } from "../libs/socket";
import Contact from "../models/Contact";
import Ticket from "../models/Ticket";
import {
isValidMsg,
mediaTypeWhatsappOfficial,
verifyMediaMessage,
verifyMessage
} from "../services/WbotServices/wbotMessageListener";
import ffmpeg from "fluent-ffmpeg";
import fs from "fs";
import whatsappOfficialAPI from "./WhatsappOfficialAPI";
import path from "path";
import { convertAudioToOgg } from "../helpers/ConvertAudio";
import { bytesToMB } from "./BytesToMB";
import isThisHour from "date-fns/esm/isThisHour/index";
import AppError from "../errors/AppError";
async function sendWhatsMediaOfficialAPI(
ticket: Ticket,
media: any,
type: any,
mic_audio?: any
) {
const { contactId, phoneNumberId } = ticket;
const contact: any = await Contact.findByPk(contactId);
const { number } = contact;
let { filename } = MessageMedia.fromFilePath(media.path);
const { originalname } = media;
console.log("mic_audio: ", mic_audio);
if (type == "audio" && mic_audio) {
const inputFile = media.path;
const fileNameWithoutExtension = path.basename(
media.path,
path.extname(media.path)
);
const outputFile = `${
inputFile.split(fileNameWithoutExtension)[0]
}${fileNameWithoutExtension}`;
try {
await convertAudioToOgg(inputFile, outputFile);
media = MessageMedia.fromFilePath(`${outputFile}.ogg`);
filename = media.filename;
} catch (error) {
console.error("Conversion failed:", error);
}
}
let data: any = {
messaging_product: "whatsapp",
recipient_type: "individual",
to: number,
type,
[type]: {
link: `${process.env.URL_WHATSAPP_MEDIA}/${filename}`
}
};
if (type == "document") {
data.document = { ...data.document, filename: originalname };
}
console.log("PAYLOAD MEDIA: ", data);
if (!isValidMsg({ type })) {
return;
}
whatsappOfficialAPI
.post(`/${process.env.VERSION}/${phoneNumberId}/messages`, data)
.then(response => {
console.log("Response:", response.data);
let msg = {};
msg = {
...msg,
id: { id: response.data.messages[0].id },
ticketId: ticket.id,
body: filename,
fromMe: true,
read: false,
phoneNumberId
};
verifyMediaMessage(msg, ticket, contact, media);
})
.catch(error => {
console.log("Error on try request: ", error.response.data.error.message);
});
}
export default sendWhatsMediaOfficialAPI;

View File

@ -139,7 +139,7 @@ export const initIO = (httpServer: Server): SocketIO => {
lstOnline.push({ id: userId, status: "online", try: 0 }); lstOnline.push({ id: userId, status: "online", try: 0 });
lstOnlineAux.push({ id: userId }); lstOnlineAux.push({ id: userId });
console.log(" 1 PUSHED NEW USER ID 1: ", userId); console.log(" 1 - PUSHED NEW USER ID 1: ", userId);
obj.listOnline = lstOnline; obj.listOnline = lstOnline;
} else { } else {
@ -154,7 +154,7 @@ export const initIO = (httpServer: Server): SocketIO => {
if (index == -1) { if (index == -1) {
lstOnline.push({ id: userId, status: "online", try: 0 }); lstOnline.push({ id: userId, status: "online", try: 0 });
console.log(" 2 PUSHED NEW USER ID: ", userId); console.log(" 2 - PUSHED NEW USER ID: ", userId);
obj.listOnline = lstOnline; obj.listOnline = lstOnline;
} else { } else {

View File

@ -23,6 +23,7 @@ const isAuth = (req: Request, res: Response, next: NextFunction): void => {
try { try {
const decoded = verify(token, authConfig.secret); const decoded = verify(token, authConfig.secret);
const { id, profile } = decoded as TokenPayload; const { id, profile } = decoded as TokenPayload;
req.user = { req.user = {

View File

@ -34,10 +34,15 @@ class Message extends Model<Message> {
@Column(DataType.TEXT) @Column(DataType.TEXT)
body: string; body: string;
@Column
phoneNumberId: string;
@Column(DataType.STRING) @Column(DataType.STRING)
get mediaUrl(): string | null { get mediaUrl(): string | null {
if (this.getDataValue("mediaUrl")) { if (this.getDataValue("mediaUrl")) {
return `${process.env.BACKEND_URL}:${process.env.PROXY_PORT}/public/${this.getDataValue("mediaUrl")}`; return `${process.env.BACKEND_URL}:${
process.env.PROXY_PORT
}/public/${this.getDataValue("mediaUrl")}`;
} }
return null; return null;
} }

View File

@ -30,6 +30,9 @@ class SettingTicket extends Model<SettingTicket> {
@Column @Column
key: string; key: string;
@Column
number: string;
@CreatedAt @CreatedAt
createdAt: Date; createdAt: Date;

View File

@ -45,6 +45,9 @@ class Ticket extends Model<Ticket> {
@Column @Column
statusChatEnd: string; statusChatEnd: string;
@Column
phoneNumberId: string;
@CreatedAt @CreatedAt
createdAt: Date; createdAt: Date;

View File

@ -42,6 +42,9 @@ class User extends Model<User> {
@Column @Column
tokenVersion: number; tokenVersion: number;
@Column
positionCompany: string;
@Default("admin") @Default("admin")
@Column @Column
profile: string; profile: string;
@ -51,7 +54,6 @@ class User extends Model<User> {
@UpdatedAt @UpdatedAt
updatedAt: Date; updatedAt: Date;
@HasMany(() => Ticket) @HasMany(() => Ticket)
tickets: Ticket[]; tickets: Ticket[];

View File

@ -62,6 +62,20 @@ class Whatsapp extends Model<Whatsapp> {
@Column @Column
urlApi: string; urlApi: string;
@Column
phoneNumberId: string;
@Column
classification: string;
@Column
wabaId: string;
@Default(false)
@AllowNull
@Column
isOfficial: boolean;
@Default(false) @Default(false)
@AllowNull @AllowNull
@Column @Column

View File

@ -11,6 +11,6 @@ authRoutes.post("/login", SessionController.store);
authRoutes.post("/refresh_token", SessionController.update); authRoutes.post("/refresh_token", SessionController.update);
authRoutes.delete("/logout", isAuth, SessionController.remove); authRoutes.delete("/logout/:userId", isAuth, SessionController.remove);
export default authRoutes; export default authRoutes;

View File

@ -9,6 +9,8 @@ queueRoutes.get("/queue", isAuth, QueueController.index);
queueRoutes.post("/queue", isAuth, QueueController.store); queueRoutes.post("/queue", isAuth, QueueController.store);
queueRoutes.post("/queue/customization", QueueController.customization);
queueRoutes.get("/queue/:queueId", isAuth, QueueController.show); queueRoutes.get("/queue/:queueId", isAuth, QueueController.show);
queueRoutes.put("/queue/:queueId", isAuth, QueueController.update); queueRoutes.put("/queue/:queueId", isAuth, QueueController.update);

View File

@ -7,6 +7,8 @@ const settingRoutes = Router();
settingRoutes.get("/settings", SettingController.index); settingRoutes.get("/settings", SettingController.index);
settingRoutes.get("/settings/ticket/:number", SettingController.ticketSettings);
// routes.get("/settings/:settingKey", isAuth, SettingsController.show); // routes.get("/settings/:settingKey", isAuth, SettingsController.show);
settingRoutes.put( settingRoutes.put(

View File

@ -5,12 +5,26 @@ import * as WhatsAppController from "../controllers/WhatsAppController";
const whatsappRoutes = express.Router(); const whatsappRoutes = express.Router();
whatsappRoutes.get("/whatsapp/", isAuth, WhatsAppController.index); whatsappRoutes.get("/whatsapp/", isAuth, WhatsAppController.index);
whatsappRoutes.post("/whatsapp/", isAuth, WhatsAppController.store); whatsappRoutes.post("/whatsapp/", isAuth, WhatsAppController.store);
whatsappRoutes.get(
"/whatsapp/official/matchQueue",
WhatsAppController.whatsAppOfficialMatchQueue
);
whatsappRoutes.get(
"/whatsapp/official/matchQueueUser",
WhatsAppController.whatsAppOfficialMatchQueueUser
);
whatsappRoutes.get("/whatsapp/official/media/:filename", WhatsAppController.media);
whatsappRoutes.post("/whatsapp/webhook", WhatsAppController.weebhook);
whatsappRoutes.get("/whatsapp/webhook", WhatsAppController.weebhook);
whatsappRoutes.get("/whatsapp/:whatsappId", isAuth, WhatsAppController.show); whatsappRoutes.get("/whatsapp/:whatsappId", isAuth, WhatsAppController.show);
whatsappRoutes.put("/whatsapp/:whatsappId", isAuth, WhatsAppController.update); whatsappRoutes.put("/whatsapp/:whatsappId", isAuth, WhatsAppController.update);

View File

@ -7,6 +7,8 @@ import User from "./models/User";
import Whatsapp from "./models/Whatsapp"; import Whatsapp from "./models/Whatsapp";
import endPointQuery from "./helpers/EndPointQuery"; import endPointQuery from "./helpers/EndPointQuery";
import { cacheSize, flushCache, loadTicketsCache } from "./helpers/TicketCache"; import { cacheSize, flushCache, loadTicketsCache } from "./helpers/TicketCache";
import "./helpers/CloseBotTickets";
import { loadContactsCache } from "./helpers/ContactsCache"; import { loadContactsCache } from "./helpers/ContactsCache";
import { loadSchedulesCache } from "./helpers/SchedulingNotifyCache"; import { loadSchedulesCache } from "./helpers/SchedulingNotifyCache";
import { delRestoreControllFile } from "./helpers/RestoreControll"; import { delRestoreControllFile } from "./helpers/RestoreControll";
@ -43,13 +45,17 @@ gracefulShutdown(server);
loadSettings(); loadSettings();
let whatsapps: any = await Whatsapp.findAll({ attributes: ["id", "url"] }); let whatsapps: any = await Whatsapp.findAll({
attributes: ["id", "url", "phoneNumberId"]
// console.log('whatsapps: ', whatsapps) });
if (whatsapps && whatsapps.length > 0) { if (whatsapps && whatsapps.length > 0) {
for (let i = 0; i < whatsapps.length; i++) { for (let i = 0; i < whatsapps.length; i++) {
try { try {
const { phoneNumberId } = whatsapps[i];
if (phoneNumberId) continue;
console.log( console.log(
`API URL: ${whatsapps[i].dataValues.url}/api/connection/status` `API URL: ${whatsapps[i].dataValues.url}/api/connection/status`
); );

View File

@ -18,73 +18,59 @@ interface Request {
messageData: MessageData; messageData: MessageData;
} }
const CreateMessageService = async ({ messageData }: Request): Promise<Message> => { const CreateMessageService = async ({
messageData
// console.log('UPSERT MESSAGE messageData: ', messageData) }: Request): Promise<Message> => {
try { try {
await Message.upsert(messageData); await Message.upsert(messageData);
const message = await Message.findByPk(messageData.id, { const message = await Message.findByPk(messageData.id, {
include: [ include: [
"contact", "contact",
{ {
model: Ticket, model: Ticket,
as: "ticket", as: "ticket",
include: ["contact", "queue"] include: ["contact", "queue"]
}, },
{ {
model: Message, model: Message,
as: "quotedMsg", as: "quotedMsg",
include: ["contact"] include: ["contact"]
} }
] ]
}); });
if (!message) { if (!message) {
throw new Error("ERR_CREATING_MESSAGE"); throw new Error("ERR_CREATING_MESSAGE");
} }
if (message.ticket.status != "queueChoice") {
if (message.ticket.status != 'queueChoice') { await updateTicketCacheByTicketId(message.ticket.id, {
// TEST DEL
await updateTicketCacheByTicketId(message.ticket.id,
{
lastMessage: message.body, lastMessage: message.body,
updatedAt: new Date(message.ticket.updatedAt).toISOString(), updatedAt: new Date(message.ticket.updatedAt).toISOString(),
'contact.profilePicUrl': message.ticket.contact.profilePicUrl, "contact.profilePicUrl": message.ticket.contact.profilePicUrl,
unreadMessages: message.ticket.unreadMessages unreadMessages: message.ticket.unreadMessages
})
//
console.log('message.ticketId.toString(): ', message.ticketId.toString())
console.log('message.ticket.status: ',message.ticket.status)
const io = getIO();
io.to(message.ticketId.toString())
.to(message.ticket.status)
.to("notification")
.emit("appMessage", {
action: "create",
message,
ticket: message.ticket,
contact: message.ticket.contact
}); });
} const io = getIO();
io.to(message.ticketId.toString())
.to(message.ticket.status)
return message; .to("notification")
.emit("appMessage", {
action: "create",
message,
ticket: message.ticket,
contact: message.ticket.contact
});
}
return message;
} catch (error: any) { } catch (error: any) {
console.error('===> Error on CreateMessageService.ts file: \n', error) console.error("===> Error on CreateMessageService.ts file: \n", error);
throw new AppError(error.message); throw new AppError(error.message);
} }
}; };
export default CreateMessageService; export default CreateMessageService;

View File

@ -7,6 +7,7 @@ interface Request {
endTime: string; endTime: string;
value: string; value: string;
message: string; message: string;
number: string;
} }
const updateSettingTicket = async ({ const updateSettingTicket = async ({
@ -14,16 +15,30 @@ const updateSettingTicket = async ({
startTime, startTime,
endTime, endTime,
value, value,
message message,
number
}: Request): Promise<SettingTicket | undefined> => { }: Request): Promise<SettingTicket | undefined> => {
try { try {
const businessHours = await SettingTicket.findOne({ where: { key } }); let businessHours = await SettingTicket.findOne({ where: { key, number } });
if (!businessHours) { if (!businessHours) {
throw new AppError("ERR_NO_SETTING_FOUND", 404); // throw new AppError("ERR_NO_SETTING_FOUND", 404);
businessHours = await SettingTicket.create({
key,
startTime,
endTime,
value,
message,
number
});
return businessHours;
} }
await businessHours.update({ startTime, endTime, message, value }); await businessHours.update(
{ startTime, endTime, message, value, number },
{ where: { key, number } }
);
return businessHours; return businessHours;
} catch (error: any) { } catch (error: any) {

View File

@ -15,6 +15,9 @@ import TicketEmiterSumOpenClosedByUser from "../../helpers/OnlineReporEmiterInfo
import { createOrUpdateTicketCache } from "../../helpers/TicketCache"; import { createOrUpdateTicketCache } from "../../helpers/TicketCache";
import User from "../../models/User"; import User from "../../models/User";
import whatsappQueueMatchingUserQueue from "../../helpers/whatsappQueueMatchingUserQueue"; import whatsappQueueMatchingUserQueue from "../../helpers/whatsappQueueMatchingUserQueue";
import Whatsapp from "../../models/Whatsapp";
import ListWhatsAppsForQueueService from "../WhatsappService/ListWhatsAppsForQueueService";
import { json } from "sequelize";
let flatten = require("flat"); let flatten = require("flat");
interface Request { interface Request {
@ -22,18 +25,50 @@ interface Request {
status: string; status: string;
userId: number; userId: number;
queueId?: number | undefined; queueId?: number | undefined;
whatsappId?: number | string;
} }
const CreateTicketService = async ({ const CreateTicketService = async ({
contactId, contactId,
status, status,
userId, userId,
queueId = undefined queueId = undefined,
whatsappId
}: Request): Promise<Ticket> => { }: Request): Promise<Ticket> => {
console.log("========> queueId: ", queueId);
try { try {
const defaultWhatsapp = await GetDefaultWhatsApp(userId); let defaultWhatsapp;
if (whatsappId) {
defaultWhatsapp = await Whatsapp.findByPk(whatsappId);
} else {
defaultWhatsapp = await GetDefaultWhatsApp({ userId, queueId });
}
// if (queueId) {
// const whatsappsByQueue = await ListWhatsAppsForQueueService(queueId);
// const exist = await CheckContactOpenTickets(
// contactId,
// defaultWhatsapp.id,
// true
// );
// console.log(
// "whatsappsByQueue: ",
// JSON.stringify(whatsappsByQueue, null, 6)
// );
// if (exist) {
// console.log("kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk");
// }
// return new Ticket();
// }
// console.log(JSON.stringify(defaultWhatsapp, null, 2));
// throw new AppError("test error");
const { phoneNumberId } = defaultWhatsapp;
const user = await User.findByPk(userId, { raw: true }); const user = await User.findByPk(userId, { raw: true });
@ -47,6 +82,13 @@ const CreateTicketService = async ({
queueId = matchingQueue ? matchingQueue.queueId : undefined; queueId = matchingQueue ? matchingQueue.queueId : undefined;
} }
console.log(
"xxxxxxxxxxxx contactId: ",
contactId,
" | defaultWhatsapp.id: ",
defaultWhatsapp.id
);
await CheckContactOpenTickets(contactId, defaultWhatsapp.id); await CheckContactOpenTickets(contactId, defaultWhatsapp.id);
const { isGroup } = await ShowContactService(contactId); const { isGroup } = await ShowContactService(contactId);
@ -56,7 +98,8 @@ const CreateTicketService = async ({
status, status,
isGroup, isGroup,
userId, userId,
queueId queueId,
phoneNumberId
}); });
const ticket = await Ticket.findByPk(id, { include: ["contact"] }); const ticket = await Ticket.findByPk(id, { include: ["contact"] });

View File

@ -13,11 +13,15 @@ const FindOrCreateTicketService = async (
contact: Contact, contact: Contact,
whatsappId: number, whatsappId: number,
unreadMessages: number, unreadMessages: number,
groupContact?: Contact groupContact?: Contact,
): Promise<Ticket> => { ): Promise<Ticket> => {
try { try {
let ticket; let ticket;
// else if (getSettingValue("whatsaAppCloudApi")?.value == "enabled") {
// }
if (getSettingValue("oneContactChatWithManyWhats")?.value == "enabled") { if (getSettingValue("oneContactChatWithManyWhats")?.value == "enabled") {
let whats = await ListWhatsAppsNumber(whatsappId); let whats = await ListWhatsAppsNumber(whatsappId);
@ -41,10 +45,8 @@ const FindOrCreateTicketService = async (
}); });
} }
const { queues, greetingMessage } = await ShowWhatsAppService(whatsappId); const { queues, greetingMessage, phoneNumberId } = await ShowWhatsAppService(whatsappId);
//Habilitar esse caso queira usar o bot
// const botInfo = await BotIsOnQueue('botqueue')
const botInfo = { isOnQueue: false }; const botInfo = { isOnQueue: false };
if (ticket) { if (ticket) {
@ -72,8 +74,6 @@ const FindOrCreateTicketService = async (
ticket = await Ticket.findOne({ ticket = await Ticket.findOne({
where: { where: {
updatedAt: { updatedAt: {
//[Op.between]: [+subHours(new Date(), 2), +new Date()]
// Tempo osioso para a ura responder thuanny // Tempo osioso para a ura responder thuanny
//[Op.between]: [+subMinutes(new Date(), 30), +new Date()] //[Op.between]: [+subMinutes(new Date(), 30), +new Date()]
@ -106,15 +106,9 @@ const FindOrCreateTicketService = async (
status: status, status: status,
isGroup: !!groupContact, isGroup: !!groupContact,
unreadMessages, unreadMessages,
whatsappId whatsappId,
phoneNumberId
}); });
// TEST DEL
// const { name } = await ShowContactService(contact.id);
// console.log('FIND OR CREATE TICKET SERVICE NAME: ', contact.name, ' STATUS: ', status)
//
} }
ticket = await ShowTicketService(ticket.id); ticket = await ShowTicketService(ticket.id);

View File

@ -0,0 +1,147 @@
import { subHours, subMinutes, subSeconds } from "date-fns";
import { Op } from "sequelize";
import BotIsOnQueue from "../../helpers/BotIsOnQueue";
import Contact from "../../models/Contact";
import Ticket from "../../models/Ticket";
import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService";
import ShowTicketService from "./ShowTicketService";
import AppError from "../../errors/AppError";
import { userInfo } from "os";
import ShowQueueService from "../QueueService/ShowQueueService";
import UpdateTicketService from "./UpdateTicketService";
const FindOrCreateTicketServiceBot = async (
contact: Contact,
whatsappId: number,
unreadMessages: number,
groupContact?: Contact
): Promise<any> => {
try {
let ticket = await Ticket.findOne({
where: {
status: {
[Op.or]: ["open", "pending", "queueChoice"]
},
contactId: groupContact ? groupContact.id : contact.id
}
});
const { queues, greetingMessage } = await ShowWhatsAppService(whatsappId);
//Habilitar esse caso queira usar o bot
const botInfo = await BotIsOnQueue('botqueue')
// const botInfo = { isOnQueue: false }
if (ticket) {
await ticket.update({ unreadMessages });
}
// if (!ticket && groupContact) {
// ticket = await Ticket.findOne({
// where: {
// contactId: groupContact.id
// },
// order: [["updatedAt", "DESC"]]
// });
// if (ticket) {
// await ticket.update({
// status: "pending",
// userId: null,
// unreadMessages
// });
// }
// }
if (!ticket && !groupContact) {
console.log('BOT CREATING OR REOPENING THE TICKET')
ticket = await Ticket.findOne({
where: {
contactId: contact.id,
userId: botInfo.userIdBot
},
order: [["updatedAt", "DESC"]]
});
if (ticket) {
await ticket.update({
status: "open",
userId: botInfo.userIdBot,
unreadMessages
});
console.log('lxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
await dialogFlowStartContext(contact, ticket, botInfo);
}
}
let created = false
if (!ticket) {
created = true
let status = "open"
if (queues.length > 1 && !botInfo.isOnQueue) {
status = "queueChoice"
}
ticket = await Ticket.create({
contactId: groupContact ? groupContact.id : contact.id,
status: status,
userId: botInfo.userIdBot,
isGroup: !!groupContact,
unreadMessages,
whatsappId
});
console.log('yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy')
await dialogFlowStartContext(contact, ticket, botInfo);
}
ticket = await ShowTicketService(ticket.id);
return { ticket, created };
} catch (error: any) {
console.error('===> Error on FindOrCreateTicketServiceBot.ts file: \n', error)
throw new AppError(error.message);
}
};
export default FindOrCreateTicketServiceBot;
async function dialogFlowStartContext(contact: Contact, ticket: Ticket, botInfo: any) {
let msg: any = { type: 'chat', from: `${contact.number}@c.us`, body: '0' };
let queue = await ShowQueueService(botInfo.botQueueId);
await UpdateTicketService({
ticketData: { queueId: queue.id },
ticketId: ticket.id
});
ticket = await ShowTicketService(ticket.id);
// await sendDialogflowAnswer(ticket.whatsappId, ticket, msg, contact, false);
}

View File

@ -1,47 +1,60 @@
import { Sequelize } from "sequelize";
import { Sequelize, } from "sequelize";
const dbConfig = require("../../config/database"); const dbConfig = require("../../config/database");
const sequelize = new Sequelize(dbConfig); const sequelize = new Sequelize(dbConfig);
const { QueryTypes } = require('sequelize'); const { QueryTypes } = require("sequelize");
import { splitDateTime } from "../../helpers/SplitDateTime"; import { splitDateTime } from "../../helpers/SplitDateTime";
import format from 'date-fns/format'; import format from "date-fns/format";
import ptBR from 'date-fns/locale/pt-BR'; import ptBR from "date-fns/locale/pt-BR";
interface Request { interface Request {
timeseconds: string | number; timeseconds: string | number;
status: string; status: string;
userId?: string | number; number?: string;
userId?: string | number;
} }
const ListTicketTimeLife = async ({timeseconds, status, userId }: Request): Promise<any[]> => { const ListTicketTimeLife = async ({
timeseconds,
status,
number,
userId
}: Request): Promise<any[]> => {
let tickets = [];
let tickets = [] let currentDate = format(new Date(), "yyyy-MM-dd HH:mm:ss", { locale: ptBR });
let currentDate = format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ptBR }) // console.log('------------------> currentDate: ', currentDate)
// console.log('------------------> currentDate: ', currentDate) if (userId) {
// CONSULTANDO FILAS PELO ID DO USUARIO
tickets = await sequelize.query(
`select user.id as user_id, user.name as user_name, t.id as ticket_id from Tickets as t inner join Users as user on
t.userId = user.id and user.name = 'botqueue' and t.status='${status}' and (TIMESTAMPDIFF(SECOND, t.updatedAt, '${currentDate}')) >= ${timeseconds};`,
{ type: QueryTypes.SELECT }
);
} else if (number) {
// CONSULTANDO TICKETS PELO WHATSAPP
tickets = await sequelize.query(
`SELECT t.id AS ticket_id
FROM Tickets t
JOIN Whatsapps w ON t.whatsappId = w.id
AND w.number = ${number}
WHERE t.status = 'open'
AND TIMESTAMPDIFF(SECOND, t.updatedAt, '${currentDate}') >= ${timeseconds};`,
{ type: QueryTypes.SELECT }
);
} else {
// CONSULTANDO FILAS PELO USUARIO
tickets = await sequelize.query(
`select id as ticket_id from Tickets where status='${status}' and
(TIMESTAMPDIFF(SECOND, updatedAt, '${currentDate}')) >= ${timeseconds};`,
{ type: QueryTypes.SELECT }
);
}
if (userId) { return tickets;
// CONSULTANDO FILAS PELO ID DO USUARIO
tickets = await sequelize.query(`select user.id as user_id, user.name as user_name, t.id as ticket_id from Tickets as t inner join Users as user on
t.userId = user.id and user.name = 'botqueue' and t.status='${status}' and (TIMESTAMPDIFF(SECOND, t.updatedAt, '${currentDate}')) >= ${timeseconds};`, { type: QueryTypes.SELECT });
} else {
// CONSULTANDO FILAS PELO USUARIO
tickets = await sequelize.query(`select id as ticket_id from Tickets where status='${status}' and
(TIMESTAMPDIFF(SECOND, updatedAt, '${currentDate}')) >= ${timeseconds};`, { type: QueryTypes.SELECT });
}
return tickets;
}; };
export default ListTicketTimeLife; export default ListTicketTimeLife;

View File

@ -194,8 +194,12 @@ const ListTicketsService = async ({
const userProfile: any = await User.findByPk(userId) const userProfile: any = await User.findByPk(userId)
if (userProfile.dataValues.profile != 'admin' && userProfile.dataValues.profile != 'master') { if (
whereCondition = { ...whereCondition, userId } userProfile.dataValues.profile != "admin" &&
userProfile.dataValues.profile != "master"
// userProfile.dataValues.profile != "supervisor"
) {
whereCondition = { ...whereCondition, userId };
} }

View File

@ -9,6 +9,8 @@ import ShowTicketService from "./ShowTicketService";
import { createOrUpdateTicketCache } from "../../helpers/TicketCache"; import { createOrUpdateTicketCache } from "../../helpers/TicketCache";
import AppError from "../../errors/AppError"; import AppError from "../../errors/AppError";
import sendWhatsAppMessageSocket from "../../helpers/SendWhatsappMessageSocket"; import sendWhatsAppMessageSocket from "../../helpers/SendWhatsappMessageSocket";
import BotIsOnQueue from "../../helpers/BotIsOnQueue";
import { deleteObject } from "../../helpers/RedisClient"
var flatten = require("flat"); var flatten = require("flat");
interface TicketData { interface TicketData {
@ -48,7 +50,15 @@ const UpdateTicketService = async ({
} = ticketData; } = ticketData;
const ticket = await ShowTicketService(ticketId); const ticket = await ShowTicketService(ticketId);
// await SetTicketMessagesAsRead(ticket);
const botInfo = await BotIsOnQueue("botqueue");
if (
status == "closed" ||
(status == "open" && ticket && `${userId}` != `${botInfo.userIdBot}`)
) {
deleteObject(`${ticket.whatsappId}`, `${ticket.contactId}`, "ura");
}
const oldStatus = ticket.status; const oldStatus = ticket.status;
const oldUserId = ticket.user?.id; const oldUserId = ticket.user?.id;
@ -69,7 +79,6 @@ const UpdateTicketService = async ({
await ticket.reload(); await ticket.reload();
if (msg?.trim().length > 0) { if (msg?.trim().length > 0) {
setTimeout(async () => { setTimeout(async () => {
sendWhatsAppMessageSocket(ticket, msg); sendWhatsAppMessageSocket(ticket, msg);
}, 2000); }, 2000);

View File

@ -10,6 +10,7 @@ import Queue from "../../models/Queue";
interface SerializedUser { interface SerializedUser {
id: number; id: number;
name: string; name: string;
positionCompany: string;
email: string; email: string;
profile: string; profile: string;
queues: Queue[]; queues: Queue[];

View File

@ -8,6 +8,7 @@ interface Request {
email: string; email: string;
password: string; password: string;
name: string; name: string;
positionCompany?: string;
queueIds?: number[]; queueIds?: number[];
profile?: string; profile?: string;
} }
@ -15,6 +16,7 @@ interface Request {
interface Response { interface Response {
email: string; email: string;
name: string; name: string;
positionCompany: string;
id: number; id: number;
profile: string; profile: string;
} }
@ -23,6 +25,7 @@ const CreateUserService = async ({
email, email,
password, password,
name, name,
positionCompany,
queueIds = [], queueIds = [],
profile = "master" profile = "master"
}: Request): Promise<Response> => { }: Request): Promise<Response> => {
@ -70,6 +73,7 @@ const CreateUserService = async ({
email, email,
password, password,
name, name,
positionCompany,
profile profile
}, },
{ include: ["queues"] } { include: ["queues"] }

View File

@ -2,7 +2,7 @@ import User from "../../models/User";
import AppError from "../../errors/AppError"; import AppError from "../../errors/AppError";
import Ticket from "../../models/Ticket"; import Ticket from "../../models/Ticket";
import UpdateDeletedUserOpenTicketsStatus from "../../helpers/UpdateDeletedUserOpenTicketsStatus"; import UpdateDeletedUserOpenTicketsStatus from "../../helpers/UpdateDeletedUserOpenTicketsStatus";
import { set } from "../../helpers/RedisClient"
const DeleteUserService = async (id: string | number): Promise<void> => { const DeleteUserService = async (id: string | number): Promise<void> => {
const user = await User.findOne({ const user = await User.findOne({
@ -13,6 +13,17 @@ const DeleteUserService = async (id: string | number): Promise<void> => {
throw new AppError("ERR_NO_USER_FOUND", 404); throw new AppError("ERR_NO_USER_FOUND", 404);
} }
if (user?.name?.trim() == "botqueue") {
let botInfo = JSON.stringify({
userId: 0,
queueId: 0,
botIsOnQueue: false
});
botInfo = JSON.parse(botInfo);
await set("botInfo", botInfo);
}
const userOpenTickets: Ticket[] = await user.$get("tickets", { const userOpenTickets: Ticket[] = await user.$get("tickets", {
where: { status: "open" } where: { status: "open" }
}); });
@ -21,9 +32,7 @@ const DeleteUserService = async (id: string | number): Promise<void> => {
UpdateDeletedUserOpenTicketsStatus(userOpenTickets); UpdateDeletedUserOpenTicketsStatus(userOpenTickets);
} }
await user.destroy(); await user.destroy();
}; };
export default DeleteUserService; export default DeleteUserService;

View File

@ -0,0 +1,67 @@
import { Sequelize } from "sequelize";
import Whatsapp from "../../models/Whatsapp";
import WhatsappQueue from "../../models/WhatsappQueue";
import { List } from "whatsapp-web.js";
const dbConfig = require("../../config/database");
const { QueryTypes } = require("sequelize");
const sequelize = new Sequelize(dbConfig);
const ListWhatsappQueueByUserQueue = async (
userId: number | string,
profiles: string
): Promise<any> => {
const users = await sequelize.query(
`select wq.whatsappId, wq.queueId, uq.userId, uq.queueId, u.id, u.name, u.profile, w.number
from WhatsappQueues wq join UserQueues uq join Users u join Whatsapps w on
wq.queueId = uq.queueId where u.id = uq.userId and u.profile in (${profiles}) and u.id = ${userId}
and w.id = wq.whatsappId group by w.number ;`,
{ type: QueryTypes.SELECT }
);
return users;
};
const ListWhatsappQueesByUser = async (whatsappIds: string): Promise<any> => {
const users = await sequelize.query(
`SELECT
wq.whatsappId, wq.queueId, q.name, q.id, q.name, q.color
FROM
WhatsappQueues wq
JOIN
Queues q ON wq.whatsappId IN (${whatsappIds}) AND q.id = wq.queueId group by wq.queueId;
`,
{ type: QueryTypes.SELECT }
);
return users;
};
const ListUserByWhatsappQueuesService = async (
userId: number | string,
profiles: string
): Promise<any> => {
const whatsapps: any = await ListWhatsappQueueByUserQueue(userId, profiles);
let whatsappIds = whatsapps.map((w: any) => w.whatsappId);
if (whatsappIds.length == 0) return { users: [], queues: [] };
console.log("----------> whatsappIds: ", whatsappIds, " | userId: ", userId);
whatsappIds = whatsappIds.join(", ");
const users: any = await sequelize.query(
`select wq.whatsappId, wq.queueId, uq.userId, uq.queueId, u.id, u.name, u.profile
from WhatsappQueues wq join UserQueues uq join Users u on
wq.queueId = uq.queueId where u.id = uq.userId and u.profile in (${profiles}) and wq.whatsappId in (${whatsappIds}) group by u.id;`,
{ type: QueryTypes.SELECT }
);
const queues = await ListWhatsappQueesByUser(whatsappIds);
return { users, queues };
};
export default ListUserByWhatsappQueuesService;

View File

@ -1,77 +1,56 @@
import { Op, Sequelize } from "sequelize"; import { Op, Sequelize } from "sequelize";
import Queue from "../../models/Queue"; import Queue from "../../models/Queue";
import User from "../../models/User"; import User from "../../models/User";
import UserQueue from "../../models/UserQueue"; import UserQueue from "../../models/UserQueue";
import { List } from "whatsapp-web.js"
interface Request { interface Request {
userId?: string | number; userId?: string | number;
profile?: string; profile?: string;
raw?:boolean profiles?: Array<string>;
raw?: boolean;
userIds?: string | number;
} }
const ListUser = async ({ profile, userId, raw, userIds, profiles }: Request): Promise<User[]> => {
const ListUser = async ({ profile, userId, raw}: Request): Promise<User[]> => { let where_clause = {};
let where_clause = {}
if (userId && profile) { if (userId && profile) {
where_clause = { where_clause = {
[Op.and]: [ [Op.and]: [{ userId: userId }, { profile: profile }]
{ userId: userId }, };
{ profile: profile } } else if (userId) {
]
}
}
else if (userId) {
where_clause = { where_clause = {
[Op.and]: [ [Op.and]: [{ userId: userId }]
{ userId: userId }, };
] } else if (profile) {
}
}
else if (profile) {
where_clause = { where_clause = {
profile: profile profile: profile
} };
} else if (userIds) {
where_clause = {
id: { [Op.in]: userIds }
};
} else if (profiles) {
where_clause = {
profile: { [Op.in]: profiles }
};
} }
const users = await User.findAll({
const queryOptions:any = {
where: where_clause, where: where_clause,
raw, raw,
attributes: ["id", "name", "email"], attributes: ["id", "name", "email", "positionCompany"],
order: [["id", "ASC"]]
};
if (!raw) { include: [
queryOptions.include = [
{ model: Queue, as: "queues", attributes: ["id", "name", "color"] } { model: Queue, as: "queues", attributes: ["id", "name", "color"] }
]; ],
}
const users = await User.findAll(queryOptions);
// const users = await User.findAll({
// where: where_clause,
// raw,
// attributes: ['id', 'name', 'email'],
// include: [
// { model: Queue, as: "queues", attributes: ["id", "name", "color"] }
// ],
// order: [["id", "ASC"]],
// })
order: [["id", "ASC"]],
group: ["User.id"]
});
return users; return users;
}; };
export default ListUser; export default ListUser;

View File

@ -63,7 +63,7 @@ const ListUsersService = async ({
const { count, rows: users } = await User.findAndCountAll({ const { count, rows: users } = await User.findAndCountAll({
where: whereCondition, where: whereCondition,
attributes: ["name", "id", "email", "profile", "createdAt"], attributes: ["name", "id", "email","positionCompany", "profile", "createdAt"],
limit, limit,
offset, offset,
order: [["createdAt", "DESC"]], order: [["createdAt", "DESC"]],

View File

@ -4,7 +4,7 @@ import Queue from "../../models/Queue";
const ShowUserService = async (id: string | number): Promise<User> => { const ShowUserService = async (id: string | number): Promise<User> => {
const user = await User.findByPk(id, { const user = await User.findByPk(id, {
attributes: ["name", "id", "email", "profile", "tokenVersion"], attributes: ["name", "id", "email", "profile", "positionCompany", "tokenVersion"],
include: [ include: [
{ model: Queue, as: "queues", attributes: ["id", "name", "color"] } { model: Queue, as: "queues", attributes: ["id", "name", "color"] }
], ],

View File

@ -8,6 +8,7 @@ interface UserData {
email?: string; email?: string;
password?: string; password?: string;
name?: string; name?: string;
positionCompany?: string;
profile?: string; profile?: string;
queueIds?: number[]; queueIds?: number[];
} }
@ -60,7 +61,7 @@ const UpdateUserService = async ({
}); });
const { email, password, profile, name, queueIds = [] } = userData; const { email, password, profile, name, positionCompany, queueIds = [] } = userData;
try { try {
await schema.validate({ email, password, profile, name }); await schema.validate({ email, password, profile, name });
@ -73,6 +74,7 @@ const UpdateUserService = async ({
email, email,
password, password,
profile, profile,
positionCompany,
name name
}); });

View File

@ -5,7 +5,7 @@ import { getWbot } from "../../libs/wbot";
const CheckIsValidContact = async (number: string): Promise<any> => { const CheckIsValidContact = async (number: string): Promise<any> => {
const defaultWhatsapp = await GetDefaultWhatsApp(); const defaultWhatsapp = await GetDefaultWhatsApp({});
const wbot_url = await getWbot(defaultWhatsapp.id); const wbot_url = await getWbot(defaultWhatsapp.id);

View File

@ -16,6 +16,10 @@ const DeleteWhatsAppMessage = async (messageId: string): Promise<Message | any>
] ]
}); });
if(message && message?.phoneNumberId){
throw new AppError("Mensagens enviada pela API Oficial do Whatsapp não são deletadas!");
}
if (!message) { if (!message) {
throw new AppError("No message found with this ID."); throw new AppError("No message found with this ID.");
} }
@ -32,18 +36,6 @@ const DeleteWhatsAppMessage = async (messageId: string): Promise<Message | any>
limit: limit limit: limit
}) })
// console.log('messageToDelete.data.data: ',messageToDelete.data.data)
// const { ticket } = message;
// const messageToDelete = await GetWbotMessage(ticket, messageId);
// try {
// await messageToDelete.delete(true);
// } catch (err) {
// throw new AppError("ERR_DELETE_WAPP_MSG");
// }
if (messageToDelete && messageToDelete.data.data) { if (messageToDelete && messageToDelete.data.data) {
await message.update({ isDeleted: true }); await message.update({ isDeleted: true });
} }

View File

@ -4,19 +4,15 @@ import { getWbot } from "../../libs/wbot";
const GetProfilePicUrl = async (number: string): Promise<any> => { const GetProfilePicUrl = async (number: string): Promise<any> => {
const defaultWhatsapp = await GetDefaultWhatsApp(); const defaultWhatsapp = await GetDefaultWhatsApp({});
const wbot_url = await getWbot(defaultWhatsapp.id); const wbot_url = await getWbot(defaultWhatsapp.id);
// const profilePicUrl = await wbot.getProfilePicUrl(`${number}@c.us`);
let profilePicUrl = await endPointQuery(`${wbot_url}/api/GetProfilePicUrl`, { number: `${number}`, }) let profilePicUrl = await endPointQuery(`${wbot_url}/api/GetProfilePicUrl`, { number: `${number}`, })
console.log('profilePicUrl.data.data: ', profilePicUrl.data.data) console.log('profilePicUrl.data.data: ', profilePicUrl.data.data)
if (profilePicUrl && profilePicUrl.data.data) { if (profilePicUrl && profilePicUrl.data.data) {
console.log('GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG')
return profilePicUrl.data.data; return profilePicUrl.data.data;
} }

View File

@ -4,47 +4,61 @@ import AppError from "../../errors/AppError";
import GetTicketWbot from "../../helpers/GetTicketWbot"; import GetTicketWbot from "../../helpers/GetTicketWbot";
import Ticket from "../../models/Ticket"; import Ticket from "../../models/Ticket";
import { updateTicketCacheByTicketId } from '../../helpers/TicketCache' import { updateTicketCacheByTicketId } from "../../helpers/TicketCache";
import { date } from "faker"; import { date } from "faker";
import { getIO } from "../../libs/socket"; import { getIO } from "../../libs/socket";
import sendWhatsAppMessageSocket from "../../helpers/SendWhatsappMessageSocket"; import sendWhatsAppMessageSocket from "../../helpers/SendWhatsappMessageSocket";
import sendWhatsAppMediaSocket from "../../helpers/SendWhatsappMessageMediaSocket"; import sendWhatsAppMediaSocket from "../../helpers/SendWhatsappMessageMediaSocket";
import sendWhatsMediaOfficialAPI from "../../helpers/sendWhatsMediaOfficialAPI";
import { mediaTypeWhatsappOfficial } from "./wbotMessageListener";
import { bytesToMB } from "../../helpers/BytesToMB";
interface Request { interface Request {
media: Express.Multer.File; media: Express.Multer.File;
ticket: Ticket; ticket: Ticket;
mic_audio?: any
} }
const SendWhatsAppMedia = async ({ const SendWhatsAppMedia = async ({
media, media,
ticket ticket,
mic_audio
}: Request): Promise<WbotMessage | any> => { }: Request): Promise<WbotMessage | any> => {
const { phoneNumberId } = ticket;
if (phoneNumberId) {
const { type, mbMaxSize }: any = mediaTypeWhatsappOfficial(media.mimetype);
const filesize: any = bytesToMB(media.size);
if (filesize > mbMaxSize) {
throw new AppError("FILE TOO LARGE!");
}
if (!type) {
throw new AppError("FILE TYPE NOT SUPPORTED!");
}
sendWhatsMediaOfficialAPI(ticket, media, type, mic_audio);
return;
}
try { try {
// const wbot = await GetTicketWbot(ticket);
const newMedia = MessageMedia.fromFilePath(media.path); const newMedia = MessageMedia.fromFilePath(media.path);
//const sentMessage = await wbot.sendMessage(`${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us`, newMedia, { sendAudioAsVoice: true });
sendWhatsAppMediaSocket(ticket, newMedia); sendWhatsAppMediaSocket(ticket, newMedia);
await ticket.update({ lastMessage: media.filename }); await ticket.update({ lastMessage: media.filename });
// TEST DEL await updateTicketCacheByTicketId(ticket.id, {
await updateTicketCacheByTicketId(ticket.id, { lastMessage: media.filename, updatedAt: new Date(ticket.updatedAt).toISOString() }) lastMessage: media.filename,
// updatedAt: new Date(ticket.updatedAt).toISOString()
});
console.log('media.path: ', media.path) console.log("media.path: ", media.path);
fs.unlinkSync(media.path); fs.unlinkSync(media.path);
// return sentMessage;
} catch (err) { } catch (err) {
throw new AppError("ERR_SENDING_WAPP_MSG"); throw new AppError("ERR_SENDING_WAPP_MSG");
} }
}; };
export default SendWhatsAppMedia; export default SendWhatsAppMedia;

View File

@ -8,17 +8,19 @@ import Ticket from "../../models/Ticket";
import Whatsapp from "../../models/Whatsapp"; import Whatsapp from "../../models/Whatsapp";
import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService"; import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService";
import wbotByUserQueue from '../../helpers/GetWbotByUserQueue' import wbotByUserQueue from "../../helpers/GetWbotByUserQueue";
import { WhatsIndex } from "../../helpers/LoadBalanceWhatsSameQueue"; import { WhatsIndex } from "../../helpers/LoadBalanceWhatsSameQueue";
import { deleteTicketsByContactsCache, updateTicketCacheByTicketId } from '../../helpers/TicketCache' import {
deleteTicketsByContactsCache,
updateTicketCacheByTicketId
} from "../../helpers/TicketCache";
import ListWhatsAppsNumber from "../WhatsappService/ListWhatsAppsNumber"; import ListWhatsAppsNumber from "../WhatsappService/ListWhatsAppsNumber";
import { getWbot } from "../../libs/wbot"; import { getWbot } from "../../libs/wbot";
import { json } from "sequelize/types"; import { json } from "sequelize/types";
import sendMessageMultiSession from "../../helpers/TrySendMessageMultiSession";
import { restartWhatsSession } from "../../helpers/RestartWhatsSession"; import { restartWhatsSession } from "../../helpers/RestartWhatsSession";
// import { insertOrUpeateWhatsCache, searchWhatsappCache } from "../../helpers/WhatsCache"; // import { insertOrUpeateWhatsCache, searchWhatsappCache } from "../../helpers/WhatsCache";
import GetDefaultWhatsApp from "../../helpers/GetDefaultWhatsApp"; import GetDefaultWhatsApp from "../../helpers/GetDefaultWhatsApp";
@ -26,14 +28,13 @@ import autoRestore from "../../helpers/AutoRestore";
import { _restore } from "../../helpers/RestoreControll"; import { _restore } from "../../helpers/RestoreControll";
import { getIO } from "../../libs/socket"; import { getIO } from "../../libs/socket";
import sendWhatsAppMessageSocket from "../../helpers/SendWhatsappMessageSocket"; import sendWhatsAppMessageSocket from "../../helpers/SendWhatsappMessageSocket";
import sendWhatsAppMessageOfficialAPI from "../../helpers/sendWhatsAppMessageOfficialAPI";
interface Request { interface Request {
body: string; body: string;
ticket: Ticket; ticket: Ticket;
quotedMsg?: Message; quotedMsg?: Message;
number?: string number?: string;
} }
const SendWhatsAppMessage = async ({ const SendWhatsAppMessage = async ({
@ -42,112 +43,111 @@ const SendWhatsAppMessage = async ({
quotedMsg, quotedMsg,
number number
}: Request): Promise<WbotMessage | any> => { }: Request): Promise<WbotMessage | any> => {
try { try {
const { phoneNumberId } = ticket;
// let timestamp = Math.floor(Date.now() / 1000) if (phoneNumberId) {
let timestamp = Date.now() + String(Math.floor(Math.random() * 1000)) sendWhatsAppMessageOfficialAPI(ticket, body, quotedMsg);
return;
}
let timestamp = Date.now() + String(Math.floor(Math.random() * 1000));
var timetaken = `########################################${timestamp}| TicketId: ${ticket.id} => Time taken to send the message`; var timetaken = `########################################${timestamp}| TicketId: ${ticket.id} => Time taken to send the message`;
console.time(timetaken) console.time(timetaken);
let quotedMsgSerializedId: string | undefined; let quotedMsgSerializedId: string | undefined;
if (quotedMsg) { if (quotedMsg) {
await GetWbotMessage(ticket, quotedMsg.id); await GetWbotMessage(ticket, quotedMsg.id);
quotedMsgSerializedId = SerializeWbotMsgId(ticket, quotedMsg); quotedMsgSerializedId = SerializeWbotMsgId(ticket, quotedMsg);
} }
console.log('quotedMsgSerializedId: ', quotedMsgSerializedId) console.log("quotedMsgSerializedId: ", quotedMsgSerializedId);
let whatsapps: any;
let whatsapps: any let listWhatsapp = null;
let listWhatsapp = null
// listWhatsapp = await searchWhatsappCache(`${ticket.whatsappId}`, 'CONNECTED') // listWhatsapp = await searchWhatsappCache(`${ticket.whatsappId}`, 'CONNECTED')
if (!ticket.whatsappId) { if (!ticket.whatsappId) {
const defaultWhatsapp: any = await GetDefaultWhatsApp({
const defaultWhatsapp: any = await GetDefaultWhatsApp(ticket.userId); userId: ticket.userId
});
await ticket.update({ whatsappId: +defaultWhatsapp.id }); await ticket.update({ whatsappId: +defaultWhatsapp.id });
} }
if (!listWhatsapp) { if (!listWhatsapp) {
listWhatsapp = await ListWhatsAppsNumber(ticket.whatsappId, 'CONNECTED') listWhatsapp = await ListWhatsAppsNumber(ticket.whatsappId, "CONNECTED");
} }
if (listWhatsapp.whatsapp && listWhatsapp.whatsapp.status != 'CONNECTED' && listWhatsapp.whatsapps.length > 0) { if (
listWhatsapp.whatsapp &&
await ticket.update({ whatsappId: + listWhatsapp.whatsapps[0].id }); listWhatsapp.whatsapp.status != "CONNECTED" &&
listWhatsapp.whatsapps.length > 0
) {
await ticket.update({ whatsappId: +listWhatsapp.whatsapps[0].id });
} }
if (listWhatsapp.whatsapps.length > 1) { if (listWhatsapp.whatsapps.length > 1) {
const _whatsapp =
const _whatsapp = listWhatsapp.whatsapps[Math.floor(Math.random() * listWhatsapp.whatsapps.length)]; listWhatsapp.whatsapps[
Math.floor(Math.random() * listWhatsapp.whatsapps.length)
];
await ticket.update({ whatsappId: +_whatsapp.id }); await ticket.update({ whatsappId: +_whatsapp.id });
} }
if (listWhatsapp.whatsapps.length == 0 && listWhatsapp.whatsapp.status != 'CONNECTED') { if (
listWhatsapp.whatsapps.length == 0 &&
console.log('listWhatsapp.whatsapps == 0') listWhatsapp.whatsapp.status != "CONNECTED"
) {
whatsapps = await wbotByUserQueue(ticket.userId) whatsapps = await wbotByUserQueue({ userId: ticket.userId });
console.log('============> The whatsapps: ', whatsapps)
if (whatsapps.length > 0) { if (whatsapps.length > 0) {
whatsapps = whatsapps.filter((w: any) => !w?.isOfficial);
if (whatsapps.length > 1) { if (whatsapps.length > 1) {
await ticket.update({
await ticket.update({ whatsappId: whatsapps[+WhatsIndex(whatsapps)].id }); whatsappId: whatsapps[+WhatsIndex(whatsapps)].id
});
} } else if (whatsapps && whatsapps.length == 1) {
else {
await ticket.update({ whatsappId: whatsapps[0].id }); await ticket.update({ whatsappId: whatsapps[0].id });
} else {
throw new Error(
"Sessão de Whatsapp desconectada! Entre em contato com o suporte."
);
} }
} }
} }
console.log('1 --------> send from whatsapp ticket.whatsappId: ', ticket.whatsappId) console.log(
"1 --------> send from whatsapp ticket.whatsappId: ",
ticket.whatsappId
);
try { try {
sendWhatsAppMessageSocket(ticket, body, quotedMsgSerializedId, number); sendWhatsAppMessageSocket(ticket, body, quotedMsgSerializedId, number);
await ticket.update({ lastMessage: body }); await ticket.update({ lastMessage: body });
await updateTicketCacheByTicketId(ticket.id, { lastMessage: body, updatedAt: new Date(ticket.updatedAt).toISOString() }) await updateTicketCacheByTicketId(ticket.id, {
lastMessage: body,
console.timeEnd(timetaken) updatedAt: new Date(ticket.updatedAt).toISOString()
});
console.timeEnd(timetaken);
} catch (err: any) { } catch (err: any) {
console.error("0 ===> Error on SendWhatsAppMessage.ts file: \n", err);
console.error('0 ===> Error on SendWhatsAppMessage.ts file: \n', err)
throw new AppError("ERR_SENDING_WAPP_MSG"); throw new AppError("ERR_SENDING_WAPP_MSG");
} }
} catch (error: any) { } catch (error: any) {
console.error('===> Error on SendWhatsAppMessage.ts file: \n', error) console.error("===> Error on SendWhatsAppMessage.ts file: \n", error);
throw new AppError(error.message); throw new AppError(error.message);
} }
}; };
export default SendWhatsAppMessage; export default SendWhatsAppMessage;

View File

@ -87,6 +87,10 @@ import { Op } from "sequelize";
import SettingTicket from "../../models/SettingTicket"; import SettingTicket from "../../models/SettingTicket";
import mostRepeatedPhrase from "../../helpers/MostRepeatedPhrase"; import mostRepeatedPhrase from "../../helpers/MostRepeatedPhrase";
import ListWhatsAppsNumber from "../WhatsappService/ListWhatsAppsNumber";
import { createObject, findObject, get } from "../../helpers/RedisClient";
import FindOrCreateTicketServiceBot from "../TicketServices/FindOrCreateTicketServiceBot"
import ShowTicketService from "../TicketServices/ShowTicketService"
var lst: any[] = getWhatsappIds(); var lst: any[] = getWhatsappIds();
@ -96,16 +100,27 @@ interface Session extends Client {
const writeFileAsync = promisify(writeFile); const writeFileAsync = promisify(writeFile);
const verifyContact = async (msgContact: any): Promise<Contact> => { const verifyContact = async (
// const profilePicUrl = await msgContact.getProfilePicUrl(); msgContact: any,
const profilePicUrl = msgContact.getProfilePicUrl; whatsAppOfficial?: any
): Promise<Contact> => {
const contactData = { let contactData: any = {};
name: msgContact.name || msgContact.pushname || msgContact.id.user, if (msgContact) {
number: msgContact.id.user, contactData = {
profilePicUrl, name: msgContact.name || msgContact.pushname || msgContact.id.user,
isGroup: msgContact.isGroup number: msgContact.id.user,
}; profilePicUrl: msgContact.getProfilePicUrl,
isGroup: msgContact.isGroup
};
} else {
contactData = {
name: whatsAppOfficial?.name
? whatsAppOfficial.name
: whatsAppOfficial.number,
number: whatsAppOfficial.number,
isGroup: false
};
}
const contact = CreateOrUpdateContactService(contactData); const contact = CreateOrUpdateContactService(contactData);
@ -138,48 +153,11 @@ const verifyMediaMessage = async (
media: any, media: any,
quotedMsg?: any quotedMsg?: any
): Promise<Message | any> => { ): Promise<Message | any> => {
// const quotedMsg = await verifyQuotedMessage(msg);
// const media = await msg.downloadMedia();
if (!media) { if (!media) {
throw new Error("ERR_WAPP_DOWNLOAD_MEDIA"); throw new Error("ERR_WAPP_DOWNLOAD_MEDIA");
} }
console.log( let messageData = {
"MEDIA.FILENAME: ",
media.fileName,
" | msg.fromMe: ",
msg.fromMe
);
if (!media.filename) {
console.log("No file name -----------------------------------------");
const ext = media.mimetype.split("/")[1].split(";")[0];
media.filename = `${new Date().getTime()}.${ext}`;
}
try {
// await writeFileAsync(
// join(__dirname, "..", "..", "..", "public", media.filename),
// media.data,
// "base64"
// );
console.log("FROM wbotMessageListener.ts media.filename: ", media.filename);
await writeFileAsync(
join(__dirname, "..", "..", "..", "..", "..", "public", media.filename),
media.data,
"base64"
);
} catch (err) {
Sentry.captureException(err);
logger.error(`There was an error: wbotMessageLitener.ts: ${err}`);
}
const messageData = {
id: msg.id.id, id: msg.id.id,
ticketId: ticket.id, ticketId: ticket.id,
contactId: msg.fromMe ? undefined : contact.id, contactId: msg.fromMe ? undefined : contact.id,
@ -188,26 +166,112 @@ const verifyMediaMessage = async (
read: msg.fromMe, read: msg.fromMe,
mediaUrl: media.filename, mediaUrl: media.filename,
mediaType: media.mimetype.split("/")[0], mediaType: media.mimetype.split("/")[0],
quotedMsgId: quotedMsg quotedMsgId: quotedMsg,
// quotedMsgId: quotedMsg?.id phoneNumberId: msg?.phoneNumberId
}; };
if (!ticket?.phoneNumberId) {
if (!media.filename) {
const ext = media.mimetype.split("/")[1].split(";")[0];
media.filename = `${new Date().getTime()}.${ext}`;
messageData = {
...messageData,
mediaUrl: media.filename,
body: media.filename
};
}
try {
await writeFileAsync(
join(__dirname, "..", "..", "..", "..", "..", "public", media.filename),
media.data,
"base64"
);
} catch (err) {
Sentry.captureException(err);
logger.error(`There was an error: wbotMessageLitener.ts: ${err}`);
}
}
await ticket.update({ lastMessage: msg.body || media.filename }); await ticket.update({ lastMessage: msg.body || media.filename });
const newMessage = await CreateMessageService({ messageData }); const newMessage = await CreateMessageService({ messageData });
return newMessage; return newMessage;
}; };
// const verifyMediaMessage = async (
// msg: any,
// ticket: Ticket,
// contact: Contact,
// media: any,
// quotedMsg?: any
// ): Promise<Message | any> => {
// // const quotedMsg = await verifyQuotedMessage(msg);
// // const media = await msg.downloadMedia();
// if (!media) {
// throw new Error("ERR_WAPP_DOWNLOAD_MEDIA");
// }
// console.log(
// "MEDIA.FILENAME: ",
// media.fileName,
// " | msg.fromMe: ",
// msg.fromMe
// );
// if (!media.filename) {
// console.log("No file name -----------------------------------------");
// const ext = media.mimetype.split("/")[1].split(";")[0];
// media.filename = `${new Date().getTime()}.${ext}`;
// }
// try {
// // await writeFileAsync(
// // join(__dirname, "..", "..", "..", "public", media.filename),
// // media.data,
// // "base64"
// // );
// console.log("FROM wbotMessageListener.ts media.filename: ", media.filename);
// await writeFileAsync(
// join(__dirname, "..", "..", "..", "..", "..", "public", media.filename),
// media.data,
// "base64"
// );
// } catch (err) {
// Sentry.captureException(err);
// logger.error(`There was an error: wbotMessageLitener.ts: ${err}`);
// }
// const messageData = {
// id: msg.id.id,
// ticketId: ticket.id,
// contactId: msg.fromMe ? undefined : contact.id,
// body: msg.body || media.filename,
// fromMe: msg.fromMe,
// read: msg.fromMe,
// mediaUrl: media.filename,
// mediaType: media.mimetype.split("/")[0],
// quotedMsgId: quotedMsg
// // quotedMsgId: quotedMsg?.id
// };
// await ticket.update({ lastMessage: msg.body || media.filename });
// const newMessage = await CreateMessageService({ messageData });
// return newMessage;
// };
const verifyMessage = async ( const verifyMessage = async (
msg: WbotMessage, msg: any,
ticket: Ticket, ticket: Ticket,
contact: Contact, contact: Contact,
quotedMsg?: any quotedMsg?: any
) => { ) => {
// const quotedMsg = await verifyQuotedMessage(msg);
// const quotedMsg = await verifyQuotedMessage(msg);
const messageData = { const messageData = {
id: msg.id.id, id: msg.id.id,
ticketId: ticket.id, ticketId: ticket.id,
@ -216,8 +280,8 @@ const verifyMessage = async (
fromMe: msg.fromMe, fromMe: msg.fromMe,
mediaType: msg.type, mediaType: msg.type,
read: msg.fromMe, read: msg.fromMe,
quotedMsgId: quotedMsg quotedMsgId: quotedMsg,
// quotedMsgId: quotedMsg?.id phoneNumberId: msg?.phoneNumberId
}; };
await ticket.update({ lastMessage: msg.body }); await ticket.update({ lastMessage: msg.body });
@ -233,20 +297,12 @@ const verifyQueue = async (
) => { ) => {
const { queues, greetingMessage } = await ShowWhatsAppService(wbot.id!); const { queues, greetingMessage } = await ShowWhatsAppService(wbot.id!);
/*if (queues.length === 1) {
await UpdateTicketService({
ticketData: { queueId: queues[0].id },
ticketId: ticket.id
});
return;
}*/
let selectedOption = null; let selectedOption = null;
let choosenQueue = null; let choosenQueue = null;
//Habilitar esse caso queira usar o bot //Habilitar esse caso queira usar o bot
// const botInfo = await BotIsOnQueue('botqueue') const botInfo = await BotIsOnQueue("botqueue");
const botInfo = { isOnQueue: false, botQueueId: 0, userIdBot: 0 }; // const botInfo = { isOnQueue: false, botQueueId: 0, userIdBot: 0 };
if (botInfo.isOnQueue) { if (botInfo.isOnQueue) {
choosenQueue = await ShowQueueService(botInfo.botQueueId); choosenQueue = await ShowQueueService(botInfo.botQueueId);
@ -284,13 +340,23 @@ const verifyQueue = async (
ticketId: ticket.id ticketId: ticket.id
}); });
data_ura.forEach((s, index) => { const data = await get("ura");
botOptions += `*${index + 1}* - ${s.option}\n`;
await createObject({
whatsappId: `${ticket.whatsappId}`,
contactId: `${ticket.contactId}`,
identifier: "ura",
value: data[1].id
}); });
botSendMessage(ticket, data[1].value);
return;
} }
// //
const outService = await outOfService(); let whatsapp: any = await whatsappInfo(ticket?.whatsappId);
const outService = await outOfService(whatsapp?.number);
if (outService.length > 0) { if (outService.length > 0) {
const { type, msg: msgOutService } = outService[0]; const { type, msg: msgOutService } = outService[0];
@ -307,9 +373,6 @@ const verifyQueue = async (
body = `\u200e${choosenQueue.greetingMessage}`; body = `\u200e${choosenQueue.greetingMessage}`;
} }
// const sentMessage = await wbot.sendMessage(`${contact.number}@c.us`, body);
// await verifyMessage(sentMessage, ticket, contact);
sendWhatsAppMessageSocket(ticket, body); sendWhatsAppMessageSocket(ticket, body);
} else { } else {
//test del transfere o atendimento se entrar na ura infinita //test del transfere o atendimento se entrar na ura infinita
@ -336,14 +399,13 @@ const verifyQueue = async (
const body = `\u200e${greetingMessage}\n${options}`; const body = `\u200e${greetingMessage}\n${options}`;
const { phoneNumberId } = ticket;
const debouncedSentMessage = debounce( const debouncedSentMessage = debounce(
async () => { async () => {
// const sentMessage = await wbot.sendMessage(`${contact.number}@c.us`, body);
// verifyMessage(sentMessage, ticket, contact);
sendWhatsAppMessageSocket(ticket, body); sendWhatsAppMessageSocket(ticket, body);
}, },
3000, phoneNumberId ? 0 : 3000,
ticket.id ticket.id
); );
@ -352,9 +414,57 @@ const verifyQueue = async (
} }
}; };
const isValidMsg = (msg: WbotMessage): boolean => { const mediaTypeWhatsappOfficial = (mimetype: string): object => {
const document = [
"text/plain",
"text/csv",
"application/pdf",
"application/vnd.ms-powerpoint",
"application/msword",
"application/vnd.ms-excel",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
];
const image = ["image/jpeg", "image/png"];
const video = ["video/mp4", "video/3gp"];
const sticker = ["image/webp"];
const audio = [
"audio/mp3",
"audio/aac",
"audio/mp4",
"audio/mpeg",
"audio/amr",
"audio/ogg"
];
const types = [
{ name: "document", values: document, mbMaxSize: 15 },
{ name: "image", values: image, mbMaxSize: 5 },
{ name: "video", values: video, mbMaxSize: 15 },
{ name: "sticker", values: sticker, mbMaxSize: 0.5 },
{ name: "audio", values: audio, mbMaxSize: 15 }
];
for (let i in types) {
if (types[i].values.includes(mimetype)) {
return { type: types[i].name, mbMaxSize: types[i].mbMaxSize };
}
}
return { type: null, mbsize: 0 };
};
const isValidMsg = (msg: any): boolean => {
if (msg.from === "status@broadcast") return false; if (msg.from === "status@broadcast") return false;
if ( if (
msg.type === "template" ||
msg.type === "text" ||
msg.type === "hsm" ||
msg.type === "chat" || msg.type === "chat" ||
msg.type === "audio" || msg.type === "audio" ||
msg.type === "ptt" || msg.type === "ptt" ||
@ -380,12 +490,35 @@ const queuesOutBot = async (wbot: Session, botId: string | number) => {
return { queues, greetingMessage }; return { queues, greetingMessage };
}; };
const botTransferTicket = async ( const transferTicket = async (queueName: any, wbot: any, ticket: Ticket) => {
queues: Queue, const botInfo = await BotIsOnQueue("botqueue");
ticket: Ticket,
contact: Contact, console.log("kkkkkkkkkkkkkkkkkkkkk queueName: ", queueName);
wbot: Session
) => { const queuesWhatsGreetingMessage = await queuesOutBot(
wbot,
botInfo.botQueueId
);
let queue: any;
const queues = queuesWhatsGreetingMessage.queues;
// console.log("queues ---> ", console.log(JSON.stringify(queues, null, 6)));
if (typeof queueName == "string") {
queue = queues.find(
(q: any) => q?.name?.toLowerCase() == queueName.trim().toLowerCase()
);
// await deleteObject(wbot.id, `${ticket.contactId}`, "ura");
} else if (typeof queueName == "number") {
queue = queues[queueName];
}
if (queue) await botTransferTicket(queue, ticket);
};
const botTransferTicket = async (queues: Queue, ticket: Ticket) => {
await ticket.update({ userId: null }); await ticket.update({ userId: null });
await UpdateTicketService({ await UpdateTicketService({
@ -395,43 +528,19 @@ const botTransferTicket = async (
}; };
const botSendMessage = (ticket: Ticket, msg: string) => { const botSendMessage = (ticket: Ticket, msg: string) => {
const { phoneNumberId } = ticket;
const debouncedSentMessage = debounce( const debouncedSentMessage = debounce(
async () => { async () => {
//OLD
// const sentMessage = await wbot.sendMessage(`${contact.number}@c.us`, `${msg}`);
// verifyMessage(sentMessage, ticket, contact);
//NEW
await SendWhatsAppMessage({ body: msg, ticket }); await SendWhatsAppMessage({ body: msg, ticket });
}, },
3000, phoneNumberId ? 0 : 3000,
ticket.id ticket.id
); );
debouncedSentMessage(); debouncedSentMessage();
}; };
// const botSendMessage = (
// ticket: Ticket,
// contact: Contact,
// wbot: Session,
// msg: string
// ) => {
// const debouncedSentMessage = debounce(
// async () => {
// const sentMessage = await wbot.sendMessage(
// `${contact.number}@c.us`,
// `${msg}`
// );
// verifyMessage(sentMessage, ticket, contact);
// },
// 3000,
// ticket.id
// );
// debouncedSentMessage();
// };
const _clear_lst = () => { const _clear_lst = () => {
console.log("THE lst.length: ", lst.length); console.log("THE lst.length: ", lst.length);
@ -446,30 +555,29 @@ const _clear_lst = () => {
setWhatsappId(whatsappIdsSplited, true); setWhatsappId(whatsappIdsSplited, true);
}; };
const handleMessage = async (msg: any, wbot: any): Promise<void> => { const handleMessage = async (
// if (!msg.fromMe) { msg: any,
_clear_lst(); wbot: any,
whatsAppOfficial?: any
): Promise<void> => {
if (!whatsAppOfficial) {
_clear_lst();
let index = lst.findIndex((x: any) => x.id == msg.id.id); let index = lst.findIndex((x: any) => x.id == msg.id.id);
console.log("INDEX: ", index); console.log("INDEX: ", index);
if (index == -1) { if (index == -1) {
// console.log('-----------------> LST: ', lst):q lst.push({ id: msg.id.id });
lst.push({ id: msg.id.id }); setWhatsappId(msg.id.id);
} else {
console.log("IGNORED ID: ", msg.id.id);
setWhatsappId(msg.id.id); return;
} else { }
console.log("IGNORED ID: ", msg.id.id);
return;
} }
// console.log('LIST OF ID MESSAGE lst: ', lst)
// }
if (!isValidMsg(msg)) { if (!isValidMsg(msg)) {
return; return;
} }
@ -479,44 +587,32 @@ const handleMessage = async (msg: any, wbot: any): Promise<void> => {
// let groupContact: Contact | undefined; // let groupContact: Contact | undefined;
if (msg.fromMe) { if (msg.fromMe) {
const ticketExpiration = await SettingTicket.findOne({ const whatsapp = await whatsappInfo(wbot.id);
where: { key: "ticketExpiration" }
});
if ( if (whatsapp?.number) {
ticketExpiration && const ticketExpiration = await SettingTicket.findOne({
ticketExpiration.value == "enabled" && where: { key: "ticketExpiration", number: whatsapp.number }
ticketExpiration?.message.trim() == msg.body.trim() });
) {
console.log("*********** TICKET EXPIRATION"); if (
return; ticketExpiration &&
ticketExpiration.value == "enabled" &&
ticketExpiration?.message.trim() == msg.body.trim()
) {
console.log("*********** TICKET EXPIRATION");
return;
}
} }
// console.log('FROM ME: ', msg.fromMe, ' | /\u200e/.test(msg.body[0]: ', (/\u200e/.test(msg.body[0])))
// messages sent automatically by wbot have a special character in front of it // messages sent automatically by wbot have a special character in front of it
// if so, this message was already been stored in database; // if so, this message was already been stored in database;
// if (/\u200e/.test(msg.body[0])) return; // if (/\u200e/.test(msg.body[0])) return;
// console.log('PASSOU 1')
// media messages sent from me from cell phone, first comes with "hasMedia = false" and type = "image/ptt/etc" // media messages sent from me from cell phone, first comes with "hasMedia = false" and type = "image/ptt/etc"
// in this case, return and let this message be handled by "media_uploaded" event, when it will have "hasMedia = true" // in this case, return and let this message be handled by "media_uploaded" event, when it will have "hasMedia = true"
if (!msg.hasMedia && msg.type !== "chat" && msg.type !== "vcard") return; if (!msg.hasMedia && msg.type !== "chat" && msg.type !== "vcard") return;
// console.log('PASSOU 2')
// msgContact = await wbot.getContactById(msg.to);
// console.log('1 --------------> msgContat: ', JSON.parse(JSON.stringify(msgContact)))
// console.log(' # msg.type: ', msg.type )
} else { } else {
// msgContact = await msg.getContact();
// console.log('2 --------------> msgContat: ', JSON.parse(JSON.stringify(msgContact)))
//
console.log(`\n <<<<<<<<<< RECEIVING MESSAGE: console.log(`\n <<<<<<<<<< RECEIVING MESSAGE:
Parcial msg and msgContact info: Parcial msg and msgContact info:
msgContact.name: ${msgContact.name} msgContact.name: ${msgContact.name}
@ -529,7 +625,6 @@ const handleMessage = async (msg: any, wbot: any): Promise<void> => {
msg.from: ${msg.from} msg.from: ${msg.from}
msg.to: ${msg.to}\n`); msg.to: ${msg.to}\n`);
} }
// const chat = await msg.getChat();
const chat = wbot.chat; const chat = wbot.chat;
// if(chat.isGroup){ // if(chat.isGroup){
@ -554,13 +649,14 @@ const handleMessage = async (msg: any, wbot: any): Promise<void> => {
const whatsapp = await ShowWhatsAppService(wbot.id!); const whatsapp = await ShowWhatsAppService(wbot.id!);
// const whatsapp = await ShowWhatsAppService(46);
const unreadMessages = msg.fromMe ? 0 : chat.unreadCount; const unreadMessages = msg.fromMe ? 0 : chat.unreadCount;
const contact = await verifyContact(msgContact); const contact = !whatsAppOfficial
? await verifyContact(msgContact)
// console.log('----------> contact: ', JSON.parse(JSON.stringify(contact))) : await verifyContact(null, {
number: wbot.msgContact.number,
name: wbot.msgContact.name
});
if ( if (
unreadMessages === 0 && unreadMessages === 0 &&
@ -569,15 +665,39 @@ const handleMessage = async (msg: any, wbot: any): Promise<void> => {
) )
return; return;
const ticket = await FindOrCreateTicketService( let ticket;
contact,
wbot.id!, const _botInfo = await BotIsOnQueue("botqueue");
unreadMessages
// groupContact if (_botInfo.isOnQueue) {
); let ticket_obj: any = await FindOrCreateTicketServiceBot(
contact,
wbot.id!,
unreadMessages
// groupContact
);
ticket = ticket_obj.ticket;
if (ticket_obj.created) {
let queue = await ShowQueueService(_botInfo.botQueueId);
await UpdateTicketService({
ticketData: { queueId: queue.id },
ticketId: ticket.id
});
ticket = await ShowTicketService(ticket.id);
}
} else {
ticket = await FindOrCreateTicketService(
contact,
wbot.id!,
unreadMessages
// groupContact
);
}
//
// await updateTicketCacheByTicketId(ticket.id, {'contact.profilePicUrl': ticket.contact.profilePicUrl})
if (getSettingValue("oneContactChatWithManyWhats")?.value == "disabled") { if (getSettingValue("oneContactChatWithManyWhats")?.value == "disabled") {
// Para responder para o cliente pelo mesmo whatsapp que ele enviou a mensagen // Para responder para o cliente pelo mesmo whatsapp que ele enviou a mensagen
@ -624,9 +744,88 @@ const handleMessage = async (msg: any, wbot: any): Promise<void> => {
// 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 // 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
//Habilitar esse caso queira usar o bot //Habilitar esse caso queira usar o bot
// const botInfo = await BotIsOnQueue('botqueue') const botInfo = await BotIsOnQueue("botqueue");
// const botInfo = { isOnQueue: false, botQueueId: 0, userIdBot: 0 }; // const botInfo = { isOnQueue: false, botQueueId: 0, userIdBot: 0 };
if (
botInfo.isOnQueue &&
!msg.fromMe &&
ticket.userId == botInfo.userIdBot
) {
const repet: any = await mostRepeatedPhrase(ticket.id);
console.log("repet.occurrences: ", repet.occurrences);
if (repet.occurrences > 4) {
await transferTicket(0, wbot, ticket);
await SendWhatsAppMessage({
body: `Seu atendimento foi transferido para um agente!\n\nPara voltar ao menu principal digite *0*
`,
ticket,
number: `${contact.number}@c.us`
});
} 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" && String(msg.body).length > 120) {
botSendMessage(
ticket,
`Desculpe, nao compreendi!\nTexto acima de 120 caracteres!\n _Digite *0* para voltar ao menu principal._`
);
return;
}
const menuMsg: any = await menu(msg.body, wbot.id, contact.id);
console.log("menuMsg: ", menuMsg);
await botSendMessage(ticket, menuMsg.value);
if (
menuMsg?.transferToQueue &&
menuMsg.transferToQueue.trim().length > 0
) {
console.log(
"YYYYYYYYYYYYYYYYYYYY menuMsg.transferToQueue: ",
menuMsg.transferToQueue
);
transferTicket(menuMsg.transferToQueue.trim(), wbot, ticket);
}
}
return;
} else if (
!msg.fromMe &&
msg.body == "0" &&
ticket.status == "pending" &&
ticket.queueId
) {
let choosenQueue = await ShowQueueService(botInfo.botQueueId);
await UpdateTicketService({
ticketData: {
status: "open",
userId: botInfo.userIdBot,
queueId: choosenQueue.id
},
ticketId: ticket.id
});
const menuMsg: any = await menu(msg.body, wbot.id, contact.id);
await botSendMessage(ticket, menuMsg.value);
return;
}
if (msg && !msg.fromMe && ticket.status == "pending") { if (msg && !msg.fromMe && ticket.status == "pending") {
await setMessageAsRead(ticket); await setMessageAsRead(ticket);
} }
@ -638,7 +837,9 @@ const handleMessage = async (msg: any, wbot: any): Promise<void> => {
} }
if (ticketHasQueue && ticket.status != "open") { if (ticketHasQueue && ticket.status != "open") {
const outService = await outOfService(); let whatsapp: any = await whatsappInfo(ticket?.whatsappId);
const outService = await outOfService(whatsapp.number);
if (outService.length > 0) { if (outService.length > 0) {
const { type, msg: msgOutService } = outService[0]; const { type, msg: msgOutService } = outService[0];
@ -659,8 +860,143 @@ const handleMessage = async (msg: any, wbot: any): Promise<void> => {
} }
}; };
const handleMsgAck = async (msg_id: any, ack: any) => { const menu = async (userTyped: string, whatsappId: any, contactId: any) => {
await new Promise(r => setTimeout(r, 4000)); let lastId = await findObject(whatsappId, contactId, "ura");
const data: any = await get("ura");
console.log("lastId[0]: ", lastId[0]);
if (!lastId[0]) {
await createObject({
whatsappId,
contactId,
identifier: "ura",
value: data[1].id
});
}
lastId = await findObject(whatsappId, contactId, "ura");
console.log("LAST ID: ", lastId);
let option: any;
if (
lastId &&
lastId.length == 4 &&
lastId[3] &&
lastId[3].trim().length > 0
) {
option = data.find(
(o: any) =>
o.idmaster == lastId[3] &&
o.value.toLowerCase() == userTyped.toLowerCase()
);
// TEST DEL
console.log("OPTION: ", option);
if (!option && userTyped != "0") {
if (!existSubMenu()) {
const response = await mainOptionsMenu(userTyped);
if (response) return response;
else {
console.log("kkkkkkkkkkkkkkkkkkk");
await createObject({
whatsappId,
contactId,
identifier: "ura",
value: data[1].id
});
return data[1];
}
}
}
//
if (option) {
let response: any = data.find((o: any) => o.idmaster == option.id);
console.log(
"RRRRRRRRRRRRRRRRRRRRRRRRRRRRR response: ",
response,
" | option: ",
option
);
await createObject({
whatsappId,
contactId,
identifier: "ura",
value: response.id
});
return response;
} else if (userTyped == "0") {
await createObject({
whatsappId,
contactId,
identifier: "ura",
value: data[1].id
});
return data[1];
} else {
console.log("INVALID SEARCH");
let response = await existSubMenu();
if (response) return response;
return {
value: data.find((o: any) => o.id == lastId[3])?.value
};
// return {
// value: `Você digitou uma opçao inválida!\n\n${
// data.find((o: any) => o.id == lastId[3])?.value
// }\n\nDigite 0 para voltar ao menu `
// };
}
}
function existSubMenu() {
let existSubMenu = data.find((o: any) => o.idmaster == lastId[3]);
if (existSubMenu) true;
return false;
}
async function mainOptionsMenu(userTyped: any) {
let menuOption = data.find(
(o: any) => o.value.toLowerCase() == userTyped.toLowerCase()
);
console.log("============> menuOption OPTION: ", menuOption);
if (menuOption) {
let response = data.find((o: any) => o.idmaster == menuOption.id);
if (response) {
await createObject({
whatsappId,
contactId,
identifier: "ura",
value: response.id
});
return response;
}
}
}
};
const handleMsgAck = async (
msg_id: any,
ack: any,
whatsAppOfficial?: boolean
) => {
if (whatsAppOfficial) {
if (ack == "sent") ack = 1;
else if (ack == "delivered") ack = 2;
else if (ack == "read") ack = 3;
} else await new Promise(r => setTimeout(r, 4000));
const io = getIO(); const io = getIO();
@ -681,6 +1017,11 @@ const handleMsgAck = async (msg_id: any, ack: any) => {
); );
return; return;
} }
console.log("messageToUpdate.ack: ", messageToUpdate.ack, " | ack: ", ack);
if (messageToUpdate.ack > ack) return;
await messageToUpdate.update({ ack }); await messageToUpdate.update({ ack });
io.to(messageToUpdate.ticketId.toString()).emit("appMessage", { io.to(messageToUpdate.ticketId.toString()).emit("appMessage", {
@ -707,9 +1048,9 @@ const wbotMessageListener = (wbot: Session): void => {
}); });
}; };
const outOfService = async () => { const outOfService = async (number: string) => {
// MESSAGE TO HOLIDAY // MESSAGE TO HOLIDAY
const holiday: any = await isHoliday(); const holiday: any = await isHoliday(number);
let objs: any = []; let objs: any = [];
@ -718,14 +1059,14 @@ const outOfService = async () => {
} }
// MESSAGES TO SATURDAY OR SUNDAY // MESSAGES TO SATURDAY OR SUNDAY
const weekend: any = await isWeekend(); const weekend: any = await isWeekend(number);
if (weekend && weekend.set) { if (weekend && weekend.set) {
objs.push({ type: "weekend", msg: weekend.msg }); objs.push({ type: "weekend", msg: weekend.msg });
} }
// MESSAGE TO BUSINESS TIME // MESSAGE TO BUSINESS TIME
const businessTime = await isOutBusinessTime(); const businessTime = await isOutBusinessTime(number);
if (businessTime && businessTime.set) { if (businessTime && businessTime.set) {
objs.push({ type: "businessTime", msg: businessTime.msg }); objs.push({ type: "businessTime", msg: businessTime.msg });
@ -734,4 +1075,17 @@ const outOfService = async () => {
return objs; return objs;
}; };
export { wbotMessageListener, handleMessage, handleMsgAck, lst }; export {
wbotMessageListener,
handleMessage,
handleMsgAck,
lst,
verifyMessage,
verifyMediaMessage,
verifyContact,
isValidMsg,
mediaTypeWhatsappOfficial
};
async function whatsappInfo(whatsappId: string | number) {
return await Whatsapp.findByPk(whatsappId);
}

View File

@ -13,6 +13,9 @@ interface Request {
farewellMessage?: string; farewellMessage?: string;
status?: string; status?: string;
isDefault?: boolean; isDefault?: boolean;
phoneNumberId?: string;
wabaId?: string;
isOfficial?: boolean;
} }
interface Response { interface Response {
@ -29,10 +32,11 @@ const CreateWhatsAppService = async ({
greetingMessage, greetingMessage,
farewellMessage, farewellMessage,
isDefault = false, isDefault = false,
isOfficial = false,
phoneNumberId,
wabaId
}: Request): Promise<Response> => { }: Request): Promise<Response> => {
try { try {
const schema = Yup.object().shape({ const schema = Yup.object().shape({
name: Yup.string() name: Yup.string()
.required() .required()
@ -48,12 +52,11 @@ const CreateWhatsAppService = async ({
return !nameExists; return !nameExists;
} }
), ),
isDefault: Yup.boolean().required(), isDefault: Yup.boolean().required()
urlApi: Yup.string().required()
}); });
try { try {
await schema.validate({ name, status, isDefault, urlApi }); await schema.validate({ name, status, isDefault });
} catch (err: any) { } catch (err: any) {
throw new AppError(err.message); throw new AppError(err.message);
} }
@ -70,7 +73,6 @@ const CreateWhatsAppService = async ({
}); });
if (oldDefaultWhatsapp) { if (oldDefaultWhatsapp) {
await oldDefaultWhatsapp.update({ isDefault: false }); await oldDefaultWhatsapp.update({ isDefault: false });
} }
} }
@ -78,6 +80,12 @@ const CreateWhatsAppService = async ({
throw new AppError("ERR_WAPP_GREETING_REQUIRED"); throw new AppError("ERR_WAPP_GREETING_REQUIRED");
} }
const classification = isOfficial ? "GREEN" : null;
if (isOfficial) {
status = "CONNECTED";
}
const whatsapp = await Whatsapp.create( const whatsapp = await Whatsapp.create(
{ {
name, name,
@ -86,7 +94,11 @@ const CreateWhatsAppService = async ({
urlApi, urlApi,
greetingMessage, greetingMessage,
farewellMessage, farewellMessage,
isDefault isDefault,
phoneNumberId,
wabaId,
isOfficial,
classification
}, },
{ include: ["queues"] } { include: ["queues"] }
); );
@ -94,13 +106,10 @@ const CreateWhatsAppService = async ({
await AssociateWhatsappQueue(whatsapp, queueIds); await AssociateWhatsappQueue(whatsapp, queueIds);
return { whatsapp, oldDefaultWhatsapp }; return { whatsapp, oldDefaultWhatsapp };
} catch (error: any) {
} catch (error: any) { console.error("===> Error on CreateWhatsAppService.ts file: \n", error);
console.error('===> Error on CreateWhatsAppService.ts file: \n', error)
throw new AppError(error.message); throw new AppError(error.message);
} }
}; };
export default CreateWhatsAppService; export default CreateWhatsAppService;

View File

@ -0,0 +1,22 @@
import { Sequelize } from "sequelize";
import Whatsapp from "../../models/Whatsapp";
import WhatsappQueue from "../../models/WhatsappQueue";
const dbConfig = require("../../config/database");
const { QueryTypes } = require("sequelize");
const sequelize = new Sequelize(dbConfig);
const ListWhatsAppsForQueueService = async (queueId: number | string): Promise<any> => {
const distinctWhatsapps = await sequelize.query(
`SELECT w.id, w.number, w.status, wq.whatsappId, wq.queueId
FROM Whatsapps w
JOIN WhatsappQueues wq ON w.id = wq.whatsappId AND wq.queueId = ${queueId}
GROUP BY w.number;`,
{ type: QueryTypes.SELECT }
);
return distinctWhatsapps;
};
export default ListWhatsAppsForQueueService;

View File

@ -12,14 +12,14 @@ const ListWhatsAppsNumber = async (
if (status) { if (status) {
whatsapps = await Whatsapp.findAll({ whatsapps = await Whatsapp.findAll({
raw: true, raw: true,
where: { number: whatsapp.number, status: status }, where: { number: whatsapp.number, status: status, },
attributes: ["id", "number", "status", "isDefault", "url"] attributes: ["id", "number", "status", "isDefault", "url", 'isOfficial']
}); });
} else { } else {
whatsapps = await Whatsapp.findAll({ whatsapps = await Whatsapp.findAll({
raw: true, raw: true,
where: { number: whatsapp.number }, where: { number: whatsapp.number },
attributes: ["id", "number", "status", "isDefault", "url"] attributes: ["id", "number", "status", "isDefault", "url", 'isOfficial']
}); });
} }

View File

@ -2,19 +2,46 @@ import Whatsapp from "../../models/Whatsapp";
import AppError from "../../errors/AppError"; import AppError from "../../errors/AppError";
import Queue from "../../models/Queue"; import Queue from "../../models/Queue";
const ShowWhatsAppService = async (id: string | number): Promise<Whatsapp> => { const ShowWhatsAppService = async (
const whatsapp = await Whatsapp.findByPk(id, { id: string | number | null,
include: [ whatsAppOfficial?: any
{ ): Promise<Whatsapp> => {
model: Queue, let whatsapp;
as: "queues",
attributes: ["id", "name", "color", "greetingMessage"]
}
],
order: [["queues", "id", "ASC"]]
});
// console.log('kkkkkkkkkkkkkkkkkkkk: ', whatsapp) if (id) {
whatsapp = await Whatsapp.findByPk(id, {
include: [
{
model: Queue,
as: "queues",
attributes: [
"id",
"name",
"color",
"greetingMessage",
]
}
],
order: [["queues", "id", "ASC"]]
});
} else {
whatsapp = await Whatsapp.findOne({
where: { number: whatsAppOfficial?.number },
include: [
{
model: Queue,
as: "queues",
attributes: [
"id",
"name",
"color",
"greetingMessage",
]
}
],
order: [["queues", "id", "ASC"]]
});
}
if (!whatsapp) { if (!whatsapp) {
throw new AppError("ERR_NO_WAPP_FOUND", 404); throw new AppError("ERR_NO_WAPP_FOUND", 404);

View File

@ -9,8 +9,6 @@ import AssociateWhatsappQueue from "./AssociateWhatsappQueue";
import { getWbot } from "../../libs/wbot"; import { getWbot } from "../../libs/wbot";
import { restartWhatsSession } from "../../helpers/RestartWhatsSession"; import { restartWhatsSession } from "../../helpers/RestartWhatsSession";
interface WhatsappData { interface WhatsappData {
name?: string; name?: string;
url?: string; url?: string;
@ -18,6 +16,9 @@ interface WhatsappData {
status?: string; status?: string;
session?: string; session?: string;
isDefault?: boolean; isDefault?: boolean;
isOfficial?: boolean;
phoneNumberId?: string;
wabaId?: string;
greetingMessage?: string; greetingMessage?: string;
farewellMessage?: string; farewellMessage?: string;
queueIds?: number[]; queueIds?: number[];
@ -37,19 +38,20 @@ const UpdateWhatsAppService = async ({
whatsappData, whatsappData,
whatsappId whatsappId
}: Request): Promise<Response> => { }: Request): Promise<Response> => {
try { try {
const schema = Yup.object().shape({ const schema = Yup.object().shape({
name: Yup.string().min(2), name: Yup.string().min(2),
status: Yup.string(), status: Yup.string(),
isDefault: Yup.boolean() isDefault: Yup.boolean()
}); });
const { let {
name, name,
status, status,
isDefault, isDefault,
phoneNumberId,
wabaId,
isOfficial,
url, url,
urlApi, urlApi,
session, session,
@ -58,8 +60,6 @@ const UpdateWhatsAppService = async ({
queueIds = [] queueIds = []
} = whatsappData; } = whatsappData;
try { try {
await schema.validate({ name, status, isDefault }); await schema.validate({ name, status, isDefault });
} catch (err: any) { } catch (err: any) {
@ -85,10 +85,23 @@ const UpdateWhatsAppService = async ({
// console.log('############## whatsapp: ', JSON.parse(JSON.stringify(whatsapp))) // console.log('############## whatsapp: ', JSON.parse(JSON.stringify(whatsapp)))
if (name && !name.includes(whatsapp.number) && whatsapp.status === 'CONNECTED') { if (
name &&
!name.includes(whatsapp.number) &&
whatsapp.status === "CONNECTED" &&
!whatsapp.isOfficial
) {
throw new AppError("ERR_WAPP_WRONG_SESSION_NAME"); throw new AppError("ERR_WAPP_WRONG_SESSION_NAME");
}
const classification = isOfficial ? "GREEN" : null;
if (isOfficial) {
status = "CONNECTED";
}
if (whatsapp.isOfficial && !isOfficial) {
status = "OPENING";
} }
await whatsapp.update({ await whatsapp.update({
@ -99,7 +112,11 @@ const UpdateWhatsAppService = async ({
urlApi, urlApi,
greetingMessage, greetingMessage,
farewellMessage, farewellMessage,
isDefault isDefault,
isOfficial,
phoneNumberId,
wabaId,
classification
}); });
// await insertOrUpeateWhatsCache(`whatsapp:${whatsapp.id}`, { // await insertOrUpeateWhatsCache(`whatsapp:${whatsapp.id}`, {
@ -114,12 +131,10 @@ const UpdateWhatsAppService = async ({
await AssociateWhatsappQueue(whatsapp, queueIds); await AssociateWhatsappQueue(whatsapp, queueIds);
return { whatsapp, oldDefaultWhatsapp }; return { whatsapp, oldDefaultWhatsapp };
} catch (error: any) { } catch (error: any) {
console.error('===> Error on UpdateWhatsAppService.ts file: \n', error) console.error("===> Error on UpdateWhatsAppService.ts file: \n", error);
throw new AppError(error.message); throw new AppError(error.message);
} }
}; };
export default UpdateWhatsAppService; export default UpdateWhatsAppService;

View File

@ -1 +0,0 @@
REACT_APP_BACKEND_URL = http://localhost:8080/

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, } from 'react' import React, { useState, useEffect, useContext } from 'react'
// import * as Yup from 'yup' // import * as Yup from 'yup'
import { Formik, Form, Field, } from 'formik' import { Formik, Form, Field, } from 'formik'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
@ -12,7 +12,7 @@ import DateFnsUtils from '@date-io/date-fns'
import ptBrLocale from "date-fns/locale/pt-BR" import ptBrLocale from "date-fns/locale/pt-BR"
import { WhatsAppsContext } from "../../context/WhatsApp/WhatsAppsContext"
import { import {
MuiPickersUtilsProvider, MuiPickersUtilsProvider,
@ -28,12 +28,16 @@ import {
TextField, TextField,
Switch, Switch,
FormControlLabel, FormControlLabel,
Divider,
} from '@material-ui/core' } from '@material-ui/core'
import api from '../../services/api' import api from '../../services/api'
import { i18n } from '../../translate/i18n' import { i18n } from '../../translate/i18n'
import toastError from '../../errors/toastError' import toastError from '../../errors/toastError'
import Select from "@material-ui/core/Select"
import MenuItem from "@material-ui/core/MenuItem"
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
root: { root: {
display: 'flex', display: 'flex',
@ -87,13 +91,35 @@ const ConfigModal = ({ open, onClose, change }) => {
enableWeekendMessage: false enableWeekendMessage: false
} }
const { whatsApps } = useContext(WhatsAppsContext)
const [selectedNumber, setSelectedNumber] = useState('')
const [config, setConfig] = useState(initialState) const [config, setConfig] = useState(initialState)
useEffect(() => {
console.log('selectedNumber: ', selectedNumber)
if (selectedNumber?.trim().length === 0) {
setConfig(initialState)
}
}, [selectedNumber])
useEffect(() => { useEffect(() => {
const fetchSession = async () => { const fetchSession = async () => {
try { try {
const { data } = await api.get('/settings') // const { data } = await api.get('/settings')
if (!selectedNumber) return
const { data } = await api.get(`/settings/ticket/${selectedNumber}`)
if (data?.config && data.config.length === 0) {
setConfig(initialState)
return
}
const outBusinessHours = data.config.find((c) => c.key === "outBusinessHours") const outBusinessHours = data.config.find((c) => c.key === "outBusinessHours")
const ticketExpiration = data.config.find((c) => c.key === "ticketExpiration") const ticketExpiration = data.config.find((c) => c.key === "ticketExpiration")
@ -127,11 +153,12 @@ const ConfigModal = ({ open, onClose, change }) => {
} }
} }
fetchSession() fetchSession()
}, [change]) }, [change, selectedNumber])
const handleSaveConfig = async (values) => { const handleSaveConfig = async (values) => {
values = { values = {
number: selectedNumber,
outBusinessHours: { outBusinessHours: {
startTime: values.startTimeBus, startTime: values.startTimeBus,
endTime: values.endTimeBus, endTime: values.endTimeBus,
@ -147,7 +174,7 @@ const ConfigModal = ({ open, onClose, change }) => {
message: values.weekendMessage, message: values.weekendMessage,
value: values.enableWeekendMessage ? 'enabled' : 'disabled' value: values.enableWeekendMessage ? 'enabled' : 'disabled'
}, },
saturday:{ saturday: {
value: values.checkboxSaturdayValue ? 'enabled' : 'disabled' value: values.checkboxSaturdayValue ? 'enabled' : 'disabled'
}, },
sunday: { sunday: {
@ -211,6 +238,39 @@ const ConfigModal = ({ open, onClose, change }) => {
<DialogContent dividers> <DialogContent dividers>
<div>
<Select
value={selectedNumber}
onChange={(e) => setSelectedNumber(e.target.value)}
label={i18n.t("transferTicketModal.fieldQueuePlaceholder")}
required
>
<MenuItem style={{ background: "white", }} value={''}>&nbsp;</MenuItem>
{whatsApps.reduce((acc, curr) => {
const existingObject = acc.find(item => item.number === curr.number)
if (!existingObject) {
acc.push(curr)
}
return acc
}, []).map((whatsapp) => (
<MenuItem
key={whatsapp.id}
value={whatsapp.number}
>
{whatsapp.number}
</MenuItem>
))}
</Select>
</div>
<Divider />
<br />
<div className={classes.multFieldLine}> <div className={classes.multFieldLine}>
<Field <Field
component={TimePicker} component={TimePicker}

View File

@ -1,27 +1,26 @@
import React, { useState, useEffect, useContext, useRef, useCallback } from "react"; import React, { useState, useEffect, useContext, useRef, useCallback } from "react"
import { useHistory } from "react-router-dom"; import { useHistory } from "react-router-dom"
import { toast } from "react-toastify"; import { toast } from "react-toastify"
import Button from "@material-ui/core/Button"; import Button from "@material-ui/core/Button"
import Dialog from "@material-ui/core/Dialog"; import Dialog from "@material-ui/core/Dialog"
import Select from "@material-ui/core/Select"; import Select from "@material-ui/core/Select"
import FormControl from "@material-ui/core/FormControl"; import FormControl from "@material-ui/core/FormControl"
import InputLabel from "@material-ui/core/InputLabel"; import InputLabel from "@material-ui/core/InputLabel"
import MenuItem from "@material-ui/core/MenuItem"; import MenuItem from "@material-ui/core/MenuItem"
import LinearProgress from "@material-ui/core/LinearProgress"; import { makeStyles } from "@material-ui/core"
import { makeStyles } from "@material-ui/core";
import DialogActions from "@material-ui/core/DialogActions"; import DialogActions from "@material-ui/core/DialogActions"
import DialogContent from "@material-ui/core/DialogContent"; import DialogContent from "@material-ui/core/DialogContent"
import DialogTitle from "@material-ui/core/DialogTitle"; import DialogTitle from "@material-ui/core/DialogTitle"
import { i18n } from "../../translate/i18n"; import { i18n } from "../../translate/i18n"
import ButtonWithSpinner from "../ButtonWithSpinner"; import ButtonWithSpinner from "../ButtonWithSpinner"
import { AuthContext } from "../../context/Auth/AuthContext"; import { AuthContext } from "../../context/Auth/AuthContext"
import toastError from "../../errors/toastError"; import toastError from "../../errors/toastError"
import api from "../../services/api"; import api from "../../services/api"
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
maxWidth: { maxWidth: {
@ -33,30 +32,59 @@ const useStyles = makeStyles((theme) => ({
linearProgress: { linearProgress: {
marginTop: "5px" marginTop: "5px"
} }
})); }))
const ContactCreateTicketModal = ({ modalOpen, onClose, contactId }) => { const ContactCreateTicketModal = ({ modalOpen, onClose, contactId }) => {
const { user } = useContext(AuthContext); const { user } = useContext(AuthContext)
let isMounted = useRef(true) let isMounted = useRef(true)
const history = useHistory(); const history = useHistory()
const [queues, setQueues] = useState([]); const [queues, setQueues] = useState([])
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false)
const [selectedQueue, setSelectedQueue] = useState(''); const [selectedQueue, setSelectedQueue] = useState('')
const [itemHover, setItemHover] = useState(-1) const [itemHover, setItemHover] = useState(-1)
const classes = useStyles();
const [selectedWhatsId, setSelectedWhatsId] = useState()
const [whatsQueue, setWhatsQueue] = useState()
const [disabled, setDisabled] = useState(false)
const classes = useStyles()
useEffect(() => { useEffect(() => {
const userQueues = user.queues.map(({ id, name, color }) => { return { id, name, color } })
if (userQueues.length === 1) setSelectedQueue(userQueues[0].id) const delayDebounceFn = setTimeout(() => {
setQueues(userQueues)
}, [user]); const fetchMatchQueueUserOfficialWhatsapp = async () => {
try {
const { data } = await api.get("/whatsapp/official/matchQueueUser", { params: { userId: user.id, queueId: selectedQueue }, })
setQueues(data)
} catch (err) {
console.log(err)
}
}
fetchMatchQueueUserOfficialWhatsapp()
}, 500)
return () => clearTimeout(delayDebounceFn)
}, [user, selectedQueue])
const handleClose = () => { const handleClose = () => {
onClose(); onClose()
}; }
const handleSaveTicket = useCallback(async (contactId, userId, queueId, selectedWhatsId, whatsQueue, queues) => {
if (queues && queues.length === 1 && queues[0].disable) {
toast.warn('Não é possível criar um Ticket porque a fila a qual você esta adicionado(a) não está associado a nenhum numero!')
return
}
const handleSaveTicket = useCallback(async (contactId, userId, queueId) => {
if (!contactId || !userId) { if (!contactId || !userId) {
console.log("Missing contactId or userId") console.log("Missing contactId or userId")
return return
@ -65,8 +93,12 @@ const ContactCreateTicketModal = ({ modalOpen, onClose, contactId }) => {
toast.warning("Nenhuma Fila Selecionada") toast.warning("Nenhuma Fila Selecionada")
return return
} }
if (isMounted.current) setLoading(true); if (isMounted.current) setLoading(true)
if (whatsQueue && !selectedWhatsId) {
toast.warn('Selecione um numero!')
return
}
const delayDebounceFn = setTimeout(() => { const delayDebounceFn = setTimeout(() => {
const ticketCreate = async () => { const ticketCreate = async () => {
@ -76,32 +108,77 @@ const ContactCreateTicketModal = ({ modalOpen, onClose, contactId }) => {
userId: userId, userId: userId,
queueId: queueId, queueId: queueId,
status: "open", status: "open",
}); whatsappId: selectedWhatsId
history.push(`/tickets/${ticket.id}`); })
} catch (err) { history.push(`/tickets/${ticket.id}`)
toastError(err);
}
if (isMounted.current) setLoading(false);
}; setWhatsQueue(null)
ticketCreate(); setSelectedWhatsId(null)
}, 300); } catch (err) {
return () => clearTimeout(delayDebounceFn); toastError(err)
}
if (isMounted.current) setLoading(false)
}
ticketCreate()
}, 300)
return () => clearTimeout(delayDebounceFn)
}, [history]) }, [history])
useEffect(() => { useEffect(() => {
if (modalOpen && queues.length <= 1) {
handleSaveTicket(contactId, user.id, selectedQueue)
}
return () => {
isMounted.current = false;
};
}, [modalOpen, contactId, user.id, selectedQueue, handleSaveTicket, queues.length]);
if (modalOpen && queues.length <= 1) { if (selectedQueue && (selectedQueue.length === 0 || !selectedQueue)) {
return <LinearProgress /> setDisabled(true)
} setWhatsQueue(null)
}
if (!Array.isArray(whatsQueue)) {
setSelectedWhatsId(null)
setWhatsQueue(null)
}
return () => {
isMounted.current = false
}
}, [modalOpen, contactId, user.id, selectedQueue, handleSaveTicket, queues.length, selectedWhatsId, whatsQueue, queues])
useEffect(() => {
if (!selectedQueue) return
setDisabled(true)
const delayDebounceFn = setTimeout(() => {
const fetChMatchQueueOfficialWhatsapp = async () => {
try {
const { data } = await api.get("/whatsapp/official/matchQueue", { params: { userId: user.id, queueId: selectedQueue }, })
console.log('WHATSAPP DATA: ', data)
setWhatsQueue(data)
setDisabled(false)
} catch (err) {
console.log(err)
}
}
fetChMatchQueueOfficialWhatsapp()
}, 500)
return () => clearTimeout(delayDebounceFn)
}, [selectedQueue, user.id])
useEffect(() => {
console.log('selectedWhatsId: ', selectedWhatsId)
console.log('whatsQuee: ', whatsQueue)
}, [whatsQueue, selectedWhatsId])
return ( return (
<Dialog open={modalOpen} onClose={handleClose} maxWidth="xs" scroll="paper" classes={{ paper: classes.paper }}> <Dialog open={modalOpen} onClose={handleClose} maxWidth="xs" scroll="paper" classes={{ paper: classes.paper }}>
@ -117,9 +194,10 @@ const ContactCreateTicketModal = ({ modalOpen, onClose, contactId }) => {
label={i18n.t("Filas")} label={i18n.t("Filas")}
> >
<MenuItem value={''}>&nbsp;</MenuItem> <MenuItem value={''}>&nbsp;</MenuItem>
{queues.map(({ id, color, name }) => ( {queues.map(({ id, color, name, disable }) => (
<MenuItem <MenuItem
key={id} key={id}
disabled={disable}
value={id} value={id}
onMouseEnter={() => setItemHover(id)} onMouseEnter={() => setItemHover(id)}
onMouseLeave={() => setItemHover(-1)} onMouseLeave={() => setItemHover(-1)}
@ -131,6 +209,31 @@ const ContactCreateTicketModal = ({ modalOpen, onClose, contactId }) => {
</Select> </Select>
</FormControl> </FormControl>
</DialogContent> </DialogContent>
{whatsQueue && Array.isArray(whatsQueue) ?
<DialogContent dividers>
<FormControl variant="outlined" className={classes.maxWidth}>
<InputLabel>{i18n.t("Selecionar Numero")}</InputLabel>
<Select
value={selectedWhatsId}
onChange={(e) => setSelectedWhatsId(e.target.value)}
label={i18n.t("Numeros")}
>
<MenuItem value={''}>&nbsp;</MenuItem>
{whatsQueue.map(({ id, number, phoneNumberId }) => (
<MenuItem
key={id}
value={id}
>{phoneNumberId ? `${number} OFICIAL` : `${number}`}</MenuItem>
))}
</Select>
</FormControl>
</DialogContent>
: <></>
}
<DialogActions> <DialogActions>
<Button <Button
onClick={handleClose} onClick={handleClose}
@ -141,16 +244,17 @@ const ContactCreateTicketModal = ({ modalOpen, onClose, contactId }) => {
{i18n.t("newTicketModal.buttons.cancel")} {i18n.t("newTicketModal.buttons.cancel")}
</Button> </Button>
<ButtonWithSpinner <ButtonWithSpinner
onClick={() => handleSaveTicket(contactId, user.id, selectedQueue)} onClick={() => handleSaveTicket(contactId, user.id, selectedQueue, selectedWhatsId, whatsQueue, queues)}
variant="contained" variant="contained"
color="primary" color="primary"
loading={loading} loading={loading}
disabled={disabled}
> >
{i18n.t("newTicketModal.buttons.ok")} {i18n.t("newTicketModal.buttons.ok")}
</ButtonWithSpinner> </ButtonWithSpinner>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
); )
}; }
export default ContactCreateTicketModal; export default ContactCreateTicketModal

View File

@ -1,47 +1,50 @@
import React, { useState, useEffect, useContext, useRef } from "react"; import React, { useState, useEffect, useContext, useRef } from "react"
import "emoji-mart/css/emoji-mart.css"; import "emoji-mart/css/emoji-mart.css"
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom"
import { Picker } from "emoji-mart"; import { Picker } from "emoji-mart"
import MicRecorder from "mic-recorder-to-mp3"; import MicRecorder from "mic-recorder-to-mp3"
import clsx from "clsx"; import clsx from "clsx"
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles"
import Paper from "@material-ui/core/Paper"; import Paper from "@material-ui/core/Paper"
import InputBase from "@material-ui/core/InputBase"; import InputBase from "@material-ui/core/InputBase"
import CircularProgress from "@material-ui/core/CircularProgress"; import CircularProgress from "@material-ui/core/CircularProgress"
import { green } from "@material-ui/core/colors"; import { green } from "@material-ui/core/colors"
import AttachFileIcon from "@material-ui/icons/AttachFile"; import AttachFileIcon from "@material-ui/icons/AttachFile"
import IconButton from "@material-ui/core/IconButton"; import IconButton from "@material-ui/core/IconButton"
import MoreVert from "@material-ui/icons/MoreVert"; import MoreVert from "@material-ui/icons/MoreVert"
import MoodIcon from "@material-ui/icons/Mood"; import MoodIcon from "@material-ui/icons/Mood"
import SendIcon from "@material-ui/icons/Send"; import SendIcon from "@material-ui/icons/Send"
import CancelIcon from "@material-ui/icons/Cancel"; import CancelIcon from "@material-ui/icons/Cancel"
import ClearIcon from "@material-ui/icons/Clear"; import ClearIcon from "@material-ui/icons/Clear"
import MicIcon from "@material-ui/icons/Mic"; import MicIcon from "@material-ui/icons/Mic"
import CheckCircleOutlineIcon from "@material-ui/icons/CheckCircleOutline"; import CheckCircleOutlineIcon from "@material-ui/icons/CheckCircleOutline"
import HighlightOffIcon from "@material-ui/icons/HighlightOff"; import HighlightOffIcon from "@material-ui/icons/HighlightOff"
import { import {
FormControlLabel, FormControlLabel,
Hidden, Hidden,
Menu, Menu,
MenuItem, MenuItem,
Switch, Switch,
} from "@material-ui/core"; } from "@material-ui/core"
import ClickAwayListener from "@material-ui/core/ClickAwayListener"; import ClickAwayListener from "@material-ui/core/ClickAwayListener"
import { i18n } from "../../translate/i18n"; import { i18n } from "../../translate/i18n"
import api from "../../services/api"; import api from "../../services/api"
import RecordingTimer from "./RecordingTimer"; import RecordingTimer from "./RecordingTimer"
import { ReplyMessageContext } from "../../context/ReplyingMessage/ReplyingMessageContext"; import { ReplyMessageContext } from "../../context/ReplyingMessage/ReplyingMessageContext"
import { AuthContext } from "../../context/Auth/AuthContext"; import { AuthContext } from "../../context/Auth/AuthContext"
import { useLocalStorage } from "../../hooks/useLocalStorage"; import { useLocalStorage } from "../../hooks/useLocalStorage"
import toastError from "../../errors/toastError"; import toastError from "../../errors/toastError"
// import TicketsManager from "../../components/TicketsManager/"; // import TicketsManager from "../../components/TicketsManager/";
import { TabTicketContext } from "../../context/TabTicketHeaderOption/TabTicketHeaderOption"; import { TabTicketContext } from "../../context/TabTicketHeaderOption/TabTicketHeaderOption"
import ModalTemplate from "../ModalTemplate"
const Mp3Recorder = new MicRecorder({ bitRate: 128 }); import { render } from '@testing-library/react'
const Mp3Recorder = new MicRecorder({ bitRate: 128 })
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
mainWrapper: { mainWrapper: {
@ -203,78 +206,83 @@ const useStyles = makeStyles((theme) => ({
}, },
}, },
}, },
})); }))
const MessageInput = ({ ticketStatus }) => { const MessageInput = ({ ticketStatus }) => {
const { tabOption, setTabOption } = useContext(TabTicketContext); const { tabOption, setTabOption } = useContext(TabTicketContext)
const classes = useStyles(); const classes = useStyles()
const { ticketId } = useParams(); const { ticketId } = useParams()
const [medias, setMedias] = useState([]); const [medias, setMedias] = useState([])
const [inputMessage, setInputMessage] = useState(""); const [inputMessage, setInputMessage] = useState("")
const [showEmoji, setShowEmoji] = useState(false); const [showEmoji, setShowEmoji] = useState(false)
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false)
const [recording, setRecording] = useState(false); const [recording, setRecording] = useState(false)
const [quickAnswers, setQuickAnswer] = useState([]); const [quickAnswers, setQuickAnswer] = useState([])
const [typeBar, setTypeBar] = useState(false); const [typeBar, setTypeBar] = useState(false)
const inputRef = useRef(); const inputRef = useRef()
const [anchorEl, setAnchorEl] = useState(null); const [anchorEl, setAnchorEl] = useState(null)
const { setReplyingMessage, replyingMessage } = useContext(ReplyMessageContext); const { setReplyingMessage, replyingMessage } = useContext(ReplyMessageContext)
const { user } = useContext(AuthContext); const { user } = useContext(AuthContext)
const [templates, setTemplates] = useState(null)
const [params, setParams] = useState(null)
const [signMessage, setSignMessage] = useLocalStorage("signOption", true)
const isRun = useRef(false)
const [signMessage, setSignMessage] = useLocalStorage("signOption", true);
useEffect(() => { useEffect(() => {
inputRef.current.focus(); inputRef.current.focus()
}, [replyingMessage]); }, [replyingMessage])
useEffect(() => { useEffect(() => {
inputRef.current.focus(); inputRef.current.focus()
return () => { return () => {
setInputMessage(""); setInputMessage("")
setShowEmoji(false); setShowEmoji(false)
setMedias([]); setMedias([])
setReplyingMessage(null); setReplyingMessage(null)
}; }
}, [ticketId, setReplyingMessage]); }, [ticketId, setReplyingMessage])
const handleChangeInput = (e) => { const handleChangeInput = (e) => {
setInputMessage(e.target.value); setInputMessage(e.target.value)
handleLoadQuickAnswer(e.target.value); handleLoadQuickAnswer(e.target.value)
}; }
const handleQuickAnswersClick = (value) => { const handleQuickAnswersClick = (value) => {
setInputMessage(value); setInputMessage(value)
setTypeBar(false); setTypeBar(false)
}; }
const handleAddEmoji = (e) => { const handleAddEmoji = (e) => {
let emoji = e.native; let emoji = e.native
setInputMessage((prevState) => prevState + emoji); setInputMessage((prevState) => prevState + emoji)
}; }
const handleChangeMedias = (e) => { const handleChangeMedias = (e) => {
if (!e.target.files) { if (!e.target.files) {
return; return
} }
const selectedMedias = Array.from(e.target.files); const selectedMedias = Array.from(e.target.files)
setMedias(selectedMedias); setMedias(selectedMedias)
}; }
const handleInputPaste = (e) => { const handleInputPaste = (e) => {
if (e.clipboardData.files[0]) { if (e.clipboardData.files[0]) {
console.log('clipboardData: ', e.clipboardData.files[0]) console.log('clipboardData: ', e.clipboardData.files[0])
setMedias([e.clipboardData.files[0]]); setMedias([e.clipboardData.files[0]])
} }
}; }
const handleUploadMedia = async (e) => { const handleUploadMedia = async (e) => {
setLoading(true); setLoading(true)
e.preventDefault(); e.preventDefault()
@ -282,92 +290,171 @@ const MessageInput = ({ ticketStatus }) => {
setTabOption('open') setTabOption('open')
} }
const formData = new FormData(); const formData = new FormData()
formData.append("fromMe", true); formData.append("fromMe", true)
medias.forEach((media) => { medias.forEach((media) => {
formData.append("medias", media); formData.append("medias", media)
formData.append("body", media.name); formData.append("body", media.name)
}); })
try { try {
await api.post(`/messages/${ticketId}`, formData); const { data } = await api.post(`/messages/${ticketId}`, formData)
console.log('DATA FROM SEND MESSAGE MEDIA: ', data)
} catch (err) { } catch (err) {
toastError(err); toastError(err)
} }
setLoading(false); setLoading(false)
setMedias([]); setMedias([])
}; }
const handleSendMessage = async () => { const handleSendMessage = async (templateParams = null) => {
if (inputMessage.trim() === "") return;
setLoading(true);
console.log('templateParams: ', templateParams, ' | inputMessage: ', inputMessage)
if (inputMessage.trim() === "") return
setLoading(true)
if (tabOption === 'search') { if (tabOption === 'search') {
setTabOption('open') setTabOption('open')
} }
const message = { if (templateParams) {
for (let key in templateParams) {
if (templateParams.hasOwnProperty(key)) {
// let value = templateParams[key]
// console.log('key: ', key, ' | ', 'VALUE: ', value)
if (key === '_reactName') {
templateParams = null
break
}
}
}
}
let message = {
read: 1, read: 1,
fromMe: true, fromMe: true,
mediaUrl: "", mediaUrl: "",
body: signMessage body: (signMessage && !templateParams) ? `*${user?.name}:*\n${inputMessage.trim()}` : inputMessage.trim(),
? `*${user?.name}:*\n${inputMessage.trim()}` quotedMsg: replyingMessage
: inputMessage.trim(),
quotedMsg: replyingMessage,
};
try {
// console.log('message: ', message)
await api.post(`/messages/${ticketId}`, message);
} catch (err) {
toastError(err);
} }
setInputMessage(""); if (templateParams) {
setShowEmoji(false); message = { ...message, params: templateParams }
setLoading(false); }
setReplyingMessage(null);
};
try {
const { data } = await api.post(`/messages/${ticketId}`, message)
setParams(null)
if (data && data?.data && Array.isArray(data.data)) {
setTemplates(data.data)
}
} catch (err) {
toastError(err)
}
setInputMessage("")
setShowEmoji(false)
setLoading(false)
setReplyingMessage(null)
}
useEffect(() => {
if (!params) return
const body_params = params.find(p => p?.type === 'BODY')
let { text } = body_params
console.log('PARAMS FROM MESSAGE INPUT: ', params, ' | text: ', text)
let body = text.match(/{{\d+}}/g)
if (body && body.length > 0) {
const { parameters } = body_params
for (const key in parameters) {
if (!isNaN(key)) {
const { index, text: body_text } = parameters[key]
text = text.replace(`{{${index}}}`, body_text)
}
}
}
console.log('NEW TEXT: ', text)
setInputMessage(text)
}, [params])
useEffect(() => {
if (params) {
handleSendMessage(params)
}
}, [inputMessage, params])
useEffect(() => {
if (!templates) return
return render(<ModalTemplate
modal_header={'Escolha um template para iniciar o Atendimento'}
func={setParams}
templates={templates.map(({ id, name, components, language, }) => {
return { id, name, components, language, }
})}
ticketId={ticketId}
/>)
}, [templates])
const handleStartRecording = async () => { const handleStartRecording = async () => {
setLoading(true); setLoading(true)
try { try {
await navigator.mediaDevices.getUserMedia({ audio: true }); await navigator.mediaDevices.getUserMedia({ audio: true })
await Mp3Recorder.start(); await Mp3Recorder.start()
setRecording(true); setRecording(true)
setLoading(false); setLoading(false)
} catch (err) { } catch (err) {
toastError(err); toastError(err)
setLoading(false); setLoading(false)
} }
}; }
const handleLoadQuickAnswer = async (value) => { const handleLoadQuickAnswer = async (value) => {
if (value && value.indexOf("/") === 0) { if (value && value.indexOf("/") === 0) {
try { try {
const { data } = await api.get("/quickAnswers/", { const { data } = await api.get("/quickAnswers/", {
params: { searchParam: inputMessage.substring(1) }, params: { searchParam: inputMessage.substring(1) },
}); })
setQuickAnswer(data.quickAnswers); setQuickAnswer(data.quickAnswers)
if (data.quickAnswers.length > 0) { if (data.quickAnswers.length > 0) {
setTypeBar(true); setTypeBar(true)
} else { } else {
setTypeBar(false); setTypeBar(false)
} }
} catch (err) { } catch (err) {
setTypeBar(false); setTypeBar(false)
} }
} else { } else {
setTypeBar(false); setTypeBar(false)
} }
}; }
const handleUploadAudio = async () => { const handleUploadAudio = async () => {
setLoading(true); setLoading(true)
@ -376,44 +463,45 @@ const MessageInput = ({ ticketStatus }) => {
} }
try { try {
const [, blob] = await Mp3Recorder.stop().getMp3(); const [, blob] = await Mp3Recorder.stop().getMp3()
if (blob.size < 10000) { if (blob.size < 10000) {
setLoading(false); setLoading(false)
setRecording(false); setRecording(false)
return; return
} }
const formData = new FormData(); const formData = new FormData()
const filename = `${new Date().getTime()}.mp3`; const filename = `${new Date().getTime()}.mp3`
formData.append("medias", blob, filename); formData.append("medias", blob, filename)
formData.append("body", filename); formData.append("body", filename)
formData.append("fromMe", true); formData.append("fromMe", true)
formData.append("mic_audio", true)
await api.post(`/messages/${ticketId}`, formData); await api.post(`/messages/${ticketId}`, formData)
} catch (err) { } catch (err) {
toastError(err); toastError(err)
} }
setRecording(false); setRecording(false)
setLoading(false); setLoading(false)
}; }
const handleCancelAudio = async () => { const handleCancelAudio = async () => {
try { try {
await Mp3Recorder.stop().getMp3(); await Mp3Recorder.stop().getMp3()
setRecording(false); setRecording(false)
} catch (err) { } catch (err) {
toastError(err); toastError(err)
} }
}; }
const handleOpenMenuClick = (event) => { const handleOpenMenuClick = (event) => {
setAnchorEl(event.currentTarget); setAnchorEl(event.currentTarget)
}; }
const handleMenuItemClick = (event) => { const handleMenuItemClick = (event) => {
setAnchorEl(null); setAnchorEl(null)
}; }
const renderReplyingMessage = (message) => { const renderReplyingMessage = (message) => {
return ( return (
@ -442,8 +530,8 @@ const MessageInput = ({ ticketStatus }) => {
<ClearIcon className={classes.sendMessageIcons} /> <ClearIcon className={classes.sendMessageIcons} />
</IconButton> </IconButton>
</div> </div>
); )
}; }
if (medias.length > 0) if (medias.length > 0)
return ( return (
@ -475,7 +563,7 @@ const MessageInput = ({ ticketStatus }) => {
<SendIcon className={classes.sendMessageIcons} /> <SendIcon className={classes.sendMessageIcons} />
</IconButton> </IconButton>
</Paper> </Paper>
); )
else { else {
return ( return (
<Paper square elevation={0} className={classes.mainWrapper}> <Paper square elevation={0} className={classes.mainWrapper}>
@ -529,7 +617,7 @@ const MessageInput = ({ ticketStatus }) => {
size="small" size="small"
checked={signMessage} checked={signMessage}
onChange={(e) => { onChange={(e) => {
setSignMessage(e.target.checked); setSignMessage(e.target.checked)
}} }}
name="showAllTickets" name="showAllTickets"
color="primary" color="primary"
@ -591,7 +679,7 @@ const MessageInput = ({ ticketStatus }) => {
size="small" size="small"
checked={signMessage} checked={signMessage}
onChange={(e) => { onChange={(e) => {
setSignMessage(e.target.checked); setSignMessage(e.target.checked)
}} }}
name="showAllTickets" name="showAllTickets"
color="primary" color="primary"
@ -604,8 +692,8 @@ const MessageInput = ({ ticketStatus }) => {
<div className={classes.messageInputWrapper}> <div className={classes.messageInputWrapper}>
<InputBase <InputBase
inputRef={(input) => { inputRef={(input) => {
input && input.focus(); input && input.focus()
input && (inputRef.current = input); input && (inputRef.current = input)
}} }}
className={classes.messageInput} className={classes.messageInput}
placeholder={ placeholder={
@ -620,12 +708,12 @@ const MessageInput = ({ ticketStatus }) => {
onChange={handleChangeInput} onChange={handleChangeInput}
disabled={recording || loading || ticketStatus !== "open"} disabled={recording || loading || ticketStatus !== "open"}
onPaste={(e) => { onPaste={(e) => {
ticketStatus === "open" && handleInputPaste(e); ticketStatus === "open" && handleInputPaste(e)
}} }}
onKeyPress={(e) => { onKeyPress={(e) => {
if (loading || e.shiftKey) return; if (loading || e.shiftKey) return
else if (e.key === "Enter") { else if (e.key === "Enter") {
handleSendMessage(); handleSendMessage()
} }
}} }}
/> />
@ -642,7 +730,7 @@ const MessageInput = ({ ticketStatus }) => {
{`${value.shortcut} - ${value.message}`} {`${value.shortcut} - ${value.message}`}
</a> </a>
</li> </li>
); )
})} })}
</ul> </ul>
) : ( ) : (
@ -698,8 +786,8 @@ const MessageInput = ({ ticketStatus }) => {
)} )}
</div> </div>
</Paper> </Paper>
); )
} }
}; }
export default MessageInput; export default MessageInput

View File

@ -56,7 +56,7 @@ const MessageOptionsMenu = ({ message, menuOpen, handleClose, anchorEl }) => {
onClose={handleClose} onClose={handleClose}
> >
{message.fromMe && ( {message.fromMe && (
<MenuItem onClick={handleOpenConfirmationModal}> <MenuItem onClick={handleOpenConfirmationModal} disabled={message?.phoneNumberId ? true : false}>
{i18n.t("messageOptionsMenu.delete")} {i18n.t("messageOptionsMenu.delete")}
</MenuItem> </MenuItem>
)} )}

View File

@ -0,0 +1,242 @@
import React, { useState, useEffect, useRef, } from 'react'
import Button from '@mui/material/Button'
import Dialog from '@mui/material/Dialog'
import DialogActions from '@mui/material/DialogActions'
import DialogContent from '@mui/material/DialogContent'
import DialogContentText from '@mui/material/DialogContentText'
import DialogTitle from '@mui/material/DialogTitle'
import SelectField from "../Report/SelectField"
import TextField from '@mui/material/TextField'
const ModalTemplate = ({ templates, modal_header, func }) => {
templates = [{}, ...templates]
const [open, setOpen] = useState(true)
const [scroll, /*setScroll*/] = useState('body')
const [templateId, setTemplateId] = useState(null)
const [templateComponents, setTemplateComponents] = useState(null)
const [language, setLanguage] = useState(null)
const [params, setParams] = useState([])
const handleCancel = (event, reason) => {
if (reason && reason === "backdropClick")
return
setOpen(false)
}
const handleChatEnd = () => {
console.log('PARAMS TO SEND TO MESSAGE INPUT: ', params)
func(params)
setOpen(false)
}
const descriptionElementRef = useRef(null)
useEffect(() => {
if (open) {
const { current: descriptionElement } = descriptionElementRef
if (descriptionElement !== null) {
descriptionElement.focus()
}
}
}, [open])
// Get from child 1
const changedTextFieldSelect = (data) => {
setTemplateId(data)
}
useEffect(() => {
const index = templates.findIndex(t => t.id === templateId)
setParams([])
if (index !== -1) {
const { components, language, name } = templates[index]
setParams((params) => [...params, { 'template_name': name }])
const buttons = components.find(c => c.type === 'BUTTONS')
if (buttons) {
handleButtons(buttons?.buttons)
}
setTemplateComponents(components)
setLanguage(language)
}
else {
setTemplateComponents(null)
setLanguage(null)
}
}, [templateId])
const handleButtons = (buttons) => {
let buttonsParams = {
type: 'BUTTONS',
parameters: []
}
if (buttons && buttons.length > 0) {
for (let i in buttons) {
const { text, type: sub_type } = buttons[i]
buttonsParams.parameters.push({ sub_type, text, index: i, })
}
}
setParams((params) => [...params, buttonsParams])
}
const handleTextChange = (value, index, type, text, language,) => {
if (!params) return
setParams((params) => {
const _index = params.findIndex(({ type }) => type === 'BODY')
if (_index !== -1) {
const indexParameter = params[_index].parameters.findIndex((param) => param.index === index)
if (indexParameter !== -1) {
params[_index].parameters[indexParameter] = { ...params[_index].parameters[indexParameter], type: 'text', text: value, index }
}
else {
params[_index].parameters = [...params[_index].parameters, { type: 'text', text: value, index }]
}
return params
}
return [...params, { type, text, language, parameters: [{ type: 'text', text: value, index }] }]
})
}
useEffect(() => {
console.log('---------> PARAMS: ', params)
}, [params])
const dinamicTextField = (replicateItems, func, type, text, language) => {
let textFields = Array.from({ length: replicateItems }, (_, index) => index)
return textFields.map((t) => {
return <TextField
key={t}
label={`{{${t + 1}}}`}
variant="outlined"
style={{ margin: '4px', }} // Adjust the height as needed
onChange={(e) => func(e.target.value, (t + 1), type, text, language)}
/>
})
}
return (
<Dialog
open={open}
onClose={handleCancel}
// fullWidth={true}
// maxWidth={true}
disableEscapeKeyDown
scroll={scroll}
aria-labelledby="scroll-dialog-title"
aria-describedby="scroll-dialog-description"
>
<DialogTitle id="scroll-dialog-title">{modal_header}</DialogTitle>
<DialogContent dividers={scroll === 'body'}>
<DialogContentText
id="scroll-dialog-description"
ref={descriptionElementRef}
tabIndex={-1}
>
</DialogContentText>
<>
<SelectField func={changedTextFieldSelect}
emptyField={false}
textBoxFieldSelected={'1'}
header={'Selecione um template'}
currencies={templates.map((template,) => {
const { name, id } = template
return { 'value': id, 'label': name }
})} />
{templateComponents &&
templateComponents.map((components,) => {
const { type, format, text, buttons } = components
let body_params = 0
if (type === 'BODY') {
body_params = text.match(/{{\d+}}/g)
}
const titleCss = {
margin: 0,
fontSize: 12
}
const valueCss = { margin: 0, padding: 0, 'marginBottom': '15px', fontSize: 12 }
const valueCssButton = { margin: 0, padding: 0, fontSize: 11 }
return <div key={text}>
{type && <strong style={titleCss}>{type}</strong>}
{format && format !== 'TEXT' && <p style={valueCss}>TYPE {format}</p>}
{text &&
<div style={{ margin: 0, padding: 0, 'marginBottom': '15px' }}>
<p style={{ margin: 0, padding: 0, fontSize: 12 }}>{text}</p>
{type && (type === 'BODY') && dinamicTextField(body_params.length, handleTextChange, type, text, language)}
</div>}
{buttons && <div>{buttons.map((b) => {
const { type, text, url } = b
return <div style={{ margin: 0, padding: 0, 'marginBottom': '10px' }}>
{type && <p style={valueCssButton}>TYPE {type}</p>}
{text && <p style={valueCssButton}>{text}</p>}
{url && <p style={valueCssButton}>{url}</p>}
</div>
})} </div>}
</div>
})
}
</>
</DialogContent>
<DialogActions>
<div style={{ marginRight: '50px' }}>
<Button onClick={handleCancel}>Cancelar</Button>
</div>
<Button onClick={handleChatEnd}>Ok</Button>
</DialogActions>
</Dialog >
)
}
export default ModalTemplate

View File

@ -87,8 +87,15 @@ const NewTicketModal = ({ modalOpen, onClose }) => {
if (newValue?.number) { if (newValue?.number) {
setSelectedContact(newValue); setSelectedContact(newValue);
} else if (newValue?.name) { } else if (newValue?.name) {
setNewContact({ name: newValue.name });
setContactModalOpen(true); if (/^-?\d+(\.\d+)?$/.test(newValue?.name)) {
setNewContact({ number: newValue.name })
}
else {
setNewContact({ name: newValue.name })
}
setContactModalOpen(true)
} }
}; };

View File

@ -1,24 +1,24 @@
import React, { useState, useRef, useEffect, useContext } from "react"; import React, { useState, useRef, useEffect, useContext } from "react"
import { useHistory } from "react-router-dom"; import { useHistory } from "react-router-dom"
import { format } from "date-fns"; import { format } from "date-fns"
import openSocket from "socket.io-client"; import openSocket from "socket.io-client"
import useSound from "use-sound"; import useSound from "use-sound"
import Popover from "@material-ui/core/Popover"; import Popover from "@material-ui/core/Popover"
//import IconButton from "@material-ui/core/IconButton"; //import IconButton from "@material-ui/core/IconButton";
import List from "@material-ui/core/List"; import List from "@material-ui/core/List"
import ListItem from "@material-ui/core/ListItem"; import ListItem from "@material-ui/core/ListItem"
import ListItemText from "@material-ui/core/ListItemText"; import ListItemText from "@material-ui/core/ListItemText"
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles"
//import Badge from "@material-ui/core/Badge"; //import Badge from "@material-ui/core/Badge";
//import ChatIcon from "@material-ui/icons/Chat"; //import ChatIcon from "@material-ui/icons/Chat";
import TicketListItem from "../TicketListItem"; import TicketListItem from "../TicketListItem"
import { i18n } from "../../translate/i18n"; import { i18n } from "../../translate/i18n"
import useTickets from "../../hooks/useTickets"; import useTickets from "../../hooks/useTickets"
import alertSound from "../../assets/sound.mp3"; import alertSound from "../../assets/sound.mp3"
import { AuthContext } from "../../context/Auth/AuthContext"; import { AuthContext } from "../../context/Auth/AuthContext"
const useStyles = makeStyles(theme => ({ const useStyles = makeStyles(theme => ({
tabContainer: { tabContainer: {
@ -38,89 +38,62 @@ const useStyles = makeStyles(theme => ({
noShadow: { noShadow: {
boxShadow: "none !important", boxShadow: "none !important",
}, },
})); }))
let _fifo
// const onlineEmitter = async (socket, user) => {
// try {
// clearInterval(_fifo);
// socket.emit("online", user.id)
// } catch (error) {
// console.log('error on onlineEmitter: ', error)
// }
// finally {
// _fifo = setInterval(onlineEmitter, 3000);
// }
// }
// _fifo = setInterval(onlineEmitter, 3000);
const NotificationsPopOver = () => { const NotificationsPopOver = () => {
const classes = useStyles(); const classes = useStyles()
const history = useHistory(); const history = useHistory()
const { user } = useContext(AuthContext); const { user } = useContext(AuthContext)
const ticketIdUrl = +history.location.pathname.split("/")[2]; const ticketIdUrl = +history.location.pathname.split("/")[2]
const ticketIdRef = useRef(ticketIdUrl); const ticketIdRef = useRef(ticketIdUrl)
const anchorEl = useRef(); const anchorEl = useRef()
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false)
const [notifications, setNotifications] = useState([]); const [notifications, setNotifications] = useState([])
const [, setDesktopNotifications] = useState([]); const [, setDesktopNotifications] = useState([])
const { tickets } = useTickets({ withUnreadMessages: "true" }); const { tickets } = useTickets({ withUnreadMessages: "true" })
const [play] = useSound(alertSound); const [play] = useSound(alertSound)
const soundAlertRef = useRef(); const soundAlertRef = useRef()
const historyRef = useRef(history); const historyRef = useRef(history)
const { handleLogout } = useContext(AuthContext); const { handleLogout } = useContext(AuthContext)
// const [lastRef] = useState(+history.location.pathname.split("/")[2]) // const [lastRef] = useState(+history.location.pathname.split("/")[2])
useEffect(() => { useEffect(() => {
soundAlertRef.current = play; soundAlertRef.current = play
if (!("Notification" in window)) { if (!("Notification" in window)) {
} else { } else {
Notification.requestPermission(); Notification.requestPermission()
} }
}, [play]); }, [play])
useEffect(() => { useEffect(() => {
setNotifications(tickets); setNotifications(tickets)
}, [tickets]); }, [tickets])
useEffect(() => { useEffect(() => {
ticketIdRef.current = ticketIdUrl
}, [ticketIdUrl])
ticketIdRef.current = ticketIdUrl;
}, [ticketIdUrl]);
useEffect(() => { useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL); const socket = openSocket(process.env.REACT_APP_BACKEND_URL)
socket.on("reload_page", (data) => { socket.on("reload_page", (data) => {
if (user.id === data.userId) { if (user.id === data.userId) {
window.location.reload(true); window.location.reload(true)
} }
@ -130,18 +103,15 @@ const NotificationsPopOver = () => {
if (data.action === "logout") { if (data.action === "logout") {
if (`${user.id}` === data.userOnlineTime['userId']) { if (`${user.id}` === data.userOnlineTime['userId']) {
socket.emit("online", { logoutUserId: user.id }) socket.emit("online", { logoutUserId: user.id })
handleLogout(); handleLogout()
} }
} }
})
});
// socket.on("isOnline", (data) => { // socket.on("isOnline", (data) => {
@ -159,159 +129,145 @@ const NotificationsPopOver = () => {
if (user.profile === 'user') { if (user.profile === 'user') {
if (_fifo) { // if (_fifo) {
clearInterval(_fifo); // clearInterval(_fifo);
} // }
_fifo = setInterval(() => { // _fifo = setInterval(() => {
// socket.emit("online", user.id)
// }, 3000);
const intID = setInterval(() => {
console.log('emitting the online')
socket.emit("online", user.id) socket.emit("online", user.id)
}, 3000); }, 3000)
return () => clearInterval(intID)
} }
return () => { return () => {
socket.disconnect(); socket.disconnect()
}; }
}, [user.id, handleLogout, user.profile]); }, [user.id, handleLogout, user.profile])
useEffect(() => { useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL); const socket = openSocket(process.env.REACT_APP_BACKEND_URL)
socket.on("connect", () => socket.emit("joinNotification")); socket.on("connect", () => socket.emit("joinNotification"))
socket.on("ticket", data => { socket.on("ticket", data => {
if (data.action === "updateUnread" || data.action === "delete") { if (data.action === "updateUnread" || data.action === "delete") {
setNotifications(prevState => { setNotifications(prevState => {
const ticketIndex = prevState.findIndex(t => t.id === data.ticketId); const ticketIndex = prevState.findIndex(t => t.id === data.ticketId)
if (ticketIndex !== -1) { if (ticketIndex !== -1) {
prevState.splice(ticketIndex, 1); prevState.splice(ticketIndex, 1)
return [...prevState]; return [...prevState]
} }
return prevState; return prevState
}); })
setDesktopNotifications(prevState => { setDesktopNotifications(prevState => {
const notfiticationIndex = prevState.findIndex( const notfiticationIndex = prevState.findIndex(
n => n.tag === String(data.ticketId) n => n.tag === String(data.ticketId)
); )
if (notfiticationIndex !== -1) { if (notfiticationIndex !== -1) {
prevState[notfiticationIndex].close(); prevState[notfiticationIndex].close()
prevState.splice(notfiticationIndex, 1); prevState.splice(notfiticationIndex, 1)
return [...prevState]; return [...prevState]
} }
return prevState; return prevState
}); })
} }
}); })
socket.on("appMessage", data => { socket.on("appMessage", data => {
if ( if (
data.action === "create" && data.action === "create" &&
!data.message.read && !data.message.read &&
(data.ticket.userId === user?.id || !data.ticket.userId) (data.ticket.userId === user?.id || !data.ticket.userId)
) { ) {
setNotifications(prevState => { setNotifications(prevState => {
const ticketIndex = prevState.findIndex(t => t.id === data.ticket.id)
// prevState.forEach((e)=>{
//
// })
const ticketIndex = prevState.findIndex(t => t.id === data.ticket.id);
if (ticketIndex !== -1) { if (ticketIndex !== -1) {
prevState[ticketIndex] = data.ticket; prevState[ticketIndex] = data.ticket
return [...prevState]; return [...prevState]
} }
return [data.ticket, ...prevState]; return [data.ticket, ...prevState]
}); })
const shouldNotNotificate = (data.message.ticketId === ticketIdRef.current && document.visibilityState === "visible") || const shouldNotNotificate = (data.message.ticketId === ticketIdRef.current && document.visibilityState === "visible") ||
(data.ticket.userId && data.ticket.userId !== user?.id) || (data.ticket.userId && data.ticket.userId !== user?.id) ||
data.ticket.isGroup || !data.ticket.userId; data.ticket.isGroup || !data.ticket.userId
if (shouldNotNotificate) return; if (shouldNotNotificate) return
handleNotifications(data)
handleNotifications(data);
} }
}); })
return () => { return () => {
socket.disconnect(); socket.disconnect()
}; }
}, [user]); }, [user])
const handleNotifications = data => { const handleNotifications = data => {
const { message, contact, ticket } = data; const { message, contact, ticket } = data
const options = { const options = {
body: `${message.body} - ${format(new Date(), "HH:mm")}`, body: `${message.body} - ${format(new Date(), "HH:mm")}`,
icon: contact.profilePicUrl, icon: contact.profilePicUrl,
tag: ticket.id, tag: ticket.id,
renotify: true, renotify: true,
}; }
const notification = new Notification( const notification = new Notification(
`${i18n.t("tickets.notification.message")} ${contact.name}`, `${i18n.t("tickets.notification.message")} ${contact.name}`,
options options
); )
notification.onclick = e => { notification.onclick = e => {
e.preventDefault(); e.preventDefault()
window.focus(); window.focus()
historyRef.current.push(`/tickets/${ticket.id}`); historyRef.current.push(`/tickets/${ticket.id}`)
}; }
setDesktopNotifications(prevState => { setDesktopNotifications(prevState => {
const notfiticationIndex = prevState.findIndex( const notfiticationIndex = prevState.findIndex(
n => n.tag === notification.tag n => n.tag === notification.tag
); )
if (notfiticationIndex !== -1) { if (notfiticationIndex !== -1) {
prevState[notfiticationIndex] = notification; prevState[notfiticationIndex] = notification
return [...prevState]; return [...prevState]
} }
return [notification, ...prevState]; return [notification, ...prevState]
}); })
soundAlertRef.current(); soundAlertRef.current()
}; }
// const handleClick = () => { // const handleClick = () => {
// setIsOpen(prevState => !prevState); // setIsOpen(prevState => !prevState);
// }; // };
const handleClickAway = () => { const handleClickAway = () => {
setIsOpen(false); setIsOpen(false)
}; }
const NotificationTicket = ({ children }) => { const NotificationTicket = ({ children }) => {
return <div onClick={handleClickAway}>{children}</div>; return <div onClick={handleClickAway}>{children}</div>
}; }
return ( return (
<> <>
@ -359,7 +315,7 @@ const NotificationsPopOver = () => {
</Popover> </Popover>
</> </>
); )
}; }
export default NotificationsPopOver; export default NotificationsPopOver

Some files were not shown because too many files have changed in this diff Show More