estado funcional whatsapp api oficial

pull/21/head
adriano 2023-09-08 16:50:51 -03:00
parent 42535f2e6c
commit 6d525e4224
38 changed files with 1229 additions and 603 deletions

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

@ -17,6 +17,8 @@ import {
verifyMessage verifyMessage
} from "../services/WbotServices/wbotMessageListener"; } from "../services/WbotServices/wbotMessageListener";
import CreateOrUpdateContactService from "../services/ContactServices/CreateOrUpdateContactService"; import CreateOrUpdateContactService from "../services/ContactServices/CreateOrUpdateContactService";
import sendWhatsAppMessageOfficialAPI from "../helpers/sendWhatsAppMessageOfficialAPI";
import Whatsapp from "../models/Whatsapp";
type IndexQuery = { type IndexQuery = {
pageNumber: string; pageNumber: string;
@ -27,6 +29,7 @@ type MessageData = {
fromMe: boolean; fromMe: boolean;
read: boolean; read: boolean;
quotedMsg?: Message; quotedMsg?: Message;
mic_audio?: boolean
}; };
export const index = async (req: Request, res: Response): Promise<Response> => { export const index = async (req: Request, res: Response): Promise<Response> => {
@ -38,103 +41,18 @@ 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 }: 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);
const { queueId } = ticket; const { queueId } = ticket;
console.log("-----------> queueId: ", queueId); console.log("-----------> queueId: ", queueId, " | quotedMsg: ", quotedMsg);
// TEST FILA DE ATENDIMENTO 42 WHATSAPP OFFICIAL
if (queueId == 42) {
const { contactId } = ticket;
const contact: any = await Contact.findByPk(contactId);
const { number } = contact;
console.log("NUMBER: ", number);
console.log("CONTACT ID: ", contactId);
const data = {
messaging_product: "whatsapp",
recipient_type: "individual",
to: number,
type: "text",
text: {
preview_url: true,
body
}
};
if (!isValidMsg({ type: data.type })) {
return res.status(400).json({ message: "Wrong message type" });
}
whatsappOfficialAPI
.post("/v17.0/105394365522185/messagess", data)
.then(response => {
console.log("Response:", response.data);
if (response.status == 200) {
console.log("STATUS 200");
}
let msg = {};
msg = {
...msg,
id: { id: response.data.messages[0].id },
fromMe: true,
type: "chat",
read: false,
body
};
verifyMessage(msg, ticket, contact, quotedMsg);
})
.catch(error => {
console.log(
"Error on try request: ",
error.response.data.error.message
);
// return res
// .status(500)
// .json({ error: error.response.data.error.message });
if (error?.response?.data?.error?.message && error.response?.status) {
}
if (error.response) {
// The request was made and the server responded with a non-2xx status code
throw new Error(
`Request failed with status ${error.response.status}`
);
} else if (error.request) {
// The request was made but no response was received (e.g., network error)
throw new Error("No response received from the server");
} else {
// Something happened in setting up the request that triggered an error
throw new Error("Request configuration error");
}
});
// return res.status(500).json({ error: "Internal server error" });
return res.send();
}
// ORIGINAL
if (medias) { if (medias) {
await Promise.all( await Promise.all(
medias.map(async (media: Express.Multer.File) => { medias.map(async (media: Express.Multer.File) => {
@ -153,8 +71,7 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
media, media,
"\n" "\n"
); );
await SendWhatsAppMedia({ media, ticket, mic_audio });
await SendWhatsAppMedia({ media, ticket });
}) })
); );
} else { } else {

View File

@ -103,7 +103,8 @@ 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;
let ticket = await Ticket.findOne({ let ticket = await Ticket.findOne({
where: { where: {
@ -120,7 +121,13 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
ticketId: ticket.id ticketId: ticket.id
}); });
} else { } else {
ticket = await CreateTicketService({ contactId, status, userId, queueId }); ticket = await CreateTicketService({
contactId,
status,
userId,
queueId,
whatsappId
});
} }
const io = getIO(); const io = getIO();
@ -128,15 +135,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);
}; };
@ -243,9 +241,9 @@ 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 defaultWhatsapp: any = await GetDefaultWhatsApp({
ticketData.userId userId: ticketData.userId
); });
const _ticket: any = await Ticket.findByPk(ticketId); const _ticket: any = await Ticket.findByPk(ticketId);
@ -276,8 +274,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

@ -16,19 +16,25 @@ 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 Message from "../models/Message";
import FindOrCreateTicketService from "../services/TicketServices/FindOrCreateTicketService"; import FindOrCreateTicketService from "../services/TicketServices/FindOrCreateTicketService";
import { import {
handleMessage,
handleMsgAck, handleMsgAck,
verifyContact, verifyContact,
verifyMessage verifyMessage
} from "../services/WbotServices/wbotMessageListener"; } from "../services/WbotServices/wbotMessageListener";
import Contact from "../models/Contact"; import Contact from "../models/Contact";
import CreateOrUpdateContactService from "../services/ContactServices/CreateOrUpdateContactService"; 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";
interface WhatsappData { interface WhatsappData {
name: string; name: string;
@ -47,12 +53,113 @@ export const index = async (req: Request, res: Response): Promise<Response> => {
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;
const whatsapps = await GetDefaultWhatsApp({ userId, queueId });
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 ( export const weebhook = async (
req: Request, req: Request,
res: Response res: Response
): Promise<Response> => { ): Promise<Response> => {
// console.log(JSON.stringify(req.body, null, 2)); // 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 // MESSAGE
if (req.body.object) { if (req.body.object) {
if ( if (
@ -62,37 +169,71 @@ export const weebhook = async (
req.body.entry[0].changes[0].value.messages && req.body.entry[0].changes[0].value.messages &&
req.body.entry[0].changes[0].value.messages[0] req.body.entry[0].changes[0].value.messages[0]
) { ) {
const contact_from = req.body.entry[0].changes[0].value.messages[0].from; // extract the phone number from the webhook payload const message = req.body.entry[0].changes[0].value.messages[0];
const msg_body = req.body.entry[0].changes[0].value.messages[0].text.body; // extract the message text from the webhook payload const contact_from = message.from; // extract the phone number from the webhook payload
const type = req.body.entry[0].changes[0].value.messages[0].type;
const contact_to = const contact_to =
req.body.entry[0].changes[0].value.metadata.display_phone_number; req.body.entry[0].changes[0].value.metadata.display_phone_number;
let type = message.type;
console.log("from: ", contact_from); let wbot = {};
console.log("to: ", contact_to);
console.log("msg_body: ", msg_body);
console.log("msg type: ", type);
const contact: any = await verifyContact(null, { number: contact_from });
const whatsapp: any = await Whatsapp.findOne({
where: { number: contact_to }
});
const ticket = await FindOrCreateTicketService(contact, whatsapp.id, 1);
let msg = {}; let msg = {};
let contacts = req.body.entry[0].changes[0].value.contacts[0];
msg = { msg = {
...msg, ...msg,
id: { id: req.body.entry[0].changes[0].value.messages[0].id }, id: { id: message.id },
fromMe: false, fromMe: false,
type: type === "text" ? "chat" : type, type: type,
read: false, read: false,
body: req.body.entry[0].changes[0].value.messages[0].text.body hasMedia: false
}; };
await verifyMessage(msg, ticket, contact, undefined); // 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,
};
} 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 } };
}
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); return res.sendStatus(200);
} }
@ -266,5 +407,3 @@ export const remove = async (
return res.status(200).json({ message: "Whatsapp deleted." }); return res.status(200).json({ message: "Whatsapp deleted." });
}; };

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,13 +2,13 @@ import { QueryInterface, DataTypes } from "sequelize";
module.exports = { module.exports = {
up: (queryInterface: QueryInterface) => { up: (queryInterface: QueryInterface) => {
return queryInterface.addColumn("Whatsapps", "official", { return queryInterface.addColumn("Tickets", "phoneNumberId", {
type: DataTypes.BOOLEAN, type: DataTypes.STRING,
defaultValue: false allowNull: true
}); });
}, },
down: (queryInterface: QueryInterface) => { down: (queryInterface: QueryInterface) => {
return queryInterface.removeColumn("Whatsapps", "official"); return queryInterface.removeColumn("Tickets", "phoneNumberId");
} }
}; };

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,3 @@
export function bytesToMB(bytes: number | string) {
return (+bytes / (1024 * 1024)).toFixed(2);
}

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,66 @@
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 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 > 1) {
let whatsAppOfficial: any = whatsapps.find(
(w: any) => w.phoneNumberId != null
);
if (whatsAppOfficial) {
return whatsapps;
}
}
}
if (whatsapps.length > 0) { if (whatsapps.length > 0) {
if (whatsapps.length > 1) { if (whatsapps.length > 1) {
defaultWhatsapp = whatsapps[+WhatsIndex(whatsapps)];
defaultWhatsapp = whatsapps[+WhatsIndex(whatsapps)] } else {
defaultWhatsapp = whatsapps[0];
} }
else {
defaultWhatsapp = whatsapps[0]
}
} // Quando o usuário não está em nenhuma fila } // Quando o usuário não está em nenhuma fila
else { else {
defaultWhatsapp = await Whatsapp.findOne({ where: { status: 'CONNECTED' } }); 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 +69,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;
status?: string;
queueId?: string | number;
}
let defaultWhatsapp: Whatsapp[] = [] const wbotByUserQueue = async ({
userId,
status = "CONNECTED",
queueId
}: Request): Promise<any> => {
let defaultWhatsapp: Whatsapp[] = [];
try { try {
let query: any = {};
const queue = await UserQueue.findOne( query = { userId };
{
where: { userId: userId }, if (queueId) {
query = { ...query, queueId };
}
const queue = await UserQueue.findOne({
where: query,
raw: true, raw: true,
attributes: ['queueId'] attributes: ["queueId"]
}); });
if (queue?.queueId) { if (queue?.queueId) {
// Pega todas conexões de whatsaap que estão adicionadas à uma fila // Pega todas conexões de whatsaap que estão adicionadas à uma fila
const whatsappQueues = await WhatsappQueue.findAll( const whatsappQueues = await WhatsappQueue.findAll({
{
where: { queueId: `${queue?.queueId}` }, where: { queueId: `${queue?.queueId}` },
raw: true, raw: true,
attributes: ['whatsappId'] attributes: ["whatsappId"]
}); });
defaultWhatsapp = await Whatsapp.findAll({ defaultWhatsapp = await Whatsapp.findAll({
where: { where: {
id: {[Op.in]: whatsappQueues.map((w) => { return w.whatsappId })}, id: {
[Op.in]: whatsappQueues.map(w => {
return w.whatsappId;
})
},
status: status status: status
} }
}); });
} }
} catch (err) { } catch (err) {
console.log('There was an error on select a whatsapp id by user queue: ', err) console.log(
"There was an error on select a whatsapp id by user queue: ",
err
);
} }
return defaultWhatsapp; return defaultWhatsapp;
};
}
export default wbotByUserQueue; export default wbotByUserQueue;

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

@ -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", { io.to(`session_${ticket.whatsappId.toString()}`).emit("send_message", {
action: "create", action: "create",
msg: { msg: {
number: number ? number : `${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us`, number: number
? number
: `${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us`,
body: body, body: body,
quotedMessageId: quotedMsgSerializedId, quotedMessageId: quotedMsgSerializedId,
linkPreview: false linkPreview: false
} }
}); });
} }
export default sendWhatsAppMessageSocket; export default sendWhatsAppMessageSocket;

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

@ -1,13 +1,10 @@
import axios from "axios"; import axios from "axios";
const token =
"EAADwEiQkJqABOZBOuvnVZAywgKUw8wPETCcleXWDqKN3X2W1LZC5UMEnSjBIVjBavZBxwVTD4WnpDmoEFN9HZCOt842XZCTSPm1ShnnUB2iJca3nZC6gS8GLIKqP78kkW5EtllhWrg4I8JnbllrLNKa2B066Wwh0G3uySYgcA7WnKyZAmPOGEZB8UiljBaqhg";
const api = axios.create({ const api = axios.create({
baseURL: "https://graph.facebook.com", baseURL: process.env.URL_WHATSAPP_API,
headers: { headers: {
Accept: "application/json", Accept: "application/json",
Authorization: `Bearer ${token}` Authorization: `Bearer ${process.env.TOKEN}`
} }
}); });

View File

@ -0,0 +1,65 @@
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
) {
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,
type: "text",
text: {
preview_url: true,
body
}
};
if (quotedMsgSerializedId) {
data = { ...data, context: { message_id: quotedMsgSerializedId.id } };
}
if (!isValidMsg({ type: data.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 },
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

@ -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

@ -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

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

View File

@ -9,8 +9,22 @@ 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.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

@ -43,13 +43,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

@ -23,6 +23,7 @@ const CreateMessageService = async ({
}: Request): Promise<Message> => { }: 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, {

View File

@ -15,6 +15,7 @@ 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";
let flatten = require("flat"); let flatten = require("flat");
interface Request { interface Request {
@ -22,18 +23,29 @@ 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 });
}
// 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 });
@ -56,7 +68,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,7 +13,7 @@ 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;
@ -45,7 +45,7 @@ const FindOrCreateTicketService = async (
}); });
} }
const { queues, greetingMessage } = await ShowWhatsAppService(whatsappId); const { queues, greetingMessage, phoneNumberId } = await ShowWhatsAppService(whatsappId);
const botInfo = { isOnQueue: false }; const botInfo = { isOnQueue: false };
@ -106,7 +106,8 @@ const FindOrCreateTicketService = async (
status: status, status: status,
isGroup: !!groupContact, isGroup: !!groupContact,
unreadMessages, unreadMessages,
whatsappId whatsappId,
phoneNumberId
}); });
} }

View File

@ -48,7 +48,6 @@ const UpdateTicketService = async ({
} = ticketData; } = ticketData;
const ticket = await ShowTicketService(ticketId); const ticket = await ShowTicketService(ticketId);
// await SetTicketMessagesAsRead(ticket);
const oldStatus = ticket.status; const oldStatus = ticket.status;
const oldUserId = ticket.user?.id; const oldUserId = ticket.user?.id;

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

@ -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,11 +8,14 @@ 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";
@ -26,14 +29,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 +44,109 @@ 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 &&
listWhatsapp.whatsapp.status != "CONNECTED" &&
listWhatsapp.whatsapps.length > 0
) {
await ticket.update({ whatsappId: +listWhatsapp.whatsapps[0].id }); 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 &&
listWhatsapp.whatsapp.status != "CONNECTED"
) {
console.log("listWhatsapp.whatsapps == 0");
console.log('listWhatsapp.whatsapps == 0') whatsapps = await wbotByUserQueue({ userId: ticket.userId });
whatsapps = await wbotByUserQueue(ticket.userId) console.log("============> The whatsapps: ", whatsapps);
console.log('============> The whatsapps: ', whatsapps)
if (whatsapps.length > 0) { if (whatsapps.length > 0) {
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 {
else {
await ticket.update({ whatsappId: whatsapps[0].id }); await ticket.update({ whatsappId: whatsapps[0].id });
}
}
} }
} 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

@ -110,7 +110,9 @@ const verifyContact = async (
}; };
} else { } else {
contactData = { contactData = {
name: whatsAppOfficial.number, name: whatsAppOfficial?.name
? whatsAppOfficial.name
: whatsAppOfficial.number,
number: whatsAppOfficial.number, number: whatsAppOfficial.number,
isGroup: false isGroup: false
}; };
@ -151,6 +153,19 @@ const verifyMediaMessage = async (
throw new Error("ERR_WAPP_DOWNLOAD_MEDIA"); throw new Error("ERR_WAPP_DOWNLOAD_MEDIA");
} }
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
};
if (!ticket?.phoneNumberId) {
if (!media.filename) { if (!media.filename) {
const ext = media.mimetype.split("/")[1].split(";")[0]; const ext = media.mimetype.split("/")[1].split(";")[0];
media.filename = `${new Date().getTime()}.${ext}`; media.filename = `${new Date().getTime()}.${ext}`;
@ -166,19 +181,7 @@ const verifyMediaMessage = async (
Sentry.captureException(err); Sentry.captureException(err);
logger.error(`There was an error: wbotMessageLitener.ts: ${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 }); await ticket.update({ lastMessage: msg.body || media.filename });
const newMessage = await CreateMessageService({ messageData }); const newMessage = await CreateMessageService({ messageData });
@ -200,7 +203,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,
phoneNumberId: msg?.phoneNumberId
}; };
await ticket.update({ lastMessage: msg.body }); await ticket.update({ lastMessage: msg.body });
@ -273,9 +277,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
@ -302,14 +303,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
); );
@ -318,6 +318,51 @@ const verifyQueue = async (
} }
}; };
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 => { const isValidMsg = (msg: any): boolean => {
if (msg.from === "status@broadcast") return false; if (msg.from === "status@broadcast") return false;
if ( if (
@ -363,43 +408,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);
@ -414,8 +435,12 @@ 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,
wbot: any,
whatsAppOfficial?: any
): Promise<void> => {
if (!msg.fromMe && !whatsAppOfficial) {
_clear_lst(); _clear_lst();
let index = lst.findIndex((x: any) => x.id == msg.id.id); let index = lst.findIndex((x: any) => x.id == msg.id.id);
@ -502,7 +527,12 @@ const handleMessage = async (msg: any, wbot: any): Promise<void> => {
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)
: await verifyContact(null, {
number: wbot.msgContact.number,
name: wbot.msgContact.name
});
if ( if (
unreadMessages === 0 && unreadMessages === 0 &&
@ -653,6 +683,11 @@ const handleMsgAck = async (
); );
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", {
@ -685,6 +720,8 @@ export {
handleMsgAck, handleMsgAck,
lst, lst,
verifyMessage, verifyMessage,
verifyMediaMessage,
verifyContact, verifyContact,
isValidMsg, isValidMsg,
mediaTypeWhatsappOfficial
}; };

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,
whatsAppOfficial?: any
): Promise<Whatsapp> => {
let whatsapp;
if (id) {
whatsapp = await Whatsapp.findByPk(id, {
include: [ include: [
{ {
model: Queue, model: Queue,
as: "queues", as: "queues",
attributes: ["id", "name", "color", "greetingMessage"] attributes: [
"id",
"name",
"color",
"greetingMessage",
]
} }
], ],
order: [["queues", "id", "ASC"]] order: [["queues", "id", "ASC"]]
}); });
} else {
// console.log('kkkkkkkkkkkkkkkkkkkk: ', whatsapp) 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

@ -1,27 +1,30 @@
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 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 { WhatsAppsContext } from "../../context/WhatsApp/WhatsAppsContext"
import api from "../../services/api"
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
maxWidth: { maxWidth: {
@ -33,30 +36,64 @@ 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)
const { whatsApps } = useContext(WhatsAppsContext)
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 }, })
console.log('DATA: ', data)
setQueues(data)
} catch (err) {
console.log(err)
}
}
fetchMatchQueueUserOfficialWhatsapp()
}, 500)
return () => clearTimeout(delayDebounceFn)
}, [user])
const handleClose = () => { const handleClose = () => {
onClose(); onClose()
}; }
const handleSaveTicket = useCallback(async (contactId, userId, queueId, selectedWhatsId, whatsQueue, queues) => {
console.log(`contactId: ${contactId}, userId: ${userId}, queueId: ${queueId}, selectedWhatsId: ${selectedWhatsId}, whatsQueue: ${whatsQueue}, queues: ${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 +102,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,33 +117,80 @@ 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 (modalOpen && queues.length <= 1) {
// handleSaveTicket(contactId, user.id, selectedQueue, selectedWhatsId, whatsQueue, queues)
// }
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 }, })
setWhatsQueue(data)
setDisabled(false)
} catch (err) {
console.log(err)
}
}
fetChMatchQueueOfficialWhatsapp()
}, 500)
return () => clearTimeout(delayDebounceFn)
}, [selectedQueue, user.id])
// if (modalOpen && queues.length <= 1) {
// return <LinearProgress />
// }
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 }}>
<DialogTitle id="form-dialog-title"> <DialogTitle id="form-dialog-title">
@ -117,9 +205,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 +220,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 +255,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

@ -388,6 +388,7 @@ const MessageInput = ({ ticketStatus }) => {
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) {

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

@ -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,7 +38,7 @@ const useStyles = makeStyles(theme => ({
noShadow: { noShadow: {
boxShadow: "none !important", boxShadow: "none !important",
}, },
})); }))
let _fifo let _fifo
@ -64,63 +64,58 @@ let _fifo
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)
} }
@ -133,13 +128,12 @@ const NotificationsPopOver = () => {
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) => {
@ -157,159 +151,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 (
<> <>
@ -357,7 +337,7 @@ const NotificationsPopOver = () => {
</Popover> </Popover>
</> </>
); )
}; }
export default NotificationsPopOver; export default NotificationsPopOver